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>
57 lines
13 KiB
TOML
57 lines
13 KiB
TOML
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]]
|
|
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]]
|
|
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]]
|
|
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]]
|
|
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]]
|
|
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]]
|
|
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]]
|
|
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]]
|
|
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'
|
|
|
|
[[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'
|