Sometimes you don’t want a paragraph; you want JSON that fits a specific shape so your downstream code can use it directly. This page covers two approaches the Engine supports and when each is the right call.Documentation Index
Fetch the complete documentation index at: https://internal.september.wtf/llms.txt
Use this file to discover all available pages before exploring further.
Two approaches
1. Provider-native structured outputs
OpenAI and Anthropic both support a “give me JSON that matches this schema” mode at the API level. The Engine surfaces this through a request flag (when running on a provider that supports it) and the provider enforces the schema during decoding. The output is guaranteed to match the schema — no malformed JSON, no missing required fields. This is the right answer when:- You have a fixed schema you control.
- The schema is meaningful to the model (good field names, descriptions).
- You’re on a provider that supports it for the model you’re using.
2. Tool-as-extractor
Define a tool whose only purpose is to return the structured data. The model “calls” the tool with the structured JSON as the input; you read the JSON from thetool_call.input rather than executing anything:
submit_summary with the result. Do not write the summary as
text.” When you see tool_call.input for submit_summary, that’s your
structured output.
This works on every provider. It also gives you context — the model can
write some preamble (which you can ignore or surface as “thinking”) and
then commit to the structured output as a “submit.”
Pick the right one
| Provider-native | Tool-as-extractor | |
|---|---|---|
| Provider support | OpenAI, Anthropic (varies by model) | All |
| Schema enforced | Yes, at decode | At validation, not decode |
| Mid-task usage | Whole turn is structured | Can interleave with text |
| Streaming behavior | Field-by-field as decoded | Comes through tool_call |
| Best for | Pure extractor tasks | Multi-step tasks ending in structure |
Schema design
Whichever approach you pick, schema quality affects output quality.Use descriptive field names
Add descriptions
The model uses field descriptions like documentation. They guide the fill:Use enums for closed sets
If a field has 3–5 valid values, useenum:
enum, you’ll see
“Medium” vs. “medium” vs. “MEDIUM” inconsistently.
Mark required fields
Optional fields that the model thinks are unnecessary will be omitted. Required fields force the model to fill them. Userequired to express
your contract.
Avoid deeply nested structures
Models do well with one or two levels of nesting. Three levels deep hurts accuracy. If your data is naturally hierarchical, consider:- A flat structure with parent IDs.
- Separate calls for nested levels.
- Two passes: first the outline, then the details.
Validation
The Engine does NOT silently accept invalid JSON. If you use provider-native structured outputs, the provider guarantees validity. If you use tool-as-extractor, the Engine validates the input against the schema before emitting thetool_call event. Invalid input becomes a
tool_error and feeds back into context — the model can correct and
retry.
For your application code, treat the JSON as already validated against
the schema, but still validate against your business rules. The
schema can’t enforce “the timestamp must be in the future” or “the
user_id must exist.”
Pattern: extractor-with-confidence
Add aconfidence field to your schema:
Pattern: parsed-then-acted
For tasks where the structure is the input to a subsequent action, run the agent in two turns:- Turn 1: agent calls
submit_summarywith structured JSON. - Your code reads the JSON, performs the action.
- Turn 2: agent gets a
tool_resultdescribing what you did, replies with confirmation text to the user.
Pitfalls
- Schema drift. Updating the schema without updating the agent’s system prompt or tool description leads to garbage output. Treat schemas like APIs — version and migrate.
- Free-form fields in structured outputs. A
stringfield with no description ends up holding everything from a code snippet to a paragraph of explanation. Constrain with description and length hints. - Enums that grow. If you keep adding to an enum, the model gets confused as the list lengthens. At some point, you want a classifier-as-tool instead.
- Streaming partial JSON. Provider-native structured outputs stream the JSON token-by-token. You can’t parse it until it’s complete unless you use a streaming JSON parser. Tool-as-extractor sends the full input in one event.
See also
- Tool use — for the tool-as-extractor pattern.
- Evaluation — how to measure structured-output accuracy.
- Build a research agent — uses structured outputs for the final report.

