gt spawn should use bd mol run for molecule attachment

Simplify Gas Town to use bd mol run for work tracking.

## Key Insight
Two distinct mechanisms, not duplicative:

| Mechanism | Purpose | Query |
|-----------|---------|-------|
| **Pinned molecule** (bd mol run) | What am I working on? | `bd list --pinned --assignee=me` |
| **Handoff mail** | Context notes for restart | `gt mail read` (self-addressed) |

The handoff is just **mail to yourself** - optional context notes.
The molecule is **the actual work** - required state.

## Current State (Overengineered)
Gas Town has custom attachment system:
- Permanent "Foo Handoff" pinned beads per identity
- AttachMolecule(pinnedBeadID, moleculeID)
- Attachment fields parsed from description
- Separate from beads pinning

## New Model (Simplified)
Use bd mol run directly:

```bash
# Spawn polecat with molecule
bd mol run mol-polecat-work --var issue=gt-xyz
# This: spawns, assigns to caller, pins root, sets in_progress
```

Query current work:
```bash
bd list --pinned --assignee=gastown/furiosa --status=in_progress
```

Handoff context (when needed):
```bash
gt mail send gastown/furiosa -s "Context notes" -m "Was on step 4..."
```

## Changes Required

### Remove from Gas Town
- AttachMolecule() / DetachMolecule()
- AttachmentFields struct and parsing
- GetAttachment() / SetAttachmentFields()
- Permanent pinned handoff beads per identity
- Daemon attachment detection (checkDeaconAttachment)

### Update gt spawn
```go
// Old: custom molecule instantiation + attachment
// New: just call bd mol run
cmd := exec.Command("bd", "mol", "run", protoID, "--var", "issue="+issueID)
```

### Update gt prime / agent context
```go
// Old: find handoff bead, parse attachment
// New: query for pinned molecule
cmd := exec.Command("bd", "list", "--pinned", "--assignee="+identity, "--status=in_progress", "--json")
```

### Update documentation
- Remove handoff bead attachment docs
- Clarify: handoff = mail, molecule = work
- Update CLAUDE.md templates

## Benefits
1. One system for work tracking (beads)
2. Simpler Gas Town code
3. bd mol squash works naturally
4. Handoff is just mail (already works)

## Related
- gt-3x0z: Wisp Molecule Integration
- gt-rana: Patrol System
- gt-lek6: gt rig reset --stale
- gt-ay1r: gt molecule current


beads: Stop searching upward when .beads found

## Problem

When running bd commands in a nested directory structure with multiple .beads directories, bd shows a confusing warning:

```
╔══════════════════════════════════════════════════════════════════════════╗
║ WARNING: 2 beads databases detected in directory hierarchy              ║
╠══════════════════════════════════════════════════════════════════════════╣
║ Multiple databases can cause confusion and database pollution.          ║
║                                                                          ║
║   /Users/stevey/gt/gastown/.beads (261 issues)                           ║
║   /Users/stevey/gt/.beads (21 issues)                                    ║
║                                                                          ║
║ WARNING: Not using the closest database! Check your BEADS_DB setting.   ║
║                                                                          ║
║ RECOMMENDED: Consolidate or remove unused databases to avoid confusion. ║
╚══════════════════════════════════════════════════════════════════════════╝
```

## Why This Is Wrong

In Gas Town, nested .beads directories are **intentional and necessary**:
- Town level: /Users/stevey/gt/.beads (mail, town-level issues)
- Rig level: /Users/stevey/gt/gastown/.beads (gastown project issues)
- Worker level: polecats have their own beads in worktrees

These are **unrelated** beads instances for different scopes. They should never be consolidated.

## Expected Behavior

When bd finds a .beads directory, it should:
1. Use that directory (closest ancestor wins)
2. **Stop searching upward** - do not look for parent .beads directories
3. **No warning** about multiple databases

## Current Behavior

bd searches all the way up to root, finds all .beads directories, and warns about "multiple databases" even though they are separate, intentional instances.

## Fix

In the database discovery code, stop the upward search as soon as a .beads directory is found. The first .beads found is the one to use, and parent directories are out of scope.

## Note

This is a beads issue, filed here for tracking. Should be implemented in the beads codebase.


gt session capture: Support positional line count argument

Make 'gt session capture gastown/Toast 50' work.

Currently requires: gt session capture gastown/Toast -n 50
Should also accept: gt session capture gastown/Toast 50

Agent UX principle: commands should work the way agents guess they work.


Merge: gt-3x1

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


Polecat Mood Plugin (standard)

Standard witness plugin that assesses polecat emotional state.

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

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

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

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

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


Pinned Beads: Ongoing concerns and anchors

Pinned beads represent persistent concerns that do not close traditionally. Stay out of bd ready. Examples: Monitor production, Weekly syncs. Implementation: pinned: true field on bead.


gt mail send should tmux-notify recipient

## Problem

When mail is sent via gt mail send, the recipient session does not get a tmux notification. In Python Gas Town (PGT), mail delivery triggers a tmux display-message or similar notification so the agent knows mail arrived.

## Expected Behavior

When gt mail send <addr> is called:
1. Mail is delivered to recipient inbox
2. If recipient has an active tmux session, send notification
3. Notification should be visible (display-message or bell)

## Current Behavior

Mail is delivered but no notification. Agent has to poll inbox to discover new mail.

## Impact

- Agents miss time-sensitive messages
- Heartbeat pokes from daemon will not wake agents
- Coordination is slower (polling vs push)

## Implementation Notes

After successful mail delivery, check if recipient has active session:
- gt session list can identify active sessions
- tmux display-message or send-keys can notify
- Could inject a visible prompt like "[MAIL] New message from <sender>"

## Reference

Check PGT implementation for how it handles this.


Molecule format bridge: convert embedded markdown to child issues

## Problem

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

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

## Options

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

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

## Recommendation

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


Merge: gt-3x1.4

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


Auto-handoff: self-cycling with guaranteed work pickup

## Summary

A mechanism for agents to self-cycle: send handoff mail, request Deacon to kill the process, and automatically restart to pick up the work.

## Use Case

User says: `auto-handoff: gt-bug3`

The agent:
1. Sends handoff mail documenting current state
2. Requests Deacon to terminate the session
3. Deacon restarts the agent in background
4. New session picks up the handoff and continues working

When the user returns to terminal, the old session is gone but work continues in background. User can reattach.

## Key Requirements

- **Guaranteed pickup**: Something in the hook and/or handoff must ensure the new session picks up the work (not just a passive "check inbox")
- **Background execution**: Work continues without user presence
- **Reattachability**: User can reconnect to see progress

## Design Questions

1. Can molecules help coordinate this? (e.g., molecule binds the handoff + auto-restart)
2. What hook handles the pickup guarantee?
3. How does Deacon know to restart vs just kill?
4. How does user reattach to a background agent?

