API

Structured output

Machine-to-machine callers usually want a typed object from an agent, not prose. Structured output lets you attach a JSON Schema to a turn: Mobius makes the agent submit its final result through a reserved tool, validates the submission against your schema on the server, and stores the validated object on the turn. You read it from one field. There is no fenced-JSON scraping, no JSON.parse, and no ambiguity about whether the model actually met your contract.

Use it when your backend consumes the result — enrichment, classification, extraction, option generation — and a malformed object would break the next step. For conversational replies your app renders as text, keep the normal turn output.

The contract is per turn, not part of the agent. You attach it on invokeAgent or on a start-turn call, so the same stored agent can run with a schema on one turn and without one on the next, and two turns of a session can declare different schemas.

Attach a schema

Add an output object with a schema to the request:

{
  "agent_ref": { "name": "lexicon-augmenter" },
  "session": { "mode": "new", "visibility": "private" },
  "input": {
    "content": [{ "type": "text", "text": "Augment: throughput" }]
  },
  "output": {
    "schema": {
      "type": "object",
      "additionalProperties": false,
      "required": ["options"],
      "properties": {
        "options": {
          "type": "array",
          "minItems": 12,
          "maxItems": 12,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["term", "definition"],
            "properties": {
              "term": { "type": "string", "minLength": 1 },
              "definition": { "type": "string", "minLength": 1 }
            }
          }
        }
      }
    }
  }
}

The schema root must be type: object. The object you get back is the value that satisfies it.

Mobius appends a reserved mobius_submit_output tool whose input schema is the schema you sent, and adds a short instruction telling the agent to call it once with its final result. The tool is present even for agents that otherwise have no tools, so a no-tool agent becomes a one-tool agent for that turn.

Read the validated object

The validated value lands on the completed turn as output. Read it from the turn record — the REST turn read, listTurns, or the streaming turn.upsert frame all carry it once the turn is completed:

{
  "id": "turn_01J9...",
  "status": "completed",
  "output": {
    "options": [{ "term": "throughput", "definition": "..." }]
  },
  "output_source": "tool"
}

output is present only on a completed turn that declared a schema. A turn with no schema, or a turn that failed, has no output.

output_source tells you where the value came from: tool when the agent submitted it through mobius_submit_output (the normal path), or text when Mobius accepted a schema-valid final message as a fallback. A steady stream of text sources is a signal that the model is not using the tool reliably.

The SDKs expose a typed reader on the folded turn record — turn.structuredOutput<T>() (TypeScript), turn.structured_output() (Python), and turn.StructuredOutput() (Go). The generic is your own type assertion; the server-side schema is the authority, and the SDK adds no second validation layer. These SDK helpers ship in a follow-up release; the REST fields (output, output_source) are available now.

Supported schema features

Mobius validates against the OpenAPI 3.0 schema subset. Author your schema to this table:

SupportedNot supported
type (root must be object)$ref / $defs (inline instead)
requiredif / then / else
enumconst (use a one-value enum)
additionalPropertiespatternProperties
minItems / maxItemsprefixItems
minLength / maxLength
pattern
numeric bounds (minimum, maximum, …)
nested objects and arrays
oneOf / anyOf / allOf
nullable

Mobius rejects an unsupported keyword at request time rather than silently ignoring it. Because the validator would otherwise skip the keyword — leaving the value it was meant to constrain unchecked — a schema that leans on one would validate more permissively than you intended. Inline any $ref/$defs, and use the supported forms (for example a one-value enum instead of const).

The schema must be self-contained and at most 32 KiB serialized. A single submitted value is capped at 256 KiB serialized; a larger submission is rejected at the tool boundary like any other validation failure.

When validation fails

A rejected submission comes back to the agent as an ordinary tool error inside the same turn, so the model can correct itself and submit again. This repair is bounded by the turn's tool-iteration budget and its timeout, and the extra tokens are billed as normal turn usage. You do not configure a retry count.

If the turn ends without a schema-valid object — the model never called the tool, or every submission was invalid — the turn fails with error_type: output_schema_unsatisfied, and error_message carries the final validation error. An invalid result can never produce a completed turn.

Rejected submissions are ordinary tool_use / tool_result rows in the turn transcript, so you can see exactly what the model tried and why it was rejected without any extra diagnostics surface.

If a turn submits its output and then suspends — waiting on an event or a long-running job — the in-memory submission is not carried across the resume. On resume Mobius falls back to accepting a schema-valid final message, so a tool-using turn that resumes with prose only can fail with output_schema_unsatisfied. Structured-output workloads are usually no-tool or tool-light and rarely suspend; if yours both uses tools and suspends, expect the text fallback to govern the resumed turn.

Size the timeout for a repair round

Because repair happens inside the turn, size timeout_seconds for at least one correction round, not just one clean generation. If a single generation takes about 11 seconds, a 20-second timeout leaves no room to recover from a first invalid submission; give the turn enough budget to submit, read the validation error, and submit again.

Example: replace a hand-rolled parser

Before, a caller collected assistant text, stripped code fences, found the first {, parsed it, and validated in application code — and still could not tell whether the model produced the wrong shape or produced the right shape that failed app-side validation.

With a schema attached, the turn either completes with a validated object or fails with output_schema_unsatisfied:

const turn = await client.invokeAgent({
  agentName: "lexicon-augmenter",
  content: [{ type: "text", text: input }],
  session: { mode: "new", visibility: "private" },
  config: { effort: "low", timeout_seconds: 45, toolkits: [], skills: [] },
  output: { schema: augmentationOutputSchema }, // e.g. minItems 12, maxItems 12
});
 
for await (const _ of turn.updates()) {
}
 
if (turn.status !== "completed") {
  throw new Error(turn.errorMessage);
}
 
const value = turn.structuredOutput<AugmentationOutput>();

The parser is gone. What remains in your app is whatever no schema can express — semantic checks like naming quality — not shape validation.