Skip to main content

Gas Town Reference

Technical reference for Gas Town internals. Read the README first.

For directory structure details, see architecture.md.

Beads Routing

Gas Town routes beads commands based on issue ID prefix. You don't need to think about which database to use - just use the issue ID.

bd show gp-xyz    # Routes to greenplace rig's beads
bd show hq-abc # Routes to town-level beads
bd show wyv-123 # Routes to wyvern rig's beads

How it works: Routes are defined in ~/gt/.beads/routes.jsonl. Each rig's prefix maps to its beads location (the mayor's clone in that rig).

PrefixRoutes ToPurpose
hq-*~/gt/.beads/Mayor mail, cross-rig coordination
gp-*~/gt/greenplace/mayor/rig/.beads/Greenplace project issues
wyv-*~/gt/wyvern/mayor/rig/.beads/Wyvern project issues

Debug routing: BD_DEBUG_ROUTING=1 bd show <id>

Configuration

Rig Config (config.json)

{
"type": "rig",
"name": "myproject",
"git_url": "https://github.com/...",
"default_branch": "main",
"beads": { "prefix": "mp" }
}

Rig config fields:

FieldTypeDefaultDescription
default_branchstring"main"Default branch for the rig. Auto-detected from remote during gt rig add. Used as the merge target by the Refinery and as the base for polecats when no integration branch is active.

Settings (settings/config.json)

{
"theme": "desert",
"merge_queue": {
"enabled": true,
"run_tests": true,
"setup_command": "",
"typecheck_command": "",
"lint_command": "",
"test_command": "go test ./...",
"build_command": "",
"on_conflict": "assign_back",
"delete_merged_branches": true,
"retry_flaky_tests": 1,
"poll_interval": "30s",
"max_concurrent": 1,
"integration_branch_polecat_enabled": true,
"integration_branch_refinery_enabled": true,
"integration_branch_template": "integration/{title}",
"integration_branch_auto_land": false
}
}

Merge queue fields:

FieldTypeDefaultDescription
enabledbooltrueWhether the merge queue is active
run_testsbooltrueRun tests before merging
setup_commandstring""Setup/install command (e.g., pnpm install)
typecheck_commandstring""Type check command (e.g., tsc --noEmit)
lint_commandstring""Lint command (e.g., eslint .)
test_commandstring"go test ./..."Test command to run
build_commandstring""Build command (e.g., go build ./...)
on_conflictstring"assign_back"Conflict strategy: assign_back or auto_rebase
delete_merged_branchesbooltrueDelete source branches after merging
retry_flaky_testsint1Number of times to retry flaky tests
poll_intervalstring"30s"How often Refinery polls for new MRs
max_concurrentint1Maximum concurrent merges
integration_branch_polecat_enabled*booltruePolecats auto-source worktrees from integration branches
integration_branch_refinery_enabled*booltruegt done / gt mq submit auto-target integration branches
integration_branch_templatestring"integration/{title}"Branch name template ({title}, {epic}, {prefix}, {user})
integration_branch_auto_land*boolfalseRefinery patrol auto-lands when all children closed

See Integration Branches for integration branch details.

Runtime (.runtime/ - gitignored)

Process state, PIDs, ephemeral data.

Rig-Level Configuration

Rigs support layered configuration through:

  1. Wisp layer (.beads-wisp/config/) - transient, local overrides
  2. Rig identity bead labels - persistent rig settings
  3. Town defaults (~/gt/settings/config.json)
  4. System defaults - compiled-in fallbacks

Polecat Branch Naming

Configure custom branch name templates for polecats:

# Set via wisp (transient - for testing)
echo '{"polecat_branch_template": "adam/{year}/{month}/{description}"}' > \
~/gt/.beads-wisp/config/myrig.json

# Or set via rig identity bead labels (persistent)
bd update gt-rig-myrig --labels="polecat_branch_template:adam/{year}/{month}/{description}"

Template Variables:

VariableDescriptionExample
{user}From git config user.nameadam
{year}Current year (YY format)26
{month}Current month (MM format)01
{name}Polecat namealpha
{issue}Issue ID without prefix123 (from gt-123)
{description}Sanitized issue titlefix-auth-bug
{timestamp}Unique timestamp1ks7f9a

Default Behavior (backward compatible):

When polecat_branch_template is empty or not set:

  • With issue: polecat/{name}/{issue}@{timestamp}
  • Without issue: polecat/{name}-{timestamp}

Example Configurations:

# GitHub enterprise format
"adam/{year}/{month}/{description}"

# Simple feature branches
"feature/{issue}"

# Include polecat name for clarity
"work/{name}/{issue}"

Formula Format

formula = "name"
type = "workflow" # workflow | expansion | aspect
version = 1
description = "..."

[vars.feature]
description = "..."
required = true

[[steps]]
id = "step-id"
title = "{{feature}}"
description = "..."
needs = ["other-step"] # Dependencies

Composition:

extends = ["base-formula"]

[compose]
aspects = ["cross-cutting"]

[[compose.expand]]
target = "step-id"
with = "macro-formula"

Molecule Lifecycle

For the full lifecycle diagram and detailed command reference, see concepts/molecules.md.

Summary: Formula (TOML) --bd cook--> Protomolecule --bd mol pour--> Mol (persistent) or Wisp (ephemeral) --bd squash--> Digest.

Operationbd (data)gt (agent)
Cook/pour/wispbd cook, bd mol pour/wisp
Squash/burnbd mol squash/burn <id>gt mol squash/burn (attached)
Navigatebd mol current, bd mol showgt hook, gt mol current
Attachgt mol attach/detach

Agent Lifecycle

Polecat Shutdown

1. Work through formula checklist (shown inline by gt prime)
2. Submit to merge queue via gt done
3. gt done nukes sandbox and exits
4. Witness removes worktree + branch

Session Cycling

1. Agent notices context filling
2. gt handoff (sends mail to self)
3. Manager kills session
4. Manager starts new session
5. New session reads handoff mail

Environment Variables

Gas Town sets environment variables for each agent session via config.AgentEnv(). These are set in tmux session environment when agents are spawned.

Core Variables (All Agents)

VariablePurposeExample
GT_ROLEAgent role typemayor, witness, polecat, crew
GT_ROOTTown root directory/home/user/gt
BD_ACTORAgent identity for attributiongastown/polecats/toast
GIT_AUTHOR_NAMECommit attribution (same as BD_ACTOR)gastown/polecats/toast
BEADS_DIRBeads database location/home/user/gt/gastown/.beads

Rig-Level Variables

VariablePurposeRoles
GT_RIGRig namewitness, refinery, polecat, crew
GT_POLECATPolecat worker namepolecat only
GT_CREWCrew worker namecrew only
BEADS_AGENT_NAMEAgent name for beads operationspolecat, crew

Other Variables

VariablePurpose
GIT_AUTHOR_EMAILWorkspace owner email (from git config)
GT_TOWN_ROOTOverride town root detection (manual use)
CLAUDE_RUNTIME_CONFIG_DIRCustom Claude settings directory

Environment by Role

RoleKey Variables
MayorGT_ROLE=mayor, BD_ACTOR=mayor
DeaconGT_ROLE=deacon, BD_ACTOR=deacon
BootGT_ROLE=deacon/boot, BD_ACTOR=deacon-boot
WitnessGT_ROLE=witness, GT_RIG=<rig>, BD_ACTOR=<rig>/witness
RefineryGT_ROLE=refinery, GT_RIG=<rig>, BD_ACTOR=<rig>/refinery
PolecatGT_ROLE=polecat, GT_RIG=<rig>, GT_POLECAT=<name>, BD_ACTOR=<rig>/polecats/<name>
CrewGT_ROLE=crew, GT_RIG=<rig>, GT_CREW=<name>, BD_ACTOR=<rig>/crew/<name>

Doctor Check

The gt doctor command verifies that running tmux sessions have correct environment variables. Mismatches are reported as warnings:

⚠ env-vars: Found 3 env var mismatch(es) across 1 session(s)
hq-mayor: missing GT_ROOT (expected "/home/user/gt")

