Gas Town

Study handbook for Steve Yegge's industrialized multi-agent coding factory — the town, rigs, beads, and seven roles that turn babysitting one agent into running a fleet.

Tested against gt v0.12.0-197-gf28273a1 · 2026-07-11 Repo's newest tag is now v1.2.1 — versioning scheme doesn't line up cleanly with the dev build above; not confirmed stale, not confirmed current

1 · What Gas Town Is

Gas Town is a workspace manager and multi-agent orchestration system: it coordinates multiple AI coding agents — Claude Code, GitHub Copilot, Codex, Gemini, and others — working on different tasks from one workspace. [README]

Its creator, Steve Yegge, frames it as “a new take on the IDE for 2026” — infrastructure for running fleets of Claude Code (and other) instances instead of babysitting them one at a time. He had predicted back in March 2025 that someone would “lash the Claude Code camels together into chariots”; when nobody built it, he built Gas Town himself to solve the “tedium of running lots of Claude Code instances.” [Welcome to Gas Town]

The project's current, more sober positioning (gastownhall.ai) calls it “an orchestration layer that treats AI agent work as structured data,” built to answer accountability questions (“Who did what? Which agent introduced this bug?”) and quality-tracking questions (“Which agents are reliable? Which need tuning?”) as agents become central to engineering work — every action attributed, every agent with a track record, all work carrying provenance. [docs.gastownhall.ai] The landing page's own tagline is blunter: “Scale without limits,” and it “Works with Claude Code, Codex, Gemini and more!” [gastownhall.ai]

The README pitches the same idea as four problem/solution pairs: agents losing context on restart → work persists in git-backed hooks; manual agent coordination → built-in mailboxes, identities, and handoffs; “4-10 agents become chaotic” → “Scale comfortably to 20-30 agents”; work state lost in agent memory → work state stored in the Beads ledger. [README]

From babysitting one agent to running a fleet

Yegge frames Gas Town as the last rung of an eight-stage ladder he calls the “8 Stages of Dev Evolution”: stages 1–5 are a single agent with increasing autonomy; stage 6 is hand-running 3–5 parallel Claude Code instances; stage 7 is hand-managing 10+ agents — the practical limit for manual babysitting; stage 8 is building an orchestration system like Gas Town. He characterizes the whole arc as moving from “babysitting” individual agents, to supervising them, to managing fleets at scale — the framing this guide uses throughout. [Welcome to Gas Town]

Scale, and the Kubernetes comparison

Yegge says he personally runs “12 to 30 workers” depending on intensity, and claims Gas Town can run “20–30 at once, productively, on a sustained basis” — deliberately kept under 30 pending remote hyperscaler integration. As a scale demonstration he cites a “million-step wisp” run that solved the 20-disc Hanoi-towers MAKER problem “trivially.” At publication Gas Town was 17 days old — “75,000 lines of Go code across 2,000 commits” — with acknowledged high cost and operational overhead to run. [Welcome to Gas Town]

Yegge draws the Kubernetes comparison directly: “Both systems coordinate unreliable workers toward a goal. Both have a control plane (Mayor/Deacon vs kube-scheduler/controller-manager) watching over execution nodes (Rigs vs Nodes).” The stated difference is the goal itself: “Kubernetes asks, ‘Is it running?’ while Gas Town asks ‘Is it done?’” — Kubernetes maintains continuous desired state; Gas Town optimizes for completion — “finish this work, land the convoy, then nuke the worker and move on.” He candidly compares the system's overall complexity to “Kubernetes, or Temporal,” saying it grew by “adding components until it was a self-sustaining machine,” ending up with parts “that look a lot like Kubernetes mated with Temporal.” [Welcome to Gas Town]

The current README's scale language is more conservative in framing but higher in ceiling: “4-10 agents become chaotic” manually, Gas Town lets a team “Scale comfortably to 20-30 agents,” and its gt feed --problems view exists specifically because “At scale (20-50+ agents), spotting stuck agents in the activity stream becomes difficult.” A three-tier watchdog chain (Daemon → Boot → Deacon → Witnesses/Refineries) exists to keep agents healthy at that scale — echoing the control-plane-watching-nodes framing above. [README]

Gas City extracted this machinery into an SDK — see the Gas City guide for its own primitives; §13 of this guide maps every Gas Town concept in Parts I–III to its Gas City equivalent (or lack of one).

Sources: README · Welcome to Gas Town · docs.gastownhall.ai · gastownhall.ai

2 · The Mental Model

Five words carry almost all of Gas Town's vocabulary. Learn these and the rest of this guide is just detail.

Town — “Top-level folder containing your individual projects. The gt binary manages projects under this folder.” The README's equivalent: “Your workspace directory (e.g., ~/gt/). Contains all projects, agents, and configuration.” Structurally, a Town holds a Mayor coordinator plus multiple Rigs. [Gas Town, Decoded · README]
Rig — “A project. It's a folder tracked by a unique Git repository within your workspace.” The README calls Rigs “Project containers. Each rig wraps a git repository and manages its associated agents.” Each Rig also holds its own Crew workspace, Hooks, and Polecats. [Gas Town, Decoded · README]
Bead — the foundational data structure: “a special kind of issue-tracker issue, with an ID, description, status, assignee, and so on,” stored as JSON and tracked in git — that was Yegge's original telling; ground truth confirms Dolt is now the sole storage backend, no embedded mode and no SQLite (see §12). Beads operate at two levels — Rig-level project work and Town-level orchestration work — and “pinned beads” give agents, roles, and hooks a persistent identity that never closes, unlike ordinary task beads, which move open → in_progress → closed. [Welcome to Gas Town · Gas Town, Decoded]
Role — a job description, not a person: Gas Town names seven worker roles (Mayor, Polecats, Refinery, Witness, Deacon, Dogs, Crew), each a job description that any number of ephemeral or persistent agent sessions can fill, rather than a fixed individual. Polecats show the pattern clearest — “Worker agents with persistent identity but ephemeral sessions” — so the role, not any one running process, is the durable unit. [Welcome to Gas Town · README]
Sling — the verb for putting work onto an agent's hook. It names the work-distribution step of the Mayor-led workflow (MEOW): work gets distributed by slinging beads to agents via their hooks. [README]
Gas Town, at a glance Town Hall M D Mayor + Deacon P C Polecats & Crew beads Rig A git repo Crew · Hooks · Polecats Rig B git repo Crew · Hooks · Polecats Rig C git repo Crew · Hooks · Polecats Dolt — Records Office every bead, durably, in Dolt town / rig structure roles (agents) beads / work automatic flow (GUPP)
One Town, several Rigs, one road between them. Work drops from the Mayor's Town Hall through Polecats and Crew onto the road as beads, into whichever Rig it's addressed to — and every Rig's history lands durably in Dolt.

Sources: Gas Town, Decoded · README · Welcome to Gas Town

Stage 03 · Town Up

Before there is a single rig or a single bead, there is the town's own infrastructure: a background daemon, a Dolt-backed datastore, and two singleton agents (the Mayor and the Deacon). Stage 0 is bringing that infrastructure up — and learning to read its status honestly, including the one time it lied to us.

Native prerequisites: Git 2.20+ (worktree support), Go 1.26.2+ (Linux/Windows and macOS source builds only), Beads bd 0.57.0+, Dolt, sqlite3 (convoy DB queries), ICU4C dev headers (source builds), tmux 3.0+ (required for gt up and the tmux-backed roles), and the Claude Code CLI as the default runtime. [README] On macOS, brew install gastown installs gt, bd, and dolt together — avoid go install there, since Gatekeeper kills the unsigned binary; Linux/Windows use go install plus a separate Dolt install. [README]

Homebrew Core lags behind Gas Town's own releases — check before you install or upgrade. As of this writing, Homebrew Core's gastown formula is pinned to v1.1.0 (released 2026-05-07), while Gas Town itself has shipped two versions past that — v1.2.0 (2026-05-30) and v1.2.1 (2026-06-06) — neither of which ever landed in Core. There's an open upstream issue tracking the failed release pipeline for both, opened 2026-06-04 and still unresolved. The project's own tap does carry the real latest version, so install (or fix an already-stale install) from it directly, using the fully-qualified formula name — this auto-adds the tap, no separate brew tap needed:

brew uninstall gastown
brew install gastownhall/gastown/gastown
gt version

Keep using the qualified name for future upgrades too (brew upgrade gastownhall/gastown/gastown) — plain brew upgrade gastown will silently re-pin you to whatever Core still has. Swapping the formula this way only replaces the installed binary; it doesn't touch your existing ~/gt HQ directory or its config. [Homebrew Core formula page] [gastownhall/gastown issue #4179] [gastownhall/homebrew-gastown tap]

gt install ~/gt --shell --git creates the headquarters (HQ) directory at ~/gt: --shell installs shell integration and turns Gas Town on globally, --git initializes HQ as its own git repo (set git config --global user.name/email first, so the initial commit has a valid identity). The quick-start one-liner strings the whole first run together: gt install ~/gt --shell --git && cd ~/gt && gt up && gt doctor --fix && gt config agent list && gt mayor attach — "and tell the Mayor what you want to build!" [README]

The daemon — one of the long-lived services gt up starts alongside Dolt, the Deacon, and the Mayor. gt doctor / gt doctor --fix is the paired health-check/auto-repair command you run right after — --fix clears warnings gt install doesn't preempt. [README · reference.md]
Dolt — the other long-lived service gt up starts: a SQL server the daemon health-checks and restarts like everything else. Ground truth's own gt status output confirms it by PID and port below; Stage 2 introduces what actually lives inside it. [README]

gt up boots all of it in one idempotent command — Dolt, the daemon, the Deacon, the Mayor, and (once rigs exist) per-rig Witnesses and Refineries. Verify afterward with gt doctor --fix, which clears warnings gt install doesn't preempt. Teardown is layered too: gt shutdown && gt up fixes env-var mismatches in a running session. The docs describe an emergency kill switch, gt stop --all / gt stop --rig <name> — but ground truth found this doesn't exist on v0.12.0 (Error: unknown command "stop" for "gt"). The real commands are gt down (infra only, reversible) or gt down --all (full shutdown, orphan cleanup) town-wide, and gt rig stop <name> per rig. [reference.md] [ground truth: local gt v0.12.0-197-gf28273a1]

gt status, line by line

Ground truth, before any service is started:

Services: daemon (stopped)  dolt (stopped, :3307)  tmux (-L default, PID 62915, 2 sessions, /tmp/tmux-501/default)
🎩 mayor ○ [zclaude/GLM-5]
🐺 deacon ○ [zclaude/GLM-5]
No rigs registered. Use 'gt rig add' to add one.
  • daemon (stopped) / dolt (stopped, :3307) — neither service is running yet; :3307 is the port Dolt will bind to once it starts.
  • tmux (-L default, PID 62915, 2 sessions, …) — the shared tmux server backing every role's session. These 2 sessions pre-date Gas Town entirely; nothing here ever touches them.
  • 🎩 mayor ○ [zclaude/GLM-5] / 🐺 deacon ○ [zclaude/GLM-5] — the two singleton roles already show their resolved agent (here, the custom zclaude alias running model GLM-5) even while stopped; means idle/no active turn, not "misconfigured."
  • No rigs registered. — that's Stage 1, next.

[ground truth: local gt v0.12.0-197-gf28273a1]

Ground truth, after gt up:

Services: daemon (PID 10405)  dolt (PID 10948, :3307, ~/gt/.dolt-data)  tmux (-L default, PID 62915, 5 sessions, /tmp/tmux-501/default)
🎩 mayor  ○ [zclaude/GLM-5]
🐺 deacon  ○ [zclaude/GLM-5]
No rigs registered.
  • daemon (PID 10405) — the watchdog process is alive.
  • dolt (PID 10948, :3307, ~/gt/.dolt-data) — the Dolt SQL server is up, listening on 3307, storing its data under ~/gt/.dolt-data.
  • tmux (…, 5 sessions, …) — grew by exactly 3: hq-boot, hq-deacon, hq-mayor, each its own tmux session named after its agent bead. The 2 pre-existing sessions are still there, untouched.
  • 🎩 mayor ○ / 🐺 deacon ○ — still , and that's the trap: ground truth confirms this glyph tracks hook/heartbeat state, not raw tmux liveness — gt status -v printed hq-mayor stopped even while the hq-mayor tmux pane was demonstrably alive and running Claude Code. Don't read /"stopped" as "no tmux session"; check tmux ls directly if that distinction matters.
  • No rigs registered. — unchanged; that's still Stage 1.

[ground truth: local gt v0.12.0-197-gf28273a1]

Stage 0: gt up → gt status gt up idempotent — safe to re-run brings up 5 service kinds Daemon health-checked via gt doctor --fix PID 10405 Dolt SQL server, :3307 sole datastore PID 10948 Deacon + Mayor hq-deacon / hq-mayor sessions tmux: 2 → 5 gt status daemon (PID …) dolt (PID …, :3307) was: (stopped) → now: running
One command, five service kinds: gt up starts the daemon, then Dolt, then the Deacon and Mayor sessions — gt status is how you confirm all of it actually happened.
Lab 1 — Bring the town up, and read gt status for real

