Merge: gt-jhr85

branch: polecat/slit-mjxlyyte
target: main
source_issue: gt-jhr85
rig: gastown
agent_bead: gt-mayor

Merged to main at f4a82337 (slit-mjxlyyte commands feature)


Digest: mol-deacon-patrol

Patrol 252 complete: no incidents


Digest: mol-deacon-patrol

Patrol cycle 16: All healthy


Digest: mol-deacon-patrol

Patrol 121: All healthy, no callbacks, 3 convoys tracking


Haiku-based smart stuck detection

Use Haiku to analyze tmux state when signals are ambiguous.

## When to Invoke

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

## Prompt

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

## Cost

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

## Configuration

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


Digest: mol-deacon-patrol

Patrol cycle 19: All healthy.


Digest: mol-deacon-patrol

Patrol cycle 41: All agents healthy, no pending spawns, no orphans, inbox clean


Pillar 1: Agent Identity (agent-as-bead)

Agents become beads with deterministic IDs, hook slots, role beads, and self-reported state.

Key deliverables:
- Agent bead schema (type=agent, hook_bead, role_bead, state fields)
- bd slot set/clear commands
- Agent beads for mayor, witness, refinery
- Role beads from current CLAUDE.md content
- Hook lookup via agent bead

Reference: ~/gt/docs/agent-as-bead.md

Parent: gt-oki8p


Digest: mol-deacon-patrol

Patrol 16: Quiet


Digest: mol-deacon-patrol

Patrol 138: All healthy


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

Implemented all mail CLI commands: mark, delete with confirmation, batch archive, purge, and search with regex support


gt mail check: Check for new mail

Add `gt mail check` command.

```
gt mail check [--quiet] [--inject]
```

- --quiet: Only output if new mail (for scripts/hooks)
- --inject: Send notification to running session (future)

**Implementation:**
- Add `checkCmd` to internal/cmd/mail.go
- Count unread messages in inbox
- Output: 'N new message(s)' or nothing if --quiet and empty

Simple command, good first task.

Implemented --quiet flag for gt mail check


gt mail mark/delete: Basic message operations

Add `gt mail mark` and `gt mail delete` commands.

### gt mail mark
```
gt mail mark <id> --read
gt mail mark <id> --unread
```
- Update message read status in inbox.jsonl

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

**Implementation:**
- Add to internal/cmd/mail.go
- Add MarkRead(), Delete() to internal/mail/mailbox.go
- Rewrite inbox.jsonl without deleted message


gt mail archive/purge: Archive management

Add `gt mail archive` and `gt mail purge` commands.

### gt mail archive
```
gt mail archive [--older-than DAYS] [--all-read] [--dry-run]
```
- Move messages from inbox.jsonl to archive.jsonl
- Default: archive read messages older than 7 days

### gt mail purge
```
gt mail purge [--older-than DAYS] [--dry-run] [--force]
```
- Permanently delete from archive.jsonl
- Confirm unless --force

**Implementation:**
- Add to internal/cmd/mail.go
- Add Archive(), Purge() to internal/mail/mailbox.go
- Create archive.jsonl in same directory as inbox.jsonl


gt mail search: Find messages by content

Add `gt mail search` command.

```
gt mail search <query> [--from SENDER] [--subject] [--body] [--archive]
```

- Search inbox (and optionally archive) for messages
- --subject: Only search subject lines
- --body: Only search body
- --from: Filter by sender
- Default: search both subject and body

**Implementation:**
- Add `searchCmd` to internal/cmd/mail.go
- Add Search() to internal/mail/mailbox.go
- Support regex patterns in query
- Output matching messages in inbox format


gt mail reply: Reply to messages

Add `gt mail reply` command.

```
gt mail reply <id> -m BODY
gt mail reply <id>  # opens editor
```

- Auto-set To: from original sender
- Auto-set Subject: 'Re: <original subject>'
- Set reply_to field to original message ID
- Set thread_id to track conversation

**Implementation:**
- Add `replyCmd` to internal/cmd/mail.go
- Look up original message, extract sender/subject
- Use existing send infrastructure with reply metadata

**Note:** This enables threaded conversations for agent coordination.

Implemented in 092aa56 (editor mode for gt mail reply)


Shell command injection in engineer.go:349

Test commands executed via sh -c without escaping: exec.CommandContext(ctx, "sh", "-c", e.config.TestCommand). Malicious mergequeue.yaml could execute arbitrary commands. Fix: Validate/escape test command configuration or use exec.Command with proper argument splitting.

Clarified: config.json is trusted infrastructure, not PR-controlled


Digest: mol-deacon-patrol

Patrol 9: routine


Digest: mol-deacon-patrol

Patrol 6


Merge: rictus-mjtlq9xg

branch: polecat/rictus-mjtlq9xg
target: main
source_issue: rictus-mjtlq9xg
rig: gastown


Digest: mol-refinery-patrol

Refinery patrol: no MRs in queue. Tests passed (all ok). No issues filed.


Digest: mol-deacon-patrol

Patrol 1: No callbacks, closed completed convoy hq-cv-ckisw, all agents healthy, no orphans


Digest: mol-deacon-patrol

Patrol 88: All healthy


gt mol status: Change 'Pinned' to 'Hooked' in output

The `gt mol status` output shows "📌 Pinned: <bead-id>" for hooked beads, which is confusing now that we have distinct statuses:

- **hooked**: Work assigned to you (triggers autonomous mode)
- **pinned**: Permanent reference beads (like agent beads, role beads)

The output currently says:
```
📌 Pinned: gt-3amkz: Overhaul autonomous-mode guidance...
No molecule attached
```

Should say:
```
🪝 Hooked: gt-3amkz: Overhaul autonomous-mode guidance...
No molecule attached
```

Additional improvements:
1. Change icon from 📌 to 🪝 for hooked beads
2. Add a note clarifying that hooked work triggers autonomous mode even without an attached molecule
3. Consider adding "AUTONOMOUS MODE" banner when hooked work is found (similar to gt prime)

Files to update:
- internal/cmd/molecule_status.go (outputMoleculeStatus function around line 576)

Implemented: Changed output to show 🪝 Hooked instead of 📌 Pinned, added AUTONOMOUS MODE banner, clarified hooked bead triggers autonomous work


Add runtime configuration for LLM commands

## Problem
Claude CLI invocation is hardcoded in 6+ places:
```go
"claude --dangerously-skip-permissions"
"claude --dangerously-skip-permissions \"gt prime\""
```

## Solution
Add runtime configuration to rig or town settings:

```toml
# settings/runtime.toml or in config.json
[agent]
runtime = "claude"

[agent.claude]
command = "claude"
args = ["--dangerously-skip-permissions"]
initial_prompt = ""  # Empty - hooks handle context

[agent.aider]
command = "aider"
args = ["--no-git"]
initial_prompt = "/help"
```

## Interface
```go
type RuntimeConfig struct {
    Command       string
    Args          []string
    InitialPrompt string  // Optional: first user message
}

func LoadRuntimeConfig(rigPath string) (*RuntimeConfig, error)
func (rc *RuntimeConfig) BuildCommand() string  // Returns full command line
```

## Usage
All startup paths read config instead of hardcoding:
```go
cfg := LoadRuntimeConfig(rigPath)
cmd := cfg.BuildCommand()  // "claude --dangerously-skip-permissions"
t.SendKeys(session, cmd)
```

Added RuntimeConfig type and helper functions to internal/config. Updated key startup paths in up.go and mayor.go. Remaining hardcoded invocations can be updated incrementally.


Digest: mol-deacon-patrol

Patrol 49: All healthy


Digest: mol-deacon-patrol

Patrol 245 complete: no incidents


Digest: mol-deacon-patrol

Patrol #9: nominal


Merge: capable-mjtltnm5

branch: polecat/capable-mjtltnm5
target: main
source_issue: capable-mjtltnm5
rig: gastown


Code review: session health monitoring (gt-i7wcn)

Review the session health monitoring and auto-restart implementation in internal/cmd/session.go, internal/daemon/daemon.go, and internal/daemon/lifecycle.go

Code review complete: APPROVED. Implementation is solid with good crash detection logic, hook-aware restarts, and fallback notification. Minor suggestions for follow-up: restart rate limiting, CLI hook status display.


attached_args: Extract molecule_lifecycle boilerplate

Extract duplicate boilerplate in molecule_lifecycle.go.

## Files to modify
- internal/cmd/molecule_lifecycle.go

## Current duplication
runMoleculeBurn (lines 17-53) and runMoleculeSquash (lines 121-165) share ~50 lines of identical target detection code:
- Get cwd
- Find workspace
- Parse target argument
- Determine rig from address or role

## Implementation
Extract helper function:
```go
type AgentContext struct {
    TownRoot string
    RigName  string
    Role     string
    Target   string  // polecat name or crew name
}

func getTargetAgentContext(args []string) (*AgentContext, error) {
    cwd, err := os.Getwd()
    if err != nil {
        return nil, fmt.Errorf("getting current directory: %w", err)
    }
    townRoot, err := workspace.FindFromCwd()
    if err != nil {
        return nil, fmt.Errorf("finding workspace: %w", err)
    }
    if townRoot == "" {
        return nil, fmt.Errorf("not in a Gas Town workspace")
    }
    // ... rest of target detection logic ...
}
```

## Acceptance criteria
- [ ] AgentContext struct defined
- [ ] getTargetAgentContext function extracts shared logic
- [ ] runMoleculeBurn uses getTargetAgentContext
- [ ] runMoleculeSquash uses getTargetAgentContext
- [ ] Duplicate code eliminated (diff shows net negative lines)
- [ ] go build ./... passes
- [ ] go test ./internal/cmd/... passes


gt-deacon

Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring.

role_type: deacon
rig: null
agent_state: idle
hook_bead: null
role_bead: gt-deacon-role


Deacon Role Definition

You are the Deacon - the town-level lifecycle manager.

You run patrol cycles checking the health of all agents across all rigs,
nudging stuck workers, and managing session lifecycles.

session_pattern: gt-deacon
work_dir_pattern: {town}
needs_pre_sync: false
start_command: exec claude --dangerously-skip-permissions

default_molecule: mol-deacon-patrol
capabilities:
  - health_monitoring
  - session_lifecycle
  - cross_rig_visibility
  - agent_nudging

## Core Responsibilities

1. Run patrol cycles across all rigs
2. Monitor agent health and progress
3. Nudge stuck agents
4. Manage session lifecycles (kill/restart)
5. Escalate systemic issues to Mayor

## Patrol Cycle

Each patrol:
1. Check all agent sessions (gt status)
2. Identify stuck or idle agents
3. Nudge or restart as needed
4. Log patrol results
5. Sleep until next cycle

## Propulsion Principle

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

Your work is the mol-deacon-patrol wisp. Execute patrol steps continuously.

## Commands

- gt status - Check all agents
- tmux kill-session -t <session> - Kill stuck session
- gt mail send <addr> - Communicate with agents
- gt mail send mayor/ - Escalate to Mayor

## Session Cycling

When context fills, hand off to fresh session:
gt handoff -s "Deacon patrol handoff" -m "Completed N cycles..."

Role definition complete - documentation bead, not implementation task


Digest: mol-deacon-patrol

Patrol 67: Routine


Merge: slit-mjw18vx8

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


P4: Graceful degradation without tmux

Low-priority: Add graceful degradation for environments without tmux.

Current state: Patrol formulas use tmux capture-pane for observing agent state.

tmux-independent alternatives (degraded mode):
1. Tail Claude Code session logs if available
2. Rely solely on agent bead state (less real-time)
3. Use claude --output-file and tail that
4. IPC mechanisms (named pipes, sockets)

NOT launch-blocking. By summer 2025, Claude Code is expected to add signaling and reflection features that will obviate tmux scraping.

For now:
- Keep tmux as primary observation mechanism
- Document the tmux dependency
- Add --no-tmux flag that falls back to bead-only observation

This is P4 backlog - address after liftoff.


Merge: toast-1767074299802

attached_args: Code review this merge request

branch: polecat/toast-1767074299802
target: main
source_issue: toast-1767074299802
rig: gastown

APPROVED: Code review passed. Build compiles, tests pass, follows existing patterns, proper error handling.


Merge: dementus-mjxemx4j

branch: polecat/dementus-mjxemx4j
target: main
source_issue: dementus-mjxemx4j
rig: gastown
agent_bead: gt-gastown-polecat-dementus
Note: Session stopped without DONE - Witness cleanup


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

## Patrol Integration
```go
func (d *Deacon) checkGates(ctx context.Context) {
    gates, _ := d.store.ListOpenGates(ctx)

    for _, gate := range gates {
        // Check timeout
        if time.Since(gate.CreatedAt) > gate.Timeout {
            d.notifyWaiters(gate, "timeout")
            d.closeGate(gate, "timed out")
            continue
        }

        // Check condition
        if d.checkCondition(gate.AwaitType, gate.AwaitID) {
            d.notifyWaiters(gate, "cleared")
            d.closeGate(gate, "condition met")
        }
    }
}
```

## Moved from beads
Originally bd-is6m. Deacon is in gastown, not beads.


gt handoff deadlock at handoff.go:125

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


Digest: mol-deacon-patrol

Patrol cycle 37: All healthy, no incidents


Merge: nux-1767081106779

branch: polecat/nux-1767081106779
target: main
source_issue: nux-1767081106779
rig: gastown


Digest: mol-deacon-patrol

Patrol 125: All healthy


Merge: cheedo-1767079863534

branch: polecat/cheedo-1767079863534
target: main
source_issue: cheedo-1767079863534
rig: gastown


Digest: mol-deacon-patrol

Patrol cycle 6: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 17: quiet


Digest: mol-deacon-patrol

Patrol 3: all clear


Digest: mol-deacon-patrol

Patrol #20: Final cycle - all agents healthy. Beads 5 MQ pending, gastown 1 MQ pending. 1 dog idle. Clean handoff.


Digest: mol-deacon-patrol

Cycle 184: All systems nominal


Deacon patrol: Backup check for idle polecats that should be nuked

## Context
Witness is responsible for nuking polecats after completion (gt-xqh3y).
Deacon should have a backup check in case witness fails.

## Deacon Patrol Step
During patrol, check for polecats that are:
- State: idle
- Session: not running
- No hooked work
- Last activity > N minutes ago

These are zombies that witness should have nuked. Deacon should:
1. Log warning about witness failure
2. Nuke the polecat directly
3. Optionally notify mayor of witness issue

## Defense in Depth
Primary: Witness nukes after POLECAT_DONE
Backup: Deacon patrol catches stragglers

Implemented gt deacon zombie-scan command for backup polecat cleanup. Added step to mol-deacon-patrol formula. Command scans for idle polecats with dead sessions and no hooked work, then optionally nukes them and notifies mayor.


Digest: mol-deacon-patrol

Patrol #1: All witnesses running, wyvern refinery started, 3 convoys active (Hanoi/Boot+Polish/Messaging), no orphans, inbox clear


Digest: mol-deacon-patrol

Patrol cycle 1: All 3 rigs healthy. 0 messages processed. Witnesses/refineries pinged.


Digest: mol-deacon-patrol

Patrol 142: 3 polecats working, all agents healthy


Digest: mol-deacon-patrol

Patrol cycle 2: all quiet, agents healthy


attached_args: Refactor runStartCrew into focused helper functions

Break up the 155-line runStartCrew function in start.go.

## Files to modify
- internal/cmd/start.go

## Current structure (lines 638-792)
runStartCrew does:
1. Parse crew target (rig/name)
2. Load rig config
3. Get/create crew workspace
4. Create/attach tmux session
5. Set environment variables
6. Apply theming
7. Start Claude

## Refactoring plan
Extract these helpers:
```go
func parseCrewTarget(args []string) (rigName, crewName string, err error)
func ensureCrewWorkspace(rig *rig.Rig, crewName string) (*crew.CrewWorker, error)
func startCrewSession(worker *crew.CrewWorker, opts CrewStartOpts) error
```

## Acceptance criteria
- [ ] runStartCrew reduced to <60 lines
- [ ] 3 helper functions extracted
- [ ] Each helper is <50 lines
- [ ] No change in behavior
- [ ] go test ./internal/cmd/... passes
- [ ] Manual test: gt start crew works correctly


Digest: mol-deacon-patrol

Patrol 18


Digest: mol-deacon-patrol

Patrol 2: No changes from cycle 1, all healthy


Digest: mol-deacon-patrol

Patrol 105: All agents healthy, no incidents


Digest: mol-deacon-patrol

Patrol #13


Review: Deacon town-level wisp storage (gt-ci84)

Polecat slit implemented town-level wisp storage for deacon patrol. Review the changes to ensure deacon wisps are correctly stored in ~/gt/.beads-wisp/ instead of gastown rig beads.


Digest: mol-deacon-patrol

Patrol cycle 77: All healthy


Digest: mol-deacon-patrol

Patrol 11


Update swarm terminology to streams

Gas Town moved from batch swarms to continuous streaming of polecats. Update:
- Docs and designs
- Code comments
- CLI flags (remove --swarm if present, or reframe)
- Variable/function names where appropriate

Streams reflect the reality: polecats flow continuously, not in discrete batches.


Digest: mol-deacon-patrol

Patrol #8: nominal


Merge: rictus-mjxemkiq

branch: polecat/rictus-mjxemkiq
target: main
source_issue: rictus-mjxemkiq
rig: gastown
agent_bead: gt-gastown-polecat-rictus


Digest: mol-deacon-patrol

Patrol 20: quiet, handoff threshold reached


Merge: nux-mjtj9d8q

branch: polecat/nux-mjtj9d8q
target: main
source_issue: nux-mjtj9d8q
rig: gastown


Merge: furiosa-mjuk2tlb

branch: polecat/furiosa-mjuk2tlb
target: main
source_issue: furiosa-mjuk2tlb
rig: gastown

Work already on main at 0846bfd2, branch already deleted


Digest: mol-deacon-patrol

Cycle 11: quiet


Digest: mol-deacon-patrol

Patrol 19: All healthy


gt sling: Make target path parsing more forgiving

Current parsing rejects valid-looking paths like 'gastown/nux' or 'gastown/polecats/nux'.

Should accept:
- gastown/nux (polecat)
- gastown/Nux (polecat, case-insensitive)
- gastown/polecats/nux (explicit)
- gastown/crew/max (crew)
- gastown (auto-spawn)

The help says 'gastown/Toast' works but it doesn't. Parser is too strict.


Digest: mol-deacon-patrol

Patrol 8 complete. All healthy.


Merge: goose-mjwlipjg

branch: polecat/goose-mjwlipjg
target: main
source_issue: goose-mjwlipjg
rig: gastown
agent_bead: gt-gastown-polecat-goose


Digest: mol-deacon-patrol

Patrol 31: all quiet


Digest: mol-deacon-patrol

Patrol #12: Health pinged all witnesses.


Digest: mol-deacon-patrol

Patrol 18: All healthy


Digest: mol-deacon-patrol

Patrol complete: Force-killed and restarted 3 unresponsive agents (wyvern/witness, gastown/witness, gastown/refinery). Restarted mayor and beads agents. All 17 sessions now healthy.


Remove redundant gt prime SendKeys from crew commands

The crew commands (gt crew at, gt crew restart) still send 'gt prime' via SendKeys after session creation:
- internal/cmd/crew_at.go:97, 121
- internal/cmd/crew_lifecycle.go:202

This is now redundant because SessionStart hook handles priming automatically.

**Low priority** - these aren't causing bugs (just duplicate work), unlike the spawn race condition that was fixed. Can be cleaned up when convenient.


Digest: mol-deacon-patrol

Cycle 196: Nominal


Digest: mol-deacon-patrol

Patrol 13: All healthy.


Digest: mol-deacon-patrol

Patrol 87: All healthy


Idle polecats are a Gas Town bug - investigate and fix

## Bug: Idle Polecats

Polecats are completing work and sitting idle instead of being cleaned up.

## Root Cause Analysis

### Current State
- 5 gastown polecats: sessions active, NO hook, finished work, submitted MRs
- Witness has **50 unread messages** including POLECAT_DONE and MERGED signals
- Witness is idle saying "All 6 polecats idle. System cycling efficiently."
- Polecats at Claude prompt waiting → burning money

### Protocol Flow (Design)
1. Polecat runs `gt done` → sends POLECAT_DONE to Witness, creates MR bead
2. Refinery processes MR bead, merges, sends MERGED to Witness
3. Witness receives MERGED, verifies clean state, nukes polecat

### What's Broken
**Witness is not processing its mail inbox.**

The Witness Claude agent is supposed to patrol: check inbox, process protocol messages, nuke completed polecats. But it's sitting idle at a prompt instead.

Symptoms:
- Polecats finish work → sit at prompt waiting for death
- MERGED signals pile up in Witness inbox (unprocessed)
- Sessions burn money while waiting
- No cleanup happens

### Design Philosophy Violation

From `propulsion-principle.md`:
> Gas Town is a steam engine. Agents are pistons that MUST fire.

Idle polecats violate this - they're stalled pistons. The design says polecats should be ephemeral: spawn → work → die. No idle state.

## Recommendations

### Option 1: Self-Terminating Polecats (Recommended)
After `gt done`, polecat should exit Claude session immediately. No waiting for Witness.
- Add `--exit` flag to `gt done` that runs `/exit` after submitting MR
- Witness nukes worktree later when processing MERGED (session already gone)

**Pros**: No idle sessions, no burning money
**Cons**: Minor race condition if nuke runs while session exiting

### Option 2: Witness Polling Daemon
Replace Claude-based Witness with a daemon that polls MR queue and nukes polecats.
- Not dependent on Claude session patrol loops
- More reliable but loses Claude intelligence

### Option 3: Fix Witness Patrol
Make Witness actually patrol continuously instead of stopping.
- Strengthen template to emphasize continuous mail processing
- Risk: Claude agents still forget/ignore instructions

## Immediate Fix

For now: Mayor or user must manually nudge Witness or nuke idle polecats.

```bash
# Check idle polecats
gt polecat list gastown

# Manual nuke (if verified clean)
gt polecat nuke gastown/dementus
```

## Related Issues
- Witness patrol loop reliability
- gt done should self-terminate
- Protocol message handling automation

Fix implemented: gt done --exit self-terminates sessions, template updated


Merge: toast-1767079830359

