Digest: mol-deacon-patrol

Patrol 11: all clear


Update vision.md with proto/mol/wisp terminology

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

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

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

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


Bug: Deacon session linked to Mayor pane causes heartbeat crosstalk

**Root cause found**: The gt-deacon tmux session was linked to gt-mayor window 2 - they shared the same pane (@283). When the daemon sent heartbeats to the Deacon, they appeared as typed input in the Mayor's window, interrupting user prompts.

**Fix applied**: Killed the broken gt-deacon session. The daemon auto-recreates it with a proper independent pane.

**Prevention**: Investigate how sessions can get linked and add safeguards to session creation code.

Original symptom: Mayor receiving 'HEARTBEAT: Tip: Witnesses monitor polecats...' messages that ate user input.


Document Rig/Cook/Run lifecycle

The rig-cook-run.md spec is written. Propagate terminology to all relevant docs: - molecule-algebra.md (done) - molecular-chemistry.md (done) - vision.md (done) - propulsion-principle.md - sling-design.md - Any agent templates that reference cooking/instantiation


Polecat role references deprecated 'swarm' terminology

prompts/roles/polecat.md line 12 says:
'Part of a swarm: Other polecats may be working on related issues in parallel'

But architecture.md explicitly states:
'There are no swarm IDs - just epics with children'

The swarm concept has been replaced by streams/dependency model.
Update polecat.md to remove swarm references.


Create mol-install-go-binary molecule

Create a single-step molecule for building and installing the gt binary.

## Molecule Definition
\`\`\`markdown
## Molecule: install-go-binary
Single step to rebuild and install the gt binary after code changes.

## Step: install
Build and install the gt binary locally.

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

Verify the installed binary is updated:
\`\`\`
which gt
gt --version  # if we have version command
\`\`\`
\`\`\`

## Implementation
1. Add to builtin_molecules.go
2. Update SeedBuiltinMolecules to include it
3. Run gt molecule seed

## Acceptance
- [ ] mol-install-go-binary exists in beads
- [ ] Can be instantiated standalone
- [ ] Can be included by other molecules


Day 3.7: Witness pings Deacon for second-order monitoring

Add second-order monitoring: Witnesses monitor the Deacon.

Problem: Who monitors the Deacon? If Deacon dies, the whole town stalls.

Solution:
1. Each Witness periodically sends a ping to Deacon during patrol:
   ```bash
   gt mail send deacon/ -s "WITNESS_PING" -m "{\"rig\": \"gastown\"}"
   ```

2. Deacon patrol (inbox-check) aggregates witness pings as health signals

3. If Witness doesn't get Deacon response for 2 cycles:
   - Escalate to Mayor: "Deacon unresponsive"
   - Mayor can restart Deacon via daemon

This creates overlapping patrols:
- Deacon monitors Witnesses/Refineries
- Witnesses monitor Deacon (collectively)
- Mayor is escalation target for both

Add to mol-witness-patrol after survey-workers step.


Digest: mol-deacon-patrol

Patrol 8: All clear


Digest: mol-deacon-patrol

Patrol 19: all healthy


gt polecat add should spawn with --dangerously-skip-permissions

When adding polecats via gt polecat add, the subsequent Claude session needs --dangerously-skip-permissions flag to run autonomously.

Currently:
- gt polecat add creates worktree
- tmux new-session spawns 'claude' without flags
- Polecat gets stuck at permission prompts

Needed:
- gt polecat add (or gt sling) should spawn with --dangerously-skip-permissions
- Or store a config in polecat worktree indicating autonomous mode

This is critical for swarm automation - polecats can't self-approve permissions.


Plugin surface for daemon lifecycle hooks

Allow rigs to customize daemon behavior via hooks/plugins.

## Hook Points

- on_heartbeat: Called during daemon heartbeat cycle
- on_worker_idle: Called when worker goes idle
- on_worker_stuck: Called when stuck detection triggers
- on_lifecycle_request: Called before processing lifecycle

## Configuration

```
<rig>/config/daemon-hooks.json
{
  "on_heartbeat": "./hooks/check-ci-status.sh",
  "on_worker_idle": "./hooks/maybe-assign-work.sh",
  "idle_threshold": "5m",
  "custom_signals": [
    {"name": "ci_status", "command": "./hooks/get-ci.sh"}
  ]
}
```

## Signal Contribution

Hooks can return signals that feed into decision engine:
- SKIP_POKE: Don't poke this agent
- FORCE_POKE: Override backoff, poke now
- CUSTOM_DATA: Extra context for logging

## Use Cases

- Check CI status before interrupting test runs
- Auto-assign work when worker becomes idle
- Custom stuck detection for specific workflows


Digest: mol-deacon-patrol

Patrol 8: routine, all healthy


Test gate for verification


TODO: Check if issue is child of configured epic in Witness

witness/manager.go:688 has a TODO to filter issues by whether they're children of the configured epic. Currently this filter is skipped.


load-context

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

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


Digest: mol-deacon-patrol

Patrol cycle 12: nominal


Work on ga-xxp: Define mol-polecat-work standard molecule...

Work on ga-xxp: Define mol-polecat-work standard molecule. See bd show ga-xxp for full details.


gt sling: auto-sync beads in new polecat worktrees

## Problem
`gt sling <bead> <rig>` could fail to pin because polecat worktree branches persisted and diverged from current state.

## Root Cause
- Polecat branches (`polecat/<name>`) were reused across runs
- Stale branches could diverge from main and beads-sync
- Branch reuse logic was complex with fallback behavior

## Solution Implemented
**Fresh unique branches per polecat run**

Instead of syncing beads (Options A/B/C), we changed the branch model:

1. **Unique timestamped branches**: Each polecat run creates `polecat/<name>-<timestamp>`
2. **No branch reuse**: Each spawn starts fresh from current HEAD
3. **Simplified code**: Removed branch existence checks and reset logic
4. **Garbage collection**: Added `gt polecat gc <rig>` to clean up old branches

### Why This Works
- Polecats already use beads redirect (`../../mayor/rig/.beads`)
- Mayor's beads are authoritative - polecats read from there
- Fresh branches ensure clean starting state
- Old branches are ephemeral (never pushed to origin)

### Files Changed
- `internal/polecat/manager.go`: Add/Recreate/CleanupStaleBranches
- `internal/git/git.go`: ListBranches helper
- `internal/cmd/polecat.go`: `gt polecat gc` command

### Commands Added
```
gt polecat gc <rig>           # Clean up stale branches
gt polecat gc <rig> --dry-run # Show what would be deleted
```


Digest: mol-deacon-patrol

Patrol 6: all clear


Digest: mol-deacon-patrol

Patrol 14: nominal


Digest: mol-deacon-patrol

deacon patrol: 21 cycles complete


Digest: mol-deacon-patrol

Patrol 7: all clear


Merge: morsov-1767074381346

attached_args: Code review this merge request

branch: polecat/morsov-1767074381346
target: main
source_issue: morsov-1767074381346
rig: gastown


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

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

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

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

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

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


load-context

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

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


Digest: mol-deacon-patrol

Patrol 9: clear


Digest: mol-deacon-patrol

Patrol 2: All systems nominal


Test infrastructure: fixtures, mocks, integration tests

Test infrastructure for Gas Town.

## Test Categories

### Unit Tests
- Config parsing
- Mail operations
- Merge queue operations
- Template rendering

### Integration Tests
- Git operations (uses real git in temp dirs)
- Full workflows (spawn → work → merge)
- CLI command tests

## Fixtures

```go
package testutil

// Creates temp town with minimal structure
func NewTestTown(t *testing.T) *TestTown

// Creates temp rig with git repo
func NewTestRig(t *testing.T, town *TestTown, name string) *TestRig

// Creates test polecat
func NewTestPolecat(t *testing.T, rig *TestRig, name string) *TestPolecat
```

## Mocks

```go
// Mock tmux for session tests (avoid actual tmux)
type MockTmux struct {
    Sessions map[string][]string  // captured send-keys
}

func (m *MockTmux) NewSession(name, dir string) error
func (m *MockTmux) SendKeys(session, keys string) error
func (m *MockTmux) CapturePane(session string, lines int) (string, error)
```

## Test Helpers

```go
// Run test in temp directory
func InTempDir(t *testing.T, fn func(dir string))

// Initialize git repo for testing
func InitGitRepo(t *testing.T, dir string)

// Assert file exists with content
func AssertFileContains(t *testing.T, path, substr string)

// Assert JSON file has field
func AssertJSONField(t *testing.T, path, field string, expected any)
```

## CI Integration

- go test ./... runs all tests
- Integration tests skip with -short flag
- Coverage report generation

## Test Data

Embedded test fixtures:
```go
//go:embed testdata/*
var testdataFS embed.FS
```


Digest: mol-deacon-patrol

Patrol 20: routine, all healthy


Formula cycle detection during cooking

Detect and error on circular extends chains during bd cook. E.g., if formula A extends B extends A, cooking should fail with clear error message pointing to the cycle.


town handoff command (optional)

CLI commands for session handoff workflow (optional convenience).

## Commands

### gt handoff
Generate handoff interactively.
```
gt handoff [--send]
```
- Collects current state (status, inbox, beads)
- Prompts for additional notes
- --send: Mail to self and exit

### gt resume
Check for and display pending handoff.
```
gt resume
```
- Checks inbox for handoff message
- Displays formatted handoff if found
- Suggests next actions

## Implementation

These are convenience wrappers. The same workflow can be done manually:
```bash
# Manual handoff
town status > /tmp/handoff
town inbox >> /tmp/handoff
bd ready >> /tmp/handoff
# Edit and send
town mail send mayor/ -s "Session Handoff" -f /tmp/handoff
```

## Priority

P2 - Optional. Manual workflow works fine. Nice to have for UX.

## Notes

Part of session cycling workflow designed in [deleted:gt-u82].


Digest: mol-deacon-patrol

Patrol 4: all healthy


Test activity feed enhancement


Document the Propulsion Principle

Write canonical documentation for the Universal Gas Town Propulsion Principle.

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

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

This is foundational theory-of-operation documentation.


Switch formulas from JSON to TOML

## Problem
Formula JSON files have poor ergonomics for agents and humans:
- Multi-line strings require `\n` escaping
- Diffs are hard to read (long single-line changes)
- No comments allowed

## Solution
Switch formulas to TOML format:

```toml
formula = "mol-deacon-patrol"
version = 1
description = """
Mayor's daemon patrol loop.
Multi-line strings work naturally.
"""

