Pinned bead naming convention for all roles

Establish consistent naming convention for pinned beads across all roles.

## Problem

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

## Proposed Convention

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

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

## Storage Location

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

## Why This Matters

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

## Tasks

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

## Related

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


Digest: mol-deacon-patrol

Patrol 17: all clear


Digest: mol-deacon-patrol

Patrol 16: All healthy.


Digest: mol-deacon-patrol

Patrol cycle 45: All healthy


Session ended: gt-gastown-crew-gus


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 9


Digest: mol-deacon-patrol

Patrol cycle 3: All healthy


Digest: mol-deacon-patrol

Patrol 20: Quiet


Cost tracking v2: Use beads instead of JSONL

## Overview

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

## Dependencies

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

## Model

### Agent Identity Bead (type: agent)

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

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

### Session Event Bead (type: session)

Created when a session ends (via Stop hook).

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

## Implementation

### 1. Update `gt costs record`

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

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

### 2. Update `gt costs` queries

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

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

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

### 3. Remove JSONL support

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

## Queries Supported

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

## Benefits

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

## Non-goals

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

## References

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

Implemented cost tracking v2 using beads events


Session ended: gt-gastown-crew-max


Session ended: gt-gastown-chumbucket


Digest: mol-deacon-patrol

Patrol cycle 14: All agents healthy


Town & Rig Management: install, doctor, federation

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

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

## Architecture Reference

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

All 9 children completed (closed/tombstoned)


Extended addressing: Parse [machine:]rig/polecat

Extended addressing for cross-machine operations.

## Address Format

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

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

## Parser

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

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

## Validation

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

## Usage

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

## Backward Compatibility

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

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


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

Workspace-level doctor checks.

## Checks

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

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

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

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

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

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

## Implementation

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

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


Rig doctor checks: Refinery health, clones, gitignore

Rig-level doctor checks.

## Checks

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

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

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

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

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

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

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

## Implementation

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

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


Connection interface: Protocol for local/remote ops

Abstract interface for local vs remote (SSH) operations.

## Concept

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

## Interface

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

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

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

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

## Implementations

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

## Usage

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

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

## Design Notes

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

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


LocalConnection: Local file/exec/tmux operations

LocalConnection implementation for local machine operations.

## Implementation

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

func NewLocalConnection() *LocalConnection

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

## File Operations

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

## Command Execution

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

## Tmux

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

## Notes

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

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


Machine registry: Store and manage machine configs

Registry for managing remote machines in federation.

## Data Model

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

## Interface

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

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

## Storage

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

## Connection Factory

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

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


Better error messages when bd daemon fails to start

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

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


Session ended: gt-gastown-crew-george


gt sling: Set status=hooked instead of pinned

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

## Current Behavior

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

## New Behavior

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

## Files to Update

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

## CROSS-RIG DEPENDENCY

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

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

## Verification

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


Digest: mol-deacon-patrol

Patrol 90: All healthy


Merge: slit-mk1wa0rj

branch: polecat/slit-mk1wa0rj
target: main
source_issue: slit-mk1wa0rj
rig: gastown
agent_bead: gt-gastown-polecat-slit
retry_count: 0
last_conflict_sha: null
conflict_task_id: null


Merge: dinki-mk0rlxqc

branch: polecat/dinki-mk0rlxqc
target: main
source_issue: dinki-mk0rlxqc
rig: gastown
agent_bead: gt-gastown-polecat-gus
retry_count: 0
last_conflict_sha: null
conflict_task_id: null


Digest: mol-deacon-patrol

Patrol cycle 13: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 97: All healthy


Add tests for critical untested packages

Several packages have no tests:

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

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

Low priority:
- internal/style/ - terminal styling

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


Digest: mol-deacon-patrol

Patrol 53: All healthy


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 94: All healthy


Digest: mol-deacon-patrol

Patrol #14: Quick cycle, all quiet.


Merge: furiosa-mjw1p03l

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


Merge: slit-mjtnyszt

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


Digest: mol-deacon-patrol

Patrol 144 complete: All agents healthy


Merge: rictus-mjtnzt6k

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


gt install/bd init: ensure daemon can start

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

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


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

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

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

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

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


Session ended: gt-gastown/crew/joe


Merge: capable-mjw47ef9

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


Digest: mol-deacon-patrol

Patrol cycle 4: All healthy. 3 rigs.


Convoy list UI: numbered shortcuts + expandable tree view

Improve gt convoy list UI:

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

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


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

## Problem

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

## Current Behavior

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

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

## Proposed Fix

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

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

## Implementation Options

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

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

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


Digest: mol-deacon-patrol

Patrol 218: All nominal


Digest: mol-deacon-patrol

Patrol 3: All clear


gt done should verify commits ahead of main before MR

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

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

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


Add gate timeout tracking and notification

Implement timeout and notification logic for gates.

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

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

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


Merge: rictus-mjw3nj1a

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


Hookable mail beads: Ad-hoc instruction handoff

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

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

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

## Proposed Behavior

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

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

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

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

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

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

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

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


gt hook attach: Accept any bead type including mail

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

Implementation in internal/cmd/hook.go

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


gt hook: Display mail details when mail bead hooked

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

Implementation in internal/cmd/hook.go

Implemented mail bead display in gt hook


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

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

Implementation in internal/cmd/handoff.go

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


Document hookable mail in all role templates and priming

Update documentation for hookable mail beads:

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

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

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

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

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

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

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


gt convoy create/status/list commands

CLI for convoy management in Gas Town.

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

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

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

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


Digest: mol-deacon-patrol

Patrol 211: All nominal


Review formula molecules implementation

Review the formula molecules work that's been merged:

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

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

This is launch-critical code - be thorough.

## Review Complete

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

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

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

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


Digest: mol-deacon-patrol

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


Clean up unused isFirstRig parameter in initAgentBeads

In manager.go initAgentBeads(), the isFirstRig parameter is no longer used (assigned to blank identifier).

Options:
1. Remove the parameter if no callers depend on the signature
2. Add TODO comment if keeping for backward compatibility
3. Document why it's kept in the function comment

Already completed and merged to main in commit 4d24f794


Extract shared state load/save pattern to generic helper

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

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

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


Digest: mol-deacon-patrol

Patrol 156: All healthy


Session ended: gt-gastown-crew-gus


Merge: toast-1767079830359

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

Branch not found - likely already merged


Daemon patrol should clean stale locks

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

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

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


Digest: mol-deacon-patrol

Patrol 9: quiet


Digest: mol-deacon-patrol

Patrol 5: clear


Digest: mol-deacon-patrol

Patrol cycle 48: All healthy


Track spawned sessions in DogDispatchInfo

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

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

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

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

Implemented: track spawned dogs in DispatchToDog result


Merge: nux-mjtua3jw

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


Merge: slit-mjw3n5iw

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


Session ended: gt-gastown-crew-jack


Digest: mol-deacon-patrol

Patrol 4: 17 procs, healthy


Refactor: Break up refinery/manager.go Start() function

Start() (lines 107-215, ~110 lines) handles both foreground and background modes with significant branching.

Suggested split:
- startForeground() - the deprecated foreground path
- startBackground() - tmux session creation and Claude agent launch
- ensureClaudeSettings() - settings setup (already exists as separate function)
- configureSession() - environment and theme setup


Digest: mol-deacon-patrol

Patrol cycle 8: All quiet.


Digest: mol-deacon-patrol

Patrol cycle 7: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol cycle 31: All healthy, no incidents


Session ended: gt-gastown-slit


attached_args: Standardize warning output to use style.Warning

Unify warning message formatting across codebase.

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

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

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

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


Merge: toecutter-mjwlby4g

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


Witness lifecycle doesn't kill tmux sessions after POLECAT_DONE

After witness processes POLECAT_DONE:
- Git verified clean ✓
- MERGE_READY sent to refinery ✓
- 'Session stopped' reported ✓
- BUT tmux session still running ✗

Evidence: Found 16 idle polecat tmux sessions piled up, all completed work but sessions not killed.

The witness lifecycle mail says 'Session stopped' but the tmux session persists. Need to actually kill the session:
  tmux kill-session -t <session-name>

Related to gt-7vdqv (witness patrol issues).

Fixed in commit 60418482 - explicitly kill tmux session in NukePolecat before nuke command


Session ended: gt-gastown-refinery


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 89: All healthy


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


Crew worker test-crew38 in gastown - human-managed persistent workspace.

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


Crew worker wolf in gastown


gt-gastown-polecat-ace

gt-gastown-polecat-ace

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


gt-gastown-polecat-angharad

gt-gastown-polecat-angharad

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


gt-gastown-polecat-bix

gt-gastown-polecat-bix

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


gt-gastown-polecat-blackfinger

gt-gastown-polecat-blackfinger

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.9
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-bullet

gt-gastown-polecat-bullet

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


gt-gastown-polecat-bullet-farmer

gt-gastown-polecat-bullet-farmer

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.3
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-buzzard

gt-gastown-polecat-buzzard

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.1
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-cal

gt-gastown-polecat-cal

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


gt-gastown-polecat-capable

gt-gastown-polecat-capable

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


gt-gastown-polecat-cheedo

gt-gastown-polecat-cheedo

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


gt-gastown-polecat-chrome

gt-gastown-polecat-chrome

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


gt-gastown-polecat-chumbucket

gt-gastown-polecat-chumbucket

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


gt-gastown-polecat-citadel

gt-gastown-polecat-citadel

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.4
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-coma

gt-gastown-polecat-coma

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


gt-gastown-polecat-corpus

gt-gastown-polecat-corpus

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


gt-gastown-polecat-dag

gt-gastown-polecat-dag

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


gt-gastown-polecat-dementus

gt-gastown-polecat-dementus

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


gt-gastown-polecat-dex

gt-gastown-polecat-dex

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


gt-gastown-polecat-dinki

gt-gastown-polecat-dinki

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


gt-gastown-polecat-eli

gt-gastown-polecat-eli

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


gt-gastown-polecat-furiosa

gt-gastown-polecat-furiosa

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


gt-gastown-polecat-fury

gt-gastown-polecat-fury

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.6
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-gastown

gt-gastown-polecat-gastown

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.2
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-glory

gt-gastown-polecat-glory

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


gt-gastown-polecat-goose

gt-gastown-polecat-goose

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


gt-gastown-polecat-gus

gt-gastown-polecat-gus

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


gt-gastown-polecat-immortan

gt-gastown-polecat-immortan

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


gt-gastown-polecat-imperator

gt-gastown-polecat-imperator

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


gt-gastown-polecat-interceptor

gt-gastown-polecat-interceptor

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.8
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-keeper

gt-gastown-polecat-keeper

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


gt-gastown-polecat-max

gt-gastown-polecat-max

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


gt-gastown-polecat-mediocre

gt-gastown-polecat-mediocre

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


gt-gastown-polecat-morsov

gt-gastown-polecat-morsov

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


gt-gastown-polecat-nightrider

gt-gastown-polecat-nightrider

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


gt-gastown-polecat-nux

gt-gastown-polecat-nux

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


gt-gastown-polecat-organic

gt-gastown-polecat-organic

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


gt-gastown-polecat-prime

gt-gastown-polecat-prime

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


gt-gastown-polecat-rictus

gt-gastown-polecat-rictus

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


gt-gastown-polecat-road-warrior

gt-gastown-polecat-road-warrior

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.7
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-rockryder

gt-gastown-polecat-rockryder

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-ttn3h
role_bead: hq-polecat-role
cleanup_status: has_uncommitted
active_mr: gt-3g4b0
notification_level: null


gt-gastown-polecat-scrotus

gt-gastown-polecat-scrotus

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


gt-gastown-polecat-shiny

gt-gastown-polecat-shiny

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


gt-gastown-polecat-slit

gt-gastown-polecat-slit

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


gt-gastown-polecat-splendid

gt-gastown-polecat-splendid

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


gt-gastown-polecat-testcat

gt-gastown-polecat-testcat

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


gt-gastown-polecat-testcat2

gt-gastown-polecat-testcat2

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


gt-gastown-polecat-testcat3

gt-gastown-polecat-testcat3

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


gt-gastown-polecat-testcat4

gt-gastown-polecat-testcat4

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


gt-gastown-polecat-testcat5

gt-gastown-polecat-testcat5

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


gt-gastown-polecat-testcat6

gt-gastown-polecat-testcat6

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


gt-gastown-polecat-toast

gt-gastown-polecat-toast

role_type: polecat
rig: gastown
agent_state: running
hook_bead: gt-38doh
role_bead: gt-polecat-role
cleanup_status: clean
active_mr: gt-4bi8o
notification_level: null


gt-gastown-polecat-toecutter

gt-gastown-polecat-toecutter

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


gt-gastown-polecat-valkyrie

gt-gastown-polecat-valkyrie

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


gt-gastown-polecat-vuvalini

gt-gastown-polecat-vuvalini

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a40d8
role_bead: gt-polecat-role
cleanup_status: clean
active_mr: gt-3ee79
notification_level: null


gt-gastown-polecat-warboy

gt-gastown-polecat-warboy

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


gt-gastown-polecat-wasteland

gt-gastown-polecat-wasteland

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.5
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-witness

gt-gastown-polecat-witness

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.11
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-wraith

gt-gastown-polecat-wraith

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: gt-a02fj.10
role_bead: hq-polecat-role
cleanup_status: null
active_mr: null
notification_level: null


gt-gastown-polecat-wretched

gt-gastown-polecat-wretched

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


Refinery for gastown - processes merge queue.

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

Marked dead by daemon at 2026-01-06T17:02:41-08:00 (was running, last update too old)


Witness for gastown - monitors polecat health and progress.

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

Marked dead by daemon at 2026-01-06T17:02:41-08:00 (was running, last update too old)


Refactor: Extract common AgentStateManager pattern

witness/manager.go and refinery/manager.go both have identical loadState()/saveState() patterns. Extract to a shared AgentStateManager interface or struct.

Files:
- internal/witness/manager.go:40-68
- internal/refinery/manager.go:67-95

Also unify the duplicate State type (StateStopped, StateRunning, StatePaused) defined identically in both witness/types.go and refinery/types.go.

Implemented shared AgentStateManager pattern


Digest: mol-deacon-patrol

Patrol 7: all healthy


Merge: nux-mjwny9w0

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


Audit and migrate gt-mayor/gt-deacon references to hq- prefix

## Background

Phase 1 (gt-y24km) added hq- prefix helpers for town-level agent beads:
- MayorBeadIDTown() → "hq-mayor"
- DeaconBeadIDTown() → "hq-deacon"
- DogBeadIDTown(name) → "hq-dog-<name>"

The old gt-mayor/gt-deacon IDs are now deprecated.

## Scope

Audit and update all references to the old naming:

### Code
- All callers of deprecated MayorBeadID() and DeaconBeadID()
- Hardcoded "gt-mayor" and "gt-deacon" strings
- ParseAgentBeadID() logic if needed

### Documentation
- CLAUDE.md and other markdown files
- Code comments referencing the old IDs
- Any architecture docs

### Open Plans
- Check for any in-progress work that assumes gt-* for mayor/deacon

## Acceptance Criteria
- [ ] No code uses deprecated MayorBeadID()/DeaconBeadID()
- [ ] No hardcoded "gt-mayor" or "gt-deacon" strings (except in deprecated functions)
- [ ] Documentation updated to reflect hq- prefix for town-level agents
- [ ] Tests updated/added as needed

Migrated all callers of deprecated MayorBeadID()/DeaconBeadID() to use Town variants. Tests pass.


Session ended: gt-gastown-crew-gus


Session ended: gt-gastown-crew-joe


Add logging for silently ignored errors

attached_args: Add logging for silently ignored errors

Add logging for silently ignored errors.

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

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

## Implementation options

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

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

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

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


Merge: rictus-1767141956287

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


Merge: furiosa-mjtj9d4g

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


Digest: mol-deacon-patrol

Cycle 15: quiet, handoff for fresh context


Session ended: gt-gastown-corpus

