feat: Auto-start refinery/witness on spawn, add refinery check to Witness patrol (gt-bjft)

Implements redundant systems to ensure infrastructure stays operational:
- gt spawn: Auto-starts refinery and witness if not running
- mol-witness-patrol: Added check-refinery step to verify refinery is alive

This ensures polecats don't wait forever for merges.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-23 00:52:45 -08:00
parent 67d7193c61
commit a3c671188f
3 changed files with 64 additions and 12 deletions
+25 -1
View File
@@ -814,6 +814,30 @@ Handle by message type:
Record any pending actions for later steps. Record any pending actions for later steps.
Mark messages as processed when complete. Mark messages as processed when complete.
## Step: check-refinery
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 <rig>/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 <rig>/refinery
gt mail send <rig>/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.
Needs: inbox-check
## Step: load-state ## Step: load-state
Read handoff bead and get nudge counts. Read handoff bead and get nudge counts.
@@ -829,7 +853,7 @@ bd show <handoff-bead-id>
If no handoff exists (fresh start), initialize empty state. If no handoff exists (fresh start), initialize empty state.
This state persists across wisp burns and session cycles. This state persists across wisp burns and session cycles.
Needs: inbox-check Needs: check-refinery
## Step: survey-workers ## Step: survey-workers
List polecats and categorize by status. List polecats and categorize by status.
+17 -11
View File
@@ -300,16 +300,17 @@ func TestWitnessPatrolMolecule(t *testing.T) {
t.Fatalf("failed to parse: %v", err) t.Fatalf("failed to parse: %v", err)
} }
// Should have 10 steps: inbox-check, load-state, survey-workers, inspect-workers, // Should have 11 steps: inbox-check, check-refinery, load-state, survey-workers,
// decide-actions, execute-actions, save-state, generate-summary, context-check, burn-or-loop // inspect-workers, decide-actions, execute-actions, save-state, generate-summary,
if len(steps) != 10 { // context-check, burn-or-loop
t.Errorf("expected 10 steps, got %d", len(steps)) if len(steps) != 11 {
t.Errorf("expected 11 steps, got %d", len(steps))
} }
expectedRefs := []string{ expectedRefs := []string{
"inbox-check", "load-state", "survey-workers", "inspect-workers", "inbox-check", "check-refinery", "load-state", "survey-workers",
"decide-actions", "execute-actions", "save-state", "generate-summary", "inspect-workers", "decide-actions", "execute-actions", "save-state",
"context-check", "burn-or-loop", "generate-summary", "context-check", "burn-or-loop",
} }
for i, expected := range expectedRefs { for i, expected := range expectedRefs {
if i >= len(steps) { if i >= len(steps) {
@@ -327,13 +328,18 @@ func TestWitnessPatrolMolecule(t *testing.T) {
t.Errorf("inbox-check should have no deps, got %v", steps[0].Needs) t.Errorf("inbox-check should have no deps, got %v", steps[0].Needs)
} }
// load-state needs inbox-check // check-refinery needs inbox-check
if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "inbox-check" { if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "inbox-check" {
t.Errorf("load-state should need inbox-check, got %v", steps[1].Needs) t.Errorf("check-refinery should need inbox-check, got %v", steps[1].Needs)
}
// load-state needs check-refinery
if len(steps[2].Needs) != 1 || steps[2].Needs[0] != "check-refinery" {
t.Errorf("load-state should need check-refinery, got %v", steps[2].Needs)
} }
// burn-or-loop needs context-check // burn-or-loop needs context-check
if len(steps[9].Needs) != 1 || steps[9].Needs[0] != "context-check" { if len(steps[10].Needs) != 1 || steps[10].Needs[0] != "context-check" {
t.Errorf("burn-or-loop should need context-check, got %v", steps[9].Needs) t.Errorf("burn-or-loop should need context-check, got %v", steps[10].Needs)
} }
} }
+22
View File
@@ -407,6 +407,28 @@ func runSpawn(cmd *cobra.Command, args []string) error {
fmt.Printf(" %s\n", style.Dim.Render("Deacon notified of polecat start")) fmt.Printf(" %s\n", style.Dim.Render("Deacon notified of polecat start"))
} }
// Auto-start infrastructure if not running (redundant system - Witness also self-checks)
// This ensures the merge queue and polecat monitor are alive to handle work
refineryRunning, _ := sessMgr.IsRunning("refinery")
if !refineryRunning {
fmt.Printf("Starting refinery for %s...\n", rigName)
if err := sessMgr.Start("refinery", session.StartOptions{}); err != nil {
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("Warning: could not start refinery: %v", err)))
} else {
fmt.Printf(" %s\n", style.Dim.Render("Refinery started"))
}
}
witnessRunning, _ := sessMgr.IsRunning("witness")
if !witnessRunning {
fmt.Printf("Starting witness for %s...\n", rigName)
if err := sessMgr.Start("witness", session.StartOptions{}); err != nil {
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("Warning: could not start witness: %v", err)))
} else {
fmt.Printf(" %s\n", style.Dim.Render("Witness started"))
}
}
return nil return nil
} }