Agent failure kinds
For: plugin authors.
Interpret typed failures and their retry behaviour.
Failure kinds
A job that fails says how, in a word a caller can branch on: its failure kind, with three flags that say what the caller may do. An agent declares the kinds of its own work under failures:; every other failure gets one of the platform's kinds, so a caller always sees one.
tools: [github.get_issue, oz.fail]
failures:
- kind: not_found
description: The issue does not exist
person: true # a person must look before anything else happens
- kind: tracker_busy
description: The tracker answered 429 or 503
retryable: true # a caller may run the job again unchanged
| Field | Means |
|---|---|
kind | The name: lowercase letters, digits and _, starting with a letter, at most 40; unique within the agent, and never one of the platform's kinds below |
description | What it means, for the person or program that reads it; required, at most 200 characters |
retryable | A caller may run the job again unchanged and expect it may pass. False when left out |
partial | The job may have done part of its work before it failed: some writes may have happened. False when left out |
person | A person must look before anything else happens. False when left out |
An agent declares at most 32 kinds. A flow declares none: its steps fail with their agents' kinds.
The agent ends a job with one by calling oz.fail(kind, message), which it lists under tools:; the message is its own words, at most 2,000 characters. A kind it does not declare ends the job as undeclared. Pydantic AI and OpenAI Agents SDK agents call it as a tool; an ACP agent finds it on its oz0 MCP server, and a JSON-lines harness gets it among its built-ins. plugin lint warns about failures: without oz.fail in tools:, and the other way round.
The platform's own kinds, with their flags:
| Kind | Retryable | Partial | Person | When |
|---|---|---|---|---|
timeout | yes | yes | The job, a tool call or a question ran out of time | |
budget | yes | yes | A budget or quota was spent, or a person must raise it | |
limit | yes | The job reached one of its own limits: steps, tokens, the tree's depth or children, or a loop | ||
outcome_not_met | yes | The answer did not pass its outcome check | ||
best_of_no_pick | yes | No candidate of a best-of was good enough to keep | ||
unknown_outcome | yes | yes | A write may or may not have happened, and a person said to stop (unknown outcomes) | |
denied | yes | A person or a hook said no to a step | ||
refused | yes | yes | The platform refused what the job needed: a grant, a policy, a sandbox or the data's rules | |
invalid_input | The input did not match the input contract | |||
unavailable | yes | yes | Something the job needs was not there: its agent, a tool, a harness or a node | |
model | yes | yes | The model failed, refused the request or answered in a way the job cannot use | |
tool_failed | yes | A tool call the job could not go on without failed | ||
cancelled | yes | Someone or a kill switch stopped the job | ||
undeclared | yes | The agent called oz.fail with a kind it does not declare | ||
error | yes | yes | Something failed that has no kind of its own |
Where the kind shows:
The runtime API
A failed job's failure holds kind, retryable, partial, person, message and declared, beside its error text, which stays as it was for older clients. What a failure said is the job's content, whatever its kind: a tool's error repeats its input as readily as an agent's words do. A reader the job's content is closed to (no content permission, a wall, someone else's private chat) gets the kind and flags with no message, and an error that names the kind in a fixed sentence; only the platform's own canceled, terminated and timed out stay.
The same holds for a step's error in a trace, a retry's reason in GetJobWait and a turn's error in a chat. A stream's child_ended carries a child's kind and flags, never its message. The OpenAPI document lists each agent's kinds under x-oz0-failures, and the catalog under failures.
A delegating agent
When a child job fails, the parent's model reads the failure as the tool's answer (failed, the job, the kind and flags, declared, and the message with whose words it is) and goes on, on every path: oz.delegate, the built-in Ask agent and a flow tool. A quarantine child gives only the kind, the flags and the error's type, never words. A child's words that came without its labels taint the parent, as a failed child's always did. A cancelled or terminated child depends on why.
When the parent itself is cancelled, the cancel reaches its running child, and the child's cancellation ends the parent too: the parent never goes on from it. A child that someone cancelled or terminated on its own, while the parent was not asked to stop, is an answer with the kind cancelled for the Ask agent, a flow tool and oz.quarantine, as it always was; with oz.delegate it fails the parent, as it always did.
- A flow: a later step may read
steps.<id>.failure(error branches). - Findings: a failed job's summary keeps its kind (
oz0 job search "finding:failure_not_found"), its step and its attempts, never its words, since summaries outlive the job and feed comparisons and insights that do not read its labels, and an agent's scorecard counts failed runs by kind, in all and per version (oz0 insights agent <agent>).