[DEV-102] Structured (JSON) output support in agent-harness for reliable downstream automation - #97
Merged
Merged
Conversation
…arness for reliable downstream automation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task: DEV-102
Summary: Structured (JSON) output support in agent-harness for reliable downstream automation
Implementation Details
I'll start by exploring the agent-harness package structure to understand the existing conventions.
Now let me read all the remaining harnesses to see which CLIs may support JSON output:
Now let me look at the existing tests and detection helpers:
Let me look at the remaining source files and tests to understand conventions fully:
I now have a good picture of the codebase. Per the task notes, I need to investigate each real CLI's actual JSON output support before wiring args. Let me research the documented CLI flags:
Claude Code and Cline confirmed. Now let me check Codex, Cursor, Opencode, and Qwen:
Codex and Cursor confirmed. Now checking Opencode and Qwen:
Opencode and Qwen confirmed. Let me quickly verify the remaining harnesses (Grok, Kimi, Goose, agy, Kilo):
Grok and Goose confirmed too. Let me check the remaining ones (Kimi, Kilo, agy, Pi, Reasonix):
Kimi and Kilo confirmed. Quick check on the last few (agy, Pi, Reasonix):
All harnesses' CLIs have documented structured output. One nuance to verify: Pi uses
--mode jsoninstead of-p— I need to check whether-p(ourpromptFlag) combines with it:Pi's parser handles
-pand--mode jsonindependently, so both can combine safely. Now let me verify Cline's--jsonplacement (root command vstasksubcommand) since our harness emitscline task:All CLIs verified. Now let me check the runner test patterns and how other packages consume the harness API:
I have everything I need. Let me set up the task list and check two remaining details (tsconfig strictness, registry test):
Now implementing. First,
types.ts— the run option, capability flag, and result field:Now the new
src/structured-output.tsmodule:Now wiring the harnesses. Claude Code:
Cline:
Codex:
Cursor:
Opencode:
Grok:
Goose:
Antigravity:
Qwen:
Kilo Code:
Kimi (uses
stream-json, its only structured format):Pi:
DeepSeek (Reasonix):
Now the runners — fail-closed assert plus result parsing. Bun runner:
Node runner:
Now the package exports:
Now the new test file:
I need to fix a shell quoting issue in the bun NDJSON test —
printf '%s'doesn't interpret\nescapes in arguments;%bdoes:Now extending
tests/harnesses.test.tswith per-harness args assertions:Now updating the readme — capability table row and a dedicated section:
Now run the agent-harness test suite and typecheck:
Two bugs: my parser lost the NDJSON success return path, and the fake test harnesses lack the capability. Fixing the parser:
Now fix the test harness fixtures to declare the capability:
Three issues left. Two parser bugs (bracket-like banner text breaks span recovery) and one test using
onStdout, which I need to check is actually wired in the runners:Let me re-read the current parser to trace the remaining failures:
Found it —
[INFO] ...banner lines start with[, so my JSON-line filter miscounts them as malformed arrays. I'll tighten the line filter and make span recovery try multiple bracket candidates:Also updating the doc comment to match, and replacing the
onStdouttest (the runners never wiredonStdout— onlyonStderrin the Bun runner — so I'll test what's actually wired):All 315 tests pass. Now typecheck, lint, and format:
These typecheck errors are all in untouched files (
resolver,sandbox,spawn-agent, etc.) about missingnode/processtypes — looks like a pre-existing environment issue (missing type packages). Let me verify against a clean tree:The typecheck failures are pre-existing/environmental. Let me check if dependencies are installed:
No
node_modules— dependencies were never installed in this worktree. Installing per the repo workflow:Typecheck clean. Now lint and format:
Lint exits 0 (warnings only, consistent with the repo's
correctness: warnbaseline), but I'll clean up the four new warnings my test file introduced:All remaining warnings are pre-existing. Now full re-verification — format, typecheck, and tests:
All green in agent-harness. Now verifying downstream packages that consume these types (pm, code):
pm was cached — forcing a re-run since agent-harness changed:
All downstream checks pass. Final review of the diff:
Let me check whether the docs directory references agent-harness capabilities that need updating:
No docs references — the package readme was the correct surface. One final full-monorepo test run to be thorough:
DEV-102 is implemented. All 11 monorepo tasks pass (tests, typecheck, lint, format).
Summary
Researched every real CLI's docs before wiring args (per the task notes) — all 13 built-in harnesses have documented JSON modes:
--output-format json--json--json--output-format json--format json--output-format json--output-format json--output-format json--output-format json--format json--output-format stream-json--mode json(pairs with-p)--output-format jsonChanges (all in
packages/agent-harness):src/types.ts—structuredOutput?run option,supportsStructuredOutput?capability (same pattern assupportsMaxTurns), optionalstructured?field onAgentRunResult+ newStructuredOutputResulttypesrc/structured-output.ts—UnsupportedStructuredOutputError(mirrorsUnsupportedAgentModeError),assertStructuredOutputSupported, and a tolerant-but-fail-closedparseStructuredOutput: whole-document parse → NDJSON line scan (ignores log banners like[INFO], bracket-aware) → bracket-span recovery; malformed/truncated NDJSON returns{ok:false, error}instead of partial payloadssrc/runners/{bun,node}.ts— fail-closed assert before spawn (proven by tests using a nonexistent executable path), parse after exit; identical otherwisesrc/index.tsexports; readme capability table + "Structured (JSON) output" section with per-CLI table and detector-interplay notesKey design decisions: opt-in per run (plain-text default and all detectors unchanged — JSON event lines still match provider-limit patterns),
structuredfield absent when not requested (backward compatible — verified against@devintern/pmconsumers), and truncated output fails closed so automation never mistakes partial JSON for a complete result.This PR was automatically created by @devintern/code