Required by bd cook command - each step needs a title field. Cooked 4 protos: deacon(9), witness(10), refinery(11), polecat-arm(6)
235 lines
7.1 KiB
YAML
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.
|