Polecats must not create GitHub PRs

Polecats should never use 'gh pr create' or create GitHub pull requests.

## Correct Workflow
1. Polecat works on polecat/<name> branch
2. Commits and pushes to origin
3. Creates beads MR issue (type: merge-request)
4. Refinery processes the MR and merges to main

## Wrong Workflow
- Using gh pr create
- Creating GitHub pull requests directly

## Why
- Refinery is our merge queue processor
- GitHub PRs bypass our workflow
- Beads MRs are the coordination mechanism


Workspace cleanup: preflight and postflight

Workspace preflight and postflight commands for clean state management.

## Preflight
```
gt preflight [--rig <rig>] [--dry-run]
```

Run before starting batch work:
1. Clean stale mail in inboxes
2. Check for stuck workers (warn)
3. Check rig health (polecats, refinery)
4. Verify git state is clean
5. Run bd sync to ensure beads current

## Postflight
```
gt postflight [--rig <rig>] [--archive-mail] [--dry-run]
```

Run after batch work completes:
1. Archive old mail with --archive-mail
2. Clean up stale integration branches
3. Sync beads
4. Report on rig state

## Implementation
```go
func Preflight(rigName string, dryRun bool) (*PreflightReport, error)
func Postflight(rigName string, opts PostflightOptions) (*PostflightReport, error)
```

## Report Structures
```go
type PreflightReport struct {
    MailCleaned    int
    RigHealthy     bool
    StuckWorkers   []string
    Warnings       []string
}

type PostflightReport struct {
    MailArchived   int
    BranchesCleaned int
    Warnings       []string
}
```

## Note

These are workspace maintenance commands, not tied to "swarm" lifecycle. Run them anytime to keep the rig clean.

## Acceptance Criteria
- [ ] Preflight cleans stale state
- [ ] Postflight archives old mail
- [ ] Both have --dry-run mode
- [ ] Clear reports of actions taken


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)


Mail CLI: archive, purge, search, mark

GGT mail CLI needs more commands for mail management.

## Commands to Add

### gt mail check
Check for new mail without full inbox display.
```
gt mail check [--quiet] [--inject]
```
- --quiet: Only output if new mail (for scripts)
- --inject: Send notification to running session

### gt mail mark
Change read status.
```
gt mail mark <id> --read
gt mail mark <id> --unread
```

### gt mail delete
Remove message from inbox.
```
gt mail delete <id> [--force]
```
- Confirm unless --force

### gt mail archive
Move old/read messages to archive.
```
gt mail archive [--older-than DAYS] [--all-read] [--dry-run]
```
- Creates inbox.jsonl.archive or separate archive.jsonl

### gt mail purge
Permanently delete archived messages.
```
gt mail purge [--older-than DAYS] [--dry-run] [--force]
```

### gt mail search
Find messages by content.
```
gt mail search <query> [--from SENDER] [--subject] [--body]
```

### gt mail reply
Reply to a message.
```
gt mail reply <id> -m BODY
```
- Auto-sets reply_to and thread_id
- Auto-addresses to original sender

## Files to Modify
- internal/cmd/mail.go: Add commands
- internal/mail/mailbox.go: Add Archive(), Search(), Delete()

## Acceptance Criteria
- [ ] All commands implemented
- [ ] Archive stores in separate file
- [ ] Search supports regex patterns
- [ ] Delete confirms by default


Molecule Workflow Engine: Composable Crystallized Workflows

# Epic: Molecule Workflow Engine

**Vision**: Molecules are crystallized, composable, nondeterministic-idempotent workflow templates. Any worker can pick up where any other worker was interrupted and continue along the molecule.

**Christmas Target**: Full molecule-based workflow engine operational by Dec 25, 2025.

## The Core Concepts

1. **Molecule**: Read-only workflow template (beads issue with type=molecule)
2. **Atom/Step**: Individual work unit with prose instructions
3. **Bond**: Dependency between steps
4. **Polymer/Derived**: Molecule composed from other molecules
5. **Instance**: Concrete beads created when molecule is attached to work

## Key Features Needed

### 1. Molecule Composition (Includes Directive)
Molecules can include other molecules:
\`\`\`markdown
## Molecule: gastown-polecat
Includes: mol-engineer-in-box

## Step: install-binary
Build and install the local gt binary.
Needs: submit
\`\`\`

### 2. Standard Molecules
- mol-install-go-binary: Single step to build/install gt
- mol-gastown-polecat: engineer-in-box + install-binary

### 3. Spawn Integration
\`gt spawn --issue <id> --molecule <mol-id>\` creates molecule instance then starts polecat on first ready step.

### 4. Nondeterministic Idempotence
- Steps are atomic (pending → in_progress → completed)
- Any worker can pick up any ready step
- Step timeout/recovery for stuck workers

## Success Criteria
- [ ] Polecats can be spawned with mol-gastown-polecat
- [ ] Derived molecules work end-to-end
- [ ] 10+ polecat swarm completes molecule workflows
- [ ] install-go-binary step runs after successful merges


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


gt swarm: Not discovering tasks from epic dependents

gt swarm create/start shows '0 tasks loaded' even when epic has dependents.

Repro:
1. Create epic gt-hw6
2. Create tasks and add deps: bd dep add gt-xxx gt-hw6
3. gt swarm create gastown --epic gt-hw6 --worker Toast
4. Swarm shows 'Tasks: 0'

Expected: Swarm should discover tasks that depend on the epic.
Actual: Shows '(no tasks loaded)'


Phase 4: Polish and observability

Production readiness.

## Commands
- gt patrol status - Show patrol state for all agents
- gt patrol history - Recent patrol activity

## Observability
- Metrics collection
- Alert thresholds
- Dashboard (optional)

## Tuning
- Cooldown optimization
- Wake SLA verification
- Error threshold tuning

Depends: Phase 3


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.


Merge: gt-3x1.5

branch: polecat/Immortan
target: main
source_issue: gt-3x1.5
rig: gastown


test-after-fix

test


Add molecule to update local go binary on push to main

When pushing to main branch, automatically rebuild and install the gt binary on the local machine so changes are immediately available.


Molecule converge: iterate until AI says done

Work isn't 'done' until AI is confident it's done. Fixes the 85% problem.

**From VC**: internal/iterative/converge.go - 3-7 iterations with AI convergence detection. ~600 lines.
Target: 20%+ more issues discovered. Observed: ~25% average improvement.

**Gas Town implementation**: Molecule converge config:
```yaml
converge:
  strategy: ai
  min_iterations: 3
  max_iterations: 7
  confidence: 0.85
```

Polecat iterates, AI checks convergence. Stop when AI is confident or max reached.

**Value**: Raw agents get to ~85% and stop. Iteration catches the remaining 15%.

**VC metrics**: Convergence rate >70%, mean 4-5 iterations for complex work.


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)


Add deacon/ mail identity support

Ensure deacon/ works as a mail identity in town-level beads. Test with bd mail send deacon/ and bd mail inbox --identity deacon/.


Work on gt-e11: gt mail send priority flag is incompatibl...

Work on gt-e11: gt mail send priority flag is incompatible with bd mail send. Run 'bd show gt-e11' for details.


Test merge request

branch: polecat/Test/gt-test
target: main
source_issue: gt-test
worker: TestWorker
rig: gastown


Polecats should not push branches to remote

## Current Behavior

Polecats push their branches to origin (e.g., `polecat/furiosa`), which pollutes the remote with many short-lived branches.

## Desired Behavior

Polecats should only commit locally. The Refinery handles all remote pushes:
1. Polecat works on local `polecat/<name>` branch
2. Polecat signals done (state → idle)
3. Refinery pulls from local polecat branch
4. Refinery runs tests, merges to main
5. Refinery pushes main to remote
6. If PR review needed, Refinery creates the PR

## Benefits

- Clean remote (no branch pollution)
- Clear responsibility (Refinery is the quality gate)
- Simpler cleanup (local branches deleted with worktree)
- Less noise in GitHub UI

## Trade-offs

- If polecat crashes before Refinery merges, code is lost locally
- But beads issue remains open, another polecat can redo the work
- This is acceptable for ephemeral workers

## Implementation

In polecat CLAUDE.md or landing protocol:
- Remove `git push origin HEAD` from workflow
- Replace with just `git commit` + signal done
- Refinery handles the rest


Improve test coverage in low-coverage packages

Several packages have low test coverage:
- internal/cmd: 6.8%
- internal/mail: 3.6%
- internal/daemon: 12.1%
- internal/doctor: 14.5%
- internal/refinery: 20.6%
- internal/session: 27.8%
- internal/git: 28.8%

Priority should be given to mail, cmd, and daemon packages which handle critical functionality.


TIDY UP: Your previous work (patrol runner) was already m...

TIDY UP: Your previous work (patrol runner) was already merged to main. Check your git status is clean, sync beads, and if nothing to do, just run 'gt done'.


Update prompts.md: Engineer role and templates

Update docs/prompts.md with Engineer role:

1. Role Prompts table: Change Refinery to Engineer
2. Add Engineer-specific prompts:
   - Session restart request template
   - Subtask filing template
   - Handoff mail template
3. Update refinery.md template name to engineer.md
4. Ensure consistency with architecture.md


Update harness beads redirect for GGT

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


Test Task 2: Add comment to version.go


Merge: gt-70b3

type: merge-request
branch: polecat/Rictus
target: main
source_issue: gt-70b3
rig: gastown


load-context

Run gt prime and bd prime. Verify issue assignment.
Check inbox for any relevant messages.

Read the assigned issue (gt-test123) and understand the requirements.
Identify any blockers or missing information.


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


gt spawn beads sync warning is misleading for redirect-based polecats

## Problem

When spawning a polecat, gt spawn shows a warning:
```
Warning: beads sync: exit status 1
```

This is misleading because polecats using the redirect architecture (`.beads/redirect`) share the canonical database at `mayor/rig/.beads/beads.db`. The 'stale beads' indicated by the warning is just git branch divergence (main vs beads-sync), not actual data staleness.

## Expected Behavior

gt spawn should either:
1. Skip the beads sync check for polecats using redirects (they share the canonical DB)
2. Or provide a clearer message like 'beads redirect active, using shared database'

## Reproduction

```bash
gt spawn --issue gt-xxx --rig gastown --create
# Shows 'Warning: beads sync: exit status 1' even though beads are current
```

## Root Cause

spawn.go calls beads sync and treats any non-zero exit as a warning. But with redirects, the polecat doesn't need its own beads - it uses the canonical source via the redirect chain:
```
polecat/.beads/redirect -> ../../.beads -> gastown/.beads/redirect -> mayor/rig/.beads
```

## Fix Options

1. Check for .beads/redirect before calling sync
2. Have bd sync return 0 when redirect is present
3. Suppress the warning in spawn when redirect exists


Fix TestHasPolecat test failure


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


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


Implement handoffs using pinned beads

Replace the current mail-based handoff system with pinned beads.

## Current Problem

Handoff messages get closed before the successor can read them because:
1. `gt mail read` auto-acks (closes) messages
2. `bd mail inbox` only shows open messages
3. Successor sees empty inbox

## Solution

Use pinned beads for handoffs:
- One pinned bead per role: `mayor-handoff`, `<rig>-refinery-handoff`, etc.
- Predecessor updates the content before cycling
- Successor reads on startup via `gt prime`
- Never closes - always available

## Implementation

### 1. Create handoff beads on first cycle
- `bd create --title='Mayor Handoff' --type=task --status=pinned --assignee=mayor`
- Store ID in role config or use well-known naming convention

### 2. Update gt handoff command
- Instead of `bd mail send`, update the pinned handoff bead
- `bd update <handoff-id> --description='...handoff content...'`

### 3. Update gt prime
- Read the role's handoff bead
- Display content to successor

### 4. Compression/reset
- `gt rig reset` clears handoff content
- Or manual: `bd update <handoff-id> --description=''`

## Dependencies

Requires beads-6v2 (StatusPinned) to be implemented first.


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.


gt handoff deadlock at handoff.go:125

When running 'gt handoff -m "message"' after successful MR submit, go panics with 'fatal error: all goroutines are asleep - deadlock\!' at handoff.go:125. The shutdown request still appears to be sent successfully but the command crashes. Stack trace shows issue is in runHandoff select statement.


mol-polecat-work

Full polecat lifecycle from assignment to decommission.

This proto enables nondeterministic idempotence for polecat work.
A polecat that crashes after any step can restart, read its molecule state,
and continue from the last completed step. No work is lost.

Variables:
- gt-test123 - The source issue ID being worked on


verify-tests

Run existing tests. Add new tests for new functionality.
Ensure adequate coverage.

go test ./...

Fix any test failures before proceeding.

Depends: implement


CLI: plugin commands (list, status)

Add gt plugins <rig> to list plugins and gt plugin status <name> to check plugin state. Simple directory scan of <rig>/plugins/.


Agent monitoring and status inference

Agent monitoring with status inference from activity, like PGT.

## Agent Status Enum
```go
type AgentStatus string
const (
    StatusAvailable AgentStatus = "available"
    StatusWorking   AgentStatus = "working"
    StatusThinking  AgentStatus = "thinking"
    StatusBlocked   AgentStatus = "blocked"
    StatusWaiting   AgentStatus = "waiting"
    StatusReviewing AgentStatus = "reviewing"
    StatusIdle      AgentStatus = "idle"
    StatusPaused    AgentStatus = "paused"
    StatusError     AgentStatus = "error"
    StatusOffline   AgentStatus = "offline"
)
```

## Status Sources (priority order)
```go
type StatusSource string
const (
    SourceBossOverride StatusSource = "boss"      // Witness/Mayor sets
    SourceSelfReported StatusSource = "self"      // Agent reports own status
    SourceInferred     StatusSource = "inferred"  // Detected from activity
)
```

## Activity Detection

### Pattern Registry
```go
var activityPatterns = []struct {
    Pattern string
    Status  AgentStatus
}{
    {"Thinking...", StatusThinking},
    {"BLOCKED:", StatusBlocked},
    {"Error:", StatusError},
    // etc
}
```

### Idle Detection
No pane output for N seconds → StatusIdle

### Resource Monitoring (optional)
CPU/memory via os.Process

## New Package
internal/monitoring/
├── types.go      # AgentStatus, StatusReport
├── detector.go   # PatternRegistry, detect from output
├── tracker.go    # Per-agent status tracking
└── idle.go       # Idle timeout detection

## Integration
- Session capture output → monitoring detector
- Status shown in gt status, gt session list

## PGT Reference
gastown-py/src/gastown/monitoring/

## Acceptance Criteria
- [ ] Status enum with 10 states
- [ ] Pattern-based detection from pane output
- [ ] Idle detection with configurable timeout
- [ ] Status visible in CLI output


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.


Witness commands: gt witness start/stop/status needed

## Summary

No `gt witness` command exists. The witness should:
- Monitor polecats for stuck/idle state
- Nudge polecats that seem blocked
- Report status to mayor
- Handle polecat lifecycle

## Expected Commands

```bash
gt witness start gastown   # Start witness for rig
gt witness stop gastown    # Stop witness
gt witness status          # Show witness status
```

## Current State

- Witness directory exists: /Users/stevey/gt/gastown/witness/
- Has state.json but no active process
- gt status shows 'Witnesses: 0'

## Context

Full polecat flow needs witness monitoring for production use.


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


gt crew at: auto-prime when exec'ing Claude in-session

When running 'gt crew at <name>' from inside the target session, we exec Claude directly. But this means we can't send 'gt prime' afterward since we ARE the process.

Possible solutions:
1. Claude startup hook that runs gt prime
2. Pass prompt as argument to claude CLI
3. Wrapper script approach

Related: crew resume prompt also can't be sent in this path.


town handoff command (optional)

CLI commands for session handoff workflow (optional convenience).

## Commands

### gt handoff
Generate handoff interactively.
```
gt handoff [--send]
```
- Collects current state (status, inbox, beads)
- Prompts for additional notes
- --send: Mail to self and exit

### gt resume
Check for and display pending handoff.
```
gt resume
```
- Checks inbox for handoff message
- Displays formatted handoff if found
- Suggests next actions

## Implementation

These are convenience wrappers. The same workflow can be done manually:
```bash
# Manual handoff
town status > /tmp/handoff
town inbox >> /tmp/handoff
bd ready >> /tmp/handoff
# Edit and send
town mail send mayor/ -s "Session Handoff" -f /tmp/handoff
```

## Priority

P2 - Optional. Manual workflow works fine. Nice to have for UX.

## Notes

Part of session cycling workflow designed in gt-u82.


Add --fart as easter egg alias for bd mol bond

Add a hidden alias for the bond command:

```bash
bd mol fart mol-polecat-work --wisp
# equivalent to:
bd mol bond mol-polecat-work --wisp
```

## Context
The fart joke: instantiating a proto can produce either:
- A Mol (solid/substantial output)
- A Wisp (gas/ephemeral output)

## Implementation
- Add 'fart' as an alias in the mol subcommand
- No documentation needed (easter egg)
- Maybe a fun message: 'Bonding molecule...' or similar

Low priority, just for fun.


Tech Debt: Code Review December 2024

Tech debt identified during code review on 2024-12-21. Contains 11 issues ranging from P2-P4 covering:

- Code duplication (manager creation boilerplate)
- Test coverage gaps (cmd 6.8%, mail 3.6%)
- Magic strings needing constants
- Error handling inconsistencies
- Large files needing splitting
- Unused code removal

Work through these incrementally to improve codebase maintainability.


Plugin system: subprocess JSON protocol

Language-agnostic plugin system via subprocess JSON protocol.

## Concept

Allow extending Gas Town with external scripts/programs. Plugins communicate via stdin/stdout JSON.

## Protocol

Request (gt → plugin):
```json
{"method": "check", "params": {"rig": "wyvern", "polecat": "Toast"}}
```

Response (plugin → gt):
```json
{"result": {...}, "error": null}
```

Error response:
```json
{"result": null, "error": {"code": 1, "message": "..."}}
```

## Interface

```go
type Plugin struct {
    Name    string
    Path    string  // executable path
    Methods []string
}

type PluginManager struct {
    plugins map[string]*Plugin
}

func NewPluginManager() *PluginManager
func (pm *PluginManager) Register(plugin *Plugin) error
func (pm *PluginManager) Call(plugin, method string, params any) (any, error)
func (pm *PluginManager) Discover(dir string) error  // auto-discover plugins
```

## Plugin Discovery

Look in:
1. `<town>/plugins/`
2. `~/.config/gastown/plugins/`
3. System path with `gt-plugin-*` prefix

## Built-in Hooks

Plugins can register for events:
- `polecat.started` - Polecat session started
- `polecat.done` - Polecat signals done
- `merge.requested` - MR submitted
- `merge.completed` - MR merged

## Use Cases

- Custom health checks
- Integration with external systems (Slack, etc.)
- Custom merge policies
- Metrics collection

## Future

Consider MCP integration for richer plugin capabilities.


Shutdown request handler

Handle polecat shutdown requests:

When polecat runs 'gt handoff --shutdown':
1. Polecat sends mail to <rig>/witness requesting shutdown
2. Witness receives mail
3. Witness verifies:
   - Git working tree is clean
   - Work is submitted (MR exists or in queue)
   - No uncommitted beads changes
4. If clean:
   - gt session stop <rig>/<polecat>
   - git worktree remove polecats/<name>
   - git branch -d polecat/<name>
5. If not clean:
   - Send nudge back to polecat
   - Track retry count

Depends on fixing gt-dsfi (handoff deadlock).


test with labels


Merge: beads-2nh

branch: fix/spawn-beads-path
target: main
source_issue: beads-2nh
rig: gastown


gt session restart/status: Complete session management

Add missing session subcommands:

- gt session restart <rig> <polecat> - Restart a session (stop + start)
- gt session status <rig> <polecat> - Show session status details

status should show:
- Running state
- Uptime
- Current activity
- Last output timestamp


GGT vs PGT Gap Analysis Summary

Summary of gaps comparing GGT (~4,500 LOC) to PGT (~27,700 LOC).

## Critical Gaps (P1) - Existing Issues
- gt-u1j.17: Polecat CLI (add, list, wake, sleep) - DETAILED
- gt-u1j.16: Rig CLI (add, list, show, remove) - DETAILED
- gt-u1j.18: Witness CLI (start, stop, status) - DETAILED
- gt-f9x.4: Doctor framework - DETAILED
- gt-f9x.5: Workspace doctor checks - DETAILED
- gt-f9x.6: Rig doctor checks - DETAILED

## Critical Gaps (P1) - New Issues
- gt-a95: Refinery background daemon mode - ENHANCED
- gt-hgk: Mail message types and threading - ENHANCED

## Significant Gaps (P2) - New Issues
- gt-d46: Mail CLI archive/purge/search - ENHANCED
- gt-e9k: Swarm preflight/postflight - ENHANCED
- gt-662: Swarm report generation - ENHANCED
- gt-69l: Hook system - ENHANCED
- gt-3yj: Agent monitoring - ENHANCED
- gt-1ky: Workspace CLI (may overlap f9x.3) - ENHANCED
- gt-9j9: Worker status reporting - ENHANCED
- gt-qao: Mayor CLI - ENHANCED
- gt-7o7: Session pre-shutdown checks - ENHANCED
- gt-c92: Batch all command - ENHANCED
- gt-lno: Swarm state persistence - ENHANCED
- gt-a9y: File locking - ENHANCED
- gt-30o: Error handling improvements - ENHANCED

## Significant Gaps (P2) - Existing Issues
- gt-kmn.12: Ephemeral rig support - DETAILED

## Lower Priority (P3) - New Issues
- gt-3fm: Mail orchestrator daemon - ENHANCED
- gt-2kz: Cleanup commands - ENHANCED
- gt-ebl: Names commands - ENHANCED
- gt-1u9: Interactive prompts - ENHANCED
- gt-8lz: Help text improvements - ENHANCED

## Closed as Duplicates
- gt-3tz → gt-u1j.17 (polecat CLI)
- gt-e1r → gt-u1j.16 (rig CLI)
- gt-86w → gt-f9x.4/5/6 (doctor)
- gt-alx → gt-kmn.12 (ephemeral rigs)

## Execution Order Recommendation
1. P1 CLI commands (existing detailed issues ready to implement)
2. gt-a95: Refinery daemon (blocks autonomous operation)
3. gt-7o7: Pre-shutdown checks (prevents data loss)
4. gt-a9y: File locking (prevents corruption)
5. gt-hgk + gt-d46: Mail improvements
6. gt-69l: Hook system (enables extensibility)
7. Remaining P2s in any order
8. P3s as time permits


Complete gt crew commands (list, attach, remove, refresh, status)

Add remaining crew subcommands to internal/cmd/crew.go:

1. gt crew list - List crew workspaces with status
2. gt crew at/attach - Start tmux session in crew workspace
3. gt crew remove - Remove crew workspace (with safety checks)
4. gt crew refresh - Context cycling with mail-to-self handoff
5. gt crew status - Show detailed workspace status

Build on existing crew add implementation in internal/cmd/crew.go.
Reference closed issues gt-cik.3-7 for original requirements.


Beads CLI wrapper: shell out to bd

Wrapper for bd (beads CLI) commands.

## Interface

```go
type Beads struct {
    workDir string
}

func NewBeads(workDir string) *Beads

// Query
func (b *Beads) List(opts ListOptions) ([]*Issue, error)
func (b *Beads) Ready() ([]*Issue, error)
func (b *Beads) Show(id string) (*Issue, error)
func (b *Beads) Blocked() ([]*Issue, error)

// Mutations
func (b *Beads) Create(opts CreateOptions) (*Issue, error)
func (b *Beads) Update(id string, opts UpdateOptions) error
func (b *Beads) Close(ids ...string) error

// Sync
func (b *Beads) Sync() error
func (b *Beads) SyncStatus() (*SyncStatus, error)
```

## Data Types

```go
type Issue struct {
    ID          string
    Title       string
    Status      string
    Priority    int
    Type        string
    Description string
    Parent      string
    Children    []string
    DependsOn   []string
    Blocks      []string
}

type ListOptions struct {
    Status   string  // "open", "closed", "all"
    Type     string
    Priority int
}

type CreateOptions struct {
    Title       string
    Type        string
    Priority    int
    Description string
    Parent      string
}
```

## Implementation

Shell out to bd binary, parse JSON output where available.
```go
func (b *Beads) runBd(args ...string) ([]byte, error) {
    cmd := exec.Command("bd", args...)
    cmd.Dir = b.workDir
    return cmd.Output()
}
```

## Error Handling

- bd not installed: Clear error with install instructions
- Not a beads repo: Detect and report
- Sync conflicts: Parse and report


Phase 5.2: Ephemeral error handling and recovery

Handle edge cases in ephemeral molecule lifecycle.

## Scenarios

### Agent dies mid-molecule
- Ephemeral state persists
- Witness detects stall (no heartbeat)
- Options:
  a. Nudge agent to resume
  b. Reassign to new agent
  c. Escalate to Mayor

### Squash fails
- Retry with exponential backoff
- If persistent failure, escalate
- Don't lose the ephemeral state until squash succeeds

### Orphaned molecules
- Molecule started, never completed
- gt doctor detects (>24h old with no activity)
- Options:
  a. Manual review
  b. Auto-abandon with 'abandoned' digest
  c. Reassign

### Ephemeral repo corruption
- Re-init from scratch
- Active molecules are lost
- Main beads is source of truth for assigned work

## Implementation

Add error handling to:
- bd mol bond (handle init failures)
- bd squash (retry logic)
- gt doctor (recovery suggestions)


Enhance Mayor CLAUDE.md with GGT milestone tracking

Update ~/ai/CLAUDE.md (Mayor startup context) with:

1. GGT Self-Hosting Milestone section
   - Track progress toward gt replacing town
   - Key blockers: gt-h5n (merge queue), gt-974 (daemon)
   - What "self-hosting" means (GGT working on itself)

2. Recent Architecture Decisions
   - Engineer = role, Refinery = place
   - Merge queue in Beads (not branch discovery)
   - Session restart protocol

3. Transition Plan
   - Current: use town commands (PGT)
   - Target: use gt commands (GGT)
   - Switch criteria: gt-b1g closed

Keep it concise - Mayor context should be quick to scan.


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


CLI: all command for batch polecat operations

Batch operations across multiple polecats.

## Commands

### gt all start
```
gt all start [--awake-only] [<specs>...]
```
Start sessions for multiple polecats.
- --awake-only: Only start awake polecats
- specs: Polecat names, rig/polecat patterns

### gt all stop
```
gt all stop [<specs>...] [--force]
```
Stop sessions for multiple polecats.

### gt all status
```
gt all status [<specs>...] [--json]
```
Show status of multiple polecats.

### gt all attach
```
gt all attach [<specs>...]
```
Attach to multiple sessions in tmux panes/windows.

### gt all run
```
gt all run <command> [<specs>...]
```
Run command in multiple polecat sessions.

## Spec Patterns
- `Toast`: Specific polecat (in default/current rig)
- `gastown/Toast`: Specific rig/polecat
- `gastown/*`: All polecats in rig
- `*`: All polecats everywhere

## Implementation
```go
func expandSpecs(specs []string, awakeOnly bool) ([]*polecat.Polecat, error) {
    // Expand patterns to list of polecats
}

func runForAll(polecats []*polecat.Polecat, action func(*polecat.Polecat) error) error {
    // Run action for each, collect errors
}
```

## New File
internal/cmd/all.go

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

## Acceptance Criteria
- [ ] Pattern expansion works
- [ ] Parallel execution where safe
- [ ] Aggregate error reporting
- [ ] --awake-only filter works


Create .runtime/ for town-level runtime state

Move ephemeral town state to .runtime/:
- Create ~/gt/.runtime/ directory structure
- Move daemon/state.json → .runtime/daemon.json
- Move deacon/heartbeat.json → .runtime/deacon.json
- Move .gastown/agent-state.json → .runtime/agent-requests.json
- Move .gastown/keepalive.json → .runtime/keepalive.json
- Update .gitignore to ignore .runtime/
- Update all code references


Deacon plugin scheduler: cron-like execution

Implement scheduled plugin execution in the Deacon's plugins step.

## Concept

Plugins are just directories with config. Deacon checks which are due
and executes them during the plugins step of rounds.

## Plugin Registration

Plugins are registered in:
1. Pinned kernel bead (preferred)
2. `{townRoot}/plugins/` directory scan (fallback)

Each plugin has:
```json
{
  "name": "beads-hygiene",
  "schedule": "0 2 * * *",
  "last_run": "2025-12-20T02:00:00Z",
  "enabled": true,
  "attention_budget": "low",
  "max_duration_minutes": 30
}
```

## Schedule Format

Standard cron format: `minute hour day month weekday`
- `0 2 * * *` - 2 AM daily
- `0 4 * * 0` - 4 AM Sundays
- `*/15 * * * *` - Every 15 minutes

## Execution

During plugins step:
1. Load plugin schedules from kernel bead + directory
2. For each plugin:
   a. Parse schedule, check if due (last_run + schedule)
   b. If due, execute:
      - Read `plugins/{name}/CLAUDE.md` for context
      - Run plugin logic (inline or spawn subagent)
      - Record outcome and update last_run
3. Continue to next step

## Plugin Execution Modes

- **inline**: Deacon runs the plugin logic directly (simple, fast)
- **subagent**: Spawn a polecat-like agent for the plugin (complex, parallel)
- **mail**: Send mail to another agent to do the work (delegation)

## State Tracking

`{townRoot}/deacon/plugin-state.json`:
```json
{
  "plugins": {
    "beads-hygiene": {
      "last_run": "2025-12-20T02:00:00Z",
      "last_outcome": "success",
      "run_count": 42,
      "failure_count": 2
    }
  }
}
```

## Relation to gt-axz

This implements the execution side of the plugin architecture (gt-axz).
gt-axz defines the format; this task implements the scheduler.


request-shutdown

Send shutdown request to Witness.
Wait for termination.

The polecat is now ready to be cleaned up.
Do not exit directly - wait for Witness to kill the session.

Depends: generate-summary


Test from Mayor

This is a test message via GGT mail


gt mq submit: creates task type instead of merge-request type

## Problem

`gt mq submit` creates an issue with `type: task` but should be `type: merge-request`.

## Evidence

```
$ bd show gt-n508
gt-n508: Merge: gt-70b3
Status: open
Priority: P1
Type: task      <-- WRONG, should be merge-request
...
Description:
type: merge-request   <-- Correct type is in description, not in actual type field
```

## Impact

`gt mq list` shows empty queue because it queries for `type: merge-request`

## Fix

`gt mq submit` should set `--type merge-request` when creating the bead.


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)


Outpost integration tests: Mock and E2E testing

## Overview

Integration tests for the outpost system.

## Mock Outpost

```go
type MockOutpost struct {
    name        string
    maxWorkers  int
    spawnDelay  time.Duration
    spawnError  error
    workers     []*MockWorker
}

func (o *MockOutpost) Spawn(issue string, cfg WorkerConfig) (Worker, error) {
    if o.spawnError != nil {
        return nil, o.spawnError
    }
    time.Sleep(o.spawnDelay)
    worker := &MockWorker{id: uuid.New().String(), issue: issue}
    o.workers = append(o.workers, worker)
    return worker, nil
}
```

## Unit Tests

```go
func TestOutpostManager_SelectOutpost(t *testing.T) {
    // Test policy-based selection
}

func TestOutpostManager_SpawnWithRetry(t *testing.T) {
    // Test retry logic with transient failures
}

func TestCloudRunOutpost_CostTracking(t *testing.T) {
    // Test cost calculation and caps
}
```

## Integration Tests (with mocked Cloud Run)

```go
func TestCloudRunOutpost_Integration(t *testing.T) {
    // Start mock HTTP server
    server := httptest.NewServer(mockWorkHandler())
    defer server.Close()

    outpost := NewCloudRunOutpost(OutpostConfig{
        ServiceURL: server.URL,
    })

    worker, err := outpost.Spawn("test-issue", WorkerConfig{})
    // Assert worker created, events streamed
}
```

## E2E Tests (requires real Cloud Run)

```go
// +build e2e

func TestCloudRunOutpost_E2E(t *testing.T) {
    if os.Getenv("CLOUDRUN_SERVICE_URL") == "" {
        t.Skip("CLOUDRUN_SERVICE_URL not set")
    }
    // Test against real Cloud Run service
}
```

## Files

- `internal/outpost/mock_test.go`
- `internal/outpost/manager_test.go`
- `internal/outpost/cloudrun_test.go`
- `internal/cloudrun/integration_test.go`

## Dependencies

Depends on: gt-9a2.8 (CloudRunOutpost), gt-9a2.15 (error handling)
Lower priority - can be done after implementations work.


Patrols: Cyclic molecules for autonomous maintenance

## Vision

Patrols are **cyclic molecules** - workflow loops that give Gas Town its autonomous nervous system. While regular molecules are DAGs that terminate, patrols loop forever, performing maintenance tasks at varying cadences.

This is the "steam engine" of Gas Town: converting episodic Claude sessions into continuous autonomous operation.

## Core Concepts

### 1. Cyclic Molecules

Regular molecule:     A -> B -> C -> done
Patrol molecule:      A -> B -> C --+
                      ^             |
                      +-------------+

Patrols have a loop_to field specifying where to restart.

### 2. Cooldown-Aware Steps (Atoms)

Steps encode their own cadence. When patrol reaches a step:
1. Check: now - last_run > cooldown?
2. If yes: execute, update last_run
3. If no: skip (immediately close)

The patrol runner is simple - steps self-skip. Complexity distributed into atoms.

### 3. The Beacon

The heartbeat that fires patrol triggers:
- Internal ticker in Deacon (goroutine)
- Or external cron firing gt deacon tick
- Or mail-based triggers

Without beacon, nothing proactive happens.

### 4. Session Reset as Patrol Step

Connects to auto-handoff (gt-bcwn). Session reset is a patrol step, not a separate mechanism.

### 5. Multi-Role Patrols

Each supervisor has its own patrol:

**Deacon patrol:** health-check (30s), session-gc (5m), beacon-tick (10s)
**Witness patrol:** orphan-scan (10m), stuck-check (2m), molecule-progress (1m)
**Refinery patrol:** queue-check (30s), pr-status (1m), merge-ready (30s)

### 6. Cadence Tiers

- Critical (10-30s): Health checks
- Active (1-5m): Progress, nudges
- Maintenance (10-30m): Orphans, GC
- Periodic (1h+): Reports

### 7. Best-Effort Scheduling

Not real-time - more like cron. No hard deadlines. Catch-up, dont pile-up.
Priority preemption (mail interrupts patrol). Graceful degradation under load.

## Open Questions

1. State persistence: Beads (self-describing) or file (faster)?
2. Interruption: How does urgent mail preempt patrol?
3. Error recovery: Backoff? Escalate? Circuit breaker?
4. Coordination: Can patrols send mail to trigger other patrols?

## Related

- gt-bcwn: Auto-handoff (session reset is a patrol step)
- Molecule system (patrols extend molecules with loops)
- Deacon lifecycle management

## Metaphor

Claude was fire. Claude Code was steam. Gas Town is the steam engine. Beads is the train tracks.

The steam engine converts episodic combustion into continuous rotary motion.
Gas Town converts episodic Claude sessions into continuous autonomous work.


CLI: spawn command

High-level command to spawn a polecat with work assignment.

## Command

```
gt spawn <rig>/<polecat> --issue <id>
gt spawn <rig> --issue <id>  # auto-select available polecat
gt spawn <rig>/<polecat> -m "Work on X"  # free-form task
```

## What It Does

1. Select or create polecat
2. Ensure polecat is in idle/active state
3. Assign the issue (update beads, set polecat state)
4. Start session (gt session start)
5. Inject initial context

## Options

- `--issue <id>`: Beads issue to assign
- `-m "message"`: Free-form work description
- `--create`: Create polecat if it does not exist
- `--no-start`: Assign work but do not start session

## Auto-Select Logic

If polecat not specified:
1. Look for idle polecats in rig
2. Prefer ones that worked on similar issues before
3. If none available, fail (or create if --create)

## Initial Context

Injects into session:
```
[SPAWN] You have been assigned: <issue-title>
Issue: <id>
Priority: <priority>

<description>

Work on this issue. Signal DONE when complete.
```

## Implementation

Orchestrates PolecatManager + SessionManager + Beads:
```go
func Spawn(rig, polecat, issue string, opts SpawnOptions) error
```


gt crew add: Create crew workspace

Implement 'gt crew add <name> [--rig <rig>]' command:
- Clone repo into <rig>/crew/<name>/
- Create feature branch (optional, or stay on main)
- Register for mail delivery
- Initialize CLAUDE.md with crew worker prompting
- Do NOT register with witness (user-managed)


self-review

Review your own changes. Look for:
- Bugs and edge cases
- Style issues
- Missing error handling
- Security concerns

Fix any issues found before proceeding.

Depends: implement


gt mq list: doesn't show submitted MRs

After submitting MRs with gt mq submit, gt mq list gastown shows empty queue.

## Reproduction
1. gt mq submit --issue gt-h5n.5 --branch polecat/Scabrous
2. gt mq list gastown → (empty)
3. bd list --type=merge-request → shows the MR

## Expected
gt mq list should show submitted MRs

## MR example
gt-ts4u has rig: gastown in description, type=merge-request, status=open


loop-or-exit

Decision: burn and loop if context low, exit for respawn if context high.


Rollback support: gt mq revert for problematic merges

Implement 'gt mq revert <mr-id>' for rolling back merges.

Actions:
1. Find merge commit from closed MR
2. Create revert commit: git revert <merge-commit>
3. Push revert to target branch
4. Create 'reverted' MR tracking the revert
5. Optionally reopen source issue

Options:
- --reason REASON: required explanation
- --reopen-issue: reopen the source work item

Track revert in MR metadata for audit trail.

Reference: docs/merge-queue-design.md#open-questions


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


Merge: gt-g44u.2

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


CLI: rig commands (add, list, show, remove)

GGT needs rig management commands.

Missing Commands:
- gt rig add <git-url> [--name NAME] - Add rig to workspace
- gt rig list - List all rigs with status
- gt rig show <rig> - Show detailed rig info
- gt rig remove <rig> - Remove a rig

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

Current State:
- gt init creates rig structure but no ongoing management
- Rig discovery in internal/rig/manager.go but not exposed via CLI


Swarm: report generation

Generate markdown reports for completed swarms.

## Command
```
gt swarm report <swarm-id> [--save <file>]
```

## Report Content

### Header
- Swarm ID and title
- Created/completed timestamps
- Duration
- Rig name

### Task Summary
| Task | Assignee | Status | Duration |
|------|----------|--------|----------|
| gt-xxx | Toast | merged | 15m |
| gt-yyy | Nux | merged | 22m |

### Worker Contributions
- Commits per worker
- Issues closed per worker
- Lines changed (optional)

### Timeline
- Chronological events from events.jsonl
- Key milestones (started, first merge, landing)

### Issues Encountered
- Conflicts resolved
- Failed tasks (if any)
- Escalations

## Implementation
```go
func GenerateReport(swarmID string) (*SwarmReport, error)
func (r *SwarmReport) ToMarkdown() string
```

## Storage
- Save to <rig>/.gastown/swarms/<id>/report.md
- Or user-specified path with --save

## PGT Reference
gastown-py/src/gastown/swarm/manager.py generate_report()

## Acceptance Criteria
- [ ] Markdown report generated
- [ ] Includes all sections above
- [ ] Auto-saved to swarm directory
- [ ] --save allows custom path


CLI: gt swarm commands (create, status, list, land)

CLI commands for swarm management.

## Commands

### gt swarm create
Create a new swarm in a rig.

```
gt swarm create <rig> --title "Fix all P1 bugs" \
  --task gt-abc --task gt-def \
  --worker Toast --worker Nux
```

Options:
- --title: Swarm title (required)
- --task: Beads issue IDs to include (repeatable)
- --worker: Polecat names (repeatable, or --workers=3 to auto-assign)
- --start: Start swarm immediately after creation

### gt swarm status <swarm-id>
Show swarm status and progress.

Output:
- Swarm metadata (title, created, workers)
- Task status (pending/in_progress/completed/merged)
- Integration branch status
- Overall progress percentage

### gt swarm list [rig]
List swarms, optionally filtered by rig or status.

```
gt swarm list --status=active
gt swarm list gastown --status=landed
```

### gt swarm land <swarm-id>
Trigger manual landing (normally automatic).

### gt swarm cancel <swarm-id>
Cancel a swarm in progress.

## Output Format

Support --json for machine-readable output.

## Dependencies

Needs: gt-kmn.1 (types), gt-kmn.2 (manager)


Refinery agent: merge queue processing loop

The Refinery agent processes the merge queue, merging polecat work to main.

## Interface

```go
type Refinery struct {
    rig    *Rig
    queue  *MergeQueue
    git    *Git
    mail   *Mailbox
    config RefineryConfig
}

type RefineryConfig struct {
    AutoMerge     bool          // Auto-merge passing MRs
    RunTests      bool          // Run tests before merge
    TestCommand   string        // Command to run tests
    RequireReview bool          // Require review before merge (future)
}

func NewRefinery(rig *Rig, ...) *Refinery

// Lifecycle
func (r *Refinery) Start() error
func (r *Refinery) Stop() error
func (r *Refinery) IsRunning() bool

// Processing
func (r *Refinery) ProcessQueue() error
func (r *Refinery) ProcessMR(mr *MergeRequest) error
```

## Processing Loop

1. Check queue for pending MRs
2. For each pending MR:
   a. Fetch polecat branch
   b. Attempt merge to refinery/rig (local main)
   c. Run tests if configured
   d. If pass: push to origin, mark merged
   e. If fail: mark rejected, notify polecat
3. Sleep, repeat

## Merge Strategy

- Fast-forward when possible
- Merge commit when not
- On conflict: reject MR, polecat must rebase

## Test Integration

If tests configured:
```bash
cd refinery/rig
git merge polecat/branch
<test_command>  # e.g., go test ./...
```
Result determines merge/reject.

## Notifications

On merge success:
- Mail to polecat: "Your work merged"
- Update bead if issue tracked

On merge failure:
- Mail to polecat: "Merge failed: <reason>"
- Include conflict details if applicable

## Dependencies

Needs: Rig management, Git wrapper, Mail system, Merge queue


Add molecules.jsonl as separate catalog file for template molecules


gt mail read: Support numeric indices for message ID

Allow 'gt mail read 1' to read the first message in inbox.

Current behavior requires full message ID like 'msg-abc123'.
Should support:
- Numeric index: 'gt mail read 1' reads first/newest message
- Partial ID match: 'gt mail read abc' matches 'msg-abc123'

This is a UX improvement - agents frequently type 'gt mail read 1'.


Phase 1.1: Attachment field on pinned beads

Add attached_molecule field to pinned bead schema.

## Schema Change
Add to handoff/pinned bead issues:
- attached_molecule: string (root issue ID of attached mol)
- attached_at: timestamp

## Implementation
- Update beads Issue struct if needed
- Or: use labels/metadata field
- Ensure bd show displays attachment info

## Acceptance
- Can set/clear attachment on a bead
- bd show displays attachment status


detectSender() doesn't recognize crew workers

## Problem

`detectSender()` in mail.go only detects polecats, not crew workers.

## Current Code (line 445)

```go
// If in a rig's polecats directory, extract address
if strings.Contains(cwd, "/polecats/") {
    // extract rig/polecat
}

// Default to mayor
return "mayor/"
```

## Symptom

Emma (crew worker at `/Users/stevey/gt/beads/crew/emma`) runs:
- `gt mail inbox` → checks `mayor/` inbox (wrong!)
- Should check `beads/emma` or `beads/crew/emma`

## Fix

Add crew detection:
```go
// If in a rig's crew directory, extract address
if strings.Contains(cwd, "/crew/") {
    parts := strings.Split(cwd, "/crew/")
    if len(parts) >= 2 {
        rigPath := parts[0]
        crewName := strings.Split(parts[1], "/")[0]
        rigName := filepath.Base(rigPath)
        return fmt.Sprintf("%s/%s", rigName, crewName)
    }
}
```

## Also Check

- `gt prime` correctly detects role, so there may be another detection function that works
- Should unify detection logic


Deacon keepalive: quiet daemon during rounds

Replace tmux-poke interrupts with a keepalive file the daemon respects.

## Problem

Current daemon pokes Deacon via tmux SendKeys every ~5 minutes if heartbeat
is stale. This can INTERRUPT the Deacon mid-work, especially during long
plugin execution or health remediation.

## Solution

Deacon writes a keepalive file at start of rounds:
```json
{
  "timestamp": "2025-12-20T15:30:00Z",
  "expected_duration_minutes": 10,
  "action": "executing mol-deacon-rounds"
}
```

Daemon reads this and backs off:
- If keepalive is fresh AND within expected_duration: skip poke
- If keepalive is stale OR past expected_duration: poke (fallback)

## Files

- `{townRoot}/deacon/keepalive.json` - Written by Deacon
- Daemon already reads `{townRoot}/deacon/heartbeat.json` - similar pattern

## Deacon Behavior

1. Start of rounds: write keepalive with expected duration
2. End of rounds: write heartbeat (existing), clear/update keepalive
3. If rounds take longer than expected: Deacon gets one poke, squelches it

## Daemon Changes

Modify `pokeDeacon()` in daemon.go:
1. Check keepalive.json freshness and expected_duration
2. If within bounds: skip poke, log "Deacon active"
3. If past bounds: poke as fallback

## Worst Case

Deacon crashes mid-rounds without clearing keepalive:
- Daemon waits until expected_duration expires
- Then pokes, triggering respawn loop
- Max delay: expected_duration (configurable, default 10-15 min)

## Configuration

In pinned kernel bead:
```yaml
config:
  rounds_expected_duration_minutes: 10
```


Update Mayor prompting in gastown-py

The Mayor CLAUDE.md and related prompting in gastown-py (still in production use) needs to reflect current design decisions: session cycling, handoff protocol, cleanup responsibilities, beads access model. Sync prompting with GGT design work.


Merge: gt-svi.1

type: merge-request
branch: polecat/Max
target: main
source_issue: gt-svi.1
rig: gastown


Test Epic: GGT MVP Validation


Refactor: Eliminate state.json, use beads assignee field for polecat state

## Problem

state.json in each polecat worktree duplicates data that should be in beads:
- `issue` field duplicates assignment (should be issue.assignee)
- `state` field duplicates status (derivable from issue.status)
- `name/rig/branch/clone_path` are all derivable from filesystem/git

This violates 'Beads as the data plane' (HOP Decision 001).

## Current State

```json
{
  "name": "Angharad",        // Derivable: basename(cwd)
  "rig": "gastown",          // Derivable: parent dir name
  "state": "working",        // Should be: issue.status
  "clone_path": "/path",     // Derivable: cwd
  "branch": "polecat/X",     // Derivable: git branch
  "issue": "gt-xyz",         // Should be: issue.assignee
  "timestamps": "..."        // Should be in beads history
}
```

## Target State

**Polecat identity**: Derived from worktree path
**Polecat assignment**: `issue.assignee = 'gastown/Angharad'`
**Polecat state**: Derived from issue.status (in_progress = working, closed = done)
**Polecat existence**: Worktree directory exists

## Benefits

1. Single source of truth (no sync issues)
2. Queryable: `bd list --assignee=gastown/Angharad`
3. History tracking via beads
4. Cross-agent coordination via beads sync
5. Simpler code (no state file management)

## Implementation

1. Update `gt spawn` to set issue.assignee instead of writing state.json
2. Update polecat list/status commands to query beads
3. Update Witness to query beads for polecat state
4. Remove state.json read/write code
5. Keep state.json as optional bootstrap cache (perf optimization only)


Add mayor/config.json for town-level configuration

Create new town-level config file:
- Add TownConfig.Config field to types.go
- Create mayor/config.json with theme defaults, daemon settings
- Update loader to read/write mayor/config.json
- Migrate any town-level config from .gastown/ to here


Test: Alpha to Beta

Sibling communication test


Refinery semantic merge processing loop

Implement the Refinery semantic merge loop for processing polecat work.

## Overview

The Refinery is a fully empowered Claude Code agent that:
1. Fetches polecat branches
2. Merges to integration branch
3. Resolves conflicts semantically (re-implements if needed)
4. Runs tests
5. Escalates only when truly stuck

## Processing Loop

1. Check queue for completed tasks (polecat done, not yet merged)
2. For each task:
   - Fetch polecat branch
   - Attempt merge to integration/<swarm-id>
   - On conflict: semantic resolution (understand intent, re-implement)
   - Run tests if configured
   - On success: mark merged, file bead, notify witness
   - On failure: notify polecat (test fail) or escalate (stuck)
3. When all tasks merged: land integration to main

## Semantic Merge

When git merge fails, Refinery:
1. Reads polecat changes and beads issue
2. Understands intent
3. Re-implements on current integration branch
4. Only escalates if truly blocked

## Daemon Poking

Refinery is poked by:
- Daemon heartbeat (periodic)
- Witness notification (polecat done)
- Mayor request (check queue)

## Dependencies

Needs: gt-u1j.6 (mail), gt-u1j.3 (git), gt-u1j.14 (merge queue)

## Reference

See PGT ephemeral.py for branch operations.


Patrol step: check parked molecules for unblock

Add patrol step to Deacon for checking parked molecules:

```yaml
- step: check-parked-molecules
  action: |
    For each molecule with:
      - status: in_progress
      - assignee: null
      - has step with external: blocked_by
    Check if external deps are now satisfied.
    If yes: spawn polecat to resume the molecule.
```

This automates the resume process - no manual intervention needed when
upstream dependencies ship.

Part of cross-project dependency system.
See: docs/cross-project-deps.md

Depends on: gt-in3x (spawn --continue)
Priority: P3 (future automation, not required for launch)


P0: Multiple agents can claim same worker identity

Multiple Claude Code sessions running simultaneously all think they are 'dave' on beads/crew/dave. No detection or prevention of identity collision. This breaks the single-agent-per-worker assumption.

## Fix Implemented

1. **Lock Package** (`internal/lock/lock.go`):
   - PID-based lockfile at `<worker>/.gastown/agent.lock`
   - Contains PID, timestamp, session ID, hostname
   - Stale lock detection (checks if owning PID is dead)

2. **Prevention in gt prime**:
   - Workers (crew/polecat) acquire identity lock before loading context
   - If another live process holds the lock, prime fails with clear error
   - Shows lock holder details and resolution steps

3. **Detection with gt agents**:
   - `gt agents check` - scans for collisions and stale locks
   - `gt agents fix` - cleans stale locks
   - JSON output available for patrol tooling

4. **Correction in gt doctor**:
   - New `identity-collision` check
   - `gt doctor --fix` cleans stale locks


CLI: session commands (start, stop, at, list, capture, inject)

Session management CLI commands.

## Commands

### gt session start
Start a polecat session.
```
gt session start <rig>/<polecat> [--issue <id>]
```
- Creates tmux session
- Launches claude in polecat workdir
- Optionally injects initial issue context

### gt session stop
Stop a polecat session.
```
gt session stop <rig>/<polecat> [--force]
```
- Graceful shutdown by default
- --force kills immediately

### gt session at (attach)
Attach to running session.
```
gt session at <rig>/<polecat>
```
- Attaches tmux session to current terminal
- Detach with Ctrl-B D

### gt session list
List all sessions.
```
gt session list [--rig <rig>] [--json]
```
- Shows running/stopped status
- Optionally filter by rig

### gt session capture
Capture recent output from session.
```
gt session capture <rig>/<polecat> [--lines <n>]
```
- Default: last 100 lines
- Useful for checking polecat progress

### gt session inject
Send message to session.
```
gt session inject <rig>/<polecat> -m "message"
gt session inject <rig>/<polecat> -f <file>
```
- Injects text via tmux send-keys
- Used for nudges, notifications

## Address Format

`<rig>/<polecat>` e.g., `wyvern/Toast`

## Implementation

Session subcommand group:
```go
var sessionCmd = &cobra.Command{Use: "session"}
sessionCmd.AddCommand(startCmd, stopCmd, atCmd, listCmd, captureCmd, injectCmd)
```


Mayor handoff mail template

Add MAYOR_HANDOFF mail template to templates.py.

## Template Function

def mayor_handoff(
    active_swarms: List[SwarmStatus],
    rig_status: Dict[str, RigStatus],
    pending_escalations: List[Escalation],
    in_flight_decisions: List[Decision],
    recent_actions: List[str],
    delegated_work: List[DelegatedItem],
    user_requests: List[str],
    next_steps: List[str],
    warnings: Optional[str] = None,
    session_duration: Optional[str] = None,
) -> Message:
    metadata = {
        'template': 'MAYOR_HANDOFF',
        'timestamp': datetime.utcnow().isoformat(),
        'session_duration': session_duration,
        'active_swarm_count': len(active_swarms),
        'pending_escalation_count': len(pending_escalations),
    }
    # ... format sections ...
    return Message.create(
        sender='mayor/',
        recipient='mayor/',
        subject='Session Handoff',
        body=body,
        priority='high',
    )

## Metadata Fields

- template: MAYOR_HANDOFF
- timestamp: ISO format
- session_duration: Human readable
- active_swarm_count: Number of active swarms
- pending_escalation_count: Number of escalations

## Mail Priority

Use priority='high' to ensure handoff is seen on startup.


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


TODO: Check if issue is child of configured epic in Witness

witness/manager.go:688 has a TODO to filter issues by whether they're children of the configured epic. Currently this filter is skipped.


gt rig reset --stale: Clear orphaned in_progress items

Reset in_progress issues when their assigned agent no longer exists.

## Problem
When polecats die without cleanup, their issues remain in_progress forever.
Need a way to bulk-reset these orphaned items.

## Command
```bash
gt rig reset --stale [--dry-run]
```

## Logic
For each in_progress issue in rig:
1. Parse assignee (e.g., "gastown/furiosa")
2. Map to tmux session name (gt-gastown-furiosa)
3. If session does NOT exist:
   - Reset status to "open"
   - Clear assignee
4. Exception: skip crew/* assignees (persistent identities)
   OR check if crew tmux session exists

## Output
```
Resetting stale work in gastown:
  gt-abc: gastown/furiosa (no session) → open
  gt-def: gastown/nux (no session) → open
  Skipped: gt-xyz: gastown/crew/max (persistent)
Reset 2 issues, skipped 1
```

## Related
- gt-2kz: CLI cleanup commands for stale state
- gt-rdmw: orphan-check in deacon patrol
- gt-orphans command (list orphaned molecules)


Implement pinned beads for handoff state

Add pinned bead support to beads:
- Pinned beads never close, only update
- Use for persistent state like Refinery handoffs
- bd create --pinned flag
- bd list --pinned to find them
- Update description to change state


gt mail: Cross-level routing is broken

When Mayor sends mail to rig worker, message lands in wrong beads database. Sender's beads vs recipient's.


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.


gt witness: Witness management commands

Add 'gt witness' command group for witness lifecycle management.

Subcommands:
- gt witness start [rig] - Start witness for a rig
- gt witness stop [rig] - Stop witness
- gt witness status [rig] - Show witness status
- gt witness attach [rig] - Attach to witness session

Witness monitors polecats and handles:
- Idle detection and cleanup
- Session health checks
- Nudging stuck agents


Bug: LIFECYCLE messages use literal <rig> placeholder

Found stale LIFECYCLE messages addressed to @<rig>/witness instead of actual rig names like @gastown/witness. Template substitution not happening.

Examples found:
- gm-bay: LIFECYCLE: polecat requesting shutdown (to @<rig>/witness)
- gm-7du, gm-94e, gm-u5x, gm-89b: same issue

Fix: Ensure rig name is substituted in lifecycle mail templates.


Work assignment mail not received by polecat

When gt spawn sends a work assignment:

1. gt spawn says '✓ Work assignment sent'
2. Polecat runs 'gt mail inbox'
3. Shows '0 messages, 0 unread'

The work assignment mail never arrived at the polecat's inbox.

## Observed in session
- Spawned polecat dementus on gt-th7
- Polecat checked inbox: empty
- Polecat couldn't find issue (separate sync bug)

## Possible causes
- Mail routing issue for polecat addresses
- gt spawn not actually sending mail
- Mail sent to wrong address format

This was likely caused by stale beads in old polecats. With gt-9nf (fresh polecats), polecats now use shared rig beads via redirect file, eliminating sync issues. Mail routing uses town beads correctly.


Design GGT prompt architecture

Audit PGT prompts and design canonical prompt system for GGT. Create docs/prompts.md with inventory, gap analysis, and Witness prompt design.


Phase 4.2: Witness/Refinery patrol molecules

Extend ephemeral patrol pattern to Witness and Refinery.

## Witness Patrol Cycle

mol-witness-patrol-cycle:
1. scan-polecats: Check each polecat status
2. detect-stalls: Find stuck/idle polecats
3. nudge-or-escalate: Take action on stalls
4. log-status: Record cycle results
→ Squash with summary

## Refinery Patrol Cycle

mol-refinery-patrol-cycle:
1. scan-queue: Check merge queue
2. process-ready: Merge ready MRs
3. handle-failures: Deal with conflicts/failures
4. log-status: Record cycle results
→ Squash with summary

## Quiescent Mode

When no work: just log 'no activity' summary.
Witness and Refinery can sleep between cycles until triggered.


gt install command: Create workspace structure

Create Gas Town workspace structure.

## Command

```
gt install [path]
```

If path omitted, uses current directory.

## Created Structure

```
<path>/
├── config/
│   ├── town.json      # {"type": "town", "version": 1, ...}
│   └── rigs.json      # {"version": 1, "rigs": {}}
│
└── mayor/
    ├── CLAUDE.md      # Mayor role prompting (from template)
    ├── mail/
    │   └── inbox.jsonl  # Empty inbox
    └── state.json     # Initial mayor state
```

## Implementation

```go
func Install(path string, opts InstallOptions) error

type InstallOptions struct {
    TownName string  // defaults to directory name
    Force    bool    // overwrite existing
}
```

## Steps

1. Validate path (exists, writable)
2. Check not already a town (unless --force)
3. Create config/ directory
4. Write town.json with name and timestamp
5. Write empty rigs.json
6. Create mayor/ directory structure
7. Write CLAUDE.md from template
8. Create empty inbox.jsonl
9. Write initial state.json

## Error Cases

- Path does not exist: Create it (like mkdir -p)
- Already a town: Error unless --force
- Permission denied: Clear error message
- Inside existing town: Warn (nested towns not recommended)

## Templates

Mayor CLAUDE.md comes from embedded template (see gt-u1j.20).


Polecat management: add, remove, list, state

Polecat lifecycle: add, remove, list, state tracking.

## Data Model

```go
type Polecat struct {
    Name      string          `json:"name"`
    Rig       string          `json:"rig"`
    State     PolecatState    `json:"state"`
    ClonePath string          `json:"clone_path"`
    Branch    string          `json:"branch"`
    Issue     string          `json:"issue,omitempty"`
    CreatedAt time.Time       `json:"created_at"`
    UpdatedAt time.Time       `json:"updated_at"`
}

type PolecatState string
const (
    StateIdle       PolecatState = "idle"
    StateActive     PolecatState = "active"
    StateWorking    PolecatState = "working"
    StateDone       PolecatState = "done"
    StateStuck      PolecatState = "stuck"
)
```

## Interface

```go
type PolecatManager struct {
    rig *Rig
    git *Git
}

func NewPolecatManager(rig *Rig, git *Git) *PolecatManager

// Lifecycle
func (pm *PolecatManager) Add(name string) (*Polecat, error)
func (pm *PolecatManager) Remove(name string) error
func (pm *PolecatManager) List() ([]*Polecat, error)
func (pm *PolecatManager) Get(name string) (*Polecat, error)

// State
func (pm *PolecatManager) SetState(name string, state PolecatState) error
func (pm *PolecatManager) AssignIssue(name, issue string) error
func (pm *PolecatManager) ClearIssue(name string) error

// Convenience
func (pm *PolecatManager) Wake(name string) error   // idle → active
func (pm *PolecatManager) Sleep(name string) error  // active → idle
```

## Add Flow

1. Create directory: `<rig>/polecats/<name>/`
2. Clone rig repo into it (or copy from rig root)
3. Create new branch: `polecat/<name>`
4. Initialize polecat state file
5. Create mail inbox

## Remove Flow

1. Verify session not running
2. Check for uncommitted changes (warn/error)
3. Remove directory
4. Clean up state

## State Persistence

Store in `<rig>/polecats/<name>/state.json`


Add agent abstraction layer to support non-Claude agents

Currently Gas Town hardcodes 'claude --dangerously-skip-permissions' throughout the codebase for spawning agents. We should add an abstraction layer to support other AI agents (e.g., Gemini CLI, OpenAI agents, local models).

Locations that spawn Claude:
- internal/cmd/mayor.go:131
- internal/cmd/deacon.go:150
- internal/cmd/witness.go:280
- internal/cmd/crew.go (multiple locations)
- internal/cmd/up.go:190, 229
- internal/session/manager.go:146
- internal/refinery/manager.go:207

Suggested approach:
1. Create an agent package with an interface
2. Add configuration for agent type in town/rig config
3. Replace hardcoded claude commands with agent.Spawn() calls
4. Support agents: claude, gemini, openai, local (ollama, etc.)


gt shutdown should fully cleanup polecats (worktrees, branches, inboxes)

Current gt shutdown only kills tmux sessions but leaves:

1. Git worktrees in polecats/ directory
2. Polecat branches (polecat/<name>)
3. Inbox messages
4. Beads assignee state

This causes stale polecats to be reused on next startup.

## Expected Behavior

gt shutdown should:
1. Kill all polecat tmux sessions (current behavior)
2. For each polecat with StateIdle or StateDone:
   - Remove worktree
   - Delete branch
   - Clear inbox
3. For polecats with uncommitted work:
   - REFUSE to clean up
   - Report which polecats have uncommitted changes
   - Require --force or --nuclear to proceed

## Current Code

- start.go:runImmediateShutdown() only calls t.KillSession()
- Witness cleanupPolecat() does full cleanup but isn't called by shutdown

## Fix

Either:
1. Call Witness cleanup for each polecat during shutdown
2. OR add rig.ShutdownPolecats() that does the full cleanup


Crew worker CLAUDE.md prompting

Create CLAUDE.md template for crew workers:
- Explain crew worker role (overseer's personal workspace)
- Include mail-to-self handoff instructions
- Document gt crew refresh for context cycling
- Explain no witness monitoring (user-managed)
- Include beads usage if BEADS_DIR configured


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

Polecat management CLI commands.

## Commands

### gt polecat list
List polecats in a rig.
```
gt polecat list <rig> [--json]
gt polecat list --all [--json]
```
Output:
- Name
- State (idle/active/working/done/stuck)
- Current issue (if any)
- Session status (running/stopped)

### gt polecat add
Add a new polecat to a rig.
```
gt polecat add <rig> <name>
```
- Creates polecat directory
- Clones rig repo
- Creates work branch
- Initializes state

### gt polecat remove
Remove a polecat from a rig.
```
gt polecat remove <rig>/<polecat> [--force]
```
- Fails if session running
- Warns if uncommitted changes
- --force bypasses checks

### gt polecat wake
Mark polecat as active (ready for work).
```
gt polecat wake <rig>/<polecat>
```
Transitions: idle → active

### gt polecat sleep
Mark polecat as idle (not available).
```
gt polecat sleep <rig>/<polecat>
```
Transitions: active → idle
Fails if session running (stop first).

## Address Format

`<rig>/<polecat>` for operations on specific polecat.
`<rig>` for list command.

## Implementation

```go
var polecatCmd = &cobra.Command{Use: "polecat"}
polecatCmd.AddCommand(listCmd, addCmd, removeCmd, wakeCmd, sleepCmd)
```


gt spawn --continue for resuming parked molecules

Add `--continue` flag to gt spawn for resuming parked molecules:

```bash
gt spawn --continue gt-mol-root
```

This:
1. Finds molecule root
2. Verifies molecule is in "parked" state (in_progress, no assignee)
3. Checks external deps are now satisfied
4. Spawns polecat with molecule context
5. Polecat reads handoff mail and continues from blocked step

Part of cross-project dependency system.
See: docs/cross-project-deps.md

Depends on: gt-zniu (gt park)
Depends on Beads: bd-zmmy (bd ready resolution)


Heartbeat convention: simple liveness signal for agents

Lightweight liveness signal extracted from Deacon epic (gt-5af).

**Implementation**: Each agent writes a timestamp file on activity:
```bash
echo '{"ts":"'$(date -Iseconds)'"}' > ~/gt/<role>/heartbeat.json
```

**Integration points**:
- SessionStart hook writes heartbeat
- Periodic activity (mail check, work completion) refreshes it
- `gt status` shows staleness (e.g., 'mayor: 5m ago')

**Weight**: ~5 lines per agent
**Value**: Quick debugging - see which agents are active at a glance

No monitoring daemon needed - human checks `gt status` when curious.


Test7

Test with fixed workdir


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.


Merge: gt-xw7b

branch: polecat/morsov
target: main
source_issue: gt-xw7b
rig: gastown


Implement gt mol status command

Show what's on an agent's hook.

```bash
gt mol status [target]
```

Output:
- What's slung (molecule name, associated issue)
- Current phase and progress
- Whether it's a wisp
- Next action hint

If no target, shows current agent's status.

Acceptance:
- [ ] Read pinned bead attachment
- [ ] Display molecule/issue info
- [ ] Show phase progress
- [ ] Indicate wisp vs durable


Create Refinery role template

Add Refinery template to internal/templates/roles/:
- refinery.md.tmpl with full role context
- Variables: rig name, working directory, handoff bead ID
- Update SeedRoleTemplates to include it
- gt prime uses this template for Refinery context


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)


Swarm: ephemeral rig support

PGT has ephemeral rigs for swarms - temporary worker groups that are destroyed after landing.

Missing Features:
- gt swarm init [--rig <name>|--git-url <url>] [--num-workers N]
- gt swarm worker add/remove/list <rig-id>
- gt swarm rigs - List ephemeral rigs
- gt swarm destroy <rig-id> - Destroy ephemeral rig

Directory structure:
<workspace>/mayor/workers/<rig-id>/
├── rig.json (metadata)
├── alice/ (git clone)
├── bob/
└── carol/

PGT Reference: gastown-py/src/gastown/ephemeral.py

Note: Beads issue gt-kmn.12 mentions this but implementation is missing.


gt done: Command doesn't exist but documented in polecat CLAUDE.md

The polecat CLAUDE.md documents 'gt done' as the command to signal work is ready for merge queue, but running it gives 'unknown command'. Either implement the command or update the documentation.


Bug: refinery --foreground detects parent session as already running

When gt refinery start gastown runs:
1. Creates tmux session gt-gastown-refinery
2. Sends 'gt refinery start gastown --foreground' into the session
3. The foreground command checks HasSession() - finds the session it's inside
4. Returns 'already running' error

The foreground mode check should either:
- Skip the tmux session check (only check PID)
- Use a different indicator that the daemon loop is running
- Pass a flag to indicate we're being called from the background starter

Workaround: Manually run 'gt refinery start gastown --foreground' from a fresh session.


Design: Collapse mail into beads

## Proposal

Replace the separate mail system (JSONL inboxes) with beads issues using a naming convention.

## Rationale

If all state should be in beads (work items, swarm state, dependencies), why have a separate system for messages? Mail is just:
- Handoffs (agent → self)
- Commands (Mayor → Refinery)
- Escalations (Witness → Mayor)

These can all be beads issues with a special prefix.

## Design

### Convention
- Messages use `@-` prefix: `@-witness-1734012345`
- Assignee = recipient
- Status: open = unread, closed = read/acknowledged
- Priority 0 = urgent

### Commands (thin wrappers)
```bash
gt mail send witness -s "Subject" -m "Body"
  → bd create --prefix=@ --title="Subject" --assignee=witness --description="Body"

gt mail inbox
  → bd list --prefix=@ --assignee=$(gt whoami) --status=open

gt mail read @-abc
  → bd show @-abc && bd close @-abc
```

### Notification
Daemon watches for new `@-` issues and pokes relevant sessions.
Or: agents poll on heartbeat (simpler).

## What We Remove
- `mail/inbox.jsonl` files
- Mail JSONL read/write code
- Separate delivery mechanism

## What We Keep
- `gt mail` CLI (as wrapper)
- Handoff semantics
- Notification (via daemon or polling)

## Benefits
- One system, one sync, one query interface
- All communication in git history
- Simpler architecture

## Risks
- Beads prefix filtering must be efficient
- Namespace collision with user prefixes
- Performance for high-frequency messages (probably fine for handoffs)

## Decision Point
Do we need first-class mail support in beads (`bd mail` commands) or is convention sufficient?


Add Deacon heartbeat mechanism

Deacon writes deacon/heartbeat.json on each wake. Go daemon reads it to decide whether to poke. Add heartbeat read/write helpers.


CloudRun worker container: Dockerfile and entrypoint

## Overview

Docker container image for Cloud Run workers.

## Dockerfile

```dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o gt ./cmd/gt

FROM ubuntu:22.04

# Install Claude Code
RUN apt-get update && apt-get install -y     curl git nodejs npm     && npm install -g @anthropic-ai/claude-code     && apt-get clean

# Install gt
COPY --from=builder /app/gt /usr/local/bin/gt

# Worker entrypoint
COPY deploy/cloudrun/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
```

## Entrypoint

```bash
#!/bin/bash
git config --global user.name "$GIT_USER"
git config --global user.email "$GIT_EMAIL"
exec gt worker serve --port 8080
```

## Files

- `deploy/cloudrun/Dockerfile`
- `deploy/cloudrun/entrypoint.sh`
- `deploy/cloudrun/README.md`

## Dependencies

Depends on: gt-9a2.11 (HTTP server - gt worker serve)


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


Merge: gt-unrd

branch: polecat/Ace
target: main
source_issue: gt-unrd
rig: gastown


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


Crew workers getting wrong CLAUDE.md (shows Refinery)

## Problem

Crew workers (emma, dave) have CLAUDE.md that says they're the Refinery.

## Evidence

```
$ head -5 /Users/stevey/gt/beads/crew/emma/CLAUDE.md
# Claude: Beads Refinery
You are the **Refinery** for the **beads** rig...
```

## Expected

Should use crew.md.tmpl which correctly says:
```
# Crew Worker Context
You are a **crew worker** - the overseer's (human's) personal workspace...
```

## Impact

- Crew workers have wrong identity in static context
- `gt prime` correctly outputs crew context, but CLAUDE.md conflicts
- Confusing role information

## Fix

Check `gt crew create` or whatever populates CLAUDE.md for crew workers.


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.


gt handoff: Deadlock bug in runHandoff

Running 'gt handoff' from a polecat causes a deadlock:

Stack trace shows:
- goroutine 1 [select (no cases)] in runHandoff
- File: internal/cmd/handoff.go:125

The command successfully sends the shutdown request but then hangs with 'fatal error: all goroutines are asleep - deadlock!'


inbox-check

Handle callbacks from agents. Check gt mail inbox, process lifecycle requests.


/restart: Personal slash command for in-place agent restart

Create ~/.claude/commands/restart.md that restarts current Gas Town agent in place.

## Detection
- Read tmux session name: gt-mayor, gt-witness-*, gt-refinery-*, gt-polecat-*
- Fallback: check GT_ROLE env var

## Behavior by role
- mayor: gt mayor restart (sends Ctrl-C, loop respawns)
- witness: gt witness restart
- refinery: gt refinery restart
- polecat: gt polecat restart (or witness-mediated)

## Command format
Uses backticks for inline bash to detect context, then instructs Claude to run appropriate restart.


Merge: gt-3x1.4

branch: polecat/Nux
target: main
source_issue: gt-3x1.4
rig: gastown


Remove stale migration docs from gastown-py

The gastown-py repo has migration-related documentation that is now misinformation since we have made design decisions. Remove or clearly mark as obsolete: any docs about migration paths, old architecture assumptions, or superseded designs.


Refinery startup: Use Claude agent instead of foreground mode

WIP found in stash: Refactor refinery startup to spawn Claude as the refinery agent rather than using gt refinery start --foreground.

Changes needed:
- Use refineryDir (refinery/rig) as working directory
- Start Claude with --dangerously-skip-permissions
- Wait for shell ready, then Claude ready
- Send gt prime, then refinery startup prompt
- Remove foreground mode complexity

Related: gt-n7z7 (refinery --foreground race condition bug)


Test message

Testing GGT mail via beads


mol-polecat-work

Full polecat lifecycle from assignment to decommission.

This proto enables nondeterministic idempotence for polecat work.
A polecat that crashes after any step can restart, read its molecule state,
and continue from the last completed step. No work is lost.

Variables:
- gt-test123 - The source issue ID being worked on


implement

Implement the solution for gt-qwyu. Follow codebase conventions.
File discovered work as new issues with bd create.

Make regular commits with clear messages.
Keep changes focused on the assigned issue.

Depends: load-context


Merge: gt-5wb7

branch: polecat/nux
target: main
source_issue: gt-5wb7
rig: gastown


Test message

Testing GGT mail system


Merge: gt-h5n.5

branch: polecat/Scabrous
target: main
source_issue: gt-h5n.5
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.


Town root .beads has schema mismatch with bd

The .beads directory at town root (/Users/stevey/gt/.beads) has an incompatible schema:

```
Error: failed to open database: failed to initialize schema: sqlite3: SQL logic error: no such column: thread_id
```

Meanwhile, gastown/.beads (symlinked to mayor/rig/.beads) works fine.

## Impact

- gt mail inbox fails at town root
- gt handoff sends mail to broken db
- Daemon can't check its inbox

## Options

1. Delete town root .beads/beads.db and let it recreate
2. Symlink town root .beads to gastown/.beads
3. Run schema migration on existing db

## Root Cause

Likely a beads version upgrade that added thread_id column, but the town root db was created before that.


Engineer main loop: poll for ready merge-requests

Implement the Engineer's main processing loop.

Loop structure:
1. Query: bd ready --type=merge-request
2. If empty: sleep(poll_interval), continue
3. Select highest priority, oldest MR
4. Claim: bd update <id> --status=in_progress
5. Process (delegate to other subtasks)
6. Repeat

Configuration:
- poll_interval: from rig config (default 30s)
- max_concurrent: from rig config (default 1)

The loop should be interruptible and handle graceful shutdown.

Reference: docs/merge-queue-design.md#engineer-processing-loop


Per-rig beads repo configuration

Add per-rig beads configuration to rig config schema.

## Config Schema

In each rig's config.json:

```json
{
  "version": 1,
  "name": "wyvern",
  "git_url": "https://github.com/steveyegge/wyvern",
  "beads": {
    "repo": "local",    // "local" | "<path>" | "<git-url>"
    "root": null,       // Override bd --root (optional)
    "prefix": "wyv"     // Issue prefix for this rig
  }
}
```

## Repo Options

| Value | Meaning | Use Case |
|-------|---------|----------|
| `"local"` | Use project's `.beads/` | Own projects, full commit access |
| `"<path>"` | Use beads at path | OSS contributions |
| `"<git-url>"` | Clone and use repo | Team shared beads |

## Environment Injection

When spawning polecats, Gas Town sets:
```bash
export BEADS_ROOT="<resolved-path>"
```

## Resolution Logic

```go
func ResolveBeadsRoot(rigConfig *RigConfig, rigPath string) (string, error) {
    beads := rigConfig.Beads
    switch {
    case beads.Root != "":
        return beads.Root, nil
    case beads.Repo == "local" || beads.Repo == "":
        return filepath.Join(rigPath, ".beads"), nil
    case strings.HasPrefix(beads.Repo, "/"):
        return beads.Repo, nil
    case strings.Contains(beads.Repo, "://"):
        return cloneAndResolve(beads.Repo)
    default:
        return filepath.Join(rigPath, beads.Repo), nil
    }
}
```

## Backwards Compatibility

If `beads` section missing, assume `"repo": "local"`.


CloudRun cost tracking: Usage monitoring and limits

## Overview

Track Cloud Run usage costs and enforce spending limits.

## Cost Model

Cloud Run pricing (approximate):
- CPU: ~$0.000024/vCPU-second
- Memory: ~$0.0000025/GiB-second
- Requests: ~$0.40/million

For 5-minute worker (2 vCPU, 4GB):
- CPU: 300 × 2 × $0.000024 = $0.0144
- Memory: 300 × 4 × $0.0000025 = $0.003
- **Total: ~$0.017 per session**

## Implementation

```go
type CostTracker struct {
    cpuRate    float64  // per vCPU-second
    memRate    float64  // per GiB-second
    sessions   []SessionCost
    mu         sync.RWMutex
}

type SessionCost struct {
    WorkerID  string
    StartTime time.Time
    EndTime   time.Time
    CPUs      float64
    MemoryGiB float64
}

func (t *CostTracker) RecordSession(workerID string, start, end time.Time, cpus, mem float64) {
    t.mu.Lock()
    defer t.mu.Unlock()
    t.sessions = append(t.sessions, SessionCost{...})
}

func (t *CostTracker) CurrentCost() float64 {
    t.mu.RLock()
    defer t.mu.RUnlock()
    var total float64
    for _, s := range t.sessions {
        duration := s.EndTime.Sub(s.StartTime).Seconds()
        total += duration * s.CPUs * t.cpuRate
        total += duration * s.MemoryGiB * t.memRate
    }
    return total
}
```

## Cost Cap Enforcement

```go
func (o *CloudRunOutpost) Spawn(...) (Worker, error) {
    if o.costCap > 0 && o.costTracker.CurrentCost() >= o.costCap {
        return nil, ErrCostCapExceeded
    }
    // ... spawn worker
}
```

## Config

```yaml
outposts:
  - name: cloudrun-burst
    type: cloudrun
    cost_cap_hourly: 5.00   # Stop spawning if hourly cost exceeds $5
    cost_cap_daily: 50.00   # Daily limit
```

## CLI

```bash
gt outpost status cloudrun-burst
# Shows: Cost (today): $0.42 / $50.00 cap
```

## Files

- `internal/cloudrun/cost.go`

## Dependencies

Depends on: gt-9a2.8 (basic CloudRunOutpost)


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.


Direct landing integration: gt land uses MR or bypasses

Integrate direct landing with merge queue.

'gt land --direct' should:
1. Check if MR exists for the polecat's work
2. If MR exists: process it directly (skip queue)
3. If no MR: create temporary MR, process, close

Options:
- --direct: bypass queue (existing)
- --via-queue: ensure goes through queue (new)
- --force: skip safety checks (existing)

This ensures direct landing still creates proper records.

Reference: docs/merge-queue-design.md#direct-landing-bypass-queue


mol-deacon-patrol

Deacon patrol molecule template. Label: template


Refinery tmux status: show merge queue length

Add refinery-specific status line showing:
- MQ length (pending merges)
- Currently processing item (if any)
- Maybe: success/failure counts

Implement in runRefineryStatusLine() in internal/cmd/statusline.go


Swarm learning: Beads database locality gap

## PGT Bug (GGT Design Already Correct)

Mayor created issues in `~/ai/mayor/rigs/beads/.beads/` but polecats use `~/ai/beads/.beads/`. Different databases = polecats can't see Mayor's beads.

**GGT Fix**: architecture.md already specifies:
- All agents use BEADS_DIR pointing to rig-level `.beads/`
- Lines 116-143: Beads Configuration for Multi-Agent
- Lines 573-586: Rig-Level Beads via BEADS_DIR

This is a PGT implementation gap, not a design issue. GGT spawn must set BEADS_DIR correctly.


HOP: Skill vectors on work items

Add skill embeddings to work items for capability-based matching. See ~/ai/stevey-gastown/hop/CONTEXT.md. Post-v0.1 work.


Fix TestHasPolecat test failure in internal/session

TestHasPolecat in internal/session/manager_test.go fails because it expects
specific polecats (Toast, Cheedo) to exist in the test environment.

Error:
```
manager_test.go:46: expected hasPolecat(Toast) = true
manager_test.go:49: expected hasPolecat(Cheedo) = true
```

Fix: Either create test fixtures or mock the filesystem check.