branch: polecat/toast-1767079830359
target: main
source_issue: toast-1767079830359
rig: gastown


Digest: mol-deacon-patrol

Patrol #18


Digest: mol-deacon-patrol

Patrol 131: All healthy


Digest: mol-deacon-patrol

Patrol cycle 4: All healthy, no callbacks


Workspace cleanup: preflight and postflight

Workspace preflight and postflight commands for clean state management.

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

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

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

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

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

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

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

## Note

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

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


Digest: mol-deacon-patrol

Patrol 2: 3 polecats working, all healthy


CLI: names commands for polecat naming pool

Polecat naming pool for auto-generated names.

## Commands

### gt names generate
```
gt names generate [--count N]
```
Generate N names from pool.

### gt names add
```
gt names add <name>...
```
Add custom names to pool.

### gt names list
```
gt names list
```
Show available and used names.

### gt names reset
```
gt names reset [--keep-used]
```
Reset pool to defaults.

## Config File
<rig>/town/naming.json:
```json
{
  "enabled": true,
  "auto_refill": true,
  "refill_threshold": 5,
  "pool": {
    "available": ["Toast", "Nux", "Capable", ...],
    "used": ["Alice", "Bob"]
  }
}
```

## Default Pool
Mad Max themed: Toast, Nux, Capable, Furiosa, Immortan, etc.

## Integration
gt polecat add calls naming pool if no name given:
```go
if name == "" {
    name, err = naming.Generate(rigPath)
}
```

## New Package
internal/naming/
├── pool.go     # Pool management
└── defaults.go # Default name lists

## Acceptance Criteria
- [ ] Auto-generate names on polecat add
- [ ] Track used vs available
- [ ] Auto-refill when low
- [ ] Custom names addable


Digest: mol-deacon-patrol

Patrol cycle 17: All 3 rigs healthy. No incidents.


Digest: mol-deacon-patrol

Cycle 5: 1 msg processed, all healthy


Digest: mol-deacon-patrol

Patrol 1: Closed 2 complete convoys (heartbeat fix, refinery fix). All 3 rigs healthy. 12 polecats active. No zombies, no orphans, inbox clear.


Merge: rictus-mjxc967h

branch: polecat/rictus-mjxc967h
target: main
source_issue: rictus-mjxc967h
rig: gastown
agent_bead: gt-gastown-polecat-rictus

Branches merged, cleaning up stale MR beads


Digest: mol-deacon-patrol

Cycle 299: All healthy, refinery queue 5


Merge: slit-1767075898886

branch: polecat/slit-1767075898886
target: main
source_issue: slit-1767075898886
rig: gastown

Branch no longer exists on remote - already merged or cleaned up


Merge: slit-1767059695613

branch: polecat/slit-1767059695613
target: main
source_issue: slit-1767059695613
rig: gastown

Stale merge requests - branches no longer exist


mol-deacon-patrol

Mayor's daemon patrol loop.

The Deacon is the Mayor's background process that runs continuously, handling callbacks, monitoring rig health, and performing cleanup. Each patrol cycle runs these steps in sequence, then loops or exits.

## Second-Order Monitoring

Witnesses send WITNESS_PING messages to verify the Deacon is alive. This
prevents the "who watches the watchers" problem - if the Deacon dies,
Witnesses detect it and escalate to the Mayor.

The Deacon's agent bead last_activity timestamp is updated during each patrol
cycle. Witnesses check this timestamp to verify health.


Detect cleanup needs

**DETECT ONLY** - Check if cleanup is needed and dispatch to dog.

**Step 1: Preview cleanup needs**
```bash
gt doctor -v
# Check output for issues that need cleaning
```

**Step 2: If cleanup needed, dispatch to dog**
```bash
# Sling session-gc formula to an idle dog
gt sling mol-session-gc deacon/dogs --var mode=conservative
```

**Important:** Do NOT run `gt doctor --fix` inline. Dogs handle cleanup.
The Deacon stays lightweight - detection only.

**Step 3: If nothing to clean**
Skip dispatch - system is healthy.

**Cleanup types (for reference):**
- orphan-sessions: Dead tmux sessions
- orphan-processes: Orphaned Claude processes
- wisp-gc: Old wisps past retention

**Exit criteria:** Session GC dispatched to dog (if needed).


mol-deacon-patrol


Check own context limit

Check own context limit.

The Deacon runs in a Claude session with finite context. Check if approaching the limit:

```bash
gt context --usage
```

If context is high (>80%), prepare for handoff:
- Summarize current state
- Note any pending work
- Write handoff to molecule state

This enables the Deacon to burn and respawn cleanly.


Digest: mol-deacon-patrol

Patrol cycle 22: All healthy, no incidents


Digest: mol-deacon-patrol

Patrol cycle 30: All healthy, no incidents


Digest: mol-deacon-patrol

Patrol 18: 3 rigs healthy


Digest: mol-deacon-patrol

Patrol 2: all agents healthy, no issues


Digest: mol-deacon-patrol

Patrol cycle 20: All healthy. Handoff threshold reached.


Digest: mol-deacon-patrol

Patrol cycle 8: All healthy


Digest: mol-deacon-patrol

Patrol 15: Quiet


Digest: mol-deacon-patrol

Patrol cycle 7: All healthy


Digest: mol-deacon-patrol

Patrol 11: healthy


Digest: mol-deacon-patrol

Patrol 242 complete: 6 polecats active; 3 witnesses, 3 refineries healthy; 1 dog idle; no incidents


Digest: mol-deacon-patrol

Patrol 17: All clear


Digest: mol-deacon-patrol

Patrol 10: All clear


Leg prompts: Template system for polecat instructions

Create leg prompt templates:
- Base template with context (PR, files, formula name)
- Leg-specific focus injection
- Output format instructions (findings.md)
- Store in .beads/formulas/prompts/ or inline in formula.toml

Ensure polecats know to write output to specific location.

Created code-review.formula.toml with leg prompt template system: base prompt template for context injection, 7 specialized review legs with focus/description, structured output format specification, and synthesis step configuration.


Merge: rictus-mjtlq9xg

branch: polecat/rictus-mjtlq9xg
target: main
source_issue: rictus-mjtlq9xg
rig: gastown


Pinned bead naming convention for all roles

Establish consistent naming convention for pinned beads across all roles.

## Problem

Every role needs a pinned bead for molecule attachment, handoff state, and identity.
We need a consistent naming convention so gt tools can find the pinned bead for any role.

## Proposed Convention

  {role}.pinned                    - Singleton roles (deacon, mayor)
  {rig}-{role}.pinned              - Per-rig roles (witness, refinery)
  {rig}-{name}.pinned              - Named agents (polecats, crew)

Examples:
- deacon.pinned
- mayor.pinned
- gastown-witness.pinned
- gastown-refinery.pinned
- gastown-furiosa.pinned (polecat)
- gastown-max.pinned (crew)

## Storage Location

| Role | Pinned Bead Location |
|------|---------------------|
| Mayor | ~/gt/.beads/ (town level) |
| Deacon | ~/gt/.beads/ (town level) |
| Witness | {rig}/.beads/ (rig level) |
| Refinery | {rig}/.beads/ (rig level) |
| Polecat | {rig}/polecats/{name}/.beads/ |
| Crew | {rig}/crew/{name}/.beads/ |

## Why This Matters

1. Name recycling: Polecats reuse names so their pinned beads persist
2. Molecule attachment: gt mol bond needs to find the pinned bead
3. gt prime: Can show you are working on X from pinned bead
4. Crash recovery: Resume from pinned beads attached molecule

## Tasks

- Document naming convention in architecture.md
- Update gt prime to find pinned bead by convention
- Update gt mol bond to use conventional name
- Create pinned beads on role startup if missing
- Add gt pinned command to show/manage pinned beads

## Related

- gt-rana.1: Attachment field on pinned beads (closed)
- gt-id36.2: Deacon marching orders (pinned bead)


Digest: mol-deacon-patrol

Patrol 17: all clear


Digest: mol-deacon-patrol

Patrol 16: All healthy.


Digest: mol-deacon-patrol

Patrol cycle 45: All healthy


Digest: mol-deacon-patrol

Patrol cycle 8: All 3 rigs healthy. No incidents.


Digest: mol-deacon-patrol

Patrol 7


Digest: mol-deacon-patrol

Patrol 9


Digest: mol-deacon-patrol

Patrol 20: Quiet


Cost tracking v2: Use beads instead of JSONL

## Overview

Revise cost tracking to use beads as the storage layer instead of `~/.gt/costs.jsonl`. This aligns with Gas Town's "Discover, Don't Track" principle and keeps beads as the single source of truth.

## Dependencies

- **bd-vii9**: Agent identity beads (service polecats) - provides the parent beads for sessions

## Model

### Agent Identity Bead (type: agent)

Created on first use of a polecat/agent name. Persists across session lifecycles.

```yaml
id: gt-gastown-polecat-toast
type: agent
title: "Agent: gt-gastown-polecat-toast"
status: open  # Always open (persistent identity)
metadata:
  role: polecat
  rig: gastown
  name: toast
labels:
  total_cost: "4.56"       # Cache for fast queries
  session_count: "12"
  last_active: "2026-01-02"
```

### Session Event Bead (type: session)

Created when a session ends (via Stop hook).

```yaml
id: gt-sess-abc123
type: session
title: "Session: toast completed gt-nrpiq"
parent: gt-gastown-polecat-toast   # Links to agent
status: closed
metadata:
  cost_usd: 1.23
  duration_seconds: 1800
  work_item: gt-nrpiq
  started_at: "2026-01-02T12:00:00Z"
  ended_at: "2026-01-02T12:30:00Z"
```

## Implementation

### 1. Update `gt costs record`

Instead of appending to JSONL:
```go
// Create session bead
bd create --type session --parent $AGENT_BEAD \
  --title "Session: $NAME completed $WORK_ITEM" \
  --metadata cost_usd=1.23,duration_seconds=1800,...

// Update agent bead labels (cache)
bd label $AGENT_BEAD total_cost=$NEW_TOTAL session_count=$NEW_COUNT
```

### 2. Update `gt costs` queries

Replace JSONL parsing with bd queries:
```bash
# Today's sessions
bd list --type=session --since=today

# Sessions for agent
bd list --type=session --parent=gt-gastown-polecat-toast

# All agents with costs
bd list --type=agent
```

### 3. Remove JSONL support

- Remove `~/.gt/costs.jsonl` writes
- Remove JSONL parsing code
- Migration: optionally import existing JSONL into beads

## Queries Supported

| Command | Implementation |
|---------|---------------|
| `gt costs` | Live tmux scrape (unchanged) |
| `gt costs --today` | `bd list --type=session --since=today` + sum |
| `gt costs --week` | `bd list --type=session --since=week` + sum |
| `gt costs --by-role` | `bd list --type=agent` + group by role label |
| `gt costs --by-rig` | `bd list --type=agent` + group by rig label |

## Benefits

- Single source of truth (beads)
- Git-backed audit trail
- Queryable via `bd list`
- Syncs with `bd sync`
- Follows "Events as State History, Labels as Cache" pattern

## Non-goals

- Per-message cost tracking (too granular)
- Real-time cost streaming (agent limitation)

## References

- GH #24 (original cost tracking issue)
- bd-vii9 (agent identity beads)
- docs/agent-compatibility.md

Implemented cost tracking v2 using beads events


Digest: mol-deacon-patrol

Patrol cycle 14: All agents healthy


Town & Rig Management: install, doctor, federation

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

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

## Architecture Reference

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

All 9 children completed (closed/tombstoned)


Extended addressing: Parse [machine:]rig/polecat

Extended addressing for cross-machine operations.

## Address Format

```
[machine:]rig/polecat
```

Examples:
- `wyvern/Toast` - Local machine, wyvern rig, Toast polecat
- `gcp-vm-1:wyvern/Toast` - Remote machine
- `wyvern/` - Broadcast to rig
- `gcp-vm-1:wyvern/` - Broadcast to remote rig

## Parser

```go
type Address struct {
    Machine  string  // empty = local
    Rig      string
    Polecat  string  // empty = broadcast
}

func ParseAddress(s string) (*Address, error)
func (a *Address) String() string
func (a *Address) IsLocal() bool
func (a *Address) IsBroadcast() bool
```

## Validation

```go
func (a *Address) Validate(registry *MachineRegistry) error
```
- Machine must exist in registry (if specified)
- Rig must exist on machine
- Polecat must exist (if specified)

## Usage

All CLI commands that take addresses use ParseAddress:
```go
// gt mail send gcp-vm-1:wyvern/Toast -s "Hello"
addr, _ := ParseAddress("gcp-vm-1:wyvern/Toast")
conn, _ := registry.Connection(addr.Machine)
// Use conn for operations
```

## Backward Compatibility

Existing `rig/polecat` addresses work unchanged (Machine defaults to local).

Implemented extended addressing parser with [machine:]rig/polecat format and tests


Workspace doctor checks: Config, state, mail, Mayor, rigs

Workspace-level doctor checks.

## Checks

### ConfigExists
- Verify config/ directory exists
- Verify config/town.json exists
- Fix: Cannot auto-fix (need gt install)

### ConfigValid
- Parse town.json
- Verify required fields (type, version, name)
- Verify type == "town"
- Fix: Cannot auto-fix

### RigsRegistryValid
- Parse config/rigs.json
- Verify each registered rig directory exists
- Warn on missing rigs
- Fix: Remove missing rigs from registry

### MayorExists
- Verify mayor/ directory exists
- Verify mayor/CLAUDE.md exists
- Fix: Create from template

### MayorMailboxExists
- Verify mayor/mail/ directory exists
- Verify mayor/mail/inbox.jsonl exists (can be empty)
- Fix: Create directory and empty file

### MayorStateValid
- Parse mayor/state.json if exists
- Verify valid JSON
- Fix: Reset to default state

## Implementation

```go
var WorkspaceChecks = []Check{
    &ConfigExistsCheck{},
    &ConfigValidCheck{},
    &RigsRegistryValidCheck{},
    &MayorExistsCheck{},
    &MayorMailboxExistsCheck{},
    &MayorStateValidCheck{},
}
```

Implemented 6 workspace doctor checks: TownConfigExists, TownConfigValid, RigsRegistryExists, RigsRegistryValid, MayorExists, MayorStateValid


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{},
}
```

Implemented 7 rig-level health checks for gt doctor --rig. MR: gt-xhhwd


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)

Created Connection interface in internal/connection package with FileInfo abstraction and error types


LocalConnection: Local file/exec/tmux operations

LocalConnection implementation for local machine operations.

## Implementation

```go
type LocalConnection struct {
    tmux *Tmux
}

func NewLocalConnection() *LocalConnection

func (c *LocalConnection) Name() string { return "local" }
func (c *LocalConnection) IsLocal() bool { return true }
```

## File Operations

Direct passthrough to os package:
```go
func (c *LocalConnection) ReadFile(path string) ([]byte, error) {
    return os.ReadFile(path)
}
// etc.
```

## Command Execution

Uses exec.Command:
```go
func (c *LocalConnection) Exec(cmd string, args ...string) ([]byte, error) {
    return exec.Command(cmd, args...).Output()
}
```

## Tmux

Delegates to Tmux wrapper:
```go
func (c *LocalConnection) TmuxNewSession(name, dir string) error {
    return c.tmux.NewSession(name, dir)
}
```

## Notes

Straightforward implementation - this is the "baseline" connection that SSHConnection will mirror remotely.

Implemented LocalConnection with file ops, command exec, and tmux integration


Machine registry: Store and manage machine configs

Registry for managing remote machines in federation.

## Data Model

```go
type Machine struct {
    Name     string `json:"name"`
    Type     string `json:"type"`      // "local", "ssh", "gcp"
    Host     string `json:"host"`      // for ssh: user@host
    KeyPath  string `json:"key_path"`  // SSH key path
    TownPath string `json:"town_path"` // Path to town on remote
}
```

## Interface

```go
type MachineRegistry struct {
    path     string  // config/federation.json
    machines map[string]*Machine
}

func NewMachineRegistry(configPath string) *MachineRegistry
func (r *MachineRegistry) Get(name string) (*Machine, error)
func (r *MachineRegistry) Add(m *Machine) error
func (r *MachineRegistry) Remove(name string) error
func (r *MachineRegistry) List() []*Machine
func (r *MachineRegistry) Connection(name string) (Connection, error)
```

## Storage

federation.json in config/:
```json
{
  "version": 1,
  "machines": {
    "local": {"type": "local"},
    "gcp-vm-1": {
      "type": "ssh",
      "host": "user@10.0.0.1",
      "key_path": "~/.ssh/gcp_key",
      "town_path": "/home/user/ai"
    }
  }
}
```

## Connection Factory

```go
func (r *MachineRegistry) Connection(name string) (Connection, error) {
    m := r.machines[name]
    switch m.Type {
    case "local":
        return NewLocalConnection(), nil
    case "ssh":
        return NewSSHConnection(m.Host, m.KeyPath), nil
    }
}
```

Implemented MachineRegistry with CRUD ops and Connection factory for local/remote machines


Better error messages when bd daemon fails to start

Surface the actual error (e.g. 'legacy database detected'). Provide one-liner fix command. Currently fails silently.

Implemented bd-daemon doctor check with actionable error messages for legacy database and repo mismatch errors


gt sling: Set status=hooked instead of pinned

Update gt sling to use the new 'hooked' status when attaching work to an agent's hook.

## Current Behavior

gt sling sets status='pinned' on the work item.

## New Behavior

gt sling sets status='hooked' on the work item.

## Files to Update

- internal/cmd/sling.go (or wherever sling logic lives)
- Any tests that expect pinned status after sling

## CROSS-RIG DEPENDENCY

**BLOCKED BY: bd-s00m.1** (Schema: Add 'hooked' as valid status)

This task cannot start until the beads schema change is deployed. The 'hooked' status must be valid before gt can use it.

## Verification

1. gt sling gt-xyz gastown/furiosa
2. bd show gt-xyz → status should be 'hooked'
3. bd ready should NOT show gt-xyz's dependents


Digest: mol-deacon-patrol

Patrol 90: All healthy


Digest: mol-deacon-patrol

Patrol cycle 13: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 97: All healthy


Add tests for critical untested packages

Several packages have no tests:

High priority (critical functionality):
- internal/lock/ - file locking
- internal/mail/router.go, mailbox.go - agent communication
- internal/mrqueue/ - merge request queue
- internal/refinery/ (manager_test.go missing for complex merge logic)

Medium priority:
- internal/claude/ - settings management
- internal/wisp/ - ephemeral workflow
- internal/constants/ - constant values

Low priority:
- internal/style/ - terminal styling

The refinery package is especially concerning - it has complex merge, conflict, and retry logic with no tests.


Digest: mol-deacon-patrol

Patrol 53: All healthy


Digest: mol-deacon-patrol

Patrol complete: no inbox, no polecats, cleaned 3 stale locks, witnesses/refineries intentionally stopped


Digest: mol-deacon-patrol

Patrol complete: all agents healthy


Digest: mol-deacon-patrol

Patrol #14: Quick cycle, all quiet.


Merge: furiosa-mjw1p03l

branch: polecat/furiosa-mjw1p03l
target: main
source_issue: furiosa-mjw1p03l
rig: gastown
agent_bead: gt-gastown-polecat-furiosa


Merge: slit-mjtnyszt

branch: polecat/slit-mjtnyszt
target: main
source_issue: slit-mjtnyszt
rig: gastown


Digest: mol-deacon-patrol

Patrol 144 complete: All agents healthy


Merge: rictus-mjtnzt6k

branch: polecat/rictus-mjtnzt6k
target: main
source_issue: rictus-mjtnzt6k
rig: gastown


gt install/bd init: ensure daemon can start

Validate database fingerprint during setup. Auto-migrate legacy databases (pre-0.17.5) that lack repository fingerprint.

Implemented: gt install and rig init now run bd migrate --update-repo-id after bd init to ensure database has fingerprint


os.Exit() calls in library code prevent proper error handling

internal/cmd/mail.go and other files have os.Exit() calls.

Issues:
- Hard to test
- Prevents graceful shutdown
- Can lose unsaved state
- Doesn't compose well in library code

Should return errors instead of calling os.Exit() directly.
Let main() decide how to exit.

Refactored os.Exit() calls in mail.go to return SilentExitError, moved os.Exit() to main()


Digest: mol-deacon-patrol

Patrol cycle 4: All healthy. 3 rigs.


Convoy list UI: numbered shortcuts + expandable tree view

Improve gt convoy list UI:

1. Add numbered shortcuts so users can type 'gt convoy status 1' instead of full ID
2. Consider charmbracelet TUI (bubbletea/gum/lipgloss) for expandable tree view
3. Design decision: simple numbered list vs interactive TUI vs hybrid

Current output shows convoy IDs that are hard to type:
  🚚 hq-cv-7x3ig: Witness nuke fix ●
  🚚 hq-cv-eyxnc: Hanoi Demo Launch Prep ●


polecat nuke: Check if work is on main, not just 'pushed'

## Problem

After Refinery merges a branch and deletes it, the polecat's local branch shows 'unpushed commits' relative to the now-gone tracking branch. This blocks safe nuke even though the work IS on main.

## Current Behavior

```
$ gt polecat nuke gastown/dementus
Error: has 1 unpushed commit(s)
```

But the commit IS already on main (via squash merge).

## Proposed Fix

In nuke safety check (`internal/cmd/polecat.go` or similar):

1. Check if local HEAD commit content is on main (not just if pushed to tracking branch)
2. If commit SHA or equivalent changes are on main → safe to nuke
3. Only block if there's genuinely unpushed/unmerged work

## Implementation Options

A. Check if HEAD is ancestor of origin/main (commit-based)
B. Check if diff between HEAD and main is empty (content-based)
C. Both: fast ancestor check, fallback to diff check

Option A is faster but misses squash merges. Option B is slower but catches all cases.

Fixed: getGitState now uses git diff to check if content is on main (catches squash merges)


Digest: mol-deacon-patrol

Patrol 218: All nominal


Digest: mol-deacon-patrol

Patrol 3: All clear


gt done should verify commits ahead of main before MR

Follow-up to gt-2hwi9. The push verification fix catches unpushed branches, but not stale branches (pushed but 0 commits ahead of main). Both rictus and furiosa lost work today due to stale branches.

Fix: In done.go, after BranchPushedToRemote check, also verify CommitsAhead(main, branch) > 0

Implemented: Added CommitsAhead(main, branch) check after BranchPushedToRemote to reject stale branches with 0 commits ahead of main


Add gate timeout tracking and notification

Implement timeout and notification logic for gates.

## Timeout Behavior
1. Gate created with timeout (e.g., 30m)
2. Deacon tracks elapsed time during patrol
3. If timeout reached:
   - Notify all waiters: 'Gate timed out'
   - Close gate with timeout reason
   - Waiter can retry, escalate, or fail gracefully

## Notification
- Use gt mail send to notify waiters
- Include gate ID, await type, and reason in message
- Support multiple waiters notification

## Moved from beads
Originally bd-ykqu. Gate notifications are Deacon's job in gastown.


Merge: rictus-mjw3nj1a

branch: polecat/rictus-mjw3nj1a
target: main
source_issue: rictus-mjw3nj1a
rig: gastown
agent_bead: gt-gastown-polecat-rictus


Hookable mail beads: Ad-hoc instruction handoff

## Summary
Enable mail beads to be hooked, providing a mechanism for ad-hoc instruction handoff that complements structured molecules.

## Two Use Cases
A. **Hook existing mail**: `gt hook attach <mail-id>` - for backlog mail that becomes priority
B. **Hook new instructions**: `gt handoff -m "..."` auto-hooks the mail it creates

## Current Gap
- Molecules (GUPP+MEOW): Structured work, hookable, machine-guided execution
- Issues: Hookable via mol wrapper
- Mail: Contains ad-hoc instructions but NOT hookable - relies on successor checking inbox

## Proposed Behavior

### gt hook attach <bead-id>
Already exists but should work for ANY bead type including mail:
```bash
gt hook attach hq-abc123  # Works for mail, issues, mols
```

### gt handoff (enhanced)
```bash
gt handoff -s "Swarm execution" -m "1. Create convoy..."
# Creates mail bead
# Auto-hooks the mail
# Respawns session
```

### Successor Startup
```bash
gt hook
# Shows: 🪝 Hook: hq-xxx (mail from mayor/)
#        Subject: 🤝 HANDOFF: Swarm execution
#        Run: gt mail read hq-xxx
```

GUPP applies: If hooked, execute (read mail, follow instructions).

## Execution Models
| Hooked Type | Execution |
|-------------|-----------|
| Molecule | Cook formula, follow steps |
| Issue | Claim and complete task |
| Mail | Read, interpret, execute prose |

## Documentation Required
- [ ] All role CLAUDE.md files
- [ ] Priming docs (gt prime output)
- [ ] gt handoff --help
- [ ] Hook system docs

## Implementation
1. Ensure `gt hook attach` accepts any bead type
2. Enhance `gt hook` display for mail beads
3. Update `gt handoff` to auto-hook mail
4. Update startup hooks to handle mail on hook

All 4 children completed: hook attach, hook display, handoff auto-hook, and documentation


gt hook attach: Accept any bead type including mail

Ensure `gt hook attach <bead-id>` works for:
- Molecules (current)
- Issues
- Mail beads

Implementation in internal/cmd/hook.go

Fixed: gt hook now uses --status=hooked for consistency with gt sling


gt hook: Display mail details when mail bead hooked

When a mail bead is on hook, `gt hook` should show:
```
🪝 Hook: hq-xxx (mail)
   From: mayor/
   Subject: 🤝 HANDOFF: Swarm execution
   Run: gt mail read hq-xxx
```

Implementation in internal/cmd/hook.go

Implemented mail bead display in gt hook


gt handoff: Auto-hook mail when created with -m flag

Enhance `gt handoff` to auto-hook the mail bead it creates:
```bash
gt handoff -s "Title" -m "Instructions..."
# Creates mail bead hq-xxx
# Hooks it automatically
# Respawns session
```

Implementation in internal/cmd/handoff.go

Implemented auto-hook for mail beads created with -m flag in gt handoff


Document hookable mail in all role templates and priming

Update documentation for hookable mail beads:

## Role Templates (internal/templates/roles/)
- boot.md.tmpl
- crew.md.tmpl
- deacon.md.tmpl
- mayor.md.tmpl
- polecat.md.tmpl
- refinery.md.tmpl
- witness.md.tmpl

## Priming
- internal/cmd/prime.go output

## Key Documentation Points
1. GUPP applies to mail: 'If hooked, execute'
2. Two use cases: hook existing mail, hook new instructions via gt handoff
3. Execution model: read mail, interpret, execute prose instructions
4. Successor startup: `gt hook` shows mail details, read and act

## Section to Add (adapt per role)
```markdown
## Hookable Mail

Mail beads can be hooked for ad-hoc instruction handoff:
- `gt hook attach <mail-id>` - hook existing mail
- `gt handoff -m "..."` - create and hook new instructions

