From ef0008ea2c68641ff353129b4290bbbcc6de55fa Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Mon, 29 Dec 2025 14:19:26 -0800 Subject: [PATCH] feat: Add swarm dispatch to Witness patrol (gt-kc7yj.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dispatch-swarm-work step to mol-witness-patrol formula that: - Queries bd ready --parent= to find ready swarm tasks - Finds idle polecats via agent beads - Dispatches work via gt sling / Also updated: - SWARM mail handling to store epic_id in labels - check-swarm-completion to use beads discovery model 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../formulas/mol-witness-patrol.formula.toml | 134 +++++++++++++++--- 1 file changed, 113 insertions(+), 21 deletions(-) diff --git a/.beads/formulas/mol-witness-patrol.formula.toml b/.beads/formulas/mol-witness-patrol.formula.toml index 0e638fe2..e5d9029c 100644 --- a/.beads/formulas/mol-witness-patrol.formula.toml +++ b/.beads/formulas/mol-witness-patrol.formula.toml @@ -78,14 +78,16 @@ gt mail send mayor/ -s "Escalation: needs help" -m "
" **HANDOFF**: Read predecessor context. Continue from where they left off. -**SWARM_START**: -Mayor initiating batch polecat work. Initialize swarm tracking. +**SWARM: ** (subject pattern): +Mayor assigning swarm coordination. The epic IS the swarm - beads tracks everything. ```bash -# Parse swarm info from mail body: {"swarm_id": "batch-123", "beads": ["bd-a", "bd-b"]} -bd create --wisp --title "swarm:" \ - --description "Tracking batch: " \ - --labels swarm,swarm_id:,total:,completed:0,start: +# Parse epic ID from subject: "SWARM: gt-epic-123" +# Create tracking wisp with epic_id in labels for dispatch step +bd create --wisp --title "swarm:" \ + --description "Coordinating swarm for epic: " \ + --labels swarm,epic_id:,start: ``` +The dispatch-swarm-work step will use epic_id to query `bd ready --parent=`. Mark mail as read.""" [[steps]] @@ -267,11 +269,81 @@ gt mail send mayor/ -s "Escalation: stuck" \\ **ZFC Principle**: Trust agent_state from beads. Don't infer state from PID/tmux.""" [[steps]] -id = "check-swarm-completion" -title = "Check if active swarm is complete" +id = "dispatch-swarm-work" +title = "Dispatch ready swarm tasks to idle polecats" needs = ["survey-workers"] description = """ -If Mayor started a batch (SWARM_START), check if all polecats have completed. +If an active swarm exists, dispatch ready tasks to available polecats. + +This is the core swarm coordination logic - the Witness keeps the swarm moving +by matching ready issues with idle workers. + +**Step 1: Find active swarm tracking wisps** +```bash +bd list --wisp --labels=swarm --status=open +``` +If no active swarm, skip this step entirely. + +**Step 2: For each swarm, get ready tasks** + +Extract the epic_id from the swarm wisp (stored in labels or description). +Then query the ready front: +```bash +bd ready --parent= +``` +This returns issues that have no blockers and are ready to work. + +**Step 3: Find idle polecats** + +From the survey-workers step, you should have agent beads data. +Filter for polecats where: +- agent_state = idle (no work assigned) +- OR hook_bead is empty/none + +```bash +bd list --type=agent --json | jq '[.[] | select(.description | contains("role_type: polecat") and contains("agent_state: idle"))]' +``` + +**Step 4: Dispatch ready tasks to idle polecats** + +For each ready task (up to number of idle polecats): +```bash +gt sling / +``` + +This will: +- Attach the task to the polecat's hook +- Spawn the polecat session if not running +- Inject work context + +Example: +```bash +# Ready task: gt-abc.3, Idle polecat: toast +gt sling gt-abc.3 gastown/toast +``` + +**Step 5: Log dispatch activity** + +For observability, record what was dispatched: +```bash +# Update swarm wisp with dispatch info (optional) +echo "Dispatched gt-abc.3 -> toast at $(date)" +``` + +**Parallelism**: Dispatch calls can be parallelized if multiple idle polecats +and multiple ready tasks exist. + +**Rate limiting**: Don't dispatch more than max_active_polecats (typically 3-5) +to avoid overwhelming the rig/refinery.""" + +[[steps]] +id = "check-swarm-completion" +title = "Check if active swarm is complete" +needs = ["dispatch-swarm-work"] +description = """ +Check if any active swarm has completed (all tasks closed). + +**Discovery over tracking**: Don't count - QUERY. The beads state IS the source of truth. **Step 1: Find active swarm tracking wisps** ```bash @@ -279,22 +351,42 @@ bd list --wisp --labels=swarm --status=open ``` If no active swarm, skip this step. -**Step 2: Count completed polecats for this swarm** +**Step 2: For each swarm, check completion via beads** -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** +Extract epic_id from wisp labels, then check if all children are closed: ```bash -gt mail send mayor/ -s "SWARM_COMPLETE: " -m "All polecats merged. -Duration: minutes -Swarm: " - -# Close the swarm tracking wisp -bd close --reason "All polecats merged" +# Get swarm status from beads (shows ready/active/blocked/completed) +bd swarm status --json ``` -Note: Runs every patrol cycle. Notification sent exactly once when all complete.""" +Swarm is complete when: +- ready = [] (empty) +- active = [] (empty) +- blocked = [] (empty) +- All children are closed + +Alternative direct check: +```bash +# If bd ready --parent returns nothing AND no active issues, swarm is done +bd ready --parent= # Should return nothing +bd list --parent= --status=in_progress # Should return nothing +``` + +**Step 3: If complete, notify Mayor and close swarm** +```bash +gt mail send mayor/ -s "SWARM_COMPLETE: " -m "Epic: +All tasks completed and merged. +Duration: minutes" + +# Close the swarm tracking wisp +bd close --reason "All tasks completed" + +# Optionally close the epic itself (or leave for Mayor to close) +# bd close --reason "Swarm complete" +``` + +**Key insight**: Notification sent exactly once because the wisp gets closed. +Next patrol cycle finds no open swarm wisps, so this step is skipped.""" [[steps]] id = "ping-deacon"