Skip to main content
result.object is typed as z.output<typeof schema>, so zod defaults and transforms apply. result.text keeps the raw JSON text the model wrote. The schema can come from zod 3 or zod 4: it is rendered with z.toJSONSchema for zod 4 and with the ai SDK’s converter for zod 3, and validated with the schema’s own safeParse either way. output is typed as a Standard Schema, the same as defineTool({ input }), so result.object is inferred without a cast from a zod 3 schema, a zod 4 schema (zod/v4 on zod 3.25, or zod 4 itself) or any other Standard Schema that can produce JSON Schema:

Sessions

agent.session() on an agent with output returns a session whose send() result (and stream()’s result) carries the typed object of each turn. There is no per-call output override: the schema is the agent’s.

Sub-agents

A sub-agent created with its own output schema answers the lead with its validated object. The task tool result (and agent_await’s result for a background task) is the object as JSON, then a blank line and the usual [sub-agent '<name>': ... taskId '<id>'] footer, so the lead model sees the object rather than a prose rendering. If the sub-agent’s reply is still invalid after its repair step, the task call fails with the structured tool error (kind: 'execution', a message naming the schema issues) and the lead can retry or adapt. A sub-agent does not inherit the lead’s output: each agent’s output is its own, and a sub-agent without output returns text as before. A remoteAgent() whose deployed agent has an output schema returns the remote object the same way (JSON, then its footer), read from the object of the remote stream’s run.done event.

How it works

  1. The system prompt gets an ## Output format section asking for the final answer as only a JSON object matching the schema, rendered as JSON Schema.
  2. Every model call carries responseFormat: { type: 'json', schema } on GenerateOptions. It is a hint: the built-in ai-SDK providers turn on the model’s JSON mode (with the schema, for models that support structured outputs); a custom provider may use it or ignore it.
  3. When the model replies without tool calls, the reply is parsed as JSON (a ```json code fence around it is tolerated) and validated with the schema.
  4. If it is invalid, the model gets one repair step: a user message starting with [output-invalid] that lists the issues, for example 1 issue (tempC: Expected number, received string). The repair step counts against maxSteps, and there is none when the budget is spent.
  5. Still invalid, the run resolves (it does not reject) with finishReason: 'output-invalid', no object, and outputError: { message, issues: [{ path, message }] }.

Streaming

agent.stream() works the same way: run.result resolves with object, and the final run.done event carries object (JSON-encoded) when the reply was valid. The repair step shows up as one more step.start / step.done pair. See Streaming.

Without createAgent()

AgentExecutor.execute() and AgentExecutor.stream() take the same output option. There result.object is unknown; parse it again with your schema, or use createAgent() for the inferred type.

Testing

With mockModel, script the JSON text the model would write: