docs/reference.md
Technical reference for Gas Town internals. Read the README first.
For directory structure details, see architecture.md.
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).
| Prefix | Routes To | Purpose |
|---|---|---|
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>
config.json){
"type": "rig",
"name": "myproject",
"git_url": "https://github.com/...",
"default_branch": "main",
"beads": { "prefix": "mp" }
}
Rig config fields:
| Field | Type | Default | Description |
|---|---|---|---|
default_branch | string | "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/config.json){
"theme": {
"disabled": false,
"name": "forest",
"custom": {
"bg": "#111111",
"fg": "#eeeeee"
},
"role_themes": {
"witness": "rust",
"refinery": "plum",
"crew": "none"
}
},
"merge_queue": {
"enabled": true,
"run_tests": true,
"setup_command": "",
"typecheck_command": "",
"lint_command": "",
"test_command": "",
"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
}
}
Theme fields:
| Field | Type | Default | Description |
|---|---|---|---|
disabled | bool | false | Disable tmux status/window theming for the rig |
name | string | auto-assigned by rig name | Use a named built-in palette theme |
custom.bg | string | unset | Custom tmux background color |
custom.fg | string | unset | Custom tmux foreground color |
role_themes | map[string]string | unset | Per-role overrides for witness, refinery, crew, polecat; use "none" to disable theming for a role |
Theme resolution:
theme config: auto-assign a built-in palette theme by rig namedisabled: true: skip both status-style and window-stylename: use that built-in themecustom: use exact {bg, fg} colorsrole_themes: override role-specific sessions within the rigTown-level role defaults live in mayor/config.json under:
{
"theme": {
"disabled": false,
"name": "forest",
"custom": {
"bg": "#111111",
"fg": "#eeeeee"
},
"role_defaults": {
"mayor": "forest",
"deacon": "plum",
"witness": "rust",
"crew": "none"
}
}
}
role_defaults supports mayor, deacon, witness, refinery, crew, and polecat.
Merge queue fields:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether the merge queue is active |
run_tests | bool | true | Run tests before merging |
setup_command | string | "" | Setup/install command (e.g., pnpm install) |
typecheck_command | string | "" | Type check command (e.g., tsc --noEmit) |
lint_command | string | "" | Lint command (e.g., eslint .) |
test_command | string | "" | Test command to run. Empty = skip. |
build_command | string | "" | Build command (e.g., go build ./...) |
on_conflict | string | "assign_back" | Conflict strategy: assign_back or auto_rebase |
delete_merged_branches | bool | true | Delete source branches after merging |
retry_flaky_tests | int | 1 | Number of times to retry flaky tests |
poll_interval | string | "30s" | How often Refinery polls for new MRs |
max_concurrent | int | 1 | Maximum concurrent merges |
integration_branch_polecat_enabled | *bool | true | Polecats auto-source worktrees from integration branches |
integration_branch_refinery_enabled | *bool | true | gt done / gt mq submit auto-target integration branches |
integration_branch_template | string | "integration/{title}" | Branch name template ({title}, {epic}, {prefix}, {user}) |
integration_branch_auto_land | *bool | false | Refinery patrol auto-lands when all children closed |
See Integration Branches for integration branch details.
.runtime/ - gitignored)Process state, PIDs, ephemeral data.
Rigs support layered configuration through:
.beads-wisp/config/) - transient, local overrides~/gt/settings/config.json)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:
| Variable | Description | Example |
|---|---|---|
{user} | From git config user.name | adam |
{year} | Current year (YY format) | 26 |
{month} | Current month (MM format) | 01 |
{name} | Polecat name | alpha |
{issue} | Issue ID without prefix | 123 (from gt-123) |
{description} | Sanitized issue title | fix-auth-bug |
{timestamp} | Unique timestamp | 1ks7f9a |
Default Behavior (backward compatible):
When polecat_branch_template is empty or not set:
polecat/{name}/{issue}@{timestamp}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 = "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"
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.
| Operation | bd (data) | gt (agent) |
|---|---|---|
| Cook/pour/wisp | bd cook, bd mol pour/wisp | — |
| Squash/burn | bd mol squash/burn <id> | gt mol squash/burn (attached) |
| Navigate | bd mol current, bd mol show | gt hook, gt mol current |
| Attach | — | gt mol attach/detach |
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
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
Gas Town sets environment variables for each agent session via config.AgentEnv().
These are set in tmux session environment when agents are spawned.
| Variable | Purpose | Example |
|---|---|---|
GT_ROLE | Agent role type | mayor, witness, polecat, crew |
GT_ROOT | Town root directory | /home/user/gt |
BD_ACTOR | Agent identity for attribution | gastown/polecats/toast |
GIT_AUTHOR_NAME | Commit attribution (same as BD_ACTOR) | gastown/polecats/toast |
BEADS_DIR | Beads database location | /home/user/gt/gastown/.beads |
| Variable | Purpose | Roles |
|---|---|---|
GT_RIG | Rig name | witness, refinery, polecat, crew |
GT_POLECAT | Polecat worker name | polecat only |
GT_CREW | Crew worker name | crew only |
BEADS_AGENT_NAME | Agent name for beads operations | polecat, crew |
| Variable | Purpose |
|---|---|
GIT_AUTHOR_EMAIL | Workspace owner email (from git config) |
GT_TOWN_ROOT | Override town root detection (manual use) |
CLAUDE_RUNTIME_CONFIG_DIR | Custom Claude settings directory |
| Role | Key Variables |
|---|---|
| Mayor | GT_ROLE=mayor, BD_ACTOR=mayor |
| Deacon | GT_ROLE=deacon, BD_ACTOR=deacon |
| Boot | GT_ROLE=deacon/boot, BD_ACTOR=deacon-boot |
| Witness | GT_ROLE=witness, GT_RIG=<rig>, BD_ACTOR=<rig>/witness |
| Refinery | GT_ROLE=refinery, GT_RIG=<rig>, BD_ACTOR=<rig>/refinery |
| Polecat | GT_ROLE=polecat, GT_RIG=<rig>, GT_POLECAT=<name>, BD_ACTOR=<rig>/polecats/<name> |
| Crew | GT_ROLE=crew, GT_RIG=<rig>, GT_CREW=<name>, BD_ACTOR=<rig>/crew/<name> |
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
Each agent runs in a specific working directory and has its own Claude settings. Understanding this hierarchy is essential for proper configuration.
| Role | Working Directory | Notes |
|---|---|---|
| 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 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.
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?
~/gt/ find the town-root filegt prime, making on-disk bootstrap pointers redundantGas 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 cleanlygt prime injects role context ephemerally via SessionStart hook, which is
additive with the customer's CLAUDE.md — both are loaded.claude/ files are fully preservedDoctor 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.
Claude Code's settings are layered from multiple sources:
.claude/settings.json in current working directory (customer project).claude/settings.json in parent directories (traversing up)~/.claude/settings.json (user global settings)--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.
Gas Town uses two settings templates based on role type:
| Type | Roles | Key Difference |
|---|---|---|
| Interactive | Mayor, Crew | Mail injected on UserPromptSubmit hook |
| Autonomous | Polecat, Witness, Refinery, Deacon | Mail 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.
| Problem | Solution |
|---|---|
| Agent using wrong settings | Check gt doctor, verify .claude/settings.json in role parent dir |
| Settings not found | Run gt install to recreate settings, or gt doctor --fix |
| Source repo settings leaking | Run gt doctor --fix to remove legacy sparse checkout |
| Mayor settings affecting polecats | Mayor should run in mayor/, not town root |
gt install [path] # Create town
gt install --git # With git init
gt doctor # Health check
gt doctor --fix # Auto-repair
# 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, opencode, copilot
Note on GitHub Copilot: The
copilotpreset uses executable lifecycle hooks in.github/hooks/gastown.json(sessionStart,userPromptSubmitted,preToolUse,sessionEnd) — the same lifecycle events as Claude Code, in Copilot's JSON format. Copilot uses a 5-second ready delay instead of prompt-based detection. Requires a Copilot seat and org-level CLI policy enabled.
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"]
}
}
}
ACP-enabled custom agents (settings/config.json):
{
"type": "town-settings",
"version": 1,
"default_agent": "opencode-acp-debug",
"agents": {
"opencode-acp-debug": {
"command": "opencode",
"acp": {
"command": "acp",
"args": ["--debug", "--print-logs"]
}
}
}
}
The acp field configures Agent Communication Protocol support:
command: ACP subcommand (e.g., "acp" for opencode acp)args: Additional arguments passed to the ACP subcommandCustom agents inherit ACP support from their base command's preset. For example,
a custom agent with "command": "opencode" automatically inherits ACP support
from the opencode preset. You can override or extend the ACP args by specifying
the acp field explicitly.
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"}'
gt rig add <name> <url>
gt rig list
gt rig remove <name>
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.
# 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.gt mail inbox
gt mail read <id>
gt mail send <addr> -s "Subject" -m "Body"
gt mail send --human -s "..." # To overseer
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.
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.
gt stop --all # Kill all sessions
gt stop --rig <name> # Kill rig sessions
gt deacon health-check <agent> # Send health check ping, track response
gt deacon health-state # Show health check state for all agents
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
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.
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
Deacon, Witness, and Refinery run continuous patrol loops using wisps:
| Agent | Patrol Molecule | Responsibility |
|---|---|---|
| Deacon | mol-deacon-patrol | Agent lifecycle, plugin execution, health checks |
| Witness | mol-witness-patrol | Monitor polecats, nudge stuck workers |
| Refinery | mol-refinery-patrol | Process 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
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"
CRITICAL: Different formula types require different invocation methods.
Examples: shiny, shiny-enterprise, mol-polecat-work
gt sling <formula> --on <bead-id> <target>
gt sling shiny-enterprise --on gt-abc123 gastown
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
gt formula show <name> # Shows "Type: convoy" or "Type: workflow"
bd formula list # Lists formulas by type
gt sling attempts to cook+pour the formula, which fails for convoy typegt formula run handles convoy dispatch directly, spawning parallel polecats| Problem | Solution |
|---|---|
| Agent in wrong directory | Check cwd, gt doctor |
| Beads prefix mismatch | Check bd show vs rig config |
| Worktree conflicts | Check worktree state, gt doctor |
| Stuck worker | gt nudge, then gt peek |
| Dirty git state | Commit or discard, then gt handoff |
For architecture details (bare repo pattern, beads as control plane, nondeterministic idempotence), see architecture.md.