Skip to content

Docs: write the Structured output page for kagent 1.x #518

Description

@Rachael-Graham

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 $defs count toward both bounds.
  • Only the kagent Harness supports structured output. The codex, claude and byo Harnesses fail compilation with Harness "<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 Compatible condition to False with the reason UnsupportedConfiguration. A ConfigMap that does not exist, or a key that is missing from it, sets ResolvedRefs to False with the reason ReferenceResolutionFailed. 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 DataPart with media type application/json, carrying the canonical schema digest in kagent.dev/a2a/output-schema-sha256. The generated Agent Card advertises application/json as its default output mode. A streaming client that wants only the answer reads the last content-bearing artifact before the Task reaches TASK_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

  1. Read docs/architecture/structured-output.md in the kagent repository.
  2. 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.
  3. Confirm the limits and the profile against go/core/internal/translator/outputschema.go on the version you are documenting.
  4. 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 outputSchema and outputSchemaFrom, 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 claude or codex Harness on a cluster.
  • An error table maps each failure to the condition and reason it sets, distinguishing Compatible from ResolvedRefs.
  • 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationhelp wantedExtra attention is neededkagent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions