feat(recovery): route failed write-less stages to recovery instead of futile retry
Adds the failure-ticket + recovery-routing mechanism: a deterministic gate->capability table gates whether a stage has agency to fix its own failure, opens a FailureTicketOpenedEvent when it doesn't, and routes to a metadata role=recovery stage (per-stage budget, cap 2, not reset by TransitionExecuted) instead of retrying in place. Extends salvage decisions with a RECOVER option so the review-gate judge can also route to recovery, unifies deterministic-gate and review-gate routing through routeToRecovery(), and has ExecutionPlanCompiler synthesize a write-capable recovery stage + edge for freestyle plans. Read-only tools (file_read, list_dir) are now always available on any tool-granting stage so a recovery stage can inspect the write-less stage's failure without flooding context via shell ls -R.
This commit is contained in:
@@ -105,8 +105,16 @@ data class FailureTicketOpenedEvent(
|
||||
val routeTo: StageId,
|
||||
// The gate's captured failure output (command/exit/stderr/findings) that recovery must resolve.
|
||||
val evidence: String,
|
||||
// 1-based count of times this stage has been routed to recovery (own, small route budget).
|
||||
// Count of NO-PROGRESS routes charged against this stage's recovery budget so far. A route whose
|
||||
// failure fingerprint changed from the previous route (recovery made progress — a different error
|
||||
// now) is FREE and leaves this flat; only a route that reproduces the same failure charges it. So
|
||||
// a multi-cause failure repaired one layer at a time is not penalised for progressing. Folded
|
||||
// directly by the reducer (idempotent under replay). Defaulted for backward-compatible replay of
|
||||
// pre-progress-aware tickets.
|
||||
val routeAttempt: Int,
|
||||
// Fingerprint of [evidence] (whitespace/digit-normalised, see FailureFingerprint) at route time.
|
||||
// The next route compares against this to decide progress vs. no-progress. Empty on old events.
|
||||
val fingerprint: String = "",
|
||||
) : EventPayload
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,4 +31,11 @@ data class OrchestrationState(
|
||||
// verification→recovery→verification loop must stay bounded across those transitions, exactly as
|
||||
// refinementIterations does for back-edge loops.
|
||||
val recoveryRoutes: Map<String, Int> = emptyMap(),
|
||||
// Last-seen failure fingerprint per recovering stage id — the recovery-routing analogue of
|
||||
// gateFailureFingerprints. Lets routeToRecovery tell a no-progress route (same fingerprint,
|
||||
// charged against recoveryRoutes) from a genuine-progress one (changed fingerprint, free), so a
|
||||
// recovery that fixes one cause and surfaces the next is not killed by the small route budget.
|
||||
// Like recoveryRoutes, NOT reset by TransitionExecutedEvent — it must survive the
|
||||
// verification→recovery→verification round-trip.
|
||||
val recoveryFailureFingerprints: Map<String, String> = emptyMap(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user