recovery

package
v1.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package recovery defines typed recovery recipes that decide what to do when a node fails. The runtime engine consults a recipe per error class to choose between retrying the same node, forcing a compaction, pausing for human intervention, or failing terminally.

The package is deliberately decoupled from the engine: a Recipe returns an Action describing what to do, leaving the actual execution (retry, compact, pause) to the caller. Hosts wire the dispatcher into the engine via runtime.WithRecoveryDispatch and recovery.Dispatch(DefaultRecipes()).

Index

Constants

View Source
const (
	// ActionRetrySameNode re-executes the failing node, optionally
	// after Delay; AttemptsLeft tracks the remaining budget.
	ActionRetrySameNode = runtime.RecoveryRetrySameNode

	// ActionCompactAndRetry asks the LLM client to drop older
	// conversation turns (when supported) and then retry. Falls back
	// to a plain retry when the executor doesn't implement Compactor.
	ActionCompactAndRetry = runtime.RecoveryCompactAndRetry

	// ActionPauseForHuman pauses the run with a synthetic
	// interaction so an operator can resolve and resume.
	ActionPauseForHuman = runtime.RecoveryPauseForHuman

	// ActionFailTerminal surfaces the error as a non-recoverable
	// failure (still produces a checkpoint via failRunWithCheckpoint).
	ActionFailTerminal = runtime.RecoveryFailTerminal
)
View Source
const DefaultMaxRetries = 3

DefaultMaxRetries is the per-class retry budget unless overridden per recipe.

Variables

This section is empty.

Functions

func Classify

func Classify(err error) runtime.ErrorCode

Classify inspects err and returns the canonical RuntimeError code for it. It recognises:

  • *runtime.RuntimeError → its declared Code
  • *delegate.ErrRateLimited → RATE_LIMITED (CLI-backend rate-limit signal raised when the assistant text matches the provider's quota wording; not an api.APIError because the CLI's wire is pre-parsed JSON, so the 429 never reaches the SDK as such)
  • *api.APIError with StatusCode 429 → RATE_LIMITED
  • *api.APIError with body containing "context_length_exceeded" or "context length" → CONTEXT_LENGTH_EXCEEDED
  • any other *api.APIError → EXECUTION_FAILED

Hosts that want richer classification can wrap or replace this function.

func DefaultRecipes

func DefaultRecipes() map[runtime.ErrorCode]Recipe

DefaultRecipes maps each well-known error code to its default recipe. Hosts can override individual entries before installing.

func Dispatch

func Dispatch(recipes map[runtime.ErrorCode]Recipe) runtime.RecoveryDispatch

Dispatch turns a recipe map into a runtime.RecoveryDispatch callback. The dispatcher classifies, asks the engine for the prior attempt count under that class, and returns the recipe's decision plus the matched code. Errors that don't classify into a wired code fall through to RecoveryFailTerminal.

Safe for concurrent use as long as the wrapped recipes are.

Types

type Action

type Action = runtime.RecoveryAction

Action is the engine-facing decision returned by a recipe.

type ActionKind

type ActionKind = runtime.RecoveryActionKind

ActionKind is an alias for the engine-facing decision kind so existing callers of the recovery package keep working after the engine wiring.

type Recipe

type Recipe interface {
	Apply(ctx context.Context, err *runtime.RuntimeError, attempts int) Action
}

Recipe decides what to do for a given error class. `attempts` is the count of prior retries for this class on this node (zero on first failure).

func AuthFailedRecipe added in v0.39.0

func AuthFailedRecipe() Recipe

AuthFailedRecipe: the model provider rejected our credentials (expired/invalid token, HTTP 401/403). Retrying the same call can never succeed — only a human re-authenticating can fix it. Pause for human (like BudgetRecipe) rather than burning the retry budget on guaranteed failures; the run is resumable once the credential is refreshed. This also keeps a dispatcher from re-dispatching the run in a tight loop (every cycle would re-spend sandbox + partial-run cost only to hit the same 401).

func BudgetRecipe

func BudgetRecipe() Recipe

BudgetRecipe: always pause for human. There is no automatic retry path for budget exhaustion — operator must extend or terminate.

func ContextLengthRecipe

func ContextLengthRecipe() Recipe

ContextLengthRecipe: compact-and-retry twice, then fail terminal (the conversation can't be made smaller).

func ExecutionFailedRecipe added in v0.39.0

func ExecutionFailedRecipe(maxRetries int) Recipe

ExecutionFailedRecipe: the catch-all bucket for unclassified node failures. Tries one retry with a short delay (covers transient subprocess crashes, flaky network blips, momentary fs races), then falls through to FailTerminal so the engine produces a failed_resumable checkpoint that the operator can /resume after fixing the root cause. Without this recipe registered, the first failure of any unclassified node short-circuits to FailTerminal with no retry attempted at all.

func NetworkTransientRecipe added in v0.39.0

func NetworkTransientRecipe(maxRetries int) Recipe

NetworkTransientRecipe: longer exponential-backoff loop for transient network failures reaching the upstream model API (ISP blip, captive portal handoff, DNS flutter, datacenter routing change). Each attempt doubles the delay up to a 60s cap; with the default 6 retries that covers ~10 min of cumulative wait — enough to ride out the kind of outages an operator would expect a long-running pipeline to recover from on its own. Beyond that we surface as failed_resumable so the operator can /resume after the network is verified back.

Why a separate recipe (not just a bigger ExecutionFailedRecipe): non-network execution failures (schema mismatch, missing fs entry, in-sandbox script crash) usually won't fix themselves on retry — burning 6 * 60s on a deterministic bug wastes operator time. Network transients DO routinely fix themselves; rewarding the right pattern matters for unattended overnight runs.

Default cap = 6 attempts, 60s max backoff. Hosts override via DefaultRecipes map mutation if they want a different shape.

func PermanentToolRecipe

func PermanentToolRecipe() Recipe

PermanentToolRecipe: no retry, immediately escalate to terminal.

func RateLimitRecipe

func RateLimitRecipe(maxRetries int) Recipe

RateLimitRecipe: exponential backoff + jitter, escalates to human pause after maxRetries (operator may rotate credentials).

func TransientToolRecipe

func TransientToolRecipe(maxRetries int) Recipe

TransientToolRecipe: retry with linear backoff up to maxRetries (model gets the error in its next turn), then fail terminal.

func UsageLimitRecipe added in v0.50.0

func UsageLimitRecipe() Recipe

UsageLimitRecipe: fail terminal immediately — a subscription/quota WINDOW (forfait 5h / session / weekly cap) cannot clear within a node's retry budget, so in-node retries only burn attempts. The run lands failed_resumable; the run-level auto-resume loop owns the reset-aware wait (pkg/cli/auto_resume.go).

type RecipeFunc

type RecipeFunc func(ctx context.Context, err *runtime.RuntimeError, attempts int) Action

func (RecipeFunc) Apply

func (f RecipeFunc) Apply(ctx context.Context, err *runtime.RuntimeError, attempts int) Action

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL