From af360662d98e62a8c0f51232889edda7dd148386 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 23 Dec 2025 22:31:03 -0800 Subject: [PATCH] feat: witness bonds mol-polecat-arm for each polecat (gt-59zd.4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add arm bonding to witness health check: - Add ensurePolecatArm() method that: - Checks if arm already exists for polecat (ArmID in WorkerState) - If not, calls `gt mol bond mol-polecat-arm` with template vars - Parses arm ID from output and stores in handoff state - Call ensurePolecatArm() in healthCheck() for each running polecat This creates the Christmas Ornament pattern where mol-witness-patrol has dynamically bonded mol-polecat-arm children, one per active polecat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/witness/manager.go | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/internal/witness/manager.go b/internal/witness/manager.go index f9e04e61..f2680728 100644 --- a/internal/witness/manager.go +++ b/internal/witness/manager.go @@ -436,6 +436,92 @@ type: patrol-instance return nil } +// ensurePolecatArm ensures a mol-polecat-arm instance exists for tracking a polecat. +// If one already exists (from a previous patrol), it's reused. Otherwise, a new +// arm is bonded to the patrol instance. +func (m *Manager) ensurePolecatArm(polecatName string) error { + // Check if we have handoff state and a patrol instance + if m.handoffState == nil { + return nil // No handoff state, can't bond arms + } + if m.handoffState.PatrolInstanceID == "" { + return nil // No patrol instance, can't bond arms + } + + // Check if we already have an arm for this polecat + ws := m.handoffState.WorkerStates[polecatName] + if ws.ArmID != "" { + // Verify it still exists + cmd := exec.Command("bd", "show", ws.ArmID, "--json") + cmd.Dir = m.workDir + if err := cmd.Run(); err == nil { + return nil // Arm exists, nothing to do + } + // Arm no longer exists, clear it + ws.ArmID = "" + m.handoffState.WorkerStates[polecatName] = ws + } + + // Bond a new arm using gt mol bond + armRef := fmt.Sprintf("arm-%s", polecatName) + cmd := exec.Command("gt", "mol", "bond", "mol-polecat-arm", + "--parent", m.handoffState.PatrolInstanceID, + "--ref", armRef, + "--var", fmt.Sprintf("polecat_name=%s", polecatName), + "--var", fmt.Sprintf("rig=%s", m.rig.Name), + ) + cmd.Dir = m.workDir + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("bonding arm: %s", stderr.String()) + } + + // Parse the arm ID from output + // Expected format: "🔗 Bonded mol-polecat-arm as arm-toast (gt-xyz.1)" + output := stdout.String() + var armID string + // Try to extract ID from parentheses + if start := strings.LastIndex(output, "("); start != -1 { + if end := strings.LastIndex(output, ")"); end > start { + armID = strings.TrimSpace(output[start+1 : end]) + } + } + + if armID == "" { + // Fallback: try to find an issue ID pattern + for _, word := range strings.Fields(output) { + if strings.HasPrefix(word, "gt-") || strings.HasPrefix(word, "(gt-") { + armID = strings.Trim(word, "()") + break + } + } + } + + if armID == "" { + return fmt.Errorf("could not parse arm ID from: %s", output) + } + + // Store the arm ID + if m.handoffState.WorkerStates == nil { + m.handoffState.WorkerStates = make(map[string]WorkerState) + } + ws = m.handoffState.WorkerStates[polecatName] + ws.ArmID = armID + m.handoffState.WorkerStates[polecatName] = ws + + // Persist the updated handoff state + if err := m.saveHandoffState(m.handoffState); err != nil { + return fmt.Errorf("saving handoff state: %w", err) + } + + fmt.Printf("Bonded arm for %s: %s\n", polecatName, armID) + return nil +} + // checkAndProcess performs health check, shutdown processing, and auto-spawn. func (m *Manager) checkAndProcess(w *Witness) { // Perform health check @@ -492,6 +578,11 @@ func (m *Manager) healthCheck(w *Witness) error { if running { active = append(active, p.Name) + // Ensure we have a tracking arm for this polecat + if err := m.ensurePolecatArm(p.Name); err != nil { + fmt.Printf("Warning: could not ensure arm for %s: %v\n", p.Name, err) + } + // Check health of each active polecat status := m.checkPolecatHealth(p.Name, p.ClonePath) if status == PolecatStuck {