Agent contracts

Agent failure kinds

Interpret typed failures and their retry behaviour.

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.

agents/triage.yaml
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
FieldMeans
kindThe 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
descriptionWhat it means, for the person or program that reads it; required, at most 200 characters
retryableA caller may run the job again unchanged and expect it may pass. False when left out
partialThe job may have done part of its work before it failed: some writes may have happened. False when left out
personA 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:

KindRetryablePartialPersonWhen
timeoutyesyesThe job, a tool call or a question ran out of time
budgetyesyesA budget or quota was spent, or a person must raise it
limityesThe job reached one of its own limits: steps, tokens, the tree's depth or children, or a loop
outcome_not_metyesThe answer did not pass its outcome check
best_of_no_pickyesNo candidate of a best-of was good enough to keep
unknown_outcomeyesyesA write may or may not have happened, and a person said to stop (unknown outcomes)
deniedyesA person or a hook said no to a step
refusedyesyesThe platform refused what the job needed: a grant, a policy, a sandbox or the data's rules
invalid_inputThe input did not match the input contract
unavailableyesyesSomething the job needs was not there: its agent, a tool, a harness or a node
modelyesyesThe model failed, refused the request or answered in a way the job cannot use
tool_failedyesA tool call the job could not go on without failed
cancelledyesSomeone or a kill switch stopped the job
undeclaredyesThe agent called oz.fail with a kind it does not declare
erroryesyesSomething 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>).
Copyright © 2026