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:
2026-07-08 10:28:53 +04:00
parent d6bada6f10
commit f51a8dada4
21 changed files with 325 additions and 29 deletions
+21 -2
View File
@@ -60,6 +60,7 @@ const (
TypeHealthChecks = "health.checks"
TypeFileList = "file.list"
TypeGrantList = "grant.list"
TypeReviewFindings = "review.findings"
)
// ServerMessage is a flat decode of every server->client variant. Field names
@@ -82,7 +83,9 @@ type ServerMessage struct {
LastResponse string `json:"lastResponse"` // session_snapshot: last stage response (reopen)
Content string `json:"content"`
Diff *string `json:"diff"`
Tier string `json:"tier"`
// tool.completed: file/dir path(s) the tool read, wrote, or listed.
AffectedEntities []string `json:"affectedEntities"`
Tier string `json:"tier"`
Message string `json:"message"`
RequestID string `json:"requestId"`
ArtifactID string `json:"artifactId"`
@@ -136,6 +139,11 @@ type ServerMessage struct {
AssessedIssues []AssessedIssueDto `json:"issues"`
Artifacts []ArtifactDto `json:"artifacts"`
// review.findings
ReviewVerdict string `json:"verdict"`
ReviewFindings []ReviewFindingDto `json:"findings"`
ReviewBlocked bool `json:"blocked"`
// config.snapshot
ConfigFields []ConfigFieldDto `json:"fields"`
ConfigRestartRequired []string `json:"restartRequired"`
@@ -331,6 +339,17 @@ type AssessedIssueDto struct {
Severity string `json:"severity"`
}
// ReviewFindingDto is a PR-comment-style finding from the semantic reviewer (Gate 3).
type ReviewFindingDto struct {
Severity string `json:"severity"`
Confidence float64 `json:"confidence"`
Category string `json:"category"`
Target string `json:"target"`
Message string `json:"message"`
SuggestedFix *string `json:"suggestedFix"`
Correctness bool `json:"correctness"`
}
type ProviderHealthDto struct {
ProviderID string `json:"providerId"`
Status string `json:"status"`
@@ -396,7 +415,7 @@ func (m ServerMessage) IsEventBearing() bool {
TypeWorkflowProposed,
TypeWorkspaceBound, TypeArtifactCreated,
TypeArtifactValid, TypePlanLocked,
TypeRouterNarration:
TypeRouterNarration, TypeReviewFindings:
return true
default:
return false