InitRunner

Structured Output

Structured output lets agents return validated JSON instead of free-form text. Define a JSON Schema in spec.output and the agent's response is parsed, validated, and returned as JSON that matches your schema.

This is useful for pipelines, automation, and any case where downstream code needs to consume agent output programmatically.

Quick Example

apiVersion: initrunner/v1
kind: Agent
metadata:
  name: invoice-classifier
  description: Classifies invoices and extracts structured data
spec:
  role: |
    You are an invoice classifier. Given a description of an invoice,
    extract the relevant fields and return structured JSON.
  model:
    provider: openai
    name: gpt-5-mini
    temperature: 0.0
  output:
    type: json_schema
    schema:
      type: object
      properties:
        status:
          type: string
          enum: [approved, rejected, needs_review]
        amount:
          type: number
          description: Invoice amount in USD
        vendor:
          type: string
      required: [status, amount, vendor]
initrunner run invoice-classifier.yaml -p "Acme Corp invoice for $250 for office supplies"
# → {"status": "approved", "amount": 250.0, "vendor": "Acme Corp"}

Configuration

Structured output is configured in the spec.output section:

spec:
  output:
    type: json_schema        # "text" (default) or "json_schema"
    mode: auto               # how structured output is requested (see Output Modes below)
    schema: { ... }          # inline JSON Schema (mutually exclusive with schema_file)
    schema_file: schema.json # path to external JSON Schema file
FieldTypeDefaultDescription
typestr"text"Output type. "text" for free-form text, "json_schema" for validated JSON.
modestr"auto"Strategy used to obtain structured output: auto, tool, native, prompted, or text. See Output Modes.
schemadictnullInline JSON Schema definition. Required when type is json_schema (unless schema_file is set).
schema_filestrnullPath to an external JSON Schema file. Relative paths are resolved from the role file's directory.

When type is json_schema, exactly one of schema or schema_file must be provided.

Output Modes

The mode field controls how InitRunner asks the model for structured output. Each mode maps onto one of PydanticAI's output markers. With mode: auto (the default) InitRunner passes the resolved model to PydanticAI without a marker, so PydanticAI selects the strategy from the model's structured-output profile. The explicit modes pin a single strategy regardless of which model runs the role.

ModeBehavior
autoDefault. Defers to PydanticAI, which picks the strategy from the model's profile (usually a tool call). The output type is passed without a marker wrapper, so behavior matches earlier InitRunner releases.
toolForces a tool call (PydanticAI's ToolOutput). The most widely compatible option, and it keeps the same strategy when you switch models.
nativeUses the provider's native structured-output API (NativeOutput), such as OpenAI Structured Outputs. Faster and cheaper on models that support it.
promptedDescribes the schema in the prompt and asks the model to reply with matching JSON (PromptedOutput). A fallback for providers without native or tool support.
textPlain unstructured text. Only valid with type: text. There is no structured wrapper; the output is returned as a string.

Native support varies by provider, so check Providers before pinning mode: native. Whether a model defaults to a tool call or native output under auto is also a provider and model detail.

Validation Rules

The mode and type must agree, or loading the role fails:

  • tool, native, and prompted require type: json_schema.
  • mode: text requires type: text.
  • With type: text, only auto or text are allowed.

Pinning a Mode

To request native structured output explicitly:

spec:
  output:
    type: json_schema
    mode: native
    schema:
      type: object
      properties:
        status:
          type: string
          enum: [approved, rejected, needs_review]
        amount:
          type: number
      required: [status, amount]

For reasoning models, auto is usually the right choice, since PydanticAI matches the strategy to the model. Pin an explicit mode only when you need the same behavior across different models.

Supported Types

JSON Schema TypePython TypeNotes
stringstrPlain string
string + enumLiteral[...]Constrained to listed values
numberfloatFloating-point number
integerintInteger number
booleanboolTrue/false
objectnested BaseModelRecursive: nested objects become nested models
arraylist[ItemType]Item type resolved from items schema

Schema Keywords

  • properties defines the fields of an object
  • required lists field names that must be present (non-required fields become Optional with a None default)
  • description is field-level documentation passed to the model
  • enum constrains a string field to specific values
  • items defines the element type for arrays

Nested Objects & Arrays

spec:
  output:
    type: json_schema
    schema:
      type: object
      properties:
        title:
          type: string
          description: Report title
        sections:
          type: array
          items:
            type: object
            properties:
              heading:
                type: string
              body:
                type: string
            required: [heading, body]
        metadata:
          type: object
          properties:
            author:
              type: string
            tags:
              type: array
              items:
                type: string
      required: [title, sections]

External Schema File

For larger schemas, use schema_file to reference a separate JSON file:

spec:
  output:
    type: json_schema
    schema_file: schemas/invoice.json

The file must contain a valid JSON Schema object. Relative paths are resolved from the role YAML file's directory. Absolute paths are used as-is.

{
  "type": "object",
  "properties": {
    "status": { "type": "string", "enum": ["approved", "rejected"] },
    "amount": { "type": "number" }
  },
  "required": ["status", "amount"]
}

Streaming Partials

Since v2026.4.17, structured-output roles stream progressively-validated partials. The previous hard restriction on output.type != "text" is gone, so both the sync and async streaming paths accept structured output.

  • Sync. StreamedRunResultSync.stream_output() yields partials as JSON fragments validate against the schema. An on_partial callback receives each validated partial.
  • Async. The async path accepts both on_partial and on_event. run_stream_events() yields typed AgentStreamEvent instances you can branch on.
  • API and dashboard. The dashboard emits a partial_output SSE frame for structured roles instead of falling back to non-streaming. Consumers that previously handled only token frames should add a partial_output handler to surface the intermediate shape.

Behavior is unchanged for non-structured (type: text) roles.

See also: Guardrails for enforcing resource limits on structured output agents, and Flow for wiring structured-output roles into multi-agent pipelines.

On this page