## Related

- Molecules system
- Deacon lifecycle management
- Handoff hooks


Session management: start, stop, attach, capture

Polecat session lifecycle management.

## Interface

```go
type SessionManager struct {
    tmux *Tmux
    rig  *Rig
}

func NewSessionManager(tmux *Tmux, rig *Rig) *SessionManager

// Lifecycle
func (sm *SessionManager) Start(polecat string, opts StartOptions) error
func (sm *SessionManager) Stop(polecat string) error
func (sm *SessionManager) IsRunning(polecat string) (bool, error)
func (sm *SessionManager) List() ([]SessionInfo, error)

// Interaction
func (sm *SessionManager) Attach(polecat string) error
func (sm *SessionManager) Capture(polecat string, lines int) (string, error)
func (sm *SessionManager) Inject(polecat string, message string) error

type StartOptions struct {
    WorkDir   string   // defaults to polecat clone dir
    Issue     string   // optional: issue to work on
    Command   string   // optional: override claude command
}

type SessionInfo struct {
    Polecat   string
    SessionID string
    Running   bool
    StartedAt time.Time
}
```

## Session Naming

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

## Start Flow

1. Verify polecat clone exists
2. Check no existing session
3. Create tmux session in polecat workdir
4. Send initial command: `claude` or custom
5. If issue provided, inject initial prompt

## Stop Flow

1. Capture final output (for logging)
2. Send exit/quit command
3. Wait briefly for graceful shutdown
4. Kill session if still running

## Capture

Uses tmux capture-pane to get recent output. Returns last N lines.

## Inject

Sends text to session via tmux send-keys. Used for:
- Mail notifications
- Witness nudges
- User messages


Digest: mol-deacon-patrol

Test patrol cycle - verifying wisp flow


load-context

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

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


Swarm learning: Spawn should auto-notify polecats

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


gt spawn should create fresh polecat worktree, never reuse

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

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

## Current Behavior

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

## Desired Behavior

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

## Name Pool Still Useful

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

But worktrees should be created fresh each time.

## Implementation

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

This ensures polecats always start with latest code and beads.


Merge: gt-rana.4

branch: polecat/ace
target: main
source_issue: gt-rana.4
rig: gastown


submit-merge

Submit to merge queue. Create branch if needed.
Verify CI passes.

gt done  # Signal work ready for merge queue

If there are CI failures, fix them before proceeding.

Depends: rebase-main


gt doctor: detect orphaned code on beads-sync branch

After merging beads-sync to main, some code changes can be lost if the merge conflict resolution drops files.

Add a doctor check that runs:
  git diff main..beads-sync -- '*.go' '*.md'

If there are differences in code files (not just .beads/), warn about potentially orphaned work.

Today's incident: Merge 96c773f lost mailbox.go and router.go changes from 5791752, requiring re-implementation.

Acceptance:
- gt doctor warns if beads-sync has unmerged code changes
- Excludes .beads/ directory (expected to differ)
- Shows file list of orphaned changes


Merge: gt-rana.3

branch: polecat/dementus
target: main
source_issue: gt-rana.3
rig: gastown


gt handoff: Deadlock in waitForRetirement


Direct test

Testing direct bd create


Patrol step: check parked molecules for unblock

Add patrol step to Deacon for checking parked molecules:

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

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

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

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


Remove bd sync instruction from polecat startup workflow

Polecats are instructed to run `bd sync --from-main` on startup (spawn.go:634).

## Problem
- Spawn command already syncs beads before spawning (line 239)
- Polecats share rig-level beads via `.beads/redirect`
- Multiple polecats starting simultaneously all try to sync same shared beads
- This causes git conflicts/failures when many polecats spawn at once

## Observed
User reported: 'all polecats failing on beads sync on startup in one run'

## Fix
Remove line 634 from buildWorkAssignmentMail():
```
body.WriteString("2. Run \`bd sync --from-main\` to get fresh beads\n")
```

Polecats only need to sync at END of work (already in steps 5/7).

## Files
- internal/cmd/spawn.go: buildWorkAssignmentMail() and buildSpawnContext()


Digest: mol-deacon-patrol

Old test patrol cycle - cleanup


context-check

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


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


🤝 HANDOFF: Timing fixes applied

Fixed mayor attach timing bug. Completed: timing fix, gt-tulx, Emma beads-6v2. Remaining P1: gt-17zr, gt-kcee, gt-szsq. Run gt prime on startup.


CLI: rig commands (list, show, add)

Rig management CLI commands.

## Commands

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

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

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

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

## Implementation

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


plugin-run

Execute plugins from ~/gt/plugins/. Check gates, run eligible plugins.


Mayor restart loop: auto-prime and mail check on attach

When mayor session cycles (after /exit, gt mayor attach reconnects), the new session lacks context.

## Current Behavior
- Mayor exits, shell script loop restarts claude
- New session starts cold - no gt prime, no mail check
- Mayor is disoriented, doesn't know prior context
- Strong 'antimemetic properties' - we discuss fixing it, then forget

## Expected Behavior
After restart, mayor should automatically:
1. Run gt prime (load role context)
2. Check gt mail inbox (find handoff messages)
3. Look for 🤝 HANDOFF messages from predecessor

## Possible Fixes
1. Startup hook in Claude Code settings that runs gt prime
2. CLAUDE.md instructions that say 'FIRST THING: run gt prime'
3. Shell script wrapper that injects context before attach
4. Modify gt mayor attach to inject prime command

## Related
- gt-vci: Mayor handoff mail template
- gt-sye: Mayor startup protocol prompting


self-review

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

Fix any issues found before proceeding.

Depends: implement


Molecule composition: Includes directive

Add support for molecule composition via the Includes directive.

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

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

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

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

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


Review and audit all GGT beads

Thorough review of all filed beads in gastown GGT repo. Check for: consistency, completeness, correct dependencies, accurate descriptions, proper prioritization. Ensure beads are self-contained and dont rely on external docs.


Spawn inject: Enter not submitted after multiline paste

When gt spawn injects a multiline context (85+ lines), Claude Code shows
'[Pasted text #1 +85 lines]' but the prompt is not submitted.

The tmux SendKeys function does include 'Enter' but it appears to not work
for long pasted text:

```go
func (t *Tmux) SendKeys(session, keys string) error {
    _, err := t.run("send-keys", "-t", session, keys, "Enter")
    return err
}
```

Possible fixes:
1. Use tmux paste-buffer instead of send-keys for long text
2. Send Enter separately after the text
3. Use bracketed paste mode correctly

Reproduction:
```bash
gt spawn gastown/Nux --issue gt-u1j.13 --create
# Session shows pasted text but waits at prompt
```