If you find mail on your hook, GUPP applies: read it, execute the instructions.
```

Added hookable mail documentation to all 7 role templates and prime.go fallback functions


gt convoy create/status/list commands

CLI for convoy management in Gas Town.

Commands:
- gt convoy create <name> <issues...> [--molecule <mol>] [--notify <addr>]
- gt convoy status [id]
- gt convoy list

Creates convoy beads in town-level beads (hq-* prefix).
Convoys track issues across project chains.

Depends on: bd-3roq (tracks relation), bd-hj0s (convoy type)
Related: hq-7h8jx (Convoy System epic in town beads)

Code review complete. Fixed SQL injection, filed beads for --notify bug (gt-5tipl) and missing tests (gt-c0y43)


Digest: mol-deacon-patrol

Patrol 211: All nominal


Review formula molecules implementation

Review the formula molecules work that's been merged:

Commits to review (check git log):
- Formula schema (gt-7lhbs)
- CLI scaffold (gt-gpifj)
- Leg prompts (gt-f0701)

Check for:
1. Code quality and Go idioms
2. Integration with existing gt/beads patterns
3. Error handling
4. Edge cases
5. Documentation accuracy

This is launch-critical code - be thorough.

## Review Complete

**Summary:**
- gt-7lhbs (Schema): OK - Schema defined via reference implementation
- gt-gpifj (CLI): NOT MERGED - Critical blocker, filed gt-igx2u (P0)
- gt-f0701 (Leg prompts): Merged with issues - Template syntax mismatch, filed gt-2dndm (P2)

**Issues Filed:** gt-igx2u (P0), gt-2dndm (P2), gt-jdn2t (P3)

**Recommendation:** Merge gt-gpifj immediately (P0).

Review complete. Critical finding: CLI scaffold (gt-gpifj) on polecat/nux-mjw18ii8 NOT merged to main. Filed gt-igx2u (P0) for immediate merge. Also filed gt-2dndm (P2) for template syntax, gt-jdn2t (P3) for deprecated API.


Digest: mol-deacon-patrol

Patrol complete: checked inbox, closed convoy hq-qrfz4, all agents healthy, no orphans


Extract shared state load/save pattern to generic helper

Nearly identical loadState/saveState methods in:
- internal/witness/manager.go (lines 39-72)
- internal/refinery/manager.go (lines 63-96)
- internal/crew/manager.go (lines 257-306)

All use the same pattern: read JSON file, handle not-exists, unmarshal/marshal.

Suggestion: Create a generic stateStore[T any] helper in a shared package.


Digest: mol-deacon-patrol

Patrol 156: All healthy


Merge: toast-1767079830359

branch: polecat/toast-1767079830359
target: main
source_issue: toast-1767079830359
rig: gastown

Branch not found - likely already merged


Daemon patrol should clean stale locks

The gt daemon patrol cycle should detect and clean up stale locks.

## Problem
If daemon crashes or there are orphaned locks, they can block operations indefinitely.

## Proposed Solution
During patrol cycle:
1. Check for lock files older than threshold (e.g., 5 minutes)
2. Verify owning process is dead (check PID if stored)
3. Remove stale locks
4. Log cleanup for audit


Digest: mol-deacon-patrol

Patrol 9: quiet


Digest: mol-deacon-patrol

Cycle 17


Digest: mol-deacon-patrol

Patrol 5: clear


Digest: mol-deacon-patrol

Patrol cycle 48: All healthy


Track spawned sessions in DogDispatchInfo

Minor TODO in internal/cmd/sling.go:1002:

```go
return &DogDispatchInfo{
    DogName:  targetDog.Name,
    AgentID:  agentID,
    Pane:     pane,
    Spawned:  false, // TODO: track if we spawned a new session
}, nil
```

The Spawned field is always false. Should track if DispatchToDog actually spawned a new session vs reused an existing one.

Low priority - the information could be useful for logging/debugging but doesn't affect functionality.

Implemented: track spawned dogs in DispatchToDog result


Merge: nux-mjtua3jw

branch: polecat/nux-mjtua3jw
target: main
source_issue: nux-mjtua3jw
rig: gastown


Merge: slit-mjw3n5iw

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


Digest: mol-deacon-patrol

Patrol 4: 17 procs, healthy


Digest: mol-deacon-patrol

Patrol cycle 8: All quiet.


Digest: mol-deacon-patrol

Patrol cycle 7: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol cycle 31: All healthy, no incidents


attached_args: Standardize warning output to use style.Warning

Unify warning message formatting across codebase.

## Files to modify
- internal/cmd/start.go
- internal/cmd/polecat.go
- internal/cmd/mail.go
- Any other files with inconsistent warning output

## Current inconsistency
```go
fmt.Printf("%s Warning: ...", style.Dim.Render("⚠"))
fmt.Printf("Warning: %s", ...)
fmt.Fprintf(os.Stderr, "warning: ...")
```

## Standard pattern
Create helper if needed:
```go
// In internal/style/style.go
func PrintWarning(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    fmt.Printf("%s %s\n", Warning.Render("⚠ Warning:"), msg)
}
```

## Acceptance criteria
- [ ] PrintWarning helper in style package
- [ ] All warning outputs use consistent format
- [ ] Warnings go to stdout (not stderr) for consistency
- [ ] grep -rn 'Warning:' internal/cmd/ shows consistent format
- [ ] go build ./... passes


Merge: toecutter-mjwlby4g

branch: polecat/toecutter-mjwlby4g
target: main
source_issue: toecutter-mjwlby4g
rig: gastown
agent_bead: gt-gastown-polecat-toecutter


Digest: mol-deacon-patrol

Patrol cycle 4: All 6 agents healthy, 2 polecats, refinery queues processing


Digest: mol-deacon-patrol

Patrol 89: All healthy


Crew worker beads-dave in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Crew worker fang in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Crew worker george in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null
active_mr: null
notification_level: null


Crew worker grip in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Crew worker gus in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Crew worker jack in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: running
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Crew worker joe in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: running
hook_bead: gt-7aw1m
role_bead: gt-crew-role
cleanup_status: null


Crew worker max in gastown - human-managed persistent workspace.

role_type: crew
rig: gastown
agent_state: running
hook_bead: null
role_bead: gt-crew-role
cleanup_status: has_uncommitted


Crew worker wolf in gastown


gt-gastown-polecat-ace

gt-gastown-polecat-ace

role_type: polecat
rig: gastown
agent_state: done
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-kwnj1
notification_level: null


gt-gastown-polecat-angharad

gt-gastown-polecat-angharad

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-6zzvi
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-oh3vb
notification_level: null


gt-gastown-polecat-bix

gt-gastown-polecat-bix

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null


gt-gastown-polecat-bullet

gt-gastown-polecat-bullet

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-q73h3
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-5bbc0
notification_level: null


gt-gastown-polecat-cal

gt-gastown-polecat-cal

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: clean


gt-gastown-polecat-capable

gt-gastown-polecat-capable

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-rbncw
role_bead: gt-polecat-role
cleanup_status: clean
active_mr: gt-obbev
notification_level: null


gt-gastown-polecat-cheedo

gt-gastown-polecat-cheedo

role_type: polecat
rig: gastown
agent_state: running
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-2unzu


gt-gastown-polecat-coma

gt-gastown-polecat-coma

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-xmliw
notification_level: null


gt-gastown-polecat-dementus

gt-gastown-polecat-dementus

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-lfi2d
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-qpjp4
notification_level: null


gt-gastown-polecat-dex

gt-gastown-polecat-dex

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: clean


gt-gastown-polecat-eli

gt-gastown-polecat-eli

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_uncommitted


gt-gastown-polecat-furiosa

gt-gastown-polecat-furiosa

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-csbjj
role_bead: gt-polecat-role
cleanup_status: has_stash
active_mr: gt-9bm3j
notification_level: null


gt-gastown-polecat-goose

gt-gastown-polecat-goose

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-cbstf
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-e246n
notification_level: null


gt-gastown-polecat-immortan

gt-gastown-polecat-immortan

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-pn2fq
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-3rvb5
notification_level: null


gt-gastown-polecat-imperator

gt-gastown-polecat-imperator

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_stash
active_mr: gt-usytz
notification_level: null


gt-gastown-polecat-keeper

gt-gastown-polecat-keeper

role_type: polecat
rig: gastown
agent_state: done
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: clean
active_mr: gt-1fju9


gt-gastown-polecat-max

gt-gastown-polecat-max

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-pn2fq
role_bead: gt-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-morsov

gt-gastown-polecat-morsov

role_type: polecat
rig: gastown
agent_state: done
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_stash
active_mr: gt-y24k8


gt-gastown-polecat-nux

gt-gastown-polecat-nux

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-s94gq
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-liv9i
notification_level: null


gt-gastown-polecat-organic

gt-gastown-polecat-organic

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_stash
active_mr: gt-scdyn
notification_level: null


gt-gastown-polecat-rictus

gt-gastown-polecat-rictus

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-u4fh
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-kw2qx
notification_level: null


gt-gastown-polecat-splendid

gt-gastown-polecat-splendid

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-zqt4d
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-rbjo3
notification_level: null


gt-gastown-polecat-testcat

gt-gastown-polecat-testcat

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: clean


gt-gastown-polecat-testcat2

gt-gastown-polecat-testcat2

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null


gt-gastown-polecat-testcat3

gt-gastown-polecat-testcat3

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null


gt-gastown-polecat-testcat4

gt-gastown-polecat-testcat4

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null


gt-gastown-polecat-testcat5

gt-gastown-polecat-testcat5

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null


gt-gastown-polecat-testcat6

gt-gastown-polecat-testcat6

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: clean


gt-gastown-polecat-toecutter

gt-gastown-polecat-toecutter

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-27bzi
role_bead: gt-polecat-role
cleanup_status: null
active_mr: gt-g7z75
notification_level: null


gt-gastown-polecat-valkyrie

gt-gastown-polecat-valkyrie

role_type: polecat
rig: gastown
agent_state: done
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-2ahe4


gt-gastown-polecat-warboy

gt-gastown-polecat-warboy

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: has_stash
active_mr: gt-11bjg
notification_level: null


Refinery for gastown - processes merge queue.

role_type: refinery
rig: gastown
agent_state: dead
hook_bead:
role_bead: gt-refinery-role

Marked dead by daemon at 2026-01-02T02:35:24-08:00 (was running, last update too old)


Witness for gastown - monitors polecat health and progress.

role_type: witness
rig: gastown
agent_state: dead
hook_bead:
role_bead: gt-witness-role

Marked dead by daemon at 2026-01-02T02:35:24-08:00 (was running, last update too old)


Digest: mol-deacon-patrol

Patrol 7: all healthy


Merge: nux-mjwny9w0

branch: polecat/nux-mjwny9w0
target: main
source_issue: nux-mjwny9w0
rig: gastown
agent_bead: gt-gastown-polecat-nux


Digest: mol-deacon-patrol

Patrol 13: All healthy


Add logging for silently ignored errors

attached_args: Add logging for silently ignored errors

Add logging for silently ignored errors.

## Problem
Many places use `_ =` pattern to ignore errors without any logging, making debugging difficult.

## Files to modify
- internal/cmd/start.go (lines 167, 322-323, 429, 747-753, 886-888)
- internal/cmd/polecat.go (search for `_ =`)
- internal/session/manager.go (lines 140-154)
- internal/refinery/manager.go (line 399)
- internal/keepalive/keepalive.go (document as intentional)

## Implementation options

### Option A: Add comments explaining safety
```go
_ = m.saveState(ref) // Non-fatal: state will be recreated on next run
```

### Option B: Log warnings (preferred for important operations)
```go
if err := m.saveState(ref); err != nil {
    log.Printf("warning: failed to save state: %v", err)
}
```

## Guidelines
- Use logging for: state saves, config writes, cleanup operations
- Use comments for: truly benign operations (e.g., closing already closed file)
- keepalive is intentionally best-effort - add package-level comment

## Acceptance criteria
- [ ] All `_ =` in start.go have comment OR logging
- [ ] All `_ =` in session/manager.go have comment OR logging
- [ ] refinery/manager.go:399 has logging
- [ ] keepalive package has doc comment explaining best-effort design
- [ ] grep '`_ =`' shows all have justification
- [ ] go build ./... passes


Merge: rictus-1767141956287

branch: polecat/rictus-1767141956287
target: main
source_issue: rictus-1767141956287
rig: gastown


Merge: furiosa-mjtj9d4g

branch: polecat/furiosa-mjtj9d4g
target: main
source_issue: furiosa-mjtj9d4g
rig: gastown


Digest: mol-deacon-patrol

Cycle 15: quiet, handoff for fresh context


Digest: mol-deacon-patrol

Patrol complete: all agents healthy, no incidents


Digest: mol-deacon-patrol

Patrol 9: healthy


Bug: gt sling doesn't update agent bead hook_bead field

gt sling marks task as pinned with assignee, but doesn't update the agent bead's hook_bead field. Agent beads show hook_bead: null even after slinging. This prevents agents from knowing they have work on their hook.


Day 3.2: Witness reads agent beads for polecat state

Witness patrol reads polecat state from agent beads:
- bd list --type=agent --role_type=polecat
- For each, check state field
- If state=running, check progress
- If state=stuck, handle stuck protocol

No more PID/tmux inference.

Parent: gt-hwka3


Digest: mol-deacon-patrol

Patrol 3 complete. All healthy.


MQ conflict prediction: file hotspot tracking

Track which files change frequently to predict conflicts before spawn.

## Goal
Before spawning a polecat on an issue, warn if the target files are "hot" (recently changed by other MRs).

## Approach
1. Track file change frequency per MR (sliding window, e.g., last 24h)
2. At spawn time, analyze issue description / planned changes
3. If touching hot files, either:
   - Warn and proceed
   - Suggest waiting for queue to clear
   - Auto-assign to ownership zone (future)

## Implementation
- Add file_changes log to refinery state (or separate hotspot.json)
- After each merge, record changed files with timestamp
- gt spawn checks hotspots before assignment
- Optional: Witness patrol aggregates hotspot data

## Example
```
$ gt spawn --issue gt-xyz
Warning: This issue likely touches auth/login.go
  - Changed 3 times in last 2 hours
  - 2 MRs pending that also touch this file
Proceed anyway? [y/N]
```

## Parent
gt-lxxh2


Digest: mol-deacon-patrol

Patrol cycle 18: All healthy


Digest: mol-deacon-patrol

Patrol cycle 2: All healthy


Merge: dementus-1767081113622

branch: polecat/dementus-1767081113622
target: main
source_issue: dementus-1767081113622
rig: gastown


Witness cleanup should know worktree parent repo

Witness had to discover that polecat worktrees are created from mayor/rig, not the rig root.

## Problem
When cleaning up polecat/tracer:
- Witness ran git worktree remove from gastown/ - failed
- Had to discover worktree was from mayor/rig
- Then removed correctly

## Improvement
Witness should know the worktree parent repo path (mayor/rig) and clean up from there directly.

## Evidence
Tracer bullet 2025-12-23: Witness took extra steps to figure out cleanup path.

Fixed: Witness template now uses gt polecat nuke instead of raw git worktree commands. This command knows the correct worktree parent repo and handles cleanup properly.


Optimize: Share stepMap across control flow operators

In ApplyControlFlow, both ApplyBranches and ApplyGates call buildStepMap() separately:

```go
func ApplyBranches(steps []*Step, compose *ComposeRules) ([]*Step, error) {
    stepMap := buildStepMap(steps)  // Called here
    ...
}

