Dispatch (Sling)
Last verified against code: 2026-03-01
Summary
Dispatch is Gas City’s work routing mechanism — a Layer 2-4 derived mechanism that composes primitives (Agent Protocol, Bead Store, Event Bus, Config) to route work to agents. Thegc sling command resolves a target
agent or pool, optionally instantiates a formula as a wisp, executes the
agent’s sling query to route each bead, optionally wraps single beads in
a tracking convoy, records telemetry, and nudges the target. Convoys are
expanded to their open children before routing.
Key Concepts
-
Sling: The act of routing a bead to an agent or pool by executing
the target’s sling query. The sling query is a shell command template
with
{}as a placeholder for the bead ID. Implemented incmd/gc/cmd_sling.go. -
Sling Query: A shell command template on each agent config
(
sling_query) that routes a bead to that agent. Defaults tobd update {} --assignee=<qualified-name>for fixed agents andbd update {} --label=pool:<qualified-name>for pool agents. The{}placeholder is replaced with the actual bead ID at dispatch time. Defined ininternal/config/config.go:EffectiveSlingQuery. - Container Expansion: When a convoy is slung, dispatch expands it to its open children and routes each child individually. Non-open children are skipped. Epics are ordinary beads and are not expanded. The container itself becomes the convoy — no auto-convoy is created.
-
Auto-Convoy: When slinging a single bead (not a formula, not a
container), dispatch automatically wraps it in a new convoy bead for
batch tracking. Suppressed with
--no-convoy. -
Wisp Instantiation: When
--formulais set, dispatch creates an ephemeral molecule (wisp) from the named formula viaStore.MolCookand routes the wisp’s root bead to the target. Variable substitution and custom titles are supported. -
Target Resolution: The 2-step resolution of agent names — first
literal match against qualified names, then contextual match using the
current rig directory. Implemented in
cmd/gc/cmd_agent.go:resolveAgentIdentity. -
System Formula: A formula embedded in the
gcbinary that is materialized to.gc/system-formulas/at startup. System formulas are always overwritten to stay in sync with the binary version. Stale files are cleaned up. Implemented incmd/gc/system_formulas.go.
Architecture
Dispatch is not a separate Go package. It is a composition of primitives orchestrated bycmd/gc/cmd_sling.go. The dispatch pipeline has three
layers:
Data Flow
Single bead dispatch (gc sling <agent> <bead-id>):
cmdSlingresolves the city path, loads config, and resolves the target agent viaresolveAgentIdentity.doSlingBatchchecks if the bead is a container type. If not, falls through todoSling.doSlingwarns about suspended agents or empty pools (unless--force).- If
--formula, callsinstantiateWispwhich delegates toStore.MolCookto create the wisp and uses the root bead ID. - Pre-flight: warns if the bead already has an assignee or pool labels
(unless
--force). - Builds the sling command by replacing
{}in the agent’sEffectiveSlingQuery()with the bead ID. - Executes the sling command via
SlingRunner(shellsh -c). - Records telemetry via
telemetry.RecordSling. - If
--mergeis set, writes the merge strategy as bead metadata. - If auto-convoy is enabled (not
--no-convoy, not--formula), creates a convoy bead and sets the routed bead’s ParentID to the convoy. - If
--nudge, sends a nudge to the target agent.
gc sling <agent> <convoy-id>):
doSlingBatchlooks up the bead and checksIsContainerType.- Lists all children via
querier.Children. - Partitions children into open (routable) and skipped (non-open).
- Routes each open child individually through
buildSlingCommand+runner. No auto-convoy is created — the container IS the convoy. - Reports per-child success/failure and a summary line.
- Nudges once after all children are routed (if
--nudgeand at least one succeeded).
Key Types
-
SlingOpts(cmd/gc/cmd_sling.go) — All flags for the sling command:IsFormula,DoNudge,Force,Title,Vars,Merge,NoConvoy,Owned. -
SlingRunner(cmd/gc/cmd_sling.go) — Function typefunc(command string) (string, error)that executes the sling shell command. Injectable for testing. -
BeadQuerier(cmd/gc/cmd_sling.go) — Interface for retrieving a single bead by ID. Used for pre-flight checks. -
BeadChildQuerier(cmd/gc/cmd_sling.go) — ExtendsBeadQuerierwithChildren(parentID)for container expansion. -
config.Agent(internal/config/config.go) — Carries theSlingQueryfield andEffectiveSlingQuery()method that determines how beads are routed to this agent.
Invariants
-
Sling query placeholder is always
{}. ThebuildSlingCommandfunction performs literal string replacement of all{}occurrences with the bead ID. No other placeholder syntax is supported. -
Container expansion routes only open children. Children with
status other than
"open"are skipped and reported, never routed. -
Auto-convoy is suppressed for formulas and containers. When
--formulais set or the target bead is a container type, no auto-convoy is created. Formulas have their own molecule structure; containers are their own convoy. -
--ownedrequires a convoy. The--ownedand--no-convoyflags are mutually exclusive. The CLI rejects the combination before dispatch begins. -
Merge strategy is one of three values.
--mergeaccepts only"direct","mr", or"local". The CLI validates before dispatch. - Pre-flight warnings are best-effort. If the bead store query fails, dispatch proceeds silently. Warnings never block routing.
-
Telemetry records every dispatch attempt.
RecordSlingis called on both success and failure paths with the target name, target type ("agent"or"pool"), method ("bead","formula", or"batch"), and error status. - Pool nudge targets the first running instance. When nudging a pool, dispatch iterates pool instances in order and nudges the first one with a running session. If none are running, a warning is emitted.
-
System formulas are idempotent.
MaterializeSystemFormulasalways overwrites files to match the binary version and removes stale formula files that are no longer embedded. Non-formula files in the directory are left alone. -
Default sling queries differ by agent type. Fixed agents default
to
bd update {} --assignee=<name>; pool agents default tobd update {} --label=pool:<name>. Customsling_queryoverrides the default entirely.
Interactions
| Depends on | How |
|---|---|
internal/beads (Store) | MolCook for wisp instantiation, Create for auto-convoy, Get/Children for container expansion, Update for ParentID linking, SetMetadata for merge strategy |
internal/config | Agent resolution, EffectiveSlingQuery, pool detection via IsPool, PoolConfig for sizing, Suspended flag |
internal/runtime | Provider.IsRunning and Provider.Nudge for agent nudging via doSlingNudge |
internal/agent | SessionNameFor to compute session names, agent.New + Nudge to deliver nudge text |
internal/telemetry | RecordSling for metrics and log events on every dispatch |
cmd/gc/cmd_agent.go | resolveAgentIdentity for 2-step target resolution (literal then contextual) |
| Depended on by | How |
|---|---|
cmd/gc/cmd_convoy.go | Convoys are the batch tracking containers that dispatch creates and expands |
internal/orders | Order dispatch creates wisps and routes them through the same formula instantiation path (Store.MolCook) |
cmd/gc/cmd_handoff.go | Work handoff between agents uses similar agent resolution and bead routing patterns |
| Controller | The controller’s reconciliation loop drives pool sizing via evaluatePool which determines how many pool instances exist to receive slung work |
Code Map
| Path | Description |
|---|---|
cmd/gc/cmd_sling.go | CLI command, SlingOpts, doSling, doSlingBatch, buildSlingCommand, instantiateWisp, checkBeadState, doSlingNudge |
cmd/gc/cmd_sling_test.go | Unit tests: command building, single-bead dispatch, formula dispatch, container expansion, nudge behavior, merge strategy, auto-convoy, pre-flight warnings |
cmd/gc/cmd_convoy.go | Convoy CRUD: create, list, status, add, close, check (auto-close), stranded, autoclose (hidden hook) |
cmd/gc/system_formulas.go | MaterializeSystemFormulas, ListEmbeddedSystemFormulas, stale file cleanup |
cmd/gc/system_formulas_test.go | Tests for materialization: empty FS, write, overwrite, stale cleanup, idempotency, orders |
cmd/gc/pool.go | evaluatePool (scale check), poolAgents (instance expansion), expandSessionSetup (template context) |
internal/config/config.go | Agent.SlingQuery, Agent.EffectiveSlingQuery(), Agent.EffectiveWorkQuery(), Agent.IsPool() |
internal/beads/beads.go | IsContainerType, Store.MolCook, Store.Children, Store.SetMetadata |
internal/beads/bdstore.go | BdStore.MolCook and BdStore.MolCookOn — formula-backed wisp instantiation via bd mol wisp / bd mol bond |
internal/telemetry/recorder.go | RecordSling — metrics counter + structured log event for each dispatch |
cmd/gc/cmd_agent.go | resolveAgentIdentity — 2-step agent name resolution |
Configuration
The dispatch mechanism is configured through agent-level fields incity.toml:
gc binary and materialized to
.gc/system-formulas/ at startup. They form the lowest-priority formula
layer (Layer 0) in the formula resolution stack. Pack and city-level
formulas override system formulas by name.
Testing
Dispatch testing follows the philosophy in TESTING.md, relying heavily on injected fakes: Unit tests (cmd/gc/cmd_sling_test.go): All dispatch logic is tested
through doSling and doSlingBatch with injected fakeRunner (records
shell commands), session.NewFake() (fake session provider), and
beads.NewMemStore() (in-memory bead store). Tests cover:
buildSlingCommandplaceholder substitution including multiple{}- Single-bead dispatch to fixed agents and pools
- Formula dispatch with
--formulaflag (wisp instantiation) - Container expansion: convoy beads expand to open children; epics are rejected
- Merge strategy metadata (
--merge=direct,--merge=mr,--merge=local) - Auto-convoy creation and suppression (
--no-convoy) - Owned convoy labeling (
--owned) - Pre-flight warnings for already-assigned beads and pool-labeled beads
- Suspended agent and empty pool warnings
- Nudge delivery to fixed agents and first running pool member
- Error paths: runner failure, MolCook failure, store failure
cmd/gc/system_formulas_test.go): Cover
materialization from embedded FS including empty FS (no-op), file
writing, overwrite semantics, stale file cleanup, idempotency, and
order subdirectory support.
Config tests (internal/config/config_test.go):
TestEffectiveSlingQuery* tests verify default sling query generation
for fixed agents, rig-scoped agents, pool agents, and custom overrides.
TestValidatePoolWorkQueryMismatch verifies that pool agents must set
both sling_query and work_query together or neither.
Known Limitations
-
Sling query is a shell command, not a Go function call. Every
dispatch forks a shell process via
sh -c. This is simple and flexible (any CLI tool can be a routing backend) but adds per-bead fork overhead during batch expansion of large containers. - Container expansion is serial. When expanding a convoy, each child is slung sequentially. A single slow or failing sling command blocks subsequent children. Partial success is reported but not retried.
-
No built-in load balancing across pool instances. Sling routes to
the pool as a whole (via label), not to a specific instance. Work
distribution depends on the pool’s work query and claim semantics
(
bd ready --label=pool:<name> --limit=1), which is first-come first-served. -
Nudge targets only one pool instance. After slinging to a pool,
--nudgewakes the first running instance found. Other instances discover work on their next poll cycle. -
No dry-run mode. There is no way to preview what a sling command
would do without actually executing it. The pre-flight
checkBeadStateonly warns; it does not prevent routing.
See Also
- Architecture glossary — authoritative definitions of sling, convoy, wisp, formula, and other terms used in this document
- Bead Store architecture — the persistence substrate that dispatch reads and writes through, including MolCook molecule instantiation
- Health Patrol architecture — the supervision model that keeps pool agents alive to receive dispatched work
- Config architecture — how agent configuration (sling_query, pool, suspended) drives dispatch behavior
- CLAUDE.md — design principles including “the controller drives all SDK infrastructure operations” (layering invariant 6)
- Formula file reference — formula structure, layer resolution, and wisp instantiation inputs
- TESTING.md — testing philosophy and tier boundaries for the fake-injection approach used in dispatch tests