[DEV-107] Cleaner AI-generated task content by using harness structured output mode - #100
[DEV-107] Cleaner AI-generated task content by using harness structured output mode#100danii1 wants to merge 6 commits into
Conversation
…harness structured output mode
|
|
||
| ```bash | ||
| # .devintern-pm/.env | ||
| AGENT_STRUCTURED_OUTPUT=0 |
There was a problem hiding this comment.
@devintern-ai if structured output is supported by harness let's treat that it outputs info correctly and engine should use it, we won't offer overrides by the env vars as it's our problem to verify that every harness works correctly in this mode
|
|
||
| In the DevIntern PM desktop app, set the same override per project from **Settings → Agent model**; it persists to `.devintern-pm/.env` (same file the CLI reads) and applies to new agent runs immediately. | ||
|
|
||
| ### Structured (JSON) output |
There was a problem hiding this comment.
@devintern-ai let's not modify docs/pm/configuration.md as it's our internals and for user it doesn't matter
…al docs (review feedback) - Revert docs/pm/configuration.md: structured output is internal engine behavior, not user-facing configuration - Remove AGENT_STRUCTURED_OUTPUT opt-out env var and the createEngine structuredOutput override; harnesses supporting JSON mode are always asked for structured output and its parsed payload is trusted - Drop the corresponding opt-out tests
| * {@link MAX_REPLY_TEXT_DEPTH}. Returns `undefined` when nothing readable is | ||
| * found. | ||
| */ | ||
| function extractReplyText(value: unknown, depth: number): string | undefined { |
There was a problem hiding this comment.
@devintern-ai you need to pass a structured output schema or use the exact harness schema(they differ from harness to harness) to parse the output properly. Exact harness schema maybe preferrable as it lets us surface stats like token usage and costs
…mas (DEV-107 review) Address review feedback on packages/pm/lib/engine/structured.ts: the generic reply-text field sniffing (REPLY_TEXT_FIELDS / extractReplyText) could not parse harness envelopes reliably, since each harness's JSON mode has its own shape. Implement the reviewer-preferred option: exact harness schemas, which also unlock usage/cost stats. - agent-harness: new src/structured-envelope.ts registers an exact envelope schema per structured-output harness (claude-code result envelope, codex item/turn events, opencode step/text events, qwen result entries, kimi messages, cline/kilo say events, pi session events, cursor/grok/deepseek result and goose/antigravity response envelopes). extractHarnessStructuredReply recovers the model's final reply plus normalized token usage and cost; extractHarnessEventText maps single event lines to readable text. Unknown harnesses/mismatches yield nothing so callers fail open to legacy handling. - pm engine: structuredPayloadFromResult and createReadableStdoutTap are keyed on the configured harness name; envelope field guessing is removed. - pm engine: new EngineCallEvents.onAgentUsage surfaces token usage and cost reported by the harness envelope (e.g. Claude Code usage/total_cost_usd, Codex turn.completed, opencode step_finish). - tests: agent-harness envelope schema suite; pm engine tests updated to harness-keyed fixtures plus usage-surfacing coverage; readme documents the per-harness schema table and adds a checklist step for new harnesses.
…EV-107 review)
Reviewer reported a live grok --output-format json run failing to parse:
the envelope carries the reply in `text` (not `result`), and the model
wraps the JSON payload in narration prose.
Verified the exact shape against the open-source CLI (xai-org/grok-build,
crates/codegen/xai-grok-pager/src/headless.rs `build_json_result`) and:
- register a dedicated grok envelope schema: reply text from `text`,
Claude-style usage (incl. `reasoning_tokens`/`total_tokens`) plus
`total_cost_usd` via a shared claudeStyleStats helper
- make parseReplyText recover JSON embedded in narration: fenced
```json blocks and string-aware balanced `{...}` spans are tried
before falling back to the raw reply text (all harnesses benefit)
- pin the shape in the module header, harness comment, and readme
envelope table; note the open-source-CLI verification path from the
review discussion
# Conflicts: # packages/agent-harness/tests/sandbox-providers.test.ts
|
🔀 This branch had merge conflicts with |
|
@devintern-ai for opensource harnesses, check their repos and document/link their exact json response shape |
…arness (DEV-107 review)
Checked each open-source harness's repo for its exact structured-output
JSON shape and documented it in the structured-envelope module header and
readme (with upstream repo + file links). Verification corrected three
schemas that did not match upstream:
- goose: `--output-format json` emits a `{messages, metadata}` document
(reply = last assistant message's text blocks, usage/cost in
`metadata`), not a `response` envelope
- cline: `--json` ends in a final `run_result` record (`text` +
camelCase `usage`), not `say:"text"` records
- kilo-code: the CLI is an opencode fork, so it shares the opencode
event schema instead of cline's
Also aligned parsers with the verified shapes: codex (no upstream
`total_tokens`; map `cache_write_input_tokens` /
`reasoning_output_tokens`), opencode (no `tokens.total`), pi (usage also
read from `message_end.message.usage`; `totalTokens`/`reasoning`/
`cost.total`), and deepseek (verified Claude-style usage +
`total_cost_usd`). Envelope tests now use upstream-shaped fixtures;
closed-source CLIs (claude-code, cursor, antigravity) are documented
from their official docs.
Task: DEV-107
Summary: Cleaner AI-generated task content by using harness structured output mode
Implementation Details
I'll start by exploring the codebase to understand the existing structures before implementing.
Now let me look at the pm engine:
Let me look at the remaining key files —
agent-json.ts, engine types, tests, and how pm-desktop consumesonAgentChunk:Now let me check how pm-desktop consumes chunks and how config is structured:
Let me look at the config structure, the prompts, and how the chat bot uses the engine:
Let me check the envelope shapes documented in harness sources, and the chunk consumers:
I now have full context. Let me set up the task plan:
Now creating the structured-output engine module:
Now wiring the engine (
packages/pm/lib/engine/index.ts):Now the
runAndParsecore change:Now add the env opt-out helper to
structured.ts:Core wiring is complete. Now let me add tests to
packages/pm/engine.test.ts:Now add the structured-output test suites before the
engine module isolationblock:These look like missing dependency types. Let me install deps first:
Typecheck passes. Now run the pm tests:
Found it — a regression: when structured mode is off but
onAgentChunkexists, stdout chunks were dropped. Fixing:All pm tests pass. Now update todos and docs:
Now run the full verification suite:
All builds pass. Let me run the root-level checks to make sure nothing else broke:
All monorepo checks pass. Final review of the diff:
DEV-107 is implemented. Summary of the change:
New module —
packages/pm/lib/engine/structured.tsunwrapStructuredPayload: unwraps harness envelopes soisStoryPayload/isDecompositionPayloadkeep working — Claude/Cursor/Grok/Goose/Antigravity{"type":"result",...,"result":"<reply>"}single-doc envelopes, Codex/Opencode/Kilo/Cline/Kimi/Pi NDJSON event arrays (last text-bearing entry, incl. nesteditem.text), and Qwen buffered message arrays.structuredPayloadFromResult: readsresult.structured; when parsing failed (ok === false) or the unwrapped payload doesn't validate, returns not-ok so the engine falls back toextractJsonPayloadon raw stdout. Reply text that isn't clean JSON still gets the tolerant repair.createReadableStdoutTap: in JSON mode, rewrites JSON event stdout lines carrying assistant text into readable text for live output views (pm-desktop agent log), passing everything else through raw; plain-text runs forward chunks byte-for-byte as before.AGENT_STRUCTURED_OUTPUTenv opt-out (0/false/off/no) for harness-specific JSON-mode quirks.Engine wiring —
packages/pm/lib/engine/index.tsrunAndParserequestsstructuredOutput: trueonly when the configured harness declaressupportsStructuredOutput(capability-gated; non-supporting harnesses keep the untouched plain-text path and can never seeUnsupportedStructuredOutputError).result.structuredfirst; falls back to the existingextractJsonPayloadrepair on raw stdout; the one-shotSTRICT_JSON_REMINDERcorrective re-run still applies on the fallback path. Transcript detectors and debug dumps are untouched (they scan raw streams in the runner).pm-desktop needs no code change — it builds the engine via
createEngine(src/main/session.ts:481), so it gets structured mode and readable streaming automatically across multi-ticket/edit flows.Tests — 14 new cases in
packages/pm/engine.test.ts: JSON flag emitted for a supporting harness, plain-path fallback for non-supporting harnesses, fallback whenstructured.ok === falseor payload fails validation, envelope/NDJSON/message-array unwrapping, narration repair inside reply text, corrective re-run intact, both opt-outs, and stream-tap behavior (readable text, raw passthrough, flush, stderr untouched).Docs —
docs/pm/configuration.mdgains a "Structured (JSON) output" section documenting the behavior andAGENT_STRUCTURED_OUTPUT.Verification:
bun run typecheck,lint,test, andbuildall pass across the monorepo (pm: 352 tests, pm-desktop: 543, agent-harness unchanged).This PR was automatically created by @devintern/code