Fix by restarting sessions: gt shutdown && gt up

Agent Working Directories and Settings

Each agent runs in a specific working directory and has its own Claude settings. Understanding this hierarchy is essential for proper configuration.

Working Directories by Role

RoleWorking DirectoryNotes
Mayor~/gt/mayor/Town-level coordinator, isolated from rigs
Deacon~/gt/deacon/Background supervisor daemon
Witness~/gt/<rig>/witness/No git clone, monitors polecats only
Refinery~/gt/<rig>/refinery/rig/Worktree on main branch
Crew~/gt/<rig>/crew/<name>/rig/Persistent human workspace clone
Polecat~/gt/<rig>/polecats/<name>/rig/Polecat worktree (ephemeral sandbox)

Note: The per-rig <rig>/mayor/rig/ directory is NOT a working directory—it's a git clone that holds the canonical .beads/ database for that rig.

Settings File Locations

Settings are installed in gastown-managed parent directories and passed to Claude Code via the --settings flag. This keeps customer repos clean:

~/gt/
├── mayor/.claude/settings.json # Mayor settings (cwd = settings dir)
├── deacon/.claude/settings.json # Deacon settings (cwd = settings dir)
└── <rig>/
├── crew/.claude/settings.json # Shared by all crew members
├── polecats/.claude/settings.json # Shared by all polecats
├── witness/.claude/settings.json # Witness settings
└── refinery/.claude/settings.json # Refinery settings

The --settings flag loads these as a separate priority tier that merges additively with any project-level settings in the customer repo.

CLAUDE.md

Only ~/gt/CLAUDE.md exists on disk — a minimal identity anchor that prevents agents from losing their Gas Town identity after context compaction or new sessions.

Full role context (~300-500 lines per role) is injected ephemerally by gt prime via the SessionStart hook. No per-directory CLAUDE.md or AGENTS.md files are created.

Why no per-directory files?

  • Claude Code traverses upward from CWD for CLAUDE.md — all agents under ~/gt/ find the town-root file
  • AGENTS.md (for Codex) uses downward traversal from git root — parent directories are invisible, so per-directory AGENTS.md never worked
  • The real context comes from gt prime, making on-disk bootstrap pointers redundant

Customer Repo Files (CLAUDE.md and .claude/)

Gas Town no longer uses git sparse checkout to hide customer repo files. Customer repositories can have their own .claude/ directory and CLAUDE.md — these are preserved in all worktrees (crew, polecats, refinery, mayor/rig).

Gas Town's context comes from the town-root CLAUDE.md identity anchor (picked up by all agents via Claude Code's upward directory traversal), gt prime via the SessionStart hook, and the customer repo's own CLAUDE.md. These coexist safely because:

  • --settings flag provides Gas Town settings as a separate tier that merges additively with customer project settings, so both coexist cleanly
  • gt prime injects role context ephemerally via SessionStart hook, which is additive with the customer's CLAUDE.md — both are loaded
  • Gas Town settings live in parent directories (not in customer repos), so customer .claude/ files are fully preserved

Doctor check: gt doctor warns if legacy sparse checkout is still configured. Run gt doctor --fix to remove it. Tracked settings.json files in worktrees are recognized as customer project config and are not flagged as stale.

Settings Inheritance

Claude Code's settings are layered from multiple sources:

  1. .claude/settings.json in current working directory (customer project)
  2. .claude/settings.json in parent directories (traversing up)
  3. ~/.claude/settings.json (user global settings)
  4. --settings <path> flag (loaded as a separate additive tier)

Gas Town uses the --settings flag to inject role-specific settings from gastown-managed parent directories. This merges additively with customer project settings rather than overriding them.

Settings Templates

Gas Town uses two settings templates based on role type:

TypeRolesKey Difference
InteractiveMayor, CrewMail injected on UserPromptSubmit hook
AutonomousPolecat, Witness, Refinery, DeaconMail injected on SessionStart hook

Autonomous agents may start without user input, so they need mail checked at session start. Interactive agents wait for user prompts.

Troubleshooting

