Structured Output
Constrain agent responses to a JSON Schema and run policy checks on the result.
Overview
By default, an ArchAgents agent responds with free-form text. That's the right shape for conversation, support, and most chat-style interactions.
When you need the agent to produce data (a triage decision, a customer summary, a structured action plan), you want the response to conform to a known shape every time. That's what an AgentMessageSchema does. It's a JSON Schema that tells the LLM exactly which fields to produce, with which types, and which are required.
Use a structured message schema when:
- the agent's output will be consumed by other code or systems
- a downstream automation depends on specific fields being present
- you want to enforce content policies on individual fields (field guards)
- you want the response to be deterministic in shape, even if the values vary
Schemas don't replace prompts, they constrain them. Your routine still tells the agent what to do; the schema tells the platform what shape to enforce on the result.
A minimal example
kind: AgentMessageSchema
id: customer-summary
schema:
type: object
properties:
summary:
type: string
sentiment:
type: string
enum: [positive, neutral, negative]
next_action:
type: string
required: [summary, sentiment]
This config tells the platform: "When this schema is in effect, the agent must produce an object with at least summary and sentiment fields, where sentiment is one of three exact values."
Save it as a config and deploy it like any other config:
archagent describe configsamples agent_message_schema --to-file ./schemas/customer-summary.yaml
# edit the file
archagent validate configs -k agent_message_schema -f ./schemas/customer-summary.yaml
archagent deploy configs
Linking a schema to a routine
Reference the schema from an agent routine using structured_message_template_refs:
routines:
- name: customer-triage
handler_type: preset
preset_name: do_task
preset_config:
instructions: |
Read the customer's most recent message in the thread and summarize it.
Decide the sentiment and propose the next action.
structured_message_template_refs:
- "#/schemas/customer-summary"
status: active
When the routine runs, the agent generates a response that matches the schema. The platform validates the shape automatically and rejects malformed responses before they reach your other code.
You can attach more than one schema to a routine when the agent has several response modes. The agent picks the schema appropriate to what it's doing, and the platform validates against that one.
Inline vs. referenced schemas
For small, single-use schemas, declare them inline on the routine:
routines:
- name: ticket-triage
handler_type: preset
preset_name: do_task
structured_message_template_refs:
- kind: AgentMessageSchema
id: ticket-triage-output
schema:
type: object
properties:
priority:
type: string
enum: [low, medium, high, urgent]
category:
type: string
required: [priority, category]
status: active
For schemas you want to reuse across multiple agents or routines, deploy them as standalone configs and reference by ID:
structured_message_template_refs:
- ref!: "#/schemas/customer-summary"
Inline is fine for one-off shapes. Use a deployed config when the schema is a contract that more than one place depends on.
Field guards
Schemas don't only validate shape, they can also enforce content policies on the resolved field values. Add field_guards to a schema to require that fields pass content checks before the response is accepted:
kind: AgentMessageSchema
id: customer-summary
schema:
type: object
properties:
summary: { type: string }
sentiment: { type: string, enum: [positive, neutral, negative] }
required: [summary, sentiment]
field_guards:
- kind: ContainsString
fields: ["summary"]
value: "CONFIDENTIAL"
on_match: redact
- kind: LLMJudge
fields: ["summary"]
prompt: >
Does this summary mention any specific customer's full name
or financial account number?
on_match: reject
Guards can reject (block the response), redact (rewrite the field), or warn (record a violation but let it through). See Field Guards for the full guard catalogue and how to design good policies.
What the schema controls
A schema constrains the shape of the response, types, required fields, enums, nesting. It does not constrain the content unless you also attach field guards. The agent's instructions and tools still drive what the values mean; the schema makes sure those values arrive in a known structure.
For free-form fields (a string summary, a string description), the LLM still generates whatever text fits the prompt. Pair the schema with field guards when content rules matter.
Validation
Validate schemas before deploying them:
archagent validate configs -k agent_message_schema -f ./schemas/customer-summary.yaml
The validator checks:
- the schema is a valid JSON Schema
- all referenced field paths in any attached field guards exist
- guard configs are valid for their kind
The portal also validates schemas when you save them through the visual editor.
Best practices
- Make the schema as tight as the work requires. A schema with five required fields is easier to consume than one with twenty optional ones.
- Use enums for categorical fields.
priority: low | medium | highis much more useful thanpriority: stringwith conventions in the prompt. - Pair structured output with field guards when the response will be shared with humans or external systems. The schema enforces shape; guards enforce content.
- Reuse schemas across routines when the same response shape applies in more than one place. Deploy once, reference by ID.
- Test schemas with real prompts. Run the routine in a sandbox and inspect the resulting messages to confirm the agent is producing useful values, not just the right shape.
Where to go next
- Field Guards: content policies that run on schema fields.
- Agents: how routines reference schemas.
- Activity Feed: where validation failures and guard violations are recorded.
- Configs: the file-backed deploy workflow for schemas.
Have feedback?
Help us make this page even more useful.
Tell us what you'd like to see expanded, which examples would help, or what workflow you want covered next. Every message gets read.