Agent SDK · Running agents

Structured output.

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

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.

TypeScript
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:

TypeScript
{ 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.

TypeScript
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:

Fieldtypedescription
objectTThe 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):

TypeScript
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:

TypeScript
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.

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.