Files
gastown/.beads/formulas/mol-deacon-patrol.formula.yaml
Steve Yegge 80d3672999 Add title fields to patrol formulas for bd cook (gt-ingm.2)
Required by bd cook command - each step needs a title field.
Cooked 4 protos: deacon(9), witness(10), refinery(11), polecat-arm(6)
2025-12-24 13:48:19 -08:00

235 lines
7.1 KiB
YAML

# Deacon Patrol: Mayor's daemon loop
# The Deacon is the Mayor's background process that runs continuously,
# handling callbacks, monitoring rig health, and performing cleanup.
formula: mol-deacon-patrol
description: |
Mayor's daemon patrol loop.
The Deacon is the Mayor's background process that runs continuously,
handling callbacks, monitoring rig health, and performing cleanup.
Each patrol cycle runs these steps in sequence, then loops or exits.
version: 1
steps:
- id: inbox-check
title: Handle callbacks from agents
description: |
Handle callbacks from agents.
Check the Mayor's inbox for messages from:
- Witnesses reporting polecat status
- Refineries reporting merge results
- Polecats requesting help or escalation
- External triggers (webhooks, timers)
```bash
gt mail inbox
# For each message:
gt mail read <id>
# Handle based on message type
```
Callbacks may spawn new polecats, update issue state, or trigger other actions.
- id: trigger-pending-spawns
title: Nudge newly spawned polecats
needs: [inbox-check]
description: |
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.
```bash
# For each rig with polecats
for rig in gastown beads; do
gt polecats $rig
# For each working polecat, check if Claude is ready
# Use tmux capture-pane to look for "> " prompt
done
```
For each ready polecat that hasn't been triggered yet:
1. Send "Begin." to trigger UserPromptSubmit hook
2. The hook injects mail, polecat sees its assignment
3. Mark polecat as triggered in state
Use WaitForClaudeReady from tmux package (polls for "> " prompt).
Timeout: 60 seconds per polecat. If not ready, try again next cycle.
- id: health-scan
title: Check Witness and Refinery health
needs: [trigger-pending-spawns]
description: |
Check Witness and Refinery health for each rig.
**ZFC Principle**: You (Claude) make the judgment call about what is "stuck" or
"unresponsive" - there are no hardcoded thresholds in Go. Read the signals,
consider context, and decide.
For each rig, run:
```bash
gt witness status <rig>
gt refinery status <rig>
```
**Signals to assess:**
| Component | Healthy Signals | Concerning Signals |
|-----------|-----------------|-------------------|
| Witness | State: running, recent activity | State: not running, no heartbeat |
| Refinery | State: running, queue processing | Queue stuck, merge failures |
**Tracking unresponsive cycles:**
Maintain in your patrol state (persisted across cycles):
```
health_state:
<rig>:
witness:
unresponsive_cycles: 0
last_seen_healthy: <timestamp>
refinery:
unresponsive_cycles: 0
last_seen_healthy: <timestamp>
```
**Decision matrix** (you decide the thresholds based on context):
| Cycles Unresponsive | Suggested Action |
|---------------------|------------------|
| 1-2 | Note it, check again next cycle |
| 3-4 | Attempt restart: gt witness restart <rig> |
| 5+ | Escalate to Mayor with context |
**Restart commands:**
```bash
gt witness restart <rig>
gt refinery restart <rig>
```
**Escalation:**
```bash
gt mail send mayor/ -s "Health: <rig> <component> unresponsive" \
-m "Component has been unresponsive for N cycles. Restart attempts failed.
Last healthy: <timestamp>
Error signals: <details>"
```
Reset unresponsive_cycles to 0 when component responds normally.
- id: plugin-run
title: Execute registered plugins
needs: [health-scan]
description: |
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.
- id: orphan-check
title: Find abandoned work
needs: [health-scan]
description: |
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
- id: session-gc
title: Clean dead sessions
needs: [orphan-check]
description: |
Clean dead sessions.
Garbage collect terminated sessions:
- Remove stale polecat directories
- Clean up wisp session artifacts
- Prune old logs and temp files
- Archive completed molecule state
```bash
gt gc --sessions
gt gc --wisps --age=1h
```
Preserve audit trail. Only clean sessions confirmed dead.
- id: context-check
title: Check own context limit
needs: [session-gc]
description: |
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.
- id: loop-or-exit
title: Burn and respawn or loop
needs: [context-check]
description: |
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.