From 1ba425eac8462c3701f287374a00d59b955f33ac Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Wed, 24 Dec 2025 13:00:04 -0800 Subject: [PATCH] Add patrol formula YAML files (gt-ingm.1) Create initial formula definitions for patrol molecules: - mol-deacon-patrol: Mayor daemon loop (8 steps) - mol-witness-patrol: Christmas Ornament pattern with dynamic bonding (9 steps) - mol-refinery-patrol: Merge queue processor with verification gates (10 steps) - mol-polecat-arm: Single polecat inspection cycle for witness bonding (5 steps) These replace the embedded markdown in molecules_patrol.go with proper formula YAML files that can be cooked into proto beads. Generated with Claude Code Co-Authored-By: Claude Opus 4.5 --- .../formulas/mol-deacon-patrol.formula.yaml | 226 ++++++++++++++++++ .beads/formulas/mol-polecat-arm.formula.yaml | 128 ++++++++++ .../formulas/mol-refinery-patrol.formula.yaml | 154 ++++++++++++ .../formulas/mol-witness-patrol.formula.yaml | 212 ++++++++++++++++ 4 files changed, 720 insertions(+) create mode 100644 .beads/formulas/mol-deacon-patrol.formula.yaml create mode 100644 .beads/formulas/mol-polecat-arm.formula.yaml create mode 100644 .beads/formulas/mol-refinery-patrol.formula.yaml create mode 100644 .beads/formulas/mol-witness-patrol.formula.yaml diff --git a/.beads/formulas/mol-deacon-patrol.formula.yaml b/.beads/formulas/mol-deacon-patrol.formula.yaml new file mode 100644 index 00000000..a708ac55 --- /dev/null +++ b/.beads/formulas/mol-deacon-patrol.formula.yaml @@ -0,0 +1,226 @@ +# 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 + 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 + # Handle based on message type + ``` + + Callbacks may spawn new polecats, update issue state, or trigger other actions. + + - id: trigger-pending-spawns + 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 + 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 + gt refinery status + ``` + + **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: + : + witness: + unresponsive_cycles: 0 + last_seen_healthy: + refinery: + unresponsive_cycles: 0 + last_seen_healthy: + ``` + + **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 | + | 5+ | Escalate to Mayor with context | + + **Restart commands:** + ```bash + gt witness restart + gt refinery restart + ``` + + **Escalation:** + ```bash + gt mail send mayor/ -s "Health: unresponsive" \ + -m "Component has been unresponsive for N cycles. Restart attempts failed. + Last healthy: + Error signals:
" + ``` + + Reset unresponsive_cycles to 0 when component responds normally. + + - id: plugin-run + 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 + 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 + 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 + 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 + 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. diff --git a/.beads/formulas/mol-polecat-arm.formula.yaml b/.beads/formulas/mol-polecat-arm.formula.yaml new file mode 100644 index 00000000..a821f5fa --- /dev/null +++ b/.beads/formulas/mol-polecat-arm.formula.yaml @@ -0,0 +1,128 @@ +# Polecat Arm: Single polecat inspection cycle +# Dynamically bonded by mol-witness-patrol for each polecat being monitored. + +formula: mol-polecat-arm +description: | + 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 | +version: 1 + +variables: + - name: polecat_name + required: true + description: Name of the polecat to inspect + - name: rig + required: true + description: Rig containing the polecat + +steps: + - id: capture + description: | + Capture recent tmux output for {{polecat_name}}. + + ```bash + tmux capture-pane -t gt-{{rig}}-{{polecat_name}} -p | tail -50 + ``` + + Record: + - Last activity timestamp (when was last tool call?) + - Visible errors or stack traces + - Completion indicators ("Done", "Finished", etc.) + + - id: assess + needs: [capture] + description: | + 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. + + - id: load-history + needs: [assess] + description: | + Read nudge history for {{polecat_name}} from patrol state. + + ``` + nudge_count = state.nudges[{{polecat_name}}].count + last_nudge_time = state.nudges[{{polecat_name}}].timestamp + ``` + + This data was loaded by the parent patrol's load-state step and passed + to the arm via the bonding context. + + - id: decide + needs: [load-history] + description: | + Apply the nudge matrix to determine action for {{polecat_name}}. + + | 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. + + - id: execute + needs: [decide] + description: | + Take the decided action for {{polecat_name}}. + + **nudge-N**: + ```bash + tmux send-keys -t gt-{{rig}}-{{polecat_name}} "{{nudge_text}}" Enter + ``` + + **pre-kill-verify**: + ```bash + cd polecats/{{polecat_name}} + git status # Must be clean + git log origin/main..HEAD # Check for unpushed + bd show # 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: {{polecat_name}} stuck" -m "..." + ``` + + **none**: No action needed. + + Record: action taken, result, updated nudge count. + + ## 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 {{polecat_name}} + + This data feeds back to the parent patrol's aggregate step. diff --git a/.beads/formulas/mol-refinery-patrol.formula.yaml b/.beads/formulas/mol-refinery-patrol.formula.yaml new file mode 100644 index 00000000..d5922f0c --- /dev/null +++ b/.beads/formulas/mol-refinery-patrol.formula.yaml @@ -0,0 +1,154 @@ +# Refinery Patrol: Merge queue processor with verification gates +# The Refinery is the Engineer in the engine room. You process polecat branches, +# merging them to main one at a time with sequential rebasing. + +formula: mol-refinery-patrol +description: | + Merge queue processor patrol loop. + + The Refinery is the Engineer in the engine room. You process polecat branches, + merging them to main one at a time with sequential rebasing. + + **The Scotty Test**: Before proceeding past any failure, ask yourself: + "Would Scotty walk past a warp core leak because it existed before his shift?" +version: 1 + +steps: + - id: inbox-check + description: | + Check mail for MR submissions, escalations, messages. + + ```bash + gt mail inbox + # Process any urgent items + ``` + + Handle shutdown requests, escalations, and status queries. + + - id: queue-scan + needs: [inbox-check] + description: | + Fetch remote and identify polecat branches waiting. + + ```bash + git fetch origin + git branch -r | grep polecat + gt refinery queue + ``` + + If queue empty, skip to context-check step. + Track branch list for this cycle. + + - id: process-branch + needs: [queue-scan] + description: | + Pick next branch. Rebase on current main. + + ```bash + git checkout -b temp origin/ + git rebase origin/main + ``` + + If rebase conflicts and unresolvable: + - git rebase --abort + - Notify polecat to fix and resubmit + - Skip to loop-check for next branch + + - id: run-tests + needs: [process-branch] + description: | + Run the test suite. + + ```bash + go test ./... + ``` + + Track results: pass count, fail count, specific failures. + + - id: handle-failures + needs: [run-tests] + description: | + **VERIFICATION GATE**: This step enforces the Beads Promise. + + If tests PASSED: This step auto-completes. Proceed to merge. + + If tests FAILED: + 1. Diagnose: Is this a branch regression or pre-existing on main? + 2. If branch caused it: + - Abort merge + - Notify polecat: "Tests failing. Please fix and resubmit." + - Skip to loop-check + 3. If pre-existing on main: + - Option A: Fix it yourself (you're the Engineer!) + - Option B: File a bead: bd create --type=bug --priority=1 --title="..." + + **GATE REQUIREMENT**: You CANNOT proceed to merge-push without: + - Tests passing, OR + - Fix committed, OR + - Bead filed for the failure + + This is non-negotiable. Never disavow. Never "note and proceed." + + - id: merge-push + needs: [handle-failures] + description: | + Merge to main and push immediately. + + ```bash + git checkout main + git merge --ff-only temp + git push origin main + git branch -d temp + git branch -D # Local delete (branches never go to origin) + ``` + + Main has moved. Any remaining branches need rebasing on new baseline. + + - id: loop-check + needs: [merge-push] + description: | + More branches to process? + + If yes: Return to process-branch with next branch. + If no: Continue to generate-summary. + + Track: branches processed, branches skipped (with reasons). + + - id: generate-summary + needs: [loop-check] + description: | + Summarize this patrol cycle. + + Include: + - Branches processed (count, names) + - Test results (pass/fail) + - Issues filed (if any) + - Branches skipped (with reasons) + - Any escalations sent + + This becomes the digest when the patrol is squashed. + + - id: context-check + needs: [generate-summary] + description: | + Check own context usage. + + If context is HIGH (>80%): + - Write handoff summary + - Prepare for burn/respawn + + If context is LOW: + - Can continue processing + + - id: burn-or-loop + needs: [context-check] + description: | + End of patrol cycle decision. + + If queue non-empty AND context LOW: + - Burn this wisp, start fresh patrol + - Return to inbox-check + + If queue empty OR context HIGH: + - Burn wisp with summary digest + - Exit (daemon will respawn if needed) diff --git a/.beads/formulas/mol-witness-patrol.formula.yaml b/.beads/formulas/mol-witness-patrol.formula.yaml new file mode 100644 index 00000000..33432078 --- /dev/null +++ b/.beads/formulas/mol-witness-patrol.formula.yaml @@ -0,0 +1,212 @@ +# Witness Patrol: Per-rig worker monitor using Christmas Ornament pattern +# 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. + +formula: mol-witness-patrol +description: | + Per-rig worker monitor patrol loop using the Christmas Ornament pattern. + + 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. + + This molecule uses dynamic bonding to spawn mol-polecat-arm for each worker, + enabling parallel inspection with a fanout gate for aggregation. + + ## The Christmas Ornament Shape + + ``` + ★ mol-witness-patrol (trunk) + /|\ + ┌────────┘ │ └────────┐ + PREFLIGHT DISCOVERY CLEANUP + │ │ │ + inbox-check survey aggregate (WaitsFor: all-children) + check-refnry │ save-state + load-state │ generate-summary + ↓ context-check + ┌───────┼───────┐ burn-or-loop + ● ● ● mol-polecat-arm (dynamic) + ace nux toast + ``` +version: 1 + +steps: + # --- PREFLIGHT PHASE --- + + - id: inbox-check + description: | + Process witness mail: lifecycle requests, help requests. + + ```bash + gt mail inbox + ``` + + Handle by message type: + - **LIFECYCLE/Shutdown**: Queue for pre-kill verification + - **Blocked/Help**: Assess if resolvable or escalate + - **HANDOFF**: Load predecessor state + - **Work complete**: Verify issue closed, proceed to pre-kill + + Record any pending actions for later steps. + Mark messages as processed when complete. + + - id: check-refinery + needs: [inbox-check] + description: | + Ensure the refinery is alive and processing merge requests. + + **Redundant system**: This check runs in both gt spawn and Witness patrol + to ensure the merge queue processor stays operational. + + ```bash + # Check if refinery session is running + gt session status /refinery + + # Check for merge requests in queue + bd list --type=merge-request --status=open + ``` + + If merge requests are waiting AND refinery is not running: + ```bash + gt session start /refinery + gt mail send /refinery -s "PATROL: Wake up" -m "Merge requests in queue. Please process." + ``` + + If refinery is running but queue is non-empty for >30 min, send nudge. + This ensures polecats don't wait forever for their branches to merge. + + - id: load-state + needs: [check-refinery] + description: | + Read handoff bead and get nudge counts. + + Load persistent state from the witness handoff bead: + - Active workers and their status from last cycle + - Nudge counts per worker per issue + - Last nudge timestamps + - Pending escalations + + ```bash + bd show + ``` + + If no handoff exists (fresh start), initialize empty state. + This state persists across wisp burns and session cycles. + + # --- DISCOVERY PHASE (Dynamic Bonding) --- + + - id: survey-workers + needs: [load-state] + description: | + List polecats and bond mol-polecat-arm for each one. + + ```bash + # Get list of polecats + gt polecat list + ``` + + For each polecat discovered, dynamically bond an inspection arm: + + ```bash + # Bond mol-polecat-arm for each polecat + for polecat in $(gt polecat list --names); do + bd mol bond mol-polecat-arm $PATROL_WISP_ID \ + --ref arm-$polecat \ + --var polecat_name=$polecat \ + --var rig= + done + ``` + + This creates child wisps like: + - patrol-x7k.arm-ace (5 steps) + - patrol-x7k.arm-nux (5 steps) + - patrol-x7k.arm-toast (5 steps) + + Each arm runs in PARALLEL. The aggregate step will wait for all to complete. + + If no polecats are found, this step completes immediately with no children. + + # --- CLEANUP PHASE (Gate + Fixed Steps) --- + + - id: aggregate + needs: [survey-workers] + waits_for: all-children + description: | + Collect outcomes from all polecat inspection arms. + + This is a **fanout gate** - it cannot proceed until ALL dynamically-bonded + polecat arms have completed their inspection cycles. + + Once all arms complete, collect their outcomes: + - Actions taken per polecat (nudge, kill, escalate, none) + - Updated nudge counts + - Any errors or issues discovered + + Build the consolidated state for save-state. + + - id: save-state + needs: [aggregate] + description: | + 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 --description="" + ``` + + This state survives wisp burns and session cycles. + + - id: generate-summary + needs: [save-state] + description: | + Summarize this patrol cycle for digest. + + Include: + - Workers inspected (count, names) + - Nudges sent (count, to whom) + - Sessions killed (count, names) + - Escalations (count, issues) + - Issues found (brief descriptions) + - Actions pending for next cycle + + This becomes the digest when the patrol wisp is squashed. + + - id: context-check + needs: [generate-summary] + description: | + Check own context usage. + + If context is HIGH (>80%): + - Ensure state is saved to handoff bead + - Prepare for burn/respawn + + If context is LOW: + - Can continue patrolling + + - id: burn-or-loop + needs: [context-check] + description: | + End of patrol cycle decision. + + If context is LOW: + - Burn this wisp (no audit trail needed for patrol cycles) + - Sleep briefly to avoid tight loop (30-60 seconds) + - Return to inbox-check step + + If context is HIGH: + - Burn wisp with summary digest + - Exit cleanly (daemon will respawn fresh Witness) + + ```bash + bd mol burn # Destroy ephemeral wisp + ``` + + The daemon ensures Witness is always running.