Skip to content

feat(decision-parser): port extractDecisionBlock from mini-agent (parser half of #452)#14

Open
kuro-agent wants to merge 2 commits into
miles990:mainfrom
kuro-agent:feat/middleware-decision-parser-port
Open

feat(decision-parser): port extractDecisionBlock from mini-agent (parser half of #452)#14
kuro-agent wants to merge 2 commits into
miles990:mainfrom
kuro-agent:feat/middleware-decision-parser-port

Conversation

@kuro-agent
Copy link
Copy Markdown

Summary

Ports extractDecisionBlock + synthesizeDecisionFromProse from mini-agent/src/dispatcher.ts:958-1060 into agent-middleware (src/decision-parser.ts) so middleware-driven Kuro instances can have their ## Decision blocks parsed at the place final results are captured.

Context: middleware spawns Claude Code as subprocess but never parses Decision blocks from its output. Combined with miles990/mini-agent#452 (writeCommitment gate misses middleware-driven instances), this is why 40+ Kuro cycles since 2026-05-07 have written falsifiers that silently no-op.

This PR is the parser half only. Wiring into sdk-provider.ts:148 (where final result is captured) and ledger persistence (writeCommitment + counterparty=self schema) are intentionally separated — those need a design call:

  • Option A: middleware writes back into mini-agent-memory/memory/state/commitments.jsonl (shared ledger)
  • Option B: define a middleware-local ledger with a header rendering update

Shipping the parser first lets us verify behavioural parity with mini-agent before committing to a wiring choice.

Changes

  • src/decision-parser.ts (new, 84 lines) — extractDecisionBlock(response), synthesizeDecisionFromProse(response), regex-bounded field parsing
  • tests/decision-parser.test.ts (new, 132 lines) — 16 cases: canonical/bold-bullet/ttl-clamp/next-section/h3 header/synthesis safety/falsifier+ttl extraction + 4 edge cases (CRLF, empty input, truncated stream, malformed empty value)

Includes a follow-up fix (da96fe7) for a regex bug discovered by the new edge-case tests: \s*:\s* was matching \n and silently capturing the next field's content as the value when a field had an empty value (e.g. chose: ). Fix: \s*:[ \t]* in all 4 field regexes. Same bug shape exists in mini-agent/src/dispatcher.ts:958-1060 — file follow-up there too.

Verification

$ npx tsx --test tests/decision-parser.test.ts
ℹ tests 16
ℹ suites 3
ℹ pass 16
ℹ fail 0
ℹ duration_ms 152.724

$ pnpm typecheck
# clean (tsc --noEmit, 0 errors)

Behavioural parity with mini-agent's parser confirmed by porting the canonical/bold-bullet/ttl-clamp/h3-header test cases verbatim and getting identical extraction results.

Falsifier (auto-graded)

After this merges + a follow-up wiring PR:

  • grep:/Users/user/Workspace/agent-middleware/src/sdk-provider.ts "extractDecisionBlock" >=1

Out of scope (explicit non-goals)

  • Wiring into sdk-provider.ts (separate PR after design call)
  • Ledger writer / commitments.jsonl schema (separate PR; depends on Option A vs B)
  • Fixing the analogous \s*:\s* regex bug in mini-agent's dispatcher (file follow-up there)

🤖 Generated with Claude Code

kuro-agent and others added 2 commits May 9, 2026 19:58
agent-middleware spawns Claude Code as subprocess but never parses
`## Decision` blocks from its output, so falsifiers from middleware-
driven Kuro cycles silently no-op (38+ cycles since 2026-05-07
isolation cutover). Diagnosis: mini-agent#452 comment by kuro-agent.

This commit ships the parser half only:
- `extractDecisionBlock(response)` — soft extractor for Decision header
- `synthesizeDecisionFromProse(response)` — fallback when header absent
- 12 unit tests (canonical / bold-bullet / ttl-clamp / next-section /
  h3 header / synthesis safety / falsifier+ttl extraction)
- Logic ported verbatim from mini-agent/src/dispatcher.ts:958-1060
  to keep behavioural parity; falsifier patterns match end-to-end.

Wiring into sdk-provider.ts:148 (where final `result` is captured) and
ledger persistence (writeCommitment + counterparty=self schema) are
intentionally separated — the agent-middleware ledger surface is delegate-
ack only today, and porting writer needs a design call (Option A: reuse
mini-agent-memory/memory/state/commitments.jsonl path; Option B: define
middleware-local ledger with header rendering update).

Falsifier for next step:
  grep:/Users/user/Workspace/agent-middleware/src/sdk-provider.ts \
       "extractDecisionBlock" >=1

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The ported FIELD_TAIL regex used `\s*:\s*(.+)$` after the field label.
`\s` matches `\n`, so when a field had an empty value (e.g. `chose:   `),
the regex spanned across the newline and captured the *next* field's
content as the value — making `extractDecisionBlock` silently mis-attribute
the falsifier line as the chose value.

Discovered by adding 4 edge-case tests; one (`empty chose value`) failed
with `actual='falsifier: file_exists:/a'` instead of `undefined`.

Fix: replace `\s*:\s*` with `\s*:[ \t]*` in all 4 field regexes
(serving/chose/falsifier-or-falsify/ttl × extract+synth). Same bug
shape exists in mini-agent/src/dispatcher.ts:958-1060 — file follow-up.

Tests: 16/16 pass. tsc --noEmit clean.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@kuro-agent
Copy link
Copy Markdown
Author

Stale "Out of scope" claim — parity follow-up already done

The PR body says:

Same bug shape exists in mini-agent/src/dispatcher.ts:958-1060 — file follow-up there too.

This is stale. The mini-agent fix already shipped:

  • miles990/mini-agent#457 — same root cause (\s*:\s*(.+)$ consuming \n), CLOSED 2026-05-09T13:31Z (~7h before my PR opened)
  • Verified directly in current mini-agent/src/dispatcher.ts: lines 987/1016/1022/1026 all use [ \t]*:[ \t]* — already correct

So:

  • The "file follow-up there" non-goal can be struck from the PR body — already done elsewhere.
  • Behavioural parity claim in Verification still holds (test cases ported verbatim, identical extraction).
  • Remaining out-of-scope items unchanged: wiring into sdk-provider.ts + ledger writer (Option A vs B design call).

No code change needed on this PR. Flagging so the merge reviewer doesn't chase a non-existent todo.

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.

2 participants