feat(agent-loop): Reasonix parity for scavenge source + truncation order (dirge-7bwx, dirge-ngic) - #179
Merged
Merged
Conversation
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).
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)
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.
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).build_scavenge_sourcenow merges bothContentBlock::ThinkingANDContentBlock::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 Reasonixloop.ts:910-913→repair/index.ts:71.apply_truncation_repairhelper run BETWEEN scavenge merge and storm filter, matching Reasonix order atrepair/index.ts:88-121. Previously dirge ran the brace-closer insidevalidate_and_repairat dispatch — AFTER storm — so two streams whose raw args differ but heal identically slipped past dedupe. The helper also promotes already-validValue::StringJSON to its parsed form socanonical_jsonsees the same shape regardless of stream-vs-Object representation. Hard-fallback preserves the original string (Reasonix invariantrepair/index.ts:93-102).Removed the now-redundant in-validator pre-pass;
try_truncation_repairstays public as defense-in-depth for paths that bypass the loop. The dispatch-level end-to-end test was rewired to driveapply_truncation_repairbeforeexecute_tool_calls_sequential(matching the production order). Validator contract tests now document the new contract:Value::Stringatvalidate_and_repairsurfaces asErr, never silently re-repaired.Test plan
cargo build --bin dirgecleancargo fmt --allcleanRUSTFLAGS="-D warnings" cargo test --bin dirge— 1630 passed, 0 failedrun::tests:scavenge_source_recovers_dsml_invoke_from_text_onlyscavenge_source_concatenates_thinking_and_textscavenge_source_skips_non_text_blockstruncation_repair_canonicalizes_divergent_streams_before_stormtruncation_repair_preserves_raw_on_hard_fallbacktruncation_repair_leaves_already_parsed_args_alonetruncation_repair_end_to_end_through_dispatch(rewired)