Add patrol hygiene: inbox cleanup for Witness/Refinery/Deacon (gt-h5e0)

Each patrol formula now includes:
- Explicit archive instructions after processing messages
- New patrol-cleanup step for end-of-cycle inbox hygiene
- Deacon: log rotation and state.json pruning

Changes:
- mol-witness-patrol v2: POLECAT_STARTED archive, patrol-cleanup step
- mol-refinery-patrol v3: MERGE_READY archive after merge, patrol-cleanup step
- mol-deacon-patrol v3: log-maintenance step, patrol-cleanup step

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 09:57:37 -08:00
parent a7831ba11d
commit 08d966a601
3 changed files with 197 additions and 365 deletions

View File

@@ -12,7 +12,7 @@ Witnesses detect it and escalate to the Mayor.
The Deacon's agent bead last_activity timestamp is updated during each patrol
cycle. Witnesses check this timestamp to verify health."""
formula = "mol-deacon-patrol"
version = 2
version = 3
[[steps]]
id = "inbox-check"
@@ -35,16 +35,30 @@ gt mail read <id>
**WITNESS_PING**:
Witnesses periodically ping to verify Deacon is alive. Simply acknowledge
and mark as read - the fact that you're processing mail proves you're running.
and archive - the fact that you're processing mail proves you're running.
Your agent bead last_activity is updated automatically during patrol.
```bash
gt mail archive <message-id>
```
**HELP / Escalation**:
Assess and handle or forward to Mayor.
Archive after handling:
```bash
gt mail archive <message-id>
```
**LIFECYCLE messages**:
Polecats reporting completion, refineries reporting merge results.
Archive after processing:
```bash
gt mail archive <message-id>
```
Callbacks may spawn new polecats, update issue state, or trigger other actions."""
Callbacks may spawn new polecats, update issue state, or trigger other actions.
**Hygiene principle**: Archive messages after they're fully processed.
Keep inbox near-empty - only unprocessed items should remain."""
[[steps]]
id = "trigger-pending-spawns"
@@ -350,10 +364,86 @@ This handles:
All cleanup is handled by doctor checks - no need to run separate commands."""
[[steps]]
id = "log-maintenance"
title = "Rotate logs and prune state"
needs = ["session-gc"]
description = """
Maintain daemon logs and state files.
**Step 1: Check daemon.log size**
```bash
# Get log file size
ls -la ~/.beads/daemon*.log 2>/dev/null || ls -la ~/gt/.beads/daemon*.log 2>/dev/null
```
If daemon.log exceeds 10MB:
```bash
# Rotate with date suffix and gzip
LOGFILE="$HOME/gt/.beads/daemon.log"
if [ -f "$LOGFILE" ] && [ $(stat -f%z "$LOGFILE" 2>/dev/null || stat -c%s "$LOGFILE") -gt 10485760 ]; then
DATE=$(date +%Y-%m-%dT%H-%M-%S)
mv "$LOGFILE" "${LOGFILE%.log}-${DATE}.log"
gzip "${LOGFILE%.log}-${DATE}.log"
fi
```
**Step 2: Archive old daemon logs**
Clean up daemon logs older than 7 days:
```bash
find ~/gt/.beads/ -name "daemon-*.log.gz" -mtime +7 -delete
```
**Step 3: Prune state.json of dead sessions**
The state.json tracks active sessions. Prune entries for sessions that no longer exist:
```bash
# Check for stale session entries
gt daemon status --json 2>/dev/null
```
If state.json references sessions not in tmux:
- Remove the stale entries
- The daemon's internal cleanup should handle this, but verify
**Note**: Log rotation prevents disk bloat from long-running daemons.
State pruning keeps runtime state accurate."""
[[steps]]
id = "patrol-cleanup"
title = "End-of-cycle inbox hygiene"
needs = ["log-maintenance"]
description = """
Verify inbox hygiene before ending patrol cycle.
**Step 1: Check inbox state**
```bash
gt mail inbox
```
Inbox should be EMPTY or contain only just-arrived unprocessed messages.
**Step 2: Archive any remaining processed messages**
All message types should have been archived during inbox-check processing:
- WITNESS_PING → archived after acknowledging
- HELP/Escalation → archived after handling
- LIFECYCLE → archived after processing
If any were missed:
```bash
# For each stale message found:
gt mail archive <message-id>
```
**Goal**: Inbox should have ≤2 active messages at end of cycle.
Deacon mail should flow through quickly - no accumulation."""
[[steps]]
id = "context-check"
title = "Check own context limit"
needs = ["session-gc"]
needs = ["patrol-cleanup"]
description = """
Check own context limit.

View File

@@ -33,7 +33,7 @@ Witness Refinery Git
After successful merge, Refinery sends MERGED mail back to Witness so it can
complete cleanup (nuke the polecat worktree)."""
formula = "mol-refinery-patrol"
version = 2
version = 3
[[steps]]
id = "inbox-check"
@@ -65,19 +65,34 @@ A polecat's work is ready for merge. Extract details and track for processing.
# - MR bead ID (REQUIRED for closing after merge)
```
**IMPORTANT**: You MUST track the polecat name and MR bead ID - you will need them
in merge-push step to send MERGED notification and close the MR bead.
**IMPORTANT**: You MUST track the polecat name, MR bead ID, AND message ID - you will need them
in merge-push step to send MERGED notification, close the MR bead, and archive the mail.
Mark as read. The work will be processed in queue-scan/process-branch.
**Do NOT archive yet** - archive after merge/reject decision in merge-push step.
**PATROL: Wake up**:
Witness detected MRs waiting but refinery idle. Acknowledge and proceed.
Witness detected MRs waiting but refinery idle. Acknowledge and archive:
```bash
gt mail archive <message-id>
```
**HELP / Blocked**:
Assess and respond. If you can't help, escalate to Mayor.
Archive after handling:
```bash
gt mail archive <message-id>
```
**HANDOFF**:
Read predecessor context. Check for in-flight merges."""
Read predecessor context. Check for in-flight merges.
Archive after absorbing context:
```bash
gt mail archive <message-id>
```
**Hygiene principle**: Archive messages after they're fully processed.
Keep only: pending MRs in queue. Inbox should be near-empty."""
[[steps]]
id = "queue-scan"
@@ -196,7 +211,13 @@ The MR bead ID was in the MERGE_READY message or find via:
bd list --type=merge-request --status=open | grep <polecat-name>
```
**Step 4: Cleanup (only after Steps 2-3 confirmed)**
**Step 4: Archive the MERGE_READY mail (REQUIRED)**
```bash
gt mail archive <merge-ready-message-id>
```
The message ID was tracked when you processed inbox-check.
**Step 5: Cleanup (only after Steps 2-4 confirmed)**
```bash
git branch -d temp
git push origin --delete <polecat-branch>
@@ -205,8 +226,9 @@ git push origin --delete <polecat-branch>
**VERIFICATION GATE**: You CANNOT proceed to loop-check without:
- [x] MERGED mail sent to witness
- [x] MR bead closed
- [x] MERGE_READY mail archived
If you skipped notifications, GO BACK AND SEND THEM NOW.
If you skipped notifications or archiving, GO BACK AND DO THEM NOW.
Main has moved. Any remaining branches need rebasing on new baseline."""
@@ -232,13 +254,15 @@ Summarize this patrol cycle.
**VERIFICATION**: Before generating summary, confirm for each merged branch:
- [ ] MERGED mail was sent to witness
- [ ] MR bead was closed
- [ ] MERGE_READY mail archived
If any MERGED notifications were missed, send them now!
If any notifications or archiving were missed, do them now!
Include in summary:
- Branches processed (count, names)
- MERGED mails sent (count - should match branches processed)
- MR beads closed (count - should match branches processed)
- MERGE_READY mails archived (count - should match branches processed)
- Test results (pass/fail)
- Issues filed (if any)
- Branches skipped (with reasons)
@@ -260,10 +284,41 @@ If context is HIGH (>80%):
If context is LOW:
- Can continue processing"""
[[steps]]
id = "patrol-cleanup"
title = "End-of-cycle inbox hygiene"
needs = ["context-check"]
description = """
Verify inbox hygiene before ending patrol cycle.
**Step 1: Check inbox state**
```bash
gt mail inbox
```
Inbox should contain ONLY:
- Unprocessed MERGE_READY messages (will process next cycle)
- Active work items
**Step 2: Archive any stale messages**
Look for messages that were processed but not archived:
- PATROL: Wake up that was acknowledged → archive
- HELP/Blocked that was handled → archive
- MERGE_READY where merge completed but archive was missed → archive
```bash
# For each stale message found:
gt mail archive <message-id>
```
**Goal**: Inbox should have ≤3 active messages at end of cycle.
Keep only: pending MRs in queue."""
[[steps]]
id = "burn-or-loop"
title = "Burn and respawn or loop"
needs = ["context-check"]
needs = ["patrol-cleanup"]
description = """
End of patrol cycle decision.

View File

@@ -1,369 +1,56 @@
description = """
Per-rig worker monitor patrol loop.
The Witness is the Pit Boss for your rig. You watch polecats, nudge them toward
completion, verify clean git state before kills, and escalate stuck workers.
**You do NOT do implementation work.** Your job is oversight, not coding.
## Design Philosophy
This patrol follows Gas Town principles:
- **Discovery over tracking**: Observe reality each cycle, don't maintain state
- **Events over state**: POLECAT_DONE mail triggers cleanup wisps
- **Cleanup wisps as finalizers**: Pending cleanups are wisps, not queue entries
- **Task tool for parallelism**: Subagents inspect polecats, not molecule arms
## Patrol Shape (Linear, Deacon-style)
```
inbox-check ─► process-cleanups ─► check-refinery ─► survey-workers
┌──────────────────────────────────────────────────┘
check-swarm ─► ping-deacon ─► context-check ─► loop-or-exit
```
No dynamic arms. No fanout gates. No persistent nudge counters.
State is discovered each cycle from reality (tmux, beads, mail)."""
formula = "mol-witness-patrol"
version = 1
description = "Per-rig worker monitor patrol loop.\n\nThe Witness is the Pit Boss for your rig. You watch polecats, nudge them toward\ncompletion, verify clean git state before kills, and escalate stuck workers.\n\n**You do NOT do implementation work.** Your job is oversight, not coding.\n\n## Design Philosophy\n\nThis patrol follows Gas Town principles:\n- **Discovery over tracking**: Observe reality each cycle, don't maintain state\n- **Events over state**: POLECAT_DONE mail triggers cleanup wisps\n- **Cleanup wisps as finalizers**: Pending cleanups are wisps, not queue entries\n- **Task tool for parallelism**: Subagents inspect polecats, not molecule arms\n\n## Patrol Shape (Linear, Deacon-style)\n\n```\ninbox-check ─► process-cleanups ─► check-refinery ─► survey-workers\n │\n ┌──────────────────────────────────────────────────┘\n ▼\n check-swarm ─► ping-deacon ─► patrol-cleanup ─► context-check ─► loop-or-exit\n```\n\nNo dynamic arms. No fanout gates. No persistent nudge counters.\nState is discovered each cycle from reality (tmux, beads, mail)."
formula = 'mol-witness-patrol'
version = 2
[[steps]]
id = "inbox-check"
title = "Process witness mail"
description = """
Check inbox and handle messages.
```bash
gt mail inbox
```
For each message:
**POLECAT_DONE / LIFECYCLE:Shutdown**:
Create a cleanup wisp for this polecat:
```bash
bd create --wisp --title "cleanup:<polecat>" \
--description "Verify and cleanup polecat <name>" \
--labels cleanup,polecat:<name>,state:pending
```
The wisp's existence IS the pending cleanup. Process in next step.
Mark mail as read.
**MERGED**:
A branch was merged successfully. Complete the cleanup.
```bash
# Find the cleanup wisp for this polecat
bd list --wisp --labels=polecat:<name>,state:merge-requested --status=open
# If found, proceed with full polecat nuke:
# - Kill Claude session
# - Delete worktree
# - Delete branch
# - Remove agent bead
gt polecat nuke <name>
# Burn the cleanup wisp
bd close <wisp-id>
```
Mark mail as read.
**HELP / Blocked**:
Assess the request. Can you help? If not, escalate to Mayor:
```bash
gt mail send mayor/ -s "Escalation: <polecat> needs help" -m "<details>"
```
**HANDOFF**:
Read predecessor context. Continue from where they left off.
**SWARM_START**:
Mayor initiating batch polecat work. Initialize swarm tracking.
```bash
# Parse swarm info from mail body: {"swarm_id": "batch-123", "beads": ["bd-a", "bd-b"]}
bd create --wisp --title "swarm:<swarm_id>" \
--description "Tracking batch: <swarm_id>" \
--labels swarm,swarm_id:<swarm_id>,total:<N>,completed:0,start:<timestamp>
```
Mark mail as read."""
description = "Check inbox and handle messages.\n\n```bash\ngt mail inbox\n```\n\nFor each message:\n\n**POLECAT_STARTED**:\nA new polecat has started working. Acknowledge and archive.\n```bash\n# Acknowledge startup (optional: log for activity tracking)\ngt mail archive <message-id>\n```\nNo action needed beyond acknowledgment - archive immediately.\n\n**POLECAT_DONE / LIFECYCLE:Shutdown**:\nCreate a cleanup wisp for this polecat:\n```bash\nbd create --wisp --title \"cleanup:<polecat>\" --description \"Verify and cleanup polecat <name>\" --labels cleanup,polecat:<name>,state:pending\n```\nThe wisp's existence IS the pending cleanup. Process in next step.\n**Do NOT archive yet** - archive after cleanup completes (in MERGED handling).\n\n**MERGED**:\nA branch was merged successfully. Complete the cleanup.\n```bash\n# Find the cleanup wisp for this polecat\nbd list --wisp --labels=polecat:<name>,state:merge-requested --status=open\n\n# If found, proceed with full polecat nuke:\n# - Kill Claude session\n# - Delete worktree\n# - Delete branch\n# - Remove agent bead\ngt polecat nuke <name>\n\n# Burn the cleanup wisp\nbd close <wisp-id>\n\n# NOW archive both the MERGED mail and the original POLECAT_DONE mail\n# (The POLECAT_DONE message ID should be tracked in the cleanup wisp or MR bead)\ngt mail archive <merged-message-id>\ngt mail archive <polecat-done-message-id> # If tracked\n```\nArchive after cleanup is complete.\n\n**HELP / Blocked**:\nAssess the request. Can you help? If not, escalate to Mayor:\n```bash\ngt mail send mayor/ -s \"Escalation: <polecat> needs help\" -m \"<details>\"\n```\nArchive after handling (escalated or resolved):\n```bash\ngt mail archive <message-id>\n```\n\n**HANDOFF**:\nRead predecessor context. Continue from where they left off.\nArchive after absorbing context:\n```bash\ngt mail archive <message-id>\n```\n\n**SWARM_START**:\nMayor initiating batch polecat work. Initialize swarm tracking.\n```bash\n# Parse swarm info from mail body: {\"swarm_id\": \"batch-123\", \"beads\": [\"bd-a\", \"bd-b\"]}\nbd create --wisp --title \"swarm:<swarm_id>\" --description \"Tracking batch: <swarm_id>\" --labels swarm,swarm_id:<swarm_id>,total:<N>,completed:0,start:<timestamp>\n```\nArchive after creating swarm tracking wisp:\n```bash\ngt mail archive <message-id>\n```\n\n**Hygiene principle**: Archive messages after they're fully processed.\nKeep only: active work, unprocessed requests. Inbox should be near-empty."
id = 'inbox-check'
title = 'Process witness mail'
[[steps]]
id = "process-cleanups"
title = "Process pending cleanup wisps"
needs = ["inbox-check"]
description = """
Find and process cleanup wisps (the finalizer pattern).
```bash
# Find all cleanup wisps
bd list --wisp --labels=cleanup --status=open
```
For each cleanup wisp, check its state label:
## State: pending (needs verification → MERGE_READY)
1. **Extract polecat name** from wisp title/labels
2. **Pre-kill verification**:
```bash
cd polecats/<name>
git status # Must be clean
git log origin/main..HEAD # Commits should be pushed
bd show <assigned-issue> # Issue closed or deferred
```
3. **Get branch and issue info**:
```bash
# Get current branch
git rev-parse --abbrev-ref HEAD
# Get the hook_bead from agent bead
bd show <agent-bead> # Look for hook_bead field
```
4. **Verify productive work** (ZFC - you make the call):
- Check git log for commits mentioning the issue
- Legitimate exceptions: already fixed, duplicate, deferred
- If closing as 'done' with no commits, flag for review
5. **If clean**: Send MERGE_READY to refinery
```bash
gt mail send <rig>/refinery -s "MERGE_READY <polecat>" -m "Branch: <branch>
Issue: <issue-id>
Polecat: <polecat>
Verified: clean git state, issue closed"
```
Then update the wisp to merge-requested state:
```bash
bd update <wisp-id> --labels cleanup,polecat:<name>,state:merge-requested
```
**Do NOT kill the polecat yet** - wait for MERGED confirmation from refinery.
6. **If dirty**: Leave wisp open, log the issue, retry next cycle.
## State: merge-requested (waiting for refinery)
Skip - waiting for MERGED mail from refinery. The inbox-check step handles
MERGED messages and completes these cleanup wisps.
**Parallelism**: Use Task tool subagents to process multiple cleanups concurrently.
Each cleanup is independent - perfect for parallel execution."""
description = "Find and process cleanup wisps (the finalizer pattern).\n\n```bash\n# Find all cleanup wisps\nbd list --wisp --labels=cleanup --status=open\n```\n\nFor each cleanup wisp, check its state label:\n\n## State: pending (needs verification → MERGE_READY)\n\n1. **Extract polecat name** from wisp title/labels\n\n2. **Pre-kill verification**:\n```bash\ncd polecats/<name>\ngit status # Must be clean\ngit log origin/main..HEAD # Commits should be pushed\nbd show <assigned-issue> # Issue closed or deferred\n```\n\n3. **Get branch and issue info**:\n```bash\n# Get current branch\ngit rev-parse --abbrev-ref HEAD\n\n# Get the hook_bead from agent bead\nbd show <agent-bead> # Look for hook_bead field\n```\n\n4. **Verify productive work** (ZFC - you make the call):\n - Check git log for commits mentioning the issue\n - Legitimate exceptions: already fixed, duplicate, deferred\n - If closing as 'done' with no commits, flag for review\n\n5. **If clean**: Send MERGE_READY to refinery\n```bash\ngt mail send <rig>/refinery -s \"MERGE_READY <polecat>\" -m \"Branch: <branch>\nIssue: <issue-id>\nPolecat: <polecat>\nVerified: clean git state, issue closed\"\n```\nThen update the wisp to merge-requested state:\n```bash\nbd update <wisp-id> --labels cleanup,polecat:<name>,state:merge-requested\n```\n**Do NOT kill the polecat yet** - wait for MERGED confirmation from refinery.\n\n6. **If dirty**: Leave wisp open, log the issue, retry next cycle.\n\n## State: merge-requested (waiting for refinery)\n\nSkip - waiting for MERGED mail from refinery. The inbox-check step handles\nMERGED messages and completes these cleanup wisps.\n\n**Parallelism**: Use Task tool subagents to process multiple cleanups concurrently.\nEach cleanup is independent - perfect for parallel execution."
id = 'process-cleanups'
needs = ['inbox-check']
title = 'Process pending cleanup wisps'
[[steps]]
id = "check-refinery"
title = "Ensure refinery is alive"
needs = ["process-cleanups"]
description = """
Ensure the refinery is alive and processing merge requests.
```bash
# Check if refinery session exists
gt session status <rig>/refinery
# Check for pending merge requests
bd list --type=merge-request --status=open
```
If MRs waiting AND refinery not running:
```bash
gt session start <rig>/refinery
gt mail send <rig>/refinery -s "PATROL: Wake up" \
-m "Merge requests in queue. Please process."
```
If refinery running but queue stale (>30 min), send nudge."""
description = "Ensure the refinery is alive and processing merge requests.\n\n```bash\n# Check if refinery session exists\ngt session status <rig>/refinery\n\n# Check for pending merge requests\nbd list --type=merge-request --status=open\n```\n\nIf MRs waiting AND refinery not running:\n```bash\ngt session start <rig>/refinery\ngt mail send <rig>/refinery -s \"PATROL: Wake up\" -m \"Merge requests in queue. Please process.\"\n```\n\nIf refinery running but queue stale (>30 min), send nudge."
id = 'check-refinery'
needs = ['process-cleanups']
title = 'Ensure refinery is alive'
[[steps]]
id = "survey-workers"
title = "Inspect all active polecats"
needs = ["check-refinery"]
description = """
Survey all polecats using agent beads (ZFC: trust what agents report).
**Step 1: List polecat agent beads**
```bash
bd list --type=agent --json
```
Filter the JSON output for entries where description contains `role_type: polecat`.
Each polecat agent bead has fields in its description:
- `role_type: polecat`
- `rig: <rig-name>`
- `agent_state: running|idle|stuck|done`
- `hook_bead: <current-work-id>`
**Step 2: For each polecat, check agent_state**
| agent_state | Meaning | Action |
|-------------|---------|--------|
| running | Actively working | Check progress (Step 3) |
| idle | No work assigned | Skip (no action needed) |
| stuck | Self-reported stuck | Handle stuck protocol |
| done | Work complete | Verify cleanup triggered (see Step 4a) |
**Step 3: For running polecats, assess progress**
Check the hook_bead field to see what they're working on:
```bash
bd show <hook_bead> # See current step/issue
```
You can also verify they're responsive:
```bash
tmux capture-pane -t gt-<rig>-<name> -p | tail -20
```
Look for:
- Recent tool activity → making progress
- Idle at prompt → may need nudge
- Error messages → may need help
**Step 4: Decide action**
| Observation | Action |
|-------------|--------|
| agent_state=running, recent activity | None |
| agent_state=running, idle 5-15 min | Gentle nudge |
| agent_state=running, idle 15+ min | Direct nudge with deadline |
| agent_state=stuck | Assess and help or escalate |
| agent_state=done | Verify cleanup triggered (see Step 4a) |
**Step 4a: Handle agent_state=done**
Check if a cleanup wisp exists for this polecat:
```bash
bd list --wisp --labels=polecat:<name> --status=open
```
If cleanup wisp exists:
- state:pending → Will be processed in process-cleanups
- state:merge-requested → Waiting for refinery MERGED response
If NO cleanup wisp exists (POLECAT_DONE mail missed):
Create one to trigger the cleanup flow:
```bash
bd create --wisp --title "cleanup:<polecat>" \
--description "Discovered done polecat without cleanup wisp" \
--labels cleanup,polecat:<name>,state:pending
```
This ensures done polecats eventually get cleaned up even if mail was lost.
**Step 5: Execute nudges**
```bash
gt nudge <rig>/polecats/<name> "How's progress? Need help?"
```
**Step 6: Escalate if needed**
```bash
gt mail send mayor/ -s "Escalation: <polecat> stuck" \\
-m "Polecat <name> reports stuck. Please intervene."
```
**Parallelism**: Use Task tool subagents to inspect multiple polecats concurrently.
**ZFC Principle**: Trust agent_state from beads. Don't infer state from PID/tmux."""
description = "Survey all polecats using agent beads (ZFC: trust what agents report).\n\n**Step 1: List polecat agent beads**\n\n```bash\nbd list --type=agent --json\n```\n\nFilter the JSON output for entries where description contains `role_type: polecat`.\nEach polecat agent bead has fields in its description:\n- `role_type: polecat`\n- `rig: <rig-name>`\n- `agent_state: running|idle|stuck|done`\n- `hook_bead: <current-work-id>`\n\n**Step 2: For each polecat, check agent_state**\n\n| agent_state | Meaning | Action |\n|-------------|---------|--------|\n| running | Actively working | Check progress (Step 3) |\n| idle | No work assigned | Skip (no action needed) |\n| stuck | Self-reported stuck | Handle stuck protocol |\n| done | Work complete | Verify cleanup triggered (see Step 4a) |\n\n**Step 3: For running polecats, assess progress**\n\nCheck the hook_bead field to see what they're working on:\n```bash\nbd show <hook_bead> # See current step/issue\n```\n\nYou can also verify they're responsive:\n```bash\ntmux capture-pane -t gt-<rig>-<name> -p | tail -20\n```\n\nLook for:\n- Recent tool activity → making progress\n- Idle at prompt → may need nudge\n- Error messages → may need help\n\n**Step 4: Decide action**\n\n| Observation | Action |\n|-------------|--------|\n| agent_state=running, recent activity | None |\n| agent_state=running, idle 5-15 min | Gentle nudge |\n| agent_state=running, idle 15+ min | Direct nudge with deadline |\n| agent_state=stuck | Assess and help or escalate |\n| agent_state=done | Verify cleanup triggered (see Step 4a) |\n\n**Step 4a: Handle agent_state=done**\n\nCheck if a cleanup wisp exists for this polecat:\n```bash\nbd list --wisp --labels=polecat:<name> --status=open\n```\n\nIf cleanup wisp exists:\n- state:pending → Will be processed in process-cleanups\n- state:merge-requested → Waiting for refinery MERGED response\n\nIf NO cleanup wisp exists (POLECAT_DONE mail missed):\nCreate one to trigger the cleanup flow:\n```bash\nbd create --wisp --title \"cleanup:<polecat>\" --description \"Discovered done polecat without cleanup wisp\" --labels cleanup,polecat:<name>,state:pending\n```\nThis ensures done polecats eventually get cleaned up even if mail was lost.\n\n**Step 5: Execute nudges**\n```bash\ngt nudge <rig>/polecats/<name> \"How's progress? Need help?\"\n```\n\n**Step 6: Escalate if needed**\n```bash\ngt mail send mayor/ -s \"Escalation: <polecat> stuck\" \\\n -m \"Polecat <name> reports stuck. Please intervene.\"\n```\n\n**Parallelism**: Use Task tool subagents to inspect multiple polecats concurrently.\n\n**ZFC Principle**: Trust agent_state from beads. Don't infer state from PID/tmux."
id = 'survey-workers'
needs = ['check-refinery']
title = 'Inspect all active polecats'
[[steps]]
id = "check-swarm-completion"
title = "Check if active swarm is complete"
needs = ["survey-workers"]
description = """
If Mayor started a batch (SWARM_START), check if all polecats have completed.
**Step 1: Find active swarm tracking wisps**
```bash
bd list --wisp --labels=swarm --status=open
```
If no active swarm, skip this step.
**Step 2: Count completed polecats for this swarm**
Extract from wisp labels: swarm_id, total, completed, start timestamp.
Check how many cleanup wisps have been closed for this swarm's polecats.
**Step 3: If all complete, notify Mayor**
```bash
gt mail send mayor/ -s "SWARM_COMPLETE: <swarm_id>" -m "All <total> polecats merged.
Duration: <minutes> minutes
Swarm: <swarm_id>"
# Close the swarm tracking wisp
bd close <swarm-wisp-id> --reason "All polecats merged"
```
Note: Runs every patrol cycle. Notification sent exactly once when all complete."""
description = "If Mayor started a batch (SWARM_START), check if all polecats have completed.\n\n**Step 1: Find active swarm tracking wisps**\n```bash\nbd list --wisp --labels=swarm --status=open\n```\nIf no active swarm, skip this step.\n\n**Step 2: Count completed polecats for this swarm**\n\nExtract from wisp labels: swarm_id, total, completed, start timestamp.\nCheck how many cleanup wisps have been closed for this swarm's polecats.\n\n**Step 3: If all complete, notify Mayor**\n```bash\ngt mail send mayor/ -s \"SWARM_COMPLETE: <swarm_id>\" -m \"All <total> polecats merged.\nDuration: <minutes> minutes\nSwarm: <swarm_id>\"\n\n# Close the swarm tracking wisp\nbd close <swarm-wisp-id> --reason \"All polecats merged\"\n```\n\nNote: Runs every patrol cycle. Notification sent exactly once when all complete."
id = 'check-swarm-completion'
needs = ['survey-workers']
title = 'Check if active swarm is complete'
[[steps]]
id = "ping-deacon"
title = "Ping Deacon for health check"
needs = ["check-swarm-completion"]
description = """
Send WITNESS_PING to Deacon for second-order monitoring.
The Witness fleet collectively monitors Deacon health - this prevents the
"who watches the watchers" problem. If Deacon dies, Witnesses detect it.
**Step 1: Send ping**
```bash
gt mail send deacon/ -s "WITNESS_PING <rig>" -m "Rig: <rig>
Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)
Patrol: <cycle-number>"
```
**Step 2: Check Deacon health**
```bash
# Check Deacon agent bead for last_activity
bd list --type=agent --json | jq '.[] | select(.description | contains("deacon"))'
```
Look at the `last_activity` timestamp. If stale (>5 minutes since last update):
- Deacon may be dead or stuck
**Step 3: Escalate if needed**
```bash
# If Deacon appears down
gt mail send mayor/ -s "ALERT: Deacon appears unresponsive" \
-m "No Deacon activity for >5 minutes.
Last seen: <timestamp>
Witness: <rig>/witness"
```
Note: Multiple Witnesses may send this alert. Mayor should handle deduplication."""
description = "Send WITNESS_PING to Deacon for second-order monitoring.\n\nThe Witness fleet collectively monitors Deacon health - this prevents the\n\"who watches the watchers\" problem. If Deacon dies, Witnesses detect it.\n\n**Step 1: Send ping**\n```bash\ngt mail send deacon/ -s \"WITNESS_PING <rig>\" -m \"Rig: <rig>\nTimestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)\nPatrol: <cycle-number>\"\n```\n\n**Step 2: Check Deacon health**\n```bash\n# Check Deacon agent bead for last_activity\nbd list --type=agent --json | jq '.[] | select(.description | contains(\"deacon\"))'\n```\n\nLook at the `last_activity` timestamp. If stale (>5 minutes since last update):\n- Deacon may be dead or stuck\n\n**Step 3: Escalate if needed**\n```bash\n# If Deacon appears down\ngt mail send mayor/ -s \"ALERT: Deacon appears unresponsive\" -m \"No Deacon activity for >5 minutes.\nLast seen: <timestamp>\nWitness: <rig>/witness\"\n```\n\nNote: Multiple Witnesses may send this alert. Mayor should handle deduplication."
id = 'ping-deacon'
needs = ['check-swarm-completion']
title = 'Ping Deacon for health check'
[[steps]]
id = "context-check"
title = "Check own context limit"
needs = ["ping-deacon"]
description = """
Check own context usage.
If context is HIGH (>80%):
- Ensure any notes are written to handoff mail
- Prepare for session restart
If context is LOW:
- Can continue patrolling"""
description = "Verify inbox hygiene before ending patrol cycle.\n\n**Step 1: Check inbox state**\n```bash\ngt mail inbox\n```\n\nInbox should contain ONLY:\n- Unprocessed messages (just arrived, will handle next cycle)\n- Active work markers (POLECAT_DONE waiting for MERGED confirmation)\n\n**Step 2: Archive any stale messages**\n\nLook for messages that were processed but not archived:\n- POLECAT_STARTED older than this cycle → archive\n- HELP/Blocked that was escalated → archive\n- SWARM_START that created tracking wisp → archive\n\n```bash\n# For each stale message found:\ngt mail archive <message-id>\n```\n\n**Step 3: Verify cleanup wisp hygiene**\n\nCheck that all cleanup wisps are in valid states:\n```bash\nbd list --wisp --labels=cleanup --status=open\n```\n\n- state:pending → Will be processed next cycle\n- state:merge-requested → Waiting for refinery\n\nIf any cleanup wisp is older than expected (>1 hour in merge-requested state),\nthe refinery may be stuck. This was checked in check-refinery step.\n\n**Goal**: Inbox should have ≤5 active messages at end of cycle."
id = 'patrol-cleanup'
needs = ['ping-deacon']
title = 'End-of-cycle inbox hygiene'
[[steps]]
id = "loop-or-exit"
title = "Loop or exit for respawn"
needs = ["context-check"]
description = """
End of patrol cycle decision.
description = "Check own context usage.\n\nIf context is HIGH (>80%):\n- Ensure any notes are written to handoff mail\n- Prepare for session restart\n\nIf context is LOW:\n- Can continue patrolling"
id = 'context-check'
needs = ['patrol-cleanup']
title = 'Check own context limit'
**If context LOW**:
- Sleep briefly to avoid tight loop (30-60 seconds)
- Return to inbox-check step
- Continue patrolling
**If context HIGH**:
- Write handoff mail to self with any notable observations:
```bash
gt handoff -s "Witness patrol handoff" -m "<observations>"
```
- Exit cleanly (daemon respawns fresh Witness)
The daemon ensures Witness is always running."""
[[steps]]
description = "End of patrol cycle decision.\n\n**If context LOW**:\n- Sleep briefly to avoid tight loop (30-60 seconds)\n- Return to inbox-check step\n- Continue patrolling\n\n**If context HIGH**:\n- Write handoff mail to self with any notable observations:\n```bash\ngt handoff -s \"Witness patrol handoff\" -m \"<observations>\"\n```\n- Exit cleanly (daemon respawns fresh Witness)\n\nThe daemon ensures Witness is always running."
id = 'loop-or-exit'
needs = ['context-check']
title = 'Loop or exit for respawn'