Bulk cleanup of stale session-ended events (gt-8tc1v)


Digest: mol-deacon-patrol

Patrol complete: all agents healthy, no incidents


Digest: mol-deacon-patrol

Patrol 9: healthy


Session ended: gt-gastown-crew-jack


Session ended: gt-gastown-crew-max


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

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


Day 3.2: Witness reads agent beads for polecat state

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

No more PID/tmux inference.

Parent: gt-hwka3


Digest: mol-deacon-patrol

Patrol 3 complete. All healthy.


MQ conflict prediction: file hotspot tracking

Track which files change frequently to predict conflicts before spawn.

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

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

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

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

## Parent
gt-lxxh2


Digest: mol-deacon-patrol

Patrol cycle 18: All healthy


Digest: mol-deacon-patrol

Patrol cycle 2: All healthy


Merge: dementus-1767081113622

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


Witness cleanup should know worktree parent repo

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

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

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

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

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


Optimize: Share stepMap across control flow operators

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

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

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

This is inefficient for large formulas. Options:

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

Low priority since formula sizes are typically small.


gt formula command: Scaffold CLI with subcommands

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

Wire into rootCmd. Use cobra command groups.

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


Digest: mol-deacon-patrol

Patrol 19: 3 rigs healthy


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

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


Digest: mol-deacon-patrol

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


Merge: nux-1767084010093

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


Digest: mol-deacon-patrol

Patrol cycle 2: All rigs healthy, no incidents


Digest: mol-deacon-patrol

Patrol cycle 8: All healthy


Session ended: gt-gastown-crew-jack


Integration test: agent waits for CI via gate

End-to-end test of the gate workflow.

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

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

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


Digest: mol-deacon-patrol

Patrol 16: All clear


Digest: mol-deacon-patrol

Patrol 20: all healthy, new polecats spawning


Digest: mol-deacon-patrol

Patrol 6: Quiet


Digest: mol-deacon-patrol

Patrol 25: all quiet


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 5: All clear


Add gt mail release command for releasing claimed queue messages

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

SYNTAX:
gt mail release <message-id>

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

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

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

TESTS: Add TestMailRelease

Implemented gt mail release command with tests


Digest: mol-deacon-patrol

Patrol 3: 3 polecats working, all healthy


Session ended: gt-gastown-citadel


Digest: mol-deacon-patrol

Patrol cycle 9: All agents healthy


Day 1.1b: Add type=role to bead schema

Add role as a valid bead type in the schema.

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

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

Must be done alongside type=agent schema work.


Digest: mol-deacon-patrol

Patrol 3: All healthy, no changes


Digest: mol-deacon-patrol

Patrol 38: all quiet


Add keyboard navigation and filtering

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


Session ended: gt-gastown-crew-george


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

bd ready should prioritize issues that are blocking other work.

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

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

Checks dependency graph. Issues with many dependents surface first.

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

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


Digest: mol-deacon-patrol

Patrol 18: all healthy, handled 1 handoff msg


Digest: mol-deacon-patrol

Patrol 13 complete


Session ended: gt-gastown-crew-max


BUG: Polecats not auto-nuked after MR merge

## Problem

Polecats complete their work and submit MRs, but after the Refinery merges them, the polecats are not automatically nuked. They linger in 'done' state requiring manual cleanup.

## Observed Behavior

1. Polecat completes work, runs `gt done`, submits MR
2. Refinery merges the MR, deletes the branch
3. Polecat transitions to 'done' state
4. Polecat sends POLECAT_DONE message to Witness
5. **Polecat stays around** - worktree, session (sometimes), agent bead all persist
6. Manual `gt polecat nuke` required to clean up

## Evidence

- 47 polecats found in 'done' state after convoy completion
- Witness had 29 unread POLECAT_DONE messages it wasn't processing
- All nuked polecats had closed issues still on their hooks (hook not cleared)

## Related Issues

1. Hook not cleared when issue closed
2. Witness unstable (kept dying mid-patrol)
3. `gt polecat nuke` safety check blocks on 'has work on hook' even when that work is closed

## Expected Behavior

After MR merge:
1. Refinery should signal completion
2. Witness should receive POLECAT_DONE and auto-nuke the polecat
3. Or: Refinery should nuke directly after successful merge

## Workaround

Manual: `gt polecat nuke <rig>/<polecat> --force`

Added gt witness process command to invoke handlers


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

Update gt install to create agent beads:

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

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

Files:
- internal/cmd/install.go

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


attached_args: Remove os.Exit calls from Cobra RunE handlers

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

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

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

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

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


Digest: mol-deacon-patrol

Patrol 20: final check, all healthy, handoff threshold


Set hook_bead at polecat spawn time, not after

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

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

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

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

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

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

Set hook_bead atomically at spawn time via AddOptions/HookBead


Patrol hygiene: Witness/Refinery/Deacon inbox cleanup

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

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

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

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

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

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

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


Auto-rebase on conflict: optional automatic rebase

Implement automatic rebase as conflict resolution option.

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

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

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

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


Retry logic: exponential backoff for transient failures

Implement retry logic for transient failures.

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

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

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

Track retry count in MR metadata for debugging.

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


MQ metrics: pending count, processing time, success rate

Implement merge queue metrics collection.

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

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

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

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


MQ dashboard widget: queue status in TUI

Add merge queue status widget to the Bubbletea TUI dashboard.

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

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

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

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


Direct landing integration: gt land uses MR or bypasses

Integrate direct landing with merge queue.

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

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

This ensures direct landing still creates proper records.

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


Rollback support: gt mq revert for problematic merges

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

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

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

Track revert in MR metadata for audit trail.

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


Per-epic config overrides: custom merge settings

Allow per-epic merge configuration overrides.

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

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

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

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


Day 1.3: Implement bd slot set/clear commands

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

These enforce cardinality constraints.

Parent: gt-d0jqp


Merge: slit-1767073379145

attached_args: Code review this merge request

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

CODE REVIEW (valkyrie): REQUEST CHANGES

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

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

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

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


Session ended: gt-gastown-crew-gus


gt handoff broken: unknown session type hq-mayor

dispatched_by: mayor

Regression from gt-vcvyd fix. Problem: furiosa's fix changed status.go to show 'hq-mayor' but didn't update session.MayorSessionName() in internal/session/names.go. Result: Two sessions exist (gt-mayor old, hq-mayor new). User is attached to hq-mayor but handoff calls getMayorSessionName() which returns 'gt-mayor', so sessionWorkDir() doesn't match. Fix: Update session.MayorSessionName() and session.DeaconSessionName() to return 'hq-mayor'/'hq-deacon', or add case in sessionWorkDir() for hq- prefixed names.

Already fixed in commit 6b8c897e (feat: use hq- prefix for Mayor and Deacon session names). MayorSessionName() and DeaconSessionName() already return 'hq-mayor' and 'hq-deacon'. Tests pass. Bug was valid when filed (v0.2.0 binary) but fix is now in codebase (v0.2.1).


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

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

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

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


Digest: mol-deacon-patrol

Patrol 3: quick pass, all healthy


Digest: mol-deacon-patrol

Patrol cycle 5: All healthy.


Merge: nux-mjw18ii8

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


Digest: mol-deacon-patrol

Patrol cycle 18: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 13: healthy


Session ended: gt-gastown-crew-george


Digest: mol-deacon-patrol

Patrol 17: Quiet


Merge: dex-mjufugxv

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


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

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

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

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


Session ended: gt-gastown-crew-max


Digest: mol-deacon-patrol

Patrol 117: All healthy


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

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


Merge: gt-si8rq.4

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


Digest: mol-deacon-patrol

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


gt rig config commands

Implement config viewing and manipulation commands.

Commands:
- gt rig config show <rig>           # Effective config
- gt rig config show <rig> --layers  # Show source of each value
- gt rig config set <rig> <key> <value>          # Wisp layer
- gt rig config set <rig> <key> <value> --global # Bead layer
- gt rig config set <rig> <key> --block          # Block inheritance
- gt rig config unset <rig> <key>                # Remove from wisp

Example output:
  gt rig config show gastown --layers
  Key                 Value        Source
  status              parked       wisp
  priority_adjustment 10           bead
  auto_restart        true         system
  max_polecats        4            town


Refinery: Non-blocking delegation via bead-gates

## Problem: Refinery Blocks on Delegation

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

## Solution: Gate-Based Non-Blocking Delegation

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

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

## Implementation

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

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

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

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

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

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

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

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


Polecat writes files outside worktree (integration test)

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

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

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

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

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


Remove pinned field workaround after beads fix

## Summary

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

## Current Workaround

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

## Changes After Beads Fix

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

## Blocked By

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

## Related

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

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


Session ended: gt-gastown-corpus


Merge: nux-mjw3mn8o

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


Session ended: gt-gastown-nux


Digest: mol-deacon-patrol

Patrol 62: All agents healthy, routine scan


Session ended: gt-gastown-crew-jack


Day 1.7: Define per-rig agent bead ID pattern

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

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

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

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

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


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

## Problem

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

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

## Expected

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

## Impact

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

## Fix

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

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


Digest: mol-deacon-patrol

Patrol 8: 3 rigs healthy


Session ended: gt-gastown-crew-joe


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 136: All healthy


Add merge queue observability

## Problem

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

## Requirements

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

## Possible Approaches

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

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


Digest: mol-deacon-patrol

Patrol 16: all healthy


DRY: Duplicate address normalization logic in mail/types.go

The addressToIdentity and identityToAddress functions (types.go:329-390) have duplicated logic for:
- Handling overseer special case
- Handling mayor/ and deacon/ trailing slashes
- Normalizing crew/ and polecats/ paths

The same transformations are applied with only slight variations. Consider extracting common normalization logic.

Files:
- internal/mail/types.go:329-390

Severity: low


Digest: mol-deacon-patrol

Patrol 13: all healthy


Digest: mol-deacon-patrol

Patrol cycle 3: all healthy, no incidents


Digest: mol-deacon-patrol

Patrol 9: all clear


Merge: cheedo-1767146245543

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


Merge: nux-mjvtlh4f

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


Digest: mol-deacon-patrol

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


CI integration tests for install/setup flow

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

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

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

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

GitHub: #20


Design: CI test infrastructure and approach

Design decisions for CI test infrastructure.

## Decisions Made

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

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

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

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

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

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

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

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

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

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

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

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

Design complete with implementation skeleton


Test: gt install creates correct structure

Validate fresh install:

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

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

Integration test implemented and passing


Test: gt rig add initializes rig correctly

Validate rig setup:

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

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

Implemented integration tests for gt rig add with 8 test cases covering directory structure, beads initialization, routes.jsonl, rigs.json, prefix derivation, config.json, agent dirs, and invalid name rejection.


Test: Beads routing and redirects work correctly

