feat: witness instantiates mol-witness-patrol on start (gt-59zd.3)

Add patrol instance tracking to witness manager:

- Add PatrolInstanceID to WitnessHandoffState for tracking
- Add ArmID to WorkerState for per-polecat arm tracking
- Add ensurePatrolInstance() method that:
  - Checks if patrol instance already exists (from previous session)
  - Creates new patrol instance if needed
  - Persists instance ID in handoff state

This enables the molecule-based tracking ledger pattern where the
Go-based witness creates beads issues to track its patrol state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-23 22:28:20 -08:00
parent 2b8d246122
commit c81f02de56
2 changed files with 93 additions and 0 deletions

View File

@@ -337,6 +337,11 @@ func (m *Manager) run(w *Witness) error {
m.handoffState = handoffState
fmt.Printf("Loaded handoff state with %d worker(s)\n", len(m.handoffState.WorkerStates))
// Ensure mol-witness-patrol instance exists for tracking
if err := m.ensurePatrolInstance(); err != nil {
fmt.Printf("Warning: could not ensure patrol instance: %v\n", err)
}
// Initial check immediately
m.checkAndProcess(w)
@@ -349,6 +354,88 @@ func (m *Manager) run(w *Witness) error {
return nil
}
// ensurePatrolInstance ensures a mol-witness-patrol instance exists for tracking.
// If one already exists (from a previous session), it's reused. Otherwise, a new
// instance is created and pinned to the witness handoff bead.
func (m *Manager) ensurePatrolInstance() error {
// Check if we already have a patrol instance
if m.handoffState != nil && m.handoffState.PatrolInstanceID != "" {
// Verify it still exists
cmd := exec.Command("bd", "show", m.handoffState.PatrolInstanceID, "--json")
cmd.Dir = m.workDir
if err := cmd.Run(); err == nil {
fmt.Printf("Using existing patrol instance: %s\n", m.handoffState.PatrolInstanceID)
return nil
}
// Instance no longer exists, clear it
m.handoffState.PatrolInstanceID = ""
}
// Create a new patrol instance
// First, create a root issue for the patrol
patrolTitle := fmt.Sprintf("Witness Patrol (%s)", m.rig.Name)
patrolDesc := fmt.Sprintf(`Active mol-witness-patrol instance for %s.
rig: %s
started_at: %s
type: patrol-instance
`, m.rig.Name, m.rig.Name, time.Now().UTC().Format(time.RFC3339))
cmd := exec.Command("bd", "create",
"--title", patrolTitle,
"--type", "task",
"--priority", "3",
"--description", patrolDesc,
)
cmd.Dir = m.workDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("creating patrol instance: %s", stderr.String())
}
// Parse the created issue ID from stdout
// Output format: "✓ Created issue: gt-xyz"
output := stdout.String()
var patrolID string
if _, err := fmt.Sscanf(output, "✓ Created issue: %s", &patrolID); err != nil {
// Try alternate parsing
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "Created issue:") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
patrolID = strings.TrimSpace(parts[len(parts)-1])
break
}
}
}
}
if patrolID == "" {
return fmt.Errorf("could not parse patrol instance ID from: %s", output)
}
// Store the patrol instance ID
if m.handoffState == nil {
m.handoffState = &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}
}
m.handoffState.PatrolInstanceID = patrolID
// Persist the updated handoff state
if err := m.saveHandoffState(m.handoffState); err != nil {
return fmt.Errorf("saving handoff state: %w", err)
}
fmt.Printf("Created patrol instance: %s\n", patrolID)
return nil
}
// checkAndProcess performs health check, shutdown processing, and auto-spawn.
func (m *Manager) checkAndProcess(w *Witness) {
// Perform health check

View File

@@ -90,6 +90,9 @@ type WorkerState struct {
// Issue is the current issue the worker is assigned to.
Issue string `json:"issue,omitempty"`
// ArmID is the mol-polecat-arm instance tracking this worker.
ArmID string `json:"arm_id,omitempty"`
// NudgeCount is how many times this worker has been nudged.
NudgeCount int `json:"nudge_count"`
@@ -106,6 +109,9 @@ type WitnessHandoffState struct {
// WorkerStates maps polecat names to their state.
WorkerStates map[string]WorkerState `json:"worker_states"`
// PatrolInstanceID is the mol-witness-patrol instance tracking this patrol.
PatrolInstanceID string `json:"patrol_instance_id,omitempty"`
// LastPatrol is when the last patrol cycle completed.
LastPatrol *time.Time `json:"last_patrol,omitempty"`
}