Design: Failure modes and recovery

Document failure modes and recovery strategies for Gas Town operations.

## Critical Failure Modes

### 1. Agent Crash Mid-Operation

**Scenario**: Polecat crashes while committing, Witness crashes while verifying

**Detection**:
- Session suddenly gone (tmux check fails)
- State shows 'working' but no session
- Heartbeat stops (for Witness)

**Recovery**:
- Doctor detects via ZombieSessionCheck
- Capture any recoverable state
- Reset agent state to 'idle'
- For Witness: auto-restart via supervisor or manual gt witness start

### 2. Git State Corruption

**Scenario**: Merge conflict, failed rebase, detached HEAD

**Detection**:
- Git commands fail
- Dirty state that won't commit
- Branch diverged from origin

**Recovery**:
- gt doctor reports git health issues
- Manual intervention recommended
- Severe cases: remove clone, re-clone

### 3. Beads Sync Conflict

**Scenario**: Two polecats modify same issue

**Detection**:
- bd sync fails with conflict
- Beads tombstone mechanism handles most cases

**Recovery**:
- Beads has last-write-wins semantics
- bd sync --force in extreme cases
- Issues may need manual dedup

### 4. Tmux Failure

**Scenario**: Tmux server crashes, socket issues

**Detection**:
- All sessions inaccessible
- "no server running" errors

**Recovery**:
- Kill any orphan processes
- tmux kill-server && tmux start-server
- All agent states reset to idle
- Re-spawn active work

### 5. Claude API Issues

**Scenario**: Rate limits, outages, context limits

**Detection**:
- Sessions hang or produce errors
- Repeated failure patterns

**Recovery**:
- Exponential backoff (handled by Claude Code)
- For context limits: session cycling (mail-to-self)
- For outages: wait and retry

### 6. Disk Full

**Scenario**: Clones, logs, or beads fill disk

**Detection**:
- Write operations fail
- git/bd commands error

