Skip to content
This documentation covers the kagent 1.0 alpha. For the latest 0.x release, see the 0.x docs.

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

Page as Markdown

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.

FieldDescription
outputSchemaThe schema, written inline in the AgentTemplate.
outputSchemaFrom.nameThe ConfigMap holding the schema as JSON, in the AgentTemplate’s namespace.
outputSchemaFrom.keyThe key within that ConfigMap.

Before you begin

  1. Create your first agent, so that you have the my-first-harness Harness and the default-model-config ModelConfigModelConfigA 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 and jq.

    Structured output needs a Harness that uses the kagent runtime with the golang-adk image, as my-first-harness does. For a Harness of your own, use the kagent runtime block and the same workload.image as my-first-harness. With the codex, claude, or byo runtime, an AgentTemplate with a schema fails to compile. With the kagent runtime 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.

  2. 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 version

    If kagent_version in 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.

  1. Apply an AgentTemplate with a schema. The kagent.dev/harness label matches the allowedAgentTemplates selector of my-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
    EOF

    The schema requires an object with an integer answer and a string explanation, and allows no other properties. The system prompt describes the same JSON, because some model providers do not receive the schema.

  2. Confirm that the pair is ready.

    kagent get agent-template structured-answer

    Example output:

    +-------------------+------------------+-------+----------------------+
    | NAME              | HARNESS          | READY | CREATED              |
    +-------------------+------------------+-------+----------------------+
    | structured-answer | my-first-harness | TRUE  | 2026-09-29T07:49:30Z |
    +-------------------+------------------+-------+----------------------+
    

    A READY value of FALSE right after you apply the AgentTemplate is expected, because kagent builds a snapshot of the agent’s runtime before it reports the pair ready. If READY stays FALSE, see Troubleshooting.

  3. 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')
  4. 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 answer of 9 would 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.

  1. 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
        }
    EOF
  2. Apply 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
    EOF
  3. Confirm that the pair is ready. Wait until READY is TRUE.

    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, or null
  • properties, required, additionalProperties, and items
  • enum, const, and anyOf
  • title and description
  • $defs, and references to it in the form #/$defs/<name>, which cannot be recursive
  • $schema and $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.

LimitMaximum
Size of the schema as kagent reads it64 KiB
Nesting depth32 levels
Schema nodes1,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
MessageCause
output_validation_failed: root agent output does not conform to its schemaThe answer is JSON, but it does not match the schema.
output_validation_failed: root agent output is not valid JSONThe answer is not JSON.
output_validation_failed: root agent did not complete structured outputThe model stopped for a reason other than finishing normally, such as reaching its token limit.
output_validation_failed: root agent produced no structured valueThe model returned no answer text.
output_validation_failed: root agent produced no result artifactThe 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"
    }
  ]
}
MessageCondition and reasonCause
resolve output schema ConfigMap "x": not foundResolvedRefs, ReferenceResolutionFailedThe ConfigMap does not exist in the AgentTemplate’s namespace.
resolve output schema ConfigMap "x": key "y" not foundResolvedRefs, ReferenceResolutionFailedThe ConfigMap has no such key under data.
invalid output schema: schema is emptyCompatible, UnsupportedConfigurationThe key holds an empty value.
invalid output schema: schema exceeds 65536 bytesCompatible, UnsupportedConfigurationThe schema is larger than 64 KiB.
invalid output schema: decode JSON: ...Compatible, UnsupportedConfigurationThe value is not valid JSON.
invalid output schema: decode trailing JSON: ...Compatible, UnsupportedConfigurationText that is not JSON follows the JSON value.
invalid output schema: schema must contain one JSON valueCompatible, UnsupportedConfigurationThe 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, UnsupportedConfigurationThe schema uses a keyword or form outside the portable output profile, or its root is not an object.
invalid output schema: resolve schema: ...Compatible, UnsupportedConfigurationkagent 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, UnsupportedConfigurationThe schema has a recursive reference, or exceeds the depth or node limit.
Harness "x" does not support structured outputCompatible, UnsupportedConfigurationThe 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

  1. 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-instance
  2. Delete the AgentTemplates and the ConfigMap.

    kubectl delete agenttemplate structured-answer structured-answer-shared -n kagent
    kubectl delete configmap shared-schemas -n kagent

Next steps