[[steps]]
id = "inbox-check"
title = "Handle callbacks"
description = """
Check inbox for messages.

```bash
gt mail inbox
```
"""
```

## Tasks
- [ ] Add TOML parsing for formulas (github.com/BurntSushi/toml)
- [ ] Update formula loader to try .toml first, fall back to .json
- [ ] Add `bd formula convert <name>` to migrate JSON → TOML
- [ ] Convert existing formulas
- [ ] Update docs

## Notes
- Keep issues.jsonl as-is (machine data, append-only)
- TOML only for human-edited formula files


Digest: mol-deacon-patrol

Patrol 18: All clear


Verify deacon patrol loop executes

Verify the deacon runs its patrol formula:

1. Start deacon: `gt daemon start`
2. Watch deacon session: `tmux attach -t gt-deacon`
3. Verify it cycles through:
   - inbox-check
   - trigger-pending-spawns
   - gate-evaluation
   - health-scan
   - orphan-check
   - session-gc
   - context-check
   - loop-or-exit

The deacon should continuously patrol, not stall.

Test: Watch 2-3 patrol cycles complete.


Digest: mol-deacon-patrol

Patrol 13: All healthy


Work assignment messages should auto-close on completion

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

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

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


Check own context limit

Check own context limit.

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

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

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

This enables the Deacon to burn and respawn cleanly.


Digest: mol-deacon-patrol

Patrol 3: all quiet


Digest: mol-deacon-patrol

Patrol 13: all clear


Digest: mol-deacon-patrol

Patrol 12: all healthy


gt sling reports hook occupied but bd hook shows empty

gt sling thinks a hook is occupied when bd hook shows it empty. Example: bd hook --agent beads/crew/dave shows (empty), but gt sling bd-hobo beads/crew/dave says 'hook already occupied by bd-ul59'. They should use the same source of truth (beads database pinned field).


Per-epic config overrides: custom merge settings

Allow per-epic merge configuration overrides.

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

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

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

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


gt mail send: add stdin and file input support for complex messages

Current -m flag is fragile with multi-line messages containing backticks, quotes, or shell special chars. Add: 1) --stdin flag to read message body from stdin (heredoc-friendly), 2) -f/--file flag to read from file. This sidesteps shell quoting nightmares for handoff messages with code samples.


Adaptive backoff for daemon heartbeat

Track agent responsiveness and adjust heartbeat frequency.

## Per-Agent State

```go
type AgentBackoff struct {
    BaseInterval    time.Duration  // 60s default
    CurrentInterval time.Duration  // grows when busy
    MaxInterval     time.Duration  // 10min cap
    ConsecutiveMiss int            // pokes with no response
}
```

## Strategy Options

- **Fixed**: Always 60s (current, simple)
- **Geometric**: 60s → 90s → 135s → 202s (factor 1.5)
- **Exponential**: 60s → 120s → 240s (factor 2, aggressive)

## Recovery

When agent responds (runs a command):
- Reset ConsecutiveMiss to 0
- Return to BaseInterval immediately

## Benefits

- Reduces noise for busy agents
- Saves resources during quiet periods
- Still catches stuck agents (max interval cap)


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

## Problem

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

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

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

## Proposal

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

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

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

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

## Benefits

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

## Implementation

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

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

## Future Candidates

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


Digest: mol-deacon-patrol

Patrol complete: all healthy, cleaned 2 orphan processes + 3 stale locks


Maintenance: Regular cleanup of closed MR/gate beads

Closed merge-request and gate beads accumulate over time. Need regular cleanup.

## Cleanup Candidates
- type=merge-request with status=closed
- type=gate with status=closed
- type=molecule with status=closed (completed workflows)

## Options
1. **Deacon patrol step** - squash closed MRs periodically
2. **Refinery post-merge** - close and tombstone MR after successful merge
3. **bd doctor check** - warn when stale MR count exceeds threshold

## Consideration
Keep some audit trail? Or just tombstone after N days?


Standardize agent bead naming: prefix-rig-role-name

## The Problem

Agent bead IDs use INCONSISTENT naming patterns:

| Current (WRONG) | Should Be (prefix-rig-role-name) |
|-----------------|----------------------------------|
| gt-witness-gastown | gt-gastown-witness |
| gt-refinery-gastown | gt-gastown-refinery |
| gt-crew-gastown-max | gt-gastown-crew-max |
| gt-polecat-gastown-Toast | gt-gastown-polecat-Toast |

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

## The Convention

```
prefix-rig-role-name

Where:
- prefix: always 'gt'
- rig: rig name (absent for town-level agents)
- role: witness|refinery|crew|polecat|mayor|deacon
- name: agent name (absent for singleton roles like witness/refinery)
```

Examples:
- `gt-gastown-witness` (rig=gastown, role=witness, no name)
- `gt-gastown-crew-max` (rig=gastown, role=crew, name=max)
- `gt-beads-polecat-Toast` (rig=beads, role=polecat, name=Toast)
- `gt-mayor` (no rig, role=mayor, no name)

## Files to Update

### Agent bead ID generation
- internal/cmd/status.go (lines 316-335)
- internal/daemon/lifecycle.go (lines 546-579)
- internal/cmd/crew_add.go
- internal/polecat/manager.go
- internal/rig/manager.go
- internal/doctor/agent_beads_check.go
- internal/cmd/prime.go
- internal/session/names.go (session names vs bead IDs)

### Commands to verify
- gt status - displays agent beads
- gt doctor - creates missing agent beads
- gt rig add - creates witness/refinery beads
- gt crew add - creates crew beads
- gt sling - routes by agent ID
- gt mol status - looks up agent by ID

## Migration

1. Update all ID generation code
2. Create new agent beads with correct IDs
3. Migrate hook/role slots to new beads
4. Update any mail addresses
5. Delete old incorrectly-named beads

## Also from this session

Role bead slot cleanup was done - CreateAgentBead now calls bd slot set.
But legacy description parsing still exists in daemon/lifecycle.go.

## Reference
- ~/gt/docs/agent-as-bead.md
- Session 2025-12-29: Fixed role bead patterns (gt-crew-role etc)


Digest: mol-deacon-patrol

Patrol 15


Molecule CLI: bd molecule commands

Add molecule commands to bd:

## Commands

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

## gt spawn integration

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

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

## Output Examples

```
$ bd molecule show mol-abc

mol-abc: Engineer in a Box
Type: molecule

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

Instances: 3
```

```
$ bd molecule instances mol-abc

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


Design: Deacon exponential backoff when town is idle

## Problem

When the user goes to sleep and all workers have stopped, the Deacon continues
polling at full speed (every daemon heartbeat). This wastes resources and creates
unnecessary log noise.

## Requirements

1. **Gradual slowdown**: Deacon patrol frequency should exponentially back off
   when there's no activity in the town.

2. **Wake on activity**: Any gt or bd command (human or agent) should reset
   the backoff immediately.

3. **Daemon coordination**: Deacon needs to "program" the daemon for next
   heartbeat duration.

## Design Considerations

### Option A: Daemon tracks last-activity timestamp
- Daemon mechanically slows heartbeat based on time since last activity
- Activity signal comes from gt/bd command hooks
- Simple, no Deacon state needed
- But: Daemon is "dumb" by design - adding intelligence here feels wrong

### Option B: Deacon tracks patrol-count-since-activity
- Deacon maintains counter (via label on patrol molecule or state.json)
- Each idle patrol increments counter, calculates next sleep
- Deacon tells daemon "wake me in X minutes"
- More complex but keeps intelligence in AI agent

### Option C: Discovery-based (preferred if feasible)
- Can we detect "no activity" without explicit counters?
- Ideas:
  - Compare current time to last git commit across all clones?
  - Check tmux activity timestamps?
  - Look at beads modification times?
- Pros: No state to manage
- Cons: May be expensive to compute, could miss activity

### Wake mechanism
- Hook on gt commands (gt.hooks.pre-command or similar)
- Hook on bd commands (bd.hooks.pre-command)
- Both should signal daemon (SIGUSR2?) or write to activity file
- Daemon reads activity file on each heartbeat, resets backoff if fresh

### Backoff schedule (strawman)
| Idle patrols | Next heartbeat |
|--------------|----------------|
| 0-5          | 5 min (default) |
| 6-10         | 10 min |
| 11-20        | 30 min |
| 21+          | 60 min (max) |

## Open Questions

1. Should tmux activity count as "activity"? (cursor movement, typing)
2. How does Deacon communicate next-heartbeat to daemon?
3. Where to store backoff state? (daemon state.json? deacon state.json? label?)
4. Should wake be instant (SIGUSR) or next-heartbeat?

## Implementation Sketch

Activity file written by hooks at ~/gt/daemon/activity.json:
- last_command: timestamp
- command: the gt/bd command that ran
- actor: who ran it


Show actor on pinned/status change events

Pinned events currently show no actor:

  [10:22:25] → gt-95j13 → pinned · Add tmux hotkey...

Should show who pinned it:

  [10:22:25] → gt-95j13 → pinned · Add tmux hotkey... @gastown/crew/joe

Part of epic gt-u7dxq


Remove keepalive file infrastructure

The keepalive mechanism (~/gt/daemon/activity.json) is deprecated in favor of feed-based wake.

TODO:
1. Remove keepalive.TouchTownActivity() calls from gt commands
2. Remove keepalive.ReadTownActivity() from daemon
3. Delete internal/keepalive package
4. Update daemon to use longer base heartbeat (recovery-focused)

Whitelist consideration: Some read-only commands might need to wake (TBD - none identified yet)


Digest: mol-deacon-patrol

Patrol 13: All healthy


Documentation humility pass

Update documentation with humble framing.

## Changes Needed

### README.md

- Add "Experimental" or "Alpha" badge
- Clarify current state vs future vision
- List what works vs what is planned
- Include honest limitations section

### Architecture docs

- Mark autonomous features as "planned"
- Distinguish implemented from designed
- Add "Current Status" sections

### CLAUDE.md files

- Remove claims about autonomous operation (where not true)
- Add "v0.1" qualifiers to features
- Be honest about manual steps required

## Tone

NOT: "Gas Town automatically manages agent lifecycles"
YES: "Gas Town provides infrastructure for agent lifecycle management.
     v0.1 requires some manual coordination."

NOT: "The Refinery processes merges autonomously"
YES: "The Refinery is designed for autonomous merge processing.
     v0.1 demonstrates the pattern with manual triggering."

## Files to Review

- README.md
- docs/architecture.md
- docs/quickstart.md (if exists)
- CLAUDE.md files for all roles


Pinned Beads: Ongoing concerns and anchors

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


Persist patrol state

Update handoff bead with new states.

Persist state to the witness handoff bead:
- Updated worker statuses from all arms
- Current nudge counts per worker
- Nudge timestamps
- Actions taken this cycle
- Pending items for next cycle

```bash
bd update <handoff-bead-id> --description="<serialized state>"
```

This state survives wisp burns and session cycles.


Build context self-check into patrol molecules

Patrol molecules (witness-patrol, refinery-patrol, deacon-patrol) should have
explicit context self-check as part of each cycle:

After each patrol cycle step:
- Self-assess context pressure
- If high: complete current step, then initiate handoff
- If OK: continue to next cycle

This could be:
1. Explicit step in molecule: 'context-check'
2. Guidance in step instructions: 'Before continuing, assess context...'
3. Protocol baked into the patrol loop description

The key is making it unavoidable - not optional guidance that gets ignored.


gt-gastown-polecat-dementus

gt-gastown-polecat-dementus

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


Witness: aggregate completion and notify Mayor

## Problem
No signal when swarm completes. Mayor must manually check.

## ZFC-Compliant Solution
Add step to `mol-witness-patrol.formula.toml`:

```toml
[[step]]
id = "check-swarm-completion"
title = "Report swarm completion to Mayor"
description = """
1. Check state.json for active swarm tracking:
   - swarm_id, start_time, assigned_beads[], completed_beads[]
2. For each completed merge (from cleanup step):
   - Add bead to completed_beads
3. If all assigned_beads are in completed_beads:
   - Calculate duration
   - Send summary to Mayor:
     gt mail send mayor/ -s "SWARM_COMPLETE: <swarm_id>" -m "All N polecats merged in Xm"
   - Clear swarm tracking from state.json
"""
depends_on = ["cleanup-merged-polecats"]
```

## Swarm Initialization
When Mayor slings a batch, they should:
```bash
# Mayor creates swarm tracking
gt mail send <rig>/witness -s "SWARM_START" -m '{"swarm_id": "batch-123", "beads": ["bd-a", "bd-b", "bd-c"]}'
```

Witness parses SWARM_START and initializes tracking in state.json.

## Why This Works
- Mayor initiates batch with SWARM_START mail
- Witness tracks progress in its own state
- Witness signals completion via mail
- All coordination is agent-to-agent via mail

## Files
- formulas/mol-witness-patrol.formula.toml


Integration test: simple file creation

Test task for polecat integration test (gt-7psb8).

Simple task: Create a file ~/gt/gastown/test-polecat-integration.txt with content 'Polecat integration test successful'.

This is an intentionally trivial task to verify the polecat → witness → refinery → merged flow works end-to-end.


Direct test

Testing direct bd create


Digest: mol-deacon-patrol

Patrol 9: All green


Refinery background daemon mode

The refinery 'gt refinery start' command only works in foreground mode (--foreground). Need to implement background daemon mode for production use.

Options:
1. Use a separate tmux session for the refinery
2. Implement proper daemonization
3. Use Claude Code session for the refinery agent

For MVP, option 1 (tmux session) is probably simplest.


gt context --usage: estimate remaining context via tmux line count

## Summary

Implement `gt context --usage` to estimate remaining context capacity.

## Approach

Use tmux scrollback buffer line count as proxy for token usage:
- `tmux capture-pane -p | wc -l` gives current line count
- Heuristics (tunable per model):
  - Opus 4.5: ~1800 lines = warning threshold
  - ~2000+ lines = critical, need handoff

## Output Format
```
Context Usage: 1642 lines (~82%)
Status: WARNING - consider handoff soon
```

## Notes
- Heuristics are model-specific, need config
- Line count is proxy, not exact token count
- Good enough for autonomous patrol decisions


Digest: mol-deacon-patrol

Cycle 7: quiet


Digest: mol-deacon-patrol

Patrol 2: all healthy, no events


Zombie session detection for refinery/witness

A Claude session can crash or hang while still appearing as 'running' in tmux. The current IsRunning checks only verify tmux session exists, not that Claude is actually processing.

## Problem
- Refinery session might be hung (Claude crashed, infinite loop, etc.)
- tmux HasSession returns true, so auto-start doesn't trigger
- MRs pile up with no processing

## Solution Options
1. **Activity-based detection**: Check last output timestamp in tmux
2. **Heartbeat file**: Agents write timestamp to file, check staleness
3. **Prompt detection**: Look for Claude '>' prompt indicating idle/stuck

## Related
- gt-bjft (spawn auto-start) assumes IsRunning means healthy
- Witness patrol assumes running refinery is processing


Layered context onboarding pattern

Pattern from handoff discussion:

## Pattern: Layered Context Onboarding

Town CLAUDE.md (user/org) -> Rig CLAUDE.md (project) -> Role priming

## Ultra-compressed HOP for workers (no reveal)

- Permanent record: All work tracked. Outcomes matter.
- Quality gates: Molecule steps exist for a reason.
- Attribution: Completions build your track record.
- Handoff clean: Leave state any worker can continue.

## Recommendation

Create Town @AGENTS.md for shared worker context that all workers see.
This provides common behavioral guidance without revealing full HOP context.


CloudRunOutpost: Basic implementation

## Overview

Basic CloudRunOutpost implementation. Persistent connections and cost tracking are separate tasks.

## Implementation

```go
type CloudRunOutpost struct {
    name        string
    project     string
    region      string
    service     string
    maxWorkers  int
    client      *WorkClient
    workers     map[string]*CloudRunWorker
    mu          sync.RWMutex
}

func NewCloudRunOutpost(cfg OutpostConfig) (*CloudRunOutpost, error) {
    serviceURL := fmt.Sprintf(
        "https://%s-%s.a.run.app",
        cfg.Service, cfg.Region,
    )
    return &CloudRunOutpost{
        name:       cfg.Name,
        project:    cfg.Project,
        region:     cfg.Region,
        service:    cfg.Service,
        maxWorkers: cfg.MaxWorkers,
        client:     NewWorkClient(serviceURL),
        workers:    make(map[string]*CloudRunWorker),
    }, nil
}
```

## Spawn

```go
func (o *CloudRunOutpost) Spawn(issue string, cfg WorkerConfig) (Worker, error) {
    req := WorkRequest{
        IssueID: issue,
        Rig:     RigConfig{URL: cfg.RigURL, Branch: cfg.GitBranch},
        Beads:   BeadsConfig{URL: cfg.BeadsURL, Branch: "beads-sync"},
        Branch:  "polecat/" + issue,
    }

    events, err := o.client.DispatchWork(context.Background(), req)
    if err != nil {
        return nil, err
    }

    worker := &CloudRunWorker{
        id:      uuid.New().String(),
        outpost: o.name,
        issue:   issue,
        events:  events,
        status:  WorkerStatusWorking,
    }

    o.mu.Lock()
    o.workers[worker.id] = worker
    o.mu.Unlock()

    go worker.monitor()
    return worker, nil
}
```

## CloudRunWorker

```go
type CloudRunWorker struct {
    id      string
    outpost string
    issue   string
    status  WorkerStatus
    events  <-chan WorkEvent
    logs    []string
}

func (w *CloudRunWorker) Attach() error {
    return errors.New("Cloud Run workers do not support attach")
}

func (w *CloudRunWorker) Logs() (io.Reader, error) {
    return strings.NewReader(strings.Join(w.logs, "\n")), nil
}
```

## Files

- `internal/outpost/cloudrun.go`

## Dependencies

Depends on: gt-9a2.1 (interfaces), gt-9a2.12 (HTTP client)
Blocks: gt-9a2.13 (persistent connections), gt-9a2.14 (cost tracking)


Digest: mol-deacon-patrol

Patrol 10: All healthy, 1 in_progress issue (max working)


Dynamic arm bonding in witness patrol

Witness survey step uses bd mol bond to attach mol-polecat-arm for each active worker. Variables: worker name, rig. Arms become children of the patrol molecule. Requires on_complete/for_each runtime support.


Digest: mol-deacon-patrol

Patrol 12: All healthy


gt: Write town-level activity signal in PersistentPreRun

Extend gt's existing keepalive infrastructure to also write a town-level activity file.

Currently gt writes to <workspace>/.runtime/keepalive.json (per-rig).
Add a second write to ~/gt/daemon/activity.json (town-level).

The keepalive package already has TouchInWorkspace(). Add a new function
TouchTownActivity() that writes to the town daemon directory.

Call both in root.go's PersistentPreRun.


gt-polecat-gastown-test-cat

gt-polecat-gastown-test-cat

role_type: polecat
rig: gastown
agent_state: spawning
hook_bead: null
role_bead: null


Merge: gt-72so

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


gt-polecat-gastown-testcat

gt-polecat-gastown-testcat

role_type: polecat
rig: gastown
agent_state: running
hook_bead: null
role_bead: null


Digest: mol-deacon-patrol

Patrol 14: All healthy, 2 crew active


Digest: mol-deacon-patrol

Patrol 1: cleaned 3 stale locks, 3 orphan processes, doctor passed


Deacon patrol banners: visual feedback on atom transitions

Print large ASCII banners when transitioning between patrol atoms.

## Problem

When the deacon progresses through patrol atoms (steps), there is no visual feedback.
The operator cannot easily see what the deacon is doing without reading the full output.

## Desired Behavior

Print banners on step start and completion:

  INBOX-CHECK - Checking for lifecycle requests, escalations, timers
  INBOX-CHECK COMPLETE - Processed 3 messages, 0 lifecycle requests

## Benefits

1. Scanability: Operator can glance at tmux and see what is happening
2. Progress tracking: Easy to see where in the patrol loop we are
3. Debugging: Clear demarcation between steps for troubleshooting

## Implementation Options

1. In deacon CLAUDE.md: Instruct agent to print banners
2. gt patrol step start/end: Commands that print banners
3. bd mol step hooks: Automatically on step transitions

## Related

- gt-id36: Deacon Kernel
- gt-rana: Patrol System


MessagingConfig: Add Type field for schema consistency

MessagingConfig lacks a Type field that other config types have (TownConfig has 'town', RigConfig has 'rig', etc). Add Type: 'messaging' for consistency.


Beads epic templates for batch work patterns

Optional: Define templates for common batch work patterns.

## Concept

A template encodes a workflow pattern that can be instantiated as beads:

```yaml
# templates/batch-basic.yaml
name: basic-batch
description: Simple batch work pattern
phases:
  - name: startup
    issues:
      - title: "Verify workers ready"
  - name: working
    # Actual work issues added separately
  - name: cleanup
    issues:
      - title: "Merge all branches"
      - title: "Clean up workers"
      - title: "Report to Mayor"
```

## Usage

```bash
gt spawn --template basic-batch --epic gt-u1j --workers 3
```

Creates beads epic with template phases + actual work from gt-u1j children.

## Decision Point

Templates are OPTIONAL. The core design (beads as state, multi-wave orchestration) works without templates. Templates are sugar for common patterns.

Consider deferring to P3 or dropping entirely if beads epics with dependencies suffice.

## Note

No "swarm IDs" involved - templates just pre-populate epic/issue structure.


Harness Design & Documentation

The harness (Gas Town installation directory) needs design cleanup, documentation, and tooling.

## Current Problems

1. **Shared harness confusion**: ~/ai is shared by PGT and GGT with overlapping structures
   - PGT uses ~/ai/mayor/ as town-level Mayor home
   - GGT Mayor works in ~/ai/mayor/rigs/gastown/
   - ~/ai/gastown/ has both .gastown/ (PGT) and mayor/ (git clone)

2. **Beads redirect**: ~/ai/.beads/redirect → mayor/rigs/gastown/.beads
   - This is specific to GGT's decentralized structure
   - Should be documented as an example

3. **architecture.md**: Verify rig-level mayor/rig/ is shown correctly

4. **No harness creation tooling**: Users must manually set up

## Proposed Work

- Document what a harness IS (installation directory)
- Create harness creation command or template repo
- Update architecture.md if needed
- Create example harness configuration for docs
- Resolve PGT/GGT sharing issue


Digest: mol-deacon-patrol

Patrol 18: Nominal


Digest: mol-deacon-patrol

Patrol 20: All healthy, 2 crew active


Merge: gt-5af.2

branch: polecat/Doof
target: main
source_issue: gt-5af.2
rig: gastown


Witness updates: Remove issue filing proxy

Update Witness prompting to remove issue filing proxy, since polecats now have direct beads access.

## Remove from Witness Prompting

The following is NO LONGER Witness responsibility:
- Processing polecat 'file issue' mail requests
- Creating issues on behalf of polecats
- Forwarding issue creation requests

## Add: Legacy Request Handling

If Witness receives an old-style 'please file issue' request:

1. Respond with update:
   town inject <polecat> "UPDATE: You have direct beads access now. Use bd create to file issues yourself."

2. Do not file the issue - let the polecat learn the new workflow.

## Keep in Witness Prompting

- Monitoring polecat progress
- Nudge protocol
- Pre-kill verification
- Session lifecycle management


Digest: mol-deacon-patrol

Patrol 14: all quiet


Digest: mol-deacon-patrol

Patrol 8: quiet


ZFC #10: Polecat reports cleanup status instead of Go checking git

**ZFC Violation:** internal/polecat/manager.go:192-215

Go examines git state to decide if polecat removal is safe:
```go
status, err := polecatGit.CheckUncommittedWork()
if status.StashCount > 0 || status.UnpushedCommits > 0 {
    return &UncommittedWorkError{...}
}
```

**ZFC-compliant solution:**
Polecat reports its own cleanup status via agent bead:
- state: stopped
- cleanup_status: clean | has_uncommitted | has_stash | has_unpushed
- Witness reads cleanup_status before calling nuke
- Polecat is authority on whether it's safe to remove

**Relates to:** Day 4.1/4.2 (recycle/nuke commands)

Reference: ~/gt/docs/zfc-violations-audit.md #10


Digest: mol-deacon-patrol

Patrol 4: All clear


Polecat Arm (arm-toast)

Single polecat inspection and action cycle.

This molecule is bonded dynamically by mol-witness-patrol's survey-workers step.
Each polecat being monitored gets one arm that runs in parallel with other arms.

## Variables

| Variable | Required | Description |
|----------|----------|-------------|
| polecat_name | Yes | Name of the polecat to inspect |
| rig | Yes | Rig containing the polecat |

## Step: capture
Capture recent tmux output for toast.

```bash
tmux capture-pane -t gt-gastown-toast -p | tail -50
```

Record:
- Last activity timestamp (when was last tool call?)
- Visible errors or stack traces
- Completion indicators ("Done", "Finished", etc.)

## Step: assess
Categorize polecat state based on captured output.

States:
- **working**: Recent tool calls, active processing
- **idle**: At prompt, no recent activity
- **error**: Showing errors or stack traces
- **requesting_shutdown**: Sent LIFECYCLE/Shutdown mail
- **done**: Showing completion indicators

Calculate: minutes since last activity.
Needs: capture

## Step: load-history
Read nudge history for toast from patrol state.

```
nudge_count = state.nudges[toast].count
last_nudge_time = state.nudges[toast].timestamp
```

This data was loaded by the parent patrol's load-state step and passed
to the arm via the bonding context.
Needs: assess

## Step: decide
Apply the nudge matrix to determine action for toast.

| State | Idle Time | Nudge Count | Action |
|-------|-----------|-------------|--------|
| working | any | any | none |
| idle | <10min | any | none |
| idle | 10-15min | 0 | nudge-1 (gentle) |
| idle | 15-20min | 1 | nudge-2 (direct) |
| idle | 20+min | 2 | nudge-3 (final) |
| idle | any | 3 | escalate |
| error | any | any | assess-severity |
| requesting_shutdown | any | any | pre-kill-verify |
| done | any | any | pre-kill-verify |

Nudge text:
1. "How's progress? Need any help?"
2. "Please wrap up soon. What's blocking you?"
3. "Final check. Will escalate in 5 min if no response."

Record decision and rationale.
Needs: load-history

## Step: execute
Take the decided action for toast.

**nudge-N**:
```bash
tmux send-keys -t gt-gastown-toast "{{nudge_text}}" Enter
```

**pre-kill-verify**:
```bash
cd polecats/toast
git status                    # Must be clean
git log origin/main..HEAD     # Check for unpushed
bd show <assigned-issue>      # Verify closed/deferred
```
If clean: kill session, remove worktree, delete branch
If dirty: record failure, retry next cycle

**escalate**:
```bash
gt mail send mayor/ -s "Escalation: toast stuck" -m "..."
```

**none**: No action needed.

Record: action taken, result, updated nudge count.
Needs: decide

## Output

The arm completes with:
- action_taken: none | nudge-1 | nudge-2 | nudge-3 | killed | escalated
- result: success | failed | pending
- updated_state: New nudge count and timestamp for toast

This data feeds back to the parent patrol's aggregate step.
---
bonded_from: mol-polecat-arm
bonded_to: gt-ie33
bonded_ref: arm-toast
bonded_at: 2025-12-23T10:00:00Z


Digest: mol-deacon-patrol @ 2025-12-24 19:44

Patrol 6: All healthy


E2E Test: Add a comment to internal/util/process.go


Execute registered plugins

Execute registered plugins.

Scan ~/gt/plugins/ for plugin directories. Each plugin has a plugin.md with
YAML frontmatter defining its gate (when to run) and instructions (what to do).

See docs/deacon-plugins.md for full documentation.

Gate types:
- cooldown: Time since last run (e.g., 24h)
- cron: Schedule-based (e.g., "0 9 * * *")
- condition: Metric threshold (e.g., wisp count > 50)
- event: Trigger-based (e.g., startup, heartbeat)

For each plugin:
1. Read plugin.md frontmatter to check gate
2. Compare against state.json (last run, etc.)
3. If gate is open, execute the plugin

Plugins marked parallel: true can run concurrently using Task tool subagents.
Sequential plugins run one at a time in directory order.

Skip this step if ~/gt/plugins/ does not exist or is empty.


Digest: mol-deacon-patrol

Patrol 15: All clear


Crew workers getting wrong CLAUDE.md (shows Refinery)

## Problem

Crew workers (emma, dave) have CLAUDE.md that says they're the Refinery.

## Evidence

```
$ head -5 /Users/stevey/gt/beads/crew/emma/CLAUDE.md
# Claude: Beads Refinery
You are the **Refinery** for the **beads** rig...
```

## Expected

Should use crew.md.tmpl which correctly says:
```
# Crew Worker Context
You are a **crew worker** - the overseer's (human's) personal workspace...
```

## Impact

- Crew workers have wrong identity in static context
- `gt prime` correctly outputs crew context, but CLAUDE.md conflicts
- Confusing role information

## Fix

Check `gt crew create` or whatever populates CLAUDE.md for crew workers.


Day 2.4: Add timeout fallback for dead agents

For truly dead agents that can't report their own death:
- Daemon tracks last_seen timestamp from heartbeats
- If no heartbeat for N minutes AND state=running, mark state=dead
- This is fallback only, not primary state source

Parent: gt-d0jqp


Refinery startup: bond mol-refinery-patrol on start

Wire up Refinery to automatically bond its patrol molecule on startup.

## Current state
- mol-refinery-patrol exists in builtin_molecules.go
- prompts/roles/refinery.md describes the protocol
- Refinery doesn't auto-bond on startup

## Desired behavior
On Refinery session start:
1. gt prime detects RoleRefinery
2. Check for existing in-progress patrol: bd list --status=in_progress --assignee=refinery
3. If found: resume from current step
4. If not found: bd mol bond mol-refinery-patrol --wisp
5. Output patrol context to agent