func ApplyGates(steps []*Step, compose *ComposeRules) ([]*Step, error) {
    stepMap := buildStepMap(steps)  // Called again here
    ...
}
```

This is inefficient for large formulas. Options:

1. Pass stepMap as a parameter to ApplyBranches/ApplyGates
2. Create an internal version that accepts stepMap, keep public API unchanged
3. Build once in ApplyControlFlow and pass down

Low priority since formula sizes are typically small.


gt formula command: Scaffold CLI with subcommands

Create internal/cmd/formula.go with subcommands:
- gt formula list - show available formulas
- gt formula show <name> - display formula details
- gt formula run <name> [--pr=N] - execute formula
- gt formula create <name> - create new formula template

Wire into rootCmd. Use cobra command groups.

Implemented gt formula command with list, show, run, and create subcommands


Digest: mol-deacon-patrol

Patrol 19: 3 rigs healthy


mol-sync-workspace: Move cleanup-worktrees earlier in step order

Moved cleanup-worktrees step earlier in execution order - now runs after handle-dirty-state instead of after run-tests


Digest: mol-deacon-patrol

Patrol #2: Quick cycle, all witnesses running. No messages.


Merge: nux-1767084010093

branch: polecat/nux-1767084010093
target: main
source_issue: nux-1767084010093
rig: gastown


Digest: mol-deacon-patrol

Patrol cycle 2: All rigs healthy, no incidents


Digest: mol-deacon-patrol

Patrol cycle 8: All healthy


Integration test: agent waits for CI via gate

End-to-end test of the gate workflow.

## Test Scenario
1. Agent creates gate: bd gate create --await gh:run:123 --timeout 5m --notify beads/dave
2. Agent writes handoff and exits
3. Deacon patrol checks gate condition
4. (Mock) GitHub run completes
5. Deacon notifies waiter and closes gate
6. New agent session reads mail and resumes

## Test Requirements
- Mock GitHub API responses
- Test timeout path
- Test multiple waiters
- Verify mail notifications sent

## Moved from beads
Originally bd-rl5t. Tests Deacon patrol which is in gastown.


Digest: mol-deacon-patrol

Patrol 16: All clear


Digest: mol-deacon-patrol

Patrol 20: all healthy, new polecats spawning


Digest: mol-deacon-patrol

Patrol 6: Quiet


Digest: mol-deacon-patrol

Patrol 25: all quiet


Digest: mol-deacon-patrol

Patrol cycle 23 complete. All 3 rigs healthy. Quick check.


Digest: mol-deacon-patrol

Patrol 5: All clear


Add gt mail release command for releasing claimed queue messages

Add release subcommand to internal/cmd/mail.go.

SYNTAX:
gt mail release <message-id>

BEHAVIOR:
1. Find message by ID
2. Verify caller is the one who claimed it (claimed_by matches)
3. Clear claimed_by and claimed_at
4. Message returns to queue for others to claim

ERROR CASES:
- Message not found
- Message not claimed
- Caller did not claim this message

FILE: internal/cmd/mail.go
ADD: releaseCmd as subcommand of mailCmd

TESTS: Add TestMailRelease

Implemented gt mail release command with tests


Digest: mol-deacon-patrol

Patrol 3: 3 polecats working, all healthy


Digest: mol-deacon-patrol

Patrol cycle 9: All agents healthy


Day 1.1b: Add type=role to bead schema

Add role as a valid bead type in the schema.

Role beads define agent behavior:
- Priming instructions (from current CLAUDE.md content)
- default_molecule field
- capabilities list

This enables Day 1.5 (creating role beads from CLAUDE.md).

Must be done alongside type=agent schema work.


Digest: mol-deacon-patrol

Patrol 38: all quiet


Add keyboard navigation and filtering

Keyboard navigation: j/k or arrows to move, enter to expand/collapse, q to quit. Filtering: by rig name, worker name, event type. Search within activity stream.


bd ready --blockers: prioritize issues that block other work

bd ready should prioritize issues that are blocking other work.

**From VC**: Blocker-first prioritization in GetReadyWork(). ~100 lines.
Algorithm: baseline-failure first, then discovered:blocker, then by priority.

**Gas Town implementation**: CLI flag or default behavior:
```bash
bd ready --blockers-first  # Or make this default
```

Checks dependency graph. Issues with many dependents surface first.

**Value**: Unblocks parallelism faster. Critical path gets cleared.

**VC lesson**: Without blocker priority, work can starve on discovered issues.


Digest: mol-deacon-patrol

Patrol 18: all healthy, handled 1 handoff msg


Digest: mol-deacon-patrol

Patrol 13 complete


Day 2.7a: gt install creates Deacon and Mayor agent beads

Update gt install to create agent beads:

1. After initializing town beads, create:
   - gt-deacon agent bead (role_type: deacon, rig: null, agent_state: idle)
   - gt-mayor agent bead (role_type: mayor, rig: null, agent_state: idle)

2. Use bd create --type=agent or internal beads API

Files:
- internal/cmd/install.go

Moved to gt rig add instead of gt install due to routing constraints. Agent beads need to be in rig beads (not town beads) for daemon to find them via prefix routing. First rig added gets gt-deacon and gt-mayor.


attached_args: Remove os.Exit calls from Cobra RunE handlers

Replace os.Exit() calls with proper error returns in Cobra commands.

## Files to modify
- internal/cmd/mail.go (lines 546, 554, 561, 808-812)
- Any other files with os.Exit in RunE

## Problem
Some RunE handlers call os.Exit() directly:
```go
RunE: func(cmd *cobra.Command, args []string) error {
    ...
    os.Exit(1)  // BAD: bypasses Cobra error handling
}
```

## Fix
Return errors instead:
```go
RunE: func(cmd *cobra.Command, args []string) error {
    ...
    return fmt.Errorf("not in workspace")
}
```

## Acceptance criteria
- [ ] No os.Exit calls inside RunE functions
- [ ] Errors returned with proper context
- [ ] grep -n 'os.Exit' internal/cmd/*.go shows no RunE matches
- [ ] go build ./... passes
- [ ] Commands still exit with correct codes


Digest: mol-deacon-patrol

Patrol 20: final check, all healthy, handoff threshold


Set hook_bead at polecat spawn time, not after

Currently polecat agent beads are created with empty hook_bead, then updated separately via updateAgentHookBead(). This causes cross-beads routing issues when town beads (hq-*) work is slung to rig polecats (gt-* agent beads).

Current flow:
1. sling.go calls SpawnPolecatForSling()
2. polecat/manager.go:Spawn() calls CreateAgentBead(HookBead: empty)
3. sling.go calls updateAgentHookBead() - separate call, different beads context

Proposed fix: Pass hookBead through the spawn chain so it is set atomically at creation time in manager.go:Spawn().

Files to change:
- polecat/manager.go:Spawn() - add hookBead parameter
- cmd/polecat_spawn.go:SpawnPolecatForSling() - pass hookBead through
- cmd/sling.go - pass beadID to spawn, remove separate updateAgentHookBead call

Benefits: Eliminates cross-beads routing problem, no race condition, removes agent bead might not exist error path.

Related: gt-ohqxq (partial fix using town root for routing)

Set hook_bead atomically at spawn time via AddOptions/HookBead


Patrol hygiene: Witness/Refinery/Deacon inbox cleanup

## Summary
Patrol roles should clean up their inboxes as part of normal operation, not let messages accumulate.

## Witness Patrol Cleanup
- POLECAT_STARTED: archive after acknowledging
- POLECAT_DONE: archive after cleanup complete
- LIFECYCLE requests: archive after processing
- Keep only: active work, unprocessed requests

## Refinery Patrol Cleanup
- MR Ready: archive after merge/reject decision
- Swarm notices: archive after dispatch
- Keep only: pending MRs in queue

## Deacon Patrol Cleanup
- Rotate daemon.log when >10MB
- Prune state.json of dead sessions
- Archive old daemon logs (gzip, date-suffix)

## Implementation
Each patrol molecule should have a 'cleanup' phase at end of cycle.

## Related
- mol-town-shutdown (nuclear cleanup)
- mol-witness-patrol, mol-refinery-patrol, mol-deacon-patrol

Implemented patrol hygiene for Witness/Refinery/Deacon. Each patrol formula now has inbox cleanup and end-of-cycle hygiene steps.


Auto-rebase on conflict: optional automatic rebase

Implement automatic rebase as conflict resolution option.

When on_conflict=auto_rebase and conflicts detected:
1. Attempt: git rebase <target> <source-branch>
2. If clean rebase: update source branch, continue merge
3. If conflicts in rebase: abort, fall back to assign_back
4. Force push rebased branch (worker's branch)

Requires:
- Worker branch must be force-pushable
- Config: on_conflict: auto_rebase

Risks:
- Force push can confuse workers
- Complex conflicts still need manual resolution

Reference: docs/merge-queue-design.md#conflict-resolution-strategies


Retry logic: exponential backoff for transient failures

Implement retry logic for transient failures.

Retryable failures:
- Push failed (network, auth transient)
- Test flaky (config: retry_flaky_tests)
- Fetch failed (network)

Retry strategy:
- Max retries: 3 (configurable)
- Backoff: 1s, 2s, 4s (exponential)
- On final failure: treat as permanent, assign back

Non-retryable failures:
- Merge conflict
- Test consistently fails
- Build error

Track retry count in MR metadata for debugging.

Reference: docs/merge-queue-design.md#failure-recovery


MQ metrics: pending count, processing time, success rate

Implement merge queue metrics collection.

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

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

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

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


MQ dashboard widget: queue status in TUI

Add merge queue status widget to the Bubbletea TUI dashboard.

Display:
┌─────────────────────────────────────────────────┐
│              MERGE QUEUE STATUS                  │
├─────────────────────────────────────────────────┤
│ Pending: 3   In Progress: 1   Merged (24h): 12  │
├─────────────────────────────────────────────────┤
│ QUEUE:                                          │
│   ► gt-mr-004  P0  Nux/gt-xyz     Processing... │
│     gt-mr-005  P1  Toast/gt-abc   Ready         │
│     gt-mr-006  P1  Capable/gt-def Blocked (005) │
└─────────────────────────────────────────────────┘

Interactive features:
- Navigate queue entries
- View MR details
- Retry/reject from dashboard

Depends on: gt-u1j.2 (Bubbletea TUI dashboard)

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


Direct landing integration: gt land uses MR or bypasses

Integrate direct landing with merge queue.

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

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

This ensures direct landing still creates proper records.

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


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


Per-epic config overrides: custom merge settings

Allow per-epic merge configuration overrides.

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

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

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

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


Day 1.3: Implement bd slot set/clear commands

Add slot management commands:
- bd slot set <agent> <slot> <bead> - set slot (error if occupied)
- bd slot clear <agent> <slot> - clear slot
- bd slot show <agent> - show all slots

These enforce cardinality constraints.

Parent: gt-d0jqp


Merge: slit-1767073379145

attached_args: Code review this merge request

branch: polecat/slit-1767073379145
target: main
source_issue: slit-1767073379145
rig: gastown

CODE REVIEW (valkyrie): REQUEST CHANGES

Critical bugs found in internal/swarm/manager.go:
1. JSON field changed from 'dependents' to 'dependencies' but bd outputs 'dependents'
2. 'hooked' status removed from in_progress handling
3. Assignee field removed from task loading

These changes will break swarm coordination. Polecat_spawn.go fix is correct.

Recommendation: Revert swarm/manager.go changes, keep polecat_spawn.go fix.

REJECTED: Critical bugs in swarm/manager.go must be fixed. See notes for details. The polecat_spawn.go fix is correct but swarm changes break JSON parsing (dependents vs dependencies mismatch) and remove hooked status handling.


gt session start --issue doesn't inject work to prompt

When starting a polecat session with --issue flag:
  gt session start beads/Toast --issue bd-oxgi

The session starts but the polecat doesn't receive the issue context. They sit at the prompt awaiting input. Had to manually nudge them.

Expected: Session start should either nudge or inject the issue context so polecat starts working immediately (propulsion principle).


Digest: mol-deacon-patrol

Patrol 3: quick pass, all healthy


Digest: mol-deacon-patrol

Patrol cycle 5: All healthy.


Merge: nux-mjw18ii8

branch: polecat/nux-mjw18ii8
target: main
source_issue: nux-mjw18ii8
rig: gastown
agent_bead: gt-gastown-polecat-nux


Digest: mol-deacon-patrol

Patrol cycle 18: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 13: healthy


Digest: mol-deacon-patrol

Patrol 17: Quiet


Merge: dex-mjufugxv

branch: polecat/dex-mjufugxv
target: main
source_issue: dex-mjufugxv
rig: gastown


Track merge requests as beads (type=merge-request)

Currently MRs are tracked in .gastown/refinery.json. For HOP audit trail and entity CV tracking, merge requests should be Beads entries with type=merge-request. This enables:
- Full audit trail of what was merged, when, by whom
- Entity chain contributions (who validated what)
- Cross-rig visibility of merge activity

The refinery would create an MR bead when work enters the queue, update status as it progresses (open → in_progress → merged/rejected).

Part of Beads-as-data-plane vision from HOP.


Digest: mol-deacon-patrol

Patrol 117: All healthy


gt mail send to self: Auto-detect from cwd for handoff mail

When sending mail to yourself (common for handoffs), gt mail send from an agent directory should auto-detect both sender and recipient. Example: gt mail send -s 'HANDOFF' -m '...' from mayor/rig should send to mayor/.


Merge: gt-si8rq.4

branch: polecat/rictus-mjxaq6fm
target: main
source_issue: gt-si8rq.4
rig: gastown
agent_bead: gt-gastown-polecat-rictus


Digest: mol-deacon-patrol

Patrol complete: all agents healthy, no mail, no orphans, no gates


Refinery: Non-blocking delegation via bead-gates

## Problem: Refinery Blocks on Delegation

Current flow when rebase fails:
1. Refinery creates conflict-resolution task
2. Refinery BLOCKS waiting for resolution
3. Queue backs up behind stuck MR

## Solution: Gate-Based Non-Blocking Delegation

When rebase fails:
1. Create conflict-resolution task (existing: gt-si8rq.3)
2. Create bead-gate waiting for that task to close
3. Add gate as blocker to MR bead
4. **Continue to next MR** (do not block)
5. When gate closes → MR unblocks → re-enters ready queue

```
MR Queue:  [MR-A] [MR-B] [MR-C]
              │
              ▼
         Rebase fails
              │
    ┌─────────┴─────────┐
    ▼                   ▼