Validate beads cross-rig routing:

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

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

Added integration tests for beads routing and redirects. Tests cover: routing from town root, redirect resolution, circular redirect detection, prefix conflict detection, bd list from polecat/crew directories, routes.jsonl loading/appending/removal, and GetPrefixForRig.


CI: GitHub Actions workflow for integration tests

Set up CI to run integration tests:

## Requirements
- Run on PRs touching: internal/cmd/install.go, internal/cmd/rig.go, internal/config/
- Use build tag: //go:build integration
- Timeout: 5 minutes max
- Cache Go modules

## Workflow structure
```yaml
name: Integration Tests
on:
  pull_request:
    paths:
      - 'internal/cmd/install.go'
      - 'internal/cmd/rig.go'
      - 'internal/config/**'
      - 'internal/routing/**'
jobs:
  integration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
      - run: go test -tags=integration ./...
```

Created .github/workflows/integration.yml with path-based triggers, 5-min timeout, Go caching


Heartbeat convention: simple liveness signal for agents

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

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

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

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

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


Digest: mol-deacon-patrol

Patrol 20: All healthy - handoff cycle


Test session event


Session ended: gt-gastown-crew-jack


Digest: mol-deacon-patrol

Patrol 158: All healthy


Fix potential race conditions in state file operations

Non-atomic state file operations could cause data loss:

All packages with state files (witness, refinery, crew, swarm):
State is loaded, modified, and saved without locking:
  ref, err := m.loadState()
  // ... modify ref ...
  return m.saveState(ref)

If two processes modify state simultaneously, one update may be lost.

internal/polecat/namepool.go (lines 179-194):
Save() uses RLock() but performs file I/O - should use write lock.

internal/swarm/manager.go:
swarms map[string]*Swarm accessed without mutex but multiple goroutines could call Create/GetSwarm.

Suggestion:
1. Use file locking (flock) for critical state updates
2. Or use atomic write pattern (write to temp, then rename)
3. Add mutex to swarm.Manager.swarms map


Pillar 2: Patrol Ignition

Witness and Refinery patrol loops fire reliably.

Key deliverables:
- Witness patrol reads agent beads for polecat state
- Witness sends MERGE_READY to refinery on completion
- Refinery processes MERGE_READY mail
- Both send completion signals

Subsumes existing work:
- gt-6qyt1: Refinery event-driven merge queue
- gt-qpwv4: Witness detect completion
- gt-dtw9u: Witness active monitoring

Reference: ~/gt/docs/liftoff-plan.md

Parent: gt-oki8p


BUG: Refinery checks git ls-remote instead of gt mq list

The refinery patrol checks 'git ls-remote origin | grep polecat' to see if there's work, but the merge queue is tracked in beads via 'gt mq list'. This caused 41 MRs to pile up while refinery reported 'Queue clear'.

Root cause: Refinery implementation is checking the wrong data source.

Fix: Change refinery patrol to use 'gt mq list <rig>' as the source of truth for pending merge requests.

Related: hq-eggh5

Fixed refinery template to remove misleading git branch grep reference and add explicit warnings about using gt mq list as the only source of truth


Session ended: gt-gastown-bullet-farmer

Bulk cleanup of stale session-ended events (gt-8tc1v)


Digest: mol-deacon-patrol

Patrol 12: All clear


Session ended: gt-gastown-crew-gus


Review PR #198: refinery use default_branch

dispatched_by: gastown/crew/joe

PR #198 reviewed and approved. Well-implemented fix for hardcoded 'main' branch in refineries.


Digest: mol-deacon-patrol

Patrol 9 complete


Crew agent beads never created (dead code in reportAgentState)

## Problem

In prime.go, reportAgentState is called for ALL roles including crew workers.
The function calls bd.UpdateAgentState for crew with ID like gt-crew-gastown-max.

But crew agent beads are NEVER created:
- Polecats: Created on spawn (polecat/manager.go:167)
- Infrastructure: Created by doctor/install (doctor/agent_beads_check.go)
- Crew: Nothing creates them

The error is silently ignored, so this doesn't break anything, but it's dead code.

## Found in commit

f3a6ef6 (feat: Witness reads polecat state from agent beads)

## Decision

**Create crew agent beads** on first gt prime if they don't exist.

Crew workers are human-managed but their state is still valuable for:
- Town-wide status dashboards
- Understanding who's working on what
- Consistent agent bead model across all agent types

## Implementation

In prime.go reportAgentState(), before calling UpdateAgentState:
1. Try to get the agent bead
2. If it doesn't exist, create it with CreateAgentBead
3. Then update state as normal


Digest: mol-deacon-patrol

Patrol 1: All checks passed, no incidents. Witness/Refinery healthy, 1 dog idle, no polecats.


Digest: mol-deacon-patrol

Patrol 51: All healthy


Digest: mol-deacon-patrol

Patrol 9: Quiet


Digest: mol-deacon-patrol

Patrol 12: quiet


Digest: mol-deacon-patrol

Patrol cycle 19: All healthy


Merge: shiny-mk0vvt3o

branch: polecat/shiny-mk0vvt3o
target: main
source_issue: shiny-mk0vvt3o
rig: gastown
agent_bead: gt-gastown-polecat-gus
retry_count: 0
last_conflict_sha: null
conflict_task_id: null


Digest: mol-deacon-patrol

Patrol 5: Routine


Merge: capable-mjxdb1vx

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

Branches merged, cleaning up stale MR beads


