Fix polecat auto-start by triggering pending spawns in daemon heartbeat (gt-iu23)
The daemon now calls triggerPendingSpawns() on each heartbeat, which: - Checks for POLECAT_STARTED messages in Deacon inbox - Polls pending spawns with WaitForClaudeReady (2s timeout) - Nudges ready polecats with Begin message - Prunes stale pending spawns (>5 min old) This ensures polecats get triggered even when Deacon is not in an active patrol cycle. Uses bootstrap mode regex detection which is acceptable for daemon operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/polecat"
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
)
|
||||
|
||||
@@ -119,7 +120,12 @@ func (d *Daemon) heartbeat(state *State) {
|
||||
// 2. Send heartbeat to Deacon (simple notification, no decision-making)
|
||||
d.pokeDeacon()
|
||||
|
||||
// 3. Process lifecycle requests
|
||||
// 3. Trigger pending polecat spawns (bootstrap mode - ZFC violation acceptable)
|
||||
// This ensures polecats get nudged even when Deacon isn't in a patrol cycle.
|
||||
// Uses regex-based WaitForClaudeReady, which is acceptable for daemon bootstrap.
|
||||
d.triggerPendingSpawns()
|
||||
|
||||
// 4. Process lifecycle requests
|
||||
d.processLifecycleRequests()
|
||||
|
||||
// Update state
|
||||
@@ -258,6 +264,55 @@ func (d *Daemon) pokeDeacon() {
|
||||
// The Deacon molecule is responsible for monitoring Mayor and Witnesses.
|
||||
// The daemon only ensures Deacon is running and sends it heartbeats.
|
||||
|
||||
// triggerPendingSpawns polls pending polecat spawns and triggers those that are ready.
|
||||
// This is bootstrap mode - uses regex-based WaitForClaudeReady which is acceptable
|
||||
// for daemon operations when no AI agent is guaranteed to be running.
|
||||
// The timeout is short (2s) to avoid blocking the heartbeat.
|
||||
func (d *Daemon) triggerPendingSpawns() {
|
||||
const triggerTimeout = 2 * time.Second
|
||||
|
||||
// Check for pending spawns (from POLECAT_STARTED messages in Deacon inbox)
|
||||
pending, err := polecat.CheckInboxForSpawns(d.config.TownRoot)
|
||||
if err != nil {
|
||||
d.logger.Printf("Error checking pending spawns: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pending) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
d.logger.Printf("Found %d pending spawn(s), attempting to trigger...", len(pending))
|
||||
|
||||
// Trigger pending spawns (uses WaitForClaudeReady with short timeout)
|
||||
results, err := polecat.TriggerPendingSpawns(d.config.TownRoot, triggerTimeout)
|
||||
if err != nil {
|
||||
d.logger.Printf("Error triggering spawns: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Log results
|
||||
triggered := 0
|
||||
for _, r := range results {
|
||||
if r.Triggered {
|
||||
triggered++
|
||||
d.logger.Printf("Triggered polecat: %s/%s", r.Spawn.Rig, r.Spawn.Polecat)
|
||||
} else if r.Error != nil {
|
||||
d.logger.Printf("Error triggering %s: %v", r.Spawn.Session, r.Error)
|
||||
}
|
||||
}
|
||||
|
||||
if triggered > 0 {
|
||||
d.logger.Printf("Triggered %d/%d pending spawn(s)", triggered, len(pending))
|
||||
}
|
||||
|
||||
// Prune stale pending spawns (older than 5 minutes - likely dead sessions)
|
||||
pruned, _ := polecat.PruneStalePending(d.config.TownRoot, 5*time.Minute)
|
||||
if pruned > 0 {
|
||||
d.logger.Printf("Pruned %d stale pending spawn(s)", pruned)
|
||||
}
|
||||
}
|
||||
|
||||
// processLifecycleRequests checks for and processes lifecycle requests.
|
||||
func (d *Daemon) processLifecycleRequests() {
|
||||
d.ProcessLifecycleRequests()
|
||||
|
||||
Reference in New Issue
Block a user