One contract across the TypeScript and Python packages: every failure the
framework raises is an AgentError carrying a stable code and a
retryable verdict. Nothing else about the error changes — class, shape,
and message stay untouched, so except/catch code keeps working.
retryable=False by default: the framework only promises a retry when it
says so. Transient classes (network, 429/5xx, timeouts) are marked
retryable at the raise site.
The verdict is honored by everything that retries: middleware, remote
SPARQL, A2A/MCP clients' built-in retry loops.
stampRetryable(err,false);// mark fatal; shape/type untouchedstampRetryable(err,true);// mark retryableconstverdict=getRetryable(err);// true | false, or undefined when never marked.// AgentError maps to its own retryable flag.
Python:
stamp_retryable(err,False)stamp_retryable(err,True)verdict=get_retryable(err)# explicit stamp > AgentError field > None (unknown)
In both languages the mark rides a non-invasive channel (a well-known symbol
in JS, an attribute in Python) that never changes the error's type or JSON
shape. Non-AgentError exceptions are "unknown" until stamped.
getRetryable(err) === false (or retryable=False on an AgentError)
is NEVER retried — not even with onFailure: "continue", and no
retryOn filter can override it. Budgets (BUDGET_EXCEEDED,
BUDGET_NO_PRICE), tool-call limits, policy denials and structural
validation failures are terminal by design.
Unknown verdicts retry only when a retry mechanism's own filter says so.
Control errors always pass through untouched. Retry middleware in both
languages re-raise AgentError and the HITL interrupt regardless of
onFailure: degrading SWARM_HANDOFF (swarm routing!) or a human-suspension
into a feedback string would break swarm handoffs and HITL resume.
HitlInterrupt / HitlInterrupt (TS/Python) is not an AgentError but
rides the same guarantee: pass-through, never retried, never swallowed.
Codes are identical in both languages unless noted. "Raised by" lists the
module(s) that produce the code. Retryable defaults are as constructed at the
raise sites — a few sites raise the same code with a computed verdict
(noted).
HTTP/transport failure, truncation with zero usable content, reasoning-split loss. Site-dependent verdict: 429/5xx/network -> true; length truncation with zero content and thought-past-budget guards are false (raise maxTokens)
Router model produced an invalid/unparsable agent pick (or has no model)
false
orchestration router
SUBAGENT_UNKNOWN (TS only)
task tool asked for an unknown subagent. Python returns model-visible feedback instead
false
TS subagent middleware
SWARM_EMPTY
Swarm constructed with no agents
false
Swarm
SWARM_UNKNOWN_ENTRY
entryPoint name not in the roster
false
Swarm
SWARM_HANDOFF
Control flow, not an error for the caller: a member called handoff_to_agent; carries the HandoffSignal (to/message/context) for the swarm to route. Never retried; always passes through
false (pinned)
Swarm handoff tool
SWARM_INCOMPLETE (Python only)
Python-only guard: stream drained without a result
false
Swarm.run (Python)
RUN_INCOMPLETE (Python only)
Async-generator streams cannot return values; raised when a stream/resume drained without a result
edit_file oldString not found. Python returns feedback
false
TS edit_file
REMOTE_SPARQL
SPARQL request failed: 429/5xx and network errors retry internally (honoring Retry-After); non-JSON SELECT results are false
true except 4xx/non-JSON
remote SPARQL adapter
Both source trees were grepped for this table; the language-only rows above are
the complete set of asymmetries (they match the deliberate deviations on the
parity page).
// TypeScript — RetryMiddlewareOptions retryOn/onFailure; verdict wins over filterstoolRetryMiddleware({retryOn:[TimeoutError],onFailure:"continue"});
# Python — same semantics via middleware.tool_retry / model_retrytool_retry(retry_on=[TimeoutError],on_failure="continue")
get_retryable(err) is False -> no retry, error re-raised, ALWAYS —
even when onFailure: "continue" / on_failure="continue" is set.
HitlInterrupt and SWARM_HANDOFF always pass through untouched: the
retry layers re-raise them before any onFailure handling, so HITL
suspension and swarm routing keep working under every middleware stack.
onFailure: "continue" degrades genuine unexpected failures into
feedback text (tool message / thin completion) so the run can end
gracefully; a callable synthesizes the text (a None return is coerced to a
non-None fallback); "error" (default) re-raises.
Backoff knobs (max_retries/initial_delay_ms/backoff_factor/
max_delay_ms/jitter) are honored in both languages — exponential
backoff with jitter (Python defaults max_retries to 3, TS to 2).