Repository navigation
Docs: write the Structured output page for kagent 1.x #518
Description
An AgentTemplate can now constrain its answer to a JSON Schema, and the 1.x doc set does not mention the feature anywhere. kagent#2890 landed the CRD fields, the translator, the runtime validation, the A2A response contract, the CLI, and the UI, so a reader can configure structured output today with nothing in the docs to tell them how.
This is new writing rather than an edit.
What the page covers
Declaring a JSON Schema for an agent's successful terminal response, and what the runtime does with a response that does not match.
The spine of the page is that the schema is a compile-time contract, not a request-time hint. The translator resolves the schema, validates it against a portable profile, and records a canonical form and digest in the immutable prepared revision. A schema the profile rejects fails compilation, so no agent runs at all, rather than failing later at inference. Lead with that and the admission rules below stop looking like a list of restrictions and start looking like one decision.
Upstream wrote the design document first. docs/architecture/structured-output.md is unusually complete and is the right starting point. It is an architecture document rather than a task guide, so it is a source, not a draft.
The configuration surface
Two mutually exclusive fields on AgentTemplateSpec, defined in go/api/v1alpha3/agenttemplate_types.go. Omitting both leaves the response unconstrained.
| Field | Meaning |
|---|---|
outputSchema |
The JSON Schema, written inline. |
outputSchemaFrom |
A name and key selecting a same-namespace ConfigMap that holds the schema as JSON. |
Admission rules.
- A CEL rule rejects a template that sets both, with the message
outputSchema and outputSchemaFrom are mutually exclusive. - The schema must have an object root.
- The portable profile supports primitive JSON types, nested objects and arrays,
properties,required,additionalProperties,items,enum,const,anyOf, titles and descriptions, and non-recursive local references through$defs. External references, recursive references, conditional schemas and tuple arrays are rejected at compile time rather than silently weakened for a provider. - The serialized schema is limited to 64 KiB, and conversion is limited to 32 levels and 1,000 expanded nodes. Nodes reached through
$defscount toward both bounds. - Only the
kagentHarness supports structured output. Thecodex,claudeandbyoHarnesses fail compilation withHarness "<name>" does not support structured output.
Behavior the API does not announce
- Two different conditions report a failure, and which one fires depends on what went wrong. An invalid schema, or a Harness that cannot run it, sets the
Compatiblecondition toFalsewith the reasonUnsupportedConfiguration. A ConfigMap that does not exist, or a key that is missing from it, setsResolvedRefstoFalsewith the reasonReferenceResolutionFailed. A reader debugging a template needs to know which condition to read. - The contract belongs to the public root agent alone. A local child agent does not inherit it, and neither does an agent tool. If a child template is compiled and invoked as a root agent in its own right, its own schema applies to that separate revision.
- There is no text fallback. A provider refusal, an incomplete generation, invalid JSON, or a validation failure fails the Task. The invalid output is not published and is not included in the error, because it may carry sensitive data, so the failure message alone will not show a reader what the model actually returned.
- The response arrives as data, not text. The runtime suppresses partial root-answer fragments, validates the complete value, and publishes one A2A
DataPartwith media typeapplication/json, carrying the canonical schema digest inkagent.dev/a2a/output-schema-sha256. The generated Agent Card advertisesapplication/jsonas its default output mode. A streaming client that wants only the answer reads the last content-bearing artifact before the Task reachesTASK_STATE_COMPLETED. - Progress updates, tool events, approval requests and input-required messages are unchanged. Structured output constrains the terminal answer, not the conversation around it.
- Tool calls work alongside it across OpenAI, Anthropic, Bedrock and Gemini. A provider or model can still refuse structured output at request time even though the schema compiled, and that failure fails the Task.
Where the file goes
docs-site/content/kagent/1.x/agents/structured-output.md sits with the other AgentTemplate behavior pages, alongside system-prompts.md. Placement is the one open question in this issue. The feature is a property of the portable template rather than of the runtime, which argues for agents/, but the harness restriction and the A2A response contract both pull toward the runtime pages. Decide before writing, and say which argument won.
What to check before starting
- Read
docs/architecture/structured-output.mdin the kagent repository. - Read
agents/system-prompts.md, which documents the closest existing pattern: an inline field and a ConfigMap-backed alternative on the same spec, including its error table. - Confirm the limits and the profile against
go/core/internal/translator/outputschema.goon the version you are documenting. - Run an agent with a schema on a cluster, and run one that violates it, so that the failure behavior on the page is observed rather than inferred.
Done when
- The page states that the schema is resolved and validated at compile time, and that an invalid schema stops an agent from running.
- The field table covers
outputSchemaandoutputSchemaFrom, and the mutual exclusion is stated with its CEL message. - The portable profile is described, including what it rejects and the three limits.
- The harness restriction is stated, and was verified by pairing a schema with a
claudeorcodexHarness on a cluster. - An error table maps each failure to the condition and reason it sets, distinguishing
CompatiblefromResolvedRefs. - The A2A response contract is documented, including the
DataPart, the media type, the digest metadata key, and the absence of a text fallback. - The root-agent scope is stated, so a reader does not expect a child agent to inherit the schema.
- Both examples on the page were run against a live agent.
Activity
Rachael-Graham commentedon Sep 25, 2026
Re-verified against
kagent-dev/kagentmain at15e0fc15on 2026-09-25. The feature shipped in v1.0.0-alpha3, cut at375fe73a, and the scoping above holds. Two claims re-checked rather than assumed, plus one scope decision.The harness restriction is unchanged.
go/core/internal/translator/compiler.go:215still gates structured output to thekagentharness.The metadata key survived a refactor. kagent#2902 replaced the literal in
go/api/a2a/structured_output.gowithMetadataPrefix + "output-schema-sha256".MetadataPrefixiskagent.dev/a2a/, so the wire key is stillkagent.dev/a2a/output-schema-sha256. The same pull request removed the legacyadk_*aliases, which does not affect this page.systemPromptFromstays out of scope. kagent#2890 addedoutputSchemaFromandsystemPromptFromtogether, and only the first belongs here.systemPromptFromgoes onagents/system-prompts.md, which already documents an inline field with a ConfigMap-backed alternative and carries the error table it would join. Tracking it separately rather than widening this issue.Confirm which release the page targets before writing the A2A section. For 1.0.0-alpha4, kagent#2933 adds an HTTP JSON-RPC transport and a per-instance agent card, so the card that advertises
application/jsonas its default output mode is served over two transports rather than one.thc1006 commentedon Sep 29, 2026
I'd like to work on this. I've got a draft that targets 1.0.0-alpha3, since that's what the 1.x docs install, and I've tried the examples and the failure cases on a kind cluster. I put the page under
agents/, next tosystem-prompts.md.