Work request: gt-976 crew lifecycle support

Hey Max, filed gt-976 for you - adding crew lifecycle support to Deacon.

Currently crew is 'human-managed' and can't request automated refresh. Would be useful for molecules that need fresh sessions mid-workflow.

Key changes:
- getManager(RoleCrew) → return deacon/ instead of human
- Teach Deacon crew session patterns
- Test with gt nudge

Priority 2, no rush but would unblock molecule automation for crew.

- Dave (beads/crew/dave)


gt mq integration status: show integration branch status

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

Display:
- Integration branch name and creation date
- Commits ahead of main
- MRs merged to integration (closed)
- MRs pending (open, targeting integration)
- Comparison: main..integration/<epic>

Output example:
  Integration: integration/gt-auth-epic
  Created: 2025-12-17
  Ahead of main: 5 commits

  Merged MRs (3):
    gt-mr-001  Fix login timeout
    gt-mr-002  Fix session expiry
    gt-mr-003  Update auth config

  Pending MRs (1):
    gt-mr-004  Update auth tests (in_progress)

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


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.


Merge: gt-72so

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


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.


Timed Beads: Scheduled recurring work

## Summary

Timed beads wake up periodically and get injected into the ready queue by the daemon.

## Schema Extension

```yaml
id: gt-weekly-sync
type: task  # or sentinel
schedule: "0 9 * * 1"  # cron: Monday 9am
# OR
interval: 24h          # every 24 hours
tier: haiku            # cheap model for routine checks
next_run: 2025-12-20T09:00:00Z
```

## Daemon Integration

Daemon heartbeat loop:
1. Check timed beads where `next_run <= now`
2. For each due bead:
   - Inject into ready queue (set status to open if needed)
   - Update `next_run` based on schedule/interval
3. Witnesses pick up work via `bd ready`

## Use Cases

- Weekly team sync reminders
- Daily health checks
- Periodic cleanup tasks
- Scheduled reports

## Interaction with Pinned Beads

A pinned bead can be timed - it wakes up periodically but never closes.
This is how you model "background services" in Gas Town.


loop-or-exit

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


Test4

test4 body


Work on ga-lue: Implement Witness as Claude agent. Conver...

Work on ga-lue: Implement Witness as Claude agent. Convert from shell script to Claude agent that monitors polecats, nudges idle ones, handles escalations. When done, submit MR (not PR) to integration branch for Refinery.


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


Polecat doesn't auto-start after spawn inject - requires manual nudge

## Problem

After `gt spawn --issue <id> --create`, the polecat session shows Claude Code started with the injected prompt, but Claude doesn't begin processing. The prompt just sits there until manually nudged.

## Evidence

```
$ gt spawn --issue gt-rixa --rig gastown --create
...
✓ Session started. Attach with: gt session at gastown/furiosa
  Polecat nudged to start working
  Witness notified to monitor startup
```

Session shows:
```
> You have a work assignment. Run 'gt mail inbox' to see it, then start
  working on issue gt-rixa.

  ⏵⏵ bypass permissions on (shift+tab to cycle)
```

But Claude doesn't respond. Manual nudge required:
```
$ gt nudge gt-gastown-furiosa "Please start working..."
```

After nudge, polecat immediately starts working correctly.

## Hypothesis

The spawn inject happens before Claude Code is fully initialized. The text arrives in the input buffer, but Claude hasn't started listening yet. By the time Claude starts, the input has already been 'consumed' as initial prompt text but not submitted.

## Resolution Plan

This will be solved by the **polecat molecule workflow** (mol-polecat-work), which provides structured lifecycle management. The molecule approach handles startup, work, and shutdown as discrete steps with proper state tracking.

**Blocked on**: beads/crew/dave completing ephemeral molecules (bd mol bond, ephemeral beads repo).

## Workaround

For now, use `gt nudge` if a polecat doesn't start within ~30 seconds of spawn.


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.


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


Doctor check: Zombie session cleanup

Detect and clean up zombie tmux sessions via gt doctor.

## Problem

Zombie sessions occur when:
- Agent crashes without cleanup
- gt kill fails mid-operation
- System restart leaves orphan sessions
- Session naming collision

## Checks

### ZombieSessionCheck
- List all tmux sessions matching gt-* pattern
- Cross-reference with known polecats
- Flag sessions with no corresponding polecat state
- Flag sessions for removed polecats
- Check session age vs polecat creation time

### Detection Criteria
- Session exists but polecat directory doesn't
- Session name doesn't match any registered polecat
- Polecat state=idle but session running
- Multiple sessions for same polecat

## Output

```
[WARN] Zombie tmux sessions detected:
  - gt-wyvern-OldPolecat (polecat removed)
  - gt-beads-Unknown (no matching polecat)
  - gt-wyvern-Toast (duplicate session)

  Run 'gt doctor --fix' to clean up
```

## Auto-Fix (--fix flag)

- Kill orphan tmux sessions
- Update polecat state to match reality
- Log all cleanup actions

## Safety

- Never kill sessions where polecat state=working
- Prompt before killing if --fix used without --force
- Create audit log of killed sessions


gt doctor: Detect and clean up orphaned sessions/processes

## Problem
Orphaned tmux sessions and Claude processes accumulate over time, consuming memory.

## Discovered Orphans
- gt-deacon: Idle Claude session, not part of any rig

## Proposed Solution

### gt doctor --check (default)
Detect issues without fixing:
- Orphan sessions (not matching rig/polecat/crew/witness/refinery/mayor pattern)
- Claude processes without parent tmux session
- Tmux sessions without Claude (stuck at bash prompt)
- Polecats marked 'working' but session idle

### gt doctor --fix
Clean up detected issues:
- Kill orphan sessions
- Kill orphan Claude processes
- Optionally reset stuck polecat state

### gt gc (alternative name)
Short alias for cleanup operations.

## Acceptance Criteria
- [ ] Detects orphan sessions
- [ ] Detects orphan processes
- [ ] Safe cleanup (doesn't kill active work)
- [ ] Reports what was cleaned


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.


Design: Failure modes and recovery

Document failure modes and recovery strategies for Gas Town operations.

## Critical Failure Modes

### 1. Agent Crash Mid-Operation

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

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

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

### 2. Git State Corruption

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

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

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

### 3. Beads Sync Conflict

**Scenario**: Two polecats modify same issue

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

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

### 4. Tmux Failure

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

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

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

### 5. Claude API Issues

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

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

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

### 6. Disk Full

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

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

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

### 7. Network Failure

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

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

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

## Recovery Principles

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

## Implementation

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


Merge: gt-5af.1

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


Merge Queue in Beads: Universal chit system for all work

**Design doc**: docs/merge-queue-design.md


CLI: gt stop --all (emergency swarm kill)

Emergency kill command for swarm operations.

## Commands

```bash
gt stop --all              # Kill ALL sessions across all rigs
gt stop --rig <name>       # Kill all sessions in one rig
gt stop --rig <name> --graceful  # Try graceful first, then force
```

## Implementation

### --all flag
1. List all tmux sessions matching gt-*
2. For each: capture final output, kill session
3. Update all polecat states to idle
4. Log all killed sessions
5. Send mail to Mayor: "Emergency stop executed"

### --rig flag
1. List sessions for specific rig
2. Same as above but scoped

### --graceful flag
1. First, try to inject "exit" command
2. Wait 5 seconds
3. If still running, force kill

## Output

```
Stopping all Gas Town sessions...
  [wyvern] Toast: captured output, killed
  [wyvern] Capable: captured output, killed
  [beads] Nux: captured output, killed

3 sessions stopped.
All polecat states set to 'idle'.
Mail sent to mayor/.
```

## Safety

- Always capture output before killing
- Update state files to reflect reality
- Log all operations to gt.log
- Warn if uncommitted changes detected (but still kill)

## Dependencies

Needs: Tmux wrapper, Session management, Polecat management


Connection interface: Protocol for local/remote ops

Abstract interface for local vs remote (SSH) operations.

## Concept

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

## Interface

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

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

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

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

## Implementations

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

## Usage

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

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

## Design Notes

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


Merge: gt-unrd

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


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.


Digest: mol-deacon-patrol

Test patrol cycle - first run, no actual work done


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.


Add no-PR instructions to mol-polecat-work at two points

Update mol-polecat-work in builtin_molecules.go to explicitly forbid GitHub PRs.

## Two Points to Add Instructions

### 1. submit-work step
When polecat is ready to submit:
- Push branch to origin
- Create beads merge-request issue
- DO NOT use gh pr create or GitHub PRs

### 2. CLAUDE.md polecat context
Add to polecat role instructions:
- Never use gh pr create
- Never create GitHub pull requests
- The Refinery processes merges via beads MR issues

## Why Two Points
- Molecule step description guides the workflow
- CLAUDE.md reinforces at context level
- Belt and suspenders approach

## Implementation
1. Update PolecatWorkMolecule() submit-work step description
2. Update prompts/roles/polecat.md with explicit prohibition

Related: gt-44wh (general no-PR bug)


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.


Merge: gt-72so

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


Phase 1.2: Configure bd for ephemeral molecule bonding

Ensure bd mol bond --ephemeral works with Gas Town setup.

## Questions for Dave

1. Does bd automatically find .beads-ephemeral/ or need explicit path?
2. How does bd mol bond --ephemeral know which repo to use?
3. Is there a redirect mechanism for ephemeral like main beads?

## Integration

From polecat/crew working directory:
```bash
bd mol bond mol-polecat-work --ephemeral --assignee $(gt whoami)
```

Should create molecule in rig's .beads-ephemeral/, not main beads.


Add merge-request type to Beads schema

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

This may require beads schema changes or just convention.


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

Mayor management CLI commands.

## Commands

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

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

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

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

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

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

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

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

## New File
internal/cmd/mayor.go

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

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


bd mail send: pinned column missing from schema

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

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

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

## Error
table issues has no column named pinned

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


Test: Alpha to Beta

Sibling communication test


Add molecule phase lifecycle diagram to architecture.md

Create a clear lifecycle diagram showing molecule phases:

```
                    ┌─────────────┐
                    │   Proto     │
                    │  (crystal)  │
                    └──────┬──────┘
                           │
                      bd mol bond
                           │
              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
      ┌───────────────┐        ┌───────────────┐
      │     Mol       │        │    Wisp       │
      │   (liquid)    │        │    (gas)      │
      │   durable     │        │  ephemeral    │
      │  main beads   │        │ .beads-eph/   │
      └───────┬───────┘        └───────┬───────┘
              │                        │
         bd mol squash            bd mol squash
              │                        │
              ▼                        ▼
      ┌───────────────┐        ┌───────────────┐
      │    Digest     │        │   (nothing)   │
      │ (distillate)  │        │  evaporates   │
      │  in git hist  │        └───────────────┘
      └───────────────┘
```

Also document:
- When to use Mol vs Wisp
- Mol: code review waves, epic implementation, feature work
- Wisp: orchestration, polecat work sessions, patrol loops


Merge: gt-8v8

branch: polecat/nux
target: main
source_issue: gt-8v8
rig: gastown


Molecules: Composable Workflow Beads

## Summary

Molecules are crystallized workflow patterns stored as Beads issues.
When instantiated, the molecule creates child beads forming a DAG.

## Key Insight: Molecules ARE Beads

Per HOP Decision 001: Beads IS the ledger. Molecules don't get a separate YAML format - they're issues with `type: molecule` containing prose-based step definitions.

Agents don't need rigid schemas. They parse natural language natively. A molecule is just instructions with enough structure for tooling.

## Example: Engineer in a Box

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

This workflow takes a task from design to merge.

## Step: design
Think carefully about architecture. Consider existing patterns,
trade-offs, testability.

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

## Step: review
Review for bugs, edge cases, style issues.
Needs: implement

## Step: test
Write and run tests. Cover happy path and edge cases.
Needs: implement

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

## Instantiation

```bash
# Attach molecule when spawning
gt spawn --issue gt-abc --molecule mol-xyz

# Creates child beads atomically:
gt-abc.design     ← ready first
gt-abc.implement  ← blocked by design
gt-abc.review     ← blocked by implement
gt-abc.test       ← blocked by implement
gt-abc.submit     ← blocked by review, test
```

Each step issue gets an `instantiated-from` edge to the molecule (with step metadata).

## Why This Matters

1. **Unified data plane**: Everything in Beads, no parallel YAML channel
2. **AI-native**: Prose instructions, not rigid schemas
3. **Error isolation**: Each step is a checkpoint - failure doesn't lose progress
4. **Scales with AI**: As agents get smarter, they handle more complex molecules

## Implementation Primitives

- `ParseMoleculeSteps()`: Extract steps from prose (convention-based)
- `InstantiateMolecule()`: Atomic transaction creating all steps + edges
- `instantiated-from` edge type: Track provenance
- Parameterization: `{{variable}}` substitution from context map


Worker cleanup: Beads sync on shutdown

Add beads sync verification to worker cleanup checklist and Witness verification.

## Update to Decommission Checklist (gt-sd6)

Add to pre-done verification:
- bd sync --status must show 'Up to date'
- git status .beads/ must show no changes

## Beads Edge Cases

Uncommitted beads changes:
  bd sync
  git add .beads/
  git commit -m 'beads: final sync'

Beads sync conflict (rare):
  git fetch origin main
  git checkout main -- .beads/
  bd sync --force
  git add .beads/
  git commit -m 'beads: resolve sync conflict'

## Update to Witness Verification (gt-f8v)

When capturing worker state:
  town capture <polecat> "bd sync --status && git status .beads/"

Check for:
- bd sync --status shows 'Up to date'
- git status .beads/ shows no changes

If beads not synced, nudge:
  WITNESS CHECK: Beads not synced. Run 'bd sync' then commit .beads/. Signal done when complete.


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


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-test. 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


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.


Architecture: beads config and direct landing docs

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


CLI: mail commands (send, inbox, read)

CLI: mail commands wrapping bd mail.

## Status

Current implementation works with JSONL files. Refactor to wrap bd mail CLI.

## Commands

### gt mail inbox
```
gt mail inbox [--unread] [--json]
```
Wraps: `bd mail inbox [--json]`

### gt mail read
```
gt mail read <id>
```
Wraps: `bd mail read <id> && bd mail ack <id>`
Note: bd mail read + ack marks as read.

### gt mail send
```
gt mail send <address> -s "Subject" -m "Body" [--notify]
```
Wraps: `bd mail send <recipient> -s "Subject" -m "Body"`
If --notify: inject tmux notification after send.

### gt mail delete
```
gt mail delete <id>
```
Wraps: `bd mail ack <id>` (closes the message issue)

## Address Translation

Same as gt-u1j.6:
- `mayor/` → `mayor`
- `<rig>/refinery` → `<rig>-refinery`
- `<rig>/<polecat>` → `<rig>-<polecat>`

## Dependencies

- gt-u1j.6: Mail system backend


gt daemon: Background service management

## Summary

ONE daemon for all Gas Town - a simple Go process (not a Claude agent) that:
1. Pokes agents periodically (heartbeat)
2. Processes lifecycle requests
3. Restarts sessions when agents request cycling

## Architecture

Daemon (gt daemon)
- Pokes Mayor: "HEARTBEAT: check your rigs"
- Pokes each Witness: "HEARTBEAT: check your workers"
- Has inbox at daemon/ for lifecycle requests
- Restarts sessions on cycle requests

## NOT the daemon's job:
- Making decisions (Witnesses do that)
- Running Claude (it's a Go process)
- Processing work (polecats do that)
- Direct polecat management (Witnesses do that)

The daemon is a **dumb scheduler** - poke things, execute lifecycle requests. All intelligence is in agents.

## Commands

- gt daemon start: Start daemon (background)
- gt daemon stop: Stop daemon
- gt daemon status: Show daemon status
- gt daemon logs: View daemon logs

## Daemon Loop

Every heartbeat interval:
1. Poke Mayor
2. For each rig: Poke Witness
3. Process lifecycle requests from daemon/ inbox
4. Check for dead sessions, restart if cycle requested

## Lifecycle Request Handling

Daemon checks its inbox (daemon/) for lifecycle requests:
- From Mayor: cycle/restart Mayor session
- From Witnesses: cycle/restart Witness session

On request:
1. Verify agent state shows requesting_cycle
2. Kill session
3. Start new session
4. Clear requesting_cycle flag

## Poke Protocol

Poke = tmux inject "HEARTBEAT: do your job"
- Agent ignores if already working
- Agent wakes up if idle
- Idempotent - multiple pokes are fine

## Lifecycle Hierarchy

- Daemon manages: Mayor, all Witnesses
- Witness manages: Polecats, Refinery (per rig)
- Crew: self-managed (human workspace)

## Related Issues

- gt-kmn.11: Daemon heartbeat details
- gt-gby: gt handoff command (unified lifecycle)
- gt-u1j.9: Fold witness daemon into this


Molecule CLI: bd molecule commands

Add molecule commands to bd:

## Commands

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

## gt spawn integration

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

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

## Output Examples

```
$ bd molecule show mol-abc

mol-abc: Engineer in a Box
Type: molecule

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

Instances: 3
```

```
$ bd molecule instances mol-abc

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


gt mail send: --priority flag should work like bd mail send

UX inconsistency: gt mail send passes flags that bd mail send doesn't support.

## Root Cause

router.go line 42 passes `--priority` to bd:
```go
args = append(args, "--priority", fmt.Sprintf("%d", beadsPriority))
```

But `bd mail send` only has `--urgent` (boolean), not `--priority`.

## Fix Options

1. Add `--priority` flag to `bd mail send` (preferred - more expressive)
2. Change router to only use `--urgent` when priority=0

## Also Affected

- `--type` flag (line 46) - bd mail send doesn't have this
- `--thread-id` flag (line 51) - bd mail send doesn't have this
- `--reply-to` flag (line 56) - bd mail send doesn't have this

The router assumes bd mail send has features it doesn't have.


Merge: gt-odvf

branch: polecat/slit
target: main
source_issue: gt-odvf
rig: gastown


mol-polecat-work


Merge: gt-92l

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


Merge execution: merge, test, push

Implement the actual merge execution.

Steps:
1. git checkout <mr.target>
2. git merge <mr.branch> --no-ff -m 'Merge <branch>: <title>'
3. If config.run_tests:
   - Run test_command (from config)
   - If failed: git reset --hard HEAD~1, return Failure(tests_failed)
4. git push origin <mr.target>
5. Return Success(merge_commit=HEAD)

Configuration:
- run_tests: bool (default true)
- test_command: string (default 'go test ./...')

Handle push failures with retry logic.

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


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)
```


Remove .gastown/ after migration complete

Final cleanup:
- Remove .gastown/ directories at town and rig levels
- Remove daemon/ and deacon/ directories (replaced by .runtime/)
- Update .gitignore to remove old patterns
- Verify all tests pass with new structure


Resource Beads: Leases, locks, and quotas

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


Work on gt-role-template: Refine witness/CLAUDE.md role t...

Work on gt-role-template: Refine witness/CLAUDE.md role template. See issue for details. Run 'bd show gt-role-template' to see the full issue.


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

GGT is missing most polecat management commands that PGT has.

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

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

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


test with labels


Overhaul gt mol to match bd mol chemistry interface

## The Sling: Unified Work Dispatch

This issue tracks the overhaul of `gt molecule` to align with chemistry metaphor and introduce the **Universal Gas Town Propulsion Principle**.

### The Propulsion Principle

> **If you find something on your hook, YOU RUN IT.**

This is the one rule that drives all Gas Town agents.

### The Sling Operation

`gt sling <thing> <target> [options]` - unified command for spawn + assign + pin.

See: `gastown/mayor/rig/docs/sling-design.md`

### Implementation Tasks

| Issue | Title | Priority |
|-------|-------|----------|
| gt-4ev4 | Implement gt sling command | P1 |
| gt-uym5 | Implement gt mol status command | P1 |
| gt-i4kq | Update templates for Propulsion Principle | P1 |
| gt-7hor | Document the Propulsion Principle | P2 |

### Command Changes

| Old | New |
|-----|-----|
| `gt molecule instantiate` | `gt sling` |
| `gt molecule attach` | `gt sling --force` |
| `gt molecule detach` | `gt mol burn` |
| `gt molecule progress` | `gt mol status` |
| `gt molecule list` | `gt mol catalog` |
| `gt spawn --molecule` | `gt sling` |

### Acceptance Criteria

- [ ] `gt sling` works for protos, issues, and epics
- [ ] `gt mol status` shows hook state
- [ ] Templates updated for propulsion principle
- [ ] Old commands deprecated with warnings
- [ ] Documentation complete


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.


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 crew status: Show workspace status

Implement 'gt crew status [<name>]' command:
- Show session state (running/stopped)
- Show git status (branch, uncommitted changes, unpushed)
- Show last commit info
- Show mail inbox status (unread count)
- If no name given, show all crew workers


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


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


Update gt doctor to check new config locations

Doctor should validate new structure:
- Check mayor/config.json exists and is valid
- Check settings/ exists for each rig
- Warn if old .gastown/ files still exist
- Offer --fix to migrate old locations to new


test-after-fix

test


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


Mail system: message types and threading

GGT mail system needs message types and threading like PGT.

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

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

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

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

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

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

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

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


Rig doctor checks: Refinery health, clones, gitignore

Rig-level doctor checks.

## Checks

### RigIsGitRepo
- Verify .git/ directory exists
- Verify git status works
- Fix: Cannot auto-fix

### GitExcludeConfigured
- Check .git/info/exclude contains Gas Town dirs
- Required entries: polecats/ witness/ refinery/ mayor/
- Fix: Append missing entries

### WitnessExists
- Verify witness/ directory exists
- Verify witness/rig/ is a git clone
- Verify witness/mail/inbox.jsonl exists
- Fix: Create structure, clone repo

### RefineryExists
- Verify refinery/ directory exists
- Verify refinery/rig/ is a git clone
- Verify refinery/mail/inbox.jsonl exists
- Fix: Create structure, clone repo

### MayorCloneExists
- Verify mayor/ directory exists
- Verify mayor/rig/ is a git clone
- Fix: Create structure, clone repo

### PolecatClonesValid
- For each polecat in polecats/:
  - Verify is a git clone
  - Verify on polecat branch
  - Warn if has uncommitted changes
- Fix: Cannot auto-fix (data loss risk)

### BeadsConfigValid (if applicable)
- If .beads/ exists, verify bd commands work
- Check beads sync status
- Warn if out of sync
- Fix: Run bd sync

## Implementation

```go
var RigChecks = []Check{
    &RigIsGitRepoCheck{},
    &GitExcludeConfiguredCheck{},
    &WitnessExistsCheck{},
    &RefineryExistsCheck{},
    &MayorCloneExistsCheck{},
    &PolecatClonesValidCheck{},
    &BeadsConfigValidCheck{},
}
```


Merge: gt-rana.2

branch: polecat/nux
target: main
source_issue: gt-rana.2
rig: gastown


Plugin: merge-oracle (merge queue analysis)

Example plugin that analyzes changesets before Refinery processes them. Builds overlap graph, classifies disjointness (parallel-safe vs needs-sequencing), uses LLM for semantic complexity, identifies high-risk patterns. Based on merge-orchestration proposal. See docs/architecture.md.


Test molecule

Test


Fix TestHasPolecat test failure


Fetch and conflict check: git operations for MR

Implement git operations for MR processing.

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

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

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


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


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


gt mail reply/thread: Conversation support

Add mail conversation features:

- gt mail reply <id> -m 'message' - Reply to a message
- gt mail thread <id> - Show all messages in a thread

Enables back-and-forth communication between agents.


Update Refinery to use Beads merge queue

Replace branch discovery with Beads queue in the Refinery module:

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

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

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


gt prime: Refinery context detection and output

Update gt prime to detect Refinery role:
- Detect from directory: refinery/rig/ = Refinery
- Show handoff bead reference
- Show merge queue status
- Show polecat branches with unmerged commits
- Show last actions summary


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.


request-shutdown


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 mq list: show queue with status/priority/age

Implement 'gt mq list' command to display the merge queue.

Output format:
ID          STATUS       PRIORITY  BRANCH                    WORKER  AGE
gt-mr-001   ready        P0        polecat/Nux/gt-xyz        Nux     5m
gt-mr-002   in_progress  P1        polecat/Toast/gt-abc      Toast   12m
gt-mr-003   blocked      P1        polecat/Capable/gt-def    Capable 8m
            (waiting on gt-mr-001)

Options:
- --ready: show only ready-to-merge (no blockers, not in progress)
- --status STATUS: filter by status
- --worker WORKER: filter by worker
- --epic EPIC: show MRs targeting integration/EPIC

Under the hood: bd list --type=merge-request with filters.

Reference: docs/merge-queue-design.md#gt-mq-list


Activity feed from wisp state: gt patrol status

Enable mayor and gt tool to quickly see what patrol agents are doing.

## Problem

The mayor and gt tool cannot quickly see what the deacon (or other patrol agents) are doing.
Need a way to extract an activity feed from the current wisp state.

## Desired Commands

- gt patrol status: Show current patrol state (step, timing, cycle count)
- gt patrol history: Show recent patrol cycles (from digests)
- gt patrol feed: Live tail of patrol activity

## Data Sources

- .beads-wisp/issues.jsonl: Current wisp with step progress
- .beads/issues.jsonl: Digests from squashed patrols
- heartbeat.json: Last activity timestamp

## Use Cases

1. Mayor checking deacon: Is the deacon doing anything?
2. Debugging hangs: What step is it stuck on?
3. Operator monitoring: Dashboard of patrol health
4. Summaries for handoffs: Auto-generate patrol digest

## Related

- gt-id36: Deacon Kernel
- gt-3x0z: Wisp Molecule Integration


Rename .beads-ephemeral to .beads-wisps in docs

The wisp storage directory should be named `.beads-wisps/` not `.beads-ephemeral/`.

Also fix the architecture: Witness and Refinery share mayor/rig's beads and wisps.
They don't have separate ephemeral stores.

## Files to update
- docs/wisp-architecture.md
- docs/architecture.md
- ~/gt/docs/patrol-system-design.md
- ~/gt/CLAUDE.md

## Changes
1. Rename `.beads-ephemeral/` → `.beads-wisps/` everywhere
2. Remove incorrect references to witness/.beads-ephemeral/ and refinery/rig/.beads-ephemeral/
3. Clarify that all rig-level patrols use mayor/rig/.beads-wisps/


Document the Propulsion Principle

Write canonical documentation for the Universal Gas Town Propulsion Principle.

Location: gastown/mayor/rig/docs/propulsion-principle.md

Content:
- The One Rule (hook has work → work happens)
- Why it works (stateless agents, molecule-driven)
- The sling lifecycle diagram
- Agent startup protocol
- Examples and anti-patterns

This is foundational theory-of-operation documentation.


Engineer session restart protocol

Implement session restart flow for when the Engineer needs to split work:

1. Engineer creates subtask(s) in Beads assigned to self
2. Engineer sends handoff mail to self (🤝 HANDOFF)
3. Engineer sends restart request to Witness
4. Witness verifies:
   - Handoff mail exists in Engineer outbox/sent
   - Subtasks filed in Beads
5. Witness restarts the Refinery session (new Engineer context)

This enables "occasionally consistent, eventually convergent" work patterns.
The Refinery continues; the Engineer gets fresh context.


init.go: Replace custom contains() with strings.Contains


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


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


Merge: gt-3x1.4

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


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


MR: gt-test (main)

branch: main
target: main
source_issue: gt-test


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)


gt mq integration status: show integration branch status

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

Display:
- Integration branch name and creation date
- Commits ahead of main
- MRs merged to integration (closed)
- MRs pending (open, targeting integration)
- Comparison: main..integration/<epic>

Output example:
  Integration: integration/gt-auth-epic
  Created: 2025-12-17
  Ahead of main: 5 commits

  Merged MRs (3):
    gt-mr-001  Fix login timeout
    gt-mr-002  Fix session expiry
    gt-mr-003  Update auth config

  Pending MRs (1):
    gt-mr-004  Update auth tests (in_progress)

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


Architecture: beads config and direct landing docs

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


Merge: gt-a95

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


gt crew remove: Remove crew workspace

Implement 'gt crew remove <name> [--force]' command:
- Check for uncommitted changes, unpushed commits
- Warn and require --force if dirty
- Kill any running session
- Remove directory
- Unregister from mail


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


Speed up test suite

Tests are running slow during MQ processing. Investigate and optimize:
- Profile test execution time
- Look for slow tests (network, file I/O, sleeps)
- Consider parallel test execution
- Cache expensive setup where possible


gt rig shutdown: Gracefully stop all rig agents

Add 'gt rig shutdown <rig>' command to gracefully stop all agents in a rig.

Should:
- Stop all polecat sessions
- Stop refinery
- Stop witness
- Optionally wait for graceful shutdown with timeout


gt mq retry: retry a failed MR

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

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

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

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


Merge: gt-3x1.5

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


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


Witness Patrol Molecule

Create mol-witness-patrol molecule for witness heartbeat loop.

Similar to DeaconPatrolMolecule but for per-rig witness duties.

## Steps
1. inbox-check - Process witness mail
2. survey-workers - List polecats, check status
3. inspect-workers - Capture pane output, assess state
4. plugin-run - Execute plugins from <rig>/plugins/witness/
5. decide-actions - Determine nudges, escalations
6. execute-actions - Send nudges, process shutdowns
7. context-check - Check own context, handoff if needed
8. loop-or-exit - Continue or cycle

The plugin-run step enables user customization of the patrol loop.


Clarify Mayor vs Witness cleanup responsibilities

Document the cleanup authority model: Witness owns ALL per-worker cleanup, Mayor never involved.

## The Rule

**Witness handles ALL per-worker cleanup. Mayor is never involved.**

## Why This Matters

1. Separation of concerns: Mayor strategic, Witness operational
2. Reduced coordination overhead: No back-and-forth for routine cleanup
3. Faster shutdown: Witness kills workers immediately upon verification
4. Cleaner escalation: Mayor only hears about problems

## What Witness Handles

- Verifying worker git state before kill
- Nudging workers to fix dirty state
- Killing worker sessions
- Updating worker state (sleep/wake)
- Logging verification results

## What Mayor Handles

- Receiving swarm complete notifications
- Deciding whether to start new swarms
- Handling escalations (stuck workers after 3 retries)
- Cross-rig coordination

## Escalation Path

Worker stuck -> Witness nudges (up to 3x) -> Witness escalates to Mayor -> Mayor decides: force kill, reassign, or human

## Anti-Patterns

DO NOT: Mayor asks Witness if worker X is clean
DO: Witness reports swarm complete, all workers verified

DO NOT: Mayor kills worker sessions directly
DO: Mayor tells Witness to abort swarm, Witness handles cleanup

DO NOT: Workers report done to Mayor
DO: Workers report to Witness, Witness aggregates and reports up


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

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

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

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

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

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


Mail system: JSONL inbox, delivery, tmux injection

Refactor mail system to wrap bd mail commands.

## Status

Current implementation uses JSONL files directly. Now that Beads mail is available (gt-r01 closed), refactor to use bd mail as the backend.

## Implementation

Replace internal/mail/* with thin wrappers around bd CLI:

```go
// Send wraps: bd mail send <to> -s <subject> -m <body>
func (r *Router) Send(msg *Message) error

// List wraps: bd mail inbox --json
func (m *Mailbox) List() ([]*Message, error)

// Get wraps: bd mail read <id> --json
func (m *Mailbox) Get(id string) (*Message, error)

// MarkRead wraps: bd mail ack <id>
func (m *Mailbox) MarkRead(id string) error
```

## Address Translation

GGT addresses → Beads recipients:
- `mayor/` → `mayor`
- `<rig>/refinery` → `<rig>-refinery`
- `<rig>/<polecat>` → `<rig>-<polecat>`

## Tmux Notification

Keep the tmux injection for --notify flag. After bd mail send, optionally inject notification into tmux session.

## Migration

No migration needed - bd mail creates messages as beads issues with type=message. Old JSONL inboxes can be ignored or cleaned up.

## Dependencies

- gt-r01: CLOSED (Beads mail available)
- gt-u1j.4: Tmux wrapper (for notifications)


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.


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.


HTTP work client: CloudRun dispatch client

## Overview

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

## Implementation

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

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

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

## Streaming Reader

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

## Files

- `internal/cloudrun/client.go`

## Dependencies

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


Phase 2: Quiescent agents (Witness, Refinery)

Enable Witness and Refinery to sleep until triggered.

## Scope
- Wake triggers: gt spawn, MR submit, mail, Deacon ping
- Wake SLA: <1 minute
- mol-witness-patrol and mol-refinery-patrol definitions
- Quiescent entry/exit protocol

## Implementation
- Kill tmux session on quiescent entry
- Preserve sandbox (worktree, state files)
- Restart session on wake trigger
- Agent checks pinned, spawns default patrol if naked

Depends: Phase 1 complete


gt prime should detect mayor role from any rig's mayor/ folder

Currently gt prime only detects the Mayor role when run from town root (~/gt). It should also detect Mayor when run from:
- ~/gt/gastown/mayor/rig (rig's internal mayor dir)
- Any path containing /mayor/ under a rig

This would allow the mayor session to work correctly regardless of which rig directory it's attached to.


Interactive prompts for destructive operations

Add interactive confirmations for destructive operations.

## Operations Needing Confirmation
- `gt swarm cancel` - Cancels active swarm
- `gt swarm land --force` - Force landing
- `gt polecat decommission` - Removes polecat
- `gt rig remove` - Removes rig
- `gt stop --all` - Stops all sessions
- `gt mail purge` - Permanently deletes mail

## Implementation Options

### Option 1: promptui library
```go
import "github.com/manifoldco/promptui"

prompt := promptui.Prompt{
    Label:     "Delete polecat Toast",
    IsConfirm: true,
}
result, err := prompt.Run()
```

### Option 2: Simple stdin
```go
func confirm(prompt string) bool {
    fmt.Printf("%s [y/N]: ", prompt)
    var response string
    fmt.Scanln(&response)
    return strings.ToLower(response) == "y"
}
```

## Bypass Flags
All confirmations skippable with --force or --yes:
```go
if !force && !confirm("Really cancel swarm?") {
    return nil
}
```

## Acceptance Criteria
- [ ] Destructive ops prompt by default
- [ ] --force or --yes bypasses
- [ ] Clear prompt text explaining action
- [ ] Non-interactive mode (piped input) auto-fails


Merge: beads-2nh

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


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)


Work on ga-p6r: Add handoff protocol to spawn priming. En...

Work on ga-p6r: Add handoff protocol to spawn priming. Ensure polecats receive handoff context when spawned. When done, submit MR (not PR) to integration branch for Refinery.


Merge: gt-5af.6

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


Witness role template + CLAUDE.md

Create the Witness agent's role context:

1. internal/templates/roles/witness.md.tmpl
   - Witness responsibilities (polecat lifecycle)
   - Core loop description
   - Commands available (gt polecats, bd ready, etc)
   - Escalation protocol

2. Generate CLAUDE.md for witness/ directories
   - Context about the rig
   - Mail address: <rig>/witness
   - Startup protocol (gt prime, check inbox)

The Witness is the per-rig 'pit boss' - NOT a global coordinator. It manages polecats for ONE rig only.


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


Phase 4.1: mol-deacon-patrol uses ephemeral

Deacon patrol cycles run as ephemeral molecules.

## Current Deacon Loop

```
while running:
    check_heartbeat()
    check_mail()
    sleep(interval)
```

## Molecule-Based Loop

```
while running:
    mol = bd mol bond mol-deacon-patrol-cycle --ephemeral
    execute_cycle(mol):
        check_heartbeat()
        check_mail()
        log_status()
    summary = generate_cycle_summary()
    bd squash mol --summary summary
    sleep(interval)
```

## Benefits

- Each cycle is tracked
- Digests show daemon health over time
- Can query: 'show me patrol cycles from last hour'
- Crash mid-cycle → ephemeral shows where we were


session-gc

Clean dead sessions. Run gt gc --sessions.


Phase 1.2: Daemon attachment detection

Daemon polls Deacon's pinned bead to detect naked state.

## Implementation
- Daemon knows Deacon's pinned bead ID
- Every heartbeat cycle: bd show <pinned> --json
- Parse attached_molecule field
- If null/empty: Deacon is naked

## Actions on Naked
- Spawn mol-deacon-patrol
- Attach to Deacon's pinned
- Nudge Deacon to start

## Failsafes
- Keepalive file monitoring
- Stale detection (>10min no progress)
- Force nudge on stale

Depends: gt-rana.1


gt spawn should create fresh polecat worktree, never reuse

Currently gt spawn tries to reuse existing polecats, which causes:

1. Stale beads database
2. Stale code (behind main/integration)
3. Old inbox messages
4. Git history pollution

## Current Behavior

gt spawn:
1. Looks for idle polecat in pool
2. If found, reuses existing worktree
3. Assigns new issue

## Desired Behavior

gt spawn:
1. Always create FRESH polecat from current main/integration
2. Fresh worktree with clean beads
3. No reuse of old worktrees

## Name Pool Still Useful

Keep name pool for:
- Allocating themed names (mad-max, etc.)
- Tracking which names are in use

But worktrees should be created fresh each time.

## Implementation

In spawn.go, before starting work:
1. If worktree exists: remove it first
2. Create fresh worktree from integration branch
3. Sync beads from rig to polecat
4. Then proceed with work assignment

This ensures polecats always start with latest code and beads.


Witness session startup (gt witness start)

Implement gt witness start <rig>

Should:
1. Verify rig exists and has witness/ directory
2. Check for existing witness session (don't double-start)
3. Create tmux session: gt-<rig>-witness
4. Start Claude Code with --dangerously-skip-permissions
5. Send gt prime to load context
6. Send startup prompt

Similar pattern to refinery startup in internal/refinery/manager.go


Update architecture.md: Engineer role and Beads merge queue

Update docs/architecture.md with recent design decisions:

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

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

3. CLI section: Add gt mq commands

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


MR field parsing: extract structured fields from description

Parse merge-request beads to extract structured fields:
- branch: source branch name
- target: target branch (main or integration/xxx)
- source_issue: the work item being merged
- worker: who did the work
- rig: which rig
- merge_commit: (set on close)
- close_reason: (set on close)

Fields stored in description as YAML block or key: value lines.
Provide helper functions: ParseMRFields(issue) and SetMRFields(issue, fields).

Reference: docs/merge-queue-design.md#merge-request-schema


Immediate daemon notification on lifecycle request

When gt handoff sends a lifecycle request to the daemon (via bd mail send deacon/), the daemon only discovers it on its next heartbeat poll (every 5 min). Workers wait unnecessarily for retirement.

## Current Behavior
1. Worker runs gt handoff
2. Lifecycle mail sent to deacon/ via beads
3. Worker blocks waiting for retirement
4. Daemon heartbeat runs (up to 5 min later)
5. Daemon processes lifecycle request and kills session

## Expected Behavior
Lifecycle requests should be processed immediately, not on poll interval.

## Proposed Solutions

### Option A: SIGUSR1 Signal (simplest)
1. Add SIGUSR1 handler in daemon that calls ProcessLifecycleRequests()
2. gt handoff reads daemon.pid and sends SIGUSR1 after mail send

### Option B: Unix Socket
1. Daemon listens on ~/gt/daemon/lifecycle.sock
2. gt handoff connects and sends 'process' message

### Option C: Watchfile + fsnotify
1. gt handoff touches ~/gt/daemon/lifecycle.trigger
2. Daemon uses fsnotify to detect and process

Recommend Option A for simplicity.

## Affected Files
- internal/daemon/daemon.go - add signal handler
- internal/cmd/handoff.go - send signal after mail


Architecture: Decentralized rig structure with per-rig agents

## Decision

Adopt decentralized architecture where each rig contains all its agents (mayor/, witness/, refinery/, polecats/) rather than centralizing mayor clones at town level.

## Town Level Structure

```
~/ai/                              # Town root
├── config/                        # Town config (VISIBLE, not hidden)
│   ├── town.json                  # {"type": "town"}
│   ├── rigs.json                  # Registry of managed rigs
│   └── federation.json            # Wasteland config (future)
│
├── mayor/                         # Mayor's HOME at town level
│   ├── CLAUDE.md
│   ├── mail/inbox.jsonl
│   └── state.json
│
└── <rigs>/                        # Managed projects
```

## Rig Level Structure (e.g., wyvern)

```
wyvern/                            # Rig = clone of project repo
├── .git/info/exclude              # Gas Town adds: polecats/ refinery/ witness/ mayor/
├── .beads/                        # Beads (if project uses it)
├── [project files]                # Clean project code on main
│
├── polecats/                      # Worker clones
│   └── <name>/                    # Each is a git clone
│
├── refinery/
│   ├── rig/                       # Refinery's clone
│   ├── state.json
│   └── mail/inbox.jsonl
│
├── witness/                       # NEW: Per-rig pit boss
│   ├── rig/                       # Witness's clone
│   ├── state.json
│   └── mail/inbox.jsonl
│
└── mayor/
    ├── rig/                       # Mayor's clone for this rig
    └── state.json
```

## Key Decisions

1. **Visible config dir**: `config/` not `.gastown/` (models don't find hidden dirs)
2. **Witness per-rig**: Each rig has its own Witness (pit boss) with its own clone
3. **Mayor decentralized**: Mayor's clones live IN each rig at `<rig>/mayor/rig/`
4. **Minimal invasiveness**: Only `.git/info/exclude` modified, no commits to project
5. **Clone subdir name**: Keep `rig/` for consistency (refinery/rig/, witness/rig/, mayor/rig/)

## Role Detection

- Town root or mayor/ → Mayor (town level)
- Rig root → Mayor (canonical main)
- <rig>/mayor/rig/ → Mayor (rig-specific)
- <rig>/refinery/rig/ → Refinery
- <rig>/witness/rig/ → Witness
- <rig>/polecats/<name>/ → Polecat

## Migration from PGT

- `mayor/rigs/<rig>/` → `<rig>/mayor/rig/`
- `<rig>/town/` → eliminated (rig root IS the clone)
- Add `witness/` to each rig


Beads as Universal Data Plane

## Vision

Beads is the data plane for all Gas Town operations. Everything flows through beads:
- Work items (issues, tasks, epics)
- Mail (messages between agents)
- Merge requests (queue entries)
- Workflows (composable execution patterns)
- Resources (leases, locks, quotas)
- Schedules (timed activities)

## New Bead Categories

### Molecules (Composable Workflows)
Crystallized workflow patterns that can be attached to work items.

### Timed Beads (Scheduled Work)
Beads that wake up periodically via daemon.

### Pinned Beads (Ongoing Concerns)
Beads representing persistent concerns, not discrete tasks.

### Resource Beads (Leases/Locks)
Beads representing reserved resources.

## v1 Priority

- **P0**: Molecules (enables engineer-in-box)
- **P2**: Timed, Pinned, Resource beads (post-v1)


context-check

Assess own context usage. If high, prepare for handoff.


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


Add human escalation configuration

Add overseer contact config to mayor/town.json. Deacon uses this for escalation when it cannot resolve issues. Support email and/or other notification methods.


Tmux wrapper: session operations

Wrapper for tmux session operations via subprocess.

## Interface

```go
type Tmux struct{}

func NewTmux() *Tmux

// Session lifecycle
func (t *Tmux) NewSession(name, workDir string) error
func (t *Tmux) KillSession(name string) error
func (t *Tmux) HasSession(name string) (bool, error)
func (t *Tmux) ListSessions() ([]string, error)

// Session interaction
func (t *Tmux) SendKeys(session, keys string) error
func (t *Tmux) CapturePane(session string, lines int) (string, error)
func (t *Tmux) AttachSession(session string) error

// Window operations (if needed)
func (t *Tmux) SelectWindow(session string, index int) error
```

## Session Naming

Convention: `gt-<rig>-<polecat>` (e.g., `gt-wyvern-Toast`)

## Implementation

- Shell out to tmux binary
- CapturePane uses `tmux capture-pane -p -t <session> -S -<lines>`
- SendKeys uses `tmux send-keys -t <session> '<keys>' Enter`

## Error Handling

- "no server running": tmux not started (not an error for HasSession)
- "session not found": session doesn't exist
- Wrap all tmux errors with context

## Environment

Requires TMUX_TMPDIR or uses default socket. Don't assume tmux is running.


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


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.


Merge: gt-5wb7

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


Phase 1.3: gt doctor checks for ephemeral health

Add doctor checks for ephemeral beads repo.

## Checks

1. **ephemeral-exists**: .beads-ephemeral/ directory exists for each rig
2. **ephemeral-git**: It's a valid git repo
3. **ephemeral-orphans**: Molecules started but never squashed (>24h old)
4. **ephemeral-size**: Warn if ephemeral repo is >100MB (should be cleaned)
5. **ephemeral-stale**: Molecules with no activity in last hour

## Auto-fix

--fix can:
- Create missing ephemeral repo
- Clean up old completed molecules (already squashed)
- NOT auto-squash incomplete molecules (needs AI summary)


Polecat Mood Plugin (standard)

Standard witness plugin that assesses polecat emotional state.

## Location
`<rig>/plugins/witness/polecat-mood/`

## Mood Emojis
| Mood | Emoji | Detection |
|------|-------|-----------|
| working | 😺 | Active tool calls, making progress |
| productive | 😸 | Completing tasks, tests passing |
| idle | 🐱 | Waiting at prompt, no recent activity |
| confident | 😼 | Self-reviewing, about to submit |
| struggling | 😿 | Repeated errors, test failures |
| stuck | 🙀 | No progress for 10+ min |
| done | 😽 | Work complete, requesting shutdown |
| blocked | 😾 | Explicitly waiting on dependency |

## Plugin Config (plugin.yaml)
```yaml
name: polecat-mood
description: Assess polecat emotional state from session activity
trigger: patrol
tier: haiku
input: session-capture
```

## Output
Plugin outputs gt commands:
```
gt polecat mood Rictus 😺
gt polecat mood Nux 😿
```

## Customization
Users can fork and modify prompt.md to change assessment criteria or add custom moods.


Human escalation: notify overseer when self-heal fails

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

**Implementation**: Config in town.json or similar:
```yaml
escalation:
  contact: steve@example.com  # or slack webhook
  triggers:
    - daemon_cant_restart
    - session_missing_5min
```

**Trigger points**:
- Go daemon can't restart a session after N attempts
- Agent detects it's stuck and can't recover
- Witness can't reach polecat

**Mechanism**:
- Simple: `gt mail send --human` already exists
- Enhanced: email/slack via external script

**Weight**: Small config + one code path in daemon
**Value**: High for unattended operation - human gets notified instead of silent failure


Add molecules.jsonl as separate catalog file for template molecules


Create mol-gastown-polecat derived molecule

Create the standard Gas Town polecat workflow molecule.

## Molecule Definition
\`\`\`markdown
## Molecule: gastown-polecat
Full workflow for Gas Town polecats including binary installation.

Includes: mol-engineer-in-box

## Step: install-binary
After merge is submitted, rebuild and install the local gt binary.
This ensures the latest code is available to all local agents.

Run from the rig directory:
\`\`\`
go build -o gt ./cmd/gt
go install ./cmd/gt
\`\`\`

Needs: submit
\`\`\`

## Why This Molecule
Every polecat that pushes to main should also rebuild the binary.
This ensures the installed gt is always current with main.

## Implementation
1. Add to builtin_molecules.go (after Includes support lands)
2. Update SeedBuiltinMolecules
3. Run gt molecule seed

## Acceptance
- [ ] mol-gastown-polecat exists
- [ ] Includes all engineer-in-box steps
- [ ] Adds install-binary step after submit
- [ ] Can be used with gt spawn --molecule


Update polecat CLAUDE.md with molecule workflow

Add molecule execution guidance to polecat context:

## What to Add

### Molecule Awareness
- Polecats execute Wisps (ephemeral molecule instances)
- The work assignment mail includes molecule context
- Current step is tracked in the wisp

### Workflow Protocol
1. Read assignment (includes molecule ID and current step)
2. Execute current step
3. Update step status via bd mol step
4. Generate summary when all steps complete
5. Run bd mol squash to compress wisp into digest

### Key Commands
- bd mol show <wisp-id> - view current molecule state
- bd mol step <wisp-id> --status=complete - mark step done
- bd mol squash <wisp-id> --summary='...' - complete molecule

### Summary Generation
When completing work, generate a summary that:
- Lists what was accomplished
- Notes any deviations from the plan
- Captures key decisions made
- This becomes the permanent digest


Polecat doesn't auto-start after spawn inject - requires manual nudge

## Problem

After `gt spawn --issue <id> --create`, the polecat session shows Claude Code started with the injected prompt, but Claude doesn't begin processing. The prompt just sits there until manually nudged.

## Evidence

```
$ gt spawn --issue gt-rixa --rig gastown --create
...
✓ Session started. Attach with: gt session at gastown/furiosa
  Polecat nudged to start working
  Witness notified to monitor startup
```

Session shows:
```
> You have a work assignment. Run 'gt mail inbox' to see it, then start
  working on issue gt-rixa.

  ⏵⏵ bypass permissions on (shift+tab to cycle)
```

But Claude doesn't respond. Manual nudge required:
```
$ gt nudge gt-gastown-furiosa "Please start working..."
```

After nudge, polecat immediately starts working correctly.

## Hypothesis

The spawn inject happens before Claude Code is fully initialized. The text arrives in the input buffer, but Claude hasn't started listening yet. By the time Claude starts, the input has already been 'consumed' as initial prompt text but not submitted.

## Resolution Plan

This will be solved by the **polecat molecule workflow** (mol-polecat-work), which provides structured lifecycle management. The molecule approach handles startup, work, and shutdown as discrete steps with proper state tracking.

**Blocked on**: beads/crew/dave completing ephemeral molecules (bd mol bond, ephemeral beads repo).

## Workaround

For now, use `gt nudge` if a polecat doesn't start within ~30 seconds of spawn.


gt uninstall: Clean removal of Gas Town harness

Add 'gt uninstall' command to cleanly remove a Gas Town installation.

Should:
- Remove harness directory structure
- Optionally preserve rigs/data with --keep-data flag
- Warn about running sessions
- Clean up any global config references


CloudRun persistent connections: HTTP/2 keepalive

## Overview

Implement persistent HTTP/2 connections to keep Cloud Run containers warm.

## Problem

Cloud Run has cold start latency when scaling from 0. Persistent connections keep containers warm.

## Implementation

```go
type PersistentConnectionManager struct {
    serviceURL string
    conn       net.Conn
    client     *http2.ClientConn
    mu         sync.Mutex
    lastUsed   time.Time
    keepAlive  time.Duration
}

func (m *PersistentConnectionManager) GetConnection() (*http2.ClientConn, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    // Reuse existing connection if healthy
    if m.client != nil && m.client.CanTakeNewRequest() {
        m.lastUsed = time.Now()
        return m.client, nil
    }

    // Create new connection
    return m.dial()
}

func (m *PersistentConnectionManager) keepAliveLoop() {
    ticker := time.NewTicker(m.keepAlive / 2)
    for range ticker.C {
        m.mu.Lock()
        if time.Since(m.lastUsed) > m.keepAlive {
            // Connection idle too long, close it
            m.close()
        } else {
            // Send ping to keep alive
            m.ping()
        }
        m.mu.Unlock()
    }
}
```

## Integration with CloudRunOutpost

```go
type CloudRunOutpost struct {
    // ...existing fields...
    connMgr *PersistentConnectionManager
}

func (o *CloudRunOutpost) Spawn(...) {
    conn, err := o.connMgr.GetConnection()
    // Use connection for work dispatch
}
```

## Config

```yaml
outposts:
  - name: cloudrun-burst
    type: cloudrun
    # ...
    keep_alive: 5m  # Keep connection warm for 5 minutes
```

## Files

- `internal/cloudrun/connection.go`

## Dependencies

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


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)


Federation: Wasteland architecture for cross-town coordination

## Overview

Federation enables Gas Town to scale across machines via **Outposts** - remote compute environments that can run workers.

**Design doc**: docs/federation-design.md

## Outpost Types

| Type | Description | Cost Model |
|------|-------------|------------|
| Local | Current model (tmux panes) | Free |
| SSH/VM | Full Gas Town clone on VM | Always-on |
| CloudRun | Container workers on GCP | Pay-per-use |

## Key Concepts

### Outpost Abstraction
```go
type Outpost interface {
    Name() string
    Type() OutpostType  // local, ssh, cloudrun
    MaxWorkers() int
    Spawn(issue string, config WorkerConfig) (Worker, error)
    Workers() []Worker
    Ping() error
}
```

### Cloud Run Workers
- Persistent HTTP/2 connections solve zero-to-one cold start
- Pay only when working (~$0.017 per 5-min session)
- Scale 0→N automatically
- Git clone via persistent volumes

### SSH/VM Outposts
- Full Gas Town clone on remote machine
- SSH for commands, git for sync
- Good for long-running autonomous work

## Design Principles

1. **Outpost abstraction** - support multiple backends
2. **Local-first** - remote is for overflow/burst
3. **Git as source of truth** - code and beads sync everywhere
4. **HTTP for Cloud Run** - dont force mail onto containers
5. **Graceful degradation** - works with any subset of outposts

## Related

- gt-f9x.7-10: Connection interface (lower-level abstraction)
- docs/federation-design.md: Full architectural analysis


Test7

Test with fixed workdir


Molecule format bridge: convert embedded markdown to child issues

## Problem

Two molecule formats exist:
1. **Old (gastown builtin)**: Steps embedded as markdown in Description field, parsed by ParseMoleculeSteps()
2. **New (bd mol)**: Steps as child issues in proper beads DAG

The daemon's InstantiateMolecule() uses the old format. bd mol spawn uses the new format.

## Options

### Option A: Require child issues (new format)
- Update daemon to use bd mol spawn instead of InstantiateMolecule
- Deprecate embedded markdown format
- Pro: One format, simpler
- Con: Breaking change for existing molecules

### Option B: Build a bridge
- In InstantiateMolecule, detect format:
  - If Description has ## Step: markers → parse to child issues
  - If issue has children with template label → use directly
- Convert old format to new format on instantiation
- Pro: Backward compatible
- Con: More complexity

## Recommendation

Option B - build the bridge. On instantiation, convert markdown steps to child issues. This unifies execution while preserving authoring flexibility.


Witness MVP: Automated Polecat Lifecycle

Implement the Witness agent - per-rig 'pit boss' that manages polecat lifecycles.

The Witness enables hands-free swarming by automating:
- Spawning polecats for ready work
- Monitoring worker health
- Processing shutdown requests
- Cleaning up worktrees when done
- Escalating stuck workers to Mayor

Without Witness, humans must manually spawn, monitor, and kill each polecat.

## Core Loop

```
while True:
  # Handle pending shutdowns
  for polecat in polecats where state == pending_shutdown:
    verify git clean
    kill session
    remove worktree
    delete branch

  # Spawn for ready work
  ready = bd ready --parent=<epic> if epic else bd ready
  for issue in ready:
    if active_workers < max_workers:
      gt spawn --issue <id>

  # Check worker health
  for polecat in active polecats:
    if stuck (no progress for 30 min):
      nudge or escalate

  sleep 60
```

## Blocking Bugs to Fix
- gt-dsfi: handoff deadlock
- gt-n7z7: refinery foreground race condition
- gm-c6b: mail coordination (town vs rig beads)


Merge: gt-3x1

branch: polecat/Slit
target: main
source_issue: gt-3x1
rig: gastown


Work assignment messages should auto-close on completion

When a polecat completes work on an issue, the work assignment message (msg-type:task) stays open. Found 7 stale work assignments in gastown after swarm completed.

Proposal: When bd close is called on an issue, auto-close any work assignment messages that reference that issue in their body.

Alternative: Work assignment messages could use a different lifecycle - perhaps they should be acked (closed) when the polecat starts working, not when they finish.


implement

Implement the solution for gt-test123. 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


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)


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


Merge queue: per-rig queue management

Per-rig merge queue for coordinating polecat work.

## Concept

When polecats complete work, they submit to the merge queue. The Refinery processes the queue, merging work to main in order.

## Data Model

```go
type MergeRequest struct {
    ID        string    `json:"id"`
    Polecat   string    `json:"polecat"`
    Branch    string    `json:"branch"`
    Issue     string    `json:"issue,omitempty"`
    Status    MRStatus  `json:"status"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

type MRStatus string
const (
    MRPending   MRStatus = "pending"
    MRReviewing MRStatus = "reviewing"
    MRMerged    MRStatus = "merged"
    MRRejected  MRStatus = "rejected"
)
```

## Interface

```go
type MergeQueue struct {
    rig  *Rig
    path string  // <rig>/refinery/queue.jsonl
}

func NewMergeQueue(rig *Rig) *MergeQueue

// Queue operations
func (mq *MergeQueue) Submit(polecat, branch string, issue string) (*MergeRequest, error)
func (mq *MergeQueue) List() ([]*MergeRequest, error)
func (mq *MergeQueue) Next() (*MergeRequest, error)  // oldest pending
func (mq *MergeQueue) Get(id string) (*MergeRequest, error)

// Status updates
func (mq *MergeQueue) SetStatus(id string, status MRStatus) error
func (mq *MergeQueue) MarkMerged(id string) error
func (mq *MergeQueue) MarkRejected(id, reason string) error
```

## Queue Storage

JSONL file at `<rig>/refinery/queue.jsonl`. FIFO ordering.

## Git Coordination

Refinery processes queue:
1. Fetch polecat branch
2. Attempt merge to main
3. Run tests (if configured)
4. Push to origin
5. Update queue status

## Conflict Handling

If merge fails:
- Mark MR as rejected
- Notify polecat to rebase