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 current1 · 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.
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]
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]
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]
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;:3307is 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 customzclaudealias 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 -vprintedhq-mayor stoppedeven while thehq-mayortmux pane was demonstrably alive and running Claude Code. Don't read○/"stopped" as "no tmux session"; checktmux lsdirectly if that distinction matters.No rigs registered.— unchanged; that's still Stage 1.
[ground truth: local gt v0.12.0-197-gf28273a1]
gt up starts the daemon, then Dolt, then
the Deacon and Mayor sessions — gt status is how you confirm all of it actually
happened.gt status for realGoal: start the daemon and Dolt, and confirm they're really up — not just that a command said so.
-
Check the baseline before starting anything:
cd ~/gt && gt status --fastExpect:
daemon (stopped),dolt (stopped, :3307), and "No rigs registered." — see the annotated block above. -
Bring services up:
gt upExpect, 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 agt statuscheck 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 checkgt status, not as gospel. -
Re-run it (it's idempotent) and confirm with status:
gt up gt statusExpect:
✓ Dolt: already running/✓ Daemon: PID 10405/✓ Deacon: hq-deacon/✓ Mayor: hq-mayor/✓ All services running., then the full annotatedgt statusblock above. -
Confirm Dolt is genuinely listening, not just claimed:
lsof -i :3307Expect: a
dolt … (LISTEN)line plus established connections from thegt,dolt, andbeadsprocesses.
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
| Symptom | What ground truth showed | Fix |
|---|---|---|
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]
bd create before any bead existed
to sling — so the polecat-spawn → work → close pipeline is shown as documented, not demonstrated.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.
-
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). -
The obvious-looking rig-add command doesn't work:
cd ~/gt && gt rig add ~/gt-lab-scratchExpect, honestly: this fails —
gt rig addtakes two required positional arguments,<name> <git-url>, not a single path. Routing around it with--local-repo/--adoptpointed at an external path fails too:Error: adopting rig: directory does not exist: /Users/you/gt/gt_lab_scratchError: 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 -
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 --forceAdopting 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: glsExpect: that blank
Repository:line is real —--adopt --forcenever sets agit_url, sorigs.jsonrecords"git_url": ""for this rig.gt doctor --rig gt_lab_scratchconfirms 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. -
Unblock the beads wiring:
gt doctor --fix gt dolt init-rig gt_lab_scratchExpect:
gt doctor --fixfixes the prefix wiring ("changing issue_prefix from 'issue_prefix (not set)' to 'gls'") — and, as a real, unprompted side effect, classifies the runninghq-boot/hq-deacon/hq-mayortmux sessions as zombies and kills all three (confirmed viatmux lsdropping from 5 sessions back to 2 — the two pre-existing, unrelated sessions were never touched).gt dolt init-rigthen registers the rig's database with the running Dolt server:✓ Initialized rig database "gt_lab_scratch". -
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" --jsonExpect, 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 doctorin 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 --fixdoes 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.jsonl —
hq-* 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]
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.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.
-
List everything at the town level:
bd list --include-infra --allExpect: a long list of pre-existing
hq-*beads — agent beads (hq-mayor,hq-deacon) and dozens ofhq-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 thedependenciestable. -
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 -
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/--selfspecial 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,
stranded — show 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]
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.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.
-
Confirmed real syntax (
gt convoy create --help):gt convoy create "Deploy v2.0" gt-abc bd-xyzExpect: 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. -
Order the two tasks with a real dependency edge:
bd dep add bd-xyz gt-abcExpect:
bd-xyzis now blocked ongt-abc— it should not appear inbd readyuntilgt-abccloses. -
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]
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).
-
Confirmed entry points, from
--help: interactivegt mayor attach, or headlessgt sling <bead-or-formula> mayor/. A candidate likegt 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. -
Because
bd createis 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. -
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 -vafterward showedhq-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).
-
Baseline:
gt config cost-tierCost tier: standard / All roles use Opus (highest quality)Expect: all six roles (mayor, deacon, witness, refinery, polecat, crew) listed as
(default/opus). -
Switch to the cheaper tier and observe the real per-role assignment:
gt config cost-tier economyCost tier set to economy Patrol roles use Sonnet/Haiku, workers use OpusExpect 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 undereconomy; the two roles that actually write code stay on Opus. -
Switch back and confirm the round trip:
gt config cost-tier standardExpect: 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
| Role | tmux session naming | Log / state file | Status-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]
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.
-
See who's actually running:
tmux lsExpect, 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). -
Follow a session's live transcript:
tmux capture-pane -p -t hq-mayor | tail -40Expect: 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 togt mail inboxthat 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-bootshow the Deacon mid-patrol and Boot mid-triage the same way. -
Cross-check the status line against what you just saw was actually alive:
gt status -v --watchExpect a discrepancy, not reassurance: ground truth's own repeated
gt status -vchecks (taken as discrete snapshots, not a continuous stream this session) printedhq-mayor stopped/agent: zclaude/GLM-5/hook: (none)— whiletmux lsconfirmed 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." -
Tail the daemon-directory logs from the table above:
ls ~/gt/logs ~/gt/daemonExpect 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. -
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 runninghq-boot/hq-deacon/hq-mayorsessions as zombies and killed all three:tmux ls # dropped from 5 sessions to the 2 pre-existing, unrelated onesRecovery observed:
hq-bootandhq-mayorwere both back intmux lswithin roughly 60–90 seconds (new session-creation timestamps), consistent with the daemon's fast per-tickensureXRunning()checks.hq-deaconhad 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 blanketgt down --allused 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
| Symptom | Diagnosis commands | Fix |
|---|---|---|
| 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]
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]
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]
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]
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]
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]
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]
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]
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: SessionStart → gt prime --hook,
PreCompact → gt prime --hook, UserPromptSubmit →
gt mail check --inject, Stop → gt 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 Town | Gas City | What changed |
|---|---|---|
| Mayor — chief-of-staff agent initiating convoys, coordinating work distribution, notifying users | Configured agent + coordinating prompt | A role is a prompt, not platform code |
| Deacon — background supervisor daemon; heartbeat.json, health thresholds | Orchestrator health patrol + config thresholds | Platform absorbed watchdog duty into the controller tick |
| Witness — per-rig polecat lifecycle manager / patrol agent | Events + waits, formulas, session scale config | Observation duties became primitives, not a role |
| Refinery — per-rig merge-queue processor | Configured agent + a formula or order post-processing step | Pipeline step, not a role — see the merge-queue row below for what's actually missing |
| Polecat — ephemeral per-rig worker; persistent identity across disposable sessions | Scalable/transient agent config (a pool) | Pool with min/max active sessions |
| Crew — persistent, human-managed worker with own git clone | Persistent named agent config | Same 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 dir | Identity explicit, not path-derived |
Concepts with no direct primitive — kept explicit, not glossed over
| Gas Town | Gas City | What changed |
|---|---|---|
| Boot — ephemeral AI triage agent bridging Daemon and Deacon; one decision per 3-minute tick, then exits | No dedicated equivalent | No 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 IT | Same 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/Wisp | Acronym 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 documented | No 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 docs | No 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 anywhere | No 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 logs | No 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_live | The 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 primitive | No 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 routing | Config-time provider choice carries over; automatic routing isn't confirmed shipped in either system — parallel-but-unproven, not migrated |
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 gt→gc/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
bd create
failed with a real, town-wide Dolt schema defect (Error 1062 (HY000): duplicate primary key
given: [wisps]) before an open bead could be created — the
polecat-spawn → work → close pipeline was never reached, and gt sling itself was never
even invoked. (§4)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?gt convoy list hit the identical
schema defect from Labs 2–3 (column "depends_on_issue_id" could not be found in any table in
scope). Neither the two dependent beads nor the convoy tracking them ever materialized, so
whether the blocked task actually waited for its blocker to close could not be observed — recorded
as a partial/blocked observation, not an invented success. (§6)gt mail send
instead of the default gt nudge?gt nudge is ephemeral tmux delivery — zero storage, lost if the session dies —
and is the default; every gt mail send creates a wisp bead plus at least one Dolt
commit, so mail budgets are kept tight by role (polecats 0–1 per session, Dogs zero).
(§5)gt sling
bd-xyz beads/amber with no convoy already created?gt up run reported
that Dolt and the daemon had both “failed to start,” with exit code 1. Was that true?gt status check
run seconds later showed both services genuinely up; gt up's own readiness window had
fired before the slower-starting services actually finished. Re-running gt up returned
a clean “All services running.” The lesson: don't trust one gt up exit code — verify
with gt status. (§3)gt feed --problems for the GUPP Violation / Stalled /
Zombie / Working / Idle taxonomy, then tmux capture-pane -t <session> | tail -40
— and cross-check all three heartbeat stores (Deacon file, per-session state, agent-bead label)
against tmux window_activity first: a live session with a stale store is
heartbeat-write divergence, not a stuck agent. (§9)gt status --fast shows
daemon (stopped) and dolt (stopped, :3307). What's the fix, and what
should you specifically not do?scheduler.max_polecats > 0 is throttling dispatch
into deferred mode (fix: gt scheduler resume or raise the cap), or the rig itself
structurally can't run a Witness/Refinery at all — e.g. a remote-less rig adopted via
--adopt --force, which Lab 2 found never gets a working Witness or Refinery, so
nothing there can ever pick work up regardless of throttling. (§9, also
§4)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?settings/config.json's
default_agent is a separate, higher-precedence override supplying the actual
runtime+model; cost-tier's “opus” label only describes a quality tier for built-in presets, which
the custom alias isn't. Three live ps aux process lines, one per role, all ran the
alias's --model GLM-5 flag, confirming this live rather than just inferring it from
docs. (§8)economy cost tier, which
roles get downgraded to a cheaper model, and which two stay on Opus?gt doctor report it?gt rig add --adopt --force left git_url empty,
so the clones gt rig add normally populates for witness/refinery/mayor were never
created. gt doctor --rig gt_lab_scratch reported “Witness structure incomplete,”
“Refinery structure incomplete,” and “Mayor structure incomplete (clone requires repo URL).”
(§4, also §11)gt status -v showed hook: (none), so there was
nothing pinned for GUPP to compel action on. (§12, event in
§7)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?gt escalate, severity
routing)?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).
| Term | Definition | Gas City equivalent |
|---|---|---|
| Town | Top-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) |
| Rig | A 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) |
| Bead | The 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 bead | A 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) |
| Role | A 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) |
| Sling | The verb (gt sling) for putting work onto an agent's hook — the
work-distribution step of the delegation workflow. | gc sling |
| Mayor | Global/town-level coordinator — “chief-of-staff agent” that decomposes goals, creates convoys, notifies users, and serves as the escalation sink. | Configured agent + coordinating prompt |
| Deacon | Background supervisor daemon; runs continuous patrol cycles, monitors system health, triggers recovery. | Orchestrator health patrol + config thresholds |
| Boot | Ephemeral 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) |
| Witness | Per-rig polecat lifecycle manager; detects stalls and zombies, nudges, relays escalations; does not gate completion. | Events + waits, formulas, session scale config |
| Refinery | Per-rig merge-queue processor; merges polecat branches to main via verification gates. | Configured agent + a formula/order post-processing step |
| Polecat | Ephemeral 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) |
| Crew | Persistent, 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 hooks | The 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”) |
| MEOW | Dual-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) |
| Convoy | A 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) |
| Swarm | The ephemeral set of workers currently assigned to a convoy's issues — not persistent, unlike the convoy itself. | — (vocabulary not carried over) |
| Stranded convoy | A convoy with ready work but no polecats assigned, needing attention. | — (vocabulary not carried over) |
| Wisp | An 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) |
| Molecule | A persistent, materialized instance of decomposed work steps (“Liquid”), as opposed to an ephemeral Root Wisp (“Vapor”). | Molecule (a Gas City primitive) |
| Formula | The 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) |
| Protomolecule | A frozen template produced by bd cook from a Formula,
before it becomes a Molecule or Wisp. |
— (absorbed into Gas City's Formula/Molecule pipeline) |
| Patrol | An ephemeral, standing loop (Daemon/Boot/Deacon/Witness/Refinery) that cycles health checks and triggers recovery actions. | Orchestrator health patrol (config thresholds) |
A 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) | |
| Nudge | Ephemeral, zero-storage tmux delivery to a live session; lost if the session dies. | gc session nudge |
| Escalation ladder | gt escalate's severity system (MEDIUM/HIGH/CRITICAL)
routing to bead/mail/email/sms per settings/escalation.json. |
— (no equivalent) |
| Handoff | An agent mailing itself before its session cycles, so its replacement session picks up exactly where it left off. | — (session lifecycle handled by orchestrator config) |
| Seance | gt 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 branch | An 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) |
| Dolt | The 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 tier | A 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 |
| Wasteland | A 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.