Code Smell Cleanup: Gas Town Code Quality

Comprehensive code smell cleanup identified in code review session.

## Scope
Fixes for duplication, thread safety, missing tests, inconsistent patterns, and dead code.

## Child issues (18 total)

### Thread Safety & Data Integrity (P2)
- gt-wled7: Atomic write pattern for state files
- gt-xtsb5: Fix namepool Save() mutex
- gt-bp0ht: Add swarm.Manager mutex

### Duplication Removal (P2)
- gt-560ge: Extract processExists function
- gt-ddw3y: Extract molecule_lifecycle boilerplate
- gt-atqr8: Session name helper functions
- gt-ov2uv: AgentIdentity type for parsing

### Missing Tests (P2)
- gt-p2eg5: Lock package tests
- gt-0vu9e: mrqueue package tests
- gt-1pelm: mail router/mailbox tests

### Code Quality (P2)
- gt-gcnnr: Add logging for ignored errors

### Constants Extraction (P3)
- gt-4u682: SupportedShells constant
- gt-795e8: Timing constants

### Refactoring (P3)
- gt-tkbd5: Refactor runStart
- gt-dsqxw: Refactor runStartCrew

### Error Handling (P3)
- gt-h3skz: Remove os.Exit from RunE
- gt-g6kor: Standardize warning output

### Dead Code (P4)
- gt-2g130: Remove unused code

## Acceptance criteria
- [ ] All child issues closed
- [ ] go build ./... passes
- [ ] go test ./... passes
- [ ] go test -race ./... passes


Session ended: gt-gastown-crew-joe


Session ended: gt-gastown-prime


Digest: mol-deacon-patrol

Patrol #19: Dog pool healthy, 1 idle.


Digest: mol-deacon-patrol

Patrol 6: Routine


Digest: mol-deacon-patrol

Patrol complete: inbox empty, 2 polecats working, all agents healthy


Digest: mol-deacon-patrol

Patrol cycle 19: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 12: all healthy


Digest: mol-deacon-patrol

Patrol cycle 78: All healthy


Config directory and schema for messaging

Create ~/gt/config/ directory structure for messaging configuration.

## Deliverables

1. Create ~/gt/config/ directory
2. Define JSON schema for lists.json:
   ```json
   {
     "oncall": ["mayor/", "gastown/witness"],
     "cleanup/gastown": ["gastown/witness", "deacon/"]
   }
   ```
3. Define JSON schema for queues.json (if separate from lists)
4. Add config loading utility in internal/config/

## Acceptance
- Config directory exists
- Schema documented
- Load/parse functions work


Digest: mol-deacon-patrol

Patrol complete: 0 callbacks, no gates, all witnesses/refineries healthy, no orphans


Merge: toast-1767146237529

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


Digest: mol-deacon-patrol

Patrol 11: all clear


Digest: mol-deacon-patrol

Patrol 149 complete


Session ended: gt-gastown-crew-george


Merge: rictus-mjtlq9xg

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


Merge: rictus-1767084016819

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


Polecat sessions terminate unexpectedly during work

During swarm bd-784c, polecat sessions (Toast, Nux) terminated mid-task without completing their work.

**Observed:**
- Polecats were actively working (edits in progress, tests running)
- Sessions suddenly showed 'not running' in gt polecat status
- tmux sessions existed but were empty/reset
- Work was partially complete (some commits made, issue still open)

**Impact:**
- Lost work progress
- Required manual intervention to restart
- Unclear if work was saved/committed

**Suggestion:**
- Add session health monitoring
- Auto-restart on unexpected termination
- Log session exit reasons

Implemented session health monitoring and auto-restart for crashed polecats. Changes include: (1) daemon now proactively checks polecat session health and auto-restarts crashed sessions with work-on-hook, (2) added polecat support to lifecycle identity mapping, (3) added 'gt session check' command for manual health checking.


DRY: Duplicate config expansion in mail/router.go

The expandList, expandQueue, and expandAnnounce functions (router.go:92-158) have nearly identical structure:
- Check townRoot is set
- Build config path
- Load messaging config
- Look up item in map
- Return result or error

These should be consolidated into a helper like:

```go
func (r *Router) expandFromConfig[T any](name string, getter func(*config.MessagingConfig) (T, bool), errType error) (T, error)
```

Files:
- internal/mail/router.go:92-158

Severity: medium

Consolidated expandList, expandQueue, expandAnnounce using generic expandFromConfig helper


Digest: mol-deacon-patrol

Cycle 193: Nominal


Session ended: gt-gastown-crew-jack


Merge: valkyrie-mjxpdngw

branch: polecat/valkyrie-mjxpdngw
target: main
source_issue: valkyrie-mjxpdngw
rig: gastown
agent_bead: gt-gastown-polecat-valkyrie


Merge: toast-1767146237529


Digest: mol-deacon-patrol

Patrol 3: 3 rigs healthy, no incidents


Review: Beads Universal Data Plane documentation

Review new docs/beads-data-plane.md (275 lines) for accuracy.

## Commit
- 54c8269: Add Beads Universal Data Plane documentation

## Review focus
- Accuracy of beads architecture description
- Correct field mappings (mail → beads fields)
- Two-level architecture (town vs rig beads) correctly explained


Session ended: gt-gastown-crew-joe


Polecat didn't commit before gt done

Integration test gt-7psb8: Polecat furiosa ran gt done before committing changes.

The polecat:
1. Created a test file
2. Closed the bead with bd close --force
3. Ran bd sync
4. Ran gt done

But it NEVER ran:
- git add <file>
- git commit

Result: Branch pushed with no new commits. Refinery detected branch at same commit as main.

The polecat CLAUDE.md or session close protocol should enforce commit before gt done.


Digest: mol-deacon-patrol

Patrol cycle 12


Digest: mol-deacon-patrol

Patrol 103: All agents healthy, no incidents


crew_add.go: Agent bead ID uses wrong format

In internal/cmd/crew_add.go:84, the agent bead ID is constructed as:

  crewID := fmt.Sprintf("gt-crew-%s-%s", rigName, name)

This produces 'gt-crew-gastown-joe' but existing beads and the session name format use:

  gt-<rig>-crew-<name>  (e.g., gt-gastown-crew-joe)

This causes agent beads to not be found/matched properly.

Fix: Change line 84 to:

  crewID := fmt.Sprintf("gt-%s-crew-%s", rigName, name)

Already fixed by max in c31bff4 - now uses beads.CrewBeadIDWithPrefix()


Digest: mol-deacon-patrol

P13: stable


Digest: mol-deacon-patrol

Patrol 5: Routine


Digest: mol-deacon-patrol

Patrol cycle 17: All healthy


Digest: mol-deacon-patrol

Patrol 8: All healthy


CRITICAL: Merge formula CLI scaffold (gt-gpifj)

Commit 60da7926 (formula CLI scaffold) is on branch polecat/nux-mjw18ii8 but NOT merged to main. This blocks formula execution functionality. Main still has old formulas.go that just delegates to bd formula list.


Rig disable/enable with property layers

Implement multi-level configuration system for Gas Town.

Two-level rig control:
- Level 1 (park/unpark): Wisp layer, town-local, ephemeral
- Level 2 (dock/undock): Rig bead labels, synced globally

Property layer lookup: Wisp → Bead → Town → System defaults

See:
- ~/gt/docs/hop/PROPERTY-LAYERS.md
- docs/property-layers.md


Digest: mol-deacon-patrol

Patrol 18: Quiet


Agent bead creation uses wrong prefix for non-gastown rigs

When adding a polecat to beads rig, agent bead creation fails:
  Error: issue ID 'gt-polecat-beads-Toast' does not match configured prefix 'bd'

The agent bead ID should use the rig's prefix (bd-) not hardcoded gt-.

Fixed: Added WithPrefix variants to agent bead ID functions, updated crew_add.go, rig/manager.go, and doctor/agent_beads_check.go to use rig's configured prefix


Formula SDK: Developer Tooling for Workflow Authors

SDK for creating, testing, debugging, and validating formulas.

## Components

### Forge (Creation)
```bash
mol forge new my-workflow        # Scaffold new formula
mol forge extend base-workflow   # Create variant
mol forge lint my-workflow       # Check syntax/style
```

### Lab (Testing)
```bash
mol lab run my-workflow --dry-run   # Mock execution
mol lab test my-workflow            # Run formula tests
mol lab trace my-workflow           # Step-through debug
```

### Validator (Verification)
```bash
mol validate my-workflow                    # Check well-formedness
mol validate my-workflow --protocol=Reviewable  # Check protocol compliance
mol validate my-workflow --schematic=shiny  # Check schematic compatibility
```

### Analyzer (Static Analysis)
```bash
mol analyze my-workflow --deps     # Dependency graph
mol analyze my-workflow --coverage # Step coverage
mol analyze my-workflow --cycles   # Detect cycles
```

## Relationship to Other Concepts

The SDK operates on:
- Formulas (source level)
- Protos (compiled level)
- Molecules (runtime level)

And validates against:
- Protocols (type system)
- Schematics (domain composition)

## Open Questions

1. Bundled vs separate - mol subcommands or formula-kit binary?
2. IDE integration - LSP for formula files?
3. Test harness - how to mock steps? fixtures?

## Related

- docs/formula_evolution.md - "SDK Question" section
- gt-8tmz.30 - Proto debugging tools (subset)
- gt-8tmz.31 - Formula validation (subset)


Code Smell: Large Connection interface (17 methods)

The Connection interface in connection/connection.go has 17 methods, violating the Interface Segregation Principle.

Current interface groups:
- Identification: Name(), IsLocal()
- File operations: ReadFile, WriteFile, MkdirAll, Remove, RemoveAll, Stat, Glob, Exists
- Command execution: Exec, ExecDir, ExecEnv
- Tmux operations: TmuxNewSession, TmuxKillSession, TmuxSendKeys, TmuxCapturePane, TmuxHasSession, TmuxListSessions

**Recommendation**: Split into focused interfaces:
```go
type Identifier interface {
    Name() string
    IsLocal() bool
}

type FileSystem interface {
    ReadFile(path string) ([]byte, error)
    WriteFile(path string, data []byte, perm fs.FileMode) error
    // ...
}

type CommandRunner interface {
    Exec(cmd string, args ...string) ([]byte, error)
    ExecDir(dir, cmd string, args ...string) ([]byte, error)
    ExecEnv(env map[string]string, cmd string, args ...string) ([]byte, error)
}

type TmuxManager interface {
    TmuxNewSession(name, dir string) error
    // ...
}

type Connection interface {
    Identifier
    FileSystem
    CommandRunner
    TmuxManager
}
```

This allows consumers to depend only on what they need.

Files:
- internal/connection/connection.go:13-78

Severity: low