Goal: start the daemon and Dolt, and confirm they're really up — not just that a command said so.

  1. Check the baseline before starting anything:

    cd ~/gt && gt status --fast

    Expect: daemon (stopped), dolt (stopped, :3307), and "No rigs registered." — see the annotated block above.

  2. Bring services up:

    gt up

    Expect, honestly: ground truth's first run reported ✖ Dolt: Dolt server failed to start, ✖ Daemon: daemon failed to start, ✓ Deacon: hq-deacon, ✓ Mayor: hq-mayor, then ✗ Some services failed to start / Error: not all services started, exit code 1 — while a gt status check run immediately after showed Dolt and the daemon both fully up already. gt up's own readiness window can be shorter than a slow-starting service; treat one failing exit code as a prompt to check gt status, not as gospel.

  3. Re-run it (it's idempotent) and confirm with status:

    gt up
    gt status

    Expect: ✓ Dolt: already running / ✓ Daemon: PID 10405 / ✓ Deacon: hq-deacon / ✓ Mayor: hq-mayor / ✓ All services running., then the full annotated gt status block above.

  4. Confirm Dolt is genuinely listening, not just claimed:

    lsof -i :3307

    Expect: a dolt … (LISTEN) line plus established connections from the gt, dolt, and beads processes.

You should now see gt status reporting daemon (PID …) and dolt (PID …, :3307, ~/gt/.dolt-data), tmux up by 3 sessions (hq-boot, hq-deacon, hq-mayor), and lsof -i :3307 showing a real LISTEN.

Two failure drills

SymptomWhat ground truth showedFix
gt up reports a service "failed to start" and exits 1 A real, reproducible false negative: the readiness check fired before Dolt/the daemon finished starting. A gt status check run seconds later showed both fully up. Don't trust a single gt up exit code. Run gt status; if it confirms services are up, re-run gt up to get a clean ✓ All services running. exit.
Suspected port conflict on Dolt's :3307 lsof -i :3307 while stopped returned nothing — no listener at all, matching gt status's (stopped, :3307) reading exactly. After gt up, re-run lsof -i :3307: it should show a dolt process in LISTEN state. If it shows a different process holding the port, that's a real conflict — stop that process or reconfigure Dolt's port before retrying gt up.

Cleanup: nothing to tear down here — Stage 1 needs these services running. To pause for the day instead, gt down stops infrastructure only (reversible); gt down --all also stops polecat sessions and cleans up orphans. [ground truth: local gt v0.12.0-197-gf28273a1]

Stretch: tail the Dolt log right after gt up: gt dolt logs. Ground truth's own run surfaced a pre-existing schema-skew error here — column "depends_on_issue_id" could not be found in any table in scope — dated as far back as 2026-05-14. Stage 2's Lab 3, below, explains why this is a real, town-level defect rather than anything you just did. [ground truth: local gt v0.12.0-197-gf28273a1]

Sources: README · reference.md · glossary.md · Homebrew Core formula page · gastownhall/gastown issue #4179 · gastownhall/homebrew-gastown tap · ground truth (local gt v0.12.0-197-gf28273a1)

If your town is healthy: where to read what a clean run looks like. Starting in Lab 2, this guide's own dry run hit a real, town-wide, pre-existing Dolt schema defect (fully explained in §4, root cause in §12) before a single bead could ever be created — so Labs 2, 3, 4, and 5-Part-A never show a bead going open → in_progress → closed, a polecat spawning, or a convoy landing, live and end to end. That intended shape is still real, documented, and sourced — just not demonstrated this session. Read it in the concept prose (not just the lab steps) of §5 (the open → in_progress → closed bead lifecycle and mail round trip), §4 (the sling → polecat → branch → Refinery-merge pipeline), and §6 (how a dependency edge is meant to serialize two tasks inside a convoy). Use those sections as your one reference for "what the docs say a healthy run looks like," then treat each lab's honest blocked outcome as this specific town's own story, not the documented default.

Stage 14 · First Rig, First Sling

A rig is Gas Town's word for a project. Stage 1 registers one, then puts a single unit of work on it — and, honestly, this is where our own dry run ran into this town's real, pre-existing Dolt schema defect for the first time.

gt rig add <name> <url> clones a repository into HQ as a rig (--prefix mp sets a custom beads prefix). Rig names accept letters, digits, and underscores only — no hyphens, dots, spaces, or path separators ("Use my_project instead of my-project"). Registration effects reach further than the clone itself: a rig config.json records type, name, git_url, default_branch (auto-detected from the remote — it becomes both the Refinery's merge target and the polecat base branch), and beads.prefix; and on every gt rig add, Claude settings hooks are auto-synced for all of the new rig's targets — crew, witness, refinery, polecats. [README · reference.md · HOOKS.md]

Crew and Polecats split the same rig two ways: Crew is persistent and human-managed — it pushes to main directly, and nothing patrols it — while Polecats are transient and Witness-managed, work on branches the Refinery merges, and are cleaned up automatically once done. [overview.md] gt sling <bead-id> <rig> assigns an existing bead to a polecat in that rig; add --agent codex to override the runtime for just this spawn, or --on gt-def <rig> to sling with a workflow (formula) template. The recommended order is convoy-first, then sling — but a bare sling auto-creates its own convoy, so "even a swarm of one gets convoy visibility." [reference.md]

.gastown-ignore — Gas Town's shell integration walks upward from $PWD and disables itself for any directory tree containing a .gastown-ignore file. It gets created automatically when you answer "never" to the auto-offered gt rig quick-add prompt (touch "$repo_root/.gastown-ignore" → "Created .gastown-ignore - won't ask again for this repo."). This guides repo itself carries one at its root, so Gas Town's shell hook leaves it alone entirely — a real, checked-in example of the mechanism, not a hypothetical. [internal/shell/integration.go]

When is a plain repo-as-rig the right call? Whenever there's a real, reachable git remote, gt rig add <name> <url> is the whole story. For a local, no-remote rig, the documented path is scripts/bootstrap-local-rig.sh (which wraps gt rig add <name> <git-url> --local-repo <path>) — not --adopt. --adopt is meant only for registering an already-assembled real rig whose directory shape Gas Town can trust as-is, and, as Lab 2 below found the hard way, it has sharp edges when there is no remote at all. [local-rig-bootstrap.md]

Stage 1: gt sling, and where it actually stopped gt sling <bead> <rig> needs an existing bead ID first bd create makes the bead to sling Observed this session Documented — not reached this session Error 1062 (HY000) duplicate primary key: [wisps] schema migration 0040 fails town-wide, pre-existing — seen in logs dated 2026-05-14 No bead ID ever existed — gt sling itself was never invoked Bead created (open) → Polecat spawned in rig Works branch, gt done Bead closed real error, this session documented, not reached
What this lab was supposed to trace (right, dashed) vs. what actually happened (left, solid): a real, town-wide Dolt schema defect blocked bd create before any bead existed to sling — so the polecat-spawn → work → close pipeline is shown as documented, not demonstrated.
Lab 2 — Register a rig, and try to sling one task

Goal: register a scratch repo as a rig and sling one task to it — and see, honestly, what happens when this town's real Dolt schema defect gets in the way.

  1. Create a scratch repo:

    mkdir -p ~/gt-lab-scratch && cd ~/gt-lab-scratch && git init -b main
    echo "# gt lab scratch" > README.md && git add -A && git commit -m "init"

    Expect: a normal git init and first commit (ground truth's real commit was c16d0b0).

  2. The obvious-looking rig-add command doesn't work:

    cd ~/gt && gt rig add ~/gt-lab-scratch

    Expect, honestly: this fails — gt rig add takes two required positional arguments, <name> <git-url>, not a single path. Routing around it with --local-repo/--adopt pointed at an external path fails too:

    Error: adopting rig: directory does not exist: /Users/you/gt/gt_lab_scratch
    Error: invalid git URL ... expected a remote URL (e.g. https://, git@host:, ssh://, s3://)
    To register a local directory, use: gt rig add gt_lab_scratch --adopt
  3. The real, working sequence: physically move the repo inside the town, then adopt it in place:

    mv ~/gt-lab-scratch ~/gt/gt_lab_scratch
    gt rig add gt_lab_scratch --adopt --force
    Adopting existing rig gt_lab_scratch...
    ✓ Initialized beads database
    ! Could not create witness agent bead: Error: database not initialized: issue_prefix config is missing
    ! Could not create refinery agent bead: Error: database not initialized: issue_prefix config is missing
    ✓ Rig gt_lab_scratch adopted
    Repository:
    Prefix: gls

    Expect: that blank Repository: line is real — --adopt --force never sets a git_url, so rigs.json records "git_url": "" for this rig. gt doctor --rig gt_lab_scratch confirms the fallout: "Witness structure incomplete", "Refinery structure incomplete", "Mayor structure incomplete (clone requires repo URL)" — a purely local, remote-less rig registered this way can never actually run a Witness or Refinery, so a sling to it can never fully succeed end to end.

  4. Unblock the beads wiring:

    gt doctor --fix
    gt dolt init-rig gt_lab_scratch

    Expect: gt doctor --fix fixes the prefix wiring ("changing issue_prefix from 'issue_prefix (not set)' to 'gls'") — and, as a real, unprompted side effect, classifies the running hq-boot/hq-deacon/hq-mayor tmux sessions as zombies and kills all three (confirmed via tmux ls dropping from 5 sessions back to 2 — the two pre-existing, unrelated sessions were never touched). gt dolt init-rig then registers the rig's database with the running Dolt server: ✓ Initialized rig database "gt_lab_scratch".

  5. Try to create the task bead to sling:

    cd ~/gt/gt_lab_scratch
    bd create --title="Create hello.txt" --type=task --description="Create hello.txt containing the single line: hello gas town" --json

    Expect, honestly — this is where Lab 2 actually stopped:

    Error: failed to open database: failed to initialize schema: schema migration: migration 0040_ignored_tables_also_nonlocal_tables.up.sql: Error 1062 (HY000): duplicate primary key given: [wisps]

    bd doctor in the rig confirms a broken schema: "Schema Compatibility: Database schema is incomplete or incompatible", "Dependency Cycles: ... column 'depends_on_issue_id' could not be found in any table in scope", "Blocked State: ... table 'i' does not have column 'is_blocked'". bd doctor --fix does not resolve it — the same errors recur.

This is real, and it is not something you did wrong. It's town-wide, not rig-specific: the identical error appears against the hq town-level database in logs dated both 2026-05-14 (pre-dating this entire guide) and fresh 2026-07-12 entries. Town-level gt mail inbox mayor/, gt convoy list, and gt escalate list all fail with the exact same error; bd list (a query with no dependency join) works fine at both town and rig level. The town's own Deacon independently detected and escalated CRITICAL to the Mayor before gt doctor --fix was even run in this session, then halted its own patrol loop ("Dolt schema blocked — patrol halted, nudged Mayor, awaiting response"). A guide can't paper over a defect this load-bearing — Stage 2's Lab 3 hits the exact same wall on the mail-read path. [ground truth: local gt v0.12.0-197-gf28273a1]

What would have come next, per documented (not exercised) behavior: gt sling <bead-id> gt_lab_scratch assigns the bead to a polecat in the rig, which works a branch and closes the bead on gt done. Ground truth could not observe the bead-ID format, polecat spawn, tmux session naming, or the final closed-bead git log this session — the schema defect blocks bd create before a polecat would ever exist to spawn. [ground truth: local gt v0.12.0-197-gf28273a1]

You should now see the identical schema-migration error ground truth hit — Error 1062 (HY000): duplicate primary key given: [wisps] on create, and bd doctor failing its Dependency Cycles / Blocked State checks on the missing depends_on_issue_id/is_blocked columns. If your town's Dolt schema is healthy instead, you'll see a normal bead ID (e.g. gls-1) and can continue straight to the gt sling step above.

Cleanup:

cd ~/gt && gt dolt cleanup --force
rm -rf ~/gt/gt_lab_scratch

gt dolt cleanup --force removes the orphaned rig database (a first attempt without --force fails: "has user tables — use --force to remove"); gt dolt list afterward shows only hq (town beads); rigs.json returns to {"version": 1, "rigs": {}}. [ground truth: local gt v0.12.0-197-gf28273a1]

Stretch: re-sling with an explicit runtime override — gt sling <bead-id> <rig> --agent codex (confirmed in gt sling --help's real flag list: --agent string) overrides just this spawn's runtime. Ground truth's own attempt to exercise this was blocked at the same bd create step, so it's documented-but-unexercised here too — if your bead creation succeeds, this is the flag to reach for. [ground truth: local gt v0.12.0-197-gf28273a1]

Sources: README · reference.md · overview.md · HOOKS.md · local-rig-bootstrap.md · shell integration.go · ground truth (local gt v0.12.0-197-gf28273a1)

Stage 25 · Beads & Mail

Every unit of state in Gas Town — a task, a message, an agent's own identity — is a bead. Stage 2 is the CLI for working with them directly, plus the mail and escalation layers built on top of the same primitive.

Core bd verbs: bd ready (work with no blockers), bd list --status=open|in_progress, bd show <id>, bd create --title="..." --type=task, bd update <id> --status=in_progress, bd close <id>, and dependency edges via bd dep add <child> <parent> (the child depends on the parent). The documented state trio for an ordinary task bead is open → in_progress → closed. [reference.md]

A bead is bigger than "task," though. Ground truth's own bd list --include-infra --all at the town level returned a long tail beyond plain tasks: agent beads (hq-mayor, hq-deacon) and dozens of hq-wisp-* patrol/dog molecule beads — proof that "agent" and "wisp/molecule" are as real and listable a bead kind as "task." Mail is the same trick again: mail messages are type=message beads routed by gt mail. [mail-protocol.md] [ground truth: local gt v0.12.0-197-gf28273a1]

Routing runs on prefix: routes live in ~/gt/.beads/routes.jsonlhq-* addresses route to ~/gt/.beads/ (Mayor mail, cross-rig coordination); <rig-prefix>-* routes to ~/gt/<rig>/mayor/rig/.beads/. Debug with BD_DEBUG_ROUTING=1 bd -C <owning-root> show <id>; note that bd --global is not the town database — it targets a separate shared-server beads_global. [reference.md]

Mail CLI: gt mail inbox, gt mail read <id>, gt mail ack <id>, gt mail send <addr> -s "Subject" -m "Body", or gt mail send --human -s "..." to reach the overseer directly. Addresses are <rig>/<role> or <rig>/<type>/<name> — e.g. greenplace/witness, greenplace/polecats/nux, mayor/, deacon/. Structured protocol messages carry their own types: POLECAT_DONE (polecat→witness), MERGE_READY (witness→refinery), MERGED / MERGE_FAILED / REWORK_REQUEST (refinery→witness), RECOVERED_BEAD / RECOVERY_NEEDED (witness→deacon), HELP (any→escalation target), and 🤝 HANDOFF (agent→self/successor). [mail-protocol.md]

Mail vs. nudge. Default to gt nudge — ephemeral tmux delivery, zero storage, lost if the session dies. Reach for gt mail send only when the message must survive the recipient's session death: every mail creates a wisp bead plus at least one Dolt commit. Budgets are tight by role — polecats 0–1 mail per session, Dogs zero, patrol agents protocol messages only. [mail-protocol.md]

Escalation shares the same infrastructure: gt escalate "topic" defaults to MEDIUM; -s CRITICAL|HIGH|MEDIUM picks a severity that routes per ~/gt/settings/escalation.json. Ground truth's own file matches the documented structure exactly:

critical → [bead, mail:mayor, email:human, sms:human]
high     → [bead, mail:mayor, email:human]
medium   → [bead, mail:mayor]
low      → [bead]
stale_threshold: 4h, max_reescalations: 2
contacts: {}

That last line is a real, honest detail: contacts: {} is empty on this install, so the email:human/sms:human routes exist structurally but have nobody configured to actually receive them yet. Round it out with gt escalate ack <bead-id>, gt escalate list [--stale|--unacked], gt escalate stale (bumps severity MEDIUM→HIGH→CRITICAL after the stale threshold, max 2 re-escalations), and gt escalate close <bead-id> --reason=.... [escalation.md] [ground truth: local gt v0.12.0-197-gf28273a1]

Stage 2: the documented lifecycle vs. what Lab 2/3 actually reached open bd list --status=open bd update --status= in_progress in_progress a polecat has the hook bd close / gt done closed terminal — Refinery merged it documented states (bd/gt --help flags) — dashed = not demonstrated live this session bd create attempt bd create (this session) Schema defect: no open bead created column "depends_on_issue_id" / "is_blocked" missing blocks any query joining `dependencies`, town-wide — pre-existing, not caused by these labs bead states (documented) real error, this session
The three-state lifecycle (open → in_progress → closed) is confirmed from real bd/gt CLI flags, but this session's own attempt to create a bead never produced an open row to begin with — the same schema defect from Lab 2 got there first.
Lab 3 — List real beads, then try mail

Goal: list real beads at the town level, then try to read and send mail — using the exact syntax ground truth confirmed, including where it actually stopped working.

  1. List everything at the town level:

    bd list --include-infra --all

    Expect: a long list of pre-existing hq-* beads — agent beads (hq-mayor, hq-deacon) and dozens of hq-wisp-* patrol/dog molecule beads. None of these are from anything you just did; they pre-date this session. This query works because it doesn't join the dependencies table.

  2. Read the Mayor's inbox:

    gt mail inbox mayor/

    Expect, honestly — the same defect from Lab 2, on a different query path:

    Error: listing messages: Error: search count issues: Error 1105 (HY000): column "depends_on_issue_id" could not be found in any table in scope
  3. Documented syntax (not exercised this session, due to the blocker above):

    gt mail send mayor/ -s "Subject" -m "Body"
    gt mail read <message-id|index>

    Address formats confirmed via --help: mayor/, <rig>/witness, <rig>/refinery, <rig>/<polecat>, <rig>/crew/<name>, plus --human/--self special cases.

You should now see the identical schema-defect error as Lab 2 — this time on the mail-read path instead of bead-create. That's the same narrow-but-load-bearing defect (anything joining dependencies breaks, town-wide), not a new problem: bd list above worked precisely because it skips that join.

Cleanup: nothing to clean up — no bead or mail message was ever successfully created this session. If your town's schema is healthy and you did send a real message, gt mail ack <id> clears it from the inbox once read. [ground truth: local gt v0.12.0-197-gf28273a1]

Stretch: once gt mail inbox works, try the escalation path: gt escalate "topic" defaults to MEDIUM, which — per your town's own ~/gt/settings/escalation.json shown above — routes a bead plus mail to the Mayor. gt escalate list [--stale|--unacked] shows what's open; gt escalate ack <bead-id> acknowledges one. [escalation.md]

Sources: reference.md · mail-protocol.md · escalation.md · ground truth (local gt v0.12.0-197-gf28273a1)

Stage 36 · Convoys

A convoy is Gas Town's tracked batch of work: a persistent bead that bundles issues from any rig so their progress can be watched as one unit. Stage 3 is creating one, growing it, and — the honest part — finding out that this session's real Dolt schema defect blocked watching one actually serialize.

A convoy is a persistent tracking unit for batched work, stored as a town-level bead with ID prefix hq-cv-*; it can track issues from any rig via a tracks relation that is non-blocking, additive, and cross-rig. [convoy.md] Create one with gt convoy create "Feature X" gt-abc gt-def --notify overseer — single-issue convoys are normal too ("still creates convoy for dashboard visibility"); repeat --notify for multiple subscribers, e.g. --notify mayor/ --notify --human. [convoy.md]

Grow an existing convoy with gt convoy add hq-cv-abc gt-issue1 gt-issue2 ..., or build one straight from planning output with gt convoy create --from-epic gt-epic-abc, which BFS-walks the epic's parent-child hierarchy and tracks only slingable leaf types (task, bug, feature, chore — sub-epics and decisions are recursed into but not themselves tracked). A bare gt sling bd-xyz beads/amber with no existing convoy auto-creates one ("Work: bd-xyz") and tracks the issue — "Even a swarm of one gets convoy visibility." [convoy.md]

Convoy and swarm name different things: the convoy persists (hq-cv-*); the "swarm" is ephemeral — just whichever workers currently happen to be on the convoy's issues. A "stranded convoy" has ready work but no polecats assigned, and needs attention. Lifecycle runs open → (all tracked issues close) → closed/landed, with a "Convoy Landed" notification listing the issues and duration; adding an issue to a closed convoy auto-reopens it. [convoy.md]

Dependency edges are not a convoy-level concept — a convoy tracks a batch, it does not itself encode ordering. Ground truth's own gt convoy create --help confirms the real create syntax (gt convoy create <name> [issues...] [flags]) and confirms ordering between two tasks is expressed at the bead level instead: bd dep add <blocked-id> <blocker-id> (shorthand: bd dep <blocker-id> --blocks <blocked-id>). Two independent tasks in the same convoy run in parallel; a dependency edge between them forces serialization — the blocked bead simply never becomes ready until its blocker closes. [ground truth: local gt v0.12.0-197-gf28273a1]

View split: gt convoy status [convoy-id] is the cross-rig "status of this batch" view; gt rig status <rig> is "what's everyone in this rig working on." Dashboard commands: gt convoy list (active only), --all (include landed), --status=closed, --json. [convoy.md]

Drift: gt convoy show vs. gt convoy status. The README's Key Commands table (and its manual-workflow example) uses gt convoy show [id]; docs/concepts/convoy.md and reference.md both consistently use gt convoy status [id]. Ground truth settles it: the real gt convoy subcommand list is add, check, close, create, land, launch, list, stage, status, strandedshow does not exist at all on v0.12.0. [README] [ground truth: local gt v0.12.0-197-gf28273a1]

Better Stack's framing of the same machinery, in plainer language: the Mayor decomposes a request into beads and "groups [them] into Convoys for execution," which "allows Gas Town to track dependencies and manage the flow of work" — the convoy is the batch-visibility layer Stage 4's delegation flow (next) hangs its work on. [Better Stack guide]

Stage 3: a convoy tracking two dependent beads Convoy hq-cv-abc123 · tracks 2 issues gt convoy create "Deploy v2.0" gt-abc bd-xyz gt-abc blocker — closes first bd-xyz blocked — waits on gt-abc bd dep add bd-xyz gt-abc blocks / serializes — documented, not executed live swarm: polecat(s) assigned once bd-xyz is ready — never spawned this session What actually happened this session gt convoy list → Error 1105 (HY000): column "depends_on_issue_id" could not be found in any table in scope bd create was already blocked upstream (Stage 1 Lab 2) — no real bead ID ever existed to track town-wide, pre-existing — the same defect, a third time documented concept / real syntax documented pipeline step, not executed real error, this session
The convoy/bead/dependency shapes are real, confirmed CLI syntax (gt convoy create --help, bd dep add) — but nothing in this container was actually created or watched live: bd create was already blocked (Stage 1), and gt convoy list hit the identical schema defect on its own query path.
Lab 4 — A two-task convoy with a dependency edge

Goal: create a convoy tracking two tasks where one depends on the other, then watch whether the blocked task waits for the blocker to close — and, honestly, see how far this session's real Dolt schema defect let that go.

  1. Confirmed real syntax (gt convoy create --help):

    gt convoy create "Deploy v2.0" gt-abc bd-xyz

    Expect: a convoy bead with prefix hq-cv-* tracking both issues. Ordering between them is a separate step — a convoy doesn't itself encode a dependency edge.

  2. Order the two tasks with a real dependency edge:

    bd dep add bd-xyz gt-abc

    Expect: bd-xyz is now blocked on gt-abc — it should not appear in bd ready until gt-abc closes.

  3. Watch the convoy dashboard for serialization:

    gt convoy list
    gt convoy status hq-cv-<id>

    Expect, honestly — this is where Lab 4 actually stopped. Ground truth's real attempt returned:

    Error: listing convoys: bd list: Error: search count issues: Error 1105 (HY000): column "depends_on_issue_id" could not be found in any table in scope

Same defect as Stage 1/2 — not a new problem. This session could not create two real dependent task beads to begin with (bd create itself is blocked town-wide, see Stage 1's Lab 2) and could not list or watch a convoy either — the identical depends_on_issue_id schema defect, this time on gt convoy list's own query path. Whether two tasks genuinely serialize on a dependency edge — the entire point of this lab — could not be observed live this session. That is recorded here as a partial/blocked observation, not papered over with an invented "it worked." [ground truth: local gt v0.12.0-197-gf28273a1]

You should now see the identical column "depends_on_issue_id" could not be found error on gt convoy list that Stage 1/2 hit on bd create and gt mail inbox — the same narrow-but-load-bearing defect, a third time. If your town's schema is healthy, you'll instead see bd-xyz absent from bd ready until gt-abc closes, then the convoy landing once both do.

Cleanup: nothing was created this session to clean up — both the dependent beads and the convoy itself failed to materialize. [ground truth: local gt v0.12.0-197-gf28273a1]

Stretch: once your town's convoy dashboard works, try gt convoy create --from-epic gt-epic-abc against a real epic and compare its BFS-selected leaf set to bd show gt-epic-abc --children. [convoy.md]

Sources: convoy.md · README · Better Stack guide · ground truth (local gt v0.12.0-197-gf28273a1)

Stage 47 · Delegation

Stage 4 is the actual payoff of everything Stages 0–3 set up: stop typing individual commands and hand the Mayor a goal instead. Attach once, state what you want, and let decomposition, convoys, and the patrol loops keep work moving without you polling. Ground truth could only partly confirm the decomposition step this session — the honest version of that is below.

Entry point: gt mayor attach, then one comprehensive prompt covering everything — database, APIs, frontend, testing, deployment. "The more detail you provide upfront, the better the Mayor's plan will be." The documented flow: user goal → Mayor decomposes → beads grouped into convoys → polecats execute in parallel on isolated git branches — this is "stop babysitting," made concrete. [Better Stack guide]

MEOW, still unreconciled — now checked against the live CLI. The README expands MEOW as "Mayor-Enhanced Orchestration Workflow" (this same seven-step pattern); the repo's own docs/glossary.md expands it as "Molecular Expression of Work" — decomposition via Beads, Epics, Formulas, and Molecules. Ground truth grepped every --help transcript captured this session (~1,700 lines, including sling, convoy, mail, mol, formula, config) for "MEOW," "Enhanced," and "Molecular" — zero matches, all three terms. The live CLI is silent on the acronym entirely; it does not adjudicate the conflict. Present both expansions as documented-but-unreconciled rather than picking a winner. [glossary.md] [ground truth: local gt v0.12.0-197-gf28273a1]

GUPP is the engine underneath delegation staying unattended: the "Gas Town Universal Propulsion Principle" — "If there is work on your Hook, YOU MUST RUN IT." A delegated agent's startup contract is mechanical: (1) gt hook — work hooked → execute immediately; (2) hook empty → check mail for attached work; (3) nothing anywhere → escalate to the Witness. Orientation commands: gt hook, gt prime (inline formula checklist), bd show <issue-id>. [glossary.md] [propulsion-principle.md]

Completion path: a polecat runs gt done → branch pushed, MR bead created → the Refinery batches pending MRs, runs verification gates on the merged stack, merges all if green, bisects to isolate the failure if red — "a Bors-style merge queue — polecats never push directly to main." Merge-queue CLI: gt mq list|next|submit|status|retry|reject, plus integration branches for epics (gt mq integration create <epic-id> ... status ... land). [README] [reference.md]

Session cycling keeps long-running delegation from silently stalling: an agent notices context filling → gt handoff (mails itself) → the manager kills and restarts the session → the new session reads the handoff mail. gt handoff --shutdown terminates outright (polecats). Related: gt session stop <rig>/<agent>, gt peek <agent>, gt nudge <agent> "msg"; session archaeology via gt seance (lists discoverable predecessor sessions, --talk <id> for a full-context conversation). [reference.md]

The standing form of delegation is the patrol loop: Deacon, Witness, and Refinery each cycle gt patrol new (root-only patrol wisp) → gt prime (inline checklist) → work steps → gt patrol report --summary "...", which atomically closes the current cycle and starts the next — this is what keeps work moving between your check-ins without anyone babysitting it. Dispatch is throttle-able: default scheduler.max_polecats = -1 dispatches immediately on sling; gt config set scheduler.max_polecats 5 switches to daemon-managed deferred dispatch (gt scheduler status|pause|resume). [reference.md] [README]

Stage 4: overseer → Mayor → beads/convoy → polecats → refinery Overseer (you) gt mayor attach Mayor decomposes the goal Beads → Convoy hq-cv-* tracks the batch Polecats parallel branches Refinery merge queue, Bors-style decompose gt sling gt done → MR What actually happened this session (not the flow above) Deacon nudged Mayor: "CRITICAL from Deacon: Dolt hq schema broken —…" (08:49:18) gt status -v afterward: hq-mayor stopped / agent: zclaude/GLM-5 / hook: (none) no durable hook attached — an ephemeral nudge alone didn't stick (the exact mail-vs-nudge hygiene rule from Stage 2, observed live) Beads/Convoy → Polecats → Refinery never reached — no bead could be created this session (Stage 1 Lab 2's schema defect) documented behavior only, not observed live documented role / concept documented pipeline step, not executed real event, this session
The overseer → Mayor → beads/convoy → polecats → refinery chain is documented end to end, but this session only reached the first hop, and not in its documented form: a Deacon nudge (not an overseer goal) reached the Mayor, and left no durable hook — decomposition into beads, convoys, and polecats was never observed live.
Lab 5 — Hand the Mayor a tiny two-part goal, then switch cost tiers

Goal: delegate a small two-part goal to the Mayor and watch it decompose — honestly capped by what this session's blocker actually allowed — then run the one part of Stage 4 that has nothing to do with the broken schema: switching cost tiers and back.

Part A — delegation (partial observation).

  1. Confirmed entry points, from --help: interactive gt mayor attach, or headless gt sling <bead-or-formula> mayor/. A candidate like gt sling mayor -a "..." doesn't work — sling's first positional argument must be an existing bead or formula name, not free text; a bead has to exist first.

  2. Because bd create is blocked town-wide (Stage 1's schema defect), no new bead could be created to hand the Mayor a two-part goal this session — decomposition into beads/convoys/polecats could not be observed directly.

  3. A real, unplanned, delegation-shaped event happened anyway: the town's own Deacon nudged the Mayor — ~/gt/logs/town.log: [nudge] hq-mayor nudged with "CRITICAL from Deacon: Dolt hq schema broken —..." (08:49:18). gt status -v afterward showed hq-mayor stopped / agent: zclaude/GLM-5 / hook: (none) — no hook was ever attached, meaning either the Mayor's session ended before acting on it, or the nudge (ephemeral, non-hook delivery) simply wasn't enough on its own to produce durable work. That is exactly Stage 2's mail-vs-nudge hygiene rule, caught happening live.

Net result: partial observation only, capped well under a 15-minute budget because the blocker was structural (the schema defect), not a matter of waiting longer. No decomposition into beads, convoys, or polecats was directly observed this session — this guide is not going to claim otherwise. [ground truth: local gt v0.12.0-197-gf28273a1]

Part B — the cost-tier switch (clean, real — doesn't depend on the broken bd/convoy machinery).

  1. Baseline:

    gt config cost-tier
    Cost tier: standard / All roles use Opus (highest quality)

    Expect: all six roles (mayor, deacon, witness, refinery, polecat, crew) listed as (default/opus).

  2. Switch to the cheaper tier and observe the real per-role assignment:

    gt config cost-tier economy
    Cost tier set to economy
    Patrol roles use Sonnet/Haiku, workers use Opus

    Expect exactly this role split: mayor: claude-sonnet, deacon: claude-haiku, witness: claude-sonnet, refinery: claude-sonnet, polecat: (default/opus), crew: (default/opus) — the four patrol/coordination roles get cheaper models under economy; the two roles that actually write code stay on Opus.

  3. Switch back and confirm the round trip:

    gt config cost-tier standard

    Expect: back to all six roles (default/opus), verified.

You should now see economy's exact four-role downgrade (mayor/deacon/witness/ refinery cheaper, polecat/crew unchanged) and a clean restore to standard. This part of the lab has no honesty asterisk — gt config cost-tier doesn't touch the broken dependency-join query path at all.

Cleanup/residual, disclosed rather than hidden: running the tier round trip persists an explicit "cost_tier": "standard" key into settings/config.json that wasn't there before (previously the tier was implicit). This is functionally inert — gt config cost-tier reports standard identically either way, and all role assignments are identical — but the raw file is not byte-identical to before you ran this lab. [ground truth: local gt v0.12.0-197-gf28273a1]

Sources: Better Stack guide · glossary.md · propulsion-principle.md · README · reference.md · ground truth (local gt v0.12.0-197-gf28273a1)

8 · Mixing Models: Tiers, Runners, Aliases

Gas Town never assumes one model. Three separate mechanisms decide which runtime/model actually executes a given role or sling, and this session's own ps aux capture settled, with live process evidence rather than a docs guess, which one wins when they disagree.

Mechanism 1 — cost tiers. gt config cost-tier is a town-wide dial with named levels; ground truth exercised two of them directly. Baseline standard: all six roles (mayor, deacon, witness, refinery, polecat, crew) at (default/opus) — "All roles use Opus (highest quality)." Switching to economy produced a real, specific role-assignment table: mayor: claude-sonnet, deacon: claude-haiku, witness: claude-sonnet, refinery: claude-sonnet, polecat: (default/opus), crew: (default/opus) — "Patrol roles use Sonnet/Haiku, workers use Opus." Only standard and economy were exercised this session; whether a further, cheaper tier name exists beyond these two was not enumerated here, so this guide isn't going to invent one. [ground truth: local gt v0.12.0-197-gf28273a1]

Mechanism 2 — per-invocation overrides. Below the tier dial, any single sling or role-start can override the runtime for just that instance: gt sling <bead> <rig> --agent codex (real flags confirmed via gt sling --help: --agent string, --account string); gt mayor start|attach|restart --agent <alias>; gt deacon start|attach|restart --agent <alias>; gt start crew <name> --agent <alias> / gt crew at <name> --agent <alias>. At rig-bootstrap time, per-role mixing can be pinned permanently: bootstrap-local-rig.sh --polecat-agent claude --witness-agent codex --refinery-agent codex — Claude polecats supervised by a Codex witness/refinery is a documented, supported configuration. [reference.md] [local-rig-bootstrap.md]

Mechanism 3 — custom aliases. gt config agent set <alias> "<command>" defines a named runner; gt config default-agent <alias> makes it the town default; gt config agent list [--json] / get / remove round out the CRUD (built-ins are protected from removal). Ground truth's own town has exactly this configured for real, not as a docs example: a custom agent named zclaude, defined as zclaude --dangerously-skip-permissions --model GLM-5, set as default_agent in settings/config.json. gt config agent list lists it alongside the 10 real built-in presets it confirmed — amp, auggie, claude, codex, copilot, cursor, gemini, omp, opencode, pi — and prints Default: zclaude. [reference.md] [ground truth: local gt v0.12.0-197-gf28273a1]

Resolution order, as documented: rig-level (<rig>/settings/config.json's "agent" plus a per-rig "agents" map) → town-level (settings/agents.json, settings/config.json) → built-in presets. [reference.md]

Precedence, resolved live — not just inferred from docs. [observed] With hq-boot, hq-deacon, and hq-mayor all running, ps aux | grep -E 'zclaude|claude' returned three distinct, role-differentiated real process lines — not a templated stand-in:

claude --dangerously-skip-permissions --model GLM-5 [GAS TOWN] boot <- daemon • triage
claude --dangerously-skip-permissions --model GLM-5 [GAS TOWN] deacon <- daemon • patrol
claude --dangerously-skip-permissions --model GLM-5 [GAS TOWN] mayor <- human • cold-start

Every spawned process ran the zclaude custom agent's --model GLM-5 invocation, not the bare built-in claude preset — and each role's injected startup prompt differed (boot: triage from daemon; deacon: patrol from daemon; mayor: cold-start from human), confirming three genuinely separate live processes. tmux capture-pane independently confirmed the same via each session's Claude Code status line (GLM-5 gt/mayor, GLM-5 gt/deacon, GLM-5 dogs/boot).

Why ps aux shows claude, not zclaude, as argv[0]: which zclaude resolves to a local zsh script that reads an API token from the keychain, exports auth env vars, then ends with exec claude "$@" — replacing itself with the real claude binary while preserving $@ (including --model GLM-5). This is user-specific machine config, not a Gas Town concept the docs describe — flagged here purely so the command-line evidence above doesn't read as contradicting the configured alias name.

Answer: settings/config.json's default_agent (zclaude) is what actually runs, end to end. There is no real conflict between standard cost-tier's (default/opus) label and the configured zclaude/GLM-5 agent: cost-tier's "opus" labels describe a model-quality tier for built-in presets, while default_agent/custom aliases are a separate, higher-precedence override supplying the actual runtime+model — consistent with the documented resolution order above, with the custom agent's explicit --model flag winning outright since it isn't a built-in preset the cost-tier machinery rewrites. [ground truth: local gt v0.12.0-197-gf28273a1]

Runtime plumbing differs per agent underneath all three mechanisms: Claude and Gemini get lifecycle hooks via a managed .claude/settings.json (passed with --settings); OpenCode gets a JS plugin; Copilot gets executable JSON hooks in .github/hooks/gastown.json (a 5-second ready delay instead of prompt detection); Codex and others fall back to a startup-nudge. OpenCode's autonomous mode separately needs export OPENCODE_PERMISSION='{"*":"allow"}'. [HOOKS.md]

A model-aware molecules design (status: In Progress) would push this further — per-step TOML constraints (model = "auto" with min_mmlu/max_cost/ requires), a heuristic-only router with no LLM calls, and usage always logged to ~/.gt/usage.jsonl. Not shipped as of v0.12.0 — the three mechanisms above are what's real today. [model-aware-molecules.md]

Pattern guidance, from the same notes: put cheap models where mistakes are cheap and iteration is frequent — patrol/coordination roles under economy (mayor/deacon/witness/refinery on Sonnet/Haiku) — and reserve the model actually writing and merging code (polecat/crew) for the strongest tier. Plugin molecules independently carry "tier:haiku" labels so routine maintenance scans run cheap by default. [reference.md] Where two agents review each other's work (e.g. a Codex witness supervising Claude polecats, Mechanism 2 above) — a documented, supported configuration, not just a hypothetical [local-rig-bootstrap.md] — this guide's own suggestion, not a claim either source makes, is that using a genuinely different model, not just a cheaper one, may help: a reviewer sharing the same training data and failure modes as the work it's reviewing could plausibly catch less than one that doesn't.

Sources: reference.md · local-rig-bootstrap.md · HOOKS.md · model-aware-molecules.md · ground truth (local gt v0.12.0-197-gf28273a1)

9 · Watching the Town & Troubleshooting

At 20–30 agents, "spotting stuck agents in the activity stream becomes difficult" by design — so Gas Town gives you tmux sessions to peek into, structured logs to tail, and a status line to read. This is also the one Lab in Part II where ground truth got a genuinely full observability capture, not a partial one — because watching the town worked even while the town's own schema defect was actively breaking things underneath it.

Where to look

Roletmux session namingLog / state fileStatus-line meaning
Mayor hq-mayor (town-level role → plain hq-<role>) logs/town.log (nudges/lifecycle); daemon/dolt.log (query errors surface here) gt status's glyph means "session exists, idle between turns," not "process absent." gt status -v read hq-mayor stopped / hook: (none) even while the tmux session was demonstrably alive this session — a real discrepancy, not a hypothetical one.
Deacon hq-deacon deacon/heartbeat.json (per-cycle {timestamp, cycle, last_action, healthy_agents, unhealthy_agents}) Freshness thresholds: <5 min = fresh (no action), 5–15 min = stale (nudge if pending mail), >15 min = very stale (wake).
Boot hq-boot (deliberately not hq-deacon, to avoid clashing with a live Deacon) daemon/daemon.log (the Go daemon's own operational log) Runs one triage decision per 3-minute daemon tick, then exits — a live hq-boot session mid-triage is normal, not stuck.
Witness (per rig) gt-<rig>-witness (documented role-shortcut expansion — not directly observed this session, no rig ever got a working Witness clone) rig-level, not captured this session documented only — see the callout below
Refinery (per rig) gt-<rig>-refinery (same shortcut pattern; same caveat) rig-level, not captured this session documented only
Polecat gt-<rig>-<name> (name from the rig's slot pool — Toast, Shadow, Copper…) rig-level, not captured this session (no polecat ever spawned) documented only: Working / Idle / Done / Stalled / Zombie

Other daemon-directory files worth knowing, all confirmed via ls ~/gt/logs ~/gt/daemon: daemon/daemon.pid / daemon/dolt-state.json (small JSON/PID files tracking the daemon's and Dolt's own running state); daemon/state.json (daemon heartbeat counter/last-heartbeat timestamp); daemon/restart_state.json (crash-loop/backoff bookkeeping). [ground truth: local gt v0.12.0-197-gf28273a1]

Heartbeat gotcha. Three distinct heartbeat stores can diverge: the Deacon's heartbeat.json file, a per-session state store, and an agent-bead heartbeat:<EPOCH> label. Monitoring must never declare an agent stuck from just one store — cross-check tmux window_activity first: "a live session with a stale store is heartbeat-write divergence, not a stuck agent." [heartbeats.md]

Stuck-agent taxonomy, straight from gt feed --problems: GUPP Violation (hooked work, no progress for an extended period), Stalled, Zombie (dead tmux session), Working, Idle — with intervention keys n = nudge, h = handoff, right in the feed. Health-check tooling rounds it out: gt deacon health-check <agent> (ping + track response), gt deacon health-state (all agents); the watchdog chain is Daemon (3-min heartbeat) → Boot (triage) → Deacon (patrol) → Witnesses/Refineries. "IMPORTANT: Always use gt nudge to send messages to Claude sessions. Never use raw tmux send-keys" — nudge uses literal mode, debounce, and a separate Enter for reliable delivery. [README] [reference.md]

Lab 6 — Follow a live task, then break something on purpose

Goal: watch a real session through tmux and logs, confirm what the status line actually means, then break something and watch the town's own recovery mechanics — this is the one lab this session where "watch the observability layer" and "the observability layer actually worked" are the same story.

  1. See who's actually running:

    tmux ls

    Expect, at peak: hq-boot, hq-deacon, hq-mayor (Gas Town sessions), plus whatever unrelated sessions were already on your machine (ground truth's own two pre-existing sessions were never touched throughout).

  2. Follow a session's live transcript:

    tmux capture-pane -p -t hq-mayor | tail -40

    Expect: a live Claude Code session with a status line like GLM-5 gt/mayor caveman:full 28,191 tokens 14% context. Ground truth's own capture caught a mid-turn tool call to gt mail inbox that surfaced the Stage 1/2/3 schema error live, inside the agent's own transcript — direct proof the agent itself hit the same defect. tmux capture-pane -t hq-deacon / -t hq-boot show the Deacon mid-patrol and Boot mid-triage the same way.

  3. Cross-check the status line against what you just saw was actually alive:

    gt status -v --watch

    Expect a discrepancy, not reassurance: ground truth's own repeated gt status -v checks (taken as discrete snapshots, not a continuous stream this session) printed hq-mayor stopped / agent: zclaude/GLM-5 / hook: (none) — while tmux ls confirmed the same session demonstrably alive at the exact same moment. gt status's "stopped" reading is derived from hook/heartbeat state, not raw tmux liveness — don't read a "stopped" glyph as "no session."

  4. Tail the daemon-directory logs from the table above:

    ls ~/gt/logs ~/gt/daemon

    Expect the file set described above — this is where a real incident's stack traces (like the schema defect's) actually live, in daemon/dolt.log.

  5. Deliberate breakage — real, not staged. This session's own gt doctor --fix (Stage 1's Lab 2) had already, as an unprompted side effect, classified the running hq-boot/hq-deacon/hq-mayor sessions as zombies and killed all three:

    tmux ls   # dropped from 5 sessions to the 2 pre-existing, unrelated ones

    Recovery observed: hq-boot and hq-mayor were both back in tmux ls within roughly 60–90 seconds (new session-creation timestamps), consistent with the daemon's fast per-tick ensureXRunning() checks. hq-deacon had NOT respawned after roughly 15 minutes of further observation — on this install, in this window, nothing brought the Deacon back on its own. The command that actually recovered the town cleanly, regardless of the Deacon's stuck state, was the blanket gt down --all used at session end.

Honest substitution. The originally planned drill was to sling a task, then manually kill a polecat mid-work and watch the Witness recover it. No polecat could be spawned this session at all (Stage 1 Lab 2's schema defect blocks bead creation before any polecat would exist). gt doctor --fix's zombie-session cleanup killing the three infra sessions outright is a real substitute for the same class of event — a live Gas Town session dying unexpectedly — and it produced a genuinely more interesting data point than a clean drill would have: two roles self-healed fast, one did not, within the same observation window. [ground truth: local gt v0.12.0-197-gf28273a1]

You should now be able to name, for any live session: its tmux session name, which log file its errors actually land in, and whether gt status's glyph for it can be trusted at face value (it can't, on its own — cross-check tmux). And you should have watched at least one role recover from a kill automatically, and ideally noticed one that didn't.

Cleanup: gt down --all stops every Gas Town tmux session cleanly, Deacon's stuck state included — this is the command ground truth's own restore step used. [ground truth: local gt v0.12.0-197-gf28273a1]

Troubleshooting playbook

SymptomDiagnosis commandsFix
Stalled polecat — tmux session alive, bead not progressing gt feed --problems (GUPP Violation / Stalled / Zombie / Working / Idle taxonomy); tmux capture-pane -t <session> | tail -40; cross-check the three heartbeat stores against tmux window_activity before concluding it's actually stuck. gt nudge <agent> "msg" first — never raw tmux send-keys; gt peek <agent> to inspect; gt handoff to force a session cycle if still unresponsive. The daemon's own safety net waits a deliberately generous 30 minutes of hooked-no-progress before mailing the Witness — "distinguishing 'thinking deeply' from 'hung' requires intelligence."
Daemon/Dolt down gt status --fast (look for daemon (stopped) / dolt (stopped, :3307)); gt dolt logs. gt up — but don't trust a single exit code: ground truth's own first run reported "Dolt server failed to start" / "daemon failed to start" as a false negative, with gt status seconds later showing both genuinely up. Re-run gt up for a clean ✓ All services running.
Suspected port conflict on Dolt's :3307 lsof -i :3307 — ground truth confirmed empty while stopped, and a real dolt … (LISTEN) line plus established gt/dolt/beads connections once up. If the listener is dolt, there's no conflict — it's just up. If a different process holds the port, stop it (or reconfigure Dolt's port) before retrying gt up; the daemon health-checks Dolt every 30s and restarts it on crash otherwise.
Slung work never picked up (stranded convoy) gt convoy status <id> / gt convoy stranded; gt rig status <rig>; gt scheduler status for the dispatch throttle. If scheduler.max_polecats > 0 (deferred-dispatch mode), gt scheduler resume or raise the cap; otherwise re-gt sling directly. Also check structurally whether the rig can run a Witness/Refinery at all — Stage 1's Lab 2 found a remote-less rig adopted via --adopt --force never gets one, so nothing there can ever pick work up.
Runaway agent — burning tokens or touching files fast, no one reviewing gt feed --problems (is it genuinely Working, or a Zombie masquerading as one?); tmux capture-pane -t <session> | tail -60 to see exactly what it's doing right now. gt nudge <agent> "..." to redirect; gt session stop <rig>/<agent> to kill outright. The system's own breakers: 3 crashes on the same step → the Witness stops respawning it, files a bug bead, mails the Mayor; an abandoned polecat's bead resets to open (RECOVERED_BEAD mail) or, if there's uncommitted work, raises RECOVERY_NEEDED instead of discarding it; the Deacon's own gt deacon redispatch <bead-id> is rate-limited to a 5-minute cooldown, escalating to the Mayor after 3 failures. This is why Gas Town exists at all: it's infrastructure for running fleets of Claude Code (and other) instances instead of babysitting them one at a time, and Yegge's own words for the threshold where hand-management stops working are "10+ agents, hand-managed. You are starting to push the limits of hand-management." Past that point a human can no longer be the one watching for a session gone off the rails, so Witness/Deacon/Boot are the mechanized substitute. Better Stack's operational warning names the real stakes plainly: concurrent agents "can burn through API tokens very quickly," have full repo access, and a misinterpretation "could lead to it 'wrecking your repo.'"

Dolt-outage discipline, one level up from any single symptom above: for a Dolt outage or a behavior mismatch involving Dolt-backed state, attach the RCA capture checklist from docs/dolt-health-guide.md to the escalation before restarting services — exactly the discipline this guide's own schema-defect writeups (Stages 1–3) followed. [escalation.md]

Sources: heartbeats.md · README · reference.md · escalation.md · Better Stack guide · Welcome to Gas Town · ground truth (local gt v0.12.0-197-gf28273a1)

10 · A Day in Gas Town

DoltHub's own engineer spent a real day running Gas Town on the Dolt repo itself — fixing failing Bats integration tests. It's the single richest narrative account in the source set of what actually happens when delegation goes right, then wrong, then gets sorted out. Walking through it beat by beat, tied back to the sections that teach each piece, is a good comprehension check for everything above — including the places where the post's telling and this guide's own v0.12.0 ground truth visibly disagree. [A Day in Gas Town]

Morning — setup

The author installed Beads, tmux, and Gas Town following each project's GitHub instructions — "worked out of the box." The rig was a clone of the Dolt repo itself; the goal: fix failing Bats integration tests.

Teaches: §3 Stage 0 — Town Up (the install/service-start sequence) and §4 Stage 1 — First Rig, First Sling (registering a real repo as a rig).

DRIFT. "Worked out of the box" doesn't match this guide's own Stage 0 ground truth: the real gt up reported a false-negative service failure on its first run, and Stage 1's gt rig add has real sharp edges the moment there's no clean remote (Lab 2). The post likely had a working git remote and a healthy Dolt schema — this guide's install did not.

Late morning — kickoff

gt mayor attach opened what looked like a plain Claude Code session — initially confusing. The author told the Mayor to "fix some bats tests"; the Mayor analyzed the codebase and proposed four Bats-test fixes; the author replied "fix all four in parallel."

Teaches: §7 Stage 4 — Delegation — the exact entry point and one-comprehensive-prompt pattern this guide documents ("the more detail you provide upfront, the better the Mayor's plan will be"), and §6 Stage 3 — Convoys, since "fix all four in parallel" is precisely what turns into a tracked convoy grouping four beads.

DRIFT. gt mayor attach itself is confirmed real syntax (this guide's own Lab 5 checked it). What isn't confirmed is the clean decomposition that follows it — this guide's own ground truth never observed a Mayor decompose a real goal into beads/convoys end to end; the schema defect blocked bead creation before that could happen (§7's Lab 5). The post's smooth four-fix decomposition is the aspirational case this guide's own Lab 5 couldn't confirm this session.

Midday — execution

Four worker agents (polecats) spawned concurrently on isolated branches. Streaming output was "too much going on for you to reasonably comprehend." Gas Town ran in extreme "YOLO mode," pushing branches to GitHub and creating PRs autonomously.

Teaches: §6 Stage 3 — Convoys (the convoy is exactly the batch-visibility layer meant to make four parallel workers legible instead of an incomprehensible stream) and §9 Watching the Town & Troubleshooting (tmux-per-agent + gt feed --problems exist specifically to tame this firehose).

DRIFT. This guide's own convoy dashboard commands (gt convoy list, gt convoy status) are documented, not demonstrated end to end either (§6's Lab 4 hit the same schema defect) — so the underlying batch-tracking machinery the post relied on to make four parallel polecats legible is, in this guide's own session, exactly as unverified live as the post's smooth decomposition above.

Afternoon — the incident

One PR merged automatically despite failing integration tests. The author halted Gas Town immediately and had a colleague hard-reset and force-push the Dolt repository.

Teaches: §7 Stage 4 — Delegation's merge-queue section — the Refinery's documented Bors-style gate (batch pending MRs, run verification gates, merge only if green, bisect if red) exists specifically to prevent a failing PR from reaching main unreviewed — and §9's runaway-agent playbook row, which cites this exact class of incident.

DRIFT. If the documented Refinery gate (§7) had been fully in place as described, this incident is precisely what it exists to prevent — a real gap between whatever merge-queue behavior existed when this post was written and the batch-then-bisect design this guide's own sources document (and separately flag, in Part III, as partially "in progress" rather than fully shipped even now).

Late afternoon — reconciliation

Only two PRs existed on GitHub, yet the Mayor reported all four bugs fixed. After the author pushed back, investigating agents found completed work sitting in local git that had never been pushed; the Mayor coordinated the resolution.

Teaches: §5 Stage 2 — Beads & Mail's mail-vs-nudge/hook durability distinction (work that isn't in a durable hook or pushed branch can look "done" to one party and invisible to another), and §9's status-line discrepancy finding — gt status reporting one thing while tmux/git reality says another is the exact same genre of "reported state disagrees with reality" bug this guide's own Lab 6 hit independently.

Evening — outcome and review

Four PRs were ultimately created; all four were closed — "None of the PRs were good." The roughly 60-minute session cost about $100 in Claude tokens, ~10x normal Claude Code cost per unit time.

Teaches: §8 Mixing Models — this is exactly the economics cost tiers exist to manage: patrol/coordination roles are the ones meant to run cheap (economy's Sonnet/Haiku split, §8), while the roles actually writing code stay on the strongest model available. Four parallel Opus-class polecats generating unreviewed PRs for an hour is the expensive end of the same dial this guide's own Lab 5 exercised safely in isolation.

The Dolt angle

No direct Dolt-database usage occurred in the session itself; the article's real thesis is that Dolt should replace Git+SQLite as Beads' persistence layer, for better merging, cell-level tracking, and SQL queryability.

Teaches: forward to §12 Internals: Beads on Dolt — Part III territory, since this whole guide's own ground truth confirms Dolt is in fact now the sole storage backend (no embedded mode, no SQLite). The irony is worth naming directly: the post's optimistic case for Dolt-as-persistence-layer is exactly the layer that broke town-wide, in this guide's own ground-truth capture, with a real pre-existing schema-migration defect (Stages 1–3, throughout).

Sources: A Day in Gas Town · ground truth (local gt v0.12.0-197-gf28273a1)

11 · The Seven Roles

Part II put you through six labs wearing the overseer's hat, watching roles act (and, honestly, fail to act) around you. This section names the seven job descriptions properly — repo docs split them into infrastructure roles that "manage the Gas Town system itself" (Mayor, Deacon, Witness, Refinery) and worker roles that "do actual project work" (Polecats, Crew, Dogs) — and for each one ties its responsibility, trigger, hand-offs, and failure mode back to the specific moment (or specific gap) you already saw in Part II. [overview.md]

Every one of these is a bead, not a person: each agent is backed by a lifecycle "agent bead" (hq-mayor, hq-deacon, <rig>-witness, <rig>-refinery, <rig>-polecat-<name>, <rig>-crew-<name>) that references a global role-template bead via its role_bead field — and identity/attribution (BD_ACTOR, e.g. mayor, <rig>/witness, <rig>/polecats/<name>) is set at spawn: "Agents execute. Humans own." [architecture.md · identity.md]

Mayor

Responsibility: global coordinator at mayor/ — glossary calls it the "Chief-of-staff agent responsible for initiating Convoys, coordinating work distribution, and notifying users of important events," with town-level visibility across every rig. [overview.md · glossary.md]

Trigger: overseer messages — "Usually you send this agent messages, and it coordinates the work of other agents" — the chain runs overseer → Mayor → Polecats. [Gas Town, Decoded]

Hand-offs: cross-rig communication and escalations; hq-mayor lives in town beads, not any rig's. [architecture.md]

Failure mode: the Mayor is the escalation sink, not a monitored bottleneck — a Witness crash-loop (3 crashes on one step) "creates a bug bead, mails the mayor," Compactor Dog failures escalate to the Mayor the same way, and medium+ severity gt escalate calls route bead+mail to it too. [polecat-lifecycle-patrol.md · dolt-storage.md · escalation.md]

You watched exactly this sink role fire for real in Lab 5 (§7): with bd create blocked town-wide, no overseer goal was ever decomposed — but the Deacon's own escalation reached hq-mayor anyway ([nudge] hq-mayor nudged with "CRITICAL from Deacon: Dolt hq schema broken —..."). gt status -v afterward showed hq-mayor stopped / hook: (none) — the nudge landed, but left nothing durable pinned to act on, which is exactly the mail-vs-nudge distinction Stage 2 (§5) warned about, caught happening live. Lab 6 (§9) then caught the Mayor's own session transcript mid-turn, running gt mail inbox and hitting the identical schema error itself. [ground truth: local gt v0.12.0-197-gf28273a1]

Deacon

Responsibility: "Background supervisor daemon" — glossary: a "Daemon beacon running continuous Patrol cycles… ensures worker activity, monitors system health, and triggers recovery when agents become unresponsive." [overview.md · glossary.md]

Trigger: a long-running session at ~/gt/deacon/ that cycles on its own handoff loop, writing heartbeat.json at the start of every patrol cycle. [dog-infrastructure.md]

Hand-offs: nudges/wakes Witnesses and Refineries when their heartbeats go stale; hands its own health-monitoring to a narrower ephemeral helper, Boot, precisely because "the Deacon can't observe itself — a hung Deacon can't detect it's hung." [dog-infrastructure.md]

Failure mode: the very blind spot Boot exists to cover — freshness thresholds run <5 min fresh / 5–15 min stale (nudge) / >15 min very stale (wake), but nothing forces the Deacon itself back to life if the daemon's own respawn check doesn't catch it. [dog-infrastructure.md]

Lab 6 (§9) demonstrated this failure mode directly, not hypothetically: gt doctor --fix (run back in Lab 2, §4) had killed hq-boot, hq-deacon, and hq-mayor as zombies in one pass. hq-boot and hq-mayor were both back within 60–90 seconds — but hq-deacon had NOT respawned after roughly 15 minutes of further observation; only a blanket gt down --all at session end actually recovered the town. [ground truth: local gt v0.12.0-197-gf28273a1]

Witness

Responsibility: "Per-rig polecat lifecycle manager" — DECODED's plain English: "Fixer Agent: watches the worker agents and tries to fix any that are stuck." One per rig, persistent. [overview.md · Gas Town, Decoded]

Trigger: a continuous patrol cycle, 30–90 seconds per pass — the Witness observes but does not gate completion, so it never becomes the bottleneck for work finishing. [architecture.md · polecat-lifecycle.md]

Hand-offs: detects and nudges stalled polecats, cleans up zombies, respawns crashed sessions, relays escalations to the Deacon; if the Witness itself dies, the Deacon detects and restarts it. [polecat-self-managed-completion.md]

Failure mode: single-threaded — one patrol cycle at a time made completion- processing a serial bottleneck, which is exactly why polecats now self-report completion directly to the Refinery instead of routing every "done" through the Witness. [polecat-lifecycle-patrol.md]

Unlike the other roles here, you never watched a Witness act at all — because none ever came into existence. Lab 2's (§4) --adopt --force rig adoption left git_url: "", and gt doctor --rig gt_lab_scratch reported "Witness structure incomplete" outright; §9's own tmux table records the Witness row as "documented role- shortcut expansion — not directly observed this session, no rig ever got a working Witness clone." [ground truth: local gt v0.12.0-197-gf28273a1]

Refinery

Responsibility: "Per-rig merge queue processor" — glossary: "intelligently merges changes from Polecats, handling conflicts and ensuring code quality before changes reach the main branch." Runs as a git worktree off mayor/rig, not a full clone. [glossary.md · architecture.md]

Trigger: "on demand," not continuous — MERGE_READY nudges reduce latency, but the real discovery mechanism is polling beads each patrol cycle for issues labeled gt:merge-request. [polecat-lifecycle-patrol.md]

Hand-offs: in — MERGE_READY from the Witness plus MR beads from a polecat's gt done; out — squash-merge to main, close the MR bead, MERGED / MERGE_FAILED mail back to the Witness, remote branch deleted. [polecat-lifecycle-patrol.md · persistent-polecat-pool.md]

Failure mode: a merge conflict spins up a conflict-resolution task for another polecat and blocks the MR; "Refinery notifies mayor after merge" is designed but not shipped, so dependent-work dispatch stays un-triggered. [polecat-lifecycle-patrol.md]

Like the Witness, the Refinery for Lab 2's (§4) adopted rig was structurally impossible — gt doctor --rig also flagged "Refinery structure incomplete." Stage 4's own delegation diagram (§7) draws "Refinery: merge queue, Bors-style" as a box the session's pipeline never reached at all: no bead, so no polecat, so no MR, so no Refinery invocation. [ground truth: local gt v0.12.0-197-gf28273a1]

Polecats

Responsibility: rig-level workers with "persistent identity but ephemeral sessions" — permanent agent bead + CV chain + work history, disposable Claude sessions, a per- assignment git worktree sandbox. [glossary.md]

Trigger: gt sling <bead> <rig> puts a bead on a polecat's hook, which GUPP compels it to run immediately; happy path is Idle → Working → Done, with no idle reuse — a clean completion retires the session. [polecat-lifecycle.md]

Hand-offs: gt done pushes the branch, submits an MR bead, clears the hook, sets agent_state=done, and kills its own session — leaving branch/MR metadata for the Witness and Refinery to clean up. [polecat-lifecycle.md]

Failure mode: named failure modes include the stuck-in-done zombie, the orphaned sandbox, split-brain merges, and the infinite cycle — 3 crashes on the same step and the Witness stops respawning it, files a bug bead, and mails the Mayor. [polecat-lifecycle-patrol.md]

Be precise about what Lab 2 (§4) actually showed: no polecat ever spawned. The town-wide Dolt schema defect (Error 1062 (HY000): duplicate primary key given: [wisps]) blocked bd create before a bead existed to sling at all — the lab's own svg-flow-sling diagram marks "Polecat spawned in rig" as a dashed, undemonstrated step, not something observed. The polecat lifecycle above is what would have run, per docs — Part II's honesty is that it didn't, this session. [ground truth: local gt v0.12.0-197-gf28273a1]

Crew

Responsibility: "Persistent worker with own clone" — long-lived, user-managed, full git clones (unlike the polecat/Refinery worktree pattern) because human developers need independent repos. Best for exploratory work or anything needing human judgment. [overview.md · architecture.md]

Trigger: "Human-directed or self-assigned," not gt sling — Crew is an "Interactive" role like the Mayor, so mail is injected on the UserPromptSubmit hook rather than at a patrol boundary; sessions are started/attached by the human. [overview.md · reference.md]

Hand-offs: cross-rig work uses gt worktree <rig> (identity preserved) if the crew member keeps the work themselves, or a convoy+sling dispatch if the target rig's own team should own it instead. [overview.md]

Failure mode: none documented — and that absence is itself the finding. The crew-vs-polecat comparison table lists Monitoring: "None" and Cleanup: "Manual"; no fetched page documents a Crew-specific recovery flow. The human is the recovery mechanism. [overview.md · persistent-polecat-pool.md]

Stage 1 (§4) already drew this exact split from the source, live in the guide's own prose: "Crew is persistent and human-managed — it pushes to main directly, and nothing patrols it — while Polecats are transient and Witness-managed." Stage 4's Lab 5 (§7) is also where Crew showed up in real, working ground truth: the cost-tier round trip left crew: (default/opus) unchanged under both standard and economy — one of only two roles the cheap tier never touches, because Crew is the one writing code by hand. [ground truth: local gt v0.12.0-197-gf28273a1]

Dogs

Responsibility: "Dogs are NOT workers. This is a common misconception" — they are the Deacon's helpers for system-level infrastructure tasks (cleanup, health checks), never user project work: "If you need to do work in another rig, use worktrees, not dogs." [overview.md]

Trigger: named data-plane Dogs run on daemon tickers — Reaper, Compactor, Doctor (7 health checks; kills zombie Dolt servers — 45 zombies eating 7GB RAM found 2026-02-27), JSONL, Dolt Backup — all enabled by default; reliability-critical Dogs are imperative Go, not agents: "If the dog's failure would cause a Clown Show, it must be imperative Go." [dolt-storage.md · dog-execution-model.md]

Hand-offs: Deacon-managed and Deacon-dispatched; a separate species, shutdown- "dance" dogs, are lightweight Go state machines executing death warrants (WARRANT → INTERROGATE → EVALUATE → PARDON|EXECUTE) from a fixed pool of 5. [dog-infrastructure.md]

Failure mode / DRIFT: the repo's own two taxonomy tables disagree — overview.md calls Dog lifecycle "Very short (single task)" while architecture.md calls Dogs "Long-running workers for cross-rig batch work" with "Variable" persistence. [overview.md · architecture.md]

The named Doctor Dog shares its name and its zombie-hunting mission with the CLI command you actually ran twice, in Lab 2 and Lab 6 (§4/§9): gt doctor --fix. That real run's unprompted side effect — reclassifying hq-boot/hq-deacon/hq-mayor as zombies and killing all three — is a live, first-hand example of exactly this class of automated cleanup, even though the CLI command and the ticker-driven background Dog are not confirmed to be the same code path. [ground truth: local gt v0.12.0-197-gf28273a1]

Who hands work to whom Overseer (you) human Mayor coordinator, escalation sink Deacon town-wide patrol prompt / goal gt mayor attach nudge / escalation watched live — Lab 5, §7 Polecat works a branch Witness per-rig patrol Refinery merge queue Dogs infra gt sling (bead) escalation (crash-loop) POLECAT_DONE MERGE_READY MERGED / FAILED manages Crew own clone, human-managed main branch squash-merged tip squash-merge direct-assign (no sling) direct push (bypasses queue) bead mail merge direct git push (Crew — bypasses queue) human-directed / non-hook (prompt, direct-assign)
Every arrow is a real hand-off mechanism from the docs — bead (magenta), mail (teal), merge (green), Crew's direct git push (red), and human/non-hook assignment (dashed grey). The single arrow this guide's own ground truth watched fire live is Deacon → Mayor (Lab 5, §7); every other hand-off here is documented behavior the session's Dolt schema defect kept from being observed end to end (Labs 2–5, §4–§7).

Sources: overview.md · glossary.md · Gas Town, Decoded · architecture.md · dolt-storage.md · dog-infrastructure.md · dog-execution-model.md · polecat-lifecycle.md · polecat-lifecycle-patrol.md · persistent-polecat-pool.md · polecat-self-managed-completion.md · identity.md · reference.md · escalation.md · ground truth (local gt v0.12.0-197-gf28273a1)

12 · Internals: Beads on Dolt, GUPP, Hooks, Patrols, Merge Queue

Part II showed you a town that mostly didn't work this session — a schema defect chased across four labs, a Deacon that wouldn't come back, a Refinery that never got the chance to run. This section is the machinery underneath all of it: not just what these pieces are for, but specifically why each one produced the exact behavior — good and bad — that you already watched happen.

Beads on Dolt

Dolt is the sole storage backend: one Dolt SQL server per town (MySQL protocol, port 3307, no password), data under ~/gt/.dolt-data/ with one subdirectory-database per scope. "There is no embedded mode and no SQLite. JSONL is used only for disaster-recovery backups." The daemon auto-starts the server, health-checks it every 30s, and restarts it on crash with exponential backoff. [dolt-storage.md]

Two-level architecture: town-level ~/gt/.beads/ (hq-*: Mayor mail, convoy coordination, town agent beads) vs. rig-level <rig>/mayor/rig/.beads/ (project issues, MRs, rig agent beads); routes.jsonl maps prefixes to the canonical clone, and worktrees (polecats, refinery, crew) carry only a .beads/redirect file so every agent in a rig shares one database. [architecture.md]

Schema version 6: everything is a row in issues — tasks, messages, agents, gates — with agent-specific columns (hook_bead, role_bead, agent_state), molecule columns (mol_type, wisp_type), a dependencies table (types: blocks, parent-child, thread), labels, comments, and an events table serving as the audit trail. Mail is just issue_type='message' — no separate mail table. [dolt-storage.md]

The versioned-audit payoff — the thing plain git+JSON never gave Beads — is real SQL history: dolt_history_* tables, AS OF time-travel queries, dolt_diff(), and DOLT_MERGE/dolt_conflicts for programmatic conflict resolution. "Dolt is git under the hood. The commit graph IS the storage cost, not the rows" — hence a six-stage data lifecycle (CREATE → LIVE → CLOSE → DECAY → COMPACT → FLATTEN) that Reaper and Compactor run daily, and wisps are dolt_ignored so patrol exhaust doesn't generate commits at all. [dolt-storage.md]

This is the layer that broke, town-wide, throughout Stages 1–3. gt status (§3) confirmed the one real Dolt server by PID and port — dolt (PID 10948, :3307, ~/gt/.dolt-data) — exactly as this section says it should be a single server per town. But the schema underneath it was broken: migration 0040_ignored_tables_also_nonlocal_tables.up.sql failed with Error 1062 (HY000): duplicate primary key given: [wisps], and the dependencies table above was missing its own depends_on_issue_id and is_blocked columns. Every query that joins dependencies broke on it — bd create in Lab 2 (§4), gt mail inbox in Lab 3 (§5), gt convoy list in Lab 4 (§6) — while bd list, which skips that join, kept working every time. "Everything is a row in issues, with a dependencies table" is also, precisely, the shape of a schema defect narrow enough to hit three unrelated commands and load-bearing enough to block them all. [ground truth: local gt v0.12.0-197-gf28273a1]

If you hit this on your own town. This session tried the obvious repair path — gt doctor --fix and bd doctor --fix — and neither resolved it; the same migration failure (Error 1062 (HY000): duplicate primary key given: [wisps]) and the same missing depends_on_issue_id/is_blocked columns recurred every time. No verified fix was found during this guide's research. If you hit this on your own town, it's worth checking for a bd/Dolt version mismatch between your installed binaries and the schema version they expect, or filing an issue against the gastownhall/gastown repo — its GitHub issue tracker is a real, active channel for this project (the current community-contributed glossary.md itself originated as Issue #80) — but this guide did not verify a specific repair procedure, and won't pretend otherwise. [ground truth: local gt v0.12.0-197-gf28273a1]

One more confirmation this session did surface cleanly: Lab 3's (§5) bd list --include-infra --all returned "dozens of hq-wisp-* patrol/dog molecule beads" alongside agent beads like hq-mayor — real, listable rows proving "agent" and "wisp/molecule" are as genuine a bead kind as "task," exactly as this schema describes. [ground truth: local gt v0.12.0-197-gf28273a1]

GUPP

The Gastown/Gas Town Universal Propulsion Principle, stated plainly on its own concepts page: "If you find something on your hook, YOU RUN IT" — with the steam-engine metaphor: "Gas Town is a steam engine. Agents are pistons… Steam engines don't run on politeness — they run on pistons firing. You are the piston." No supervisor polls "did you start yet?"; the hook is the assignment. [propulsion-principle.md]

The handoff contract: a spawned agent with hooked work must (1) find it on its hook, (2) understand it (bd show / gt hook), (3) BEGIN IMMEDIATELY. The failure mode GUPP exists to prevent: a polecat restarts with hooked work → announces itself → waits for confirmation → the Witness assumes progress → "Nothing happens → Gas Town stops." "Hooked" (triggers autonomous mode) is explicitly distinguished from "pinned" (a permanent reference bead). [propulsion-principle.md]

Enforcement is mechanical, not aspirational: the daemon's safety-net threshold for a GUPP violation is 30 minutes with hook_bead set but no progress, after which it mails GUPP_VIOLATION: <name> to the Witness — deliberately generous, because "distinguishing 'thinking deeply' from 'hung' requires intelligence." GUPP is one leg of a completion guarantee alongside a persistent sandbox and a Witness that respawns sessions; the glossary frames it as one of three Core Principles (with MEOW and NDI) — "the heartbeat of autonomous operation." [polecat-lifecycle-patrol.md · glossary.md]

This is exactly why the Deacon's nudge to the Mayor in Lab 5 (§7) went nowhere. A nudge is ephemeral tmux delivery — it is not a hook. gt status -v afterward showed hq-mayor stopped / hook: (none), meaning nothing was ever pinned to the Mayor's hook for GUPP to compel action on. GUPP doesn't fire on a message arriving; it fires on work landing on a hook. That single missing word — hook: (none) — is the mechanical reason "hand the Mayor a goal" needs a real bead, not just a message, to produce durable autonomous progress. [ground truth: local gt v0.12.0-197-gf28273a1]

Coverage note, for completeness: across every repo docs page fetched for Parts II–III, the acronym "GUPP" itself appears only in glossary.md, README.md, polecat-lifecycle-patrol.md, and polecat-self-managed-completion.md — the dedicated propulsion-principle.md and overview.md pages state the principle without ever using the acronym. [propulsion-principle.md · overview.md]

MEOW

Genuinely unreconciled — this guide is not picking a winner. The README expands MEOW as "Mayor-Enhanced Orchestration Workflow" (the seven-step delegation pattern §7 walks through). The repo's own docs/glossary.md expands the identical acronym as "Molecular Expression of Work" — "Breaking large goals into detailed instructions for agents. Supported by Beads, Epics, Formulas, and Molecules." A grep across every fetched repo docs page shows "MEOW" appears in exactly these two places and nowhere else — no third page adjudicates between them. §7's own callout already flagged that ground truth went further and grepped ~1,700 lines of real --help transcripts captured this session (sling, convoy, mail, mol, formula, config) for "MEOW," "Enhanced," and "Molecular" — zero matches, all three terms. The live CLI does not adjudicate the conflict either. Both expansions are presented here as documented-but- unreconciled, not as one correct reading with a historical variant. [glossary.md · README] [ground truth: local gt v0.12.0-197-gf28273a1]

One observation, offered as analysis rather than a tiebreaker: the deeper internals docs consistently express the glossary's "molecular" reading — decomposition machinery (formulas, molecules, wisps, epics) is everywhere in the design docs, while the README's "Mayor-Enhanced" expansion appears nowhere outside the README section that defines it. That is a pattern in where the term shows up, not a resolution of what it stands for. [glossary.md · molecules.md]

The molecule machinery the glossary's reading names: Formula (source TOML, "Ice-9") → bd cook → Protomolecule (frozen template) → either bd mol pour → Molecule (persistent, "Liquid") or bd mol wisp --root-only → Root Wisp (ephemeral, "Vapor"). Root-only wisps are the default — formula steps are read inline at gt prime time rather than materialized as rows, cutting wisp accumulation from ~6,000+ rows/day to ~400/day. [molecules.md]

That "Vapor" — Root Wisp — reading is exactly what Lab 3 (§5) saw firsthand, without either of us needing to pick a side in the acronym dispute: bd list --include-infra --all's "dozens of hq-wisp-* patrol/dog molecule beads" are the molecular decomposition machinery made concrete in real listed data, whichever expansion of MEOW you take to be describing it. [ground truth: local gt v0.12.0-197-gf28273a1]

Hooks

Terminology hazard: Gas Town has two unrelated "hooks." (1) The agent hook — glossary: "A special pinned Bead for each agent. The Hook is an agent's primary work queue — when work appears on your Hook, GUPP dictates you must run it"; at schema level, it is the hook_bead column discussed above. (2) Claude Code lifecycle hooks — the gt hooks subsystem managing settings.json files. [glossary.md · dolt-storage.md · HOOKS.md]

On sense (1): the durable bead pointer (hook_bead) survives session cycles while the worktree holds the work product — "No explicit 'handoff payload' is needed. The beads state IS the handoff." This is precisely the field gt status -v printed as empty (hook: (none)) in Lab 5 and Lab 6 (§7/§9) — the durable pointer this whole GUPP mechanism depends on. [polecat-lifecycle-patrol.md]

On sense (2): single source of truth in ~/.gt/hooks-base.json + ~/.gt/hooks-overrides/*.json, merge chain base → role → rig+role (most specific wins), generating per-role settings.json targets passed to Claude Code via --settings so customer repos stay clean. The default base config wires GUPP straight into the session lifecycle: SessionStartgt prime --hook, PreCompactgt prime --hook, UserPromptSubmitgt mail check --inject, Stopgt costs record — the propulsion check fires mechanically at every session boundary, not just at agent startup. [HOOKS.md]

This is the exact mechanism Stage 1 (§4) already named without unpacking it: "on every gt rig add, Claude settings hooks are auto-synced for all of the new rig's targets — crew, witness, refinery, polecats." The hook registry backing it (~/gt/hooks/registry.toml) catalogs 7 hooks, 5 enabled by default (pr-workflow-guard, session-prime, pre-compact-prime, mail-check, costs-record); gt doctor --fix — the same command that killed three tmux sessions as a side effect in Lab 2/Lab 6 — is also the command that repairs out-of-sync hook files. [HOOKS.md]

Patrol loops

Patrol: "Ephemeral loop maintaining system heartbeat." Four patrol agents run at distinct scopes: Daemon (town-wide, 3-minute heartbeat), Boot/Deacon (town-wide, per daemon tick), Witness (per-rig, continuous), Refinery (per-rig, on demand). Mechanically, every patrol role cycles gt patrol new (root-only patrol wisp) → gt prime (inline checklist) → work steps → gt patrol report --summary "...", which atomically closes the cycle and starts the next; patrol wisps are ephemeral, Dolt-ignored, and reaped by the data-lifecycle Dogs. [glossary.md · polecat-lifecycle-patrol.md]

The daemon's own tick order is fixed: ensureBootRunning()checkDeaconHeartbeat() (belt-and-suspenders) → ensureWitnessesRunning()ensureRefineriesRunning()processLifecycleRequests() — agent running/stopped state is derived from tmux directly every tick, not read back out of beads. [dog-infrastructure.md]

Patrol overlap is intentional redundancy, not sloppy duplication: "The daemon detects dead sessions. The deacon detects dead witnesses. The witness detects dead polecats" — every layer converges on the same observables (beads, git, tmux liveness) with no private state, the "discover, don't track" principle. Safety-net thresholds are deliberately generous: GUPP violation 30 min hooked-no-progress, hung session 30 min no tmux output, stuck-in-done 60 seconds. [polecat-lifecycle-patrol.md]

This is the exact machinery Lab 6 (§9) put on display, both halves of it. The daemon's per-tick ensureBootRunning()/ensureXRunning() checks are why hq-boot and hq-mayor were both back in tmux ls within roughly 60–90 seconds of gt doctor --fix's zombie-kill — that recheck runs every tick regardless of what a human happens to be doing. checkDeaconHeartbeat() being explicitly "belt-and-suspenders," not a guaranteed respawn path, is the other half: hq-deacon's failure to come back after roughly 15 minutes in that same lab is a real, live instance of the exact gap Boot exists to cover — "the Deacon can't observe itself." Two roles self-healed fast in this session; one did not, inside the same observation window. [ground truth: local gt v0.12.0-197-gf28273a1]

Merge queue / refinery

The core design is batch-then-bisect, "Bors-style": batch pending MRs by rebasing them as a stack on main, run tests once on the tip — all green fast-forwards the whole stack; red triggers a binary bisect to isolate the offender. But architecture.md's own phase table lists Phase 1 (parallel gates) "In progress" and Phase 2 (batch-then-bisect) "Blocked by Phase 1" — while the README narrates batching+bisect as already-shipped, present-tense behavior. [architecture.md]

The shipped baseline, per the patrol design doc, is simpler and serialized: claim MR → acquire a merge slot (serialized push lock) → run gates → squash-merge to main → close the MR bead and source issue → send MERGED. MR discovery is poll-based, not push-based — the Refinery queries beads each patrol cycle for issues labeled gt:merge-request; MERGE_READY nudges only cut latency, so a missed nudge costs at most one patrol cycle. [polecat-lifecycle-patrol.md · polecat-self-managed-completion.md]

Integration branches extend the queue for epics: gt mq integration create <epic-id> creates an integration/<title> branch that the whole pipeline auto-targets; polecats spawn worktrees from it, and once all children close it lands as a single --no-ff merge. Landing guardrails are three-layered — soft formula instructions, a pre-push git hook blocking any push that makes an origin/integration/* tip reachable from the default branch, and only gt mq integration land itself setting the env var that clears that block: "a policy-based trust boundary, not a capability-based security mechanism." [integration-branches.md]

This is the gap §10's own DRIFT note already promised to explain here. "A Day in Gas Town"'s afternoon incident — one PR merged automatically despite failing integration tests — is exactly what a fully-shipped batch-then-bisect gate exists to prevent. Now that the phase table above is on record: Phase 2 batch-then-bisect was "Blocked by Phase 1" even in this guide's own current sources, and the shipped baseline is the simpler serialized claim → gate → merge path described above. A single-MR serialized gate is a real gate, but it is a narrower one than "batch the whole stack, bisect on red" — a gap exactly the shape of the incident that post described. [architecture.md]

And this whole section is the mechanics behind the box Stage 4's own delegation diagram (§7) drew as "Refinery: merge queue, Bors-style" — a step this guide's own session never reached at all, because no bead was ever created for a polecat to work, so no MR bead ever existed for a Refinery to discover, batch, or bisect. [ground truth: local gt v0.12.0-197-gf28273a1]

Sources: dolt-storage.md · architecture.md · propulsion-principle.md · polecat-lifecycle-patrol.md · glossary.md · README · molecules.md · HOOKS.md · dog-infrastructure.md · polecat-self-managed-completion.md · integration-branches.md · overview.md · ground truth (local gt v0.12.0-197-gf28273a1)

13 · Gas Town ↔ Gas City

Gas City is Gas Town's successor SDK — gastownhall.ai frames it as recasting the town as “a composable SDK for building observable, multi-agent dark factories.” Every row below starts Gas-Town-first: the term you already learned somewhere in Parts I–III on the left, what actually happens to it on the Gas City side on the right — including, explicitly, the concepts that don't survive the move at all. [gastownhall.ai]

A 2026-07-11 re-check of gastownhall.ai found no renames or changelog since the original mapping was drawn: same lineage claim, same v1.0.0 version numbers for both projects (Gas Town released 2026-04-03; Gas City announced 2026-04-27), and no command or role names on the landing page at all — it's marketing copy, not documentation, so the mapping below still rests on each project's own docs, not on gastownhall.ai's phrasing. [gastownhall.ai]

The shift underneath every row: Gas Town ships as one opinionated application with seven fixed worker roles, hardcoded path-derived identity (~/gt/mayor/), and a purpose-built merge queue, escalation ladder, and federation layer baked into the gt binary itself. Gas City strips that down to nine composable primitives (City/Agent/Bead/Formula/Molecule/Rig/ Pack/Convoy/Order/Event) and reintroduces Gas Town's own roles as one importable “Gastown pack” rather than platform code — “the same SDK can express Gas Town, Ralph, or any other pack.” Its own docs are blunt about the porting trap: “Do not port code or prompts that assume directory path implies who the agent is” — identity moves from folder location to declared config. [coming-from-gastown]

Roles & primitives

Gas TownGas CityWhat changed
Mayor — chief-of-staff agent initiating convoys, coordinating work distribution, notifying usersConfigured agent + coordinating promptA role is a prompt, not platform code
Deacon — background supervisor daemon; heartbeat.json, health thresholdsOrchestrator health patrol + config thresholdsPlatform absorbed watchdog duty into the controller tick
Witness — per-rig polecat lifecycle manager / patrol agentEvents + waits, formulas, session scale configObservation duties became primitives, not a role
Refinery — per-rig merge-queue processorConfigured agent + a formula or order post-processing stepPipeline step, not a role — see the merge-queue row below for what's actually missing
Polecat — ephemeral per-rig worker; persistent identity across disposable sessionsScalable/transient agent config (a pool)Pool with min/max active sessions
Crew — persistent, human-managed worker with own git clonePersistent named agent configSame idea, declarative
Dog — Deacon's system-maintenance helper (Reaper, Compactor, Doctor, JSONL export, Dolt Backup)Core-pack exec orders (+ optional agent wrapper)Named maintenance daemons collapse into generic exec-order pack content; no per-dog primitives
Plugins (gt plugin)Orders (exec order = shell/orchestrator-side; formula order = agent-driven)Same slot, renamed
Convoys (hq-cv-* town-level tracking bead, tracks relation, swarm/stranded-convoy semantics)Still bead-backed grouping/lineage; “gc sling creates convoy structure while routing”Implementation boundary moved; swarm/stranded-specific vocabulary not carried over
Beads (substrate concept)Beads (unchanged substrate)Now everything is a bead, including mail and sessions
~/gt/… hardcoded trees, role homes (~/gt/mayor/)City dir with city.toml + .gc/ runtime state; agents in agents/<name>/ with explicit dirIdentity explicit, not path-derived

Concepts with no direct primitive — kept explicit, not glossed over

Gas TownGas CityWhat changed
Boot — ephemeral AI triage agent bridging Daemon and Deacon; one decision per 3-minute tick, then exitsNo dedicated equivalentNo equivalent. Watchdog logic stays mechanical — no documented AI-triage layer sits between the controller and the health patrol
Beads storage: sole Dolt SQL server per town — “no embedded mode and no SQLite”[beads] config selects a pluggable store: bd (Dolt, default), file, or exec:<script>Storage backend becomes swappable config instead of a hardcoded Dolt server
GUPP — “Gas Town Universal Propulsion Principle”: if there is work on your hook, YOU MUST RUN ITSame rule, unbranded — “if you find work on your hook, YOU RUN IT”Principle survives verbatim; the acronym/branding is dropped
MEOW — dual-defined even in Gas Town's own docs (Mayor-Enhanced Orchestration Workflow or Molecular Expression of Work)No branded acronym anywhere in Gas City's docs; the decomposition mechanism lives directly in Formula → Molecule/WispAcronym dropped; underlying mechanism generalized and unambiguous
Mail groups/queues/channels (hq-group-* fan-out, hq-q-* fifo/priority, hq-channel-* pub/sub with retention)Mail is a bead type addressed to a session alias or human — no group/queue/channel primitives documentedNo equivalent for broadcast mail. Point-to-point mail carries over; pub/sub-style mail does not
Escalation ladder (gt escalate, severity MEDIUM→HIGH→CRITICAL, auto-stale re-escalation, per-severity routing to bead/mail/email/sms)No escalation primitive or CLI verb found anywhere in the reference docsNo equivalent.
Scheduler dispatch throttling (scheduler.max_polecats, deferred dispatch as daemon heartbeat step 14)[convergence] config: max_per_agent (default 2), max_total (default 10)Same throttling idea, reshaped as a named “convergence” limit in city.toml
Wasteland federation (gt wl join/browse/claim/done/post/sync; DoltHub-backed cross-town work sharing)No federation/cross-city work-sharing mentioned anywhereNo equivalent.
Seance (gt seance — lists/replays predecessor sessions from .events.jsonl; --talk for full-context conversation)Closest overlap: gc events (JSONL event stream) plus gc session peek/gc supervisor logsNo direct equivalent — no conversational replay of a past session
Claude Code lifecycle hooks subsystem (~/.gt/hooks-base.json + overrides, gt hooks sync/diff/scan, base→role→rig+role merge chain)Folded directly into [[agent]] config fields: prompt_template, inject_fragments/append_fragments, pre_start/session_setup/session_liveThe separate hook-file generate/sync step disappears; declared inline in city.toml instead
Merge-queue design specifics (batch-then-bisect “Bors-style” landing, integration branches with 3-layer guardrails, poll-based MR discovery)Not described as SDK infrastructure anywhere — Refinery's replacement (row above) is pipeline glue, not a merge-queue primitiveNo equivalent for the merge-queue mechanics themselves — a pack wanting Bors-style batching authors it as its own formula/order
Model-aware molecule routing (design doc, status “In Progress”: per-step model/provider constraints, heuristic router, ~/.gt/usage.jsonl)[providers]/[upstreams] presets exist for config-time model selection; no evidence of automatic capability-based runtime routingConfig-time provider choice carries over; automatic routing isn't confirmed shipped in either system — parallel-but-unproven, not migrated
Gas Town → Gas City, Gas-Town-first solid = same concept, minor rename · dashed = semantics changed · red stub = no equivalent GAS TOWN GAS CITY Mayor Agent + coordinating prompt Deacon Health-patrol config thresholds Witness Events, waits, formulas Refinery Formula/order post-step Polecat Scalable agent pool Crew Persistent agent config Dog Core-pack exec orders Beads Beads (unchanged substrate) Convoy Convoy (bead-backed, less vocab) Escalation ladder — no Gas City equivalent — Wasteland federation — no Gas City equivalent — same concept, minor rename semantics changed no Gas City equivalent Full rationale for every row, including the eleven not pictured, is in the two tables above.
Eleven representative rows out of the twenty-three in the tables above: two roles change almost nothing (Crew, Beads), seven change name or shape while keeping the idea (dashed), and two drop out entirely with no Gas City equivalent at all (red, dashed, disconnected).

When should a Gas Town user actually care about Gas City?

If you're running one opinionated town with the stock seven roles and don't need multiple concurrently-configured “packs,” Gas Town's out-of-the-box roles, merge queue, and escalation ladder still do more for you with less config than Gas City would — Gas City makes you author back several behaviors Gas Town ships free. If you need more than one flavor of orchestration on shared infrastructure, or a Kubernetes/ACP/herdr session backend instead of tmux-only, Gas City's pack/import/override model and pluggable runtime providers are the actual reason to migrate. If your install depends on Wasteland federation, mail groups/queues/channels, gt escalate, gt seance, or the Bors-style merge queue, treat those as blocking gaps, not renames, until a pack reimplementing them turns up. And if you're porting prompts or scripts written against ~/gt/<role>/ path assumptions, budget time to rewrite them against explicit agent.toml/dir config — Gas City's own docs call this out as the single most common porting mistake. [coming-from-gastown · escalation.md · WASTELAND.md]

This table runs Gas-Town-first, on purpose — it's this guide, so every row starts from a term you already learned in Parts I–III. For the same mapping read the other way — Gas City-first, with the muscle-memory gtgc/bd command table alongside it — see the Gas City guide, §10 “Gas Town → Gas City.”

Sources: gastownhall.ai · README · glossary.md · overview.md · architecture.md · dolt-storage.md · dog-infrastructure.md · escalation.md · mail-protocol.md · WASTELAND.md · scheduler.md · HOOKS.md · model-aware-molecules.md · convoy.md · coming-from-gastown · gastown-command-map

14 · Quiz, Glossary & Sources

Twenty questions, every answer traceable to a specific section above — including the honest, partial, and blocked outcomes this guide's own labs actually produced. Reveal each answer only after you've committed to one; the point is finding out what you don't know yet, not the count.

Quiz

1. In one sentence, what is Gas Town?
2. What five words carry almost all of Gas Town's vocabulary, per this guide's own mental-model section?
3. Yegge draws a direct comparison between Gas Town and Kubernetes. What's the one-line difference he states between them?
4. What is a “pinned bead,” and how is it different from an ordinary task bead?
5. Lab 2 tried to register a scratch repo as a rig and sling one task to it. What state was the task bead actually in by the end of the lab — open, in_progress, or something else?
6. Lab 4 created a dependency edge (bd dep add) between two tasks inside a convoy, then tried to watch the convoy dashboard for serialization. What did gt convoy list actually show?
7. When should you reach for gt mail send instead of the default gt nudge?
8. What happens if you run gt sling bd-xyz beads/amber with no convoy already created?
9. Lab 1's first real gt up run reported that Dolt and the daemon had both “failed to start,” with exit code 1. Was that true?
10. In Lab 5, the Deacon sent a real, unplanned CRITICAL nudge to the Mayor. Why didn't that nudge produce any durable follow-up work?
11. Symptom: a polecat's tmux session is alive, but its bead hasn't progressed in a while. What should you check before concluding it's actually stuck?
12. Symptom: gt status --fast shows daemon (stopped) and dolt (stopped, :3307). What's the fix, and what should you specifically not do?
13. Symptom: slung work never gets picked up — a “stranded convoy.” What two structurally different causes does the guide name?
14. Ground truth's ps aux capture settled a real precedence question: between a cost tier's (default/opus) label and a configured custom agent alias, which one actually determines the model that runs?
15. Under the economy cost tier, which roles get downgraded to a cheaper model, and which two stay on Opus?
16. Lab 2's adopted rig never got a working Witness or Refinery. What specifically caused that, and how did gt doctor report it?
17. What is GUPP, and where in this guide's own labs did it demonstrably fail to fire — for a real, mechanical reason, not a bug?
18. gt doctor --fix killed hq-boot, hq-deacon, and hq-mayor as zombies in one pass. Which two respawned within about a minute, and which one did not respawn even after roughly 15 minutes?
19. Per this guide's own migration table, is there a Gas City equivalent for Gas Town's escalation ladder (gt escalate, severity routing)?
20. What generally happens to Gas Town's seven worker roles (Mayor, Deacon, Witness, Refinery, Polecat, Crew, Dog) when migrating to Gas City?

Glossary

Every Gas Town term used in this guide, in one line each, with its closest Gas City equivalent (“—” where this guide's own migration research (§13) found none).

TermDefinitionGas City equivalent
TownTop-level workspace folder (e.g. ~/gt/) containing every project, agent, and piece of configuration; the gt binary manages everything under it. City (city.toml + .gc/ runtime dir)
RigA project: a folder tracked by its own git repository inside the town, holding its own Crew workspace, Hooks, and Polecats.Rig (still a primitive; reshaped by the pack/override model)
BeadThe foundational data unit — an issue-tracker-style record (ID, description, status, assignee) now stored in Dolt; tasks, agents, mail, and wisps are all beads. Bead (unchanged substrate)
Pinned beadA bead that gives an agent, role, or hook a persistent identity and never closes, unlike an ordinary open → in_progress → closed task bead. — (identity is explicit agent.toml/dir config instead)
RoleA job description, not a person — any number of ephemeral or persistent agent sessions can fill one.Configured agent + prompt (a role is a prompt, not platform code)
SlingThe verb (gt sling) for putting work onto an agent's hook — the work-distribution step of the delegation workflow.gc sling
MayorGlobal/town-level coordinator — “chief-of-staff agent” that decomposes goals, creates convoys, notifies users, and serves as the escalation sink. Configured agent + coordinating prompt
DeaconBackground supervisor daemon; runs continuous patrol cycles, monitors system health, triggers recovery.Orchestrator health patrol + config thresholds
BootEphemeral AI triage agent bridging the mechanical daemon and the intelligent Deacon; makes one decision per 3-minute tick, then exits. — (no dedicated equivalent; watchdog logic stays mechanical)
WitnessPer-rig polecat lifecycle manager; detects stalls and zombies, nudges, relays escalations; does not gate completion.Events + waits, formulas, session scale config
RefineryPer-rig merge-queue processor; merges polecat branches to main via verification gates.Configured agent + a formula/order post-processing step
PolecatEphemeral per-rig worker with persistent identity but disposable sessions; works a branch, submits an MR, then retires — no idle reuse. Scalable/transient agent config (a pool)
CrewPersistent, human-managed worker with its own full git clone; pushes to main directly, unmonitored.Persistent named agent config
Dog(s)The Deacon's system-maintenance helpers (Reaper, Compactor, Doctor, JSONL export, Dolt Backup) — never do user project work. Core-pack exec orders (+ optional agent wrapper)
Hook (agent hook)A pinned bead acting as an agent's primary work queue; GUPP compels action the instant work lands on it. Folded into [[agent]] config fields
Claude Code lifecycle hooksThe separate gt hooks subsystem managing settings.json files injected at session boundaries (SessionStart, PreCompact, etc.). Folded into [[agent]] config (prompt_template, inject_fragments, …)
GUPP“Gas Town Universal Propulsion Principle” — if there's work on your hook, you must run it immediately, with no confirmation step. Same rule, unbranded (“run what you find”)
MEOWDual-defined even inside Gas Town's own docs: “Mayor-Enhanced Orchestration Workflow” (the seven-step delegation pattern) or “Molecular Expression of Work” (decomposition via Formula/Molecule) — genuinely unreconciled. — (no branded acronym; decomposition lives directly in Formula → Molecule/Wisp)
NDI“Nondeterministic Idempotence” — the glossary's third Core Principle alongside GUPP and MEOW; named in this guide (§12) but not unpacked further there. — (not compared in this guide's migration research)
ConvoyA persistent, town-level tracking bead (hq-cv-*) that bundles issues from any rig for batch visibility. Convoy (still bead-backed; some vocabulary not carried over)
SwarmThe ephemeral set of workers currently assigned to a convoy's issues — not persistent, unlike the convoy itself.— (vocabulary not carried over)
Stranded convoyA convoy with ready work but no polecats assigned, needing attention.— (vocabulary not carried over)
WispAn ephemeral, Dolt-ignored bead (e.g. a patrol cycle's root-only working state) that generates no durable commit.— (roughly a Molecule's ephemeral working state)
MoleculeA persistent, materialized instance of decomposed work steps (“Liquid”), as opposed to an ephemeral Root Wisp (“Vapor”).Molecule (a Gas City primitive)
FormulaThe source TOML template (“Ice-9”) that gets cooked into a Protomolecule, then poured into a Molecule or wisped into a Root Wisp.Formula (a Gas City primitive)
ProtomoleculeA frozen template produced by bd cook from a Formula, before it becomes a Molecule or Wisp. — (absorbed into Gas City's Formula/Molecule pipeline)
PatrolAn ephemeral, standing loop (Daemon/Boot/Deacon/Witness/Refinery) that cycles health checks and triggers recovery actions. Orchestrator health patrol (config thresholds)
MailA structured, durable message; every mail is itself a bead (type=message) and creates at least one Dolt commit. Mail (a bead type addressed to a session alias or human)
NudgeEphemeral, zero-storage tmux delivery to a live session; lost if the session dies.gc session nudge
Escalation laddergt escalate's severity system (MEDIUM/HIGH/CRITICAL) routing to bead/mail/email/sms per settings/escalation.json. — (no equivalent)
HandoffAn agent mailing itself before its session cycles, so its replacement session picks up exactly where it left off. — (session lifecycle handled by orchestrator config)
Seancegt seance's replay/conversation tool for a predecessor session's .events.jsonl log. — (closest overlap: gc events, gc session peek)
Merge queue (MQ)The Refinery's batch-then-bisect (designed) / serialized claim-gate-merge (shipped) pipeline that keeps polecats from pushing straight to main. — (not SDK infrastructure; would be authored as a pack formula/order)
Integration branchAn epic-scoped branch (gt mq integration create) that the merge pipeline auto-targets until all children close and it lands as one merge. — (no equivalent named)
DoltThe sole SQL storage backend (MySQL protocol, port 3307) underneath every bead, town- and rig-level — no embedded mode, no SQLite. [beads] config: pluggable store (bd/Dolt default, file, exec:)
Cost tierA town-wide dial (standard/economy) that swaps which model class each role runs on. [providers]/[upstreams] presets (config-time only)
Scheduler (dispatch throttling)scheduler.max_polecats, a daemon-managed cap on how many polecats can be dispatched at once. [convergence] config: max_per_agent / max_total
WastelandA federation feature (gt wl …) for cross-town, DoltHub-backed work sharing — “wild-west mode,” no trust gating yet.— (no equivalent)

Sources

Primary: steveyegge/gastown README · reference.md · glossary.md · overview.md · HOOKS.md · convoy.md · propulsion-principle.md · heartbeats.md · mail-protocol.md · escalation.md · model-aware-molecules.md · local-rig-bootstrap.md · WASTELAND.md · internal/shell/integration.go · architecture.md · dolt-storage.md · dog-infrastructure.md · dog-execution-model.md · polecat-lifecycle.md · polecat-lifecycle-patrol.md · persistent-polecat-pool.md · polecat-self-managed-completion.md · identity.md · molecules.md · integration-branches.md · scheduler.md · gastownhall.ai · docs.gastownhall.ai · Gas City: coming-from-gastown · Gas City: gastown-command-map

Lineage: “Welcome to Gas Town” (Steve Yegge).

Community: “A Day in Gas Town” (DoltHub) · “Gas Town, Decoded” (Andrew Lilley Brinker) · Better Stack: “Building with Gas Town” · Shinzo: “How to use Gas Town” (direct fetch returned an empty JS shell; only search-snippet fragments used, low confidence, flagged inline where cited).

Local: gt v0.12.0-197-gf28273a1 ground truth, 2026-07-11.

All sources fetched 2026-07-11.