ProblemSolution
Agent using wrong settingsCheck gt doctor, verify .claude/settings.json in role parent dir
Settings not foundRun gt install to recreate settings, or gt doctor --fix
Source repo settings leakingRun gt doctor --fix to remove legacy sparse checkout
Mayor settings affecting polecatsMayor should run in mayor/, not town root

CLI Reference

Town Management

gt install [path]            # Create town
gt install --git # With git init
gt doctor # Health check
gt doctor --fix # Auto-repair

Configuration

# Agent management
gt config agent list [--json] # List all agents (built-in + custom)
gt config agent get <name> # Show agent configuration
gt config agent set <name> <cmd> # Create or update custom agent
gt config agent remove <name> # Remove custom agent (built-ins protected)

# Default agent
gt config default-agent [name] # Get or set town default agent

Built-in agents: claude, gemini, codex, cursor, auggie, amp

Custom agents: Define per-town via CLI or JSON:

gt config agent set claude-glm "claude-glm --model glm-4"
gt config agent set claude "claude-opus" # Override built-in
gt config default-agent claude-glm # Set default

Advanced agent config (settings/agents.json):

{
"version": 1,
"agents": {
"opencode": {
"command": "opencode",
"args": [],
"resume_flag": "--session",
"resume_style": "flag",
"non_interactive": {
"subcommand": "run",
"output_flag": "--format json"
}
}
}
}

Rig-level agents (<rig>/settings/config.json):

{
"type": "rig-settings",
"version": 1,
"agent": "opencode",
"agents": {
"opencode": {
"command": "opencode",
"args": ["--session"]
}
}
}

Agent resolution order: rig-level → town-level → built-in presets.

For OpenCode autonomous mode, set env var in your shell profile:

export OPENCODE_PERMISSION='{"*":"allow"}'

Rig Management

gt rig add <name> <url>
gt rig list
gt rig remove <name>

Convoy Management (Primary Dashboard)

gt convoy list                          # Dashboard of active convoys
gt convoy status [convoy-id] # Show progress (🚚 hq-cv-*)
gt convoy create "name" [issues...] # Create convoy tracking issues
gt convoy create "name" gt-a bd-b --notify mayor/ # With notification
gt convoy list --all # Include landed convoys
gt convoy list --status=closed # Only landed convoys

