Review: Security Sweep

Security-focused code review

## Scope
Entire codebase with focus on external inputs

## Focus Areas

1. **Command Injection**
   - All exec.Command calls
   - Shell escaping
   - User input in commands

2. **Path Traversal**
   - File operations with user paths
   - Symlink following
   - Directory escapes

3. **Secrets**
   - No hardcoded credentials
   - Proper secret handling
   - Env var usage

4. **Input Validation**
   - All external inputs validated
   - Mail content sanitization
   - Git ref validation

5. **OWASP Top 10**
   - Injection flaws
   - Broken authentication patterns
   - Sensitive data exposure

## Deliverables
- Security issue inventory with severity
- Immediate fixes required
- Security hardening recommendations

Polecat completed review - closing to complete convoy


Review: Beads/State Management

# Review Beads and State Management

## Packages
- internal/beads/
- internal/activity/
- internal/wisp/

## Focus Areas

1. **Git as Blockchain Principle**
   - Is git being used properly as the source of truth?
   - Are there places bypassing git for state?

2. **Schema Consistency**
   - Column names, field types
   - Migration handling

3. **Performance**
   - Query efficiency
   - Caching patterns
   - N+1 query problems

4. **Error Handling**
   - Database errors properly wrapped
   - Transaction handling

## Deliverables
- Schema issues
- Performance concerns
- Principle violations

Review complete. Found: 1 principle violation (audit.log bypasses git), 0 schema issues, 3 N+1 query performance concerns, generally solid error handling. See session for detailed analysis.


Review: Heresies in Core Command Layer

Review internal/cmd for Core Principle Violations

## Focus Areas

1. **Propulsion Principle Violations**
   - Are there places where agents wait for confirmation instead of executing?
   - Check startup flows, hook handling, mail processing

2. **Beads as Truth**
   - Is state stored in beads or in ephemeral places (files, memory)?
   - Check for state that should be in beads but is not

3. **Human-Readable**
   - Are outputs clear and auditable?
   - Check for opaque error messages or unclear state

## Packages to Review
- internal/cmd/ (all files)

## Deliverables
- List of heresy violations with file:line
- Severity rating for each
- Recommendations for alignment with principles

Review complete: internal/cmd/ is healthy, no heresies found across all three principles


Review: Coordination and Communication

Review mail, protocol, session, and connection packages

## Packages
- internal/mail/
- internal/protocol/
- internal/session/
- internal/connection/

## Focus Areas

1. **Code Duplication**
   - Similar patterns in message handling
   - Repeated connection logic

2. **Error Handling**
   - Network error recovery
   - Timeout handling

3. **Interface Design**
   - Are interfaces minimal and focused?
   - Dependency injection patterns

4. **Security**
   - Input validation on messages
   - No command injection vectors

## Deliverables
- Issues list with file:line
- Security concerns flagged
- Abstraction opportunities

Code review complete. Filed 11 issues covering code duplication (4), security (1), error handling (3), interface design (2), and code smells (1).


Review: Work Management (swarm, convoy, formula)

Review work dispatch and orchestration

## Packages
- internal/swarm/
- internal/convoy/
- internal/formula/
- internal/mrqueue/
- internal/mq/

## Focus Areas

1. **Complexity**
   - Long functions, deeply nested logic
   - State machine clarity

2. **Concurrency**
   - Goroutine lifecycle management
   - Channel usage patterns
   - Race conditions

3. **Missing Abstractions**
   - Common dispatch patterns
   - Repeated queue handling

4. **Performance**
   - Unnecessary allocations
   - Efficient iteration

## Deliverables
- Complexity hotspots
- Concurrency concerns
- Refactoring suggestions

Review complete: identified 4 complexity hotspots, 3 concurrency concerns, 4 missing abstractions, 4 performance observations. Key issues: race condition in mrqueue.Claim(), hardcoded sleep in ExecuteLanding. convoy/ package does not exist.


Review: Git and Workspace Management

Review git operations and workspace handling

## Packages
- internal/git/
- internal/workspace/
- internal/rig/
- internal/crew/

## Focus Areas

1. **Security**
   - Path traversal vulnerabilities
   - Command injection in git operations
   - Sanitization of user-provided paths

2. **Error Handling**
   - Git command failures
   - Workspace state recovery

3. **Go Idioms**
   - Proper use of os/exec
   - Context cancellation
   - Resource cleanup

4. **Duplication**
   - Similar git operations across packages
   - Repeated path manipulation

## Deliverables
- Security audit results
- Error handling gaps
- Consolidation opportunities

## Review Complete

### Security Audit (2 P2 issues filed)
- gt-wzxwm: Path traversal in crew name handling
- gt-l1xsa: Unvalidated config values passed to exec.Command

### Error Handling Gaps (1 issue filed)
- gt-rsnj9: Silent error swallowing in discovery/cleanup

### Consolidation Opportunities (2 issues filed)
- gt-0cu3o: Clone-with-reference-fallback pattern duplication
- gt-nqq5k: bd command wrapper package needed

### Go Idioms (2 issues filed)
- gt-xhjss: Missing context.Context in git package
- gt-0vvo1: Panic anti-pattern in MustGetTownName

Total: 7 issues filed from this review

Review complete. Filed 7 issues: 2 security (P2), 1 error handling (P3), 2 refactoring (P3-P4), 2 Go idioms (P3-P4).


Review: UI and Presentation Layer

Review user-facing output and display

## Packages
- internal/tmux/
- internal/tui/
- internal/web/
- internal/style/
- internal/templates/
- internal/feed/

## Focus Areas

1. **Consistency**
   - Output formatting patterns
   - Color/style usage

2. **Error Messages**
   - Clear, actionable error text
   - Helpful suggestions

3. **Code Duplication**
   - Similar display logic
   - Repeated template patterns

4. **Accessibility**
   - Terminal compatibility
   - Color blindness considerations

## Deliverables
- UX inconsistencies
- Template duplication
- Style consolidation opportunities

UI review complete: Found color duplication (3 packages), role icon duplication, missing NO_COLOR support. Recommendations: consolidate colors in style package, add symbolic status indicators alongside color.


Review: Infrastructure and Utilities

Review supporting infrastructure packages

## Packages
- internal/config/
- internal/doctor/
- internal/util/
- internal/lock/
- internal/keepalive/
- internal/townlog/
- internal/constants/
- internal/deps/
- internal/events/
- internal/checkpoint/
- internal/boot/
- internal/dog/
- internal/suggest/
- internal/claude/

## Focus Areas

1. **Dead Code**
   - Unused functions
   - Unreachable branches
   - Stale utilities

2. **Missing Abstractions**
   - Common patterns in util that should be elsewhere
   - Repeated config handling

3. **Performance**
   - Logger efficiency
   - Lock contention
   - Keepalive overhead

4. **Error Handling**
   - Consistent patterns
   - Proper wrapping

## Deliverables
- Dead code inventory
- Utility consolidation plan
- Performance concerns


Review: Test Coverage and Quality

Audit test coverage and test quality

## Scope
All *_test.go files across the codebase

## Focus Areas

1. **Coverage Gaps**
   - Packages with no tests
   - Critical paths without test coverage
   - Error paths untested

2. **Test Quality**
   - Table-driven tests where appropriate
   - Test isolation (no shared state)
   - Meaningful assertions

3. **Test Smells**
   - Flaky tests
   - Over-mocking
   - Tests that test implementation not behavior

4. **Missing Test Types**
   - Integration tests needed
   - Edge cases not covered

## Deliverables
- Coverage gap inventory
- Flaky test candidates
- Priority list for new tests

Review complete. Report at docs/test-coverage-review.md


Merge: angharad-mk02mkh7

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


Merge: organic-mjwjck2f

branch: polecat/organic-mjwjck2f
target: main
source_issue: organic-mjwjck2f
rig: gastown
agent_bead: gt-gastown-polecat-organic


Digest: mol-deacon-patrol

Patrol 14: quiet


Optimize convoy status: batch worker lookups

## Problem

`gt convoy status` is slow due to O(N×R) subprocess calls in `getWorkersForIssues()` (convoy.go:1213-1294).

Current code spawns one sqlite3 subprocess per issue per rig:
```go
for _, polecatsDir := range rigDirs {          // R rigs
    for _, issueID := range issueIDs {         // N issues
        queryCmd := exec.Command("sqlite3", ...) // R×N subprocesses!
    }
}
```

For a convoy tracking 10 issues across 3 rigs = 30 subprocess calls × ~20ms = 600ms minimum.

## Proposed Fix

1. **Batch sqlite queries** - One query per rig using `WHERE hook_bead IN (id1, id2, ...)`
   - Reduces R×N calls to R calls
   - Biggest win

2. **Parallelize rig lookups** - Query all rigs concurrently
   - Use goroutines + WaitGroup
   - Reduces R sequential calls to 1 parallel batch

3. **Optional: Short TTL cache** - Cache worker assignments for 5-10s
   - Worker assignments rarely change mid-query
   - Helps repeated status checks

## Files

- `internal/cmd/convoy.go:1213-1294` - `getWorkersForIssues()`
- `internal/cmd/convoy.go:1032-1113` - `getTrackedIssues()` (calls the above)

## Expected Impact

Before: 300-600ms for moderate convoys
After: 50-100ms (batch + parallel)

Implemented batching and parallelization of sqlite queries


Digest: mol-deacon-patrol

Patrol cycle 1: All healthy - witnesses, refineries running. No callbacks, no orphans, no pending spawns.


Digest: mol-deacon-patrol

Patrol 20: All healthy, handoff threshold reached


Session ended: gt-gastown-crew-joe


Merge: testcat-mjtk387i

branch: polecat/testcat-mjtk387i
target: main
source_issue: testcat-mjtk387i
rig: gastown


Digest: mol-deacon-patrol

Patrol 71: Routine


Digest: mol-deacon-patrol

Patrol cycle 36: All healthy, no incidents


File locking for concurrent access

Add file locking for concurrent access safety.

## At-Risk Files
- .gastown/swarms.json (or per-swarm state.json)
- .gastown/refinery.json
- polecats/<name>/state.json
- inbox.jsonl files

## Go File Locking
Use syscall.Flock for advisory locking:
```go
type FileLock struct {
    file *os.File
}

func AcquireLock(path string, timeout time.Duration) (*FileLock, error) {
    f, err := os.OpenFile(path+".lock", os.O_CREATE|os.O_RDWR, 0644)
    if err != nil {
        return nil, err
    }
    // Use syscall.Flock with timeout
}

func (l *FileLock) Release() error
```

## Integration Pattern
```go
func (m *Manager) saveState(ref *Refinery) error {
    lock, err := AcquireLock(m.stateFile(), 5*time.Second)
    if err != nil {
        return fmt.Errorf("could not acquire lock: %w", err)
    }
    defer lock.Release()

    // Read-modify-write cycle
}
```

## New Package
internal/filelock/
├── lock.go     # FileLock, AcquireLock
└── lock_test.go

## Apply To
- internal/refinery/manager.go: loadState/saveState
- internal/cmd/swarm.go: SwarmStore
- internal/mail/mailbox.go: Append, rewrite
- internal/polecat/manager.go: state operations

## Timeout Handling
Default 5 second timeout. Return error if lock not acquired.

## Acceptance Criteria
- [ ] Lock files created (.lock extension)
- [ ] Timeout on lock contention
- [ ] All state files protected
- [ ] Locks released on error paths


Merge: keeper-dogs

branch: polecat/keeper-dogs
target: main
source_issue: keeper-dogs
rig: gastown


Digest: mol-deacon-patrol

Patrol 15: All healthy.


Merge: furiosa-mjxlt6tk

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


Review PR #87: add --no-daemon flag to mol bond

PR #87 approved - follows established --no-daemon pattern


Merge: gt-svdsy

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


Digest: mol-deacon-patrol

Patrol 155 complete


Session ended: gt-gastown-crew-jack


Session ended: gt-gastown-crew-max

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


Validate rig names to reject hyphens and special characters

GHI #23: Add validation in Manager.AddRig() to reject rig names with hyphens (and other special chars) that break agent ID parsing.

**Problem**: Agent IDs use format `<prefix>-<rig>-<role>[-<name>]` with hyphens as delimiters. Hyphenated rig names like `op-baby` cause parsing failures.

**Fix location**: internal/rig/manager.go:172 (AddRig function)

**Implementation**:
1. Add validation after RigExists check (~line 173)
2. Reject names containing: hyphens, spaces, dots
3. Suggest sanitized alternative in error message

Example:
```go
if strings.Contains(opts.Name, "-") {
    return nil, fmt.Errorf("rig name %q contains hyphens which break agent ID parsing; use %q instead",
        opts.Name, strings.ReplaceAll(opts.Name, "-", ""))
}
```

Ref: https://github.com/steveyegge/gastown/issues/23

Implemented rig name validation in Manager.AddRig()


Session ended: gt-gastown-crew-gus


Digest: mol-deacon-patrol

Patrol complete: checked inbox (empty), scanned health (all witnesses/refineries running), no gates, no convoys, no orphans, 1 dog idle, no plugins


Day 2.7: Bootstrap creates agent beads

Bootstrap commands must create agent beads for ZFC compliance.

## Problem
- `gt install` doesn't create Deacon/Mayor agent beads
- `gt rig add` doesn't create Witness/Refinery agent beads
- `gt doctor` doesn't verify agent beads exist
- Current beads were created manually (not reproducible)

## Impact
Patrol ignition (gt-qpoxz) depends on agent beads existing.
Without bootstrap creating them, fresh installs will fail.

## Required
1. gt install: Create gt-deacon and gt-mayor beads
2. gt rig add: Create gt-witness-<rig> and gt-refinery-<rig> beads
3. gt doctor: Add agent-beads-exist check
4. gt doctor --fix: Create missing agent beads


Digest: mol-deacon-patrol

Patrol 4: healthy


gt mail inbox: identity auto-detection fails for refinery

When running from ~/gt/gastown/refinery or ~/gt/gastown/refinery/rig, 'gt mail inbox' detects identity as 'mayor/' instead of 'gastown/refinery'.

Workaround: Use --identity flag explicitly:
  gt mail inbox --identity gastown/refinery

This breaks the refinery patrol because it can't see its MERGE_READY messages.

Expected: Running from gastown/refinery should auto-detect identity as gastown/refinery.

Related: The refinery has 9 unread MERGE_READY messages that aren't being processed because of this bug.

Added refinery/witness detection to detectSender()


Digest: mol-deacon-patrol

Patrol 244 complete: no incidents


Digest: mol-deacon-patrol

Patrol 20: all clear - handoff threshold reached


Digest: mol-deacon-patrol

Patrol cycle 19


MessagingConfig: Add missing test cases

Missing test coverage:
1. Version > CurrentMessagingVersion rejection
2. Malformed JSON handling


Digest: mol-deacon-patrol

Patrol 8: All clear


Digest: mol-deacon-patrol

Cycle 296: All healthy, refinery active


Session ended: gt-gastown-crew-joe


Session ended: gt-gastown-slit


Restart daemon to activate Boot integration

The running daemon (PID 70670) predates Boot code. Daemon heartbeat() now calls ensureBootRunning() but the stale process never executes it.

Fix: gt shutdown --force && gt start

This is a one-time operational task, not a code change.

Daemon restarted, Boot now spawning successfully


Digest: mol-deacon-patrol

Patrol 39: all quiet


Merge: capable-1767084028536

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

Conflicts with main - mrqueue package was removed. Notified capable to rebase.


Session ended: gt-gastown-crew-max


Digest: mol-refinery-patrol

Patrol: Cleaned 2 stale MRs (dag unverifiable, furiosa already merged). Queue empty.


Digest: mol-deacon-patrol

Patrol 12: All healthy, beads/witness now responding


gt sling: nudge witness+refinery when dispatching to polecat

When gt sling dispatches work to a polecat, wake the rig's witness and refinery.

## Boot+Nudge Pattern

```go
// After successful polecat dispatch
func wakeRigAgents(rig string) {
    // Level 2: Start if asleep
    exec.Command("gt", "rig", "boot", rig).Run()

    // Level 1: Clear backoff if running
    t := tmux.NewTmux()
    t.NudgeSession(fmt.Sprintf("gt-%s-witness", rig), "wake")
    t.NudgeSession(fmt.Sprintf("gt-%s-refinery", rig), "wake")
}
```

## Graceful Behavior

- `gt rig boot` is idempotent (no-op if already running)
- Nudge failures are silent (session might not exist yet)
- Agents discover what happened by checking reality

## Reference

See ~/gt/docs/patrol-system-design.md (Two-Level Wake section)


Digest: mol-deacon-patrol

Patrol 13: routine


Merge: rictus-mjuic92c

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


Digest: mol-deacon-patrol

Patrol #16


Create session name helper functions in internal/session

attached_args: Session name helper functions

Create helper functions for consistent session name construction.

## Files to modify
- internal/session/names.go (NEW)

## Implementation
Create new file with these functions:
```go
package session

import "fmt"

const Prefix = "gt-"

func MayorSessionName() string { return Prefix + "mayor" }
func DeaconSessionName() string { return Prefix + "deacon" }
func WitnessSessionName(rig string) string { return fmt.Sprintf("%s%s-witness", Prefix, rig) }
func RefinerySessionName(rig string) string { return fmt.Sprintf("%s%s-refinery", Prefix, rig) }
func CrewSessionName(rig, name string) string { return fmt.Sprintf("%s%s-crew-%s", Prefix, rig, name) }
func PolecatSessionName(rig, name string) string { return fmt.Sprintf("%s%s-%s", Prefix, rig, name) }
```

## Acceptance criteria
- [ ] New file internal/session/names.go created
- [ ] All 6 helper functions implemented
- [ ] Unit tests in internal/session/names_test.go
- [ ] go build ./... passes
- [ ] go test ./internal/session/... passes


Review PR #42: fix(done): detect default branch instead of hardcoding 'main'

Review PR #42. Verify default branch detection works correctly. Approve with gh pr review --approve if good.


Day 1.4: Create agent beads for core roles

Create agent beads with deterministic IDs:
- gt-mayor (Mayor coordinator)
- gt-witness (per-rig polecat monitor)
- gt-refinery (per-rig merge processor)

Each with role_type, rig, and initial state.

Parent: gt-d0jqp


Session ended: gt-gastown-crew-jack


Digest: mol-deacon-patrol

Patrol 127: All healthy


Session ended: gt-gastown/crew/jack


Digest: mol-deacon-patrol

Patrol cycle 13: All healthy - Witness/Refinery running, 7 polecats monitored


Digest: mol-deacon-patrol

Patrol cycle 2: all healthy, 2 health acks


Merge: rictus-1767073382273

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

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


Digest: mol-deacon-patrol

Cycle 195: Nominal


Digest: mol-deacon-patrol

Cycle 292: All healthy, no incidents


Digest: mol-deacon-patrol

Patrol 99: All healthy


Digest: mol-deacon-patrol

Patrol 202: All agents healthy, 10 polecats working, 3 refineries processing. No incidents.


Digest: mol-deacon-patrol

Patrol 17: 3 rigs healthy


Digest: mol-deacon-patrol

Patrol 83: All healthy, quiet


Session ended: gt-gastown-crew-joe


LESSONS: Idle Polecat Incident 2026-01-02

# Incident Summary

On 2026-01-02, discovered 9 polecats (5 gastown, 4 beads) sitting idle at Claude prompts with completed work, burning money.

## Root Cause Chain

1. **Polecats completed work, submitted MRs** - correct
2. **Refinery merged work, sent MERGED to Witness** - correct
3. **Witness had 50 unread protocol messages** - BUG
4. **Witness not processing mail = no cleanup** - BUG
5. **Polecats waiting for death that never comes** - SYMPTOM

## Why Witness Wasn't Patrolling

Witness is a Claude agent with template instructions. When it finishes one patrol cycle, it stops. The template doesn't emphasize continuous operation.

Claude agents are stateless between turns - they complete a task and wait for next input. Without continuous prompting, they idle.

## Design Flaw

The current model:
```
Polecat → gt done → WAIT for Witness to kill
Witness → process mail → nuke polecat
```

**Problem**: If Witness doesn't patrol, polecats wait forever.

## Fixes Implemented

1. **gt-lynar**: `gt done --exit` - polecats self-terminate after MR submission
2. **gt-x4ad3**: Update polecat template to use --exit
3. **gt-fo9iz**: Nuke safety check - verify work on main, not just 'pushed'

## Lessons

### L1: Agents Should Self-Terminate When Done
Don't rely on external actors to clean up. If an agent finishes work, it should exit immediately. Waiting = burning money.

### L2: Mail Is Not Reliable Signaling
Claude agents may not process mail continuously. Don't design protocols that require prompt mail processing.

### L3: Safety Checks Must Match Reality
After branch merge+delete, local branch looks 'unpushed' even though work is on main. Safety checks should verify actual state (is work on main?) not proxy state (is branch pushed?).

### L4: Idle State Is A Bug
Gas Town's propulsion principle: agents are pistons that fire. Idle agents violate this. If an agent can be idle, the design is wrong.

### L5: Monitor What Matters
We had convoy status, polecat lists, etc. But no alert for 'polecat at prompt doing nothing'. Add monitoring for actual idle time.

## Future Prevention

1. Polecat template: always use `gt done --exit`
2. Witness template: emphasize continuous patrol (or replace with daemon)
3. Add `gt doctor` check: 'polecats at prompt for >N minutes'
4. Consider: Replace Witness with daemon (more reliable than Claude patrol)


Session ended: gt-gastown-chrome


Digest: mol-deacon-patrol

Patrol 2: all healthy, no events


Document polecat lifecycle (session/sandbox/slot layers)

## Problem

The polecat lifecycle has three distinct layers that are not clearly documented:

1. Session (Claude context) - ephemeral, cycles per step
2. Sandbox (worktree) - persistent until nuke
3. Slot/Name (pool allocation) - persistent until nuke

This has led to confusion and heresies like "idle polecats" and "recycling."

## Required Documentation

Create docs/polecat-lifecycle.md covering:

### The Three Layers

| Layer | Component | Lifecycle |
|-------|-----------|-----------|
| Session | Claude (tmux) | Ephemeral - cycles per step |
| Sandbox | Worktree | Persistent until nuke |
| Slot | Name from pool | Persistent until nuke |

### Correct Lifecycle

1. gt sling -> sandbox created, session started
2. Work happens, session cycles, sandbox persists
3. gt done -> Witness receives POLECAT_DONE
4. Witness nukes -> sandbox destroyed, slot released

### What "Recycle" Means

- Session cycling: Normal - Claude restarts, sandbox stays
- Sandbox recreation: Repair only - should be rare

### Anti-patterns

- Idle polecats (should not exist)
- Manual state transitions (gt polecat done/reset)
- Sandboxes without work assigned

## Location

docs/polecat-lifecycle.md with cross-references from:
- PRIMING.md
- docs/understanding-gas-town.md
- templates/polecat-CLAUDE.md

Created docs/polecat-lifecycle.md with three-layer architecture, correct lifecycle flow, and anti-patterns. Added cross-references from understanding-gas-town.md and polecat-CLAUDE.md template.


gt done: Require pushed branch before MR creation

gt done currently creates MR bead without verifying the branch is pushed.

## Problem
Polecat can run `gt done` with unpushed commits. MR bead references a branch that doesn't exist on remote. Work is lost when polecat is nuked.

## Fix
In done.go runDone():
```go
// Before creating MR bead
status, err := g.CheckUncommittedWork()
if status.UnpushedCommits > 0 {
    return fmt.Errorf("branch has %d unpushed commits; run 'git push' first", status.UnpushedCommits)
}
```

## Already Have
- `computeCleanupStatus()` checks this but only reports, doesn't block
- Just need to make it a hard error

Fixed: Added BranchPushedToRemote check in runDone() before MR creation


Digest: mol-deacon-patrol

Patrol cycle 27: All healthy, no incidents


Digest: mol-deacon-patrol

Patrol cycle 12: All healthy - Witness monitoring 7 polecats, Refinery running, no pending work. Cleaned stale wisp gt-eph-5hp.


Digest: mol-deacon-patrol

Patrol 12: healthy


Add charmbracelet TUI dependencies (bubbletea, bubbles)

Add charmbracelet TUI framework dependencies.

## Dependencies to add
```
go get github.com/charmbracelet/bubbletea
go get github.com/charmbracelet/bubbles
```

## Already have
- github.com/charmbracelet/lipgloss (styling)

## Verify
- go mod tidy
- go build ./...

Quick task, just adding deps.


Digest: mol-deacon-patrol

Patrol 46: All healthy


Digest: mol-deacon-patrol

Patrol #20: All healthy, 3 convoys active


Digest: mol-deacon-patrol

Patrol 7: all clear


Digest: mol-deacon-patrol

Patrol complete: routine, town stable


Create StartupNudge function with seance metadata

## Location
internal/session/startup.go (new file) or internal/agent/startup.go

## Interface
```go
type StartupNudgeConfig struct {
    Recipient   string  // gastown/crew/gus, deacon, gastown/witness
    Sender      string  // mayor, deacon, self (for handoff)
    Topic       string  // mol-id, "cold-start", "handoff", "assigned"
    MolID       string  // Optional: specific molecule being worked
}

func StartupNudge(t *tmux.Tmux, session string, cfg StartupNudgeConfig) error
```

## Nudge Format
```
[GAS TOWN] gastown/crew/gus ← deacon • 2025-12-30T15:42 • assigned:gt-abc12
```

This becomes the session title in Claude Codes `/resume` picker.

## Implementation
1. Build the formatted message from config
2. Call t.NudgeSession(session, message) for reliable delivery
3. Return error if session doesnt exist or nudge fails

## Key Insight
The message content doesnt trigger GUPP - CLAUDE.md and hooks handle that.
The metadata makes sessions identifiable in `/resume`.

Created StartupNudge function in internal/session/startup.go with StartupNudgeConfig struct. Format: [GAS TOWN] recipient <- sender • timestamp • topic[:mol-id]. Tests pass.


Digest: mol-deacon-patrol

Patrol cycle 71: All healthy


stderr suppression hides critical errors

Multiple files set cmd.Stderr = nil:
- prime.go
- up.go
- orphans.go
- daemon.go

This hides git errors and command failures.

Should capture stderr for debugging and log when errors occur,
rather than suppressing entirely.

Fixed stderr suppression: prime.go (3 locations), orphans.go (1 location), patrol_helpers.go (4 locations) now capture stderr for debugging. Daemon launch cases (up.go, daemon.go, daemon_check.go) keep nil for proper detachment but have clarifying comments.


Digest: mol-deacon-patrol

Patrol 8: all clear


Session ended: gt-gastown-crew-joe


Digest: mol-deacon-patrol

Patrol cycle 2: All healthy. No callbacks, orphans, or gates.


Digest: mol-deacon-patrol

Patrol complete: both rigs healthy, handoff threshold reached


Digest: mol-deacon-patrol

Patrol 10: healthy


Human escalation: notify overseer when self-heal fails

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

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

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

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

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


Merge: rictus-1767138835254

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


Merge: nux-mjxaphp6

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


Witness: must send MERGE_READY to Refinery after POLECAT_DONE

Integration test gt-7psb8 revealed that Witness processes POLECAT_DONE but doesn't send MERGE_READY to Refinery.

Observed:
- Witness receives POLECAT_DONE from furiosa
- Witness verifies branch pushed
- Witness kills polecat session
- Witness notifies Mayor
- NO MERGE_READY sent to Refinery

Expected: After successful pre-kill verification, Witness should send MERGE_READY to gastown/refinery with the MR details.


Digest: mol-deacon-patrol

Patrol 8: all healthy


Digest: mol-deacon-patrol

Patrol cycle 261: Cleaned 510+ stale wisps (57 hq, 450+ gt). All agents healthy (3 witnesses, 3 refineries, 10 polecats active). No incidents.


Add mutex to swarm.Manager for thread safety

attached_args: Add swarm.Manager mutex for thread safety

Fix potential race condition in swarm manager's map access.

## Files to modify
- internal/swarm/manager.go
- internal/swarm/manager_test.go (add concurrency test)

## Problem
The swarms map[string]*Swarm is accessed without synchronization:
```go
type Manager struct {
    swarms map[string]*Swarm  // No mutex protection
}
```

Multiple goroutines could call Create(), GetSwarm(), etc. simultaneously.

## Implementation
```go
type Manager struct {
    mu     sync.RWMutex
    swarms map[string]*Swarm
}

func (m *Manager) GetSwarm(id string) *Swarm {
    m.mu.RLock()
    defer m.mu.RUnlock()
    return m.swarms[id]
}
```

## Acceptance criteria
- [ ] sync.RWMutex added to Manager struct
- [ ] All map accesses protected by mutex
- [ ] RLock for reads, Lock for writes
- [ ] Concurrency test added
- [ ] go test -race ./internal/swarm/... passes


Witness/Refinery startup: Enforce patrol molecule attachment

Witness and Refinery CLAUDE.md says to:
1. Check for attached molecule on startup
2. If not attached, spawn patrol wisp

But sessions are just reacting to mail without patrol structure.

Current state:
- gt-8ynws (Witness Patrol) exists but not attached
- gt-t5i07 (Refinery Patrol) exists but not attached
- Sessions are running without structured patrol loops

Fix options:
1. Add SessionStart hook to enforce patrol attachment
2. Make 'gt start' attach patrol molecules when spawning witness/refinery
3. Add reminder in CLAUDE.md startup banner that's more emphatic

Fixed autoSpawnPatrol in patrol_helpers.go to use --status=hooked instead of --status=pinned. This ensures patrol wisps are visible to gt mol status and trigger autonomous mode.


Digest: mol-deacon-patrol

Patrol 4: All healthy


Day 2.7c: gt doctor checks agent beads exist

Add gt doctor check: agent-beads-exist

1. For town-level: verify gt-deacon and gt-mayor beads exist
2. For each rig: verify gt-witness-<rig> and gt-refinery-<rig> beads exist
3. Report missing beads as warnings
4. Implement --fix to create missing beads

Files:
- internal/doctor/agent_beads_check.go (new)
- internal/cmd/doctor.go (register check)


Refactor: Break up long functions in polecat/manager.go

Several functions exceed 50 lines and have too many responsibilities:

1. Add() (lines 195-276, ~80 lines)
   - Extract: createWorktree(), createAgentBead(), setupBeadsRedirect()

2. RepairWorktreeWithOptions() (lines 405-500, ~95 lines)
   - Near-duplicate of Add() logic - DRY violation
   - Extract common setup to shared helper

3. RemoveWithOptions() (lines 291-360, ~70 lines)
   - Extract: checkRemovalSafety(), cleanupWorktree()

4. setupSharedBeads() (lines 729-782, ~55 lines)
   - Fairly focused but could be cleaner


Session ended: gt-gastown-crew-max


Digest: mol-deacon-patrol

Cycle 295: All healthy, gastown refinery 1 pending


Merge: rictus-1767059698035

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

Work already merged to main - branches were never created or already deleted


Overnight Hanoi Demo: Progress monitoring for mega-molecules

Support monitoring million-step molecules for the overnight Hanoi demo.

## Context
Towers of Hanoi with 20 disks = 2^20 - 1 = 1,048,575 steps.
Current display code assumes reasonable step counts.

## Requirements
1. Progress summary command (bd mol progress)
2. Large molecule guards in display code
3. Agent-actionable formula description
4. Convoy integration for completion tracking

## Success Criteria
- Can cook and pour 20-disk Hanoi formula
- Can monitor progress without listing all steps
- Agent can resume mid-molecule with clear instructions
- Know when it is done (convoy or completion signal)

7-disk demo proved molecule system works. Large molecule creation needs optimization before overnight 20-disk run.


Add bd mol progress command for mega-molecule summary

New command that shows progress percentage, current step, rate, ETA without listing all steps.

Output format:
Molecule: gt-hanoi-xyz (Towers of Hanoi - 20 disks)
Progress: 347,892 / 1,048,575 (33.2%)
Current step: gt-hanoi-xyz.move-347893
Rate: ~1,200 steps/hour
ETA: ~9.7 hours remaining

Implementation in beads repo.

Duplicate of beads work: bd-8xnf, bd-vln0


Add display guards for large molecules

bd mol current and convoy displays need guards:
- If steps > 100, show summary instead of list
- Add --range flag for viewing specific step ranges
- Convoy panel shows count + percentage, not child list


Update Hanoi formula with agent-actionable instructions

Update towers-of-hanoi.formula.toml description to include:
- Clear execution protocol for agents
- What each step means
- What to do on resume
- Explicit DO/DO NOT guidance

Updated formula with agent-actionable instructions


Run 1K Hanoi demo (10 disks)

End-to-end test:
1. Cook 20-disk Hanoi formula
2. Pour as wisp on a polecat
3. Let it run overnight
4. Monitor with bd mol progress
5. Verify completion via convoy

7-disk demo succeeded (127 moves, 21 sec). 10-disk blocked by wisp creation timeout - needs performance optimization.


Deacon health checks too frequent - disturbs idle agents

Deacon is sending health checks to mayor every few seconds even when idle. This defeats the purpose of the patrol system - agents should NOT be disturbed when there's nothing going on.

Proposed fix:
1. Reduce health check frequency (e.g., every 60s instead of every 5s)
2. Or: Only health check agents that have active work (hooked molecules)
3. Or: Mayor shouldn't be health-checked at all since it's human-managed

The deacon should be silent/invisible when the town is healthy.

Added idle town protocol - skip health nudges when no active work, sleep 60+ seconds between cycles


Digest: mol-deacon-patrol

Patrol 9: All healthy.


Digest: mol-deacon-patrol

Patrol 246 complete: no incidents


Session ended: gt-gastown-organic


Merge: slit-mjui7j71

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


Session ended: gt-gastown-coma


Digest: mol-deacon-patrol

Patrol #9: Health pinged all refineries.


Merge: rictus-mjw1g7ny

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


Merge: keeper-dogs

branch: polecat/keeper-dogs
target: main
source_issue: keeper-dogs
rig: gastown


Digest: mol-deacon-patrol

Patrol cycle 9: All healthy, 4 polecats


Digest: mol-deacon-patrol

Cycle 17


Session ended: gt-refinery


Merge: toast-mjxm0mmo

branch: polecat/toast-mjxm0mmo
target: main
source_issue: toast-mjxm0mmo
rig: gastown
agent_bead: gt-gastown-polecat-toast


Add NODE_OPTIONS crash logging to claude invocations

## Background

Investigation in bd-6df0 found that NODE_OPTIONS can enable Node.js crash reporting for Claude Code:

```bash
NODE_OPTIONS="--report-on-fatalerror --report-uncaught-exception --report-directory=$HOME/.claude/crash-reports"
```

## Proposal

Modify gt to set NODE_OPTIONS when spawning claude sessions. Key locations in gastown:

- `internal/cmd/up.go` - deacon, witness, crew, polecat spawning
- `internal/cmd/start.go` - refinery, crew spawning
- `internal/cmd/mayor.go` - mayor spawning
- `internal/cmd/handoff.go` - handoff respawning
- `internal/cmd/crew_helpers.go` - execClaude()
- `internal/daemon/lifecycle.go` - daemon-managed spawns

