Skip to content

Reliability: retries, circuit breaker, error policy

This is the full reference for LLM/tool failure handling (retries, circuit breaker, error_policy, defaults, logging). It is a runtime topic, not a YAML kind — agent field lists stay on agent.md. Governance chain and plugins: governance.md. Infra proxy retry: infra.md.

Two layers share one taxonomy (transient / unavailable / application / fatal) so infra retry, the circuit breaker, and ingress governance never disagree about what happened.

Layer Where What it does
Infra LLM HTTP client + tool dispatch Re-issue the same call (RetryPolicy). After the budget is spent, optional circuit breaker plugin on unavailable.
Governance Ingress after _end error_policy maps the typed failure onto allow / retry / block / skip. Plugin: retry_on_error.
Lab mas-lab benchmark run Re-issue a failed scenario×item×run (execution.max_attempts, default 3). Independent of n_runs.

Application tool errors stay a TOOL_RESULT (working memory). Exhausted LLM failures stay ERROR. Unknown tool names are application / TOOL_UNKNOWN, not circuit-breaker unavailable.

Schema: control-binding.schema.yaml, retry-policy.schema.yaml. Engine helpers: mas.runtime.reliability (taxonomy, classify, infra retry). LLM HTTP POST lives in the llm_provider plugin (mas.library.standard.plugins.llm.http). Circuit breaker plugin: mas.library.standard.plugins.reliability.circuit_breaker. Logger: mas.runtime.reliability.


Defaults (bare agent, no overlay)

Seamless for ordinary chat and LLM HTTP: a dropped socket, connect-refused, or HTTP 500 is retried; a tool ValueError is shown to the model; a bad TLS setup stops the turn.

Knob Default
LLM max_attempts 4 (env MAS_LLM_HTTP_RETRIES default 3 extra retries → 4 attempts)
LLM retry_on [transient, unavailable]
LLM backoff_s / backoff_multiplier / jitter 0.5 / 2.0 / true
LLM require_idempotent false (HTTP POST of the same payload is treated as idempotent)
LLM max_backoff_s unlimited
Tools max_attempts 2
Tools retry_on [transient, unavailable]
Tools require_idempotent true (transient retries only if spec.tools[].idempotent / Tool YAML)
Circuit breaker off (omit spec.control.circuit_breaker, or set null)
Ingress (no recovery plugin) gov_ingress_profile PERMISSIVE (ALLOW engine ERROR)
error_policy.* unset until retry_on_error is installed
error_recovery_plugin unset
max_gov_retries 2
Lab run max_attempts 3 (experiment.execution.max_attempts; set 1 to disable)
spec.budget.max_llm_calls / max_tool_calls unset (no call-count ceiling)

with-hardened

pkg://mas.library.standard/overlays/with-hardened.yaml keeps the infra retry defaults and turns on governance + the breaker + call-count caps — this is the recommended overlay for production posture:

Knob Value
Egress append gov_no_undeclared_tool
Ingress append retry_on_error with error_recovery_plugin: retry_on_error
error_policy transient/unavailable → retry; application → allow; fatal → block
Circuit breaker on; failure_threshold: 5; reset_timeout_s: 30; "on": [unavailable]
spec.budget max_llm_calls: 50; max_tool_calls: 100
mas-ctl chat agent.yaml \
  -o pkg://mas.library.standard/overlays/with-hardened.yaml \
  -o pkg://mas.library.standard/overlays/observability-native.yaml

Failure classes

Class Meaning Infra default Ingress default
transient Peer answered or the socket dropped; a second identical call may work (429, 5xx including 500, timeout, reset). LLM retries; tools retry only if idempotent allow (retry on with-hardened)
unavailable Peer is not there (connect refused, DNS, missing provider, open circuit). LLM and tools retry (LLM POST is treated as idempotent) allow (retry on with-hardened)
application Call ran and returned a domain error (4xx body, tool ValueError, unknown tool name). Never retried allow (store in WM)
fatal Will not heal without a config change (TLS verify, 401/403). Never retried block

Declare a tool as down with ExplicitToolUnavailableError if the provider should trip the breaker.


Spec knobs

spec.control.retry.llm / .tools

Field Type LLM default Tools default
max_attempts integer ≥ 1 4 2
backoff_s number ≥ 0 0.5 0.5
backoff_multiplier number ≥ 1 2.0 2.0
jitter boolean true true
retry_on list of failure classes [transient, unavailable] [transient, unavailable]
require_idempotent boolean false true
max_backoff_s number ≥ 0 or omit unlimited unlimited

spec.control.retry.llm overrides infra LLMProxy.spec.proxy.retry when both are set.

spec.control.circuit_breaker

Omit the key (or set null) to leave the breaker off. Presence loads the library plugin (type: circuit_breaker, default threshold) — not a kernel op and not a governance plugin. Card: circuit-breaker.md.

Field Type Default when present
plugin string threshold
enabled boolean true
failure_threshold integer ≥ 1 5
reset_timeout_s number ≥ 0 30
"on" list of failure classes [unavailable] — quote the key in YAML; bare on is boolean true

spec.governance[].error_policy + error_recovery_plugin

YAML fields flattened from any plugin stanza. The named plugin is installed on the ingress chain; the kernel only applies GovernanceAction. Chain semantics: governance.md.

Field Values Default
error_policy.transient allow retry block skip allow
error_policy.unavailable same allow
error_policy.application same allow
error_policy.fatal same block
error_recovery_plugin plugin id unset — ingress follows gov_ingress_profile

Ingress retry re-issues the scheduled LLM or tool call up to max_gov_retries (default 2).

Environment (last resort)

Variable Effect
MAS_LLM_HTTP_RETRIES Extra LLM HTTP retries (attempts = retries + 1). Default 3.
MAS_LLM_HTTP_RETRY_BACKOFF Base backoff seconds; also disables jitter.

Do not put these in .env profiles — they apply to every process, including the controller. Prefer spec.control.retry.llm. See user-config.md.


Logging

Logger name: mas.runtime.reliability. Filter JSON logs on mas.reliability=true.

Outcome (mas.outcome) Level When
retry WARNING Infra will sleep and re-issue the same call
recovered INFO Call succeeded after at least one retry
no_retry WARNING Classified failure that the policy will not retry
exhausted ERROR Retry budget spent
circuit_open / circuit_half_open / circuit_closed WARNING / INFO Breaker state change
llm_error ERROR Engine returns ERROR after provider retries
ingress_retry / ingress_block / ingress_skip WARNING / ERROR Ingress error_policy decision

Each record also carries mas.target, mas.failure_class, mas.failure_code, mas.attempt, mas.max_attempts, mas.delay_s when they apply.

Native events.jsonl governance_decision rows for ingress include failure_class, failure_code, and retry_attempts when the engine set them. Infra re-attempts are logs only (they happen inside the HTTP client, before _end).


Composing a flavour (OpenClaw-style)

A flavour is a deployment preset, not a dump of every agent plugin. Behaviour that changes the trajectory belongs on Agent governance[] and control, usually via overlays you stack — governance.md, flavour.md.

Concern Overlay / plugin Notes
Undeclared tools with-hardened → gov_no_undeclared_tool Egress chain BLOCK
Typed retry / breaker with-hardened → retry_on_error + control.retry + circuit_breaker Infra then ingress
Native traces observability-native Sequence, not chain
Checkpoints / resume future overlay Compose; do not fold into the flavour YAML
Backtrack on application errors future error_policy.application or a dedicated plugin Mainline today is allow (WM)
HITL sample_governance stanza on the chain Flavour should not own HITL

Example

spec:
  tools:
    - name: get_metrics
      idempotent: true
  governance:
    - gov_no_undeclared_tool
    - retry_on_error:
        error_recovery_plugin: retry_on_error
        error_policy:
          transient: retry
          unavailable: retry
          application: allow
          fatal: block
  control:
    retry:
      llm:
        max_attempts: 4
        retry_on: [transient, unavailable]
      tools:
        max_attempts: 2
        retry_on: [transient, unavailable]
        require_idempotent: true
    circuit_breaker:
      failure_threshold: 5
      reset_timeout_s: 30
      "on": [unavailable]