Note: "Swarm" is ephemeral (workers on a convoy's issues). See Convoys.

Work Assignment

# Standard workflow: convoy first, then sling
gt convoy create "Feature X" gt-abc gt-def
gt sling gt-abc <rig> # Assign to polecat
gt sling gt-abc <rig> --agent codex # Override runtime for this sling/spawn
gt sling <proto> --on gt-def <rig> # With workflow template

# Quick sling (auto-creates convoy)
gt sling <bead> <rig> # Auto-convoy for dashboard visibility

Agent overrides:

  • gt start --agent <alias> overrides the Mayor/Deacon runtime for this launch.
  • gt mayor start|attach|restart --agent <alias> and gt deacon start|attach|restart --agent <alias> do the same.
  • gt start crew <name> --agent <alias> and gt crew at <name> --agent <alias> override the crew worker runtime.

Communication

gt mail inbox
gt mail read <id>
gt mail send <addr> -s "Subject" -m "Body"
gt mail send --human -s "..." # To overseer

Escalation

gt escalate "topic"              # Default: MEDIUM severity
gt escalate -s CRITICAL "msg" # Urgent, immediate attention
gt escalate -s HIGH "msg" # Important blocker
gt escalate -s MEDIUM "msg" -m "Details..."

See escalation.md for full protocol.

Sessions

gt handoff                   # Request cycle (context-aware)
gt handoff --shutdown # Terminate (polecats)
gt session stop <rig>/<agent>
gt peek <agent> # Check health
gt nudge <agent> "message" # Send message to agent
gt seance # List discoverable predecessor sessions
gt seance --talk <id> # Talk to predecessor (full context)
gt seance --talk <id> -p "Where is X?" # One-shot question

Session Discovery: Each session has a startup nudge that becomes searchable in Claude's /resume picker:

[GAS TOWN] recipient <- sender • timestamp • topic[:mol-id]

Example: [GAS TOWN] gastown/crew/gus <- human • 2025-12-30T15:42 • restart

IMPORTANT: Always use gt nudge to send messages to Claude sessions. Never use raw tmux send-keys - it doesn't handle Claude's input correctly. gt nudge uses literal mode + debounce + separate Enter for reliable delivery.

Emergency

gt stop --all                # Kill all sessions
gt stop --rig <name> # Kill rig sessions

Health Check

gt deacon health-check <agent>   # Send health check ping, track response
gt deacon health-state # Show health check state for all agents

Merge Queue (MQ)

gt mq list [rig]             # Show the merge queue
gt mq next [rig] # Show highest-priority merge request
gt mq submit # Submit current branch to merge queue
gt mq status <id> # Show detailed merge request status
gt mq retry <id> # Retry a failed merge request
gt mq reject <id> # Reject a merge request

Integration Branch Commands

gt mq integration create <epic-id>              # Create integration branch
gt mq integration create <epic-id> --branch "feat/{title}" # Custom template
gt mq integration create <epic-id> --base-branch develop # Non-main base
gt mq integration status <epic-id> # Show branch status
gt mq integration status <epic-id> --json # JSON output
gt mq integration land <epic-id> # Merge to base branch (default: main)
gt mq integration land <epic-id> --dry-run # Preview only
gt mq integration land <epic-id> --force # Land with open MRs
gt mq integration land <epic-id> --skip-tests # Skip test run

See Integration Branches for the full workflow.

Beads Commands (bd)

bd ready                     # Work with no blockers
bd list --status=open
bd list --status=in_progress
bd show <id>
bd create --title="..." --type=task
bd update <id> --status=in_progress
bd close <id>
bd dep add <child> <parent> # child depends on parent

Patrol Agents

Deacon, Witness, and Refinery run continuous patrol loops using wisps:

AgentPatrol MoleculeResponsibility
Deaconmol-deacon-patrolAgent lifecycle, plugin execution, health checks
Witnessmol-witness-patrolMonitor polecats, nudge stuck workers
Refinerymol-refinery-patrolProcess merge queue, review MRs, check integration branches
1. gt patrol new               # Create root-only wisp
2. gt prime # Shows patrol checklist inline
3. Work through each step
4. gt patrol report --summary "..." # Close + start next cycle

Plugin Molecules

Plugins are molecules with specific labels:

{
"id": "mol-security-scan",
"labels": ["template", "plugin", "witness", "tier:haiku"]
}

Patrol molecules bond plugins dynamically:

bd mol bond mol-security-scan $PATROL_ID --var scope="$SCOPE"

Formula Invocation Patterns

CRITICAL: Different formula types require different invocation methods.

Workflow Formulas (sequential steps, single polecat)

Examples: shiny, shiny-enterprise, mol-polecat-work

gt sling <formula> --on <bead-id> <target>
gt sling shiny-enterprise --on gt-abc123 gastown

Convoy Formulas (parallel legs, multiple polecats)

Examples: code-review

DO NOT use gt sling for convoy formulas! It fails with "convoy type not supported".

# Correct invocation - use gt formula run:
gt formula run code-review --pr=123
gt formula run code-review --files="src/*.go"

# Dry run to preview:
gt formula run code-review --pr=123 --dry-run

Identifying Formula Type

gt formula show <name>   # Shows "Type: convoy" or "Type: workflow"
bd formula list # Lists formulas by type

Why This Matters

  • gt sling attempts to cook+pour the formula, which fails for convoy type
  • gt formula run handles convoy dispatch directly, spawning parallel polecats
  • Convoy formulas create multiple polecats (one per leg) + synthesis step

Common Issues

ProblemSolution
Agent in wrong directoryCheck cwd, gt doctor
Beads prefix mismatchCheck bd show vs rig config
Worktree conflictsCheck worktree state, gt doctor
Stuck workergt nudge, then gt peek
Dirty git stateCommit or discard, then gt handoff

For architecture details (bare repo pattern, beads as control plane, nondeterministic idempotence), see architecture.md.