Merge: gt-pyqv

branch: polecat/dementus
target: main
source_issue: gt-pyqv
rig: gastown


Git safety audit module

Port git_audit.py safety checks to Go.

## Purpose

Before destroying polecat clones, verify no code is at risk.

## Checks

1. Uncommitted changes (staged and unstaged)
2. Unpushed commits
3. Git stashes
4. Local-only branches (not tracking remote)

## Beads-Only Classification

Some files are safe to discard:
- .beads/*
- .claude/settings.json

Changes only affecting these = "beads-only" = safe to destroy.

## Interface

```go
type GitAuditResult struct {
    Polecat              string
    Path                 string
    UncommittedFiles     []string
    UncommittedIsBeadsOnly bool
    UnpushedCommits      int
    Stashes              []StashInfo
    StashesAreBeadsOnly  bool
    LocalBranches        []string
    Error                error
}

func (r *GitAuditResult) HasCodeAtRisk() bool

func AuditPolecat(name string, path string) *GitAuditResult
func AuditPolecats(polecats []PolecatInfo) []*GitAuditResult
```

## Reference

PGT: swarm/git_audit.py


Create .runtime/ for town-level runtime state

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


MVP Cutover: GGT replaces PGT for batch work

When this is closed, stop using town and start using gt.

## Acceptance Criteria

1. gt spawn assigns issue to polecat and starts session
2. gt spawn --epic spawns workers for all epic children
3. gt session manages tmux lifecycle
4. gt send / gt inbox work for mail
5. Refinery processes merge queue with semantic merges
6. Integration branches created and landed correctly
7. gt stop --all halts all sessions
8. One successful test batch completed end-to-end

## What Must Work

- Spawn polecat with issue assignment
- Spawn workers for epic children
- Session start/stop/attach
- Mail send/inbox/read
- Refinery merge loop (semantic)
- Integration branch → main landing
- Witness cleanup protocol
- Emergency stop

## What Can Be Deferred

- Doctor checks (use PGT)
- TUI dashboard
- Plugin system
- Federation
- Ephemeral rigs
- Detailed landing reports

## Test Plan

1. Create epic with 2 tasks, spawn 2 workers
2. Verify polecats get assigned and sessions start
3. Simulate task completion
4. Verify Refinery merges to integration
5. Verify landing to main
6. Verify cleanup

## Validation

Run one real batch implementing GGT issues using GGT.

## Note

No "swarm IDs" - just spawn workers for epic, let merge queue coordinate.


Witness Plugin System

Plugin infrastructure for witness patrol customization.

## Plugin Location
`<rig>/plugins/witness/<plugin-name>/`

## Plugin Structure
```
plugin.yaml   # Config: name, description, trigger, tier
prompt.md     # The prompt to run
```

## plugin.yaml Schema
```yaml
name: plugin-name
description: What it does
trigger: patrol          # When to run (patrol, on-nudge, on-shutdown)
tier: haiku              # Model tier (haiku, sonnet, opus)
input: polecat-list      # What context to gather (polecat-list, session-capture, etc)
```

## Execution
1. Witness reads plugins from directory
2. For each plugin matching current trigger:
   - Gather specified input context
   - Run prompt.md against specified tier
   - Parse output for gt commands
   - Execute commands

## CLI
- `gt plugin list <rig>` - List installed plugins
- `gt plugin run <rig> <plugin>` - Manual run


Comprehensive help text and examples

Improve help text with examples and cross-references.

## Improvements

### 1. Examples Section
Add to Long description:
```go
var spawnCmd = &cobra.Command{
    Long: `Spawn a polecat with work assignment.

Examples:
  gt spawn gastown/Toast --issue gt-abc
  gt spawn gastown --issue gt-def          # auto-select polecat
  gt spawn gastown/Nux -m "Fix the tests"  # free-form task`,
}
```

### 2. Cross-References
Reference related commands:
```
See also:
  gt polecat list    List available polecats
  gt session attach  Attach to spawned session
```

### 3. Flag Descriptions
More detail on flags:
```go
cmd.Flags().StringVar(&issue, "issue", "",
    "Beads issue ID to assign. The polecat will work on this issue.")
```

### 4. Common Workflows
Add workflow docs to gt --help:
```
Common Workflows:
  Start a swarm:
    gt swarm preflight
    gt swarm create gastown --epic gt-abc --worker Toast --worker Nux --start
    gt refinery start gastown

  Check status:
    gt status
    gt swarm status <id>
```

## Files to Update
All internal/cmd/*.go files

## Acceptance Criteria
- [ ] All commands have Examples
- [ ] Related commands cross-referenced
- [ ] Flags have detailed descriptions
- [ ] Root help shows workflows


Test: Alpha to Beta

Sibling communication test


gt mail inbox doesn't find crew worker mail - identity mismatch

## Problem

Crew workers don't see their handoff messages when running `gt mail inbox` from their working directory.

## Root Cause

gt derives identity from cwd path, but crew workers have a different path structure:
- **cwd**: `/Users/stevey/gt/beads/crew/dave`
- **gt derives**: `beads/crew/dave` (wrong)
- **should be**: `beads/dave` (crew workers use `rig/name` format)

Messages sent to `beads/dave` don't show up because gt is looking for `beads/crew/dave`.

## Workaround

Use explicit identity flag:
```bash
gt mail inbox --identity "beads/dave"
```

## Fix

Identity detection in gt should handle crew/ subdirectory:
- Path `<rig>/crew/<name>` should derive identity as `<rig>/<name>`
- This matches how polecats work: `<rig>/polecats/<name>` → `<rig>/<name>`

## Affected

All crew workers in all rigs.


mol-crew-session: Startup/shutdown protocols for crew workers

Crew workers (like joe, max) don't have patrol molecules keeping them fresh. When gt gets rebuilt, they have stale binaries that cause hangs and bugs.

## Problem

- Crew binaries get stale when gt is rebuilt elsewhere
- No automatic pull/rebase/rebuild on session start
- No standardized shutdown protocol (sync, push, handoff)

## Solution: mol-crew-session

A molecule template for crew sessions:

### Startup Phase
1. `git pull --rebase` - get latest code
2. `bd sync` - sync beads
3. `go build -o gt ./cmd/gt` - rebuild gt (if in gastown)
4. `gt prime` - load context

### Work Phase
- Open-ended human interaction
- No molecule steps - just work until done

### Shutdown Phase
1. `git status` - check for uncommitted changes
2. `bd sync` - sync beads
3. `git push` - push code
4. Handoff if incomplete work

## Implementation

1. Define mol-crew-session in builtin_molecules.go
2. Update crew CLAUDE.md to reference the protocol
3. Optionally: gt prime auto-runs startup steps

## Dependencies

- Should implement after deacon/witness/polecat patrols are stable
- Consider: gt-3x0z.10 (Witness patrol molecules)

## Related

- gt-3x0z.9: Deacon wisp patrol (done)
- fix-gt script: Current workaround for binary freshness


Swarm learning: Refinery merge queue automation

Manually merging 15 polecat branches was painful and error-prone. Refinery should automate: detect completed work, run tests, merge to main, handle conflicts. This is core Refinery value prop.


test-after-fix

test


gt mq list shows empty when MRs exist

The gt mq list command returns empty results even when merge requests exist in the queue.


gt refinery attach: Attach to refinery session

Add 'gt refinery attach [rig]' command to attach to refinery tmux session.

Mirrors 'gt mayor attach' pattern. If rig not specified, use current rig context.


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


Plugin: Plan-to-Epic converter

## Purpose

Help users create beads epics from various planning inputs.

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

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

## Implementation Options

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

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

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

## Axiom

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

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

## Priority

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

## Note

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


Merge: gt-0ei3

branch: polecat/capable
target: main
source_issue: gt-0ei3
rig: gastown


Beads as Universal Data Plane

## Vision

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

## New Bead Categories

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

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

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

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

## v1 Priority

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


Deacon keepalive: quiet daemon during rounds

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

## Problem

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

## Solution

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

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

## Files

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

## Deacon Behavior

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

## Daemon Changes

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

## Worst Case

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

## Configuration

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


Merge: gt-g44u.3

branch: polecat/Ace
target: main
source_issue: gt-g44u.3
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.


Document bd mol bond/squash/burn CLI

Create CLI reference documentation for molecule commands:

## bd mol bond

Instantiate a proto into a runnable molecule.

```bash
bd mol bond <proto-id> [--wisp] [--assignee=<addr>]
```

- Default: creates a Mol (durable, in main beads)
- --wisp: creates a Wisp (ephemeral, in .beads-ephemeral/)
- --assignee: who will execute this molecule

## bd mol squash

Complete a molecule and generate digest.

```bash
bd mol squash <mol-id> --summary='...'
```

- For Mol: creates digest in git history
- For Wisp: evaporates (no permanent record)
- --summary: required summary of what was accomplished

## bd mol burn

Abandon a molecule without completing.

```bash
bd mol burn <mol-id> [--reason='...']
```

- Discards molecule state
- No digest created
- Use when molecule is no longer needed


gt crew remove: Remove crew workspace

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


Daemon: verify requesting_cycle before kill

Per gt-gby spec, daemon should verify agent state shows requesting_cycle=true before killing session. Currently kills on any lifecycle request without verification.

Requires state.json in agent workspace with requesting_cycle field.


Refinery tmux status: show merge queue length

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

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


Polecat beads not synced with rig beads

When gt spawn assigns an issue to a polecat, the polecat cannot find the issue because:

1. Rig beads at /gt/gastown/.beads contains gt-th7
2. Polecat beads at /gt/gastown/polecats/dementus/.beads does NOT have gt-th7
3. bd sync in polecat dir pulls from beads-sync branch which doesn't have the new issue
4. Rig bd sync uses 'main' branch, causing a branch mismatch

The polecat gets a work assignment for an issue it literally cannot see.

## Root Cause

- Rig beads and polecat beads are separate git-tracked copies
- No mechanism to propagate newly created issues from rig to polecats before assignment
- Sync branch configuration mismatch between rig and polecats

## Fix Options

1. gt spawn should sync rig beads before assigning work
2. gt spawn should sync polecat beads after assignment
3. Use shared beads (symlink or same DB) instead of copies
4. Push new issues immediately on create

Note: This sync mismatch is resolved by gt-9nf (fresh polecats). Rather than fixing sync between stale clones, we'll always create fresh worktrees. This issue documents the root cause for posterity.


Work reconciliation module

Port reconcile.py work verification to Go.

## Purpose

Verify assigned issues are completed before landing.

## Interface

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

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

## Logic

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

## Handling Incomplete Work

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

## Reference

PGT: swarm/reconcile.py


gt spawn/crew setup should create .beads/redirect for worktrees

Crew clones and polecats need a .beads/redirect file pointing to the shared beads database (../../mayor/rig/.beads). Currently:

- redirect files can get deleted by git clean
- not auto-created during gt spawn or worktree setup
- missing redirects cause 'no beads database found' errors

Found missing in: gastown/joe, beads/zoey (after git clean)

Fix options:
1. gt spawn creates redirect during worktree setup
2. gt prime regenerates missing redirects
3. bd commands auto-detect worktree and find shared beads

This should be standard Gas Town rig configuration.


Phase 1.2: Daemon attachment detection

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

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

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

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

Depends: gt-rana.1


mol-swarm-cleanup: Post-swarm debris cleanup molecule

After a 20+ worker swarm completed, found significant beads debris:
- 18 stale messages (work assignments, lifecycle requests, swarm instructions)
- 3 completed issues still open/in_progress
- Test messages accumulated

Need: Document a post-swarm checklist or create gt swarm cleanup command that:
1. Closes stale work assignment messages
2. Reviews in_progress issues for completion
3. Closes orphaned lifecycle messages
4. Optionally archives test messages


Update vision.md with proto/mol/wisp terminology

Update the Steam Engine Metaphor section to use consistent phase terminology:

Current (vision.md):
- Proto molecules = fuel (templates)
- Wisps = steam (transient execution traces)
- Digests = distillate (permanent records)

New terminology:
- **Proto** = crystal/solid - frozen template
- **Mol** = liquid - reified durable instance (tracked in git)
- **Wisp** = gas - ephemeral instance (evaporates after squash)
- **Digest** = distillate - compressed summary after squash

Key clarification needed:
- Proto → bond → creates either Mol (durable) or Wisp (ephemeral)
- The choice of Mol vs Wisp depends on --wisp flag
- Default is Mol (durable, recorded in main beads)
- Wisp lives in .beads-ephemeral/, squashes away


CLI: cleanup commands for stale state

Cleanup commands for recovering from stale state.

## Commands

### gt cleanup <polecat>
Clean stale state for specific polecat.
```
gt cleanup <rig>/<polecat> [--dry-run]
```

Actions:
- Remove orphaned state.json if clone missing
- Clear stale session references
- Reset stuck state (working → idle after timeout)

### gt cleanup --all
Clean all polecats in workspace.
```
gt cleanup --all [--dry-run]
```

## Implementation
```go
func CleanupPolecat(rigName, polecatName string, dryRun bool) (*CleanupResult, error)

type CleanupResult struct {
    OrphanedStateRemoved bool
    SessionsCleared      int
    StateReset           bool
    Warnings             []string
}
```

## Overlap with Doctor
- Doctor diagnoses and offers --fix
- Cleanup is more aggressive state recovery
- Consider merging into doctor --fix

## New File
internal/cmd/cleanup.go

## Acceptance Criteria
- [ ] Removes orphaned state files
- [ ] Clears stale session refs
- [ ] --dry-run shows what would happen
- [ ] Reports all actions taken


Outpost integration tests: Mock and E2E testing

## Overview

Integration tests for the outpost system.

## Mock Outpost

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

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

## Unit Tests

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

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

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

## Integration Tests (with mocked Cloud Run)

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

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

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

## E2E Tests (requires real Cloud Run)

```go
// +build e2e

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

## Files

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

## Dependencies

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


Swarm beads naming convention and auto-filing

Beads naming convention for swarm lifecycle events.

## Bead IDs

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

## swarm-started Bead

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

## swarm-task-merged Bead

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

## swarm-landed Bead

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

## swarm-failed Bead

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

## Why Beads (not just mail)

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


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


Phase 4: Polish and observability

Production readiness.

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

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

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

Depends: Phase 3


Merge polecat/slit: docs bd mol bond/squash/burn CLI

Branch: polecat/slit

## Summary
Added comprehensive CLI reference documentation for the three molecule lifecycle commands to molecules.md:

- **bd mol bond**: Instantiate proto into Mol (durable) or Wisp (ephemeral)
- **bd mol squash**: Complete molecule and generate digest
- **bd mol burn**: Abandon molecule without digest

Includes argument tables, behavior descriptions, examples, and a lifecycle diagram showing the steam engine metaphor mapping.

Closes: gt-odvf


Merge: beads-2nh

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


Work assignment messages should auto-close on completion

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

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

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


tmux notifications: display-message too subtle, use send-keys instead

## Problem

`tmux display-message` notifications are not visible enough - they appear briefly in the status bar and are easy to miss.

## Current Behavior

router.go uses:
```go
r.tmux.DisplayMessageDefault(sessionID, notification)
```

## What Works

Sending echo commands directly to the terminal:
```bash
tmux send-keys -t <session> "echo '📬 NEW MAIL from mayor'" Enter
```

## Proposed Fix

Change notification method to send visible output to the terminal, perhaps with a box/banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📬 NEW MAIL from mayor
Subject: <subject>
Run: bd mail inbox
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

## Considerations

- This interrupts the terminal output (acceptable for important mail)
- Could check if Claude is mid-response and queue notification
- Or use tmux popup if available

Additional issues:
1. Enter key not sent properly when chained with send-keys
2. Need to debounce and send Enter separately
3. Correct pattern: send text, sleep briefly, then send Enter


Merge: gt-rana.1

branch: polecat/rictus
target: main
source_issue: gt-rana.1
rig: gastown


Daemon: proper rig discovery

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


Silent error handling with _ = err patterns

Multiple locations intentionally ignore errors with comments like 'Ignore errors'. While some are valid (best-effort operations), others should be audited:

- witness/manager.go:542 - Ignores cmd.Run() error
- refinery/manager.go:411, 438, 509 - Ignores loadState and git pull errors
- swarm/manager.go:47 - Ignores getGitHead error
- polecat/manager.go:69, 284 - Ignores pool.Load and DeleteBranch errors
- swarm/integration.go:101, 137 - Ignores git pull and push errors

Each should be evaluated: log it, handle it, or confirm ignoring is intentional.


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

GGT is missing most polecat management commands that PGT has.

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

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

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


BUG: gt done doesn't push branch to origin - work lost when worktree deleted

## Problem

`gt done` creates a merge-request record in beads but **never pushes the branch to origin**.

When polecats are cleaned up (worktrees deleted), the branch is lost forever. The MR record exists but there's no actual branch to merge.

## Evidence

12 MQ items had `ready` status but no corresponding branches on origin. All polecat worktrees were deleted, losing all work.

## Impact

**CRITICAL**: All polecat work is silently lost unless polecats manually push (which they don't know to do).

## Fix

In `done.go`, before creating the MR:
1. `git push origin <branch>` to push the branch to remote
2. Only create MR record if push succeeds

## Location
`mayor/rig/internal/cmd/done.go:124-135`


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

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


Beads CLI wrapper: shell out to bd

Wrapper for bd (beads CLI) commands.

## Interface

```go
type Beads struct {
    workDir string
}

func NewBeads(workDir string) *Beads

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

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

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

## Data Types

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

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

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

## Implementation

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

## Error Handling

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


Test Epic: GGT MVP Validation


request-shutdown

Send shutdown request to Witness.
Wait for termination.

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

Depends: generate-summary


Remove stale migration docs from gastown-py

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


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

Current gt shutdown only kills tmux sessions but leaves:

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

This causes stale polecats to be reused on next startup.

## Expected Behavior

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

## Current Code

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

## Fix

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


gt molecule current: Show what agent should be working on

Query what an agent identity is supposed to be working on via breadcrumb trail.

## Command
```bash
gt molecule current <identity>
gt molecule current gastown/furiosa
gt molecule current deacon
```

## Logic
1. Find handoff bead for identity (pinned bead titled "<role> Handoff")
2. Parse attachment field → molecule ID
3. If no attachment → "naked" (no active molecule)
4. If attached → load molecule, find current step:
   - bd ready --parent=<mol-id> → next unblocked step
   - Or first in_progress step

## Output
```
Identity: gastown/furiosa
Handoff:  gt-8v2 (Furiosa Handoff)
Molecule: gt-mol-abc (mol-polecat-work)
Progress: 3/8 steps complete
Current:  gt-mol-abc.4 - verify-tests
```

Or if naked:
```
Identity: gastown/angharad
Handoff:  gt-9x1 (Angharad Handoff)
Molecule: (none attached)
Status:   naked - awaiting work assignment
```

## Use Cases
- Mayor checking what polecats are doing
- Witness verifying polecat progress
- Debug: "why isnt this polecat working?"
- Deacon patrol: track all agent states


Refinery startup: Use Claude agent instead of foreground mode

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

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

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


Refinery agent: merge queue processing loop

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

## Interface

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

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

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

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

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

## Processing Loop

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

## Merge Strategy

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

## Test Integration

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

## Notifications

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

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

## Dependencies

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


Design: Additional design issues (placeholder)

Placeholder for additional design issues the user wants to raise and work through. Convert to specific subtasks as issues are identified.


Swarm: report generation

Generate markdown reports for completed swarms.

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

## Report Content

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

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

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

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

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

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

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

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

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


Witness swarm landing protocol

Witness responsibilities for swarm landing cleanup.

## Trigger

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

## Landing Protocol

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

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

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

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

## Code at Risk Handling

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

## Reference

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


load-context

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

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


Add unit tests for daemon package

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


Enhanced polecat decommission prompting

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

## Checklist for AGENTS.md.template

```markdown
## Decommission Checklist

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

### Pre-Done Verification

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

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

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

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

### If Any Check Fails

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

### Signaling Done

Only after ALL checks pass:

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

## Implementation

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


Add godoc comments to exported functions

Several exported functions lack godoc comments. While not critical, adding documentation would improve code maintainability. Focus on:

- Public API functions in each package
- Exported types and their methods
- Functions that have non-obvious behavior

Can be addressed incrementally as code is touched.


Document how to configure a Gas Town Harness

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


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


Add gt decommission command for clean swarm/worker shutdown

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


Deacon plugin scheduler: cron-like execution

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

## Concept

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

## Plugin Registration

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

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

## Schedule Format

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

## Execution

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

## Plugin Execution Modes

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

## State Tracking

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

## Relation to gt-axz

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


Merge: gt-5af.6

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


request-shutdown

Send shutdown request to Witness.
Wait for termination.

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

Depends: generate-summary


bd create --dedup: semantic deduplication before issue creation

Before creating issue, check for semantic duplicates using AI similarity.

**From VC**: internal/deduplication/ - AI-powered batch comparison. ~300 lines.
VC had issue pollution problem: 438 issues with ~350+ spam because no early dedup.

**Gas Town implementation**: CLI flag on bd create:
```bash
bd create --dedup --title="Fix auth bug" --description="..."
```

Checks recent issues (7-day window) with AI similarity. If confidence >0.85, warns or blocks.

**Value**: Prevents pollution from parallel workers discovering same issues.

**VC lesson**: 115 issues filed in single day (Nov 2) because supervisor over-discovered without dedup. Rate limiting + dedup are essential.


Phase 2: Quiescent agents (Witness, Refinery)

Enable Witness and Refinery to sleep until triggered.

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

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

Depends: Phase 1 complete


Merge: gt-ih0s

branch: polecat/capable
target: main
source_issue: gt-ih0s
rig: gastown


Add 'wit' alias for witness command

ref works as alias for refinery, but wit doesn't work for witness. Add Aliases: []string{"wit"} to witnessCmd.


Add gt hook command (wrapper for bd hook)

Add a thin wrapper command `gt hook` that calls `bd hook` to inspect what's pinned to an agent's hook.

## Usage

```bash
gt hook                    # Show what's on current agent's hook
gt hook --agent deacon     # Show Deacon's hook
gt hook --agent gastown/furiosa  # Show polecat's hook
```

## Implementation

Thin wrapper in gt that:
1. Determines current agent identity
2. Calls `bd hook [--agent <name>]`
3. Formats output for gt context

## Why gt wrapper?

- Consistent with gt ecosystem (gt mail, gt status, etc.)
- Can add gt-specific context (session status, etc.)
- Easier discovery for gt users

## Related

- bd hook command (implemented by Dave)
- Chemistry UX design: gastown/mayor/rig/docs/chemistry-design-changes.md


Molecules: Composable Workflow Beads

## Summary

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

## Key Insight: Molecules ARE Beads

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

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

## Example: Engineer in a Box

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

This workflow takes a task from design to merge.

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

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

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

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

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

## Instantiation

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

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

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

## Why This Matters

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

## Implementation Primitives

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


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

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

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

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

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


gt crew status: Show workspace status

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


Update Refinery to use Beads merge queue

Replace branch discovery with Beads queue in the Refinery module:

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

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

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


bd mail send: pinned column missing from schema

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

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

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

## Error
table issues has no column named pinned

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


Test: New Router

Testing bd create for mail


Automatic spawn for ready work

Auto-spawn polecats for ready work:

When Witness has capacity (active_workers < max_workers):
1. Query bd ready for unblocked issues
2. Filter to rig-appropriate work (by prefix or epic)
3. For each ready issue up to capacity:
   - gt spawn --issue <id>
   - Track that we spawned for this issue

Configuration (in rig config.json):
- max_workers: 4 (default)
- spawn_delay: 5s between spawns
- auto_spawn: true/false

This enables 'fire and forget' swarming:
- Mayor creates epic with children
- Mayor tells Witness to work epic
- Witness spawns polecats automatically
- Witness cleans up as they complete


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

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

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

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


Polecat cleanup should refuse to lose uncommitted work

The system should stubbornly refuse to lose work from a polecat.

## Current Problem

- gt spawn --force bypasses safety checks
- gt shutdown doesn't check for uncommitted work
- Witness cleanup doesn't check git status

## Desired Behavior

Before any polecat cleanup, check:
1. git status - any uncommitted changes?
2. git stash list - any stashes?
3. Unpushed commits on branch?
4. Unsynced beads changes?

If ANY of these exist:
- REFUSE to clean up
- Print clear error message listing what would be lost
- Require explicit --nuclear flag to force (not just --force)

## Implementation

Add to cleanupPolecat() in witness/manager.go:
```go
func (m *Manager) checkUncommittedWork(polecatName string) error {
    dir := m.polecatDir(polecatName)

    // Check git status
    if hasUncommitted, _ := git.HasUncommittedChanges(dir); hasUncommitted {
        return fmt.Errorf("polecat %s has uncommitted changes", polecatName)
    }

    // Check stashes
    if stashCount, _ := git.StashCount(dir); stashCount > 0 {
        return fmt.Errorf("polecat %s has %d stashes", polecatName, stashCount)
    }

    // Check unpushed commits
    if unpushed, _ := git.UnpushedCommits(dir); unpushed > 0 {
        return fmt.Errorf("polecat %s has %d unpushed commits", polecatName, unpushed)
    }

    return nil
}
```

## Affected Commands

- gt shutdown
- gt rig shutdown
- Witness cleanup
- gt spawn --force (should warn if overwriting)


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

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


Merge: gt-svi.1

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


Add molecule phase lifecycle diagram to architecture.md

Create a clear lifecycle diagram showing molecule phases:

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

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


gt crew at/attach: Start session in crew workspace

Implement 'gt crew at <rig>/<name>' and 'gt crew attach <name>' commands:
- Start tmux session (optional - could just print cd instructions)
- Launch claude code in the workspace
- Deliver any pending mail
- Support --no-tmux to just print directory path
- 'attach' infers rig from cwd


mol-polecat-work

Full polecat lifecycle from assignment to decommission.

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

Variables:
- {{issue}} - The source issue ID being worked on


Polecats must not create GitHub PRs

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

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

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

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


Swarm learning: Beads database locality gap

## PGT Bug (GGT Design Already Correct)

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

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

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


Replaceable notifications via Claude Code queue

Leverage Claude Code's ability to replace queued text for notifications that supersede previous ones.

## Problem

If daemon sends 10 heartbeats while agent is busy, agent returns to see 10 stacked messages. Wasteful and noisy.

## Solution

Use Claude Code's queue replacement for:
- Heartbeat messages (only latest matters)
- Status updates that supersede previous
- Progress notifications

## Implementation

Notifications get a 'slot' identifier. New notification in same slot replaces old one:
- Slot: 'heartbeat' → only one heartbeat queued at a time
- Slot: 'status-<rig>' → latest status per rig
- No slot → stacks normally (for unique messages)

## Research Needed

- How does Claude Code expose queue replacement?
- tmux send-keys behavior with pending input
- Alternative: clear + resend pattern


gt mq integration create: create integration branch for epic

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

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

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

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

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


Test issue for spawn molecule


Swarm: ephemeral rig support

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

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

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

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

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


loop-or-exit

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


MQ config schema: merge_queue section in rig config.json

Define and implement merge_queue configuration in rig config.

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

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

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


HOP: Skill vectors on work items

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


Doctor framework: Check interface, Result types, Report

Framework for gt doctor health checks.

## Check Interface

```go
type Check interface {
    Name() string
    Description() string
    Run(ctx *CheckContext) *CheckResult
    Fix(ctx *CheckContext) error  // optional auto-fix
    CanFix() bool
}

type CheckContext struct {
    TownRoot string
    RigName  string  // empty for town-level checks
    Verbose  bool
}

type CheckResult struct {
    Status  CheckStatus
    Message string
    Details []string  // additional info
    FixHint string    // suggestion if not auto-fixable
}

type CheckStatus int
const (
    StatusOK CheckStatus = iota
    StatusWarning
    StatusError
)
```

## Report

```go
type Report struct {
    Timestamp time.Time
    Checks    []CheckResult
    Summary   ReportSummary
}

type ReportSummary struct {
    Total    int
    OK       int
    Warnings int
    Errors   int
}

func (r *Report) Print(w io.Writer, verbose bool)
```

## Doctor Runner

```go
type Doctor struct {
    checks []Check
}

func NewDoctor() *Doctor
func (d *Doctor) Register(check Check)
func (d *Doctor) Run(ctx *CheckContext) *Report
func (d *Doctor) Fix(ctx *CheckContext) *Report  // run with auto-fix
```

## Built-in Checks

Town-level (gt-f9x.5):
- ConfigExists, ConfigValid
- StateExists, StateValid
- MayorMailboxExists
- RigsRegistryValid

Rig-level (gt-f9x.6):
- RigCloneExists
- GitExcludeConfigured
- WitnessExists, RefineryExists
- PolecatClonesValid


Merge polecat/rictus: Add molecule phase lifecycle diagram

Branch: polecat/rictus

Adds molecule phase lifecycle diagram to architecture.md showing Proto → Mol/Wisp → Digest state transitions with the 'states of matter' metaphor. Also documents when to use Mol (durable) vs Wisp (ephemeral).

Closes: gt-c6zs


Outpost configuration: YAML config and OutpostManager

## Overview

Configuration file for outposts and manager to load/track them.

## Config File

`~/ai/config/outposts.yaml`:

```yaml
outposts:
  - name: local
    type: local
    max_workers: 4

  - name: gce-burst
    type: ssh
    host: 10.0.0.5
    user: steve
    ssh_key: ~/.ssh/gce_worker
    town_path: /home/steve/ai
    max_workers: 8

  - name: cloudrun-burst
    type: cloudrun
    project: my-gcp-project
    region: us-central1
    service: gastown-worker
    max_workers: 20
    cost_cap_hourly: 5.00

policy:
  default_preference: [local, gce-burst, cloudrun-burst]
  overrides:
    - condition: "priority >= P3"
      prefer: cloudrun-burst
```

## OutpostManager

```go
type OutpostManager struct {
    outposts map[string]Outpost
    policy   AssignmentPolicy
}

func NewOutpostManager(configPath string) (*OutpostManager, error)
func (m *OutpostManager) Get(name string) (Outpost, bool)
func (m *OutpostManager) List() []Outpost
func (m *OutpostManager) SelectOutpost(issue Issue) Outpost
```

## Files

- `internal/outpost/manager.go`
- `internal/outpost/config.go` (extend)
- `internal/outpost/policy.go`

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


Merge: gt-3x0z.3

branch: polecat/slit
target: main
source_issue: gt-3x0z.3
rig: gastown


Merge: gt-a95

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


Add role-specific subcommands that delegate to core commands (agent UX)

## Problem

Agents naturally guess command patterns like:
- `gt witness nudge <polecat>`
- `gt polecat nudge <name>`
- `gt crew restart <member>`

But these don't exist - the actual commands are:
- `gt nudge <session> <message>`
- `gt polecat reset <name>`

This creates friction in agent UX. Agents shouldn't have to memorize the 'correct' command structure.

## Proposal

Add subcommands to each role that delegate to core commands with argument reordering:

```
gt witness nudge furiosa "Start working"
  → gt nudge gt-gastown-furiosa "Start working"

gt polecat nudge gastown/furiosa "Check mail"
  → gt nudge gt-gastown-furiosa "Check mail"

gt crew nudge gastown/max "Wake up"
  → gt nudge gt-crew-gastown-max "Wake up"
```

## Benefits

1. **Discoverable**: Agents explore `gt witness --help` and find nudge
2. **Lenient**: Multiple valid ways to express the same intent
3. **Role-contextual**: Commands under the role namespace feel natural
4. **Extensible**: Pattern works for future subcommands (status, reset, etc.)

## Implementation

Each role command (witness, polecat, crew, refinery) gets thin wrapper subcommands:

```go
// In witness.go
witnessCmd.AddCommand(&cobra.Command{
    Use:   "nudge <polecat> <message>",
    Short: "Nudge a polecat (delegates to gt nudge)",
    RunE: func(cmd *cobra.Command, args []string) error {
        session := formatSession(rig, args[0])
        return runNudge(session, args[1])
    },
})
```

## Future Candidates

Other subcommands to add as role-specific aliases:
- `gt polecat status <name>` (already exists, good pattern)
- `gt witness check <polecat>` → trigger a manual check
- `gt refinery merge <mr-id>` → process specific MR
- `gt crew send <member> <message>` → mail to crew member


Enhance Mayor CLAUDE.md with GGT milestone tracking

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

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

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

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

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


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

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


Phase 3.1: Summary generation protocol

Define how agents generate summaries for squash.

## The Problem

Beads doesn't make AI calls (inversion of control). Gas Town agents must:
1. Generate their own summary before calling squash
2. Pass summary to bd squash command

## Summary Template

```
Task: <source issue title>
Action: <what was done - fix/implement/refactor/etc>
Outcome: <result - tests pass, committed, needs follow-up, etc>
Details: <1-2 sentences of specifics if needed>
```

## Example

```
Task: Fix lifecycle parser matching bug (gt-rixa)
Action: Reordered conditional checks in parseLifecycleRequest
Outcome: Tests passing, committed to polecat/furiosa
Details: The 'cycle' keyword was matching 'lifecycle:' prefix. Now checks restart/shutdown first, uses word boundary for cycle.
```

## Command

```bash
bd squash eph-abc123 --summary "Task: Fix lifecycle parser..."
```


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


Design: Swarm shutdown and worker cleanup

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

## Key Decisions

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

## Subtasks (implementation)

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

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


Plugin: merge-oracle (merge queue analysis)

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


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


Configuration in Beads: use pinned beads for rig/agent config

## Summary

Move configuration from JSON files into Beads data plane using pinned beads.

## Motivation

- Config becomes versionable and syncable like issues
- Agents can read config via `bd show`
- Unified data model for everything
- Supports the 'Beads as Universal Data Plane' vision (gt-aqm)

## Config Types to Migrate

### Rig-level (pinned per rig)
- Theme/colors for tmux status line
- Default branch naming conventions
- Merge queue settings
- Witness thresholds

### Agent-level (pinned per role)
- Handoffs (already planned - gt-cu7r)
- Agent preferences
- Current focus/context

### Town-level (pinned at town root)
- Cross-rig settings
- Federation config
- Global themes

## Implementation

1. Implement StatusPinned (beads-6v2) ✓ in progress
2. Create config pinned beads on rig init
3. Add `bd config get/set` commands that read/write pinned beads
4. Migrate existing config.json fields

## Naming Convention

```
<prefix>-cfg-<scope>
gt-cfg-theme      # gastown theme
bd-cfg-merge      # beads merge settings
gm-cfg-town       # town-level config
```

## Dependencies

- beads-6v2: Add StatusPinned to beads schema
- gm-w13: Pinned Beads epic


mol-polecat-work

Full polecat lifecycle from assignment to decommission.

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

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


MCP beads tools fail from polecat directories

The beads MCP plugin (plugin:beads:beads) fails to find issues when invoked from polecat directories.

## Observed
- Polecat in /gt/gastown/polecats/dementus
- MCP tool: plugin:beads:beads - show (issue_id: 'gt-th7')
- Error: 'bd command failed: Error: no issue found matching gt-th7'

## Expected
MCP tools should work the same as running bd directly.

## Note
This may be related to gt-tmm (beads sync issue) rather than an MCP-specific bug. The MCP tool uses bd under the hood, and bd can't find the issue because the polecat's beads database is stale.


Tmux wrapper: session operations

Wrapper for tmux session operations via subprocess.

## Interface

```go
type Tmux struct{}

func NewTmux() *Tmux

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

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

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

## Session Naming

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

## Implementation

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

## Error Handling

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

## Environment

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


Step recovery: timeout and release

Implement recovery mechanism for stuck molecule steps.

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

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

## Implementation Options

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

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

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

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

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


Design: Plugin architecture (agents-as-plugins)

Plugin system where plugins are just additional agents with identities, mailboxes, and beads access. See docs/architecture.md Plugins section. No special framework - just directory conventions and mail-based invocation.


session-gc

Clean dead sessions. Run gt gc --sessions.


gt handoff: Deadlock bug in runHandoff

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

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

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


Add molecule to update local go binary on push to main

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


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

## Problem

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

## Evidence

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

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

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

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

After nudge, polecat immediately starts working correctly.

## Hypothesis

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

## Resolution Plan

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

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

## Workaround

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


Ephemeral polecats: spawn fresh, delete on completion

## Design Decision

Switch from pooled/idle polecats to ephemeral model:
- Spawn creates fresh worktree from main
- Polecat requests shutdown when done (bottom-up)
- Witness verifies handoff, kills session, deletes worktree
- No 'idle' state - polecats exist only while working

## Rationale

1. **Git worktrees are fast** - pooling optimization is obsolete
2. **Pooling creates maintenance burden:**
   - Git stashes accumulate
   - Untracked artifacts pile up
   - Branches drift from main
   - Beads DB gets stale
3. **PGT sync problems** came from persistent branches
4. **Support infrastructure exists** - Witness, Refinery, Mayor handle continuity
5. **Simpler mental model** - polecat exists = work in progress

## Lifecycle

```
Spawn:
  gt spawn --issue <id>
  → Creates fresh worktree: git worktree add polecats/<name> -b polecat/<name>
  → Initializes beads in worktree
  → Starts session, assigns work

Working:
  Polecat does task
  → Pushes to polecat/<name> branch
  → Submits to merge queue when ready

Completion (POLECAT-INITIATED):
  Polecat runs: gt handoff
  → Verifies git state clean
  → Sends mail to Witness: "Ready for shutdown"
  → Marks itself done, waits for termination

Cleanup (WITNESS-OWNED):
  Witness receives shutdown request
  → Verifies PR merged or in queue
  → Verifies no uncommitted changes
  → Kills session: gt session stop <rig>/<polecat>
  → Deletes worktree: git worktree remove polecats/<name>
  → Deletes branch: git branch -d polecat/<name>
  → Optionally: Notifies Mayor of completion
```

## Key Insight: Bottom-Up Shutdown

**Old model (wrong)**: Top-down batch shutdown - "cancel the swarm"
**New model (right)**: Bottom-up individual shutdown - polecat requests, Witness executes

This enables streaming:
- Workers come and go continuously
- No "swarm end" to trigger cleanup
- Each worker manages its own lifecycle
- Witness is the lifecycle authority

## Implementation

1. Add `gt handoff` command for polecats to request shutdown
2. Modify gt spawn to always create fresh worktree
3. Run bd init in new worktree (beads needs initialization)
4. Add shutdown request handler to Witness
5. Witness verifies handoff, then cleans up:
   - Kill session
   - Remove worktree
   - Delete branch
6. Remove 'idle' state from polecat state machine
7. Simplify gt polecat list (only shows active)

## Impact on Other Tasks

- gt-17r (Zombie cleanup): Becomes trivial - orphan worktrees
- gt-4my (Worker health): Simpler - no idle/stuck ambiguity
- gt-f9x.5/f9x.6 (Doctor): Fewer states to validate
- gt-eu9 (Witness handoff): Witness receives polecat shutdown requests


Support numeric shortcuts in mail read (e.g., 'mail read 1')

When inbox shows numbered messages like:
* 1. gm-19b29031...  2025-12-16  mayor  Subject...
* 2. gm-19b26d51...  2025-12-16         Subject...

Users should be able to run 'gt mail read 1' instead of needing the full message ID 'gt mail read gm-19b29031f6a172206'.

Implementation:
- Track inbox message order in display
- Map numeric indices to actual message IDs
- Accept both numeric shortcuts and full IDs in 'mail read' command