Skip to content

feat(agent-loop): Reasonix parity for scavenge source + truncation order (dirge-7bwx, dirge-ngic) - #179

Merged
yogthos merged 2 commits into
mainfrom
feat/pillar2-reasonix-parity-7bwx-ngic
May 28, 2026
Merged

feat(agent-loop): Reasonix parity for scavenge source + truncation order (dirge-7bwx, dirge-ngic)#179
yogthos merged 2 commits into
mainfrom
feat/pillar2-reasonix-parity-7bwx-ngic

Conversation

@yogthos

@yogthos yogthos commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Phase 2.5 of the three-pillar parity work — closes the last two gaps between dirge's agent loop and DeepSeek-Reasonix's reference repair pipeline (repair/index.ts:65-123).

  • dirge-ngicbuild_scavenge_source now merges both ContentBlock::Thinking AND ContentBlock::Text. Previously only Thinking was scanned, so any DSML invoke that streamed as visible Text (the common case on Anthropic cache hits) was lost — orphan call, loop stalls waiting for a tool result that never dispatches. Mirrors Reasonix loop.ts:910-913repair/index.ts:71.
  • dirge-7bwx — truncation repair is hoisted into a new apply_truncation_repair helper run BETWEEN scavenge merge and storm filter, matching Reasonix order at repair/index.ts:88-121. Previously dirge ran the brace-closer inside validate_and_repair at dispatch — AFTER storm — so two streams whose raw args differ but heal identically slipped past dedupe. The helper also promotes already-valid Value::String JSON to its parsed form so canonical_json sees the same shape regardless of stream-vs-Object representation. Hard-fallback preserves the original string (Reasonix invariant repair/index.ts:93-102).

Removed the now-redundant in-validator pre-pass; try_truncation_repair stays public as defense-in-depth for paths that bypass the loop. The dispatch-level end-to-end test was rewired to drive apply_truncation_repair before execute_tool_calls_sequential (matching the production order). Validator contract tests now document the new contract: Value::String at validate_and_repair surfaces as Err, never silently re-repaired.

Test plan

  • cargo build --bin dirge clean
  • cargo fmt --all clean
  • RUSTFLAGS="-D warnings" cargo test --bin dirge — 1630 passed, 0 failed
  • 7 new proving tests under run::tests:
    • scavenge_source_recovers_dsml_invoke_from_text_only
    • scavenge_source_concatenates_thinking_and_text
    • scavenge_source_skips_non_text_blocks
    • truncation_repair_canonicalizes_divergent_streams_before_storm
    • truncation_repair_preserves_raw_on_hard_fallback
    • truncation_repair_leaves_already_parsed_args_alone
    • truncation_repair_end_to_end_through_dispatch (rewired)
  • CI matrix (8 build variants + 3 test variants) green

Yogthos added 2 commits May 28, 2026 11:04
…der (dirge-7bwx, dirge-ngic)

Two Phase 2.5 fixes bringing the dirge agent loop into byte-for-byte
parity with DeepSeek-Reasonix's repair pipeline (repair/index.ts:65-123).

dirge-ngic — scavenge inspects both Thinking AND Text content
  Reasonix combines reasoning+content at loop.ts:910-913 →
  repair/index.ts:71. Dirge previously merged only ContentBlock::Thinking,
  losing any DSML invoke that streamed as plain Text — the common case
  on Anthropic cache hits, where the model emits visible content rather
  than a thinking block. Orphan calls in that path went unrecovered and
  the loop stalled waiting for a tool result that never dispatched.
  build_scavenge_source now concatenates both block types.

