Gas City

“Composable orchestration infrastructure for multi-agent coding workflows.” An open-source Go platform for building your own multi-agent system: you declare a city of AI coding agents in TOML, and an orchestrator keeps reality matching that declaration — starting and stopping sessions, routing work, running scheduled automation — while every unit of work lives as a durable, queryable record called a bead. Write a formula and that orchestrator runs it as a graph across your whole fleet: decomposing the job into beads, fanning ready ones out to many agents, gating each step on its dependencies, retrying failures, and driving it to completion outside your session.

Go · MIT Declarative city.toml Beads-backed work tracking 6 session backends, tmux → k8s Any CLI agent (claude, codex, gemini…) Zero hardcoded roles Extracted from Gas Town

All facts verified against the linked sources on 2026-08-09, at the v1.4.0 tag (released 2026-07-24) — every source link on this page is pinned to v1.4.0, so quotes stay checkable. The project moves fast — trust the live docs over this page for anything newer.

v1.4.1 released 2026-08-15, after this page's verification pass — not yet re-checked against it.

The idea: orchestration as infrastructure

Every serious multi-agent setup ends up rebuilding the same machinery: something to start and supervise agent processes, something to track work that outlives any one session, something to let agents message each other, something to run jobs on a schedule and restart what breaks. Gas City extracts exactly that machinery from Gas Town — Steve Yegge's 20-to-30-agent “coding factory” — into a primitive-first platform. Six primitives (agent, bead, formula, rig, pack, event) are generic; every behavior, including all of Gas Town's famous roles, is just configuration layered on top. The engdocs state the design goal plainly: “The same SDK can express Gas Town, Ralph, or any other pack”“the orchestrator hardcodes zero roles — no built-in ‘manager’ or ‘reviewer.’” [README · how-gas-city-works · nine-concepts]

Concretely, the README's feature list is the whole story in five bullets: declarative city configuration via city.toml; work routing with Beads-backed tracking, formulas, molecules, waits and mail; runtime providers “tmux, subprocess, exec, ACP, Kubernetes, and herdr”; a controller/supervisor loop that reconciles desired state to running state; and packs, overrides, and rig-scoped orchestration for multi-project setups. [README]

Where it came from

Gas Town launched in January 2026 as Yegge's opinionated “industrialized coding factory manned by superintelligent robot chimps”: seven fixed worker roles, merge queues, patrol loops, operational complexity he compared to Kubernetes. Gas City v1.0 — built by Julian Knutsen and Chris Sells, announced April 2026 — deconstructs that application into composable packs. Two ideas from the announcement shape everything: full observability with git-versioned audit trails (Dolt) turning “dark factories” into light factories, and pool-first reliability — “any agent can go temporarily insane, at any time, and make a bad call.” The human's role shifts to “shepherds, tending flocks of agents which do the ground-level work.” [Welcome to Gas Town · Welcome to Gas City]

Used Gas Town? This guide assumes nothing from it — but section 10 maps every Town role, command, and habit onto Gas City primitives, and green notes like this one flag the ancestry as we go.

Vital statistics

FactValue
What“Composable orchestration infrastructure for multi-agent coding workflows” — an orchestration-builder platform, not a hosted product
Version coveredv1.4.0, released 2026-07-24 (this page pins every source link to that tag)
BuildersJulian Knutsen & Chris Sells; announced by Steve Yegge, April 2026
LanguageGo ≈93%, TypeScript ≈5%, Shell — repo gastownhall/gascity, MIT license
Required toolstmux, git, jq, pgrep, lsof
Optional toolsdolt 2.1.0+ & bd 1.0.0+ + flock (the default bd beads store), gh (GitHub gates), herdr 0.7.1+ (optional backend), agent CLIs (claude, codex, gemini…) per provider
Session backendstmux (default and required fallback), subprocess, exec, ACP, Kubernetes, herdr
Docsdocs/ user docs (Mintlify) · engdocs/ contributor architecture docs

What changed in v1.4.0

If you learned Gas City on the 1.3 line, five things moved. Each gets its full treatment in the section named beside it.

ChangeWhy it mattersWhere
The vocabulary shifted — user docs now say platform and orchestrator where they said SDK and controller, and the canonical model is “the six primitivesAgent · Bead · Formula · Rig · Pack · Event, with orchestrator, bead store, and event bus as the role-agnostic machinery under them. engdocs/ still says “controller” for the same daemon§2
A run-centered dashboard and typed APIRun detail unifies the formula stage ladder, structured transcripts, live token rate, and estimated burn rate; session and run reads moved to typed, paginated surfaces backed by warm projections. The SPA is now compiled into the gc binary and served by the supervisor§3
Command-usage metrics ship in release buildsOfficial releases record a canonical command ID, release, OS, and an anonymous installation ID after a one-time disclosure — never arguments, paths, or file contents. gc metrics off, DO_NOT_TRACK=1, or GC_DISABLE_USAGE_METRICS=1 opt out§3
Formulas v2 is the shape to learnv1 and v2 are peers, not a version ladder — but v2 is what makes the orchestrator, rather than one agent, the engine. Production controls landed for retry, fan-out, drain, scope, artifacts, and finalization§5
New operator surfacegc costs (per-run usage from .gc/usage.jsonl), gc context (named remote cities), gc extmsg (external-conversation bindings), gc login/logout/whoami, gc metrics, gc runtime check/conformance/heartbeat. Nothing was removed§4, §10

Upgrading an existing city? Run gc doctor --fix once, first. It “converges pack imports, provider catalogs, project identity, retired hold labels, and managed beads/Dolt metadata before the orchestrator starts.” The known 1.3→1.4 papercuts and their one-command fixes are in §3. [v1.4.0 release notes]

Sources: repo · README · Yegge announcement · Gas Town origin post

2 · The mental model

Three sentences carry the whole system. You declare what should exist — agents, projects, methods, schedules — in city.toml and the packs it imports. An orchestrator daemon makes reality match, reconciling running sessions against that declaration on every tick and driving formula runs forward. All state lives in beads — “everything is a bead: tasks, mail, molecules, convoys, and epics” — so work survives any crash, and agents pull work instead of being pushed to. [glossary · life-of-a-bead]

Since v1.4.0 the docs name a canonical model: six primitives, each answering one question, sitting on three pieces of role-agnostic machinery — the orchestrator (runs formulas, reconciles sessions), the bead store (durable work), and the event bus (fires activity outward). “None of this machinery knows what your agents do.” [how-gas-city-works]

PrimitiveAsksIs
AgentWHOa configured worker — name, provider, prompt template, scope
BeadWHATone unit of work — ID, title, status, type
FormulaHOWa reusable, written-down method applied over work
RigWHEREan external project (usually a git repo) registered with the city
PackCONFIGURESthe unit of configuration — declares agents, formulas, orders
EventOBSERVEan outbound notification fired by activity — fired, not polled

