You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
StructuredOutput: accept a validated result file path instead of re-emitted JSON #140
In schema mode the model writes and lints its result file (e.g. explain-change's $RESULT), then has to re-emit the WHOLE object as StructuredOutput tool arguments (prompt.ts:1120-1157), and run.ts:590 writes those args to --output-result. The linted file is never read.
Measured on 10 aictrl prod runs (2026-10-10):
the re-emission is about 5–9k output tokens per run (12–23% of output), or about 16k when a corrective retry re-emits everything (40–46%);
about $0.02 per Gemini run, plus estimated generation time of 30 s to 2 min;
it creates a second, unvalidated-by-lint copy that can drift from the linted file. On Gemini it silently lost every map field (see the companion issue).
Proposal
StructuredOutput also accepts {"path": "<file>"} (or a flag like --output-result-from <file>). The CLI reads the file, validates it with Ajv and writes --output-result. A rejection asks for a small edit to the file instead of a full re-emission.
Acceptance Criteria
Path form supported: validated, size-capped, confined to the work dir, with the same exit codes.
The corrective turn on the path form tells the model to edit the file.
Backward compatible with the inline-args form.
References
aictrl-dev/aictrl#6172; aictrl skill explain-change SKILL.md "call StructuredOutput with the contents of $RESULT", output contract pr-explanation-output-contract.ts:45,48.
More evidence (2026-10-10, offline eval via agy with Gemini 3.8 Flash, 13 runs): re-emitting the linted result as a schema-enforced object drifted in 7 of 13 Gemini runs, and in the 1 Sonnet run checked. Keys that aren't plain identifiers were quoted, renamed or dropped ('a->b', 'T.col' → T_col, "\"1\""); optional fields (userSees, canDo, related) were emptied; one result shrank from 8 elements to 1. A probe asking the model to copy a lint-clean file verbatim still produced 19 lint errors. Prompt text didn't fix it. This is agy's own loop, not aictrl run, so it needs confirming on our CLI, but it supports the path form here: publish the validated file from disk rather than a re-emitted copy.
Confirmed on aictrl run 0.4.7: the published result is the re-emitted copy, not the validated file
The minimal reproduction and matrix are on #139: #139
The same runs prove this issue:
In all 12 Gemini and GLM map-schema runs, the model's own ws/result.json matched the expected object exactly (6/6 keys, including queued->running, [*]->queued, TASKS.org_id, "1").
The CLI published only what the model re-emitted as StructuredOutput arguments. On Gemini that was {} every time (7/7): silently accepted when the schema has no minProperties (exit 0), a failed run when it does (exit 3).
GLM re-emitted it correctly (3/3), so the design isn't lossy by itself. It turns any provider-side schema loss, or any copy drift, into the published result.
In prod (aictrl, 2026-10-10) this emptied every diagram's elements in all 5 Gemini PR Explanation runs. It also costs about 5–9k output tokens per run to re-emit (~16k when a corrective turn re-emits again).
Fix options are on hold until we've chosen; the reproduction comes first, as requested.
Activity
byapparov commentedon Oct 10, 2026
More evidence (2026-10-10, offline eval via agy with Gemini 3.8 Flash, 13 runs): re-emitting the linted result as a schema-enforced object drifted in 7 of 13 Gemini runs, and in the 1 Sonnet run checked. Keys that aren't plain identifiers were quoted, renamed or dropped (
'a->b','T.col'→T_col,"\"1\""); optional fields (userSees, canDo, related) were emptied; one result shrank from 8 elements to 1. A probe asking the model to copy a lint-clean file verbatim still produced 19 lint errors. Prompt text didn't fix it. This is agy's own loop, notaictrl run, so it needs confirming on our CLI, but it supports the path form here: publish the validated file from disk rather than a re-emitted copy.byapparov commentedon Oct 10, 2026
Confirmed on
aictrl run0.4.7: the published result is the re-emitted copy, not the validated fileThe minimal reproduction and matrix are on #139: #139
The same runs prove this issue:
ws/result.jsonmatched the expected object exactly (6/6 keys, includingqueued->running,[*]->queued,TASKS.org_id,"1").{}every time (7/7): silently accepted when the schema has nominProperties(exit 0), a failed run when it does (exit 3).In prod (aictrl, 2026-10-10) this emptied every diagram's
elementsin all 5 Gemini PR Explanation runs. It also costs about 5–9k output tokens per run to re-emit (~16k when a corrective turn re-emits again).Fix options are on hold until we've chosen; the reproduction comes first, as requested.
Merge pull request #142 from aictrl-dev/feat/140-structured-output-path