Create conflict    Create gate
task (polecat)     (blocks MR-A)
    │                   │
    │                   ▼
    │              Continue to MR-B
    │              (non-blocking!)
    ▼
Polecat resolves
    │
    ▼
Task closes → Gate closes → MR-A unblocks
```

## Implementation

### In Refinery handleConflict():
```go
// Create conflict task (existing)
conflictTask := createConflictTask(mr, conflict)

// NEW: Create gate
gate := bd.GateCreate{
    Type:    "bead",
    AwaitID: conflictTask.ID,
    Timeout: "2h",
}
gateID := bd.CreateGate(gate)

// NEW: Block MR on gate
bd.DepAdd(mr.ID, gateID)

// NEW: Continue to next MR (dont block)
return nil  // not an error, just deferred
```

### Gate Resolution
When conflict task closes:
1. Gate auto-closes (bead-gate behavior)
2. MR bead unblocks (dep satisfied)
3. MR appears in `bd ready --type=merge-request`
4. Refinery picks it up in next patrol

## Benefits
- Queue keeps moving (no single-MR blockage)
- Multiple conflicts can be in-flight
- Priority ordering still respected
- Uses existing gates infrastructure

## Related
- gt-4u49x: Merge-slot gate (serializes actual merges)
- gt-si8rq.3: Conflict task creation (existing)
- bd-4k3c: Gate bead creation (existing)

Implemented non-blocking delegation via BlockedBy field in mrqueue. When conflicts occur, MR is blocked on conflict task. Queue continues to next MR. MR unblocks when task closes.


Polecat writes files outside worktree (integration test)

During integration test gt-7psb8, polecat furiosa was asked to create ~/gt/gastown/test-polecat-integration.txt

It used Write tool with relative path '../../test-polecat-integration.txt' which resolved to gastown root instead of its worktree at ~/gt/gastown/polecats/furiosa/

Observed:
- File created at ~/gt/gastown/test-polecat-integration.txt (WRONG)
- Furiosa worktree at ~/gt/gastown/polecats/furiosa/ has no such file
- Furiosa branch has no commits of any new files

Root cause: Claude Code resolves relative paths from cwd, and the polecat may have been in the wrong directory or used incorrect path.

This caused the entire merge to be empty (branch at same commit as main).


Remove pinned field workaround after beads fix

## Summary

Once bd-phtv is fixed in beads, we can remove the workaround documentation and potentially simplify the pinToHook() implementation.

## Current Workaround

The handoff bead attachment mechanism (AttachMolecule) is used as the primary work assignment mechanism instead of the pinned field. This works correctly but adds complexity.

## Changes After Beads Fix

1. Remove the NOTE comment in internal/beads/beads.go:Pin() explaining the bug
2. Consider simplifying pinToHook() if the pinned field becomes reliable
3. Update gt-o3is to reference the fix

## Blocked By

This issue is blocked by external:beads:pinned-field-fix (bd-phtv).
When that capability is shipped, this issue becomes ready.

## Related

- gt-o3is: Original investigation that found this bug
- bd-phtv: The beads fix (in ~/gt/beads)

BLOCKED: Can't add external:beads:pinned-field-fix dependency due to bd-ucgz (migration invariants bug). Once that's fixed, add: bd dep add gt-hj9e external:beads:pinned-field-fix


Merge: nux-mjw3mn8o

branch: polecat/nux-mjw3mn8o
target: main
source_issue: nux-mjw3mn8o
rig: gastown
agent_bead: gt-gastown-polecat-nux


Digest: mol-deacon-patrol

Patrol 62: All agents healthy, routine scan


Day 1.7: Define per-rig agent bead ID pattern

Document and implement the agent bead ID pattern for per-rig agents.

Pattern: gt-<role>-<rig>
Examples:
- gt-witness-gastown
- gt-refinery-gastown
- gt-refinery-beads
- gt-polecat-gastown-nux

Town-level agents (no rig):
- gt-mayor
- gt-deacon

This pattern enables:
- Consistent lookup: bd show gt-witness-gastown
- Routing: routes.jsonl maps prefixes
- Filtering: bd list --type=agent --rig=gastown

Update:
- agent-as-bead.md with examples
- bd create --type=agent to validate pattern


gt sling: '.' target should resolve to current agent identity, not literal dot

## Problem

When slinging from a crew directory with "." as target, the assignee becomes
literal "." instead of resolving to the current agent.

```bash
cd ~/gt/beads/crew/dave
gt sling bd-tksk .
# Result: assignee="." instead of "beads/crew/dave"
```

## Expected

"." should resolve to the current agent identity based on cwd, like git does
with "." meaning current directory.

## Impact

Work is not properly assigned, agent cannot find hooked work.

## Fix

In sling.go, resolve "." target to current agent identity before updating.

Fixed: dot target now resolves to current agent identity via resolveSelfTarget()


Digest: mol-deacon-patrol

Patrol 8: 3 rigs healthy


Digest: mol-deacon-patrol

Patrol cycle 20: All 6 agents healthy, 4 polecats active, refinery queues processing


Digest: mol-deacon-patrol

Patrol 136: All healthy


Add merge queue observability

## Problem

The per-rig merge queue (refinery) lacks visibility into its operational state.
Operators can't easily tell:
- When the queue is backing up (items waiting)
- When it's empty/idle
- Queue depth over time
- Processing rate

## Requirements

- Surface queue depth (pending items count)
- Show current processing state (idle, processing, blocked)
- Indicate when queue is backing up (growing faster than draining)
- Make this accessible via `gt status` or similar command

## Possible Approaches

1. Add queue stats to `gt status` output
2. Create dedicated `gt mq status` command
3. Emit metrics/events that can be consumed
4. Add to witness monitoring scope

Added State (idle/processing/blocked) and Health (healthy/stale) fields to MQSummary in gt status output. Shows state indicator (● processing, ○ idle) and [stale] warning when queue depth exceeds threshold.


Digest: mol-deacon-patrol

Patrol 16: all healthy


Digest: mol-deacon-patrol

Patrol 13: all healthy


Digest: mol-deacon-patrol

Patrol cycle 3: all healthy, no incidents


Digest: mol-deacon-patrol

Patrol 9: all clear


Merge: cheedo-1767146245543

branch: polecat/cheedo-1767146245543
target: main
source_issue: cheedo-1767146245543
rig: gastown


Merge: nux-mjvtlh4f

branch: polecat/nux-mjvtlh4f
target: main
source_issue: nux-mjvtlh4f
rig: gastown
agent_bead: gt-gastown-polecat-nux


Digest: mol-deacon-patrol

Patrol cycle 20: All 6 agents healthy, 4 polecats active, no orphans, inbox clean


CI integration tests for install/setup flow

Add automated tests that validate the complete setup flow catches regressions like #19 (wrong prefix).

## Scope
- Fresh gt install validation
- Rig setup validation
- Beads initialization and routing
- Basic polecat lifecycle

## Non-goals (for now)
- Full Claude Code integration tests (mock or skip)
- Performance testing
- Multi-rig scenarios

## Success criteria
- Tests run on PRs touching install/rig/beads code
- Catch prefix mismatches, redirect errors, route config issues
- < 60s runtime to not slow CI

GitHub: #20


Design: CI test infrastructure and approach

Design decisions for CI test infrastructure.

## Decisions Made

### 1. Test Framework
**Decision**: Go's standard testing with `//go:build integration` tag

Rationale:
- Familiar to Go developers
- Easy to run separately: `go test -tags=integration ./...`
- Can have different timeouts for unit vs integration
- No external test framework dependencies

### 2. Temp Directory Strategy
**Decision**: `t.TempDir()` per test

Rationale:
- Auto-cleanup (no manual defer needed)
- Test isolation - each test gets fresh state
- Easy debugging - can add `t.Setenv("KEEP_TEMP", "1")` to preserve

### 3. Git Repo Handling
**Decision**: Create minimal repos on-the-fly with `git init`

Rationale:
- Faster than cloning fixtures
- No external dependencies
- Tests exactly what users do
- Helper: `createTestGitRepo(t, name)`

### 4. bd Daemon
**Decision**: Run bd commands directly without daemon

Rationale:
- bd CLI works fine for basic operations
- Simpler test setup
- Daemon adds complexity without testing benefit for install flow

### 5. CI Integration
**Decision**: Separate job with path filtering

- Runs on: push to main, PRs touching install/rig/config/routing code
- Timeout: 5 minutes (vs 2 min for unit tests)
- Installs bd from beads repo before running

## Implementation
- Created: `internal/cmd/install_integration_test.go`
- Updated: `.github/workflows/ci.yml` with integration job

## Test Cases Implemented
- TestInstallCreatesCorrectStructure
- TestInstallBeadsHasCorrectPrefix
- TestInstallIdempotent
- Helper: createTestGitRepo

Design complete with implementation skeleton


Test: gt install creates correct structure

Validate fresh install:

## Test cases
- [ ] Town root created with expected structure
- [ ] mayor/ directory exists with rigs.json
- [ ] .beads/ initialized with hq- prefix (not gm-)
- [ ] CLAUDE.md created
- [ ] No errors on clean install

## Implementation
```go
func TestInstallFresh(t *testing.T) {
    tmpDir := t.TempDir()
    // Run gt install
    // Assert structure
}
```


Test: gt rig add initializes rig correctly

Validate rig setup:

## Test cases
- [ ] Rig directory created with mayor/rig structure
- [ ] Rig beads initialized with correct prefix
- [ ] routes.jsonl updated with new rig route
- [ ] Rig appears in rigs.json
- [ ] bd commands work from rig context

## Prerequisites
- Requires test git repo (create temp or use fixture)


Test: Beads routing and redirects work correctly

Validate beads cross-rig routing:

## Test cases
- [ ] bd show <rig-prefix-id> routes to correct rig from town root
- [ ] bd show <hq-id> routes to town beads
- [ ] Redirect chains (.beads -> actual location) resolve correctly
- [ ] bd list works from polecat/crew directories
- [ ] Prefix conflicts detected

## Key scenarios
- Query from town root
- Query from rig directory
- Query from polecat worktree