Terminology, so the two doc trees don't confuse you. The v1.4.0 user docs call the per-city daemon the orchestrator and Gas City a platform; engdocs/ still calls the same daemon the controller and the same thing an SDK. Same component, same code. This guide uses “orchestrator” for the concept and “controller” where it names actual Go symbols like controllerLoop(). [how-gas-city-works · nine-concepts]

City — “the local (root) pack rooted at the deployment directory.” A directory with city.toml plus .gc/ runtime state. Crucially, “the City is a pack”: the same configuration unit as everything else, just the root one. [how-gas-city-works]
Rig — “an external project (usually a git repo) registered with the city.” Each rig gets its own bead namespace (ID prefix), its own agent scope, and its own hooks — so one city can orchestrate many codebases without cross-talk. v1.4.0's docs sharpen how that isolation works: “Isolation is by bead-ID prefix, not a separate database: the city and all its rigs share one underlying store, and reads and writes are filtered to the current scope's prefix.” Operationally each scope still behaves as its own store — which is why routing across scopes is refused (§5). [how-gas-city-works]
Agent → session — an agent is “who does the work — a worker a pack defines as a prompt plus a scope and a provider”; configuration, not code. When it runs, it becomes a session: a live process the platform manages. Pools scale between min_active_sessions and max_active_sessions — “each tick the orchestrator runs the agent's scale_check query to measure demand and sizes the pool to it,” retiring sessions that fall idle. On restart the orchestrator adopts the live sessions it finds, creating a session bead for each, rather than respawning them. “The SDK manages session lifecycle. The prompt defines agent behavior.” [how-gas-city-works · nine-concepts]
Bead — “one unit with an ID, title, status, and type,” moving open → in_progress → closed. The universal substrate: tasks, mail messages, sessions, and convoys are all beads differentiated only by type. Stored durably (Dolt-backed bd by default), which is why a crashed agent's work is resumable. [how-gas-city-works]
Formula → run — a formula is “how a job gets done — a reusable, written-down method”: a TOML file of steps and dependencies. Applying (“cooking”) it materializes beads that outlive the file. What lands depends on the compiler contract: a v1 formula with steps produces a molecule (container root + step children); a v2 formula produces a workflow — a flat graph of independently routable step beads that the orchestrator drives. Either way, “from that moment a run is independent of the file and of any session.” [how-gas-city-works · glossary]
Pack — “the unit of configuration — declares agents, formulas, orders” via pack.toml. Packs compose through named imports, so shared behavior (like the entire Gas Town role set) ships as a reusable directory instead of copied files. [how-gas-city-works]
Orchestrator (controller) & supervisor — two different things. The orchestrator is “the long-running daemon that drives all SDK infrastructure” inside one city: the reconcile loop plus formula-run execution. The supervisor is machine-wide — a “typed HTTP + SSE control plane” that cities register with; it serves the REST API and, since v1.4.0, hosts the dashboard SPA itself. [glossary · reference]
Machine-wide supervisor typed HTTP + SSE control plane · REST API + dashboard (127.0.0.1:8372) · every city on this machine registers here gc supervisor · gc cities City — the root pack (~/bright-lights) city.toml + .gc/ runtime state · started with gc start Controller (daemon) reconcile loop, every tick: desired vs running sessions, order triggers, health patrol, wisp cleanup Pack config = desired state city.toml + imported packs: agents, formulas, orders, providers, rigs gc config explain Agents → live sessions mayor — coordinator (tmux) polecat pool — ×1…N workers behavior = pack-supplied prompt; pools scale min…max sessions start·stop Bead store (work) tasks · mail · convoys · sessions — “everything is a bead” Dolt-backed bd by default bd ready · gc sling · gc mail agents pull work (gc hook) Rig: hello-world external git repo, registered with gc rig add · own bead namespace (prefix) · own agent scope + hooks gc sling claude "task…" Rig: another-project one city orchestrates many projects; per-rig overrides refine imported agents work config / structure agents / sessions work / beads control / automation
One machine, one supervisor; each city declares its agents and rigs in config, the controller reconciles sessions to match, and all work flows through the bead store.

The load-bearing trick is the bead. Because sessions, mail, and batches are all just typed beads in one durable store, every other feature — routing, messaging, monitoring, crash recovery — is a query. Keep that in mind and the rest of this guide is details.

Sources: how-gas-city-works · nine-concepts · engdocs glossary · dashboard

3 · Install & first city

Labs in this guide were written against gascity v1.4.0 docs (fetched 2026-08-09) on macOS with Homebrew. Homebrew installs all runtime dependencies for you (tmux, jq, dolt, bd…); the manual dependency table only matters for direct-download or source builds. Building from source needs Go 1.26+ (pinned in go.mod as 1.26.4). [installation]

Lab 1 — Install and verify
brew install gascity
gc version

Expect: a version string. Homebrew pulls the runtime dependencies automatically.

Upgrades later: brew update && brew upgrade gascity && gc service restart. [installation]

zsh users: Oh My Zsh's git plugin aliases gc to git commit. Check with command gc version; fix permanently by adding unalias gc 2>/dev/null to ~/.zshrc. [installation]

Lab 2 — Boot your first city and look around
gc init ~/bright-lights
cd ~/bright-lights
gc status
gc dashboard

Expect: gc init “bootstraps the city directory, registers it with the supervisor, and starts the orchestrator” — the city is live immediately, no separate start step. gc status shows the city-wide overview; gc dashboard opens the supervisor's web UI (typically http://127.0.0.1:8372/) with agents, beads, mail, formula runs, and store health.

Useful from anywhere: gc cities lists every city registered with the machine-wide supervisor; gc stop stops all agent sessions in a city; gc start starts a city under the supervisor again. [quickstart · dashboard · CLI]

The dashboard, rebuilt around runs

v1.4.0 changed what gc dashboard is. The UI is “a single-page app compiled into the gc binary and served by the supervisor on its own listener, so there is nothing extra to install or run” — the old standalone static server (and its --port flag) is gone. gc dashboard now resolves the supervisor URL, opens your browser, and prints the URL; --no-open prints without launching (handy over SSH). If the supervisor isn't running it tells you how to start it instead of opening a dead URL. One supervisor serves every registered city — pick yours from the header switcher. [dashboard · CLI]