## Implementation options
A) Add to gt prime (outputRefineryPatrolContext)
B) Add startup hook in refinery CLAUDE.md
C) Both (prime detects, template reinforces)

## Testing
- Start refinery session
- Verify patrol bonds automatically
- Kill mid-patrol, restart, verify resumes

## Depends on
- gt-3x0z.10 (existing issue for Refinery patrol)


Digest: mol-deacon-patrol

Patrol 6: OK


Remove keepalive/heartbeat staleness thresholds

**Files:**
- keepalive/keepalive.go (lines 94-111)
- deacon/heartbeat.go (lines 88-108)

**Current behavior:**
- Hardcoded thresholds: 2min=fresh, 5min=stale, 15min=very stale
- Used by daemon to decide whether to poke agents
- Go classifies agent responsiveness

**Fix:**
- Remove all hardcoded time.Duration thresholds
- Keepalive becomes pure timestamp storage
- Deacon molecule step does health assessment:
  "Check if Witness responded in last N minutes" (N from mol config)

**Lines to remove:**
- keepalive.go:96-110 (classification logic)
- heartbeat.go:91-107 (age classification)


Bug: bd update --parent doesn't persist

The --parent flag on bd update reports success but doesn't persist the parent field.

**Confirmed behavior:**
```bash
bd update gt-9kp3t --parent=gt-d0jqp --no-daemon -v
# Output: ✓ Updated issue: gt-9kp3t

bd show gt-9kp3t --json | jq '.[0].parent'
# Returns: null

grep gt-9kp3t .beads/issues.jsonl | head -1 | jq '.parent'
# Returns: null (field not present)
```

**Root cause hypothesis:**
The update handler processes the --parent flag but either:
1. Doesn't write it to the Issue struct
2. Doesn't include it in the JSONL export
3. The field name doesn't match what export expects

**Files to check:**
- cmd/bd/show.go:638-640 (flag handling)
- cmd/bd/show.go:810-814 (reparenting logic)
- internal/rpc/server_issues_epics.go (RPC handler)
- internal/rpc/protocol.go (UpdateArgs.Parent field)

Related: bd-cj2e (the original feature implementation)


Digest: mol-deacon-patrol

Patrol cycle 10: All healthy


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.


Human and mail gates

Implement human:<prompt> gate (requires explicit approval command). Implement mail:<pattern> gate (waits for mail matching pattern). These enable async human-in-loop workflows.


Digest: mol-deacon-patrol

Patrol #11: Stable


Witness session cycling and handoff

Add session cycling and handoff protocol to Witness CLAUDE.md template.

## Session Cycling Protocol

```markdown
## Session Cycling

Your context will fill over long swarms. Proactively cycle when:
- Running for many hours
- Losing track of which workers you've checked
- Responses getting slower
- About to start complex operation

### Handoff Protocol

1. **Capture current state**:
```bash
town list .           # Worker states
town all beads        # Pending verifications
town inbox            # Unprocessed messages
```

2. **Compose handoff note**:
```
[HANDOFF_TYPE]: witness_cycle
[TIMESTAMP]: <now>
[RIG]: <rig>

## Active Workers
<list workers and status>

## Pending Verifications
<workers signaled done but not verified>

## Recent Actions
<last 3-5 actions>

## Warnings/Notes
<anything next session should know>

## Next Steps
<what should happen next>
```

3. **Send handoff**:
```bash
town mail send <rig>/witness -s "Session Handoff" -m "<note>"
```

4. **Exit cleanly**: End session, daemon spawns fresh one.

### On Fresh Session Start

1. Check for handoff: `town inbox | grep "Session Handoff"`
2. If found, read it and resume from handoff state
3. If not found, do full status check
```

## Implementation

Add to WITNESS_CLAUDE.md template.


Digest: mol-deacon-patrol

Patrol 15: Routine


Digest: mol-deacon-patrol @ 2025-12-24 19:28

Patrol 16


Patrol Exponential Backoff System

Cost-saving await-signal model for patrol agents.

## The Problem

Patrol agents (witness, refinery) poll continuously even when idle, burning API credits.

## The Solution: Await-Signal

Agents don't poll constantly. They **wait for a signal**, then **discover reality**.

```
PATROL LOOP:
    await-signal (just wake me)
         ↓ awake
    check-reality (mail, beads, hook, git)
         ↓
    work found? → DO WORK → loop
         ↓ no
    increase backoff → loop
```

**Key principle (ZFC-aligned):** Signal carries no semantic meaning - just 'wake up'.
Agent discovers what to do by examining reality.

## Two-Level Wake

| Level | State | Command |
|-------|-------|---------|
| 1 | Running (backoff) | `gt nudge` clears backoff |
| 2 | Asleep | `gt rig boot` starts session |

**Boot+nudge pattern:**
```bash
gt rig boot <rig>              # Wake if asleep (idempotent)
gt nudge <rig>/witness 'wake'  # Clear backoff if running
```

## Backoff Curve

Base: 30s → 60s → 120s → 240s → ... → 10min cap

Resets on: nudge received, work found, deacon ping.

## Reference

See ~/gt/docs/patrol-system-design.md (Await-Signal Model section)


Digest: mol-deacon-patrol

Patrol 13: all clear


Digest: mol-deacon-patrol

Patrol 11: All agents healthy, cleaned 18 stale wisps


Merge: gt-j6s8

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


Digest: mol-deacon-patrol

Patrol 10: quiet


Digest: mol-deacon-patrol

Patrol 7: Quick cycle, all quiet


Digest: mol-deacon-patrol

Patrol 9: All healthy


Digest: mol-deacon-patrol

Patrol 4: quiet, health OK


Digest: mol-deacon-patrol

Patrol 13: Routine


gt sling command implementation

Implement the gt sling command for handoff with attached work.

## Command Signature

```bash
gt sling <bead-id> [-s subject] [-m message]
```

## Behavior

1. Validate the bead exists: bd show <bead-id>
2. Create wisp on agents hook:
   - File: .beads-wisp/hook-<agent>.wisp.yaml
   - Content: bead_id, slung_at, optional context
3. Optionally send handoff mail with subject/message
4. Exit cleanly (or signal restart)

## Wisp Format

```yaml
type: slung-work
bead_id: gt-xxx
slung_at: 2025-12-24T...
slung_by: crew/joe
context: "Optional message"
```

## Files to Create/Modify

- cmd/gt/sling.go (new)
- internal/wisp/wisp.go (may need creation)
- internal/wisp/hook.go (hook reading)

## Notes

- Keep it simple - this is connector code
- The wisp is burned after pickup
- Sling should work for any role (crew, polecat, deacon)


Design GGT prompt architecture

Audit PGT prompts and design canonical prompt system for GGT. Create docs/prompts.md with inventory, gap analysis, and Witness prompt design.


Digest: mol-deacon-patrol

Patrol 3: All healthy, routine


Machine registry: Store and manage machine configs

Registry for managing remote machines in federation.

## Data Model

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

## Interface

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

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

## Storage

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

## Connection Factory

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


Burn and respawn or loop

Burn and let daemon respawn, or exit if context high.

Decision point at end of patrol cycle:

If context is LOW:
- Sleep briefly (avoid tight loop)
- Return to inbox-check step

If context is HIGH:
- Write state to persistent storage
- Exit cleanly
- Let the daemon orchestrator respawn a fresh Deacon

The daemon ensures Deacon is always running:
```bash
# Daemon respawns on exit
gt daemon status
```

This enables infinite patrol duration via context-aware respawning.


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

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

## Root Cause

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

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

## Fix Options

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

## Also Affected

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

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


Molecule Workflow Engine: Composable Crystallized Workflows

# Epic: Molecule Workflow Engine

**Vision**: Molecules are crystallized, composable, nondeterministic-idempotent workflow templates. Any worker can pick up where any other worker was interrupted and continue along the molecule.

**Christmas Target**: Full molecule-based workflow engine operational by Dec 25, 2025.

## The Core Concepts

1. **Molecule**: Read-only workflow template (beads issue with type=molecule)
2. **Atom/Step**: Individual work unit with prose instructions
3. **Bond**: Dependency between steps
4. **Polymer/Derived**: Molecule composed from other molecules
5. **Instance**: Concrete beads created when molecule is attached to work

## Key Features Needed

### 1. Molecule Composition (Includes Directive)
Molecules can include other molecules:
\`\`\`markdown
## Molecule: gastown-polecat
Includes: mol-engineer-in-box

## Step: install-binary
Build and install the local gt binary.
Needs: submit
\`\`\`

### 2. Standard Molecules
- mol-install-go-binary: Single step to build/install gt
- mol-gastown-polecat: engineer-in-box + install-binary

### 3. Spawn Integration
\`gt spawn --issue <id> --molecule <mol-id>\` creates molecule instance then starts polecat on first ready step.

### 4. Nondeterministic Idempotence
- Steps are atomic (pending → in_progress → completed)
- Any worker can pick up any ready step
- Step timeout/recovery for stuck workers

## Success Criteria
- [ ] Polecats can be spawned with mol-gastown-polecat
- [ ] Derived molecules work end-to-end
- [ ] 10+ polecat swarm completes molecule workflows
- [ ] install-go-binary step runs after successful merges


Phase 4: Polish and observability

Production readiness.

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

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

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

Depends: Phase 3


E2E Test: Verify mol-polecat-work spawn

Simple test task for Phase 5 E2E validation


CLI: witness commands (start, stop, status)

Witness daemon CLI commands.

## Commands

### gt witness start
Start the witness daemon for a rig.
```
gt witness start <rig> [--interval <seconds>]
```
- Starts background daemon process
- Default heartbeat interval: 30s
- Logs to <rig>/witness/witness.log

### gt witness stop
Stop the witness daemon.
```
gt witness stop <rig>
```
- Graceful shutdown
- Completes current heartbeat cycle

### gt witness status
Show witness status.
```
gt witness status <rig> [--json]
```
Output:
- Running/stopped
- Last heartbeat time
- Polecats being monitored
- Recent nudges/escalations

### gt witness logs
View witness logs.
```
gt witness logs <rig> [--follow] [--lines <n>]
```
- --follow: Continuous output (like tail -f)
- --lines: Number of lines (default 50)

## Daemon Management

Witness runs as background process:
- PID stored in <rig>/witness/witness.pid
- Uses nohup for persistence
- Logs structured JSON events

## Implementation

```go
var witnessCmd = &cobra.Command{Use: "witness"}
witnessCmd.AddCommand(startCmd, stopCmd, statusCmd, logsCmd)
```

Start launches daemon via exec, status reads PID file and pings process.


Mol Mall: Formula marketplace and aspect composition

## Summary

A marketplace for sharing molecule formulas using GitHub as backend, plus a new
**aspect composition** capability for cooking that allows formulas to be applied
as cross-cutting transformations to existing mols.

## Key Insight

Formulas aren't just standalone workflows—they're **transformations** you can
apply to other mols. This enables patterns like:
- "Apply Rule of Five code review to any coding task"
- "Wrap any epic with security review steps"
- "Add logging/metrics to any patrol molecule"

## Architecture

```
Remote Formulas (GitHub)
        ↓ install
