For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Structured output
Constrain an AgentTemplate’s final answer to a JSON Schema, set inline or in a ConfigMap, and troubleshoot schemas and answers that fail validation.
An AgentTemplateAgentTemplateA Kubernetes custom resource defining what an agent does: its model, system prompt, tools, skills, and plugins. It runs only once a Harness accepts it.Learn more can require that its final answer is JSON matching a schema. kagent checks the schema when it compiles a revisionRevisionThe compiled, immutable output of one Harness and AgentTemplate pairing, identified by a content digest. An AgentInstance runs the revision it was created from for its whole life, so editing either resource affects only instances created afterward. for a HarnessHarnessA Kubernetes custom resource defining how an agent is allowed to run: its runtime, workload image, WorkerPool and snapshot storage, and which AgentTemplates it accepts.Learn more and AgentTemplate pair, and the revision records the schema and its digest. A schema that fails the checks stops the revision from compiling, so a pair that has never been ready cannot start an AgentInstanceAgentInstanceA running, conversational pairing of a Harness and an AgentTemplate. Unlike the two, it is not a Kubernetes resource: kagent's gRPC API creates it and its database tracks it.Learn more. At runtime, the agent validates its complete answer against the recorded schema before it publishes the answer.
The schema goes in one of two AgentTemplate fields, spec.outputSchema or spec.outputSchemaFrom. The fields are mutually exclusive. An AgentTemplate that sets both is rejected when you apply it, with the message outputSchema and outputSchemaFrom are mutually exclusive. If you omit both fields, the agent’s final answer is not constrained.
| Field | Description |
|---|---|
outputSchema | The schema, written inline in the AgentTemplate. |
outputSchemaFrom.name | The ConfigMap holding the schema as JSON, in the AgentTemplate’s namespace. |
outputSchemaFrom.key | The key within that ConfigMap. |
Before you begin
Create your first agent, so that you have the
my-first-harnessHarness and thedefault-model-configModelConfigModelConfigA Kubernetes custom resource naming one model at one provider, along with the credentials to reach it. An AgentTemplate references one by name, and every agent compiled from that template calls the model that it names.Learn more that the AgentTemplates in this guide use. That guide also has you install the kagent CLI andjq.Structured output needs a Harness that uses the
kagentruntime with thegolang-adkimage, asmy-first-harnessdoes. For a Harness of your own, use thekagentruntime block and the sameworkload.imageasmy-first-harness. With thecodex,claude, orbyoruntime, an AgentTemplate with a schema fails to compile. With thekagentruntime and an image of kagent’s Python engine, the AgentTemplate compiles, but the agent ignores the schema. For the differences between runtimes, see Agent harness.Check your kagent CLI version. The steps on this page need the 1.0.0-alpha4 CLI. A CLI from another release can fail with
unknown command.kagent versionIf
kagent_versionin the output is not 1.0.0-alpha4, install that version.curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v1.0.0-alpha4
Set the schema inline
Set spec.outputSchema to keep the schema in the AgentTemplate, so that the schema and the rest of the agent’s configuration change together.
Apply an AgentTemplate with a schema. The
kagent.dev/harnesslabel matches theallowedAgentTemplatesselector ofmy-first-harness.kubectl apply -f - <<EOF apiVersion: kagent.dev/v1alpha3 kind: AgentTemplate metadata: name: structured-answer namespace: kagent labels: kagent.dev/harness: my-first-harness spec: description: Answers arithmetic questions with a structured result. modelConfig: name: default-model-config systemPrompt: Answer arithmetic questions. Reply with JSON that has an integer answer and a one-sentence explanation. outputSchema: type: object properties: answer: type: integer explanation: type: string required: - answer - explanation additionalProperties: false EOFThe schema requires an object with an integer
answerand a stringexplanation, and allows no other properties. The system prompt describes the same JSON, because some model providers do not receive the schema.Confirm that the pair is ready.
kagent get agent-template structured-answerExample output:
+-------------------+------------------+-------+----------------------+ | NAME | HARNESS | READY | CREATED | +-------------------+------------------+-------+----------------------+ | structured-answer | my-first-harness | TRUE | 2026-09-29T07:49:30Z | +-------------------+------------------+-------+----------------------+A
READYvalue ofFALSEright after you apply the AgentTemplate is expected, because kagent builds a snapshot of the agent’s runtime before it reports the pair ready. IfREADYstaysFALSE, see Troubleshooting.Create an AgentInstance from the pair, and save its ID.
kagent create agent-instance --harness my-first-harness --agent-template structured-answer export INSTANCE_ID=$(kagent get agent-instance -o json \ | jq -r '[.agentInstances[] | select(.agentTemplate.name == "structured-answer")] | sort_by(.createdAt) | last | .id')Send a question.
kagent invoke --agent-instance $INSTANCE_ID --task "What is 3 plus 5?"Example output:
{"answer":8,"explanation":"Three plus five equals eight."}The CLI prints the validated answer as JSON text. An
answerof9would also pass, because the schema checks only the shape and types of the answer.
Store the schema in a ConfigMap
Use outputSchemaFrom to keep the schema outside the AgentTemplate, so that several AgentTemplates can share one schema. The value in the ConfigMap must be JSON, even though the ConfigMap itself is written in YAML.
Create a ConfigMap holding the schema.
kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: shared-schemas namespace: kagent data: arithmetic-answer: |- { "type": "object", "properties": { "answer": {"type": "integer"}, "explanation": {"type": "string"} }, "required": ["answer", "explanation"], "additionalProperties": false } EOFApply an AgentTemplate that references the key.
kubectl apply -f - <<EOF apiVersion: kagent.dev/v1alpha3 kind: AgentTemplate metadata: name: structured-answer-shared namespace: kagent labels: kagent.dev/harness: my-first-harness spec: description: Answers arithmetic questions with a structured result. modelConfig: name: default-model-config systemPrompt: Answer arithmetic questions. Reply with JSON that has an integer answer and a one-sentence explanation. outputSchemaFrom: name: shared-schemas key: arithmetic-answer EOFConfirm that the pair is ready. Wait until
READYisTRUE.kagent get agent-template structured-answer-shared
Supported schemas
The root of the schema must declare type: object. kagent accepts the following subset of JSON Schema, which its error messages call the portable output profile.
type, with a single value:object,array,string,number,integer,boolean, ornullproperties,required,additionalProperties, anditemsenum,const, andanyOftitleanddescription$defs, and references to it in the form#/$defs/<name>, which cannot be recursive$schemaand$id
Anything else fails to compile, including oneOf and allOf, conditional schemas such as if and then, tuple arrays, references outside $defs, and validation keywords such as pattern, format, minimum, and minLength. To allow null alongside another type, use anyOf, because type takes one value.
kagent also enforces the following limits when it compiles the revision. A schema that exceeds a limit fails to compile, before any agent runs with it.
| Limit | Maximum |
|---|---|
| Size of the schema as kagent reads it | 64 KiB |
| Nesting depth | 32 levels |
| Schema nodes | 1,000 |
kagent counts depth and nodes through properties, items, and anyOf, and counts a $defs entry again each time that it is referenced. The size limit applies before kagent normalizes the schema, so whitespace in a ConfigMap value counts toward it.
Read the answer
A successful answer is published as one A2AA2AThe Agent-to-Agent protocol, which callers and other agents use to talk to an AgentInstance. The conversation's context identifier is the AgentInstance ID, so a second message on the same ID continues the same conversation.Learn more (Agent-to-Agent) DataPart with the media type application/json. The part’s metadata carries the SHA-256 digest of the schema that the answer was validated against, under the key kagent.dev/a2a/output-schema-sha256. kagent computes the digest after it normalizes the schema, so two schemas that differ only in formatting or key order have the same digest. The AgentInstance’s agent card lists application/json as its default output mode, instead of text.
To see the part and its metadata, print the task as JSON.
kagent invoke --agent-instance $INSTANCE_ID --task "What is 3 plus 5?" -o json \
| jq '.task.artifacts[-1].parts'Example output:
[
{
"data": {
"answer": 8,
"explanation": "Three plus five equals eight."
},
"metadata": {
"kagent.dev/a2a/output-schema-sha256": "59a6341782be5eacb0762a62133c7b45ec785770392216e712f109f11afb6a1b"
},
"mediaType": "application/json"
}
]The runtime holds back the agent’s partial output while the agent writes its answer, and publishes only the complete answer. Progress updates, tool calls, approval requests, and input-required messages keep their usual form, and the agent can call tools before it answers. A streaming client that needs only the answer reads the last artifact with content before the task reaches TASK_STATE_COMPLETED. That artifact’s part carries the kagent.dev/a2a/output-schema-sha256 metadata key.
Agents as tools
The schema applies only to the root agent, which is the agent of the AgentTemplate that the AgentInstance was created from. An AgentTemplate bound to it as a tool does not inherit the schema. If the bound AgentTemplate has a schema of its own, that schema applies only to AgentInstances created from it.
With a Shared binding, the root agent can hand the conversation to the bound agent, which then answers in its place, in that turn and in the turns that follow. Those tasks fail with output_validation_failed: root agent produced no result artifact, because the answers do not come from the root agent. The bound agent’s answers still reach the caller as text. Give a schema only to an AgentTemplate whose own agent writes the final answer.
Answers that fail validation
Structured output has no fallback to text. If the model refuses, stops early, or returns an answer that is not JSON or does not match the schema, the task fails. kagent does not publish the invalid answer or include it in the failure message, because it can contain sensitive data. With a Gemini model on the Gemini provider, however, the agent is told to give its answer as the arguments of a set_model_response tool call. kagent publishes that call like any other tool call, before it validates the answer, so an answer given that way reaches the caller even when it fails validation.
For example, an answer that does not match the schema fails the kagent invoke command with output like the following. The output_validation_failed: message names the cause.
processor failed: output_validation_failed: root agent output does not conform to its schema
Error: AgentInstance task 01a0ec27-157f-7034-bb89-09855f485b2a ended in TASK_STATE_FAILED
| Message | Cause |
|---|---|
output_validation_failed: root agent output does not conform to its schema | The answer is JSON, but it does not match the schema. |
output_validation_failed: root agent output is not valid JSON | The answer is not JSON. |
output_validation_failed: root agent did not complete structured output | The model stopped for a reason other than finishing normally, such as reaching its token limit. |
output_validation_failed: root agent produced no structured value | The model returned no answer text. |
output_validation_failed: root agent produced no result artifact | The task ended without a structured answer from the root agent, for example because the root agent handed the conversation to an agent bound as a tool. |
Troubleshooting
A schema problem appears on the AgentTemplate’s status, in the entry for each Harness under status.harnesses. ResolvedRefs reports a ConfigMap or key that kagent cannot find, and Compatible reports a schema that kagent found but cannot accept, or a Harness that does not use the kagent runtime.
kubectl get agenttemplate structured-answer-shared -n kagent -o json \
| jq '.status.harnesses[] | {harness, conditions: [.conditions[] | select(.type == "ResolvedRefs" or .type == "Compatible")]}'For example, if the shared-schemas ConfigMap no longer has the arithmetic-answer key, the output is similar to the following. When ResolvedRefs fails, Compatible reports Blocked instead of a result of its own.
{
"harness": "my-first-harness",
"conditions": [
{
"lastTransitionTime": "2026-09-29T07:52:08Z",
"message": "resolve output schema ConfigMap \"shared-schemas\": key \"arithmetic-answer\" not found",
"observedGeneration": 1,
"reason": "ReferenceResolutionFailed",
"status": "False",
"type": "ResolvedRefs"
},
{
"lastTransitionTime": "2026-09-29T07:52:08Z",
"message": "blocked by ResolvedRefs",
"observedGeneration": 1,
"reason": "Blocked",
"status": "False",
"type": "Compatible"
}
]
}| Message | Condition and reason | Cause |
|---|---|---|
resolve output schema ConfigMap "x": not found | ResolvedRefs, ReferenceResolutionFailed | The ConfigMap does not exist in the AgentTemplate’s namespace. |
resolve output schema ConfigMap "x": key "y" not found | ResolvedRefs, ReferenceResolutionFailed | The ConfigMap has no such key under data. |
invalid output schema: schema is empty | Compatible, UnsupportedConfiguration | The key holds an empty value. |
invalid output schema: schema exceeds 65536 bytes | Compatible, UnsupportedConfiguration | The schema is larger than 64 KiB. |
invalid output schema: decode JSON: ... | Compatible, UnsupportedConfiguration | The value is not valid JSON. |
invalid output schema: decode trailing JSON: ... | Compatible, UnsupportedConfiguration | Text that is not JSON follows the JSON value. |
invalid output schema: schema must contain one JSON value | Compatible, UnsupportedConfiguration | The value holds more than one JSON value, such as two objects in a row. |
invalid output schema: schema does not match the portable output profile: ... | Compatible, UnsupportedConfiguration | The schema uses a keyword or form outside the portable output profile, or its root is not an object. |
invalid output schema: resolve schema: ... | Compatible, UnsupportedConfiguration | kagent cannot resolve the schema, for example because a $ref names a $defs entry that does not exist. |
output schema is incompatible with Go ADK: ... | Compatible, UnsupportedConfiguration | The schema has a recursive reference, or exceeds the depth or node limit. |
Harness "x" does not support structured output | Compatible, UnsupportedConfiguration | The Harness does not use the kagent runtime. |
If the pair has never been ready, kagent create agent-instance fails with AgentTemplate and Harness do not have a ready prepared revision.
Warning
An AgentInstance keeps the revision that it was created from, and validates its answers against that revision’s schema. After you change a schema, create a new AgentInstance to use it. A changed schema that fails to compile does not stop new AgentInstances from starting. They start from the pair’s latest ready revision, which status.harnesses reports as latestSuccessfulRevision, so they use the previous schema.
A change to a ConfigMap does not change the AgentTemplate’s metadata.generation, so check each condition’s lastTransitionTime to tell whether kagent has seen your fix.
Clean up
Delete the AgentInstances that you created in this guide.
kagent get agent-instance -o json \ | jq -r '.agentInstances[] | select(.agentTemplate.name == "structured-answer" or .agentTemplate.name == "structured-answer-shared") | .id' \ | xargs -n1 kagent delete agent-instanceDelete the AgentTemplates and the ConfigMap.
kubectl delete agenttemplate structured-answer structured-answer-shared -n kagent kubectl delete configmap shared-schemas -n kagent