feat(witness): Implement ephemeral polecat model for immediate recycling
Updates HandlePolecatDone to auto-nuke polecats immediately after MR submission when cleanup_status=clean. This separates polecat lifecycle from MR lifecycle: - Polecat lifecycle: spawning → working → mr_submitted → nuked - MR lifecycle: created → queued → processed → merged (handled by Refinery) Key changes: - Try auto-nuke for ALL POLECAT_DONE messages regardless of MR status - If cleanup_status=clean (branch pushed), nuke immediately - If dirty state, create cleanup wisp for manual intervention - Cleanup wisps are now exception handling, not the normal flow Conflict resolution is handled by the Refinery, which creates NEW tasks for NEW polecats when merge conflicts are detected. (gt-si8rq.9) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,14 @@ type HandlerResult struct {
|
||||
// HandlePolecatDone processes a POLECAT_DONE message from a polecat.
|
||||
// For ESCALATED/DEFERRED exits (no pending MR), auto-nukes if clean.
|
||||
// For PHASE_COMPLETE exits, recycles the polecat (session ends, worktree kept).
|
||||
// For exits with pending MR, creates a cleanup wisp to wait for MERGED.
|
||||
// For COMPLETED exits with MR and clean state, auto-nukes immediately (ephemeral model).
|
||||
// For exits with pending MR but dirty state, creates cleanup wisp for manual intervention.
|
||||
//
|
||||
// Ephemeral Polecat Model:
|
||||
// Polecats are truly ephemeral - done at MR submission, recyclable immediately.
|
||||
// Once the branch is pushed (cleanup_status=clean), the polecat can be nuked.
|
||||
// The MR lifecycle continues independently in the Refinery.
|
||||
// If conflicts arise, Refinery creates a NEW conflict-resolution task for a NEW polecat.
|
||||
func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResult {
|
||||
result := &HandlerResult{
|
||||
MessageID: msg.ID,
|
||||
@@ -59,23 +66,28 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
// ESCALATED/DEFERRED exits typically have no MR pending
|
||||
hasPendingMR := payload.MRID != "" || payload.Exit == "COMPLETED"
|
||||
|
||||
if !hasPendingMR {
|
||||
// No MR pending - can auto-nuke immediately if clean
|
||||
nukeResult := AutoNukeIfClean(workDir, rigName, payload.PolecatName)
|
||||
if nukeResult.Nuked {
|
||||
result.Handled = true
|
||||
// Ephemeral model: try to auto-nuke immediately regardless of MR status
|
||||
// If cleanup_status is clean, the branch is pushed and polecat is recyclable.
|
||||
// The MR will be processed independently by the Refinery.
|
||||
nukeResult := AutoNukeIfClean(workDir, rigName, payload.PolecatName)
|
||||
if nukeResult.Nuked {
|
||||
result.Handled = true
|
||||
if hasPendingMR {
|
||||
// Ephemeral model: polecat nuked, MR continues in Refinery
|
||||
result.Action = fmt.Sprintf("auto-nuked %s (ephemeral: exit=%s, MR=%s): %s", payload.PolecatName, payload.Exit, payload.MRID, nukeResult.Reason)
|
||||
} else {
|
||||
result.Action = fmt.Sprintf("auto-nuked %s (exit=%s, no MR): %s", payload.PolecatName, payload.Exit, nukeResult.Reason)
|
||||
return result
|
||||
}
|
||||
if nukeResult.Error != nil {
|
||||
// Nuke failed - fall through to create wisp for manual cleanup
|
||||
result.Error = nukeResult.Error
|
||||
}
|
||||
// Couldn't auto-nuke (dirty state or verification failed) - create wisp for manual intervention
|
||||
return result
|
||||
}
|
||||
if nukeResult.Error != nil {
|
||||
// Nuke failed - fall through to create wisp for manual cleanup
|
||||
result.Error = nukeResult.Error
|
||||
}
|
||||
|
||||
// Create a cleanup wisp for this polecat
|
||||
// Either waiting for MR to be merged, or needs manual cleanup
|
||||
// Couldn't auto-nuke (dirty state or verification failed) - create wisp for manual intervention
|
||||
// Note: Even with pending MR, if we can't auto-nuke it means something is wrong
|
||||
// (uncommitted changes, unpushed commits, etc.) that needs attention.
|
||||
wispID, err := createCleanupWisp(workDir, payload.PolecatName, payload.IssueID, payload.Branch)
|
||||
if err != nil {
|
||||
result.Error = fmt.Errorf("creating cleanup wisp: %w", err)
|
||||
@@ -85,9 +97,9 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
result.Handled = true
|
||||
result.WispCreated = wispID
|
||||
if hasPendingMR {
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (waiting for MR %s)", wispID, payload.PolecatName, payload.MRID)
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (MR=%s, needs intervention: %s)", wispID, payload.PolecatName, payload.MRID, nukeResult.Reason)
|
||||
} else {
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (needs manual cleanup: dirty state)", wispID, payload.PolecatName)
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (needs manual cleanup: %s)", wispID, payload.PolecatName, nukeResult.Reason)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user