feat: structured output mode — constrain agent responses to a declared JSON schema#28964
feat: structured output mode — constrain agent responses to a declared JSON schema#28964
Conversation
…lared JSON schema Resolves: #structured-output-mode - New `structured-output` frontmatter field accepting `schema` (inline) or `schema-file` (file path) containing a JSON Schema draft-07 object - Compile-time validation of the schema using santhosh-tekuri/jsonschema/v6 - Pre-agent step writes schema to /tmp/gh-aw/structured-output-schema.json - GH_AW_STRUCTURED_OUTPUT_SCHEMA and GH_AW_STRUCTURED_OUTPUT_FILE env vars injected into all engine execution steps (copilot, claude, codex, crush, gemini, opencode) - Post-agent validation step (actions/github-script, always runs) checks /tmp/gh-aw/structured-output.json is well-formed JSON and sets the `validate_structured_output` step output - Agent job exposes `structured_output` output for downstream job consumption - JSON schema updated with `structured-output` property definition - StructuredOutputConfig added to FrontmatterConfig and WorkflowData - Unit tests covering all new functions Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d1463ae7-cb32-4831-981b-39e1993c2cbc Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
…shellEscapeSingleQuote Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d1463ae7-cb32-4831-981b-39e1993c2cbc Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
lpcox
left a comment
There was a problem hiding this comment.
Review: Engine-Level Structured Output Support
This PR implements structured output as a file-based convention — the agent is given env vars pointing to a schema file and an output file path, then a post-agent step validates the JSON. This is engine-agnostic but leaves significant native capabilities unused.
How Each Engine Supports Structured Outputs Natively
Copilot CLI — ❌ No native structured output support
- Copilot CLI does not expose a
--response-formator JSON-mode flag - The env-var + file-write approach is the only viable path
- The agent must be instructed via system prompt to write JSON to the file path
Claude CLI —
- Claude CLI (
claude) supports--output-format jsonwhich constrains the final response to valid JSON - The Anthropic API natively supports tool_use with structured schemas and prefilled responses for JSON mode
- Gap: This PR could pass
--output-format jsonto the Claude CLI command when structured output is configured, providing a belt-and-suspenders guarantee (native JSON mode + post-validation)
Codex CLI —
- OpenAI's API supports
response_format: { type: "json_schema", json_schema: {...} }which provides guaranteed schema-conformant output at the model level - The Codex CLI does not currently expose this as a flag, but OpenAI's structured outputs are the gold standard for this feature — the model is constrained at the token sampling level
- Gap: If/when Codex CLI adds a
--response-formatflag, this PR's architecture should pass the schema through
Gemini —
- Gemini API supports
response_schemain generation config for JSON mode - Gemini CLI may not expose this yet
Crush/OpenCode — Unknown/varies
Summary of Concerns
-
The PR relies entirely on prompt-engineering + post-hoc validation — the schema file is written to disk and env vars are set, but no engine is actually told to produce structured output. The agent must discover the env vars and voluntarily write JSON. There's no mechanism in the system prompt injection to instruct the agent about this requirement.
-
No runtime schema validation — The post-agent step only checks
JSON.parse()succeeds. It does NOT validate against the declared schema. The schema is validated at compile time but never enforced at runtime. A conforming-JSON-but-wrong-schema response would pass silently. -
Missing prompt injection — How does the agent know to write JSON to
$GH_AW_STRUCTURED_OUTPUT_FILE? The env vars are set, but unless the system prompt or agent instructions reference them, most engines will produce freeform text. Is there a separate PR that adds the system prompt integration? -
Native capabilities create stronger guarantees — For Claude and OpenAI/Codex, model-level JSON constraints prevent malformed output entirely (no retry needed). The current approach allows the agent to produce invalid output, fail validation, and waste the entire run.
Recommendation
The architecture is sound as a lowest-common-denominator approach, but the PR should:
- Add runtime schema validation (not just
JSON.parse) in the post-agent step - Document which engines need prompt-based instruction vs. which could use native structured output in future
- Clarify how the agent discovers and acts on the env vars (system prompt integration)
|
@copilot configure codex to use the cli argument --output-schema https://www.mintlify.com/openai/codex/cli/exec?utm_source=chatgpt.com#param-output-schema |
|
Aside from codex none of these are native, it will not work well. We need to take into the SDK and provide the Schema into the LLM inference calls. |
pelikhan
left a comment
There was a problem hiding this comment.
Unlikely to work properly
…t is configured When structured-output is configured and it is not a detection run, the Codex exec command now includes --output-schema <schema-path> so the model is constrained at the token-sampling level to produce schema-conformant JSON in addition to the file-write convention enforced by the post-agent validation step. Ref: https://openai.github.io/codex/cli/exec#param-output-schema Agent-Logs-Url: https://github.com/github/gh-aw/sessions/2811a83a-be95-4f17-a521-37792e0ae26d Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in 5aa6e43. When |
|
It's not well supported so far. Looks like a job for sub-agents. |
Agentic workflows currently produce freeform text, forcing downstream jobs to parse unstructured output with brittle regex or prompt-engineering hacks. This adds a first-class
structured-outputfrontmatter field that declares a JSON Schema the agent's response must conform to, with compile-time validation and runtime enforcement.Changes
New
structured-outputfrontmatter fieldschema(inline JSON Schema object) orschema-file(repo-relative path to a.jsonfile)santhosh-tekuri/jsonschema/v6(already a project dependency)Runtime behavior (generated YAML)
/tmp/gh-aw/structured-output-schema.jsonGH_AW_STRUCTURED_OUTPUT_SCHEMAandGH_AW_STRUCTURED_OUTPUT_FILEenv vars injected into all engine execution steps (copilot, claude, codex, crush, gemini, opencode) — parallel to the existingapplySafeOutputEnvToMappatternactions/github-scriptstep (runsalways()) reads/tmp/gh-aw/structured-output.json, validates it is well-formed JSON, and sets thestructured_outputstep outputCodex native enforcement
structured-outputis configured,--output-schema <schema-path>is passed to the Codexexeccommand, constraining the model at the token-sampling level to produce schema-conformant JSON (belt-and-suspenders on top of the file-write convention)Agent job output
structured_outputadded to the agent job'soutputsmap, exposing the compact validated JSON string to downstream jobs via${{ needs.agent.outputs.structured_output }}Data model
StructuredOutputConfigadded toFrontmatterConfigandWorkflowDataextractAdditionalConfigurationsalongside cache-memory, repo-memory, and mcp-scriptsExample
Downstream job:
Alternatively, reference a schema file to keep large schemas out of the frontmatter: