refactor: remove molecule logic from witness Go code

Remove embedded molecule management from witness - this logic belongs
in molecule definitions (YAML + hooks), not Go code.

Removed:
- WitnessHandoffState, WorkerState types
- Handoff bead management (load/save/ensure)
- Patrol instance creation (ensurePatrolInstance)
- Polecat arm tracking (ensurePolecatArm, closePolecatArm)
- updateWorkerActivity function

Simplified:
- Nudge tracking now uses only SpawnedIssues (in-memory)
- run() no longer loads handoff state or creates patrol instances
- healthCheck() no longer manages tracking arms

Fixed:
- escalateToMayor: bd mail send → gt mail send
- ackMessage: bd mail ack → gt mail archive

The witness now does its core job (health checks, nudges, escalation,
cleanup) without trying to manage molecule state. Molecule tracking
should be handled by the molecule system itself via bd mol commands.

🤖 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 23:02:20 -08:00
parent 169f23258b
commit 2afc72dd86
2 changed files with 15 additions and 503 deletions

View File

@@ -27,9 +27,8 @@ var (
// Manager handles witness lifecycle and monitoring operations.
type Manager struct {
rig *rig.Rig
workDir string
handoffState *WitnessHandoffState // Cached handoff state for persistence across burns
rig *rig.Rig
workDir string
}
// NewManager creates a new witness manager for a rig.
@@ -81,166 +80,6 @@ func (m *Manager) saveState(w *Witness) error {
return os.WriteFile(m.stateFile(), data, 0644)
}
// handoffBeadID returns the well-known ID for this rig's witness handoff bead.
func (m *Manager) handoffBeadID() string {
return fmt.Sprintf("gt-%s-%s", m.rig.Name, HandoffBeadID)
}
// loadHandoffState loads worker states from the handoff bead.
// If the bead doesn't exist, returns an empty state and creates the bead.
func (m *Manager) loadHandoffState() (*WitnessHandoffState, error) {
beadID := m.handoffBeadID()
// Try to read the bead
cmd := exec.Command("bd", "show", beadID, "--json")
cmd.Dir = m.workDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Bead doesn't exist - create it
if strings.Contains(stderr.String(), "not found") || strings.Contains(stderr.String(), "No issue") {
if err := m.ensureHandoffBead(); err != nil {
return nil, fmt.Errorf("creating handoff bead: %w", err)
}
return &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}, nil
}
return nil, fmt.Errorf("reading handoff bead: %s", stderr.String())
}
// Parse the bead JSON
var issues []struct {
Description string `json:"description"`
}
if err := json.Unmarshal(stdout.Bytes(), &issues); err != nil {
return nil, fmt.Errorf("parsing handoff bead: %w", err)
}
if len(issues) == 0 {
return &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}, nil
}
// The description contains our JSON state
desc := issues[0].Description
// Extract JSON from description (skip any markdown header)
state := &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}
// Try to find JSON in the description
if idx := strings.Index(desc, "{"); idx >= 0 {
jsonPart := desc[idx:]
// Find the matching closing brace
if endIdx := findMatchingBrace(jsonPart); endIdx > 0 {
jsonPart = jsonPart[:endIdx+1]
if err := json.Unmarshal([]byte(jsonPart), state); err != nil {
// If parsing fails, just return empty state
return &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}, nil
}
}
}
return state, nil
}
// findMatchingBrace finds the index of the matching closing brace.
func findMatchingBrace(s string) int {
depth := 0
inString := false
escaped := false
for i, c := range s {
if escaped {
escaped = false
continue
}
if c == '\\' && inString {
escaped = true
continue
}
if c == '"' {
inString = !inString
continue
}
if inString {
continue
}
if c == '{' {
depth++
} else if c == '}' {
depth--
if depth == 0 {
return i
}
}
}
return -1
}
// saveHandoffState persists worker states to the handoff bead.
func (m *Manager) saveHandoffState(state *WitnessHandoffState) error {
beadID := m.handoffBeadID()
// Serialize state to JSON
stateJSON, err := json.MarshalIndent(state, "", " ")
if err != nil {
return fmt.Errorf("serializing state: %w", err)
}
// Update the bead's description with the JSON state
desc := fmt.Sprintf("Witness handoff state for %s.\n\n```json\n%s\n```", m.rig.Name, string(stateJSON))
cmd := exec.Command("bd", "update", beadID, "--description", desc)
cmd.Dir = m.workDir
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("updating handoff bead: %s", strings.TrimSpace(string(out)))
}
return nil
}
// ensureHandoffBead creates the handoff bead if it doesn't exist.
func (m *Manager) ensureHandoffBead() error {
beadID := m.handoffBeadID()
title := fmt.Sprintf("Witness handoff state (%s)", m.rig.Name)
desc := fmt.Sprintf("Witness handoff state for %s.\n\n```json\n{\"worker_states\": {}, \"last_patrol\": null}\n```", m.rig.Name)
// Create pinned handoff bead with specific ID
cmd := exec.Command("bd", "create",
"--id", beadID,
"--title", title,
"--type", "task",
"--priority", "4", // Low priority - just state storage
"--description", desc,
)
cmd.Dir = m.workDir
if out, err := cmd.CombinedOutput(); err != nil {
// If it already exists, that's fine
if strings.Contains(string(out), "already exists") {
return nil
}
return fmt.Errorf("creating handoff bead: %s", strings.TrimSpace(string(out)))
}
// Pin the bead so it survives cleanup
cmd = exec.Command("bd", "update", beadID, "--pinned")
cmd.Dir = m.workDir
_ = cmd.Run() // Best effort - pinning might not be supported
return nil
}
// Status returns the current witness status.
func (m *Manager) Status() (*Witness, error) {
w, err := m.loadState()
@@ -326,22 +165,6 @@ func (m *Manager) run(w *Witness) error {
fmt.Println("Witness running...")
fmt.Println("Press Ctrl+C to stop")
// Load handoff state from persistent bead (survives wisp burns)
handoffState, err := m.loadHandoffState()
if err != nil {
fmt.Printf("Warning: could not load handoff state: %v\n", err)
handoffState = &WitnessHandoffState{
WorkerStates: make(map[string]WorkerState),
}
}
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)
@@ -354,211 +177,6 @@ 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
}
// 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
}
// closePolecatArm closes the mol-polecat-arm tracking issue for a polecat.
// Called when the polecat is cleaned up (completed, killed, etc.).
func (m *Manager) closePolecatArm(polecatName, reason string) error {
if m.handoffState == nil {
return nil
}
ws, ok := m.handoffState.WorkerStates[polecatName]
if !ok || ws.ArmID == "" {
return nil // No arm to close
}
// Close the arm issue
cmd := exec.Command("bd", "close", ws.ArmID, "--reason", reason)
cmd.Dir = m.workDir
if out, err := cmd.CombinedOutput(); err != nil {
// If already closed, that's fine
if !strings.Contains(string(out), "already closed") {
return fmt.Errorf("closing arm %s: %s", ws.ArmID, string(out))
}
}
fmt.Printf(" Closed arm %s: %s\n", ws.ArmID, reason)
// Clear the arm ID from handoff state
ws.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)
}
return nil
}
// checkAndProcess performs health check, shutdown processing, and auto-spawn.
func (m *Manager) checkAndProcess(w *Witness) {
// Perform health check
@@ -582,13 +200,6 @@ func (m *Manager) checkAndProcess(w *Witness) {
fmt.Printf("Auto-spawn error: %v\n", err)
}
}
// Update last patrol time and persist handoff state
if m.handoffState != nil {
now := time.Now()
m.handoffState.LastPatrol = &now
// Note: individual nudge/activity updates already persist, so this is just for LastPatrol
}
}
// healthCheck performs a health check on all monitored polecats.
@@ -615,18 +226,13 @@ 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 {
m.handleStuckPolecat(w, p.Name)
} else if status == PolecatHealthy {
// Worker is active - update activity tracking and clear nudge count
m.updateWorkerActivity(p.Name, "")
// Worker is active - clear nudge count
m.clearNudgeCount(w, p.Name)
}
}
}
@@ -733,16 +339,8 @@ func (m *Manager) handleStuckPolecat(w *Witness, polecatName string) {
}
// getNudgeCount returns how many times a polecat has been nudged.
// Uses handoff state for persistence across wisp burns.
// Uses SpawnedIssues for tracking.
func (m *Manager) getNudgeCount(w *Witness, polecatName string) int {
// First check handoff state (persistent across burns)
if m.handoffState != nil {
if ws, ok := m.handoffState.WorkerStates[polecatName]; ok {
return ws.NudgeCount
}
}
// Fallback to legacy SpawnedIssues for backwards compatibility
count := 0
nudgeKey := "nudge:" + polecatName
for _, entry := range w.SpawnedIssues {
@@ -754,68 +352,21 @@ func (m *Manager) getNudgeCount(w *Witness, polecatName string) int {
}
// recordNudge records that a nudge was sent to a polecat.
// Updates both handoff state (persistent) and legacy SpawnedIssues.
func (m *Manager) recordNudge(w *Witness, polecatName string) {
now := time.Now()
// Update handoff state (persistent across burns)
if m.handoffState != nil {
if m.handoffState.WorkerStates == nil {
m.handoffState.WorkerStates = make(map[string]WorkerState)
}
ws := m.handoffState.WorkerStates[polecatName]
ws.NudgeCount++
ws.LastNudge = &now
m.handoffState.WorkerStates[polecatName] = ws
// Persist to handoff bead
if err := m.saveHandoffState(m.handoffState); err != nil {
fmt.Printf("Warning: failed to persist handoff state: %v\n", err)
}
}
// Also update legacy SpawnedIssues for backwards compatibility
nudgeKey := "nudge:" + polecatName
w.SpawnedIssues = append(w.SpawnedIssues, nudgeKey)
}
// clearNudgeCount clears the nudge count for a polecat (e.g., when they become active again).
func (m *Manager) clearNudgeCount(polecatName string) {
if m.handoffState != nil && m.handoffState.WorkerStates != nil {
if ws, ok := m.handoffState.WorkerStates[polecatName]; ok {
ws.NudgeCount = 0
ws.LastNudge = nil
now := time.Now()
ws.LastActive = &now
m.handoffState.WorkerStates[polecatName] = ws
// Persist to handoff bead
if err := m.saveHandoffState(m.handoffState); err != nil {
fmt.Printf("Warning: failed to persist handoff state: %v\n", err)
}
func (m *Manager) clearNudgeCount(w *Witness, polecatName string) {
nudgeKey := "nudge:" + polecatName
var filtered []string
for _, entry := range w.SpawnedIssues {
if entry != nudgeKey {
filtered = append(filtered, entry)
}
}
}
// updateWorkerActivity updates the last active time for a worker.
func (m *Manager) updateWorkerActivity(polecatName, issueID string) {
if m.handoffState != nil {
if m.handoffState.WorkerStates == nil {
m.handoffState.WorkerStates = make(map[string]WorkerState)
}
ws := m.handoffState.WorkerStates[polecatName]
now := time.Now()
ws.LastActive = &now
if issueID != "" {
ws.Issue = issueID
}
// Reset nudge count if worker is active
if ws.NudgeCount > 0 {
ws.NudgeCount = 0
ws.LastNudge = nil
}
m.handoffState.WorkerStates[polecatName] = ws
}
w.SpawnedIssues = filtered
}
// escalateToMayor sends an escalation message to the Mayor.
@@ -837,7 +388,7 @@ Time: %s
m.rig.Name, polecatName,
m.rig.Name, time.Now().Format(time.RFC3339))
cmd := exec.Command("bd", "mail", "send", "mayor/",
cmd := exec.Command("gt", "mail", "send", "mayor/",
"-s", subject,
"-m", body,
)
@@ -1066,9 +617,9 @@ func (m *Manager) getWitnessMessages() ([]WitnessMessage, error) {
return messages, nil
}
// ackMessage acknowledges a message (marks it as read/handled).
// ackMessage acknowledges a message (archives it as handled).
func (m *Manager) ackMessage(id string) {
cmd := exec.Command("bd", "mail", "ack", id)
cmd := exec.Command("gt", "mail", "archive", id)
cmd.Dir = m.workDir
_ = cmd.Run() // Ignore errors
}
@@ -1351,11 +902,6 @@ func (m *Manager) cleanupPolecat(polecatName string) error {
fmt.Printf(" Warning: failed to delete branch: %v\n", err)
}
// 5. Close the tracking arm (if it exists)
if err := m.closePolecatArm(polecatName, "polecat cleaned up"); err != nil {
fmt.Printf(" Warning: failed to close arm: %v\n", err)
}
return nil
}

View File

@@ -85,37 +85,3 @@ type WitnessStats struct {
TodayNudges int `json:"today_nudges"`
}
// WorkerState tracks the state of a single worker (polecat) across wisp burns.
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"`
// LastNudge is when the worker was last nudged.
LastNudge *time.Time `json:"last_nudge,omitempty"`
// LastActive is when the worker was last seen active.
LastActive *time.Time `json:"last_active,omitempty"`
}
// WitnessHandoffState tracks all worker states across wisp burns.
// This is persisted in a pinned handoff bead that survives wisp burns.
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"`
}
// HandoffBeadID is the well-known ID suffix for the witness handoff bead.
// The full ID is constructed as "<rig>-witness-state" (e.g., "gastown-witness-state").
const HandoffBeadID = "witness-state"