dirge-7bwx — truncation repair runs BEFORE storm
  Reasonix order: truncation (repair/index.ts:88-109) then storm
  (:113-121). Dirge previously ran the brace-closer inside
  validate_and_repair at dispatch time — AFTER storm. That meant two
  streams whose raw arg strings differ but heal to the same form
  survived storm (distinct pre-repair signatures), then dispatched
  identically. The fix hoists truncation into a new
  apply_truncation_repair helper called between scavenge merge and
  storm filter, so storm sees the canonical post-repair signature and
  dedupes correctly. Also promotes Value::String args that already
  parse as JSON to their parsed form, so storm's canonical_json sees
  the same shape for stream-vs-Object representations of the same
  call. Hard-fallback preserves the original raw string — never
  silently substitutes {} (Reasonix invariant repair/index.ts:93-102).

Removed the now-redundant in-validator pre-pass from
validate_and_repair. The try_truncation_repair helper stays public
as defense-in-depth for paths that bypass the loop. The
dispatch-level end-to-end test was rewired to drive
apply_truncation_repair before execute_tool_calls_sequential
(matching the production pipeline order). Updated the validator
contract tests to document the new behavior: args arriving as
Value::String at validate_and_repair now surface as Err rather than
being silently re-repaired at dispatch.

7 new proving tests under run::tests cover the merged source builder
(Text-only, mixed, non-text-skipped) and the truncation hoist
(canonicalization across divergent streams, hard-fallback preservation,
already-parsed pass-through).

1630 tests pass with -D warnings.
Independent verification turned up 6 gaps in the Phase 2.5 parity work.
This commit closes all of them.

#1 HIGH — `truncations_fixed` now bumps on hard-fallback too. Reasonix
   counts both success (`repair/index.ts:105`) and unrecoverable
   (`repair/index.ts:99`) under the same counter; dirge was dropping
   the latter, under-reporting exactly the cases operators need most.
   `apply_truncation_repair` now records the kind whenever the closer
   ran, not just on successful repair.

#2 MEDIUM — closer notes are now surfaced to the model. Reasonix
   pushes `r.notes` into `report.notes` with `[<tool>]` prefix on
   success and `[<tool>] ⚠️ TRUNCATION UNRECOVERABLE: ...` on
   fallback (`repair/index.ts:100-101, :106`), then carries them
   into the next-turn assistant input. Dirge now stashes them
   per-call-id on a new `LoopConfig.truncation_notes` shared map;
   `prepare_tool_call` drains them and appends to `repair_notes`,
   which `prepend_notes_to_result` (already in place for
   relational-default notes) prepends to the tool result content
   so the model sees the repair in the same turn.

#3 MEDIUM — added end-to-end wiring tests through `run_agent_loop`.
   The prior 7 tests proved the helpers worked in isolation; they
   did not prove the loop calls them in the right order. Two new
   tests drive the full canned-stream loop:
   - `dirge_7bwx_end_to_end_storm_dedupes_after_truncation_repair`:
     three tool calls with different truncated raw strings that
     heal identically. Storm threshold=3 → the third must be
     suppressed (only possible if truncation runs before storm).
   - `dirge_ngic_end_to_end_orphan_dsml_in_text_dispatches`:
     DSML invoke in `ContentBlock::Text` ONLY (no Thinking, no
     declared ToolCall) must dispatch (only possible if
     `build_scavenge_source` includes Text).

#4 MEDIUM — removed dead `try_truncation_repair`. It was kept as
   "defense in depth" but marked `#[allow(dead_code)]`, so the
   safety claim was illusory. Now actually gone; direct callers
   can use `repair_truncated_json` for the brace-closer if needed.
   The `validate_and_repair` block-comment was updated to reflect
   the new contract.

#5 LOW — `truncation_repair_canonicalizes_divergent_streams_before_storm`
   tested canonicalization in isolation; the new end-to-end #3 tests
   exercise the actual storm dedupe path that depends on the
   String→Object promotion. The promotion itself is now also
   covered with an explicit note in `apply_truncation_repair`'s
   doc — it has no Reasonix analog (their args are always strings)
   and is dirge-specific compensation for mixed arg representations.

#6 LOW — added a comment near `storm.rs::inspect` documenting the
   implicit dependency on `serde_json` being built without the
   `preserve_order` feature. If feature unification ever enables
   it, storm dedupe regresses silently; the comment points at the
   workaround (`run::canonical_json`) and notes Reasonix has the
   same fragility at `repair/index.ts:127`.

`LoopConfig` gained the new `truncation_notes` field; all
constructors (production + tests) were updated. `Clone` impl
threaded through.

1632 tests pass with `-D warnings` (was 1630; +3 new, -1 removed).
@yogthos
yogthos merged commit 074af0e into main May 28, 2026
9 checks passed
@yogthos
yogthos deleted the feat/pillar2-reasonix-parity-7bwx-ngic branch May 28, 2026 16:09
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…asonix parity

Independent verification turned up 6 gaps in the Phase 2.5 parity work.
This commit closes all of them.

#1 HIGH — `truncations_fixed` now bumps on hard-fallback too. Reasonix
   counts both success (`repair/index.ts:105`) and unrecoverable
   (`repair/index.ts:99`) under the same counter; dirge was dropping
   the latter, under-reporting exactly the cases operators need most.
   `apply_truncation_repair` now records the kind whenever the closer
   ran, not just on successful repair.

#2 MEDIUM — closer notes are now surfaced to the model. Reasonix
   pushes `r.notes` into `report.notes` with `[<tool>]` prefix on
   success and `[<tool>] ⚠️ TRUNCATION UNRECOVERABLE: ...` on
   fallback (`repair/index.ts:100-101, :106`), then carries them
   into the next-turn assistant input. Dirge now stashes them
   per-call-id on a new `LoopConfig.truncation_notes` shared map;
   `prepare_tool_call` drains them and appends to `repair_notes`,
   which `prepend_notes_to_result` (already in place for
   relational-default notes) prepends to the tool result content
   so the model sees the repair in the same turn.

#3 MEDIUM — added end-to-end wiring tests through `run_agent_loop`.
   The prior 7 tests proved the helpers worked in isolation; they
   did not prove the loop calls them in the right order. Two new
   tests drive the full canned-stream loop:
   - `dirge_7bwx_end_to_end_storm_dedupes_after_truncation_repair`:
     three tool calls with different truncated raw strings that
     heal identically. Storm threshold=3 → the third must be
     suppressed (only possible if truncation runs before storm).
   - `dirge_ngic_end_to_end_orphan_dsml_in_text_dispatches`:
     DSML invoke in `ContentBlock::Text` ONLY (no Thinking, no
     declared ToolCall) must dispatch (only possible if
     `build_scavenge_source` includes Text).

#4 MEDIUM — removed dead `try_truncation_repair`. It was kept as
   "defense in depth" but marked `#[allow(dead_code)]`, so the
   safety claim was illusory. Now actually gone; direct callers
   can use `repair_truncated_json` for the brace-closer if needed.
   The `validate_and_repair` block-comment was updated to reflect
   the new contract.

dirge-code#5 LOW — `truncation_repair_canonicalizes_divergent_streams_before_storm`
   tested canonicalization in isolation; the new end-to-end #3 tests
   exercise the actual storm dedupe path that depends on the
   String→Object promotion. The promotion itself is now also
   covered with an explicit note in `apply_truncation_repair`'s
   doc — it has no Reasonix analog (their args are always strings)
   and is dirge-specific compensation for mixed arg representations.

dirge-code#6 LOW — added a comment near `storm.rs::inspect` documenting the
   implicit dependency on `serde_json` being built without the
   `preserve_order` feature. If feature unification ever enables
   it, storm dedupe regresses silently; the comment points at the
   workaround (`run::canonical_json`) and notes Reasonix has the
   same fragility at `repair/index.ts:127`.

`LoopConfig` gained the new `truncation_notes` field; all
constructors (production + tests) were updated. `Clone` impl
threaded through.

1632 tests pass with `-D warnings` (was 1630; +3 new, -1 removed).
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…ix-parity-7bwx-ngic

feat(agent-loop): Reasonix parity for scavenge source + truncation order (dirge-7bwx, dirge-ngic)
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