Orders
Last verified against code: 2026-03-01
Summary
Orders are Gas City’s derived mechanism (Layer 2-4, part of Formulas & Molecules) for scheduled and event-driven work dispatch without human intervention. Each order pairs a gate condition (when to fire) with an action (a shell script or formula wisp), living as anorder.toml file inside formula directories. The controller
evaluates all non-manual gates on every patrol tick and dispatches due
orders — exec orders run shell scripts directly with no LLM
involvement, while formula orders instantiate wisps dispatched to
agent pools.
Key Concepts
-
Order: A parsed definition from an
order.tomlfile with a Name (derived from subdirectory name), a dispatch action (Formula or Exec, mutually exclusive), a Gate type, gate-specific parameters, and optional Pool routing. Defined in theOrderstruct atinternal/orders/order.go. -
Gate: The trigger condition that controls when an order fires.
Five types exist:
cooldown(minimum interval since last run),cron(5-field schedule matching),condition(shell command exits 0),event(matching events after a cursor position), andmanual(explicit invocation only, never auto-fires). Seeinternal/orders/gates.go. -
Exec Order: An order whose action is a shell command
(
execfield) run directly by the controller. No LLM, no agent, no wisp. The script receivesORDER_DIRin its environment, set to the directory containing theorder.tomlfile. Default timeout: 60 seconds. -
Formula Order: An order whose action is a formula name
(
formulafield). When the gate opens, the controller callsMolCookto instantiate a wisp and labels it for pool dispatch. Default timeout: 30 seconds. -
ScopedName: A rig-qualified key that creates unique identity for
orders across rigs. City-level orders use the plain name
(e.g.,
dolt-health). Rig-level orders append:rig:<rigName>(e.g.,dolt-health:rig:demo-repo). ScopedName drives independent cooldown tracking, event cursors, and label scoping. -
Formula Layer: A directory scanned for
orders/*/order.toml. Layers are ordered lowest to highest priority; a higher-priority layer’s order definition overrides a lower-priority one with the same subdirectory name (last-wins semantics). -
Tracking Bead: A bead created synchronously before each dispatch
goroutine launches, labeled
order-run:<scopedName>. Serves dual purpose: prevents the cooldown gate from re-firing on the next tick, and provides execution history forgc order history.
Architecture
The order subsystem spans two packages:internal/orders/— parsing, validation, scanning, and gate evaluation. Pure library code with no side effects beyond shell command execution for condition gates.cmd/gc/— controller-side dispatch (order_dispatch.go) and CLI commands (cmd_order.go). Wires the library into the controller loop and thegc ordercommand tree.
Data Flow
Discovery (on controller start and config reload):buildOrderDispatcher()resolves city-level formula layers viacityFormulaLayers()and callsorders.Scan()to find city orders.- For each rig,
rigExclusiveLayers()strips the city prefix from the rig’s formula layers to avoid double-scanning city orders. The remaining rig-exclusive layers are scanned separately. - Rig orders get their
Rigfield stamped with the rig name. - Manual-gate orders are filtered out (they never auto-dispatch).
- If no auto-dispatchable orders remain, the dispatcher is nil (nil-guard pattern — callers check before use).
dispatch()iterates all non-manual orders.CheckGate()evaluates the gate condition against current time, last-run history (from bead store), and event state (from event bus).- For each due order, a tracking bead is created synchronously
with label
order-run:<scopedName>. This is critical: it prevents the cooldown gate from re-firing on the next tick. - A goroutine calls
dispatchOne()with a context timeout derived fromeffectiveTimeout()(per-order timeout capped by globalmax_timeout). dispatchOne()records anorder.firedevent, then branches:- Exec:
dispatchExec()runs the shell command viaExecRunner, labels the tracking bead withexec(orexec-failed), and recordsorder.completedororder.failed. - Formula:
dispatchWisp()callsinstantiateWisp()(which delegates tostore.MolCook()), labels the wisp root bead withorder-run:<scopedName>andpool:<qualifiedPool>, and recordsorder.completedororder.failed.
- Exec:
orders.Scan()):
- For each formula layer (ordered lowest to highest priority), read
<layer>/orders/*/order.toml. - Parse each TOML file into an
Orderstruct. SetNamefrom the subdirectory name,Sourcefrom the absolute file path. - Higher-priority layers overwrite lower ones by name (map keyed on subdirectory name).
- Exclude disabled orders (
enabled = false) and those in theskiplist. - Return the result slice preserving discovery order.
Key Types
-
Order(internal/orders/order.go): The parsed order definition. Fields: Name, Description, Formula, Exec, Gate, Interval, Schedule, Check, On, Pool, Timeout, Enabled, Source, Rig. -
GateResult(internal/orders/gates.go): The outcome of a gate check. Fields: Due (bool), Reason (human-readable), LastRun (time.Time). -
orderDispatcher(cmd/gc/order_dispatch.go): Interface with a single methoddispatch(ctx, cityPath, now). Production implementation ismemoryOrderDispatcher. -
memoryOrderDispatcher(cmd/gc/order_dispatch.go): Holds the scanned order list, bead store, events provider, command runner, exec runner, events recorder, stderr writer, and max timeout. -
ExecRunner(cmd/gc/order_dispatch.go): Function typefunc(ctx, command, dir string, env []string) ([]byte, error)for running shell commands. Production implementationshellExecRunnerusesos/exec.
Invariants
These properties must hold for the order subsystem to be correct. Violations indicate bugs.-
Formula XOR Exec: Every order has exactly one of
formulaorexecset.Validate()rejects orders with both or neither. -
Exec orders have no pool: An exec order runs a shell
script directly on the controller. It has no agent pipeline and
therefore no pool.
Validate()rejectsexec+poolcombinations. -
Gate type requires matching parameters: A
cooldowngate requiresinterval, acrongate requiresschedule, aconditiongate requirescheck, aneventgate requireson.Validate()enforces these per-gate-type constraints. -
Tracking beads are created before dispatch goroutines: The tracking
bead (labeled
order-run:<scopedName>) is created synchronously in the main dispatch loop. This prevents the cooldown gate from re-firing on the next controller tick while the dispatch goroutine is still running. -
ScopedName provides rig isolation: The same order name
deployed to multiple rigs produces independent scoped names (e.g.,
dolt-health:rig:rig-avsdolt-health:rig:rig-b). Cooldown tracking, event cursors, and history queries all use ScopedName. Firing one rig’s order does not affect another rig’s gate evaluation. -
Higher-priority layers override lower by name: When the same
order subdirectory name exists in multiple formula layers,
Scan()uses the definition from the highest-priority layer (last in the layers slice). The override is total (the entire TOML definition replaces the lower one). -
Manual gates never auto-fire:
CheckGate()for amanualgate always returnsDue: false. Manual orders are filtered out of the dispatcher entirely during build. They can only be triggered viagc order run. -
Disabled orders are excluded from scan results:
Scan()filters out orders withenabled = false. They do not appear in any CLI command output or dispatch evaluation. -
Cron gate fires at most once per minute: After matching the 5-field
schedule,
checkCron()verifies the last run was not in the same truncated minute. This prevents duplicate fires within a single cron window. -
Event gate uses cursor-based deduplication: Event orders track
the highest processed event sequence number via
seq:<N>labels on wisp beads. Subsequent gate checks useAfterSeqfiltering to avoid reprocessing already-handled events. -
Dispatch is fire-and-forget: Once a goroutine is launched, the
controller does not track its completion. Failed orders emit
order.failedevents but do not retry. The tracking bead prevents re-fire within the same cooldown window. - No role names in Go code: The order subsystem operates on config-driven pool names and formula references. No line of Go references a specific role name.
Interactions
| Depends on | How |
|---|---|
internal/config | OrdersConfig for skip list and max timeout. FormulaLayers for formula directory resolution. City struct for config access. |
internal/events | Recorder for emitting order.fired, order.completed, order.failed events. Provider for event gate queries (List with AfterSeq filtering). |
internal/beads | Store for creating tracking beads, querying last-run history (ListByLabel), and instantiating wisps (MolCook). CommandRunner for bd CLI invocation. |
internal/fsys | FS interface for filesystem abstraction in Scan() (enables fake filesystem in tests). OSFS for production. |
| Depended on by | How |
|---|---|
cmd/gc/controller.go | The controller loop calls buildOrderDispatcher() on startup and config reload, then calls dispatch() on each tick. |
cmd/gc/cmd_order.go | CLI commands (gc order list/show/run/check/history) use orders.Scan() and orders.CheckGate() for user-facing operations. |
Health Patrol (cmd/gc/) | Order dispatch is one phase of the Health Patrol tick cycle, running after agent reconciliation and wisp GC. |
Code Map
| File | Responsibility |
|---|---|
internal/orders/order.go | Order struct, Parse(), Validate(), IsEnabled(), IsExec(), TimeoutOrDefault(), ScopedName() |
internal/orders/gates.go | GateResult, CheckGate(), checkCooldown(), checkCron(), checkCondition(), checkEvent(), cronFieldMatches(), MaxSeqFromLabels() |
internal/orders/scanner.go | Scan() — discovers orders across formula layers with priority override |
cmd/gc/order_dispatch.go | orderDispatcher interface, memoryOrderDispatcher, buildOrderDispatcher(), dispatch(), dispatchOne(), dispatchExec(), dispatchWisp(), effectiveTimeout(), rigExclusiveLayers(), qualifyPool(), ExecRunner, shellExecRunner |
cmd/gc/cmd_order.go | CLI commands: gc order list, show, run, check, history. Helper functions: loadOrders(), loadAllOrders(), cityFormulaLayers(), findOrder(), orderLastRunFn(), bdCursorFunc() |
Configuration
Orders are defined asorder.toml files inside formula
directories following the structure
<formulaDir>/orders/<name>/order.toml. The [orders]
section in city.toml controls global order behavior.
order.toml (per-order definition)
city.toml (global settings)
Order layering (override priority, lowest to highest)
The formula layer order determines whichorder.toml wins when the
same order name exists in multiple layers:
- City pack formulas — from pack referenced in
city.toml - City local formulas — from
[formulas]section or.gc/formulas/ - Rig pack formulas — from pack applied to a specific rig
- Rig local formulas — from rig’s
formulas_dir
Rig-scoped orders
When a rig has rig-exclusive formula layers (layers beyond the city prefix), orders found in those layers are stamped with the rig name. This produces independent scoped tracking:- Same order deployed to rigs
rig-aandrig-btracks independently asdb-health:rig:rig-aanddb-health:rig:rig-b. - Pool names are auto-qualified:
pool = "worker"in rigdemo-repobecomespool:demo-repo/workeron the wisp label. Already-qualified names (containing/) are left unchanged.
Testing
The order subsystem has comprehensive unit tests across three test files in the library and two in the CLI:| Test file | Coverage |
|---|---|
internal/orders/automation_test.go | Parse (formula, exec, event orders), Validate (all gate types, mutual exclusion, missing fields, timeout validation), IsEnabled default/explicit, IsExec, TimeoutOrDefault (defaults and custom), ScopedName (city and rig) |
internal/orders/gates_test.go | CheckGate for all five gate types: cooldown (never run, due, not due), cron (matched, not matched, already run this minute), condition (pass, fail), event (due, with cursor, cursor past all, not due, nil provider), rig-scoped gates (cooldown, cron, event use ScopedName), MaxSeqFromLabels (various label configurations) |
internal/orders/scanner_test.go | Scan (basic discovery, empty layers, layer override priority, skip list, disabled filtering, source path recording) |
cmd/gc/order_dispatch_test.go | Dispatcher nil-guard (no orders, manual-only), cooldown dispatch (due, not due, multiple), exec dispatch (due, failure, cooldown, ORDER_DIR env, timeout), rig-scoped dispatch (rig stamping, independent cooldown, qualified pool), rigExclusiveLayers, qualifyPool, effectiveTimeout (default, custom, capped) |
cmd/gc/cmd_order_test.go | CLI commands: list (empty, with data, exec type), show (found, not found), check (due, not due), history, findOrder |
fsys.NewFake(), beads.NewMemStore(),
stubbed ExecRunner, memRecorder) with no external infrastructure
dependencies. Condition gate tests use real sh -c true and sh -c false
commands. See TESTING.md for the overall testing philosophy and tier
boundaries.
Known Limitations
- No retry on dispatch failure: Failed orders emit events but are not retried. The tracking bead prevents re-fire within the same cooldown window, so a failed order must wait for the next gate opening.
-
Cron granularity is minutes: The cron gate operates at
minute-level granularity with simple field matching (
*, exact integer, comma-separated values). It does not support ranges (1-5), steps (*/5), or sub-minute scheduling. -
Condition gate blocks the dispatch loop:
checkCondition()runssh -c <check>synchronously during gate evaluation. A slow check command blocks evaluation of subsequent orders on that tick. -
Event gate cursor is per-wisp, not per-dispatch: The cursor
position is computed from
seq:<N>labels on existing wisp beads viaMaxSeqFromLabels(). If wisp creation fails, the cursor is not advanced, which may cause duplicate event processing on retry. -
No hot-add of orders: Order discovery runs on controller
start and config reload (via fsnotify). Adding a new
order.tomlfile requires the config directory watcher to trigger a reload; adding a new formula layer directory requires acity.tomlchange. - Fire-and-forget goroutines: Dispatch goroutines are not tracked by the controller. On shutdown, in-flight dispatches may be interrupted mid-execution if the context is canceled.
See Also
- Architecture glossary — authoritative definitions of order, gate, wisp, formula, and other terms used in this document
- Health Patrol architecture — the controller loop that drives order dispatch on each tick
- Beads architecture — the bead store used for tracking beads, wisp instantiation via MolCook, and label-based queries
- Config architecture — FormulaLayers resolution, pack expansion, and OrdersConfig
- Gate evaluation logic — CheckGate implementation for all five gate types
- Order discovery — Scan function for formula layer traversal
- Controller dispatch — production dispatcher wiring exec and formula orders
- Event type constants — order.fired, order.completed, order.failed event types