# Structured output

> Constrain an agent turn to a zod schema with runObject and streamObject.

Source: https://maincode.com/docs/agent-sdk-structured-output
Section: Agent SDK · Matilda documentation

---

Structured output constrains the agent's response to a JSON Schema, server-side (grammar-constrained decoding), and validates it client-side against your zod schema. Pass a zod schema, receive a fully-typed object — no prompt engineering, no brittle JSON extraction.

## `runner.streamObject(agent, input, schema, options?)`

Streams exactly like `runner.stream()` — you receive every `AgentRunEvent` (including tool-loop and usage events) — plus one final event with the parsed, schema-validated object. Options are `AgentRunOptions`.

```ts
import { z } from 'zod';

const review = z.object({
  summary: z.string(),
  issues: z.array(z.object({
    severity: z.enum(['low', 'medium', 'high']),
    description: z.string(),
  })),
});

const reviewer = new Agent({
  name: 'reviewer',
  instructions: 'Review the code the user provides.',
});

for await (const event of runner.streamObject(reviewer, 'Review this function: ...', review)) {
  if (event.type === 'message.delta') process.stdout.write(event.delta);
  if (event.type === 'object') {
    console.log('\nValidated:', event.object); // typed as z.infer<typeof review>
  }
}
```

The final event:

```ts
{ type: 'object'; object: T } // T = z.infer<typeof schema>
```

## `runner.runObject(agent, input, schema, options?)`

Non-streaming convenience. Like `runner.run()`, it honours `callbacks`, `throwOnStreamError`, retries, and the tool loop — and returns an `AgentObjectResult<T>`: the full `AgentRunResult` plus the validated `object`.

```ts
const result = await runner.runObject(
  extractor,
  'Invoice total $1,250.00 AUD due 30 Sep.',
  z.object({ total: z.number(), currency: z.string() }),
);

console.log(result.object.total);    // 1250 (number)
console.log(result.object.currency); // "AUD" (string)
console.log(result.finalOutput);     // raw JSON text as returned
console.log(result.usage);           // token usage, as usual
```

### `AgentObjectResult<T>`

Extends `AgentRunResult` with one additional field:

| Field | Type | Description |
| - | - | - |
| `object` | `T` | The response text parsed as JSON and validated against your schema. |

## Convenience functions

Default-runner-backed, like the other top-level helpers (`streamObject` / `runObject` use the default `MatildaCore` singleton):

```ts
import { streamObject, runObject } from '@maincode-ai/matilda-agent-sdk';

const result = await runObject(agent, input, schema, options);
for await (const event of streamObject(agent, input, schema, options)) { /* ... */ }
```

## Raw JSON Schema via `responseSchema`

`AgentRunOptions` (and therefore `SessionOptions`) accepts a stringified JSON Schema directly on any run or stream:

```ts
const result = await runner.run(agent, 'List three Australian birds.', {
  responseSchema: JSON.stringify({
    type: 'object',
    properties: { birds: { type: 'array', items: { type: 'string' } } },
    required: ['birds'],
    additionalProperties: false,
  }),
});
JSON.parse(result.finalOutput); // guaranteed valid, schema-conforming JSON
```

With `responseSchema` set, `finalOutput` is guaranteed to be valid JSON conforming to the schema — but parsing and validation are up to you.

> **Caution** — **Safety replace and structured output.** Like `runText()` / `streamText()`, the object helpers throw `SafetyReplaceError` when the server replaces the output mid-stream — the replacement text is in `.message` and the triggering categories in `.categories`. Token deltas already yielded to your consumer are not rolled back; `runObject()` is unaffected at the value level, since it throws before returning a result.

> **Note** — **Truncation throws.** If the stream is truncated before the JSON completes, both helpers throw `MatildaObjectParseError` with the partial text in `.raw`. See [Error handling](https://maincode.com/docs/agent-sdk-error-handling).

> **Note** — **Stream errors throw.** If the server emits an `error` event mid-stream, `streamObject()` throws an `Error` with the server's error code and message, and `runObject()` throws `MatildaAgentStreamError` with the partial `result` attached — matching the behaviour of the text helpers.