Local Formulas (~/.gt/formulas/ or .beads/formulas/)
        ↓ cook --with
Mols (beads, git-tracked)
        ↓ pour/sling
Execution (ephemeral or tracked)
```

## Formula Types

1. **Standalone** - Complete workflows (mol-polecat-work, mol-deacon-patrol)
2. **Aspects** - Cross-cutting concerns woven into other mols (rule-of-five)
3. **Adapters** - Wrappers that make things runnable

## Aspect Composition (New Cooking Capability)

```bash
bd cook my-epic --with rule-of-five,security-review
```

Aspects declare injection points:

```yaml
# rule-of-five.formula.yaml
kind: aspect
name: rule-of-five
version: 1.2.0

inject_after:
  tag: generates-code  # matches any step with this tag

steps:
  - id: review-correctness
    title: "Review: Correctness"
  - id: review-edge-cases
    title: "Review: Edge Cases"
  - id: review-performance
    title: "Review: Performance"
  - id: review-security
    title: "Review: Security"
  - id: review-maintainability
    title: "Review: Maintainability"
```

Injection modes:
- `inject_after: <step-id or tag>` - insert steps after
- `inject_before: <step-id or tag>` - insert steps before
- `wrap: <step-id or tag>` - insert before AND after
- `replace: <step-id or tag>` - substitute entirely

## The Mall Index

A well-known repo maps short names to full references:

```yaml
# steveyegge/gt-mall/index.yaml
formulas:
  rule-of-five:
    repo: steveyegge/gt-formulas
    path: aspects/rule-of-five.formula.yaml
    version: v1.2.0
    description: "Five-lens code review"
    tags: [review, quality]
```

Commands:
- `gt mol search review` - search the index
- `gt mol install rule-of-five` - fetch from GitHub
- `gt mol install github.com/acme/formulas/custom@v1` - direct URL

## Implementation Phases

### Phase 1: Aspect Composition (Cooking)
- Extend `bd cook` to accept `--with aspect1,aspect2`
- Aspect application during mol generation
- Local aspects in `.beads/formulas/`

### Phase 2: Mall Index
- Create gt-mall repo with index.yaml
- `gt mol search` queries the index
- `gt mol install` fetches and caches formulas

### Phase 3: Publishing
- `gt mol publish` pushes to your formula repo
- PR workflow to add to mall index

### Phase 4: Refinements
- Aspect ordering and conflict detection
- Aspect dependencies (`requires: [other-aspect]`)
- Private formula repos (enterprise)
- Local overrides/forks

## Open Questions

1. **Ordering conflicts**: Two aspects inject after same step—which goes first?
2. **Aspect dependencies**: Aspect A requires aspect B?
3. **Compile-time vs runtime**: Aspects are compile-time (baked into mol).
   Wisps remain runtime (slung dynamically).

## Deferred Until

Gas Town patrols operational. Prerequisites:
- [ ] Patrol system working (Deacon, Witness, Refinery)
- [ ] Basic spawn/work/merge cycle
- [ ] Activity feed showing work progress

Then the mall becomes valuable for sharing patrol customizations.

## Supersedes

This replaces gt-uzf2l which had the original marketplace concept but lacked
the aspect composition insight.

## Related

- Cooking system (bd cook)
- Formula definitions (.formula.yaml)
- Wisp architecture (runtime molecules)

See docs/formula_evolution.md for expanded thinking on: formula resolution hierarchy, combinators (>>, |, wrap, inject, extends), algebraic properties, higher abstractions, Mol Mall registry predictions, distribution scenarios, and open questions. The aspect composition in this design corresponds to the Wrapping (AOP) and Injection combinators in that doc.


MQ events in gt feed

Wire MQ lifecycle events to gt feed display.

After 'Refinery emits activity events' is done:
- gt feed should show merge_started, merged, merge_failed events
- Format: timestamp, event type, MR details
- Color coding: green for merged, red for failed

Example output:
  19:45 MERGED gt-abc123 (polecat/nux → main)
  19:42 MERGE_STARTED gt-abc123
  19:40 MERGE_FAILED gt-xyz789 (conflict)


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

Workspace-level doctor checks.

## Checks

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

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

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

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

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

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

## Implementation

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


Polecat identity confusion leads to direct main commits

When polecats spawn without proper identity (due to gt-si6am and gt-y41ep):
1. They think they're mayor
2. They may commit from wrong directory (mayor/rig instead of polecats/<name>)
3. Work goes directly to main, bypassing merge queue

Observed: slit pushed 'gt polecat recycle' directly to main instead of its branch

Root cause:
- gt-si6am: Wrong CLAUDE.md template (says 'Mayor Rig Context')
- gt-y41ep: Env vars not exported (falls back to mayor detection)

Impact: CRITICAL - merge queue entirely bypassed, no refinery review


Meta-formulas: formula generators

Support meta-formulas that generate multiple formulas from a template. Example: multi-lang-shiny that generates shiny-go, shiny-python, shiny-typescript from a for-each loop over languages. Enables DRY formula libraries.


Execute registered plugins

Execute registered plugins.

Scan ~/gt/plugins/ for plugin directories. Each plugin has a plugin.md with YAML frontmatter defining its gate (when to run) and instructions (what to do).

See docs/deacon-plugins.md for full documentation.

Gate types:
- cooldown: Time since last run (e.g., 24h)
- cron: Schedule-based (e.g., "0 9 * * *")
- condition: Metric threshold (e.g., wisp count > 50)
- event: Trigger-based (e.g., startup, heartbeat)

For each plugin:
1. Read plugin.md frontmatter to check gate
2. Compare against state.json (last run, etc.)
3. If gate is open, execute the plugin

Plugins marked parallel: true can run concurrently using Task tool subagents. Sequential plugins run one at a time in directory order.

Skip this step if ~/gt/plugins/ does not exist or is empty.


Digest: mol-deacon-patrol

Patrol 12: Nominal


Extended addressing: Parse [machine:]rig/polecat

Extended addressing for cross-machine operations.

## Address Format

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

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

## Parser

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

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

## Validation

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

## Usage

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

## Backward Compatibility

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


Digest: mol-deacon-patrol

Patrol 2: Quiet cycle, all agents healthy


Digest: mol-deacon-patrol

Patrol 8: healthy


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


Find abandoned work

Find abandoned work.

Scan for orphaned state:
- Issues marked in_progress with no active polecat
- Polecats that stopped responding mid-work
- Merge queue entries with no polecat owner
- Wisp sessions that outlived their spawner

```bash
bd list --status=in_progress
gt polecats --all --orphan
```

For each orphan:
- Check if polecat session still exists
- If not, mark issue for reassignment or retry
- File incident beads if data loss occurred


Ephemeral rig support for ad-hoc batch work

Support for temporary worker rigs for ad-hoc batch work.

## Use Case

Ad-hoc batch work where you want temporary workers:
- gt spawn --epic <epic-id> --ephemeral --workers 3
- Creates temp rig with 3 clones
- Workers process epic children
- Work merges through queue
- Clones cleaned up when done

## Interface

```go
type EphemeralRig struct {
    ID               string
    GitURL           string
    BaseCommit       string
    Workers          []string
    IntegrationBranch string
    EpicID           string  // associated epic (for cleanup trigger)
    CreatedAt        time.Time
}

func InitEphemeralRig(gitURL string, numWorkers int) (*EphemeralRig, error)
func (r *EphemeralRig) AddWorker(name string) error
func (r *EphemeralRig) Destroy(force bool) error
```

## Directory Structure

```
<rig>/ephemeral/
└── rig-a3f7/
    ├── config.json
    ├── Alice/     # clone
    ├── Bob/       # clone
    └── Carol/     # clone
```

## Cleanup Trigger

When all issues in the associated epic are closed AND merged, the ephemeral rig can be destroyed.

## Note

No swarm ID needed - the epic ID provides the grouping. Ephemeral rig is just a convenience for temporary clones.

## Reference

PGT: ephemeral.py (for structure, ignore swarm ID coupling)


Digest: mol-deacon-patrol

Patrol cycle 18: routine


Digest: mol-deacon-patrol

Patrol 10: healthy


Timed Beads: Scheduled recurring work

## Summary

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

## Schema Extension

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

## Daemon Integration

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

## Use Cases

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

## Interaction with Pinned Beads

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


Digest: mol-deacon-patrol

Patrol cycle 17: routine


P0: Multiple agents can claim same worker identity

Multiple Claude Code sessions running simultaneously all think they are 'dave' on beads/crew/dave. No detection or prevention of identity collision. This breaks the single-agent-per-worker assumption.

## Fix Implemented

1. **Lock Package** (`internal/lock/lock.go`):
   - PID-based lockfile at `<worker>/.gastown/agent.lock`
   - Contains PID, timestamp, session ID, hostname
   - Stale lock detection (checks if owning PID is dead)

2. **Prevention in gt prime**:
   - Workers (crew/polecat) acquire identity lock before loading context
   - If another live process holds the lock, prime fails with clear error
   - Shows lock holder details and resolution steps

3. **Detection with gt agents**:
   - `gt agents check` - scans for collisions and stale locks
   - `gt agents fix` - cleans stale locks
   - JSON output available for patrol tooling

4. **Correction in gt doctor**:
   - New `identity-collision` check
   - `gt doctor --fix` cleans stale locks


Merge: gt-rixa

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


Daemon patrol should clean stale locks

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

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

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


Review: Beads Universal Data Plane documentation

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

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

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


Outpost integration tests: Mock and E2E testing

## Overview

Integration tests for the outpost system.

## Mock Outpost

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

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

## Unit Tests

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

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

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

## Integration Tests (with mocked Cloud Run)

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

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

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

## E2E Tests (requires real Cloud Run)

```go
// +build e2e

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

## Files

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

## Dependencies

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


gt crew at/attach: Start session in crew workspace

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


Digest: mol-deacon-patrol

Patrol 14: Mayor OK, 11 sessions


Digest: mol-deacon-patrol

P8: stable


Daemon: verify requesting_cycle before kill

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

Requires state.json in agent workspace with requesting_cycle field.


Guardrail: Verify commits exist before closing polecat issues

## Updated Approach (ZFC)

The original proposal was for mechanical guardrails in bd close. This contradicts the ZFC principle: all decisions go to models, not code.

## Correct Solution

The verification should happen in **mol-polecat-arm** execute step, not bd close:

In the pre-kill-verify action:
```bash
# Current steps
git status                    # Must be clean
git log origin/main..HEAD     # Check for unpushed
bd show <assigned-issue>      # Verify closed/deferred

# ADD: Verify productive work
git log --oneline --grep='<issue-id>' | head -1
# If no commits AND issue being closed as 'done' → flag for review
```

The agent (Witness) makes the decision. The mol gives it the verification step.

## Why Not Code Guardrails

Polecats have legitimate reasons to close issues without commits:
- Already done (someone else fixed it)
- Deferred (out of scope)
- Escalated (needs human decision)
- Duplicate (merged with another issue)

A code guardrail would block all these. The mol step lets the agent verify AND make the judgment call.

## Implementation

Update mol-polecat-arm execute step to include commit verification for 'done' closures.


test pin fix


bd close hooks: context check and notifications

Add hook system to bd close for context checking and notifications.

## Concept

Register hooks that run on every bd close:

# .beads/config.yaml
hooks:
  on_close:
    - command: 'gt context-check'
    - notify: 'Next step ready'

## Use cases

### Context check on close
After closing a step, check if context is getting full.
If >80%, output warning suggesting session cycling.

### Next step notification
Automatically show next ready step (complements --continue).

### Custom actions
User-defined scripts for workflow automation.

## Hook types

- command: Run shell command with env vars (BEAD_ID, BEAD_TITLE, etc)
- notify: Send message to agent
- webhook: POST to URL (future)

## Integration with context detection

The context-check hook could:
1. Capture tmux pane (if in tmux session)
2. Estimate context usage (turn count, output length)
3. If high, output: 'Context at ~85%. Consider cycling.'

## Priority
P2 - nice to have, not blocking launch.

## Related
- gt-qswb (bd mol current)
- gt-fly0 (bd close --continue)


Digest: mol-deacon-patrol

Patrol 10: Midpoint, all healthy


Digest: mol-deacon-patrol

Cycle 6: quiet


Inline step expansion (Step.Expand field)

Step.Expand and Step.ExpandVars fields exist in schema but are not implemented.

## Current State

Steps can declare inline expansion:

    steps:
      - id: design
        expand: rule-of-five
        expand_vars:
          iterations: 3

This is more convenient than compose.expand for single-step expansions.

## Difference from compose.expand

- compose.expand: Centralized, applies after all steps parsed
- Step.Expand: Inline, step declares its own expansion

## Implementation

1. During cooking, detect steps with Expand field
2. Load referenced expansion formula
3. Replace step with expanded template (like compose.expand)
4. Pass ExpandVars as variable overrides
5. Handle recursion depth (reuse DefaultMaxExpansionDepth)

## Files

- internal/formula/types.go: Step.Expand, Step.ExpandVars (already defined)
- internal/formula/expand.go: Add inline expansion handling
- internal/formula/expand_test.go: Add test cases

## Depends On

- gt-8tmz.34 (var overrides) should be done first


Define meta-formula YAML schema

Define the YAML schema for meta-formulas that generate multiple formulas.

## Proposed Schema

    type: meta-formula
    name: multi-lang-shiny
    generates:
      for-each:
        var: lang
        values: [go, python, typescript]
      formula:
        name: "shiny-{lang}"
        extends: base-shiny
        vars:
          language: "{lang}"

## Key Decisions

- Use "generates" key to distinguish from regular formulas
- for-each at generation level (not runtime)
- Output is N formula files or in-memory formulas

## Deliverables

1. Add MetaFormula type to types.go
2. Add ForEachGenerate struct
3. Document schema in comments


Digest: mol-deacon-patrol


Digest: mol-deacon-patrol

Patrol cycle 4: routine, healthy


Digest: mol-deacon-patrol

Patrol 9


Digest: mol-deacon-patrol

Patrol 10: clear


Digest: mol-deacon-patrol

Patrol cycle 2: all healthy, no action required


Refinery gates: test/lint/build before merge

Before merging polecat work to main, run configurable quality gates.

**From VC**: internal/gates/ - parallel execution with timeout, any failure = overall failure.

**Gas Town implementation**: Refinery config with gate commands:
```yaml
gates:
  test:
    cmd: go test ./...
    timeout: 5m
  lint:
    cmd: golangci-lint run
    timeout: 2m
  build:
    cmd: go build ./...
    timeout: 3m
parallel: true
```

If gates fail, don't merge. Polecat can iterate and retry.

**Value**: Prevents broken code from reaching main. VC had 90.9% gate pass rate.

**VC complexity**: ~200 lines Go
**Gas Town complexity**: ~10 lines YAML


Digest: mol-deacon-patrol

Patrol 17: All healthy


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

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

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

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

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


Mail should use wisps for ephemeral orchestration messages

## Problem

Town HQ beads are polluted with lifecycle orchestration messages:
- POLECAT_STARTED notifications
- Work assignments to polecats
- "Start work" nudges
- Test messages

These should never have been persistent beads. They accumulate forever.

## Analysis

Mail serves two purposes:
1. **Durable messages** - Handoffs, escalations, human-agent comms (need persistence)
2. **Ephemeral signals** - Lifecycle pings, work assignments, nudges (should be wisps)

## Options

### Option A: Dual-inbox architecture
- `gt mail inbox` checks both `.beads/` and `.beads-wisp/`
- Sender specifies `--ephemeral` flag for transient messages
- Ephemeral messages auto-expire or squash to digests

### Option B: All mail becomes wisps
- Default to wisp storage for all mail
- Important messages explicitly promoted to persistent
- Simpler model but loses audit trail for some messages

### Option C: Message type determines storage
- `message` type beads go to wisps by default
- `handoff` type stays persistent
- Automatic routing based on content

## Recommendation

Option A (dual-inbox) seems cleanest:
- Explicit control via `--ephemeral`
- Backwards compatible
- Clear mental model

## Acceptance Criteria

- Lifecycle pings (POLECAT_STARTED, etc.) go to wisps
- Work assignments go to wisps
- Handoffs stay persistent
- `gt mail inbox` shows both
- Wisps auto-cleanup on patrol squash

## Blocks

This blocks reliable swarm operations - every spawn pollutes HQ indefinitely.


Mayor startup protocol prompting

Add startup protocol to Mayor CLAUDE.md template.

## On Session Start

1. Check for handoff:
   town inbox | grep "Session Handoff"

2. If handoff found:
   - Read it: town read <msg-id>
   - Process pending escalations (highest priority)
   - Check status of noted swarms
   - Verify rig health matches notes
   - Continue with documented next steps

3. If no handoff:
   town status     # Overall health
   town rigs       # Each rig
   bd ready        # Work items
   town inbox      # Any messages
   Build your own picture of current state.

4. After processing handoff:
   - Archive or delete the handoff message
   - You now own the current state

## Handoff Best Practices

- Be specific: 'Toast has merge conflict in auth/middleware.go' not 'Toast is stuck'
- Include context: Why decisions are pending, what you were thinking
- Prioritize next steps: What is most urgent
- Note time-sensitive items: Anything that might have changed since handoff


Digest: mol-deacon-patrol

Patrol 8: all clear, handing off for fresh context


Digest: mol-deacon-patrol

Patrol 18: all healthy


Digest: mol-deacon-patrol

Patrol 7: All healthy.


Add tests for gt crew restart --all

Add integration tests for the crew restart flow:

- Test getAgentSessions() correctly identifies crew sessions
- Test --rig filtering works correctly
- Test --dry-run outputs correct targets
- Test session kill/recreate cycle (without Claude)

Consider extracting a SessionStarter interface to mock the claude-launching bit.

Related: gt crew restart --all was just implemented.


Molecule-based Witness Patrol

Wire up mol-witness-patrol as a tracking ledger for the Go-based witness.

## Key Insight

The molecule is NOT an executor - it's a **tracking structure**. The existing
Go code in internal/witness/manager.go continues to run the patrol loop.
The molecule instance provides visibility, audit trail, and polecat tracking.

## Current State

- Witness runs as Go code with direct patrol logic
- mol-witness-patrol and mol-polecat-arm exist as molecule definitions
- gt mol bond creates dynamic children
- No molecule tracking of actual witness operations

## Target State

1. When witness starts → instantiate mol-witness-patrol on its hook
2. When Go code discovers polecat → bond mol-polecat-arm child
3. When polecat completes/dies → close that arm issue
4. gt mol progress shows current patrol state
5. Molecule survives wisp burns via handoff bead

## Benefits

- Visibility: gt mol status gastown/witness shows active arms
- Audit: Each polecat inspection is a trackable issue
- Consistency: Same tracking model as polecats with molecules
- Progress: gt mol progress works for witness too

## Non-Goals

- NOT replacing Go code with Claude session
- NOT running molecule steps as prompts
- NOT requiring Claude for witness operation


Outpost/Worker interfaces: Core abstractions for remote compute

## Overview

Define the core interfaces that all outpost types implement.

## Interfaces

