From 0dac49636c07b4f7afc18e536cea42e65059766f Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 23 Dec 2025 01:43:05 -0800 Subject: [PATCH] Witness startup: auto-bond mol-witness-patrol on gt prime (gt-lx3n) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add outputWitnessPatrolContext() function in prime.go - Witness now auto-spawns mol-witness-patrol if no active patrol - Fixed catalog ID parsing (strip trailing colon) for both witness and refinery - Created mol-witness-patrol template with 10 steps (gt-qflq) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/prime.go | 179 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 3 deletions(-) diff --git a/internal/cmd/prime.go b/internal/cmd/prime.go index 248bbb7e..05241c63 100644 --- a/internal/cmd/prime.go +++ b/internal/cmd/prime.go @@ -549,8 +549,8 @@ func outputAttachmentStatus(ctx RoleContext) { // outputMoleculeContext checks if the agent is working on a molecule step and shows progress. func outputMoleculeContext(ctx RoleContext) { - // Applies to polecats, crew workers, deacon, and refinery - if ctx.Role != RolePolecat && ctx.Role != RoleCrew && ctx.Role != RoleDeacon && ctx.Role != RoleRefinery { + // Applies to polecats, crew workers, deacon, witness, and refinery + if ctx.Role != RolePolecat && ctx.Role != RoleCrew && ctx.Role != RoleDeacon && ctx.Role != RoleWitness && ctx.Role != RoleRefinery { return } @@ -560,6 +560,12 @@ func outputMoleculeContext(ctx RoleContext) { return } + // For Witness, use special patrol molecule handling (auto-bonds on startup) + if ctx.Role == RoleWitness { + outputWitnessPatrolContext(ctx) + return + } + // For Refinery, use special patrol molecule handling (auto-bonds on startup) if ctx.Role == RoleRefinery { outputRefineryPatrolContext(ctx) @@ -755,6 +761,172 @@ func outputDeaconPatrolContext(ctx RoleContext) { fmt.Println(" - Loop back to spawn new wisp, or exit if context high") } +// outputWitnessPatrolContext shows patrol molecule status for the Witness. +// Witness AUTO-BONDS its patrol molecule on startup if one isn't already running. +// This ensures polecat health is always monitored. +func outputWitnessPatrolContext(ctx RoleContext) { + fmt.Println() + fmt.Printf("%s\n\n", style.Bold.Render("## 👁 Witness Patrol Status")) + + // Witness works from its own rig clone: /witness/rig/ + // Beads are in the current WorkDir + witnessBeadsDir := ctx.WorkDir + + // Find mol-witness-patrol molecules (exclude template) + // Look for in-progress patrol first (resumable) + cmdList := exec.Command("bd", "--no-daemon", "list", "--status=in_progress", "--type=epic") + cmdList.Dir = witnessBeadsDir + var stdoutList bytes.Buffer + cmdList.Stdout = &stdoutList + cmdList.Stderr = nil + errList := cmdList.Run() + + hasPatrol := false + var patrolID string + var patrolLine string + + if errList == nil { + lines := strings.Split(stdoutList.String(), "\n") + for _, line := range lines { + if strings.Contains(line, "mol-witness-patrol") && !strings.Contains(line, "[template]") { + parts := strings.Fields(line) + if len(parts) > 0 { + patrolID = parts[0] + patrolLine = line + hasPatrol = true + break + } + } + } + } + + // Also check for open patrols with open children (active wisp) + if !hasPatrol { + cmdOpen := exec.Command("bd", "--no-daemon", "list", "--status=open", "--type=epic") + cmdOpen.Dir = witnessBeadsDir + var stdoutOpen bytes.Buffer + cmdOpen.Stdout = &stdoutOpen + cmdOpen.Stderr = nil + if cmdOpen.Run() == nil { + lines := strings.Split(stdoutOpen.String(), "\n") + for _, line := range lines { + if strings.Contains(line, "mol-witness-patrol") && !strings.Contains(line, "[template]") { + parts := strings.Fields(line) + if len(parts) > 0 { + molID := parts[0] + // Check if this molecule has open children + cmdShow := exec.Command("bd", "--no-daemon", "show", molID) + cmdShow.Dir = witnessBeadsDir + var stdoutShow bytes.Buffer + cmdShow.Stdout = &stdoutShow + cmdShow.Stderr = nil + if cmdShow.Run() == nil { + showOutput := stdoutShow.String() + if strings.Contains(showOutput, "- open]") || strings.Contains(showOutput, "- in_progress]") { + hasPatrol = true + patrolID = molID + patrolLine = line + break + } + } + } + } + } + } + } + + if !hasPatrol { + // No active patrol - AUTO-SPAWN one + fmt.Println("Status: **No active patrol** - spawning mol-witness-patrol...") + fmt.Println() + + // Find the proto ID for mol-witness-patrol + cmdCatalog := exec.Command("bd", "--no-daemon", "mol", "catalog") + cmdCatalog.Dir = witnessBeadsDir + var stdoutCatalog bytes.Buffer + cmdCatalog.Stdout = &stdoutCatalog + cmdCatalog.Stderr = nil + + if cmdCatalog.Run() != nil { + fmt.Println(style.Dim.Render("Failed to list molecule catalog. Run `bd mol catalog` to troubleshoot.")) + return + } + + // Find mol-witness-patrol in catalog + var protoID string + catalogLines := strings.Split(stdoutCatalog.String(), "\n") + for _, line := range catalogLines { + if strings.Contains(line, "mol-witness-patrol") { + parts := strings.Fields(line) + if len(parts) > 0 { + // Strip trailing colon from ID (catalog format: "gt-xxx: title") + protoID = strings.TrimSuffix(parts[0], ":") + break + } + } + } + + if protoID == "" { + fmt.Println(style.Dim.Render("Proto mol-witness-patrol not found in catalog. Run `bd mol register` first.")) + return + } + + // Spawn the wisp (default spawn creates wisp) + cmdSpawn := exec.Command("bd", "--no-daemon", "mol", "spawn", protoID, "--assignee", ctx.Rig+"/witness") + cmdSpawn.Dir = witnessBeadsDir + var stdoutSpawn, stderrSpawn bytes.Buffer + cmdSpawn.Stdout = &stdoutSpawn + cmdSpawn.Stderr = &stderrSpawn + + if err := cmdSpawn.Run(); err != nil { + fmt.Printf("Failed to spawn patrol: %s\n", stderrSpawn.String()) + fmt.Println(style.Dim.Render("Run manually: bd --no-daemon mol spawn " + protoID)) + return + } + + // Parse the spawned molecule ID from output + spawnOutput := stdoutSpawn.String() + fmt.Printf("✓ Spawned patrol molecule\n") + + // Extract molecule ID from spawn output (format: "Created molecule: gt-xxxx") + for _, line := range strings.Split(spawnOutput, "\n") { + if strings.Contains(line, "molecule:") || strings.Contains(line, "Created") { + parts := strings.Fields(line) + for _, p := range parts { + if strings.HasPrefix(p, "gt-") { + patrolID = p + break + } + } + } + } + + if patrolID != "" { + fmt.Printf("Patrol ID: %s\n\n", patrolID) + } + } else { + // Has active patrol - show status + fmt.Println("Status: **Patrol Active**") + fmt.Printf("Patrol: %s\n\n", strings.TrimSpace(patrolLine)) + } + + // Show patrol work loop instructions + fmt.Println("**Witness Patrol Work Loop:**") + fmt.Println("1. Check inbox: `gt mail inbox`") + fmt.Println("2. Check next step: `bd ready`") + fmt.Println("3. Execute the step (survey polecats, inspect, nudge, etc.)") + fmt.Println("4. Close step: `bd close `") + fmt.Println("5. Check next: `bd ready`") + fmt.Println("6. At cycle end (burn-or-loop step):") + fmt.Println(" - Generate summary of patrol cycle") + fmt.Println(" - Squash: `bd --no-daemon mol squash --summary \"\"`") + fmt.Println(" - Loop back to spawn new wisp, or exit if context high") + if patrolID != "" { + fmt.Println() + fmt.Printf("Current patrol ID: %s\n", patrolID) + } +} + // outputRefineryPatrolContext shows patrol molecule status for the Refinery. // Unlike other patrol roles, Refinery AUTO-BONDS its patrol molecule on startup // if one isn't already running. This ensures the merge queue is always monitored. @@ -853,7 +1025,8 @@ func outputRefineryPatrolContext(ctx RoleContext) { if strings.Contains(line, "mol-refinery-patrol") { parts := strings.Fields(line) if len(parts) > 0 { - protoID = parts[0] + // Strip trailing colon from ID (catalog format: "gt-xxx: title") + protoID = strings.TrimSuffix(parts[0], ":") break } }