> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured outputs

> Constrain a session's final result to a JSON Schema and read it back as typed data — no transcript scraping.

An agent's durable outputs are the [files](/docs/capabilities/files) it published and its [event history](/docs/concepts/events-and-streaming). That is everything a human needs, but a machine consumer wants one more thing: a **typed result** it can read without replaying a transcript. A **structured output** is exactly that — you attach a JSON Schema, and when the session goes [idle](/docs/concepts/sessions#lifecycle) the harness produces a `structured_output` object that conforms to it, sitting right on the session next to the files and events.

This is the contract that turns a deployment or a delegated sub-task from "read the transcript and hope" into a plain typed hand-off.

## Set an output schema

An `output_schema` is a standard [JSON Schema](https://json-schema.org). Set it on the **agent** to make it the default for every session, or on a **session** to override the default for one run. The session-level schema always wins.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session create --agent Refunder \
    --message "Refund order #4821 if it qualifies." \
    --output-schema ./schemas/refund-decision.json
  ```

  ```typescript TypeScript theme={"system"}
  const session = await vetta.sessions.create({
    agent: agent.id,
    message: "Refund order #4821 if it qualifies.",
    outputSchema: {
      type: "object",
      properties: {
        order_id: { type: "string" },
        refunded: { type: "boolean" },
        amount_micro_usd: { type: "integer" },
        reason: { type: "string" },
      },
      required: ["order_id", "refunded"],
    },
  });
  ```
</CodeGroup>

Set it once on the agent to bake the shape into every run:

```typescript TypeScript theme={"system"}
const agent = await vetta.agents.create({
  name: "Refunder",
  model: "zai-org/GLM-5.2-FP8",
  harness: "pi",
  budget: { capUsd: 50, maxTaskUsd: 5, period: "month" },
  outputSchema: refundDecisionSchema,   // agent-level default
});
```

## Reading the result

When a session with an `output_schema` reaches idle, its `structured_output` field is populated with an object that satisfies the schema. When no schema is set, `structured_output` is `null` and you fall back to files and events as before.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session get $SID    # includes structured_output when a schema was set
  ```

  ```typescript TypeScript theme={"system"}
  const s = await vetta.sessions.get(sessionId);
  if (s.structured_output) {
    const { order_id, refunded, amount_micro_usd } = s.structured_output;
    await recordRefund(order_id, refunded, amount_micro_usd);
  }
  ```
</CodeGroup>

The same object rides on the `session.idle` [webhook](/docs/capabilities/webhooks) payload, so a push consumer never has to make a follow-up read:

```json theme={"system"}
{
  "id": "evt_7h...",
  "type": "session.idle",
  "created_at": "2026-08-20T09:00:12Z",
  "data": {
    "session_id": "ses_4a...",
    "stop_reason": "end_turn",
    "structured_output": {
      "order_id": "4821",
      "refunded": true,
      "amount_micro_usd": 4200000,
      "reason": "Within 30-day policy window."
    }
  }
}
```

## Requiring conformance

By default a schema is best-effort: if the agent finishes without producing a conforming object, the session still goes idle with `structured_output: null`. Set `structured_output_required: true` to make the schema a hard contract instead.

<ParamField path="structured_output_required" type="boolean" default="false">
  When `true`, the session must produce a schema-conforming object to finish cleanly. If it cannot, the session goes idle with `stop_reason: "error"` and a typed failure describing what was missing, rather than silently returning `null`. Use this when a downstream system will break on a missing field.
</ParamField>

<Note>
  The schema constrains only the **final** result. Intermediate turns, tool calls, and reasoning are unconstrained — the agent works however it needs to, and the harness enforces the shape only at the point the session yields control.
</Note>

## How the agent submits it

Setting an `output_schema` gives the agent one extra tool, **`submit_output`**, whose parameters are
your schema. The agent calls it once with the finished result, and those arguments become the
session's `structured_output`. Two consequences worth knowing:

* **Conformance is checked at the call.** A call that does not match the schema is rejected and
  handed back to the agent with the reason, so it can correct and try again. A non-conforming object
  never reaches `structured_output`.
* **Per-tool [permissions](/docs/capabilities/tools) do not apply to it.** `submit_output` is how the
  answer comes back, not something the agent *does*, so it is offered even to an agent whose default
  tool permission is `ask` or `deny`.

You can watch it happen on the [event stream](/docs/concepts/events-and-streaming): a `tool.started` for
`submit_output` carrying the document as its arguments, then the `session.idle` event with the same
object under `structured_output`. When `structured_output_required` was not satisfied, that
`session.idle` also carries an `error` string saying so.

## Configuration reference

<ParamField path="output_schema" type="object">
  A JSON Schema describing the session's final result. Settable on the agent (default for every session) or on the session (overrides the agent default). Omit it to leave `structured_output` as `null`.
</ParamField>

<ParamField path="structured_output_required" type="boolean" default="false">
  Require a conforming object; otherwise the session ends with `stop_reason: "error"`.
</ParamField>

<ParamField path="structured_output" type="object | null" readonly>
  Read-only on the session object. The schema-conforming result once the session is idle, or `null` if no schema was set or none was produced.
</ParamField>

<Card title="Next: observability" icon="activity" href="/docs/capabilities/observability">
  Per-session cost, usage, and OpenTelemetry export.
</Card>