## Implementation

1. Create helper function to build NODE_OPTIONS string
2. Set env var before each claude invocation
3. Ensure ~/.claude/crash-reports/ directory exists
4. Optionally add `gt doctor` check to clean old reports

## Benefits

- Crash diagnostics without user config changes
- All gt-managed sessions automatically covered
- Reports available for debugging unexpected terminations


Test Patrol Parent

[RESURRECTED] This issue was deleted but recreated as a tombstone to preserve hierarchical structure.

Original description:
Test parent for Christmas Ornament pattern


Digest: mol-deacon-patrol

Patrol 58: All healthy


Digest: mol-deacon-patrol

Patrol cycle 63: All healthy


Parameterize 'read org docs' step in polecat workflows

## Problem

The mol-polecat-chrome formula has hardcoded paths to strategic docs:
- ~/gt/docs/hop/CONTEXT.md
- ~/gt/docs/PRIMING.md

This limits reusability across different projects/organizations.

## Solution

Add a `context_docs` variable to the formula that takes a list of document paths:

```toml
[vars.context_docs]
description = "Paths to organizational context documents to read before design"
default = ["~/gt/docs/hop/CONTEXT.md", "~/gt/docs/PRIMING.md"]
```

## Usage at cook/wisp time

```bash
# Use defaults
gt sling gt-abc gastown -q chrome

# Override for different project
gt sling gt-abc gastown -q chrome   --var context_docs="docs/ARCHITECTURE.md,docs/PRINCIPLES.md"
```

## Step template

The step description would interpolate the paths:

```toml
[[steps]]
id = "read-strategic-context"
title = "Read organizational context documents"
description = """
Read the following strategic context documents before designing:

{{#each context_docs}}
- {{this}}
{{/each}}

These contain the vision, principles, and constraints that should guide your work.
"""
```

## Considerations

1. How to handle the array in TOML var syntax?
2. Should this be a reusable step fragment or inline in chrome?
3. Default paths should work for Gas Town but be overridable

## Related
- mol-polecat-chrome.formula.toml (current hardcoded implementation)

Implemented mol-polecat-chrome formula with parameterized context_docs variable. Default paths point to Gas Town docs, can be overridden via --var context_docs="..." at sling time.


Session ended: gt-gastown-crew-joe


Add convoy_test.go tests

convoy.go (525 lines) has no test coverage


Digest: mol-deacon-patrol

Patrol cycle 2: All healthy. 3 rigs responding. No issues.


Consider gt polecats --status=idle for swarm dispatch

The dispatch-swarm-work step in mol-witness-patrol uses a complex jq command to find idle polecats:

```bash
bd list --type=agent --json | jq '[.[] | select(.description | contains("role_type: polecat") and contains("agent_state: idle"))]'
```

Consider adding a dedicated command:
```bash
gt polecats <rig> --status=idle
```

Benefits:
- Simpler for formula authors
- Encapsulates agent bead schema details
- Could add --json output for programmatic use

Low priority - the jq approach works, this is just ergonomics.


Witness patrol formula should document 'dead' agent state

## Problem

The witness patrol formula survey-workers step documents these agent states:

  agent_state: running|idle|stuck|done

But the daemon lifecycle (daemon/lifecycle.go:647) can set agent_state to 'dead'
when an agent goes unresponsive. This state isn't mentioned in the formula.

## Found in commit

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

## Suggested fix

Update the formula survey-workers step to include 'dead' in the state table:

| agent_state | Meaning | Action |
|-------------|---------|--------|
| running | Actively working | Check progress (Step 3) |
| idle | No work assigned | Skip (no action needed) |
| stuck | Self-reported stuck | Handle stuck protocol |
| done | Work complete | Verify cleanup triggered |
| dead | Marked dead by daemon | Clean up or respawn |


Digest: mol-deacon-patrol

Patrol 17: routine


Merge: dementus-mjxc9ivl

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

Branches merged, cleaning up stale MR beads


Digest: mol-deacon-patrol

Patrol cycle 33: All healthy, no incidents


Merge: rictus-1767084016819

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


CLI: all command for batch polecat operations

Batch operations across multiple polecats.

## Commands

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

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

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

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

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

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

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

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

## New File
internal/cmd/all.go

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

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


Campaign: Large-Scale Work Graph Execution

A Campaign is a Molecule-wrapped Epic graph ready for orchestrated execution.

## Concept

Campaign = Molecule + Epics + Dependency Graph + Execution Strategy

Key properties:
1. **Work graph**: Linked epics with dependency edges (possibly from different authors)
2. **Combinators as glue**: >>, |, wrap define bridging/shaping between epics
3. **Molecule shell**: Provides execution state, persistence across sessions
4. **Witness-aware**: Can be parallelized across polecats on independent branches
5. **Single-rig scoped**: One rig executes it (spans sessions/polecats)

## The "Maximum Speed" Model

Witness becomes a scheduler that:
- Watches campaign progress
- Identifies ready nodes (all dependencies satisfied)
- Spawns/assigns polecats to ready nodes
- Respects parallelism constraints
- Handles failures (retry? skip? escalate?)

## Where Campaign Fits in MEOW

Campaign is the convergence point where work-tracking meets workflow-execution:
- An Epic-of-Epics (work dimension)
- A Molecule (workflow dimension)

## Open Questions

1. Campaign creation UX - manual wire vs declare intent?
2. Partial completion - can you commit progress and resume?
3. Distinct entity type or molecule with type="campaign"?
4. Cross-rig campaigns - can graph span multiple rigs?

## Related

- docs/formula_evolution.md - original discussion
- gt-8tmz - molecule algebra (foundation)


Digest: mol-deacon-patrol

Patrol 48: All healthy


Break up long functions in start.go

Several functions in internal/cmd/start.go exceed recommended length:
- runStart: 126 lines (129-255) - handles mayor, deacon, rig agents, crew auto-start
- runStartCrew: 155 lines (638-792) - handles crew target parsing, workspace, session
- cleanupPolecats: 91 lines (543-634) - handles polecat cleanup loop

Suggestion: Extract into focused helpers:
- runStart → startMayorAndDeacon(), startRigAgents(), startConfiguredCrew()
- runStartCrew → parseCrewTarget(), ensureCrewWorkspace(), startOrAttachCrewSession()
- cleanupPolecats → cleanupSinglePolecat()


gt convoy status: Show assignee and actual status for tracked issues

Currently convoy status shows all open issues as ○ with no distinction:

```
○ gt-27bzi: Add gt mail announces command [task]
○ gt-pn2fq: Add isAnnounceAddress() helpers [task]
○ gt-q73h3: Implement sendToAnnounce() [task]
```

But these issues have different statuses and assignees:
- gt-pn2fq: closed (immortan) - should show ✓
- gt-q73h3: hooked (bullet) - should show 🔨 or similar
- gt-27bzi: in_progress (toecutter) - should show ▶

## Proposed display

```
✓ gt-pn2fq: Add isAnnounceAddress() helpers [immortan]
▶ gt-q73h3: Implement sendToAnnounce() [bullet]
▶ gt-27bzi: Add gt mail announces command [toecutter]
```

Show actual status symbols and assignee in brackets.


Digest: mol-deacon-patrol

Patrol cycle 10


Digest: mol-deacon-patrol

Patrol 15: routine


gt done: Fails in worktrees due to missing origin tracking ref

In polecats (git worktrees), after git push -u origin <branch>, the origin/<branch> ref doesn't exist locally. This causes gt done to fail with 'unknown revision' when checking unpushed commits.

**Workaround**: After pushing, manually create the ref:
```bash
git fetch origin <branch>
git update-ref refs/remotes/origin/<branch> FETCH_HEAD
gt done
```

Root cause: Worktrees may not have the same fetch refspecs as the main repo. The tracking is set but the remote ref isn't fetched into refs/remotes/.

Fix: gt done should handle this edge case, perhaps by checking the remote directly or creating the ref after detecting the issue.

Fixed: BranchPushedToRemote now creates missing remote tracking refs from FETCH_HEAD after fetch in worktrees


Wisp squash design: cadences, rules, templates

Design how wisps squash to digests: When to squash (cadences), what to include (summary templates), retention rules. Currently under-designed. Should cover patrol wisps (squash per cycle) and work wisps (squash on completion). Consider allowing formulas to define squash templates.

Designed wisp squash system: cadences (per-cycle for patrols, on-complete for work), summary templates (structured formats, formula-defined), retention rules (30d active, 1yr archive, rollups). See docs/wisp-squash-design.md


Digest: mol-deacon-patrol

Patrol 44: All healthy


Digest: mol-deacon-patrol

Patrol #8: Refineries healthy. Beads 5 pending, gastown 1 pending.


Merge: capable-mq

branch: polecat/capable-mq-events
target: main
source_issue: capable-mq
rig: gastown


Digest: mol-deacon-patrol

Patrol cycle 32: All healthy, no incidents


Session ended: gt-gastown-rictus


Merge: gt-k1sl4

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


Extract interfaces for Git and Tmux to enable testing

Git and Tmux packages use concrete types that make testing difficult:

internal/git/git.go:
- Git struct with 40+ methods
- No interface - tests must use real git
- Many packages directly depend on concrete type

internal/tmux/tmux.go:
- Tmux struct with 30+ methods
- No interface - tests must use real tmux
- Contains Claude-specific detection logic mixed in

Suggestion:
1. Extract GitOperations interface with common operations
2. Extract TmuxRunner interface for subprocess operations
3. Allow mock injection for unit tests
4. Move Claude-specific tmux logic to internal/claude/


Digest: mol-deacon-patrol

Patrol 6 complete. All healthy.


Digest: mol-deacon-patrol

Patrol 17: All healthy.


Session ended: gt-gastown-road-warrior

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


Phase 2: Update Refinery to merge local branches

Change Refinery to access polecat branches locally instead of fetching from origin.

Files:
- internal/refinery/engineer.go:233-260 (doMerge)
  - Remove FetchBranch('origin', branch) call
  - Add BranchExists() check for local branch
  - Change 'origin/<branch>' refs to local '<branch>'
  - Update CheckConflicts() and MergeNoFF() calls

Key insight: Refinery worktree shares .repo.git with polecat worktrees - branches are already visible locally.

Migration: Add fallback to origin/<branch> for in-flight MRs pushed under old system.

Refinery now merges local branches instead of fetching from origin


Session ended: gt-gastown-crew-joe


Session ended: gt-gastown-bullet


Session ended: gt-gastown-crew-gus


Digest: mol-deacon-patrol

Patrol cycle 16: All agents healthy


Digest: mol-deacon-patrol

Patrol cycle 10: All agents healthy


Digest: mol-deacon-patrol

Patrol cycle 14: All healthy


Digest: mol-deacon-patrol

Cycle 197: Nominal


Witness: Escalate dormant polecats with unpushed work (don't auto-nuke)

Witness escalation for dormant polecats should distinguish between safe-to-nuke and needs-recovery.

## Problem
Witness detected 4 dormant polecats with unpushed commits (hq-ww5ev) but by the time Mayor processed the mail, the polecats were nuked during cleanup, losing work.

## Fix
1. Witness dormancy check should read cleanup_status from agent bead
2. If has_unpushed or has_uncommitted: mark as NEEDS_RECOVERY, escalate
3. If clean: safe to nuke
4. NEVER auto-nuke polecats with unpushed work

## Escalation Format
Subject: RECOVERY_NEEDED <polecat>
Body includes: unpushed commit SHAs, branch name, issue ID

Implemented recovery escalation for dormant polecats with unpushed work


Digest: mol-deacon-patrol

Patrol 1: All 3 rigs healthy, 12 polecats active, no zombies, no orphans, inbox clear


Digest: mol-deacon-patrol

Patrol 42: All healthy, 6 agents pinged, no incidents


Merge: rictus-mjuic92c

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


Digest: mol-deacon-patrol

Patrol 2: All healthy. 3 rigs, 6 agents. No incidents.


Review PR #180: git init before beads

dispatched_by: gastown/crew/joe


Session ended: gt-gastown-wraith


Modified mol-deacon-patrol.formula.toml to make health check nudges conditional - only sent when concerning signals detected (agent not running, status check fails, queue stuck). Previously nudges were sent every 1-2 minute patrol cycle.


Merge: dementus-mjw062kt

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


Merge: organic-1767106082951

branch: polecat/organic-1767106082951
target: main
source_issue: organic-1767106082951
rig: gastown


Digest: mol-deacon-patrol

Patrol 155: All healthy


Session ended: gt-gastown-crew-jack


Merge: capable-mjxb80jf

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


Implement announce:name address type for bulletin-board messaging

Add support for announce:name addresses for read-only bulletin boards.

SEMANTICS:
- Messages to announce:name create ONE copy in shared location
- All eligible readers can read (no claiming, no removal)
- Messages retained up to retain_count, oldest pruned
- No inbox pollution - readers check the announce channel explicitly

USE CASE:
Low urgency broadcast that does not need ack, e.g. clean up your branches.

IMPLEMENTATION:
1. Add isAnnounceAddress() / parseAnnounceName() helpers
2. Add sendToAnnounce() - creates message in announce location
3. Add gt mail announces - list available announce channels
4. Add gt mail read-announce <channel> - read messages from channel
5. Add retention pruning on new message

CONFIG (already in messaging.json schema):
announces.alerts.readers = [@town]
announces.alerts.retain_count = 100


Phase 3: Update polecat templates and docs

Remove push instructions from polecat documentation and templates.

Files:
- templates/polecat-CLAUDE.md:153,159 - Remove push from checklist
- internal/formula/formulas/mol-polecat-work.formula.toml:365-368 - Remove push step
- internal/templates/roles/polecat.md.tmpl - Update completion protocol
- internal/templates/roles/refinery.md.tmpl:227-229 - Update to show local branch access

New checklist:
1. Tests pass
2. Commit changes
3. Sync beads
4. gt done --exit

No push step needed.

Phase 3 complete: removed push instructions from polecat templates and docs


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

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

Migrated to canonical naming: gt-gastown-crew-<name>


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

role_type: crew
rig: gastown
agent_state: idle
hook_bead: null
role_bead: gt-crew-gastown-grip-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-gastown-jack-role


gt-crew-gastown-joe

gt-crew-gastown-joe

role_type:
rig: null
agent_state: running
hook_bead: null
role_bead: null
cleanup_status: null


gt-crew-gastown-max

gt-crew-gastown-max

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


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

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


Crew Role Definition

You are a Crew Worker - a human-managed persistent workspace within a rig.

Unlike polecats which are witness-managed and transient, crew workers are:
- Persistent: Not auto-garbage-collected
- User-managed: Overseer controls lifecycle
- Long-lived identities: Keep your name across sessions
- Gas Town integrated: Mail, handoff mechanics work

session_pattern: gt-{rig}-crew-{name}
work_dir_pattern: {town}/{rig}/crew/{name}
needs_pre_sync: true
start_command: exec claude --dangerously-skip-permissions

default_molecule: null
capabilities:
  - autonomous_work
  - beads_access
  - git_operations
  - mail_communication

## Core Responsibilities

1. Execute assigned work autonomously
2. Track progress via beads (bd commands)
3. Communicate via mail when blocked or complete
4. Maintain clean git state
5. Hand off context when cycling sessions

## Propulsion Principle

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

Hook has work then Run it. Hook empty then Check mail. Nothing then Wait for overseer.

## Session End Protocol

- git status, git add, bd sync, git commit, git push
- gt handoff - hand off to fresh session

## Key Commands

- gt mol status - Check your hook
- bd ready - Find next step
- bd close <id> - Mark work complete
- gt mail inbox - Check messages
- gt handoff - Cycle to fresh session


Digest: mol-deacon-patrol

Patrol 1: all agents healthy, no callbacks, clone divergence detected (zoey 52 behind)


Session ended: gt-gastown-rictus


Errant redirect file in .beads/ causes chain warnings

A redirect file appeared in gastown/mayor/rig/.beads/ alongside the actual beads.db, causing 'redirect chains not allowed' warnings. The redirect pointed to ../../mayor/rig/.beads which is circular. Had to manually delete the redirect file.

Fixed: Added circular redirect detection to ResolveBeadsDir (auto-removes errant redirects) and added safety checks to ensureBeadsRedirect to prevent creating redirects in canonical beads location


Digest: mol-deacon-patrol

Patrol 6: routine


Digest: mol-deacon-patrol

Patrol 1: 3 rigs healthy, no incidents


Digest: mol-deacon-patrol

Patrol cycle 54: All healthy


Merge: gt-84ery

branch: polecat/dementus-mjtlqmya
target: main
source_issue: gt-84ery
rig: gastown


Merge: prime-mk0rmco7

branch: polecat/prime-mk0rmco7
target: main
source_issue: prime-mk0rmco7
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 56: All healthy


Merge: gt-jhr85

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

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


Session ended: gt-gastown-crew-jack


Digest: mol-deacon-patrol

Patrol 252 complete: no incidents


Digest: mol-deacon-patrol

Patrol cycle 16: All healthy


Digest: mol-deacon-patrol

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


Session ended: gt-gastown/crew/jack


Haiku-based smart stuck detection

Use Haiku to analyze tmux state when signals are ambiguous.

## When to Invoke

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

## Prompt

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

## Cost

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

## Configuration

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


Digest: mol-deacon-patrol

Patrol cycle 19: All healthy.


Digest: mol-deacon-patrol

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


Cleanup stale .beads-wisp references in docs and formulas

The .beads-wisp/ approach was eradicated. Wisps now live in main beads. These files still reference the old approach:

- docs/polecat-wisp-architecture.md (line 15)
- .beads/formulas/mol-session-gc.formula.toml (line 165)
- .beads/formulas/mol-boot-triage.formula.toml (line 50)
- .beads/formulas/mol-orphan-scan.formula.toml (lines 144, 233)

Update or remove these references to reflect current architecture.

Cleaned up all .beads-wisp references in docs and formulas. Wisps now reference .beads/ with type=wisp.


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

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

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

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

Parent: gt-oki8p


Digest: mol-deacon-patrol

Patrol 16: Quiet


Digest: mol-deacon-patrol

Patrol 138: All healthy


Merge: scrotus-mk083pw5

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


Mail CLI: archive, purge, search, mark

GGT mail CLI needs more commands for mail management.

## Commands to Add

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

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

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

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

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

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

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

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

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

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


gt mail check: Check for new mail

Add `gt mail check` command.

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

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

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

Simple command, good first task.

Implemented --quiet flag for gt mail check


gt mail mark/delete: Basic message operations

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

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

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

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


gt mail archive/purge: Archive management

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

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

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

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


gt mail search: Find messages by content

Add `gt mail search` command.

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

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

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


gt mail reply: Reply to messages

Add `gt mail reply` command.

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

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

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

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

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


Shell command injection in engineer.go:349

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

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


Daemon binary vs process age mismatch detection

## Problem

The daemon process can run for days/weeks while the binary gets rebuilt multiple times. The running process uses old code:

```
Daemon started: 2025-12-30 (3 days old)
Binary rebuilt: 2026-01-02 (today)
Running code: 1-hour heartbeat interval
Binary code: 3-minute heartbeat interval
```

We only discovered this when debugging why witness/refinery weren't being auto-restarted.

## Impact

- Bug fixes in daemon code don't take effect until manual restart
- New features aren't active
- Debugging is confusing (code says one thing, behavior says another)

## Solutions

### Option A: Self-restart on binary change
```go
func (d *Daemon) checkBinaryAge() {
    binaryStat, _ := os.Stat(os.Args[0])
    if binaryStat.ModTime().After(d.startTime) {
        d.logger.Println("Binary newer than process, restarting...")
        d.restart()
    }
}
```

### Option B: Version embedding + status warning
```go
// Embed build time at compile
var buildTime = "2026-01-02T18:00:00Z"

func (d *Daemon) status() {
    if buildTime != currentBinaryBuildTime() {
        fmt.Println("⚠ Daemon running old code, restart recommended")
    }
}
```

### Option C: Daemon status shows binary age
```
$ gt daemon status
● Daemon is running (PID 53143)
  Started: 2025-12-30 21:56:00
  Binary: 2026-01-02 18:34:00
  ⚠ Binary is newer than process - consider 'gt daemon stop && gt daemon start'
```

## Recommendation

Option C is simplest and most transparent. Let humans decide when to restart.

Implemented Option C: daemon status now shows binary modification time and warns when binary is newer than the running process


Digest: mol-deacon-patrol

Patrol 9: routine


Digest: mol-deacon-patrol

Patrol 6


Merge: rictus-mjtlq9xg

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


Digest: mol-refinery-patrol

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


Digest: mol-deacon-patrol

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


Session ended: gt-gastown-organic


Digest: mol-deacon-patrol

Patrol 88: All healthy


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

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

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

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

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

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

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

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


Add runtime configuration for LLM commands

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

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

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

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

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

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

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

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

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


Digest: mol-deacon-patrol

Patrol 49: All healthy


Digest: mol-deacon-patrol

Patrol 245 complete: no incidents


Digest: mol-deacon-patrol

Patrol #9: nominal


Merge: nux-mjw3mn8o

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


Merge: capable-mjtltnm5

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


Merge: gt-si8rq.9

branch: polecat/nux-mjyruwvu
target: main
source_issue: gt-si8rq.9
rig: gastown
agent_bead: gt-gastown-polecat-nux
retry_count: 0
last_conflict_sha: null
conflict_task_id: null


Code review: session health monitoring (gt-i7wcn)

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

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


attached_args: Extract molecule_lifecycle boilerplate

Extract duplicate boilerplate in molecule_lifecycle.go.

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

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

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

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

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


BUG: Wisps being created in main beads instead of .beads-wisp/

Found 128 wisp beads (hq-wisp-*) in main issues.jsonl during shutdown cleanup. These should NEVER be in main beads - wisps belong in .beads-wisp/ directory.

Root cause investigation needed:
1. Is the wisp creation code writing to wrong location?
2. Is the squash code not deleting wisps after creating digests?
3. Are wisps being copied to main during some sync operation?

Impact: Pollutes main beads with ephemeral data that should never be persisted.

Evidence: Manual cleanup deleted 128 hq-wisp-* beads from ~/gt/.beads/issues.jsonl

Invalid: Wisps in main beads is correct. .beads-wisp/ was eradicated - that was the old approach.


gt-deacon

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

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


Deacon Role Definition

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

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

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

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

## Core Responsibilities

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

## Patrol Cycle

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

## Propulsion Principle

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

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

## Commands

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

## Session Cycling

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

Role definition complete - documentation bead, not implementation task


Digest: mol-deacon-patrol

Patrol 67: Routine


Merge: slit-mjw18vx8

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


P4: Graceful degradation without tmux

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

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

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

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

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

This is P4 backlog - address after liftoff.


Merge: toast-1767074299802

attached_args: Code review this merge request

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

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


Merge: dementus-mjxemx4j

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


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

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

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

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

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


Merge: capable-mjxogaq4

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


gt handoff deadlock at handoff.go:125

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


Session ended: gt-gastown-crew-george


Digest: mol-deacon-patrol

Patrol cycle 37: All healthy, no incidents


Merge: nux-1767081106779

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


Session ended: gt-gastown-shiny


Digest: mol-deacon-patrol

Patrol 125: All healthy


Merge: valkyrie-1767106008400

branch: polecat/valkyrie-1767106008400
target: main
source_issue: valkyrie-1767106008400
rig: gastown


Merge: cheedo-1767079863534

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


Digest: mol-deacon-patrol

Patrol cycle 6: All 6 agents healthy


Digest: mol-deacon-patrol

Patrol 17: quiet


Digest: mol-deacon-patrol

Patrol 3: all clear


BUG: gt status shows 'stopped' for running sessions (bead vs tmux mismatch)

## Problem

`gt status` shows agents as 'stopped' when their tmux session is actually running:

```
🐺 Deacon
   gt-deacon stopped     ← But session exists!
```

Meanwhile:
```bash
$ tmux has-session -t gt-deacon && echo exists
exists
```

## Root Cause

Status display uses agent bead state, not tmux session state. When they disagree, the display is misleading.

## Current Logic

```go
// Simplified
if agentBead.State == "running" {
    display "running"
} else {
    display "stopped"
}
```

## Expected Logic

```go
beadState := agentBead.State
sessionExists := tmux.HasSession(sessionName)

if beadState == "running" && sessionExists {
    display "running"
} else if beadState == "running" && !sessionExists {
    display "running [dead]"  // Bead thinks running, session gone
} else if beadState != "running" && sessionExists {
    display "stopped [session exists]"  // Session exists but bead says stopped
} else {
    display "stopped"
}
```

## Related

The 'running [dead]' state already exists for witness/refinery. This should be consistent across all agents.

Fixed by reconciling tmux session state with bead state in renderAgentDetails()


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Cycle 184: All systems nominal


Session ended: gt-gastown-warboy


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

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

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

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

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

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


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

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


Digest: mol-deacon-patrol

Patrol 142: 3 polecats working, all agents healthy


Digest: mol-deacon-patrol

Patrol cycle 2: all quiet, agents healthy


attached_args: Refactor runStartCrew into focused helper functions

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

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

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

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

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


Digest: mol-deacon-patrol

Patrol 18


Session ended: gt-gastown-crew-dennis


Digest: mol-deacon-patrol

Patrol 2: No changes from cycle 1, all healthy


Digest: mol-deacon-patrol

Patrol 105: All agents healthy, no incidents


Digest: mol-deacon-patrol

Patrol #13


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

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

Obsolete: .beads-wisp/ approach was eradicated. Wisps now live in main beads.


Digest: mol-deacon-patrol

Patrol cycle 77: All healthy


Digest: mol-deacon-patrol

Patrol 11


Update swarm terminology to streams

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

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


Digest: mol-deacon-patrol

Patrol #8: nominal


Merge: rictus-mjxemkiq

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


Digest: mol-deacon-patrol

Patrol 20: quiet, handoff threshold reached


Merge: nux-mjtj9d8q

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


Phase 1: Remove push enforcement from gt done

Remove BranchPushedToRemote() checks that block gt done without push.

Files:
- internal/cmd/done.go:167-174 - Remove push check, keep commits-ahead check
- internal/cmd/mq_submit.go:93-102 - Remove push check entirely

Replace with simple check that branch has commits ahead of default branch.

Backward compatible: polecats that still push won't break.

Removed BranchPushedToRemote checks from done.go and mq_submit.go


Merge: furiosa-mjuk2tlb

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

Work already on main at 0846bfd2, branch already deleted


Digest: mol-deacon-patrol

Cycle 11: quiet


Session ended: gt-gastown-crew-gus


gt sling: Make target path parsing more forgiving

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

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

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


Session ended: gt-gastown-crew-jack


Digest: mol-deacon-patrol

Patrol 8 complete. All healthy.


Merge: goose-mjwlipjg

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


Session ended: gt-gastown-crew-max


Digest: mol-deacon-patrol

Patrol 31: all quiet


Digest: mol-deacon-patrol

Patrol #12: Health pinged all witnesses.


Digest: mol-deacon-patrol

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


Remove redundant gt prime SendKeys from crew commands

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

This is now redundant because SessionStart hook handles priming automatically.

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


Digest: mol-deacon-patrol

Cycle 196: Nominal


Session ended: gt-gastown-crew-george

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


Digest: mol-deacon-patrol

Patrol 13: All healthy.


Digest: mol-deacon-patrol

Patrol 87: All healthy


Idle polecats are a Gas Town bug - investigate and fix

## Bug: Idle Polecats

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

## Root Cause Analysis

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

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

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

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

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

### Design Philosophy Violation

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

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

## Recommendations

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

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

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

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

## Immediate Fix

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

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

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

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

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


Merge: toast-1767079830359

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


Digest: mol-deacon-patrol

Patrol #18


Digest: mol-deacon-patrol

Patrol 131: All healthy


Digest: mol-deacon-patrol

Patrol cycle 4: All healthy, no callbacks


Review PR #184: route rig beads to town

dispatched_by: gastown/crew/joe


Workspace cleanup: preflight and postflight

Workspace preflight and postflight commands for clean state management.

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

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

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

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

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

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

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

## Note

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

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


Session ended: gt-gastown-fury


Digest: mol-deacon-patrol

Patrol cycle 8: All healthy


Digest: mol-deacon-patrol

Patrol 2: 3 polecats working, all healthy


CLI: names commands for polecat naming pool

Polecat naming pool for auto-generated names.

## Commands

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

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

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

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

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

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

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

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

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


Session ended: gt-gastown-crew-max


Digest: mol-deacon-patrol

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


Session ended: gt-gastown-crew-jack


Merge: corpus-mk0aokk0

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


Digest: mol-deacon-patrol

Cycle 5: 1 msg processed, all healthy


Digest: mol-deacon-patrol

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


Merge: rictus-mjxc967h

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

Branches merged, cleaning up stale MR beads


gt witness/refinery start should have 'ensure' semantics (kill zombie if needed)

## Problem

`gt witness start <rig>` fails if the tmux session already exists:
```
⚠ Witness session already running
  Use 'gt witness attach' to connect
```

But the session might be a zombie (tmux alive, Claude dead). User has to manually:
1. `gt witness stop <rig>`
2. `gt witness start <rig>`

Or use `gt witness restart <rig>` which works but isn't intuitive.

## Proposal

`gt witness start` should have 'ensure running' semantics:

1. If no session exists → create new session
2. If session exists and healthy → do nothing (already running)
3. If session exists but zombie → kill and recreate

This makes `start` idempotent and safe to run repeatedly.

## Implementation

```go
func (w *Witness) Start(rig string) error {
    sessionName := witnessSessionName(rig)

    if w.tmux.HasSession(sessionName) {
        if w.isHealthy(sessionName) {
            return nil  // Already running and healthy
        }
        // Zombie - kill it
        w.tmux.KillSession(sessionName)
    }

    // Create fresh session
    return w.createSession(rig)
}
```

## Alternatives

- Keep current behavior, just improve error message: 'Session exists but appears dead, use restart'
- Add --force flag: `gt witness start --force <rig>`

## Recommendation

Make `start` idempotent. It's the principle of least surprise.

Implemented ensure semantics in witness.go:ensureWitnessSession and refinery/manager.go:Start using IsClaudeRunning to detect zombies


Session ended: gt-gastown-crew-gus


Merge: furiosa-mk338i8l

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


Digest: mol-deacon-patrol

Cycle 299: All healthy, refinery queue 5


Property layer lookup implementation

Implement unified config lookup across all layers.

Lookup order:
1. Wisp layer (check blocked first)
2. Rig bead labels
3. Town defaults (config.json)
4. System defaults (compiled in)

Two modes:
- Override: First non-nil value wins
- Stacking: Integer values sum (wisp + bead + base)

API:
- rig.GetConfig(key) interface{}
- rig.GetIntConfig(key) int  // stacking
- rig.GetBoolConfig(key) bool
- rig.GetConfigWithSource(key) (value, source)


Merge: slit-1767075898886

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

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


Merge: slit-1767059695613

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

Stale merge requests - branches no longer exist


Session ended: gt-gastown-furiosa


mol-deacon-patrol

Mayor's daemon patrol loop.

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

## Second-Order Monitoring

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

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


mol-deacon-patrol

Mayor's daemon patrol loop.

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

## Idle Town Principle

**The Deacon should be silent/invisible when the town is healthy and idle.**

- Skip HEALTH_CHECK nudges when no active work exists
- Sleep 60+ seconds between patrol cycles (longer when idle)
- Let the feed subscription wake agents on actual events
- The daemon (10-minute heartbeat) is the safety net for dead sessions

This prevents flooding idle agents with health checks every few seconds.

## Second-Order Monitoring

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

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


mol-deacon-patrol


Detect cleanup needs

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

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

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

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

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

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

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


Check own context limit

Check own context limit.

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

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

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

This enables the Deacon to burn and respawn cleanly.


mol-refinery-patrol

Merge queue processor patrol loop.

The Refinery is the Engineer in the engine room. You process polecat branches, merging them to main one at a time with sequential rebasing.

**The Scotty Test**: Before proceeding past any failure, ask yourself: "Would Scotty walk past a warp core leak because it existed before his shift?"

## Merge Flow

The Refinery receives MERGE_READY mail from Witnesses when polecats complete work:

```
Witness                    Refinery                      Git
   │                          │                           │
   │ MERGE_READY              │                           │
   │─────────────────────────>│                           │
   │                          │                           │
   │                    (verify branch)                   │
   │                          │ fetch & rebase            │
   │                          │──────────────────────────>│
   │                          │                           │
   │                    (run tests)                       │
   │                          │                           │
   │                    (if pass)                         │
   │                          │ merge & push              │
   │                          │──────────────────────────>│
   │                          │                           │
   │ MERGED                   │                           │
   │<─────────────────────────│                           │
   │                          │                           │
```

After successful merge, Refinery sends MERGED mail back to Witness so it can
complete cleanup (nuke the polecat worktree).


Digest: mol-deacon-patrol

Patrol cycle 22: All healthy, no incidents


Phase 3: Update agent bead lookups to use correct tier

## Goal

Update all code that looks up agent beads to use the correct tier.

## Changes

### Files to update

1. internal/cmd/status.go - gt status agent display
2. internal/cmd/sling.go - agentIDToBeadID() function
3. internal/doctor/agent_beads_check.go - health checks
4. internal/patrol/deacon.go - Deacon self-lookup
5. internal/patrol/witness.go - Witness self-lookup
6. internal/session/manager.go - session BEADS_DIR

### Pattern

For town-level agents (Mayor, Deacon):
- Use town beads path
- Look up hq-mayor, hq-deacon

For rig-level agents (Witness, Refinery, Polecats):
- Use rig beads path
- Look up with rig prefix

### Key function updates

GetPrefixForRig() - keep for rig agents
New: GetTownBeadsPath() - for town agent lookups

## Testing

- gt status shows correct agent info from both tiers
- gt doctor validates agents in correct locations
- Patrol agents can find their own beads

Implemented two-level beads architecture for agent lookups: town-level agents (Mayor, Deacon) now use hq- prefix and are looked up in town beads; rig-level agents continue using rig prefix


Digest: mol-deacon-patrol

Patrol cycle 30: All healthy, no incidents


Digest: mol-deacon-patrol

Patrol 18: 3 rigs healthy


Digest: mol-deacon-patrol

Patrol 2: all agents healthy, no issues


Digest: mol-deacon-patrol

Patrol cycle 20: All healthy. Handoff threshold reached.


Digest: mol-deacon-patrol

Patrol 15: Quiet


Digest: mol-deacon-patrol

Patrol cycle 7: All healthy


Digest: mol-deacon-patrol

Patrol 11: healthy


Session ended: gt-gastown-crew-george


Digest: mol-deacon-patrol

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


Session ended: gt-gastown-crew-max


Digest: mol-deacon-patrol

Patrol 17: All clear


Session ended: gt-gastown-angharad


Digest: mol-deacon-patrol

Patrol 10: All clear


Session ended: gt-gastown-crew-gus


Session ended: gt-gastown-crew-jack


Leg prompts: Template system for polecat instructions

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

Ensure polecats know to write output to specific location.

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


Merge: rictus-mjtlq9xg

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