```go
type OutpostType string
const (
    OutpostLocal    OutpostType = "local"
    OutpostSSH      OutpostType = "ssh"
    OutpostCloudRun OutpostType = "cloudrun"
)

type Outpost interface {
    Name() string
    Type() OutpostType
    MaxWorkers() int
    ActiveWorkers() int
    Spawn(issue string, config WorkerConfig) (Worker, error)
    Workers() []Worker
    Ping() error
    SendMail(worker string, msg Message) error  // optional
}

type Worker interface {
    ID() string
    Outpost() string
    Status() WorkerStatus  // idle, working, done, failed
    Issue() string
    Attach() error         // for interactive outposts
    Logs() (io.Reader, error)
    Stop() error
}

type WorkerConfig struct {
    RigPath    string
    BeadsDir   string
    GitBranch  string
    Context    map[string]string  // hints for worker
}
```

## Files

- `internal/outpost/outpost.go` - Outpost interface
- `internal/outpost/worker.go` - Worker interface
- `internal/outpost/config.go` - WorkerConfig, OutpostType

## Notes

This is the foundation for all federation work. Keep interfaces minimal - we can extend later.


Deacon initialization with patrol molecule

Ensure Deacon always has mol-deacon-patrol on hook.

## The Problem

When Deacon starts fresh, it needs:
1. mol-deacon-patrol bonded
2. Molecule pinned to hook
3. Ready to execute

## Solution: Bootstrap in gt prime

When gt prime runs for role=deacon:

1. Check if mol-deacon-patrol exists on hook
2. If not: bond it and pin it
3. Then show execution prompt

```go
// In prime.go for deacon role
if role == "deacon" && !hasPatrolOnHook() {
    // Bond the patrol formula
    bondDeaconPatrol()
    // Pin to hook
    pinToHook("deacon", patrolMolID)
}
```

## Alternative: Deacon Startup Hook

Or handle in Deacon SessionStart hook:
- Check hook
- If empty: gt mol bond mol-deacon-patrol && gt mol pin

## Files

- cmd/gt/prime.go (if handling in prime)
- deacon/hooks/session-start.sh (if handling in hook)


Digest: mol-deacon-patrol

Cycle 15: quiet, handoff for fresh context


Digest: mol-deacon-patrol

Patrol 7: all healthy


gt done: Notify Witness with exit outcome

When polecat runs gt done, it should send mail to Witness:

```
gt mail send <rig>/witness -s 'POLECAT_DONE <name>' -m 'Exit: COMPLETED
Issue: <issue-id>
MR: <mr-id>
Branch: <branch>'
```

Exit types:
- COMPLETED: Work done, MR submitted
- ESCALATED: Hit blocker, needs human
- DEFERRED: Work paused, issue still open

This enables Witness patrol to:
1. See completion in inbox-check step
2. Verify git state is clean
3. Kill session and prune worktree
4. Close the polecat lease in its patrol wisp

Paired with gt-r6td (spawn notification) - together they bracket polecat lifecycle.


Design: Plugin architecture (agents-as-plugins)

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


Refactor: Eliminate state.json, use beads assignee field for polecat state

## Problem

state.json in each polecat worktree duplicates data that should be in beads:
- `issue` field duplicates assignment (should be issue.assignee)
- `state` field duplicates status (derivable from issue.status)
- `name/rig/branch/clone_path` are all derivable from filesystem/git

This violates 'Beads as the data plane' (HOP Decision 001).

## Current State

```json
{
  "name": "Angharad",        // Derivable: basename(cwd)
  "rig": "gastown",          // Derivable: parent dir name
  "state": "working",        // Should be: issue.status
  "clone_path": "/path",     // Derivable: cwd
  "branch": "polecat/X",     // Derivable: git branch
  "issue": "gt-xyz",         // Should be: issue.assignee
  "timestamps": "..."        // Should be in beads history
}
```

## Target State

**Polecat identity**: Derived from worktree path
**Polecat assignment**: `issue.assignee = 'gastown/Angharad'`
**Polecat state**: Derived from issue.status (in_progress = working, closed = done)
**Polecat existence**: Worktree directory exists

## Benefits

1. Single source of truth (no sync issues)
2. Queryable: `bd list --assignee=gastown/Angharad`
3. History tracking via beads
4. Cross-agent coordination via beads sync
5. Simpler code (no state file management)

## Implementation

1. Update `gt spawn` to set issue.assignee instead of writing state.json
2. Update polecat list/status commands to query beads
3. Update Witness to query beads for polecat state
4. Remove state.json read/write code
5. Keep state.json as optional bootstrap cache (perf optimization only)


Use Beads issue status as spawn source of truth

Currently witness tracks spawned issues in .gastown/witness.json to prevent re-spawning:

{"spawned_issues": ["gt-abc1", "gt-def2", ...]}

This is redundant with Beads issue status (in_progress). The witness could query:
  bd list --status=in_progress

The local list serves as a performance cache to avoid querying beads every cycle. Consider:
1. Use Beads as source of truth
2. Keep local cache with TTL
3. Sync cache on witness start

This simplifies state management and ensures consistency.


Nudge newly spawned polecats

Nudge newly spawned polecats that are ready for input.

When polecats are spawned, their Claude session takes 10-20 seconds to initialize. The spawn command returns immediately without waiting. This step finds spawned polecats that are now ready and sends them a trigger to start working.

**ZFC-Compliant Observation** (AI observes AI):

```bash
# View pending spawns with captured terminal output
gt deacon pending
```

For each pending session, analyze the captured output:
- Look for Claude's prompt indicator "> " at the start of a line
- If prompt is visible, Claude is ready for input
- Make the judgment call yourself - you're the AI observer

For each ready polecat:
```bash
# 1. Trigger the polecat
gt nudge <session> "Begin."

# 2. Clear from pending list
gt deacon pending <session>
```

This triggers the UserPromptSubmit hook, which injects mail so the polecat sees its assignment.

**Bootstrap mode** (daemon-only, no AI available):
The daemon uses `gt deacon trigger-pending` with regex detection. This ZFC violation is acceptable during cold startup when no AI agent is running yet.


Digest: mol-deacon-patrol

Patrol 13: all healthy


bd mol install: Install formula from GitHub

Fetch and install a formula from a GitHub repository.

Usage: bd mol install github.com/org/repo[@version]

Should:
- Clone/fetch the formula.json from the repo
- Validate the formula structure
- Store in local catalog
- Track installation in .beads/installed.json


Digest: mol-deacon-patrol

Patrol cycle 14: routine


Update harness beads redirect for GGT

Change ~/ai/.beads/redirect from mayor/rigs/gastown/.beads to gastown/mayor/.beads for the GGT directory structure


Formula parser and YAML schema

Implement formula parsing from YAML:
- Define YAML schema for .formula.yaml files
- Parse steps, compose rules, vars
- Support extends for formula inheritance
- Validate formula structure

Schema should support:
- formula: name
- description: text
- version: number
- type: workflow|expansion|aspect
- vars: variable definitions
- steps: step definitions
- compose: composition rules

**Beads issue: bd-weu8**


Digest: mol-deacon-patrol

Patrol 9: healthy


Workspace cleanup: preflight and postflight

Workspace preflight and postflight commands for clean state management.

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

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

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

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

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

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

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

## Note

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

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


Digest: mol-deacon-patrol

Patrol 14


Digest: mol-deacon-patrol

Patrol #8


mol-polecat-work

Full polecat lifecycle from assignment to decommission.

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

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


Digest: mol-deacon-patrol

Patrol 3: All nominal


Work on ga-ct2: Add MR workflow to polecat completion. Wh...

Work on ga-ct2: Add MR workflow to polecat completion. When polecat completes work, auto-create MR to integration branch. When done, submit MR (not PR) to integration branch for Refinery.


Formula error handling and recovery

Specify how cooking errors are reported and recovered:
- What happens when extends chain is broken (missing formula)?
- What happens when aspect pointcut matches nothing?
- What happens when expansion produces invalid structure?
- Graceful degradation vs fail-fast semantics
- Error metadata in cooked protos

Deferred: post-launch polish


Deacon patrol wisps should use town beads, not gastown rig beads

## Problem

The deacon is a town-level role but stores patrol wisps in gastown/mayor/rig/.beads/ (see internal/cmd/prime.go:691). This fails for users who don't have the gastown rig installed.

## Correct Architecture

Deacon -> ~/gt/.beads-wisp/ (town-level, no rig dependency)

Town-level ephemeral work (deacon patrols) should use town wisp storage, just like rig-level ephemeral work (witness/refinery patrols) uses rig wisp storage.

## Implementation Tasks

### 1. Town Setup (gt install / gt town init)

- Create ~/gt/.beads-wisp/ directory during town initialization
- Initialize as git repo (for local commits, not synced)
- Add config.yaml with wisp: true
- Ensure .beads-wisp/ is gitignored at town level

Files:
- internal/cmd/install.go (or equivalent town init)
- internal/town/manager.go (if exists)

### 2. Rig Setup (gt rig init)

Already creates <rig>/.beads-wisp/ - verify this is working:
- internal/rig/manager.go:375 (beadsDir := filepath.Join(rigPath, ".beads-wisp"))
- internal/rig/manager.go:394 (ensureGitignoreEntry)

### 3. Deacon Code Fixes

Update deacon to use town wisp storage:
- internal/cmd/prime.go:691 - change rigBeadsDir to townWispDir
- internal/cmd/sling.go - route deacon wisps to town level
- internal/daemon/daemon.go - any deacon-specific wisp handling

### 4. gt doctor Checks

Add/update doctor checks for wisp directories:
- internal/doctor/wisp_check.go - already checks rig wisps
- Add TownWispCheck to verify ~/gt/.beads-wisp/ exists
- Add TownWispGitCheck to verify it is a valid git repo
- Update existing checks to handle both town and rig levels

Files:
- internal/doctor/wisp_check.go (extend for town level)
- internal/doctor/checks.go (register new checks)

### 5. Documentation

- docs/beads-data-plane.md - already updated with three-tier architecture
- Verify wisp-architecture.md reflects town-level wisps

## Testing

1. Fresh gt install should create ~/gt/.beads-wisp/
2. gt doctor should pass with no rig installed
3. Deacon patrol should work without gastown rig
4. gt sling patrol deacon/ --wisp should route to town wisps


verify-tests

Run existing tests. Add new tests for new functionality.
Ensure adequate coverage.

go test ./...

Fix any test failures before proceeding.

Depends: implement


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.

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


Digest: mol-deacon-patrol

Patrol 6: All clear