The headline feature is run detail: the release notes describe it as unifying “the formula stage ladder, structured transcripts, live token rate, and estimated burn rate,” with session and run reads moved onto “typed, paginated API surfaces backed by warm projections instead of ad hoc wire shapes.” A late fix (PR #4436) restored tokens/min and burn/hr for long-lived pool sessions by sweeping model usage at each interval end — so those fields populate even when agents self-drive after their initial claim. [v1.4.0 release notes]

Security posture: the dashboard binds where the supervisor binds — loopback (127.0.0.1) by default — and is “intended for local, single-operator use.” Without allow_mutations it runs read-only and disables its mutating controls. Set GC_SUPERVISOR_DASHBOARD=0 before starting the supervisor for a typed-API-only supervisor with no embedded dashboard. Exposing it beyond loopback is a real deployment decision — see the remote-hardened-city runbook before you do. [dashboard]

Command-usage metrics (new in v1.4.0)

Official release builds of gc now record privacy-scoped command-usage metrics. The disclosure is shown before the first eligible interactive command is recorded, and that first invocation is never recorded — collection begins with the next one. Unversioned, development, test, and CI builds fail closed and cannot collect. Events carry “only a canonical command ID, the gc release, operating system, and an anonymous installation ID — never arguments, paths, file contents, or environment values.” [release notes · CLI]

gc metrics status          # redacted local state
gc metrics example         # the exact request shape that would be sent
gc metrics off             # durable opt-out: disables, purges queued data, removes the ID
gc metrics on              # accept the disclosure (verified TTY)

# environment-level opt-outs, per process
export DO_NOT_TRACK=1
export GC_DISABLE_USAGE_METRICS=1

Note the asymmetry: DO_NOT_TRACK=0 or GC_DISABLE_USAGE_METRICS=0 “never forces collection on and never overrides a saved opt-out. There is no environment force-enable.” Don't confuse this with [usage] and gc costs (§4) — those are your own local cost accounting and go nowhere off the machine.

Lab 2b — Upgrade an existing 1.3 city to 1.4
brew update && brew upgrade gascity
cd ~/bright-lights
gc doctor --fix            # run this once per existing city, before anything else
gc start

Expect: gc doctor --fix “converges pack imports, provider catalogs, project identity, retired hold labels, and managed beads/Dolt metadata before the orchestrator starts.” The release notes report no 1.3→1.4 regressions; every known papercut comes from existing machine state left by an older install:

SymptomCauseFix
gc start keeps running an old binary, or fails with synthetic cache is invalid … missing bundled pack cache markerOlder gc/supervisor at a different path; a present-but-invalid bundled-pack cache does not self-heal (only an absent one does)gc import install to reseed, then let gc start auto-restart the supervisor (Linux: systemctl --user restart gascity-supervisor)
gc start <city> aborts over an unrelated city, hinting you to gc init the healthy oneA stale pre-1.3 registered city with un-migrated provider config fails the registry scangc doctor --fix in the offending stale city, or gc unregister <stale-city>
macOS: supervisor binary-drift auto-restart never completesmacOS can't resolve a direct (non-launchd) supervisor's executablegc supervisor stop --wait, then gc start

One config change is load-bearing: configure one store-scoped control-dispatcher for every graph-owning scope. Formula control beads route to the dispatcher whose Dir matches the city or rig store that owns the graph, and “a rig-owned graph with no matching dispatcher fails before instantiation” rather than falling back to a dispatcher that cannot read its work. [v1.4.0 release notes · CLI]

Sources: installation · quickstart · dashboard · troubleshooting · v1.4.0 release notes

4 · city.toml anatomy

Everything section 2 called “desired state” is written here. Only [workspace] is required; a useful minimal city is four blocks. Below, the documented minimal example with the fields you'll touch first, annotated. [config reference]

# ── required: city-level metadata ─────────────────────────────
[workspace]
name = "my-city"

# ── how to launch an agent CLI (a named preset) ───────────────
[providers.claude]
command = "claude"
args = ["chat", "--no-interactive", "--agent", "gascity"]

# ── a configured worker; behavior comes from its prompt ───────
[[agent]]
name = "polecat"
provider = "claude"
# scope = "rig"                 # where it may load: "city" | "rig"
# min_active_sessions = 1      # pool floor…
# max_active_sessions = 3      # …and ceiling
# wake_mode = "resume"         # or "fresh" per wake
# drain_timeout = "5m"         # default grace on shutdown

# ── an external project this city orchestrates ────────────────
[[rigs]]
name = "myrig"
path = "/path/to/rig"
# prefix = "mr"                # bead-ID namespace for this rig

The rest of the schema, by neighborhood

SectionWhat it configures
[imports], [defaults], [patches]Pack composition: named pack imports, city-level defaults, targeted post-merge patches (never recursive)
[beads]Work store backend: bd (Dolt, default), file, or exec:<script>
[session]Session provider: tmux (default), k8s, acp, subprocess, exec:<script>
[mail], [events]Mail backend (beadmail default; retention TTL for read messages) · event log (file-backed JSONL with rotation)
[daemon]Controller: patrol interval, restart thresholds, session circuit breaker, formula-v2 enablement
[orders], [convergence]Order skip list + timeout caps · convergence loop limits (max_per_agent 2, max_total 10)
[api], [[webhook]], [webhooks], [[service]]HTTP API (port 9443, mutation controls, auth keys) · inbound webhooks (default-closed) · new in 1.4: city-level webhook governance — public-exposure grants and the operator-owned rate limit · workspace-owned services
[upstreams], [agent_defaults], [[pricing]]Named model-serving endpoint presets, selectable per agent via upstream — the “Upstream axis”: who serves the model, rendered onto whatever env-var names each harness reads · fallback agent settings · per-model cost overrides
[usage], [extmsg]New in 1.4. Usage-fact sink — local (default, JSONL at .gc/usage.jsonl), exec:<script>, or discard; feeds gc costs · default routes for inbound external conversations that have no binding
[github], [dolt], [doctor], [maintenance]PR monitors with repair routing (repair_workflow now defaults to the standard polecat repair workflow) · Dolt server tuning · health thresholds · store maintenance cadence

Two 1.4 knobs worth knowing before you need them. Under [beads], conditional_writes selects the bead-write discipline — off (legacy), auto (compare-and-swap where the store can, loud degrade otherwise), or require (CAS or a typed refusal) — and guarded_release does the same for ownership release (owner-blind unclaim vs fence-guarded verbs). Both default to off, and any value outside the enum fails config load. Turn them up when concurrent claims start racing. [config reference]

If you ever bind the API off loopback, v1.4.0 added signed-grant admission control: write_auth_verify_key / read_auth_verify_key require every mutating (or reading) request to the typed per-city routes to carry a grant from a trusted authority, with *_auth_required making a missing key a startup error instead of a silently disabled gate. A non-loopback bind with allow_mutations and no verify key is a fail-closed startup error unless you explicitly set write_auth_allow_unverified. The bundled CLI and dashboard mint no grants, so enabling the gates means fronting them with the authority that does. [config reference · remote-hardened-city]

Never guess what your config resolved to. gc config show dumps the fully resolved city configuration as TOML; gc config explain adds provenance — which pack or layer each value came from. These two commands make section 7's layering rules observable. [CLI]

Templates: fields like work_dir, session_setup, on_boot, and work_query accept placeholders — {{.Session}}, {{.Agent}}, {{.Rig}}, {{.RigRoot}}, {{.CityRoot}}, {{.WorkDir}} and friends — resolved per session. [config reference]

New command surface in v1.4.0

Nothing was removed from the CLI in this release; these command families are additions. Most of them read or write config you now know where to find.

CommandWhat it does
gc costsAggregates recorded usage facts (model tokens and compute wall-seconds) by run, from .gc/usage.jsonl. Local only — under an exec: or discard [usage] provider it shows nothing. Cost is “a list-price estimate for decision support, not an authoritative charge”; unpriced invocations are flagged and excluded from the total
gc context (+ --city-url, --city-name, --context)A client-side registry of named remote cities in ~/.gc/contexts.toml — URL, remote city name, TLS options, and an optional credential- or grant-minting command. Precedence is git-like and dry-runnable with gc context current: explicit flag > explicit env > local city discovery > sticky default
gc extmsg bind / handoff / unbindBinds an external conversation (telegram, discord, …) to a session or a configured agent. An agent-name binding survives session restarts — inbound messages resolve a live session at delivery time, cold-waking one if none is live. handoff rebinds to another agent: the front-desk pattern. Requires the city API server; no local fallback
gc login / gc logout / gc whoamiAuthenticate to a hosted Gas City service and show the authenticated account
gc metrics status/example/on/offLocal controls for command-usage metrics (§3)
gc runtime check / conformance / heartbeatProbe a runtime backend and check it against the provider contract
gc import credential add/list/removeCredentials for fetching private pack imports

One flag change that will bite scripts: for gc beads list and gc beads show, the bare --json flag “is reserved by the CLI's JSON-contract layer and is not wired for this command” — use --format=json. As before, API-path JSON includes _cache_age_s and fallback-path JSON omits it. [CLI reference]

Sources: config reference (city.toml schema) · CLI reference · connected clients

5 · Work routing: beads, formulas, molecules, waits & mail

Gas City is a pull system. Nothing pushes a task into an agent's prompt; work is written into the bead store, and each agent's hook query finds what's addressed to it. The docs put the contract in capitals: “If you find work on your hook, YOU RUN IT” — the run-what-you-find rule. [life-of-a-bead]

Formula (TOML file) the written-down method: steps, vars, needs edges feature.formula.toml cook gc formula cook gc sling --formula Molecule (materialized beads) root bead — the run design implement steps outlive the file; state persists in the store ephemeral run = “wisp” (auto-GC'd) open in_progress closed claim bd close Agent session polls its hook, runs what it finds, closes the bead gc hook pull: bd ready Gates — what makes a step “ready” • needs edges: a step lists step IDs that must close first; ready steps run in parallel • waits: durable session waits — a session blocks on a condition without dying (gc wait) • mail: message beads to a session alias or human (gc mail send / check / inbox / read) • orders: trigger (cooldown · cron · condition · event · manual) + formula = scheduled automation colors: config work agents automation
A formula cooks into a molecule of durable beads; agents pull ready beads, and gates (dependencies, waits, mail, orders) decide readiness.

The primitives, one by one

Sling is the everyday verb: gc sling <target> "<task>" creates a bead and routes it — to a fixed agent by setting its assignee, to a pool by labeling it pool:<name>, and with --formula it cooks and routes in one motion. Pool agents then claim with bd update <id> --claim. [quickstart · life-of-a-bead]

Formulas go through three stages: TOML file → in-memory recipe (flattened steps plus dependency edges) → materialized beads, independent of the file. “Work persists, so whoever picks it up next finds the same state.” Preview with gc formula show, materialize without routing via gc formula cook, detect drift with gc formula version-check. [understanding-formulas]

formula = "feature-flow"
[vars]
feature = "the feature"

[[steps]]
id = "design"
title = "Design {{feature}}"

[[steps]]
id = "implement"
needs = ["design"]        # readiness gate: runs when design closes

Molecules and wisps. A molecule is “a formula instantiated at runtime: one root bead plus zero or more provider-managed step beads.” A wisp is “an ephemeral bead produced by a v1 formula run” — created by gc sling or order dispatch, auto-closed and garbage-collected once both wisp_gc_interval and wisp_ttl are set (wisp GC is disabled unless both are). The core pack's cleanup order tidies all three outcomes above: it reaps stale ephemeral runs and purges closed step records, with cleanup edges covering v2 workflows too. [glossary]

Convoys group related beads into a tracked batch (“a container bead that groups related issues”), with blocking needs edges between members; when all children close, the convoy auto-closes. Orders automate dispatch: a trigger — cooldown, cron, condition, event, or manual — paired with a formula (gc order list, gc order run). [glossary · life-of-a-bead · CLI]

Mail is messaging built from beads: a message is a bead with Type="message", sender in From, recipient as assignee; your inbox is “the set of open, unread message beads assigned to a recipient.” Reading adds a read label but keeps the bead open; archiving closes it. Threads are labels (thread:<id>). Nudges are the opposite trade-off: text sent straight into a session to wake or redirect it — fire-and-forget, no persistence, lost if the session isn't running. Waits are durable session waits — a session blocks on a condition and survives it (gc wait inspects them). [messaging · session · CLI]

Two compiler contracts — and why v2 is the one to learn

This is the decision v1.4.0's docs put first, ahead of every pattern. “Both contracts are live and supported. They are peers, not a version ladder — each makes a different thing the engine.” Under v1 the engine is the agent you sling to; under v2 it is the orchestrator. [understanding-formulas]

v1 (default)v2 ([requires])
Enginethe agent you sling tothe orchestrator
Stepsresolved at apply, then inertindependently routable units
Control flownone after applycheck/retry/drain/tally, scope checks, finalize
Routingone agent, one sessionmany agents and pools (gc.run_target per step)
Shapeparent-child molecule treeflat graph of blocking edges + appended finalize

“For new work, choose v2. The opt-in is one table”:

[requires]
formula_compiler = ">=2.0.0"

Base constructs (steps, needs, children, condition, loop, vars, extends) mean the same in both. Graph-only constructs — check, retry, drain, on_complete, tally, and reserved gc.* step metadata — require the declaration; compiling without it fails telling you exactly which table to add. The host-side [daemon] formula_v2 switch defaults to on, and the deprecated contract = "graph.v2" key still parses with a gc doctor warning.

Two v1-only edges remain, “neither a reason to start on v1”: gc converge accepts only v1 formulas (use a v2 check loop for iterate-until-it-passes instead), and container dependencies have a v2 gap — the v2 compiler creates no parent-child edges yet, so a needs on a parent gates only on that parent step; list the children explicitly until #3451 lands.

Verb vs outcome: cook, sling, or order

Three verbs create formula instances, and the outcome follows from the contract rather than from a separate choice. Cook (gc formula cook <name>) creates without routing — nothing wakes up; use it to inspect beads first, route yourself, or graft a sub-DAG onto existing work with --attach <bead-id>. Sling (gc sling <target> <name> --formula) cooks and routes in one motion. Orders are scheduled dispatch: a trigger plus a formula (or a shell command — never both), instantiated and routed to the order's pool each time it fires. [understanding-formulas]

OutcomeFromPer-step beadsRoot is visible work
Single-bead run (a wisp)v1, no steps (phase = "vapor")No — steps stay in the recipeYes — the root is the work
v1 run with steps (a molecule)v1 with steps: container root + step childrenYes, as childrenNo — the root is a container
v2 workflowv2Yes, independently routableNo — the root blocks on finalize

Two refusals that will confuse you the first time. “A pool wakes only for Ready-visible work, so slinging a v1 run at a pool is refused outright — convert to v2 first.” And cook and sling in the store the worker reads: cook materializes into the scope you run it from (--rig, else the enclosing rig directory, else the city), and sling refuses a cross-store route with refusing cross-store route, telling you to re-file the bead or pick a reachable target. City-scoped agents are the exception — they are cross-store eligible and may serve work in any store. [understanding-formulas]

Lab 3 — Route your first bead
# a toy project to orchestrate
mkdir ~/hello-world && cd ~/hello-world && git init && cd -

# register it with the city, then sling work at an agent
gc rig add ~/hello-world
cd ~/hello-world
gc sling claude "Create a script that prints hello world"

# watch the bead move open → in_progress → closed
bd show <bead-id> --watch

# and see the same activity as events
gc events

Expect: gc rig add gives the rig its own bead scope (an ID prefix filtered out of the shared store), hooks, and routing context — and, since v1.3.2, materializes the rig's skills immediately, including for rigs sourced outside the city tree. gc sling prints the created bead's ID; a session starts and the task is delivered. --watch live-updates the status line. [quickstart]

Gas Town's convoys survive intact — “convoys stay bead-backed grouping and lineage; the implementation boundary moved.” gc sling now creates convoy structure while routing. [coming-from-gastown]

Sources: understanding-formulas · life-of-a-bead · messaging · glossary · CLI reference

6 · Runtime providers

Agents don't care where they run. The [session] block picks a backend city-wide (or GC_SESSION for one process); every backend implements the same provider contract, so the orchestrator's logic — and your config — stays identical from a laptop tmux pane to a Kubernetes pod. One rule to remember: “tmux is the default session backend and the fallback, so it stays required even if you run agents on another backend.” v1.4.0 pushed this further: provider routing, ACP/automatic runtime selection, Herdr-backed sessions, and Kubernetes/subprocess/tmux execution now “share one session lifecycle and worker boundary,” with pool demand, wake, resume, drain, close, and orphan recovery all reasoning from persisted session/work identity rather than provider-specific shortcuts. [config reference · README]

Controller — reconcile, dispatch, patrol never talks to tmux/k8s directly runtime.Provider — one interface Start · Stop · Interrupt · IsRunning · ListRunning · Peek Attach · Nudge · SendKeys · Get/SetMeta · CopyTo · RunLive tmux default + fallback, interactive panes subprocess non-interactive local processes exec:<script> script-backed, documented contract ACP Agent Client Protocol (acp/auto/hybrid routing) k8s cluster scale herdr agent-focused workspaces
One interface, six backends. Optional capabilities (idle-wait, immediate nudge, interaction) are extension interfaces a backend may add.
BackendMechanismReach for it when…
tmuxSessions as tmux panesDefault. Local, interactive, attachable — and the required fallback for everything else
subprocessPlain local processesNon-interactive agents that need no terminal
exec:<script>Your script implements the session contractCustom environments — the escape hatch (contract in reference/exec-session-provider.md)
ACPAgent Client Protocol; acp/auto/hybrid routing layersEditor/protocol-native agents rather than terminal CLIs
k8sSessions in KubernetesScaling beyond one machine
herdrShared herdr session-server per city, workspace/tab hierarchy; opt-in, selected city-wide onlyMany agents, cleaner organization than raw tmux — purpose-built for AI agent fleets

The backend is a whole-city choice — you cannot pin one agent back to tmux. This trips people who expect Kubernetes-style per-workload scheduling. The per-agent session field selects a transport, not a backend, and accepts only acp, tmux, or omission. Under a herdr city: session = "acp" genuinely moves that agent onto the ACP backend — “it is the one per-agent lever that changes which backend an agent runs on” — but session = "tmux" does not keep an agent on tmux. The herdr provider does not implement the transport-capability check, so the pin is neither honored nor rejected; the agent falls back to the base provider and runs on herdr.” To put an agent on tmux, the whole city (or process) must default to tmux. [herdr-provider]

Piloting a new backend, the documented way: scope which city runs on it, not which agents. Requires herdr 0.7.1+ on PATH — if the binary is missing, sessions selected onto herdr fail to start. [herdr-provider]

# 1 · per-process trial on a throwaway city — nothing committed
GC_SESSION=herdr gc start <scratch-city>

# 2 · promote to that scratch city's default, run it end to end
[session]
provider = "herdr"

# 3 · widen to the real city only after several stable work cycles

Apply a selector change with gc reload or a city restart; already-running sessions keep their current backend until they next restart. Confirm with gc config show. Layout under herdr mirrors the town: one workspace per rig plus one for the town, one tab per agent.

Sources: config reference · herdr-provider · session architecture · README

7 · Multi-project: rigs, packs & overrides

One city, many codebases, shared behavior — this is where Gas City earns the “composable” in its tagline. Three mechanisms do the work: rigs isolate projects, packs share configuration, and overrides/patches specialize it per place.

Rigs: project isolation

Registering a repo (gc rig add) gives it its own bead scope, hook installation, and routing context; the rig's prefix namespaces its bead IDs so queries never leak across projects. Under the hood “the city and all its rigs share one underlying store, and reads and writes are filtered to the current scope's prefix” — the isolation is the prefix, not a separate database, but it is enforced hard enough that cross-scope routing is refused (§5). Rig config can set its own formulas_dir, formula_vars, session caps, and default sling targets — default_sling_targets (plural, new in 1.4) takes a list and picks one at random per targetless dispatch, taking precedence over the singular form. [how-gas-city-works · config reference]

Packs: shared behavior via named imports

“A pack is a directory with a pack.toml file” — agents in agents/, formulas in formulas/, plus prompts, orders, commands, skills. Imports are named bindings, and the binding qualifies every imported agent name: import a pack as gascity and you address its planner as gascity.planner, never bare planner. Imports live at city level, or under a [[rigs]] entry — which stamps the rig's identity onto the imported agents. [understanding-packs]

Keep two identifiers apart, because only one is portable. A registry handle like main:gascity finds a pack on this machinemain is your local registry name and another machine could call it work. A durable source like https://github.com/gastownhall/gascity-packs/tree/main/gascity is what you commit, “independent of any machine's registry name or cache.” Use the handle to search (gc pack registry …); write the source into your TOML. New in 1.4: gc import credential manages credentials for private pack sources. [understanding-packs · CLI]

The layering order (memorize the last line)

city.toml + city pack
  → imported packs (+ pack-level patches)
  → city-level imports → city-level patches
  → rig-level imports (stamp rig agents) → rig overrides
  → pack globals
  → city agent defaults

“Later layers win for replacement-style fields. Defaults run last but only fill blanks, so they never override an explicit value from an earlier layer.” Session-cap precedence runs agent > rig > workspace; an agent's scope field (city, rig, or omitted for both) says where a definition may load — “it does not name a particular rig.” [understanding-packs · config reference]

Lab 4 — Second rig, and watch the layers resolve
mkdir ~/second-project && cd ~/second-project && git init && cd -
gc rig add ~/second-project

gc rig                 # both rigs listed
gc config explain      # every resolved value, with which layer set it

Expect: the new rig appears with its own prefix and beads store; gc config explain shows provenance per value — the layering chain above, made visible. [quickstart · CLI]

The deepest habit to unlearn from Gas Town: “do not port code or prompts that assume directory path implies who the agent is.” Identity is explicit config now — dir is scope, not selfhood. [coming-from-gastown]

Sources: understanding-packs · config reference · quickstart

8 · The reconcile loop

Kubernetes users will feel at home: the orchestrator is a declarative convergence engine. Desired state comes from city.toml parsed with includes and patches; observed state comes from Provider.ListRunning() plus session metadata; reconcileSessionBeads() continuously repairs the divergence. The whole cycle lives in controllerLoop() and runs every patrol_interval (default 30s). [controller]

Where v2 orchestration actually executes. The reconcile loop keeps sessions converged; a second mechanism drives work graphs. The engdocs are blunt about it: v2 orchestration “is implemented by the control dispatcher in internal/dispatch executing control beads (check, retry, fan-out, tally, drain, scope-check, workflow-finalize).” Those control beads route to the dispatcher whose Dir matches the store that owns the graph — which is why every graph-owning scope needs its own control-dispatcher agent configured (§3). v1.4.0 also made that routing retry transient config/include read failures instead of quarantining an in-flight run, while still failing closed when a successfully loaded config genuinely lacks the scoped dispatcher. [nine-concepts · dispatch · release notes]

Desired state city.toml + includes + patches watched via fsnotify → atomic dirty flag, debounced; ConfigFingerprint() detects drift Observed state Provider.ListRunning() + session metadata + session beads in the work store controllerLoop() — one tick, every patrol_interval (30s) 1 · config changed? tryReloadConfig() re-parses city.toml with includes and patches 2 · re-evaluate desired agent set; pools via evaluatePool() check commands, parallel goroutines 3 · reconcile: “make session beads and running sessions match the desired list” 4 · wisp GC: delete closed molecules older than wisp_ttl (if enabled) 5 · evaluate order triggers; dispatch non-manual orders (cooldown · cron · condition · event) Actions start sessions, interrupt → wait → force-kill, restart Events append-only JSONL, monotonic sequence; gc events
Each tick: reload config if dirty, recompute the desired agent set, converge sessions, collect garbage, run automation — then observe the results as events.

Failure handling

  • Graceful termination is two-pass: “(1) send Interrupt (Ctrl-C) to all sessions, (2) wait shutdown_timeout, (3) force-kill survivors via Stop().”
  • Crash loops hit a breaker: the crash tracker quarantines agents using a sliding window — “max_restarts within restart_window” — so a broken agent can't consume resources forever.
  • Health patrol is just an order: “probe sessions… compare thresholds… publish stalls… restart with backoff” — remediation composed from the same primitives as any other automation.
  • Pool capacity is fenced (new in 1.4): claim ownership, wake budgets, slot selection, and confirmed-dead cleanup are “fenced against stale or partial observations,” so pool sessions no longer lose or strand work while draining, restarting, or reusing capacity.
  • Degraded is now said out loud: managed beads and Dolt paths — provider health, endpoint ownership, lock release, compaction, reindexing, stale data-dir cleanup, partial store reads — “preserve errors instead of silently reporting complete state.”

[controller · nine-concepts]

Above the loop: the supervisor

The machine-wide supervisor exposes the “typed HTTP + SSE control plane” (OpenAPI-documented) that the dashboard and gc events consume — and since v1.4.0 it serves the dashboard SPA itself, same-origin, out of the gc binary. Its API defaults to loopback with mutation controls and auth keys in [api]; the event log is file-backed JSONL with rotation (gc events rotate). v1.4.0 bounded these paths under load: “the CLI drains paginated event windows, request paths avoid unbounded scans, tmux sessions keep their shared server, and structured transcripts preserve tool and error frames.” [reference index · config reference · CLI]

Sources: engdocs controller · nine-concepts · reference index

9 · Code tour

The repo splits cleanly: cmd/gc/ holds the CLI and the controller; internal/ holds 106 top-level packages (150 Go package directories at v1.4.0, up from 76/105 at v1.3.0) implementing the primitives; docs/ is the user manual and engdocs/ the contributor manual. Start reading with three engdocs files: nine-concepts.md (the layering), life-of-a-bead.md (the data flow), controller.md (the loop). [internal/ · engdocs/architecture]

cmd/gc/ — CLI + controller controller.go · city_runtime.go session_reconciler.go crash_tracker.go · idle_tracker.go wisp_gc.go · order_dispatch.go every gc subcommand handler lives beside the daemon it drives docs/ — user manual (Mintlify) getting-started · guides · tutorials · reference (schemas, OpenAPI) · runbooks engdocs/ — contributor manual architecture/ (20 docs: controller, nine-concepts, glossary, invariants…) internal/ — 106 packages; the ones that matter first sessions runtime (Provider iface) · session (waits.go) · agent · agentutil work beads · molecule · materialize · formula · convoy · mail config & packs config · configedit · packman · packregistry · builtinpacks · overlay routing & automation sling · dispatch · graphroute · graphv2 · orders · orderdispatch · orderdiscovery control plane supervisor · api · events · eventfeed · convergence · doctor · storehealth integration & misc githubmonitor · webhook{match,sink,verify} extmsg · telemetry · pricing · nudgequeue Reading path for the reconcile loop: internal/config → cmd/gc/controller.go → cmd/gc/session_reconciler.go → internal/runtime → internal/beads
Orient by primitive, not by alphabet: sessions (green), work (violet), config (blue), control/automation (orange).

Details worth knowing before you patch anything

  • Provider contract: lifecycle (Start/Stop/Interrupt), observation (IsRunning, ListRunning, Peek, GetLastActivity…), interaction (Attach/Nudge/SendKeys), metadata, and staging (CopyTo/RunLive). Optional extensions: InteractionProvider, IdleWaitProvider, ImmediateNudgeProvider. Rules: Stop is idempotent; ProcessAlive returns true on empty process lists.
  • Session identity: agent.SessionNameFor() is “the single source of truth for runtime session naming”; beacons (startup ID strings) let restarted sessions be recognized and adopted after crashes.
  • Store implementations: BdStore (Dolt-backed, production), FileStore, MemStore, and an exec store — all behind the same CRUD + parent-child + dependencies + labels + query contract.
  • The invariant that bites: “Only non-nil fields in UpdateOpts are applied. Labels append, never replace.”
  • Where v1.4.0's new surface lives: 30 top-level packages appeared under internal/. The ones that map to features in this guide are usage and productmetrics (usage facts and command-usage metrics), eventfeed and transcriptmeta (paginated feeds, structured transcripts), poolplan, warmup and orderdispatch (pool sizing and dispatch), clientauth/clientgrant/citywriteauth/clientcontext (remote cities and signed grants), webhookmatch/webhooksink/webhookverify, and rig, storeref, doltorphan for store scoping and repair. engdocs/architecture/ lost tally-control.md (21 docs → 20).

[session · nine-concepts · life-of-a-bead]

Sources: internal/ tree · engdocs/architecture tree · session.md · controller.md

10 · Gas Town → Gas City

The migration doc's core instruction: don't import Gas Town's surface area — “re-express the intent in Gas City's primitives.” The deepest change is ownership: the orchestrator now “owns reconciling desired→running sessions, session scaling, order evaluation, health patrol” — duties Gas Town distributed across role agents. Roles didn't die; they became configuration, and ship today as the Gastown pack (mayor, polecat, witness, refinery, deacon, boot). v1.4.0's rewrite of that page adds the second half of the story: “the orchestrator can now run a formula as a graph across many agents, out of your session — decomposing a job into beads, fanning the ready ones out in parallel, gating each step on its dependencies, and retrying failures to completion. Your single-agent, in-session formulas still run (v1); this fleet orchestration (v2) is what's new.” Because it is a platform, a feature added to Gas City lifts every orchestrator built on it — Gas Town included. [coming-from-gastown · command map]

Roles & concepts

Gas TownGas CityWhat changed
Mayor — “planner/coordinator; the human's point of contact”“Configured agent + coordinating prompt”A role is a prompt now, not platform code
Deacon (watchdog)“Orchestrator health patrol + config thresholds”The platform absorbed the watchdog duty
Witness (lifecycle observer)“Events + waits, formulas, session scale config”Observation decomposed into primitives
Refinery (post-processing)“Configured agent + a formula or order post-processing step”A pipeline step, not a standing role
Polecat (on-demand worker)“Scalable/transient agent config (a pool)”min/max_active_sessions replace role spawning
Crew (persistent workers)“Persistent named agent config”Same idea, declared in TOML
Dog (integration relay)“Core-pack exec orders”, optional agent wrapperShell work no longer needs an agent session
PluginsOrders — exec order (shell/orchestrator-side) or formula order (agent-driven)One automation model: trigger + formula
ConvoysConvoys — still bead-backed grouping and lineage“The implementation boundary moved”; gc sling creates convoy structure while routing
BeadsBeadsUnchanged substrate — now literally everything (mail, sessions) is one
~/gt/… role homes, path-derived identityCity dir + city.toml + .gc/; agents in agents/<name>/ with explicit dir“Directory path implies who the agent is” is gone — identity is explicit config

Muscle-memory table (most-typed commands)

gtgc / bd
gt install · gt up / gt downgc init · gc start / gc stop
gt daemon · gt status · gt dashboardgc supervisor · gc status · gc dashboard
gt sling · gt convoy · gt hook · gt readygc sling · gc convoy · gc hook · bd ready
gt mol · gt formulagc formula cook / bd mol … · gc formula list/show/cook, gc sling --formula
gt gate · gt park · gt resumegc wait / formula [steps.gate] · gc wait · gc wait ready, gc session wake, gc mail check
gt mail · gt nudge · gt peekgc mail · gc session nudge · gc session peek
gt plugingc order
gt session at mayor/ · gt send mayor "task"gc session attach mayor · gc sling mayor "<description>"
gt activity / gt feed / gt trail / gt loggc events (+ gc session peek/logs, gc supervisor logs)
gt bead / gt cat / gt show / gt closemostly plain bd (bd close etc.)

Dropped without direct equivalent — usually on purpose: gt broadcast, gt notify, gt dnd, gt escalate, gt whoami, gt checkpoint, gt patrol, gt seance, gt mq (merge queue), gt callbacks, gt issue. Hardcoded role types, path-derived config, and helper agents for shell work are explicitly gone. [command map · coming-from-gastown]

Two entries the command map hasn't caught up with. It still lists gt costs as having “no matching top-level cost accounting command today,” but v1.4.0's CLI reference ships gc costs — per-run model tokens and compute wall-seconds with a list-price estimate, read from .gc/usage.jsonl. Treat that row as stale. gc whoami also now exists, but it is not the successor to gt whoami: it shows the authenticated hosted-service account, whereas Town's whoami answered “which agent am I” — still answered by config, session metadata, and GC_* env, exactly as the map says. [CLI reference · command map]

Documented migration mistakes: importing Town's surface area instead of re-expressing intent · inventing new hardcoded roles instead of configuring agents · duplicating state outside beads when metadata suffices · assuming directory layout enforces architecture. [coming-from-gastown]

Sources: coming-from-gastown · gastown-command-map

11 · Quiz, glossary & sources

Twenty-two questions, weighted toward the mental model and work routing, with the last four on what v1.4.0 changed. Reveal, then score yourself honestly — 17+ is mastery.

Score: 0 / 0 answered (22 total)
Q1What is the universal substrate of Gas City, and name three different things that are “all just” instances of it?
The bead — “one unit with an ID, title, status, and type.” Tasks, mail messages, sessions, convoys, and epics are all beads differentiated only by type. (§2)
Q2City vs rig — define both in one line each.
City: “the local (root) pack rooted at the deployment directory” — city.toml + .gc/ state. Rig: “an external project (usually a git repo) registered with the city,” with its own bead namespace (prefix), agent scope, and hooks. (§2, §7)
Q3What's the difference between an agent and a session, and how do pools scale?
An agent is configuration — “a worker a pack defines as a prompt plus a scope and a provider.” A session is the live process when it runs. Pools scale between min_active_sessions and max_active_sessions. (§2)
Q4Controller vs supervisor — who does what, and at which scope?
Controller: per-city long-running daemon that drives the reconcile loop. Supervisor: machine-wide “typed HTTP + SSE control plane” that cities register with; serves the REST API and dashboard (127.0.0.1:8372). (§2, §8)
Q5Explain “the City is a pack” and why it matters.
The city itself is just the root pack — the same configuration unit (pack.toml-style: agents, formulas, orders) as everything it imports. So composition is uniform: one load order, one override model, from root to leaf. (§2, §7)
Q6Chain the three terms: formula, molecule, wisp.
Formula: the reusable TOML-written method. Molecule: “a formula instantiated at runtime — one root bead plus step beads.” Wisp: an ephemeral molecule (from gc sling or order dispatch) that auto-closes and gets garbage-collected on TTL. (§5)
Q7How does work reach an agent? Name the rule (in caps in the docs).
Pull, never push: gc hook runs the agent's work_query (bd ready --assignee=…, or pool label + --claim). The rule: “If you find work on your hook, YOU RUN IT” — run-what-you-find. (§5)
Q8Mail vs nudge — durability, delivery, and when each is right.
Mail = message beads (Type="message") in the store: durable, inbox = open unread beads assigned to you; reading labels it, archiving closes it. Nudge = text fired straight into a session: no persistence, no retry, lost if the session is down. Mail for work handoffs; nudge to wake or redirect. (§5)
Q9A formula goes through three stages between file and running work — name them.
TOML file → in-memory recipe (flattened steps + dependency edges) → materialized beads that outlive the file. “Work persists, so whoever picks it up next finds the same state.” (§5)
Q10What makes a formula step “ready,” and what runs when several are?
Its needs list — the step IDs that must complete first. The orchestrator gates each step on its dependencies and runs all ready steps in parallel. (§5)
Q11Your config isn't behaving. Which two commands show what actually resolved, and what does the second add?
gc config show (fully resolved TOML) and gc config explain — which adds provenance: the pack/layer each value came from. (§4)
Q12You run everything on Kubernetes. Why must tmux still be installed?
“tmux is the default session backend and the fallback, so it stays required even if you run agents on another backend.” (§6)
Q13What three things does gc init do, and what do you have afterwards?
It “bootstraps the city directory, registers it with the supervisor, and starts the orchestrator” — the city is operational immediately, no separate start step. (§3)
Q14List the five things a controller tick does, in order.
1 reload config if changed (tryReloadConfig) · 2 re-evaluate the desired agent set (pools via evaluatePool, parallel) · 3 reconcile — make session beads and running sessions match the desired list · 4 wisp GC past TTL · 5 evaluate order triggers and dispatch non-manual orders. Default tick: every 30s (patrol_interval). (§8)
Q15Describe graceful agent termination, and the guard against crash loops.
Two-pass: send Interrupt (Ctrl-C) to all sessions → wait shutdown_timeout → force-kill survivors via Stop(). Crash tracker quarantines with a sliding window: max_restarts within restart_window. (§8)
Q16Where do “desired” and “observed” state each come from in the reconcile loop?
Desired: city.toml parsed with includes and patches (fsnotify + ConfigFingerprint detect drift). Observed: Provider.ListRunning() plus session metadata. reconcileSessionBeads() repairs the divergence. (§8)
Q17Gas Town's Deacon watched for stuck agents. Where did that job go?
Into the platform: “orchestrator health patrol + config thresholds” — probe sessions, compare thresholds, publish stalls, restart with backoff. Health patrol is just an order, not a role. (§10, §8)
Q18You'd have written a Gas Town plugin. What do you build in Gas City, and what are the two flavors?
An order — a trigger (cooldown, cron, condition, event, or manual) paired with work: an exec order for shell/orchestrator-side logic, a formula order for agent-driven work. (§10, §5)
Q19Name the six primitives and the three pieces of machinery they sit on.
Primitives: Agent (who), Bead (what), Formula (how), Rig (where), Pack (configures), Event (observe). Machinery: the orchestrator (runs formulas, reconciles sessions), the bead store (durable work), the event bus (fires activity outward). “None of this machinery knows what your agents do.” (§2)
Q20v1 vs v2 formulas: what actually differs, and how do you opt in to v2?
They are peers, not a version ladder — the difference is what the engine is. Under v1 the engine is the single agent you sling to; steps are inert after apply and the shape is a parent-child molecule. Under v2 the engine is the orchestrator: steps are independently routable to many agents and pools, with check/retry/drain/tally control flow and a flat graph plus a finalize step. Opt in with [requires] formula_compiler = ">=2.0.0". Choose v2 for new work. (§5)
Q21You want an agent under a herdr city to keep running on tmux. How do you pin it?
You don't — that's the trap. The backend is a whole-city (or whole-process) choice. The per-agent session field selects a transport and accepts only acp, tmux, or omission; session = "tmux" under a herdr city is “neither honored nor rejected” and the agent runs on herdr anyway. Only session = "acp" genuinely moves an agent to a different backend. To get tmux, the city must default to tmux. (§6)
Q22You upgraded a 1.3 city and gc start aborts complaining about a city you weren't starting. What happened, and what's the first command you run on any 1.3→1.4 upgrade?
A stale, unrelated registered city with un-migrated provider config fails the registry scan and aborts startup — with a misleading hint to gc init the healthy city. Fix the offending one (gc doctor --fix inside it) or gc unregister <stale-city>. And the first command on any upgrade is gc doctor --fix, which converges pack imports, provider catalogs, project identity, retired hold labels, and managed beads/Dolt metadata before the orchestrator starts. (§3)

Glossary

TermMeaningGas Town ancestor
BeadOne unit of work — ID, title, status, type; open → in_progress → closedBead (unchanged)
CityThe root pack at the deployment directory; city.toml + .gc/ stateThe Town
RigExternal project registered with the city; own bead namespace + hooksRig
AgentWorker a pack defines: prompt + scope + provider; config, not codeRole (mayor, crew…)
SessionA running agent — live process managed by a provider
PoolAgent scaling between min/max active sessionsPolecats
FormulaReusable TOML method: steps, vars, needs edgesgt formula
MoleculeA v1 formula instantiated at runtime: container root + step childrengt mol
WorkflowA v2 formula instantiated at runtime: flat graph of independently routable step beads, root blocks on finalize
WispEphemeral bead from a v1 formula run; auto-closes, garbage-collected on TTL
Control beadOrchestration step the control dispatcher executes: check, retry, fan-out, tally, drain, scope-check, workflow-finalize
Control dispatcherStore-scoped worker that executes control beads; one required per graph-owning scope
ConvoyContainer bead grouping related work as a tracked batchConvoy
EpicOrdinary bead type for tracking; not a first-class container
OrderTrigger (cooldown/cron/condition/event/manual) + formula or scriptPlugin
EventImmutable append-only record with monotonic sequence; replayablegt feed/activity
MailMessage beads; inbox = open unread beads assigned to yougt mail
NudgeFire-and-forget text into a session to wake or redirect itgt nudge
WaitDurable session wait — block on a condition, survive it (gc wait)gt gate / gt park
PackThe unit of configuration: pack.toml + agents, formulas, orders
SlingCreate + route work in one motion (gc sling, --formula to cook too)gt sling / gt send
OrchestratorPer-city daemon driving the reconcile loop and formula runs — called the controller in engdocs and in code(distributed across roles)
SupervisorMachine-wide HTTP+SSE control plane; serves the API and hosts the dashboard SPAgt daemon
Health patrolProbe → thresholds → stalls → restart with backoff; just an orderDeacon
UpstreamNamed model-serving endpoint preset, selected per agent; renders onto each harness's env-var names
ContextNamed remote city in ~/.gc/contexts.toml; a local city always wins over the sticky default
Usage factRecorded model-token / compute-second record in .gc/usage.jsonl; what gc costs aggregatesgt costs

Sources

Everything above traces to these. All sources fetched 2026-08-09 and pinned to the v1.4.0 tag, so every quote on this page stays checkable even as main moves on.

Official — user docs

Official — contributor architecture (engdocs)

Lineage