**Recovery**:
- Clean up logs: rm ~/.gastown/logs/*
- Remove old polecat clones
- gt doctor --fix can clean some cruft

### 7. Network Failure

**Scenario**: Can't reach GitHub, API servers

**Detection**:
- git fetch/push fails
- Claude sessions hang

**Recovery**:
- Work continues locally
- Queue pushes for later
- Sync when connectivity restored

## Recovery Principles

1. **Fail safe**: Prefer stopping over corrupting
2. **State is recoverable**: Git and beads have recovery mechanisms
3. **Doctor heals**: gt doctor --fix handles common issues
4. **Emergency stop**: gt stop --all as last resort
5. **Human escalation**: Some failures need Overseer intervention

## Implementation

- Document each failure mode in architecture.md
- Ensure doctor checks cover detection
- Add recovery hints to error messages
- Log all failures for debugging


Resource Beads: Leases, locks, and quotas

Resource beads represent reserved resources. Types: vm, lock, slot, quota. Fields: holder, expires, renewable. Daemon monitors for expiry and manages contention.


Merge: gt-72so

branch: polecat/Doof
target: main
source_issue: gt-72so
rig: gastown


Test message

Testing GGT mail integration


Batch work landing report generation

Generate reports on batch work completion.

## Trigger

When all children of an epic are closed, generate a completion report.

## Report Contents

- Epic metadata (ID, title, created by, duration)
- Task summary (completed counts, timing)
- Per-task details (issue, assignee, time taken)
- Git stats (commits merged, lines changed)
- Cleanup stats (branches deleted)

## Output Locations

1. Mail to Mayor - summary report
2. Optional: save to file with --save flag

## Command

```bash
gt report --epic <epic-id> [--save report.md]
```

## Format

Markdown with sections:
- Summary
- Tasks
- Workers
- Timeline

## Note

This replaces the swarm-based reporting (gt-662). Reports are generated from epic/issue data, not from separate swarm state files.

## Reference

PGT: swarm/report.py (for format ideas, ignore swarm ID patterns)


bd mail send: pinned column missing from schema

gt mail send fails with: sqlite3: SQL logic error: table issues has no column named pinned

Likely a schema migration issue - bd schema has 'pinned' field but SQLite table doesn't.

## Reproduction
gt mail send gastown/Immortan -s 'test' -m 'test'

## Error
table issues has no column named pinned

## Fix
Either:
1. Add migration to add pinned column
2. Remove pinned from insert if not present
3. Regenerate DB from JSONL


Step recovery: timeout and release

Implement recovery mechanism for stuck molecule steps.

## Problem
When a worker dies mid-step, the step stays in_progress forever.
Need timeout/release mechanism for nondeterministic idempotence.

## Solution
1. Track step start time (claimed_at timestamp)
2. Timeout: After 30 min in_progress, step returns to pending
3. Manual release: bd release <step-id>

## Implementation Options

### Option A: Beads-level timeout
- Add claimed_at field to issues
- bd ready excludes items in_progress < 30 min
- bd ready includes items in_progress > 30 min (auto-recovery)

### Option B: Daemon-level timeout
- Daemon watches in_progress items
- Moves back to pending after timeout

### Option C: Manual only (MVP)
- bd release <id> manually moves in_progress → pending
- Document recovery procedure
- Witness can automate for polecats

## Recommendation
Start with Option C (manual) for Christmas. Add Option A later.

## Acceptance
- [ ] bd release command works
- [ ] Stuck steps can be recovered
- [ ] Documented recovery procedure


SSHOutpost: Full Gas Town clone on remote VM

## Overview

Implement SSHOutpost for running workers on remote VMs via SSH.

## Model

Remote VM has a full Gas Town clone. SSHOutpost:
- Connects via SSH
- Spawns tmux sessions on remote
- Uses remote git clone for code/beads
- Syncs via git push/pull

## Implementation

```go
type SSHOutpost struct {
    name       string
    host       string
    user       string
    keyPath    string
    townPath   string  // e.g., /home/steve/ai
    maxWorkers int
    conn       *ssh.Client
}

func NewSSHOutpost(config OutpostConfig) (*SSHOutpost, error)

func (o *SSHOutpost) Spawn(issue string, cfg WorkerConfig) (Worker, error) {
    // SSH: create tmux session on remote
    // SSH: start claude in session
    // Return SSHWorker
}

func (o *SSHOutpost) Ping() error {
    // Test SSH connectivity
}
```

## SSHWorker

```go
type SSHWorker struct {
    outpost *SSHOutpost
    id      string
    session string
    issue   string
}

func (w *SSHWorker) Attach() error {
    // SSH + tmux attach (opens terminal)
}

func (w *SSHWorker) Logs() (io.Reader, error) {
    // SSH: tmux capture-pane
}
```

## Prerequisites on Remote VM

1. Gas Town clone at townPath
2. Claude Code installed
3. Git credentials configured
4. SSH key access

## Integration with gt-f9x.7-8

This builds on the Connection interface from gt-f9x.7/8. SSHOutpost uses SSHConnection internally for remote operations.

Depends on: gt-9a2.1 (interfaces), gt-f9x.7 (Connection interface)


Swarm learning: Spawn should auto-notify polecats

town spawn assigns issues but doesn't notify polecats. Required separate 'town session send' to inject prompts. This should be one atomic operation - spawn assigns AND pokes the polecat to start working.


Built-in molecules: engineer-in-box, quick-fix, research

Create built-in molecules as Beads issues:

## engineer-in-box

```markdown
id: mol-engineer-in-box
type: molecule
title: Engineer in a Box

Full workflow from design to merge.

## Step: design
Think carefully about architecture. Consider:
- Existing patterns in the codebase
- Trade-offs between approaches
- Testability and maintainability

Write a brief design summary before proceeding.

## Step: implement
Write the code. Follow codebase conventions.
Needs: design

## Step: review
Self-review the changes. Look for:
- Bugs and edge cases
- Style issues
- Missing error handling
Needs: implement

## Step: test
Write and run tests. Cover happy path and edge cases.
Fix any failures before proceeding.
Needs: implement

## Step: submit
Submit for merge via refinery.
Needs: review, test
```

## quick-fix

```markdown
id: mol-quick-fix
type: molecule
title: Quick Fix

Fast path for small changes.

## Step: implement
Make the fix. Keep it focused.

## Step: test
Run relevant tests. Fix any regressions.
Needs: implement

## Step: submit
Submit for merge.
Needs: test
```

## research

```markdown
id: mol-research
type: molecule
title: Research

Investigation workflow.

## Step: investigate
Explore the question. Search code, read docs,
understand context. Take notes.

## Step: document
Write up findings. Include:
- What you learned
- Recommendations
- Open questions
Needs: investigate
```

## Storage

Built-in molecules live in `<town>/.beads/` as regular issues.
Created during `gt install` or `bd init`.


CLI: rig commands (list, show, add)

Rig management CLI commands.

## Commands

### gt rig list
List all registered rigs.
```
gt rig list [--json]
```
Output:
- Rig name
- Git URL
- Polecat count
- Witness/Refinery status

### gt rig show
Show details for a specific rig.
```
gt rig show <name> [--json]
```
Output:
- Full rig info
- Polecats with status
- Recent activity
- Beads summary (open issues)

### gt rig add
Add a new rig to the town.
```
gt rig add <name> <git-url>
gt rig add <name> --local <path>
```
Steps:
- Clone repo to town root (or link local)
- Register in rigs.json
- Create agent directories
- Configure git exclude

### gt rig remove
Remove a rig from the town.
```
gt rig remove <name> [--force]
```
- Warns if active polecats
- --force required if uncommitted changes

## Implementation

```go
var rigCmd = &cobra.Command{Use: "rig"}
rigCmd.AddCommand(rigListCmd, rigShowCmd, rigAddCmd, rigRemoveCmd)
```


gt swarm start: Does not spawn polecat sessions

gt swarm start marks swarm as 'active' but doesn't start any polecat sessions.

Repro:
1. gt swarm create gastown --epic gt-hw6 --worker Toast --worker Nux
2. gt swarm start gt-hw6
3. gt session list - shows no new sessions

Expected: Polecat sessions should start for each worker.
Actual: No sessions started, workers sit idle.


Test message

Testing GGT mail via beads


Molecule CLI: bd molecule commands

Add molecule commands to bd:

## Commands

```bash
bd molecule list              # List molecules (type: molecule)
bd molecule show <id>         # Show molecule with parsed steps
bd molecule parse <id>        # Validate and show parsed structure
bd molecule instantiate <mol-id> --parent=<issue-id>  # Create steps
bd molecule instances <mol-id>  # Show all instantiations
```

## gt spawn integration

```bash
gt spawn --issue <id> --molecule <mol-id>
```

This should:
1. Call `bd molecule instantiate` (creates child beads atomically)
2. Spawn polecat on first ready step
3. Polecat grinds through via `bd ready`

## Output Examples

```
$ bd molecule show mol-abc

mol-abc: Engineer in a Box
Type: molecule

Steps (5):
  design      → (ready first)
  implement   → Needs: design
  review      → Needs: implement
  test        → Needs: implement
  submit      → Needs: review, test

Instances: 3
```

```
$ bd molecule instances mol-abc

Parent          Status    Created
gt-xyz          done      2025-12-15
gt-abc          active    2025-12-17 (3/5 complete)
gt-def          pending   2025-12-18
```


Polecat CLAUDE.md: configure auto-approve for bd and gt commands

Polecats get stuck waiting for bash command approval when running
bd and gt commands. Need to configure Claude Code to auto-approve these.

Options:
1. Add allowedTools to polecat CLAUDE.md
2. Configure .claude/settings.json in polecat directory
3. Use --dangerously-skip-permissions flag (not recommended)

Should auto-approve:
- bd (beads commands)
- gt (gastown commands)
- go build/test
- git status/add/commit/push

Should still require approval:
- rm -rf
- Arbitrary commands outside project

Related to polecat prompting (gt-e1y, gt-sd6).


Create DEACON.md role context

Create ~/gt/DEACON.md with the Deacon's role context, wake cycle instructions, and command reference. Model after existing CLAUDE.md for Mayor.


test-after-fix

test


gt mq submit: create MR from current branch

Implement 'gt mq submit' command that creates a merge-request bead.

Auto-detection logic:
1. Branch: current git branch
2. Issue: parse from branch name (polecat/Nux/gt-xyz → gt-xyz)
3. Target: main (or integration branch if --epic specified)
4. Worker: parse from branch name
5. Rig: current rig

Options:
- --branch BRANCH: explicit source branch
- --issue ISSUE: explicit source issue
- --epic EPIC: target integration/EPIC instead of main
- --priority P: override priority (default: inherit from source issue)

Creates merge-request bead and prints MR ID.

Reference: docs/merge-queue-design.md#creating-merge-requests


Daemon: integration test with real lifecycle

Need an end-to-end test that:
1. Starts daemon
2. Starts a test agent session
3. Sends lifecycle request to daemon
4. Verifies session was killed and restarted
5. Cleans up

Could use a mock 'agent' that's just a shell script.


Merge: gt-99a

branch: polecat/Slit
target: main
source_issue: gt-99a
rig: gastown


Update architecture.md: Engineer role and Beads merge queue

Update docs/architecture.md with recent design decisions:

1. Agent table: Change "Refinery" role to "Engineer"
   - Refinery = place/module/directory
   - Engineer = role (agent that works in the Refinery)

2. Merge Queue section: Document Beads-native model
   - MRs are beads issues with --type=merge-request
   - gt mq commands (submit, list, next, process, reorder)
   - Ordering via depends-on links

3. CLI section: Add gt mq commands

4. Key Design Decisions: Add decisions for:
   - #15: Merge Queue in Beads
   - #16: Engineer role (distinct from Refinery place)
   - #17: Session restart protocol for Engineer


Session cycling UX: smooth transitions via TUI wrapper

## Problem

Current CLI agent session cycling is painful:
- Shell → CC starts → priming → context loads → ready → work → exit/crash → repeat
- Each cycle is 30-60 seconds of cold boot
- No continuity between shell and agent's inner state
- Raw "session not running, starting..." loop is the baseline

## GGT Advantages (already have)

- Beads: Work state survives session death completely
- Mail: Handoff notes from past-self to future-self
- Prime commands: Structured context reload

## Gap: Transition Mechanics

Ideas to explore when actively using CLI:

1. **In-band cycling** - `/restart` or `/cycle` command, agent handles own restart without dropping to shell

2. **Hot standby** - TUI maintains pre-warmed session in background, switch to already-primed agent

3. **Persistent wrapper** - Bubbletea TUI stays running across session cycles, CC sessions come/go inside it

4. **Session pooling** - Keep 2-3 primed sessions ready, never wait for cold start

## Deferred

Deliberately P4 until we're actively using the simpler CLI and feel the pain firsthand.


Merge: gt-y5o

branch: polecat/Doof
target: main
source_issue: gt-y5o
rig: gastown


SwarmManager: create, start, update, query operations

SwarmManager operations for creating and managing swarms.

## Interface

```go
type SwarmManager struct {
    rig    *Rig
    beads  *BeadsWrapper  // shells out to bd CLI
}

func NewSwarmManager(rig *Rig) *SwarmManager

// Lifecycle
func (m *SwarmManager) Create(epicID string, workers []string) (*Swarm, error)
func (m *SwarmManager) Start(swarmID string) error
func (m *SwarmManager) UpdateState(swarmID string, state SwarmState) error
func (m *SwarmManager) Cancel(swarmID string, reason string) error

// Queries (delegate to beads)
func (m *SwarmManager) GetSwarm(id string) (*Swarm, error)
func (m *SwarmManager) GetReadyTasks(swarmID string) ([]SwarmTask, error)
func (m *SwarmManager) GetActiveTasks(swarmID string) ([]SwarmTask, error)
func (m *SwarmManager) IsComplete(swarmID string) (bool, error)
```

## Implementation Notes

- `GetReadyTasks` wraps `bd ready --parent <epicID>`
- `IsComplete` checks if all child issues are closed
- State transitions update the epic's description or a tag field
- No separate manifest files - beads IS the source of truth


Swarm learning: Witness needs automated context cycling

Furiosa hit 2% context during swarm work. Witness role needs automated detection of low context and should trigger session cycling before agents get stuck. Add to Witness responsibilities in prompts.md.


Mail orchestrator daemon

Background mail orchestrator daemon.

## Command
```
gt mail orchestrate [--interval N] [--once] [--verbose]
```

## Purpose
Background process that:
1. Monitors outbox for pending mail
2. Delivers to recipient inboxes
3. Handles offline recipients (retry later)
4. Cleans delivered messages from outbox

## Why Needed?
Current mail is synchronous. If recipient is offline or mailbox locked, send fails.
Orchestrator enables async delivery with retry.

## Implementation
```go
func (o *Orchestrator) Run(interval time.Duration) error {
    ticker := time.NewTicker(interval)
    for range ticker.C {
        o.processOutbox()
    }
}

func (o *Orchestrator) processOutbox() {
    // List outbox/*.json
    // For each, attempt delivery
    // On success, delete from outbox
    // On failure, increment retry count
}
```

## Outbox Structure
```
<town>/mayor/mail/outbox/
├── msg-abc123.json
└── msg-def456.json
```

## Lower Priority
Current synchronous delivery works. Orchestrator is optimization.

## Acceptance Criteria
- [ ] Background daemon mode
- [ ] Retry failed deliveries
- [ ] --once for single pass
- [ ] Configurable interval


Implement gt harness create command

Add scaffolding command to create a new harness:
- gt harness create [path]
- Creates config/, mayor/, .beads/redirect (optional)
- Optionally initializes git
- Generates CLAUDE.md with Mayor role
- Could also offer a template repo alternative


Document how to configure a Gas Town Harness

Create docs explaining what a harness is (private repo containing GT installation with rigs gitignored), why you'd want one, and how to set it up with beads redirects


Town & Rig Management: install, doctor, federation

Reify the Gas Town installation as a first-class concept.

## Goals
- Installable: gt install [path] creates complete installation
- Diagnosable: gt doctor checks and fixes issues
- Federable: Clone town to VMs with central control

## Architecture Reference

See docs/architecture.md for full design, especially:
- Directory structure (Town Level / Rig Level sections)
- Configuration (town.json, rigs.json schemas)
- Key design decisions (visible config dir, decentralized agents)


gt polecat remove --all: bulk polecat teardown

Currently gt polecat remove only accepts one polecat at a time. Need bulk operations:

## Requested
- `gt polecat remove gastown --all` - remove all polecats from a rig
- `gt polecat remove gastown/A gastown/B ...` - remove multiple by name

## Context
After a swarm completes, tearing down 20 polecats one at a time is tedious.
Ephemeral workers should be easy to create and destroy in bulk.

## Related
- gt-c92: CLI: all command for batch polecat operations


Outpost error handling: Retry logic and resilience

## Overview

Error handling and retry logic for outpost operations.

## Error Types

```go
var (
    ErrOutpostUnreachable = errors.New("outpost unreachable")
    ErrWorkerSpawnFailed  = errors.New("worker spawn failed")
    ErrWorkerTimeout      = errors.New("worker timed out")
    ErrCostCapExceeded    = errors.New("cost cap exceeded")
    ErrAllOutpostsBusy    = errors.New("all outposts at capacity")
)
```

## Retry Policy

```go
type RetryPolicy struct {
    MaxAttempts     int
    InitialBackoff  time.Duration
    MaxBackoff      time.Duration
    BackoffFactor   float64
    RetryableErrors []error
}

func DefaultRetryPolicy() *RetryPolicy {
    return &RetryPolicy{
        MaxAttempts:    3,
        InitialBackoff: 1 * time.Second,
        MaxBackoff:     30 * time.Second,
        BackoffFactor:  2.0,
        RetryableErrors: []error{
            ErrOutpostUnreachable,
            ErrWorkerSpawnFailed,
        },
    }
}
```

## Retry Implementation

```go
func (m *OutpostManager) SpawnWithRetry(issue string, cfg WorkerConfig) (Worker, error) {
    var lastErr error
    backoff := m.retryPolicy.InitialBackoff

    for attempt := 0; attempt < m.retryPolicy.MaxAttempts; attempt++ {
        outpost := m.policy.SelectOutpost(issue, m.outposts)
        if outpost == nil {
            return nil, ErrAllOutpostsBusy
        }

        worker, err := outpost.Spawn(issue, cfg)
        if err == nil {
            return worker, nil
        }

        if !m.isRetryable(err) {
            return nil, err
        }

        lastErr = err
        time.Sleep(backoff)
        backoff = min(backoff * m.retryPolicy.BackoffFactor, m.retryPolicy.MaxBackoff)
    }

    return nil, fmt.Errorf("spawn failed after %d attempts: %w",
        m.retryPolicy.MaxAttempts, lastErr)
}
```

## Outpost Health Tracking

```go
type OutpostHealth struct {
    LastPing      time.Time
    LastError     error
    FailureCount  int
    CircuitBreaker bool  // If true, skip this outpost
}

func (m *OutpostManager) updateHealth(name string, err error) {
    // Track failures, open circuit breaker after N failures
    // Auto-reset after successful ping
}
```

## Config

```yaml
policy:
  retry:
    max_attempts: 3
    initial_backoff: 1s
    max_backoff: 30s

  circuit_breaker:
    failure_threshold: 5
    reset_timeout: 1m
```

## Files

- `internal/outpost/retry.go`
- `internal/outpost/health.go`

## Dependencies

Depends on: gt-9a2.3 (OutpostManager)
Can be done after basic outpost implementations work.


Mail system: message types and threading

GGT mail system needs message types and threading like PGT.

## 1. Message Types
Add to internal/mail/types.go:
```go
type MessageType string
const (
    TypeTask         MessageType = "task"         // Required processing
    TypeScavenge     MessageType = "scavenge"     // Optional first-come work
    TypeNotification MessageType = "notification" // Informational
    TypeReply        MessageType = "reply"        // Response to message
)

// Update Message struct
type Message struct {
    // existing fields...
    Type     MessageType `json:"type"`
    ThreadID string      `json:"thread_id,omitempty"`
    ReplyTo  string      `json:"reply_to,omitempty"`
}
```

## 2. Priority Levels
Expand from 2 to 4:
```go
type Priority string
const (
    PriorityLow    Priority = "low"
    PriorityNormal Priority = "normal"
    PriorityHigh   Priority = "high"
    PriorityUrgent Priority = "urgent"
)
```

## 3. CLI Updates
internal/cmd/mail.go:
- Add --type flag to send: `gt mail send ... --type task`
- Add --reply-to flag: `gt mail send ... --reply-to <msg-id>`
- Add thread command: `gt mail thread <thread-id>`

## 4. Threading Logic
NewMessage() should auto-generate thread_id if not a reply.
Reply messages inherit thread_id from original.

## Files to Modify
- internal/mail/types.go: Add types, expand Priority
- internal/mail/mailbox.go: Thread filtering
- internal/cmd/mail.go: CLI flags and thread command

## PGT Reference
gastown-py/src/gastown/mail/message.py

## Acceptance Criteria
- [ ] Messages have type field (default: notification)
- [ ] 4 priority levels supported
- [ ] Reply creates thread with shared thread_id
- [ ] gt mail thread <id> shows conversation


Haiku-based smart stuck detection

Use Haiku to analyze tmux state when signals are ambiguous.

## When to Invoke

Only as escalation tier:
1. Keepalive is stale (> 2 min)
2. Tmux shows claude is running (not idle shell)
3. Heuristics can't determine state

## Prompt

Capture last 50 lines of tmux pane, ask Haiku:
'Is this Claude agent: WORKING | STUCK | IDLE | WAITING_FOR_HUMAN?'

## Cost

~$0.001 per check. At 1 check/min worst case = $0.06/hour.
In practice, most checks avoided by keepalive signal.

## Configuration

```toml
[daemon]
smart_detection = true
smart_model = "haiku"
smart_threshold = "2m"  # only check if stale > 2min
```


gt mq retry: retry a failed MR

Implement 'gt mq retry <id>' to retry a failed merge request.

Actions:
1. Verify MR exists and is in failed state (open with failure labels)
2. Remove failure labels (needs-rebase, needs-fix)
3. Reset to ready state
4. Optionally re-run immediately (--now flag)

Options:
- --now: immediately process (instead of waiting for Engineer loop)

Reference: docs/merge-queue-design.md#cli-commands


Test MR

test


Swarm cleanup: delete merged polecat branches and reset state

After a swarm completes and branches are merged, leftover state remains:

## Current Problem

1. **Remote branches not deleted** - polecat/* branches stay on origin after merge
2. **Polecat clones not reset** - still on old branch with completed work
3. **No cleanup command** - manual cleanup required

## Observed After Swarm

Remote branches still present:
- origin/polecat/Morsov
- origin/polecat/Nux
- origin/polecat/Rictus
- origin/polecat/Slit
- origin/polecat/Toast

## Proposed Solution

Add cleanup commands:

1. gt swarm cleanup <swarm-id> - Clean up after swarm completion
   - Delete remote polecat branches that were merged
   - Reset polecat clones to main
   - Clear issue assignments

2. gt polecat reset <rig>/<polecat> - Reset single polecat
   - git checkout main && git pull
   - Delete local polecat branch
   - Clear current issue assignment

3. Auto-cleanup option on gt session stop --cleanup

## Manual Cleanup For Now

git push origin --delete polecat/Nux polecat/Toast ...
cd polecats/Nux && git checkout main && git pull


HTTP work server: gt worker serve command

## Overview

HTTP server that runs inside Cloud Run containers, accepting work requests.

## Command

```bash
gt worker serve --port 8080
```

## Implementation

```go
type WorkServer struct {
    port    int
    handler *WorkHandler
}

func (s *WorkServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/work":
        s.handleWork(w, r)
    case "/health":
        s.handleHealth(w, r)
    default:
        http.NotFound(w, r)
    }
}

func (s *WorkServer) handleWork(w http.ResponseWriter, r *http.Request) {
    // 1. Parse WorkRequest
    // 2. Clone/pull repo
    // 3. Start Claude on issue
    // 4. Stream WorkEvents as NDJSON
    // 5. Return WorkResult
}
```

## Streaming Response

```go
func (s *WorkServer) streamEvents(w http.ResponseWriter, events <-chan WorkEvent) {
    flusher, _ := w.(http.Flusher)
    encoder := json.NewEncoder(w)
    for event := range events {
        encoder.Encode(event)
        flusher.Flush()
    }
}
```

## Files

- `internal/cloudrun/server.go`
- `cmd/gt/worker_serve.go` - Cobra command

## Dependencies

Depends on: gt-9a2.7 (protocol types)


Daemon: configurable heartbeat interval

Heartbeat interval is hardcoded to 60s. Should be configurable via:
- town.json config
- Command line flag
- Environment variable

Default 60s is reasonable but some deployments may want faster/slower.


Test: release command verification


Merge: gt-5af.1

branch: polecat/Ace
target: main
source_issue: gt-5af.1
rig: gastown


Design: Swarm shutdown and worker cleanup

Design for graceful swarm shutdown, worker cleanup, and session cycling.

## Key Decisions

1. Pre-kill verification uses model intelligence (not framework rules)
2. Witness can request restart when context filling (mail self, exit)
3. Mayor NOT involved in per-worker cleanup (Witness responsibility)
4. Clear responsibility boundaries between Mayor/Witness/Polecat

## Subtasks (implementation)

- gt-sd6: Polecat decommission checklist prompting
- gt-f8v: Witness pre-kill verification protocol
- gt-eu9: Witness session cycling and handoff
- gt-gl2: Mayor vs Witness cleanup responsibilities

**Design complete.** Each subtask has full specification in its description.


Define Engineer as the Refinery agent role

Clarify the distinction:

- **Refinery** = place/module/directory/workspace
  - `<rig>/refinery/` directory structure
  - `gt refinery start/stop/status` commands
  - tmux session name: `refinery` or `<rig>-refinery`

- **Engineer** = role/agent who works in the Refinery
  - CLAUDE.md prompting: "You are an Engineer..."
  - Documentation: "The Engineer processes merge requests..."
  - Mail address: `<rig>/engineer` (or `<rig>/refinery`?)

Updates needed:
- Add Engineer role description to docs
- Update CLAUDE.md templates for refinery agents
- Keep `gt refinery` commands as-is (they manage the place)
- Internal code stays `internal/refinery/` (the module)

Future consideration: Multiple Engineers in one Refinery for parallel merge processing.


Connection interface: Protocol for local/remote ops

Abstract interface for local vs remote (SSH) operations.

## Concept

Enable Gas Town to manage rigs on remote machines via SSH. The Connection interface abstracts whether operations happen locally or remotely.

## Interface

```go
type Connection interface {
    // Identification
    Name() string
    IsLocal() bool

    // File operations
    ReadFile(path string) ([]byte, error)
    WriteFile(path string, data []byte, perm os.FileMode) error
    MkdirAll(path string, perm os.FileMode) error
    Remove(path string) error
    Stat(path string) (os.FileInfo, error)
    Glob(pattern string) ([]string, error)

    // Command execution
    Exec(cmd string, args ...string) ([]byte, error)
    ExecDir(dir, cmd string, args ...string) ([]byte, error)

    // Tmux (for session management)
    TmuxNewSession(name, dir string) error
    TmuxKillSession(name string) error
    TmuxSendKeys(session, keys string) error
    TmuxCapturePane(session string, lines int) (string, error)
}
```

## Implementations

- LocalConnection (gt-f9x.8): Direct file/exec operations
- SSHConnection (future): Operations via SSH

## Usage

```go
func NewRigManager(conn Connection, ...) *RigManager

// Operations work the same regardless of connection type
rm.DiscoverRigs()  // uses conn.Glob, conn.ReadFile
```

## Design Notes

- Connection obtained from MachineRegistry (gt-f9x.9)
- Default is always local
- SSH connection requires machine config (host, key, user)


Go scaffolding: cmd/gt, go.mod, Cobra setup

Set up Go project structure with CLI framework.

**Stack:**
- Cobra for command/flag handling
- Lipgloss for styled terminal output

**Deliverables:**
- cmd/gt/main.go with Cobra root command
- Basic subcommands: version, help
- Lipgloss styles for status output (success, warning, error)
- go.mod with dependencies


Keepalive signal from bd/gt commands

Every bd and gt command should touch a keepalive file to signal 'agent is alive/working'.

## Implementation

Touch `<workspace>/.gastown/keepalive.json`:
```json
{"last_command": "bd show gt-99m", "timestamp": "2025-12-18T13:45:00Z"}
```

## Usage by Daemon

- Fresh (< 2 min) → agent is working, skip heartbeat
- Stale (2-5 min) → might be thinking, gentle poke
- Very stale (> 5 min) → likely idle, safe to interrupt

## Benefits

- Zero cost (just file I/O)
- Works during long tool calls
- Doesn't require agent cooperation
- Foundation for smarter backoff strategies


Outpost assignment policy: Smart work routing

## Overview

Policy engine for deciding which outpost gets which work.

## Policy Configuration

```yaml
policy:
  # Default order of preference
  default_preference: [local, gce-burst, cloudrun-burst]

  # Rules applied in order
  rules:
    # Background work → Cloud Run (cheap)
    - condition: "priority >= P3"
      prefer: cloudrun-burst

    # Long tasks → VM (persistent)
    - condition: "estimated_duration > 30m"
      prefer: gce-burst

    # Specific epic → specific outpost
    - condition: "epic == gt-abc"
      prefer: local
```

## Implementation

```go
type AssignmentPolicy struct {
    DefaultPreference []string
    Rules             []PolicyRule
}

type PolicyRule struct {
    Condition string   // Simple expression
    Prefer    string   // Outpost name
    Require   string   // Must use this outpost
}

func (p *AssignmentPolicy) SelectOutpost(
    issue Issue,
    outposts map[string]Outpost,
) Outpost {
    // Check rules in order
    for _, rule := range p.Rules {
        if rule.Matches(issue) {
            if op, ok := outposts[rule.Prefer]; ok {
                if op.ActiveWorkers() < op.MaxWorkers() {
                    return op
                }
            }
        }
    }

    // Fall back to default preference
    for _, name := range p.DefaultPreference {
        if op, ok := outposts[name]; ok {
            if op.ActiveWorkers() < op.MaxWorkers() {
                return op
            }
        }
    }

    return nil  // All outposts at capacity
}
```

## Condition Language

Simple expressions, not a full DSL:

```
priority >= P3
priority == P0
estimated_duration > 30m
epic == gt-abc
type == bug
label contains "urgent"
```

## Files

- `internal/outpost/policy.go`
- `internal/outpost/condition.go`

Depends on: gt-9a2.3 (config)


Per-epic config overrides: custom merge settings

Allow per-epic merge configuration overrides.

Epic can specify merge_config in its description:
  merge_config:
    run_tests: true
    test_command: 'go test -race ./...'
    on_conflict: assign_back

When processing MRs for an epic:
1. Load rig-level merge_queue config
2. Check if epic has merge_config
3. Merge epic config over rig config
4. Use merged config for processing

Use cases:
- Risky refactors: more thorough testing
- Urgent fixes: skip some checks
- Experimental work: different test suite

Reference: docs/merge-queue-design.md#per-epic-overrides


Recreate beads rigs with fresh clones

## Problem

Beads rigs have schema mismatches (missing thread_id column, etc.) from development iteration.

## Tasks

1. Shut down any active polecats
2. Delete existing beads rigs: mayor/rig, refinery/rig, witness/rig, crew/*
3. Re-clone from beads repo
4. Run bd init in each new clone

## Rigs to recreate

- /Users/stevey/gt/beads/mayor/rig
- /Users/stevey/gt/beads/refinery/rig
- /Users/stevey/gt/beads/crew/* (if any)

## Source

Clone from beads repo (need to confirm remote URL)


Design: Work flow simulation and validation

Validate GGT designs through simulation before implementation.

## Validation Approaches

### 1. Dry-Run Simulation (Recommended First)
Mayor walks through scenarios mentally/on paper:
- "If polecat Toast signals done with dirty git state, what happens?"
- "If Witness context fills mid-verification, what state is lost?"
- "If two polecats try to close same issue, what happens?"

Create beads for any gaps discovered.

### 2. Real Work in gastown-py
Use Python Gas Town to stress-test assumptions:
- Run actual batch work on test repos
- Observe edge cases in practice
- Document issues found

### 3. Edge Case Analysis
Systematic review of failure modes:
- Agent crashes mid-operation
- Network failures during sync
- Concurrent access to shared state
- Context limits hit at bad times

## Key Scenarios to Validate

- [ ] Witness session cycling (state preservation)
- [ ] Polecat decommission with dirty state
- [ ] Merge conflicts in queue
- [ ] Beads sync conflicts between workers
- [ ] Escalation path (stuck worker -> Mayor)
- [ ] Cross-rig communication
- [ ] Federation mail routing (future)

## Success Criteria

- No data loss scenarios identified
- Clear recovery paths for all failure modes
- Edge cases either handled or documented as limitations
- Design improves as model cognition improves

## Output

For each scenario validated:
1. Document in relevant bead if issue found
2. Create new beads for missing functionality
3. Update architecture.md if design changes


gt spawn: add --polecat flag for explicit worker selection

Currently gt spawn requires positional arg format:
```
gt spawn gastown/Angharad --issue gt-xyz
```

But I tried the more intuitive flag form:
```
gt spawn --issue gt-xyz --polecat Angharad
```

This failed with 'unknown flag: --polecat'.

The flag form is more discoverable and consistent with other commands. Add --polecat flag as alternative to positional arg.


Work reconciliation module

Port reconcile.py work verification to Go.

## Purpose

Verify assigned issues are completed before landing.

## Interface

```go
type ReconcileResult struct {
    CompletedIssues  []IssueInfo
    IncompleteIssues []IssueInfo
    AllComplete      bool
    PolecatsChecked  []string
    Errors           []string
}

func ReconcileWork(rig string, polecats []string) *ReconcileResult
func GetAssignedIssues(assignee string) []IssueInfo
func UnassignIssue(issueID string, resetStatus bool) error
```

## Logic

1. Get list of polecats in swarm
2. For each polecat, query beads for assigned issues
3. Categorize as completed (closed) or incomplete (open/in_progress)
4. Return summary for landing decision

## Handling Incomplete Work

If issues incomplete:
- Abort landing (default)
- Or force landing (leaves work unfinished)
- Unassign incomplete issues for future work

## Reference

PGT: swarm/reconcile.py


Molecule composition: Includes directive

Add support for molecule composition via the Includes directive.

## Format
\`\`\`markdown
## Molecule: derived-name
Includes: mol-base-molecule

## Step: additional-step
Additional instructions here.
Needs: step-from-base
\`\`\`

## Implementation
1. Add \`Includes:\` parsing to ParseMoleculeSteps()
2. Resolve included molecule by ID
3. Merge steps from included molecule
4. Allow new steps to depend on included steps
5. Support multiple includes (polymers)

## Files to modify
- internal/beads/molecule.go
- internal/beads/molecule_test.go

## Acceptance
- [ ] Parse Includes directive
- [ ] Resolve and merge included steps
- [ ] Dependencies across molecules work
- [ ] Multiple includes supported
- [ ] Tests cover composition scenarios


gt molecule seed: create built-in molecules as beads

The molecule infrastructure is complete but built-in molecules (engineer-in-box, quick-fix, research) need to be seeded into the beads database.

## Current State
- `gt molecule list` works but shows 0 molecules
- BuiltinMolecules() in internal/beads/builtin_molecules.go has 3 molecules defined
- No way to create them as beads

## Needed
Add `gt molecule seed` command that:
1. Reads BuiltinMolecules()
2. Creates each as a bead with type: molecule
3. Uses well-known IDs (mol-engineer-in-box, mol-quick-fix, mol-research)
4. Idempotent (skip if already exists)

## Acceptance Criteria
```
gt molecule seed
gt molecule list   # Shows 3 built-in molecules
```

## Parent
gt-4nn: Molecules epic


Session pre-shutdown checks

Session stop should verify clean state before killing, like PGT.

## Pre-Shutdown Checks

### 1. Git Working Tree Clean
```go
func checkGitClean(clonePath string) error {
    // git status --porcelain
    // Fail if any output
}
```

### 2. All Commits Pushed
```go
func checkCommitsPushed(clonePath string) error {
    // git log origin/HEAD..HEAD
    // Fail if any unpushed commits
}
```

### 3. Assigned Issues Handled
```go
func checkIssuesHandled(polecat *Polecat) error {
    // If polecat.Issue != "", check if closed or reassigned
}
```

### 4. Beads Synced
```go
func checkBeadsSynced(clonePath string) error {
    // bd sync --status in clone directory
}
```

## Behavior on Failure
1. First attempt: Nudge worker to fix
2. Retry up to 3 times with delay
3. After retries: Escalate to Witness/Mayor

## Integration
Modify internal/session/manager.go Stop():
```go
func (m *Manager) Stop(polecat string, force bool) error {
    if !force {
        if err := m.runPreShutdownChecks(polecat); err != nil {
            return fmt.Errorf("pre-shutdown checks failed: %w", err)
        }
    }
    // existing stop logic
}
```

## Flags
- --force: Skip checks
- --grace-period N: Time to wait for fixes

## Dependencies
- Ties into gt-69l (hook system) - can be hook-based
- Ties into gt-f8v (Witness pre-kill verification)

## Acceptance Criteria
- [ ] Stop fails if uncommitted changes (without --force)
- [ ] Stop fails if unpushed commits
- [ ] Clear error messages with fix instructions
- [ ] --force bypasses all checks


Architecture: beads config and direct landing docs

Added to architecture.md:
- Beads multi-agent configuration table (daemon, worktree, sync-branch)
- ASCII directory layout for non-mermaid rendering
- Direct landing workflow (bypass merge queue)
- Design decisions 9 and 10 for direct landing and daemon awareness
- CLI commands for gt land


Witness daemon: heartbeat loop, spawn ephemeral agent

Background daemon for agent lifecycle and monitoring.

## Core Responsibilities

1. **Heartbeat monitoring**: Check agent health periodically
2. **Session lifecycle**: Restart agents after handoff (Witness Protection)
3. **Polecat monitoring**: Track worker progress, nudging
4. **Escalation**: Report failures to Mayor

## Session Lifecycle (Witness Protection)

The daemon enables autonomous long-running operation by cycling agent sessions:

```go
type SessionLifecycle struct {
    AgentType string  // "witness", "refinery"
    StatePath string  // path to state.json
}

func (d *Daemon) monitorSessionLifecycle(agent SessionLifecycle) {
    // 1. Detect session exit
    // 2. Check state.json for requesting_cycle: true
    // 3. If cycle requested: start new session, clear flag
    // 4. If unexpected exit: escalate to Mayor
}
```

See architecture.md Key Decision #12: Agent Session Lifecycle.

## Interface

```go
type Daemon struct {
    rig      *Rig
    polecats *PolecatManager
    sessions *SessionManager
    config   DaemonConfig
}

type DaemonConfig struct {
    HeartbeatInterval time.Duration  // default: 30s
    NudgeThreshold    int            // nudges before escalation
    MaxWorkers        int            // from rig config
}

func NewDaemon(rig *Rig) *Daemon
func (d *Daemon) Start() error
func (d *Daemon) Stop() error

// Lifecycle
func (d *Daemon) CycleSession(agentType string) error
func (d *Daemon) SpawnWorker(issueID string) error
func (d *Daemon) ShutdownWorker(polecat string) error
```

## Heartbeat Loop

Every HeartbeatInterval:
1. Check Witness session (restart if cycle requested)
2. Check Refinery session (restart if cycle requested)
3. For each active polecat: assess progress, nudge if stuck
4. Query `bd ready` for new work, spawn up to max_workers


HOP: Entity chain tracking for agents

Track work history per-entity (CV chains). See ~/ai/stevey-gastown/hop/decisions/002-entity-chains.md for design. Post-v0.1 work.


GGT Command Parity: Complete gt command coverage

Complete gt command set to match/exceed PGT town commands.

Covers: uninstall, rig info, refinery attach, witness, session mgmt, mail UX, daemon.


Merge: gt-5af.2

branch: polecat/Doof
target: main
source_issue: gt-5af.2
rig: gastown


Deacon: Hierarchical health-check orchestrator

Replace daemon heartbeats with Deacon - an AI agent that keeps Gas Town running.

## Core Concept

The Deacon is a **Claude agent** (not just a Go process) that:
- Lives at town root in gt-deacon session
- Has its own mailbox (deacon/ identity in town beads)
- Is poked by a minimal Go daemon every ~60s (if idle)
- Monitors Mayor and Witnesses proactively
- Handles lifecycle requests (restart/cycle) from Mayor, Witnesses, AND Crew

## Architecture

```
Minimal Go Daemon (just watches Deacon)
         |
         v
     Deacon (Claude agent)
         |
    +----+----+
    v         v
  Mayor    Witnesses --> Polecats (Witness-managed)
    |         |
    +----+----+
         |
      Crew (lifecycle only, not monitored)
```

## Deacon Responsibilities

**Proactive monitoring:**
- Mayor health (tmux session, keepalive freshness)
- Witness health (tmux sessions, keepalive freshness)

**Reactive lifecycle:**
- Process restart/cycle requests from Mayor, Witnesses, Crew
- Kill session, create new, prime, verify startup

**Escalation:**
- Mail human (configurable) if issues can't be resolved

## Key Files

- ~/gt/DEACON.md - Role context
- ~/gt/deacon/ - State directory
  - heartbeat.json - Written each wake cycle (daemon checks this)
  - state.json - Health tracking, last scan results
- ~/gt/.beads/ - Town beads with deacon/ mail identity

## Wake Cycle

1. Write heartbeat (prevents daemon from poking)
2. Check mail (lifecycle requests)
3. Quick health scan (Mayor, Witnesses)
4. Process lifecycle requests
5. Remediate unhealthy agents
6. Update state

## Session Patterns

- Deacon: gt-deacon
- Mayor: gt-mayor
- Witness: gt-<rig>-witness
- Crew: gt-<rig>-<name> (e.g., gt-gastown-max)

## Relation to Policy Beads

Future: Deacon behavior configurable via policy beads (gt-3zw).
Initial implementation uses sensible defaults.


Split PGT/GGT harness or migrate to GGT-only

Current ~/ai harness is shared by PGT and GGT with confusing overlap. Options:
1. Keep separate (document the coexistence)
2. Migrate fully to GGT structure
3. Create separate harnesses

This affects the beads redirect, Mayor home location, and rig structure.


Refinery-Witness communication protocol

Define mail protocol between Refinery and Witness:

FROM Witness → Refinery:
- 'Polecat ready': polecat X completed work, ready for merge
- 'Rework complete': polecat Y finished requested rework

FROM Refinery → Witness:
- 'Merge success': polecat X merged, can be cleaned up
- 'Merge failed': polecat X needs rework on <reason>
- 'Rework request': please have a polecat rebase X on current main

Implement as structured mail with parseable format.


Update Refinery to use Beads merge queue

Replace branch discovery with Beads queue in the Refinery module:

Current (internal/refinery/manager.go):
- Scans for polecat/* branches
- Creates MR objects on-the-fly

New:
- Pull from Beads: bd ready --type=merge-request
- Process each MR
- Close with merge commit: bd close <id> --reason="Merged at <sha>"
- Handle failures: bd update <id> --status=blocked --reason="..."

The Engineer (agent) becomes Beads-native.
The Refinery (module) provides the infrastructure.


Enhanced polecat decommission prompting

Add decommission checklist to polecat AGENTS.md.template. Make crystal clear: verify ALL before signaling done.

## Checklist for AGENTS.md.template

```markdown
## Decommission Checklist

**CRITICAL**: Before signaling done, you MUST complete this checklist.
The Witness will verify each item and bounce you back if dirty.

### Pre-Done Verification

```bash
# 1. Git status - must be clean
git status
# Expected: "nothing to commit, working tree clean"

# 2. Stash list - must be empty
git stash list
# Expected: (empty output)

# 3. Beads sync - must be up to date
bd sync --status
# Expected: "Up to date" or "Nothing to sync"

# 4. Branch merged - your work must be on main
git log main --oneline -1
git log HEAD --oneline -1
# Expected: Same commit
```

### If Any Check Fails

- **Uncommitted changes**: Commit them or discard if unnecessary
- **Stashes**: Pop and commit, or drop if obsolete
- **Beads out of sync**: Run `bd sync`
- **Branch not merged**: Complete the merge workflow

### Signaling Done

Only after ALL checks pass:

```bash
bd close <issue-id>
bd sync
town mail send <rig>/witness -s "Work Complete" -m "Issue <id> done."
```
```

## Implementation

Add to AGENTS.md.template in the polecat prompting section.


Implement gt mq CLI commands

Add gt mq subcommands as sugar over bd:

- gt mq submit: Create MR for current branch
- gt mq list: Show open merge requests
- gt mq next: Show next MR ready to process
- gt mq process: Engineer processes the queue
- gt mq reorder <id> --after <x>: Change ordering via deps
- gt mq status <id>: Show MR details

All commands should work with the Beads data plane.


HTTP work client: CloudRun dispatch client

## Overview

HTTP client for dispatching work to Cloud Run and streaming results back.

## Implementation

```go
type WorkClient struct {
    serviceURL string
    httpClient *http.Client  // HTTP/2 enabled
}

func NewWorkClient(serviceURL string) *WorkClient {
    return &WorkClient{
        serviceURL: serviceURL,
        httpClient: &http.Client{
            Transport: &http2.Transport{},
            Timeout:   0,  // No timeout for streaming
        },
    }
}

func (c *WorkClient) DispatchWork(ctx context.Context, req WorkRequest) (<-chan WorkEvent, error) {
    // 1. POST to /work
    // 2. Return channel that streams WorkEvents
    // 3. Close channel when done/error
}
```

## Streaming Reader

```go
func (c *WorkClient) streamEvents(body io.Reader, events chan<- WorkEvent) {
    defer close(events)
    decoder := json.NewDecoder(body)
    for {
        var event WorkEvent
        if err := decoder.Decode(&event); err != nil {
            return
        }
        events <- event
    }
}
```

## Files

- `internal/cloudrun/client.go`

## Dependencies

Depends on: gt-9a2.7 (protocol types)
Used by: gt-9a2.8 (CloudRunOutpost)


LocalOutpost: Refactor current polecat spawning

## Overview

Refactor current local polecat spawning to implement the Outpost interface.

## Current State

Polecat spawning is ad-hoc. This task wraps it in LocalOutpost.

## Implementation

```go
type LocalOutpost struct {
    name       string
    maxWorkers int
    tmux       *Tmux
    workers    map[string]*LocalWorker
}

func NewLocalOutpost(config OutpostConfig) *LocalOutpost

func (o *LocalOutpost) Type() OutpostType { return OutpostLocal }

func (o *LocalOutpost) Spawn(issue string, cfg WorkerConfig) (Worker, error) {
    // Create tmux session
    // Start claude in pane
    // Return LocalWorker
}
```

## LocalWorker

```go
type LocalWorker struct {
    id      string
    session string  // tmux session name
    issue   string
    status  WorkerStatus
}

func (w *LocalWorker) Attach() error {
    // tmux attach-session
}
```

## Notes

- This should be a refactor, not new functionality
- Existing polecat code becomes LocalOutpost internals
- Tests should pass before and after

Depends on: gt-9a2.1 (interfaces)


Swarm learning: Session cleanup missing from swarm workflow

## Problem

After Enders Game swarm completed (18 issues merged), 16 polecat sessions were left running but idle. No automated cleanup occurred.

## What Should Happen

1. Witness detects polecat completed work (idle at prompt)
2. Witness verifies git state is clean
3. Witness shuts down session
4. Witness reports completion to Mayor

## GGT Components

- gt-cxx: Witness context cycling (covers self-cycling)
- gt-u1j.9: Witness daemon heartbeat loop
- gt-kmn.6: Witness swarm landing protocol

## Recommendation

Add to Witness responsibilities:
- Monitor for 'work complete' signals (DONE keyword, idle detection)
- Automated session shutdown after verification
- Swarm completion reporting to Mayor

See also: architecture.md 'Worker Cleanup (Witness-Owned)' section which describes this but it wasn't implemented in PGT.


bd ready --type: missing type filter for MQ integration

bd ready lacks --type flag that engineer.go expects.

## Code (internal/refinery/engineer.go)
readyMRs, err := e.beads.ReadyWithType("merge-request")

## Actual bd ready
No --type flag - only supports assignee, label, priority filters.

## Impact
Refinery can't find merge-requests in queue, so MQ doesn't process anything.

## Fix Options
1. Add --type flag to bd ready
2. Use bd list --type=merge-request --status=open instead
3. Both (ready filters for unblocked, list for all)

This is blocking the entire MQ pipeline.


Fix TestHasPolecat test failure


Add gt decommission command for clean swarm/worker shutdown

Single command to cleanly shut down a swarm and its workers: cancel swarm, stop sessions, optionally remove polecats. E.g. gt decommission gt-hw6 --cleanup


Adaptive backoff for daemon heartbeat

Track agent responsiveness and adjust heartbeat frequency.

## Per-Agent State

```go
type AgentBackoff struct {
    BaseInterval    time.Duration  // 60s default
    CurrentInterval time.Duration  // grows when busy
    MaxInterval     time.Duration  // 10min cap
    ConsecutiveMiss int            // pokes with no response
}
```

## Strategy Options

- **Fixed**: Always 60s (current, simple)
- **Geometric**: 60s → 90s → 135s → 202s (factor 1.5)
- **Exponential**: 60s → 120s → 240s (factor 2, aggressive)

## Recovery

When agent responds (runs a command):
- Reset ConsecutiveMiss to 0
- Return to BaseInterval immediately

## Benefits

- Reduces noise for busy agents
- Saves resources during quiet periods
- Still catches stuck agents (max interval cap)


Daemon: proper rig discovery

Currently discovers rigs by scanning tmux session names for gt-*-witness pattern. Should instead:
- Read rigs from mayor/rigs.json
- Or scan town directory for .gastown subdirs
- Handle rigs that exist but don't have running witnesses


Update harness beads redirect for GGT

Change ~/ai/.beads/redirect from mayor/rigs/gastown/.beads to gastown/mayor/.beads for the GGT directory structure


Bug: parseLifecycleRequest always matches 'cycle' due to LIFECYCLE prefix

In lifecycle.go, parseLifecycleRequest checks strings.Contains(title, "cycle") first, but the prefix "LIFECYCLE:" contains the word "cycle". This means ALL lifecycle messages match the cycle action, making restart and shutdown unreachable. Fix: check for restart/shutdown before cycle, or use word boundaries.


Swarm beads naming convention and auto-filing

Beads naming convention for swarm lifecycle events.

## Bead IDs

- swarm-started:<swarm-id>     # Filed when swarm begins
- swarm-task-merged:<swarm-id>:<task-id>  # Filed per task merge
- swarm-landed:<swarm-id>      # Filed when merged to main
- swarm-failed:<swarm-id>      # Filed on unrecoverable failure

## swarm-started Bead

Filed by: Mayor/Refinery at swarm creation
Contains:
- Swarm title and description
- List of tasks (issue IDs)
- List of workers (polecat names)
- Base commit SHA

## swarm-task-merged Bead

Filed by: Refinery after each successful merge
Contains:
- Task issue ID and title
- Polecat name
- Merge commit SHA
- Test results summary

## swarm-landed Bead

Filed by: Refinery after landing to main
Contains:
- Final merge commit SHA
- Stats: tasks completed, time taken
- List of all merged task beads
- Integration branch name (now deleted)

## swarm-failed Bead

Filed by: Refinery on unrecoverable failure
Contains:
- Failure reason
- Tasks merged vs not merged
- Recovery guidance

## Why Beads (not just mail)

- Persistent, queryable, synced to git
- Other agents can check status without mail
- Creates audit trail
- Works when agents offline


Document harness concept in docs/harness.md

Create comprehensive harness documentation:
- What is a harness (installation directory)
- Recommended structure and naming
- .beads/redirect for default project
- config/ contents (rigs.json, town.json)
- Mayor home vs rig-level mayor/
- Example configurations
- Relationship to rigs


gt rig reset: clear stale mail on reset/land

## Problem

When resetting or landing a rig/town, stale mail messages can confuse agents on startup. Old handoff messages, daemon notifications, and inter-agent mail should be cleaned up as part of reset.

## Current State

- `gt rig reset` exists but doesn't clear mail
- Stale messages accumulate (e.g., daemon SHUTDOWN messages)
- Agents may read outdated context on startup

## Proposed Behavior

`gt rig reset` and `gt town reset` should:
1. Close all open messages (`--type=message`) in the relevant beads
2. Optionally preserve pinned handoff beads (clear content, keep bead)
3. Log what was cleaned up

```bash
gt rig reset gastown          # Clears gastown mail
gt rig reset gastown --mail   # Only clear mail, keep other state
gt town reset                 # Clears all town-level mail
gt town reset --all           # Clears mail in all rigs too
```

## Implementation

1. Query `bd list --type=message --status=open`
2. Close each with reason 'Cleared during reset'
3. For pinned handoffs: `bd update <id> --description=''` instead of close


Merge: gt-5af.5

branch: polecat/Scabrous
target: main
source_issue: gt-5af.5
rig: gastown


Swarm state persistence: manifest + state + events

Upgrade swarm persistence to match PGT pattern.

## Current State
Single .gastown/swarms.json with all swarms.

## Target State (per-swarm)
```
.gastown/swarms/
└── <swarm-id>/
    ├── manifest.json  # Immutable config
    ├── state.json     # Mutable progress
    ├── events.jsonl   # Audit log
    └── report.md      # Generated report
```

## File Schemas

### manifest.json (immutable after creation)
```json
{
  "id": "sw-1",
  "title": "Epic description",
  "epic_id": "gt-abc",
  "rig": "gastown",
  "base_commit": "abc123",
  "integration_branch": "swarm/sw-1",
  "target_branch": "main",
  "workers": ["Toast", "Nux"],
  "tasks": [{"id": "gt-xyz", "title": "..."}],
  "created_at": "2024-01-01T00:00:00Z"
}
```

### state.json (mutable)
```json
{
  "state": "active",
  "task_states": {
    "gt-xyz": {"state": "merged", "assignee": "Toast"}
  },
  "updated_at": "2024-01-01T01:00:00Z",
  "error": null
}
```

### events.jsonl (append-only audit)
```jsonl
{"event": "created", "ts": "...", "data": {...}}
{"event": "task_assigned", "ts": "...", "data": {...}}
{"event": "task_merged", "ts": "...", "data": {...}}
```

## Benefits
- Audit trail via events.jsonl
- Manifest immutability prevents corruption
- Cleaner separation of concerns
- Per-swarm isolation

## Migration
Keep backward compat with swarms.json during transition.

## Files to Modify
- internal/swarm/manager.go: Refactor persistence
- internal/cmd/swarm.go: SwarmStore → directory-based

## Acceptance Criteria
- [ ] Per-swarm directory structure
- [ ] Events logged to JSONL
- [ ] Manifest immutable after creation
- [ ] List command aggregates from directories


Ephemeral rig support for ad-hoc batch work

Support for temporary worker rigs for ad-hoc batch work.

## Use Case

Ad-hoc batch work where you want temporary workers:
- gt spawn --epic <epic-id> --ephemeral --workers 3
- Creates temp rig with 3 clones
- Workers process epic children
- Work merges through queue
- Clones cleaned up when done

## Interface

```go
type EphemeralRig struct {
    ID               string
    GitURL           string
    BaseCommit       string
    Workers          []string
    IntegrationBranch string
    EpicID           string  // associated epic (for cleanup trigger)
    CreatedAt        time.Time
}

func InitEphemeralRig(gitURL string, numWorkers int) (*EphemeralRig, error)
func (r *EphemeralRig) AddWorker(name string) error
func (r *EphemeralRig) Destroy(force bool) error
```

## Directory Structure

```
<rig>/ephemeral/
└── rig-a3f7/
    ├── config.json
    ├── Alice/     # clone
    ├── Bob/       # clone
    └── Carol/     # clone
```

## Cleanup Trigger

When all issues in the associated epic are closed AND merged, the ephemeral rig can be destroyed.

## Note

No swarm ID needed - the epic ID provides the grouping. Ephemeral rig is just a convenience for temporary clones.

## Reference

PGT: ephemeral.py (for structure, ignore swarm ID coupling)


Fix gt prime to give crew workers crew context

gt prime currently gives Mayor context to all agents. Crew workers should get crew-specific context. Also extract shared theory of operation from mayor priming into shared context for all roles.


Git wrapper: shell out to git

Wrapper for git operations via subprocess.

## Interface

```go
type Git struct {
    workDir string
}

func NewGit(workDir string) *Git

// Core operations
func (g *Git) Clone(url, dest string) error
func (g *Git) Checkout(ref string) error
func (g *Git) Fetch(remote string) error
func (g *Git) Pull(remote, branch string) error
func (g *Git) Push(remote, branch string, force bool) error

// Commit operations
func (g *Git) Add(paths ...string) error
func (g *Git) Commit(message string) error
func (g *Git) CommitAll(message string) error

// Query operations
func (g *Git) Status() (*GitStatus, error)
func (g *Git) CurrentBranch() (string, error)
func (g *Git) HasUncommittedChanges() (bool, error)
func (g *Git) RemoteURL(remote string) (string, error)

// Merge operations
func (g *Git) Merge(branch string) error
func (g *Git) Rebase(onto string) error
func (g *Git) AbortMerge() error
func (g *Git) AbortRebase() error
```

## Implementation

Shell out to git binary via exec.Command. Parse output where needed (Status, CurrentBranch).

## Error Handling

- Wrap git stderr in error messages
- Detect specific failures (merge conflict, auth failure, not a repo)
- Return structured errors for callers to handle

## Testing

- Use temp directories with real git repos
- Test both success and failure paths


Fetch and conflict check: git operations for MR

Implement git operations for MR processing.

Steps:
1. git fetch origin <mr.branch>
2. git checkout <mr.target> (main or integration/xxx)
3. git merge --no-commit --no-ff <mr.branch> (test merge)
4. Check for conflicts
5. If conflicts: abort and return Failure(conflict, files)
6. If clean: abort (actual merge in next step)

Helper functions:
- FetchBranch(branch string) error
- CheckConflicts(source, target string) ([]string, error)

Reference: docs/merge-queue-design.md#process-merge-steps


Spawn --molecule integration

Implement gt spawn --molecule flag for molecule-based polecat workflows.

Usage: gt spawn --issue gt-xyz --molecule mol-gastown-polecat

Behavior:
1. Validate molecule exists and is well-formed
2. Create molecule instance (child beads) under the issue
3. Find first ready step(s) in the instance
4. Spawn polecat with first ready step as initial work

Implementation:
1. Add --molecule flag to spawn command
2. Call molecule.Instantiate()
3. Query ready steps from instance
4. Pass first ready step to polecat context

Files: internal/cmd/spawn.go

Acceptance:
- --molecule flag works
- Creates proper molecule instance
- Polecat starts on first ready step
- End-to-end test with actual polecat


Plugin: Plan-to-Epic converter

## Purpose

Help users create beads epics from various planning inputs.

## Inputs
- Markdown task lists
- GitHub issues
- Linear/Jira exports
- Free-form descriptions
- Existing beads epics

## Output
- Beads epic with properly structured children
- Dependencies set for wave ordering
- Priorities assigned
- Ready for `gt spawn --epic <id>`

## Implementation Options

### Option A: CLI Tool
```bash
gt plan import --from github --repo owner/repo --label batch-candidate
gt plan import --from markdown tasks.md
gt plan structure <epic-id>  # analyze and add dependencies
```

### Option B: Plugin Agent
A plugin at `<rig>/plugins/plan-oracle/` that:
- Receives planning requests via mail
- Analyzes scope and requirements
- Creates structured beads epic
- Sets dependencies based on analysis

### Option C: Interactive Mode
```bash
gt plan create
# Walks through questions, creates epic interactively
```

## Axiom

As stated: 'The Planning phase should end in the creation of a workable Beads plan.'

This plugin bridges the gap between human planning and machine-executable work.

## Priority

P2 - Nice to have for MVP. Manual epic creation works for now.

## Note

No "swarm IDs" - output is just a beads epic with children. Workers process it independently.


CLI: mayor commands (start, attach, stop, status)

Mayor management CLI commands.

## Commands

### gt mayor start
```
gt mayor start [--continue] [--agent AGENT]
```
- --continue: Resume from previous session (check for handoff mail)
- --agent: claude (default) or other agent type

### gt mayor attach
```
gt mayor attach
```
Attach to running Mayor session.

### gt mayor stop
```
gt mayor stop [--grace-period N]
```
Stop Mayor session with optional grace period.

### gt mayor status
```
gt mayor status [--json]
```
Show Mayor running status.

## Session Management
Session name: `gt-mayor`
Working directory: `<town>/mayor/rig/` or `<town>/mayor/`

## Implementation
Similar to session commands but for special Mayor context.

```go
func getMayorPath(townRoot string) string {
    return filepath.Join(townRoot, "mayor")
}

func getMayorSessionName() string {
    return "gt-mayor"
}
```

## New File
internal/cmd/mayor.go

## PGT Reference
gastown-py/src/gastown/cli/mayor_cmd.py

## Acceptance Criteria
- [ ] gt mayor start launches Mayor session
- [ ] gt mayor attach works
- [ ] gt mayor stop with grace period
- [ ] gt mayor status shows running state


MQ metrics: pending count, processing time, success rate

Implement merge queue metrics collection.

Metrics to track:
- mq_pending_count: MRs waiting
- mq_processing_time: Time from submit to merge (histogram)
- mq_success_rate: Merges vs rejections
- mq_conflict_rate: How often conflicts occur
- mq_test_failure_rate: Test failures

Storage options:
- Simple: JSON file with rolling stats
- Advanced: Prometheus-compatible metrics endpoint

Display via:
- gt mq stats: summary statistics
- Dashboard widget (separate task)

Reference: docs/merge-queue-design.md#observability


gt mq integration create: create integration branch for epic

Implement 'gt mq integration create <epic>' command.

Actions:
1. Verify epic exists
2. Create branch: integration/<epic-id> from main
3. Push to origin
4. Store integration branch info in epic metadata

Usage:
  gt mq integration create gt-auth-epic
  # Creates integration/gt-auth-epic from main

Future MRs for this epic's children will auto-target this branch.

Reference: docs/merge-queue-design.md#integration-branches


gt spawn: Enter key needs debounce delay after paste

## Problem

When spawning polecats via `gt spawn`, instructions are pasted into tmux but workers often sit idle at prompt because the Enter key arrives before the paste is fully processed.

## Observed Behavior

- Instructions pasted via tmux send-keys
- Enter key sent immediately after
- Worker session shows prompt with no input (paste not submitted)
- Requires manual intervention to nudge workers

## Expected Behavior

- Paste completes fully
- Enter key submits the pasted instructions
- Worker begins executing immediately

## Root Cause

Likely a race condition between paste buffer processing and keypress handling. Need either:
1. Debounce delay before sending Enter
2. Longer delay (Tmax) to ensure paste completes
3. Alternative submission mechanism

## Impact

- Swarm of 10 workers had multiple stalls at startup
- Required manual tmux send-keys to unstick them
- Defeats purpose of automated spawning

## References

- Observed during Mad Max swarm (Dec 18, 2025)
- Affects gt spawn command in internal/cmd/spawn.go


Add merge-request type to Beads schema

Define the merge-request bead type with fields:
- branch: source branch (e.g., polecat/Nux/gt-xxx)
- target_branch: destination (main or integration/xxx)
- source_issue: the work being merged (gt-xxx)
- worker: who did the work
- rig: which rig
- merge_commit: (filled on close) SHA of merge commit
- close_reason: (filled on close) success/failure details

This may require beads schema changes or just convention.


Add unit tests for daemon package

The daemon package has no unit tests. Need tests for:
- Config and state serialization
- Session name pattern matching (isWitnessSession)
- Lifecycle request parsing
- Identity to session mapping


MQ config schema: merge_queue section in rig config.json

Define and implement merge_queue configuration in rig config.

Schema:
{
  "merge_queue": {
    "enabled": true,
    "target_branch": "main",
    "integration_branches": true,
    "on_conflict": "assign_back",  // or "auto_rebase"
    "run_tests": true,
    "test_command": "go test ./...",
    "delete_merged_branches": true,
    "retry_flaky_tests": 1,
    "poll_interval": "30s",
    "max_concurrent": 1
  }
}

Implement:
- Config loading in rig package
- Default values
- Validation

Reference: docs/merge-queue-design.md#configuration


Bubbletea TUI dashboard

Interactive TUI using Charm Bubbletea. Root model, view routing, Lipgloss theme. Includes: dashboard, rig explorer, polecat detail with live tail, mail view, beads view, spawn wizard.


CLI: polecat commands (add, list, wake, sleep, decommission)

GGT is missing most polecat management commands that PGT has.

Missing Commands:
- gt polecat add <rig> <name> - Add polecat to rig (creates clone)
- gt polecat list [<rig>] - List polecats with state
- gt polecat info <polecat> - Show detailed info
- gt polecat wake <polecat> - Mark available
- gt polecat sleep <polecat> - Mark unavailable
- gt polecat decommission <polecat> - Remove polecat safely

PGT Reference: gastown-py/src/gastown/cli/polecat_cmd.py

Notes:
- spawn exists but doesn't cover management
- wake/sleep are in polecat manager but not CLI
- decommission should check for uncommitted work


Witness swarm landing protocol

Witness responsibilities for swarm landing cleanup.

## Trigger

Witness receives "swarm-landed" mail from Refinery containing:
- swarm_id
- merge_commit
- action: "cleanup"

## Landing Protocol

### Phase 1: Preflight
Verify all polecat sessions stopped:
- Check tmux for active sessions
- Kill any remaining sessions
- Wait for graceful shutdown

### Phase 2: Git Audit
For each polecat in swarm:
- Check for uncommitted changes
- Check for unpushed commits
- Check for stashes
- Classify as "beads-only" (safe) or "code at risk"
- If code at risk: escalate, abort landing

### Phase 3: Cleanup
For each polecat:
- Clear state entry (mark as idle)
- Delete inbox (or archive)
- Delete worker branch (local + remote)

### Phase 4: Final Report
- Update swarm state to "landed"
- File landing report bead
- Mail Mayor with summary

## Code at Risk Handling

If git audit finds code at risk:
1. Abort cleanup for that polecat
2. Mail Mayor with details
3. Human intervention required

## Reference

PGT: swarm/landing.py (6-phase protocol), swarm/git_audit.py