Skip to content

[DEV-102] Structured (JSON) output support in agent-harness for reliable downstream automation - #97

Merged
danii1 merged 1 commit into
mainfrom
feature/dev-102-attempt-2
Aug 28, 2026
Merged

[DEV-102] Structured (JSON) output support in agent-harness for reliable downstream automation#97
danii1 merged 1 commit into
mainfrom
feature/dev-102-attempt-2

Conversation

@danii1

@danii1 danii1 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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 json instead of -p — I need to check whether -p (our promptFlag) combines with it:
Pi's parser handles -p and --mode json independently, so both can combine safely. Now let me verify Cline's --json placement (root command vs task subcommand) since our harness emits cline 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.ts module:
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 \n escapes in arguments; %b does:
Now extending tests/harnesses.test.ts with 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 onStdout test (the runners never wired onStdout — only onStderr in 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 missing node/process types — 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: warn baseline), 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:

Harness Flag Shape
claude-code --output-format json single result envelope
cline --json NDJSON messages
codex --json JSONL events
cursor --output-format json single result object
opencode --format json JSONL events
grok --output-format json single result object
goose --output-format json single result object
antigravity (agy ≥1.1.8) --output-format json JSON envelope
qwen --output-format json JSON message array
kilo-code --format json JSONL events
kimi --output-format stream-json JSONL messages
pi --mode json (pairs with -p) JSONL events
deepseek (reasonix) --output-format json result object

Changes (all in packages/agent-harness):

  • src/types.tsstructuredOutput? run option, supportsStructuredOutput? capability (same pattern as supportsMaxTurns), optional structured? field on AgentRunResult + new StructuredOutputResult type
  • src/structured-output.tsUnsupportedStructuredOutputError (mirrors UnsupportedAgentModeError), assertStructuredOutputSupported, and a tolerant-but-fail-closed parseStructuredOutput: 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 payloads
  • src/runners/{bun,node}.ts — fail-closed assert before spawn (proven by tests using a nonexistent executable path), parse after exit; identical otherwise
  • All 13 harnesses — capability + flag emission; src/index.ts exports; readme capability table + "Structured (JSON) output" section with per-CLI table and detector-interplay notes

Key design decisions: opt-in per run (plain-text default and all detectors unchanged — JSON event lines still match provider-limit patterns), structured field absent when not requested (backward compatible — verified against @devintern/pm consumers), and truncated output fails closed so automation never mistakes partial JSON for a complete result.


This PR was automatically created by @devintern/code

@danii1
danii1 merged commit 28549c6 into main Aug 28, 2026
1 check passed
@danii1
danii1 deleted the feature/dev-102-attempt-2 branch August 28, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant