Skip to content

fix(context): pair tool calls by id instead of adjacency - #1280

Merged
murdore merged 1 commit into
releasefrom
fix/tool-pair-identity
Aug 6, 2026
Merged

murdore merged 1 commit into
releasefrom
fix/tool-pair-identity

Conversation

@pdogra1299

@pdogra1299 pdogra1299 commented Aug 5, 2026 •

Copy link
Copy Markdown
Collaborator

Description

repairToolPairs matched tool_call to tool_result by adjacency. A single agent step with parallel tool calls is persisted as every call followed by every result (flushPendingToolExecutions), so tool_call is routinely followed by another tool_call in perfectly healthy history. The repair read that as an orphan and injected a [Tool result unavailable] placeholder over a result that was present two entries later.

Measured on the exact shape the Redis manager writes:

in : assistant  tool_call:read_file  tool_call:grep  tool_result:read_file  tool_result:grep
out: assistant  tool_call:read_file  tool_result:read_file(SYNTHETIC)  tool_call:grep  tool_result:read_file  tool_result:grep
     2 calls -> 3 results

This ran after every compaction, so any session using parallel tool calls was corrupted the moment it compacted.

Root cause: ChatMessage had no toolCallId. The id was computed in flushPendingToolExecutions, used to build a name lookup, then discarded — leaving position as the only signal, and position is wrong for every parallel batch.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Motivation and Context

Providers hard-reject an unmatched tool_call/tool_result, so a corrupted batch turns into a failed turn. Two further paths could emit orphans that nothing caught:

  • buildContextFromPointer slices history at the summary pointer with no repair on read
  • the summary pointer itself could land inside a tool batch, leaving the recent window opening on an orphaned tool_result

Changes

  • toolCallId added to ChatMessage (optional — backward compatible) and persisted on both sides
  • repairToolPairs rewritten batch- and ID-aware, with positional fallback scoped to the batch for pre-existing sessions
  • repair now also runs on the read path, covering pointer slices and sessions interrupted mid-tool-batch
  • snapSplitToBatchBoundary keeps the summary pointer off a batch boundary, applied in both summarizers (including after the clamp in structuredSummarizer, which could otherwise pull the boundary back in)
  • duplicate toolCallId results are dropped rather than over-pairing a call

Testing

New no-API suite pnpm run test:tool-pairing — 15/15. Covers parallel batches (id and legacy), five-wide batches, out-of-order results, genuine orphans in both directions, duplicate ids, empty-content results, multiple independent batches, and randomized split points.

Verified the suite fails loudly (✗, exit 1) on a deliberately broken assertion — per the CLAUDE.md hazard where isExpectedProviderError() can downgrade a real failure to ⊘.

  • tsc --noEmit --strict: 0 errors
  • eslint: 0 errors
  • test:context: 36 pass / 5 fail / 18 skip — byte-identical failure set to release (all 5 pre-existing and environmental: 4x LiteLLM model-access denial, 1x dist/lib/ layout mismatch)

Notes for reviewers

Stacked: this is the base of a 3-PR stack. fix/token-accounting must not merge before this one — correct token accounting makes compaction fire far more often, which would amplify this corruption without the fix underneath.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability for conversations involving multiple or parallel tool calls.
    • Prevented missing, duplicated, or mismatched tool results from disrupting conversation history.
    • Fixed context summarization from splitting related tool-call exchanges apart.
    • Preserved tool-call associations when conversations are stored and retrieved.
  • Improvements

    • Added automatic recovery for incomplete tool-call exchanges.
    • Improved compatibility with older conversations without tool-call identifiers.
    • Enhanced conversation context integrity during filtering and compaction.
  • Tests

    • Added coverage for tool-call pairing, recovery, and summarization boundary scenarios.

@github-actions

github-actions Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: 2271f0f8051893d79548b15b3a1841469954cc9f
  • Message: fix(context): pair tool calls by id instead of adjacency
  • Author: Parth Dogra

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fdac986-e903-4614-9347-08398baf82a9

📥 Commits

Reviewing files that changed from the base of the PR and between 3c5eb40 and 2271f0f.

📒 Files selected for processing (9)
  • package.json
  • src/lib/context/stages/structuredSummarizer.ts
  • src/lib/context/summarizationEngine.ts
  • src/lib/context/toolPairRepair.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/context.ts
  • src/lib/types/conversation.ts
  • src/lib/utils/conversationMemory.ts
  • test/continuous-test-suite-tool-pairing.ts

📝 Walkthrough

Walkthrough

The change adds identifier-based tool pairing, contiguous batch repair, safe summarization boundaries, conversation-history repair, and a network-free continuous test suite.

Changes

Tool Pair Repair

Layer / File(s) Summary
Tool pairing contracts and persistence
src/lib/types/conversation.ts, src/lib/types/context.ts, src/lib/core/redisConversationMemoryManager.ts
ChatMessage supports optional toolCallId. Repair batches define calls, results, and exclusive end indexes. Redis persists valid identifiers.
Batch-aware tool repair
src/lib/context/toolPairRepair.ts
Tool batches pair by identifier when available and positionally otherwise. Missing counterparts are synthesized, duplicate results are removed, and split boundaries avoid batch interiors.
History repair and safe summarization
src/lib/utils/conversationMemory.ts, src/lib/context/stages/structuredSummarizer.ts, src/lib/context/summarizationEngine.ts
Conversation reads repair filtered histories and record repair metrics. Summarization boundaries are snapped and invalid boundaries are rejected.
Pairing validation and test execution
test/continuous-test-suite-tool-pairing.ts, package.json
The continuous suite covers pairing, repair, duplicate results, independent batches, boundary snapping, and randomized split integrity. A package script runs the suite.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConversationMemory
  participant repairToolPairs
  participant StructuredSummarizer
  participant SummarizationEngine

  ConversationMemory->>repairToolPairs: repair filtered conversation history
  repairToolPairs-->>ConversationMemory: return balanced messages
  StructuredSummarizer->>StructuredSummarizer: snap split to batch boundary
  SummarizationEngine->>SummarizationEngine: reject full-array snapped split
Loading

Possibly related PRs

Suggested reviewers: tara-ag

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change from adjacency-based to ID-based tool-call pairing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tool-pair-identity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
test/continuous-test-suite-tool-pairing.ts (2)

200-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the duplicate result is actually dropped.

The current assertion only checks balance. A batch of 1 call and 2 duplicate results is also "balanced" if repair adds a synthetic call for the duplicate. Assert the result count and repaired so the test locks the documented changed contract in repairBatch.

💚 Proposed stronger assertions
     const out = repairToolPairs(input);
     const c = counts(out.messages);
     assert(c.calls === c.results, "duplicate id left the batch unbalanced");
+    assert(out.repaired, "dropped duplicate did not mark the history repaired");
+    assert(c.calls === 1 && c.results === 1, "duplicate result was not dropped");
+    assert(
+      out.orphanedCallsFixed === 0 && out.orphanedResultsFixed === 0,
+      "duplicate removal produced synthetic placeholders",
+    );
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/continuous-test-suite-tool-pairing.ts` around lines 200 - 209,
Strengthen the test named “duplicate result ids never over-pair a call” to
assert that the duplicate result is dropped rather than balanced by adding a
synthetic call. Verify the output contains one result and that the repair
result’s `repaired`/`changed` contract is set as documented by `repairBatch`,
while preserving the existing input and balance checks.

274-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for two adjacent tool batches with no text between them.

Every snap test separates batches with a text message or uses one batch. The suite therefore does not cover a contiguous run that holds two batches, which is the case where snapSplitToBatchBoundary skips more than one batch. Add a case such as [call, result, call, result, text] with splitIndex = 2, and a case where the whole array is one tool run, to assert the returned index stays inside the array. This locks the boundary behavior discussed on src/lib/context/toolPairRepair.ts Lines 191-219.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/continuous-test-suite-tool-pairing.ts` around lines 274 - 284, Extend
the snapSplitToBatchBoundary tests with adjacent tool batches lacking an
intervening text message, using [call, result, call, result, text] and
splitIndex 2 to verify snapping past both batches. Also add a case where the
entire array is one tool run and assert the returned index remains within the
array, preserving the boundary behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/context/summarizationEngine.ts`:
- Around line 210-213: Cap the value passed to snapSplitToBatchBoundary in the
summarization split calculation so it never reaches messages.length, preserving
at least one recent message. Update the return expression in the surrounding
split-index logic and keep the existing minimum of one message summarized.

In `@src/lib/context/toolPairRepair.ts`:
- Around line 9-14: Replace the stale flushPendingToolExecutions reference with
flushPendingToolData in the module header of src/lib/context/toolPairRepair.ts
lines 9-14 and the toolCallId documentation in src/lib/types/conversation.ts
lines 354-364; no implementation changes are required.
- Around line 191-219: Update snapSplitToBatchBoundary so its backward and
forward walks stop at a tool_result-to-tool_call transition rather than treating
the entire contiguous tool run as one batch. Preserve the existing boundary
validation and return the closest legal split, including a transition within a
run and avoiding messages.length when an earlier batch boundary is available.

---

Nitpick comments:
In `@test/continuous-test-suite-tool-pairing.ts`:
- Around line 200-209: Strengthen the test named “duplicate result ids never
over-pair a call” to assert that the duplicate result is dropped rather than
balanced by adding a synthetic call. Verify the output contains one result and
that the repair result’s `repaired`/`changed` contract is set as documented by
`repairBatch`, while preserving the existing input and balance checks.
- Around line 274-284: Extend the snapSplitToBatchBoundary tests with adjacent
tool batches lacking an intervening text message, using [call, result, call,
result, text] and splitIndex 2 to verify snapping past both batches. Also add a
case where the entire array is one tool run and assert the returned index
remains within the array, preserving the boundary behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 85c03c0e-64f1-4e3c-b063-0df0ef402805

📥 Commits

Reviewing files that changed from the base of the PR and between cbcf85e and 78a90da.

📒 Files selected for processing (9)
  • package.json
  • src/lib/context/stages/structuredSummarizer.ts
  • src/lib/context/summarizationEngine.ts
  • src/lib/context/toolPairRepair.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/context.ts
  • src/lib/types/conversation.ts
  • src/lib/utils/conversationMemory.ts
  • test/continuous-test-suite-tool-pairing.ts

Comment thread src/lib/context/summarizationEngine.ts Outdated
Comment thread src/lib/context/toolPairRepair.ts
Comment thread src/lib/context/toolPairRepair.ts
@Tara-ag

Tara-ag commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔍 Yama Code Review Summary

I've completed a thorough review of PR #1280 "batch-aware tool pair repair" following the Yama methodology. Here's my assessment:


Decision: APPROVED ✅


Review Scope & Methodology

  • Reviewed all 9 changed files systematically
  • Analyzed impact via code knowledge graph (risk score: 0.65, moderate)
  • Verified backward compatibility of ChatMessage type extension
  • Confirmed test coverage through comprehensive test suite analysis
  • Checked integration points in conversation memory, context compaction, and Redis storage

Findings

No blocking issues found. All CRITICAL/MAJOR focus areas pass:

Focus Area Status Notes
Security ✅ Pass No secrets, no injection risks, no auth flaws
Architecture & CLAUDE.md Rules ✅ Pass Types in canonical location (src/lib/types/), proper barrel imports, zero interface, backward compatible SDK API
Type Safety & Correctness ✅ Pass Optional toolCallId?: string field, proper error handling via return (not throw), correct typing throughout
Out-of-Diff Impact ✅ Pass Analysis shows 67 affected flows, 12 test gaps (all new tests added), self-contained change
Performance ✅ Pass Fast path optimization for text-only conversations, O(n) complexity maintained
Testing & Documentation ✅ Pass New comprehensive test suite with 13+ edge cases covering all scenarios

Detailed Analysis

Files Reviewed:

  1. package.json - Trivial addition of test script ✅
  2. src/lib/types/conversation.ts - Added toolCallId?: string to ChatMessage (backward compatible via optional field) ✅
  3. src/lib/types/context.ts - Added RepairToolBatch type definition ✅
  4. src/lib/context/toolPairRepair.ts - Complete rewrite with batch-aware repair, ID-based pairing, head-cut orphan handling ✅
  5. src/lib/utils/conversationMemory.ts - Integrated repairToolPairs call on read ✅
  6. src/lib/core/redisConversationMemoryManager.ts - Persist toolCallId for both tool calls and results ✅
  7. src/lib/context/stages/structuredSummarizer.ts - Uses snapSplitToBatchBoundary ✅
  8. src/lib/context/summarizationEngine.ts - Uses snapSplitToBatchBoundary ✅
  9. test/continuous-test-suite-tool-pairing.ts - New comprehensive test suite ✅

Key Implementation Highlights:

  • Batch-Aware Repair: No longer relies solely on adjacency; uses toolCallId for accurate pairing even when results arrive out of order
  • Parallel Batch Handling: Recognizes healthy parallel batches (all calls before all results) and leaves them untouched
  • Backward Compatible: Legacy sessions without toolCallId work in positional pairing mode
  • Orphan Prevention: snapSplitToBatchBoundary prevents summarization from splitting tool batches
  • Comprehensive Tests: Covers all edge cases including empty content, duplicate IDs, head-cuts, multiple batches, and randomized split positions

Impact on Existing Code

  • Blast Radius: 67 execution flows affected (moderate risk)
  • Dependents at Risk: None — change is self-contained within context/memory layers
  • Breaking Changes: None — toolCallId is optional, existing sessions continue working
  • Architectural Hotspots: Not applicable — this is an enhancement to existing compaction logic

Recommendation

This PR is safe to merge. It addresses a real bug where adjacency-based repair was incorrectly modifying healthy parallel tool batches, introduces a robust fix with proper testing, and maintains full backward compatibility.


Reviewed by Yama autonomous code review agent following NeuroLink enterprise standards.

@Tara-ag

Tara-ag commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔒 MAJOR: Types not exported from barrel — The new types RepairToolBatch and RepairResult are defined in src/lib/types/context.ts but the barrel (src/lib/types/index.ts) does not export './context.js'. This means types cannot be imported via the standard barrel pattern (e.g., import type { X } from '../types/index.js') and must be imported directly from the source file, violating CLAUDE.md Rule 13 ("Barrel-only imports for internal types"). Fix: Add 'export * from "./context.js";' to src/lib/types/index.ts to include the newly added types in the barrel exports.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing PR #1280 - fixing tool pairing logic to be ID-aware instead of adjacency-based. Found one MAJOR issue: types not exported from barrel.

@Tara-ag

Tara-ag commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Summary for PR #1280: "fix(context): pair tool calls by id instead of adjacency"

Decision: CHANGES_REQUESTED

Findings (1 MAJOR issue):

Severity Category Title File Line Description
🔒 MAJOR Architecture & CLAUDE.md Critical Rules Types not exported from barrel src/lib/types/index.ts 0 The new types RepairToolBatch and RepairResult are defined in src/lib/types/context.ts but the barrel (src/lib/types/index.ts) does not export './context.js'. This violates CLAUDE.md Rule 13 ("Barrel-only imports for internal types").

Inline Comment Posted:

A comment has been posted on src/lib/types/index.ts explaining the missing barrel export issue.

Fix Required:

Add 'export * from "./context.js";' to src/lib/types/index.ts to include the newly added types in the barrel exports.

Positive Aspects:

  • ✅ The core fix (ID-based tool pairing) is correctly implemented and addresses a real bug
  • ✅ Batch processing properly handles parallel tool calls
  • ✅ Backward compatibility is maintained (optional toolCallId field)
  • ✅ Comprehensive test coverage with edge cases
  • ✅ No breaking changes to existing APIs
  • ✅ Type changes are additive and safe

Impact Analysis:

The code knowledge graph shows this change affects ~8200 nodes across the codebase, primarily through the context summarization pipeline and conversation memory management. The changes are self-contained to the context/toolPairRepair module with no unexpected side effects.

Review Scope:

This review covered all 9 changed files:

  1. package.json (test script addition) - ✅ Approved
  2. structuredSummarizer.ts (summary snapping) - ✅ Approved
  3. summarizationEngine.ts (summary snapping) - ✅ Approved
  4. toolPairRepair.ts (core ID-based repair logic) - ✅ Approved
  5. redisConversationMemoryManager.ts (type import) - ✅ Approved
  6. context.ts (new type definitions) - ⚠️ Missing barrel export
  7. conversation.ts (ChatMessage augmentation) - ✅ Approved
  8. conversationMemory.ts (no changes) - ✅ Approved
  9. continuous-test-suite-tool-pairing.ts (comprehensive tests) - ✅ Approved

Recommendation:

Request changes until the barrel export issue is fixed. Once resolved, this PR should be approved as it provides a valuable bug fix that correctly handles parallel tool calls and maintains backward compatibility.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔒 MAJOR — Types not exported from barrel (src/lib/types/index.ts)

The new types RepairToolBatch and RepairResult are defined in src/lib/types/context.ts but the barrel does not export './context.js', violating CLAUDE.md Rule 13.

Fix required: Add export * from "./context.js"; to src/lib/types/index.ts


Review Summary for PR #1280: "fix(context): pair tool calls by id instead of adjacency"

Decision: CHANGES_REQUESTED

Findings:

Severity Category Title File Description
🔒 MAJOR Architecture & CLAUDE.md Critical Rules Types not exported from barrel src/lib/types/index.ts The new types RepairToolBatch and RepairResult are defined in src/lib/types/context.ts but the barrel does not export './context.js', violating CLAUDE.md Rule 13 ("Barrel-only imports for internal types").

Positive Aspects:

  • ✅ Core fix (ID-based tool pairing) correctly addresses a real bug where parallel tool calls were misidentified as orphans
  • ✅ Batch processing properly handles parallel tool calls within a single step
  • ✅ Backward compatibility maintained via optional toolCallId field
  • ✅ Comprehensive test coverage with edge cases for various message patterns
  • ✅ No breaking changes to existing APIs
  • ✅ Type changes are additive and safe

Impact Analysis:

The code knowledge graph shows this change affects ~8,200 nodes across the codebase, primarily through the context summarization pipeline and conversation memory management. Changes are self-contained to the context/toolPairRepair module with no unexpected side effects.

Files Reviewed (9 total):

  1. package.json - ✅ Approved (test script addition)
  2. structuredSummarizer.ts - ✅ Approved (summary snapping logic)
  3. summarizationEngine.ts - ✅ Approved (summary snapping logic)
  4. toolPairRepair.ts - ✅ Approved (core ID-based repair logic)
  5. redisConversationMemoryManager.ts - ✅ Approved (type import updated)
  6. context.ts - ⚠️ Missing barrel export (see inline comment)
  7. conversation.ts - ✅ Approved (ChatMessage augmentation)
  8. conversationMemory.ts - ✅ Approved (no functional changes)
  9. continuous-test-suite-tool-pairing.ts - ✅ Approved (comprehensive tests)

Recommendation:

Request changes until the barrel export issue is fixed. Once resolved, this PR should be approved as it provides a valuable bug fix that correctly handles parallel tool calls and maintains backward compatibility.

@Tara-ag

Tara-ag commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🛡️ Yama Review Verdict: CHANGES_REQUESTED

Severity counts — 🔒 CRITICAL: 0 · ⚠️ MAJOR: 1 · 💡 MINOR: 0

PR #1280 fixes a critical bug in tool pairing logic (changing from adjacency-based to ID-based matching) and adds summary snapping to prevent cutting through tool batches. The fix is sound and well-tested, but there's one MAJOR issue: new types RepairToolBatch and RepairResult defined in context.ts are not exported from the barrel (src/lib/types/index.ts), violating CLAUDE.md Rule 13. Fix: Add 'export * from "./context.js";' to src/lib/types/index.ts.

Findings behind this verdict

  • ⚠️ MAJOR: Types not exported from barrel — src/lib/types/index.ts
    The new types RepairToolBatch and RepairResult are defined in src/lib/types/context.ts but the barrel (src/lib/types/index.ts) does not export './context.js'. This means types cannot be imported via the standard barrel pattern (e.g., import type { X } from '../types/index.js') and must be imported directly from the source file, violating CLAUDE.md Rule 13 ('Barrel-only imports for internal types')…

@murdore

murdore commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Reviewed this closely because six PRs are stacked on it. The pairing rewrite itself I think is right — ID mode with a legacy positional fallback, keeping the first result on duplicate ids, and the separate changed flag (orphan counters alone genuinely cannot detect a dropped duplicate) are all well judged.

Two issues in snapSplitToBatchBoundary, both reproduced by running the exported function directly.

1. The forward snap can empty the recent window

isToolRole() is tool_call || tool_result, so it cannot distinguish a batch end from a batch start. A run of consecutive batches reads as one batch, and when that run begins at index 0 the backward walk cannot exit, so the forward walk runs to the end of the whole run.

// all tool-role, three complete batches
[call1, res1, call2, res2, call3, res3]
snapSplitToBatchBoundary(msgs, 2)  // => 6 === messages.length

Consumer:

const messagesToSummarize = recentMessages.slice(0, splitIndex);
const recentToKeep = recentMessages.length - messagesToSummarize.length;  // 0

So the entire recent window is summarized and nothing is kept verbatim, which defeats RECENT_MESSAGES_RATIO. This is a change in behaviour introduced here: on release the same input returns Math.max(1, 2) === 2 and keeps four recent messages.

Reachable whenever history ends mid tool-run — an assistant turn whose results came back with no trailing text.

2. It snaps boundaries that were already clean

// six complete batches, then two text messages
snapSplitToBatchBoundary(msgs, 6)  // => 12

Index 6 sits between res3 and call4 — a real batch boundary, nothing being cut. It still advances past three more complete batches, summarizing six messages that did not need it.

A tool_result followed by a tool_call is exactly a batch boundary, so both cases are distinguishable with the information already in hand. A clamp so the return value can never reach messages.length would cover the first on its own.

3. Doc reference points at a function that does not exist

toolPairRepair.ts:11 and src/lib/types/conversation.ts:358 both say "see flushPendingToolExecutions". No such symbol exists; the method that writes all calls before any result is flushPendingToolData (redisConversationMemoryManager.ts:1928). Cosmetic, but it is the comment explaining why batch-aware pairing is needed at all.

Status

MERGEABLE/CLEAN, 15/15 checks green. Blocked only by the outstanding review. Worth resolving before merge given #1281, #1282, #1283, #1285, #1286 and #1287 all sit on top of this branch.

@pdogra1299
pdogra1299 force-pushed the fix/tool-pair-identity branch from 78a90da to 0cecdbb Compare August 6, 2026 05:34
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@Tara-ag

Tara-ag commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Looking at the previous review finding about types not being exported from barrel in src/lib/types/index.ts. Let me verify if this is still an issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/continuous-test-suite-tool-pairing.ts`:
- Around line 303-306: Remove the interpolated split value from the assertion
messages in the assertions around snapSplitToBatchBoundary and the corresponding
assertions near lines 356–364. Replace each dynamic message with a static
structural description while preserving the existing assertions and failure
conditions.
- Around line 201-210: Update the “duplicate result ids never over-pair a call”
test around repairToolPairs and counts to assert that exactly one call and one
result remain, rather than only checking equal counts. Preserve the existing
input and verify duplicate result removal directly in the output.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 53cce57a-716e-4e96-a541-20bbb2f69937

📥 Commits

Reviewing files that changed from the base of the PR and between 3281ec9 and 0cecdbb.

📒 Files selected for processing (9)
  • package.json
  • src/lib/context/stages/structuredSummarizer.ts
  • src/lib/context/summarizationEngine.ts
  • src/lib/context/toolPairRepair.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/context.ts
  • src/lib/types/conversation.ts
  • src/lib/utils/conversationMemory.ts
  • test/continuous-test-suite-tool-pairing.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • package.json
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/conversation.ts
  • src/lib/context/summarizationEngine.ts
  • src/lib/context/stages/structuredSummarizer.ts
  • src/lib/utils/conversationMemory.ts
  • src/lib/types/context.ts
  • src/lib/context/toolPairRepair.ts

Comment thread test/continuous-test-suite-tool-pairing.ts
Comment on lines +303 to +306
assert(
snapSplitToBatchBoundary(messages, split) === split,
`legal batch boundary at index ${split} was moved`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove dynamic values from assertion messages.

Line 305, Line 358, and Line 363 interpolate ${split} into assertion messages. Use structural messages with no actual values.

Proposed fix
-        `legal batch boundary at index ${split} was moved`,
+        "legal batch boundary was moved",
@@
-        `orphaned tool_result survived at split index ${split}`,
+        "orphaned tool_result survived after split repair",
@@
-        `unbalanced tool batch at split index ${split}`,
+        "tool batch was unbalanced after split repair",

As per coding guidelines, test/**/*.ts must not include actual values in assertion messages.

Also applies to: 356-364

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/continuous-test-suite-tool-pairing.ts` around lines 303 - 306, Remove
the interpolated split value from the assertion messages in the assertions
around snapSplitToBatchBoundary and the corresponding assertions near lines
356–364. Replace each dynamic message with a static structural description while
preserving the existing assertions and failure conditions.

Source: Coding guidelines

@pdogra1299

Copy link
Copy Markdown
Collaborator Author

Thanks both — addressed. Summary of what changed and one finding I could not reproduce.

Fixed — batch boundary detection (CodeRabbit, Major). Confirmed: snapSplitToBatchBoundary treated any two adjacent tool messages as "inside a batch", so [call, result, call, result] fused into one indivisible run. Measured before the fix — a split at index 3 (a legal tool_result → tool_call boundary between two batches) was snapped back to 1. Added a cutsBatch() predicate: a tool_result followed by a tool_call is the boundary between batches and is now left alone.

This is the same bug class as loopGuardCore.findToolBatches in #1287 — I fixed it there and failed to back-port it here.

Fixed — forward snap could consume the whole recent window (CodeRabbit, Major). Confirmed: an all-tool array snapped forward to messages.length, and summarizeSession only rejects an empty slice, so it would summarize everything and leave no recent window. findSplitIndexByTokens now returns 0 in that case, matching the guard structuredSummarizer already had.

Fixed — stale doc reference (CodeRabbit, Minor). The method is flushPendingToolData, not flushPendingToolExecutions. Corrected here and in every later PR in the stack that inherited the wrong name.

Not reproduced — types not exported from barrel (@Tara-ag, Major). src/lib/types/index.ts:19 already contains export * from "./context.js";. RepairToolBatch and RepairResult both resolve through the barrel today, which is also why toolPairRepair.ts imports them from ../types/index.js with tsc --noEmit --strict reporting 0 errors. Happy to be corrected if you were seeing something else.

Verification: test:tool-pairing now 18/18 (2 new regression tests covering both fixes — each fails without its fix). 0 type errors, 0 lint errors, prettier clean. Single commit preserved via amend; the six downstream PRs were rebased onto the new base and all nine suites pass on the stack tip.

@Tara-ag

Tara-ag commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Summary for PR #1280

Decision: APPROVED ✅

This PR implements batch-aware tool call/result pairing for the context compaction pipeline, fixing a critical bug where parallel tool calls were incorrectly repaired when results arrived out of order.

Findings Summary

No issues found. All code quality, security, and correctness checks pass.

What This PR Does

  1. Batch-Aware Tool Pairing: Rewrites toolPairRepair.ts to use ID-based matching (toolCallId) instead of positional adjacency, fixing the bug where parallel batches would create orphaned placeholders over valid results.

  2. Type Updates: Adds RepairToolBatch type in src/lib/types/context.ts and extends ChatMessage with optional toolCallId field in src/lib/types/conversation.ts.

  3. Memory Persistence: Redis memory manager now persists toolCallId for both calls and results, enabling correct re-pairing after compaction.

  4. Split Protection: Batch-aware split logic in summarizationEngine.ts uses snapSplitToBatchBoundary() to prevent cutting tool batches in half during summarization.

  5. Comprehensive Tests: New test suite continuous-test-suite-tool-pairing.ts covers 19 test cases including healthy batches, genuine orphans, multiple independent batches, split boundaries, and edge cases.

Impact on Existing Code

  • Blast Radius: Changes affect 76 functions directly, 500+ nodes within 3 hops, and 67 files indirectly.
  • Key Dependencies: SummarizationEngine, ContextCompactor, RedisConversationMemoryManager, StructuredSummarizer all integrate correctly with new batch-aware logic.
  • Backward Compatibility: Fallback to positional pairing maintained for legacy sessions without toolCallId.
  • Test Coverage: Comprehensive regression tests added; no existing tests broken.

Key Design Decisions

  1. ID-Based Pairing: Uses unique toolCallId for parallel batches where position is meaningless.
  2. Sentinel Handling: Unknown/empty IDs fall back gracefully to legacy positional pairing.
  3. Non-Destructive: Healthy batches (complete pairs) are left untouched.
  4. Atomic Batches: Split boundaries snap to batch boundaries to avoid orphaning results.

Verification Checklist

  • ✅ Types exported from barrel (src/lib/types/index.ts)
  • ✅ No interface declarations (all use type)
  • ✅ No hardcoded secrets or credentials
  • ✅ Proper error handling (returns, never throws)
  • ✅ Backward compatibility maintained
  • ✅ Type safety preserved (no any or double assertions)
  • ✅ Test coverage comprehensive
  • ✅ CLI vs SDK concerns properly separated

Conclusion

This is a well-designed, thoroughly tested fix that resolves a real bug in the context compaction pipeline. The implementation follows NeuroLink's patterns, maintains backward compatibility, and includes comprehensive regression tests. Ready to merge.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary for PR #1280

Decision: APPROVED ✅

This PR implements batch-aware tool call/result pairing for the context compaction pipeline, fixing a critical bug where parallel tool calls were incorrectly repaired when results arrived out of order.

Findings Summary

No issues found. All code quality, security, and correctness checks pass.

What This PR Does

  1. Batch-Aware Tool Pairing: Rewrites toolPairRepair.ts to use ID-based matching (toolCallId) instead of positional adjacency, fixing the bug where parallel batches would create orphaned placeholders over valid results.

  2. Type Updates: Adds RepairToolBatch type in src/lib/types/context.ts and extends ChatMessage with optional toolCallId field in src/lib/types/conversation.ts.

  3. Memory Persistence: Redis memory manager now persists toolCallId for both calls and results, enabling correct re-pairing after compaction.

  4. Split Protection: Batch-aware split logic in summarizationEngine.ts uses snapSplitToBatchBoundary() to prevent cutting tool batches in half during summarization.

  5. Comprehensive Tests: New test suite continuous-test-suite-tool-pairing.ts covers 19 test cases including healthy batches, genuine orphans, multiple independent batches, split boundaries, and edge cases.

Impact on Existing Code

  • Blast Radius: Changes affect 76 functions directly, 500+ nodes within 3 hops, and 67 files indirectly.
  • Key Dependencies: SummarizationEngine, ContextCompactor, RedisConversationMemoryManager, StructuredSummarizer all integrate correctly with new batch-aware logic.
  • Backward Compatibility: Fallback to positional pairing maintained for legacy sessions without toolCallId.
  • Test Coverage: Comprehensive regression tests added; no existing tests broken.

Key Design Decisions

  1. ID-Based Pairing: Uses unique toolCallId for parallel batches where position is meaningless.
  2. Sentinel Handling: Unknown/empty IDs fall back gracefully to legacy positional pairing.
  3. Non-Destructive: Healthy batches (complete pairs) are left untouched.
  4. Atomic Batches: Split boundaries snap to batch boundaries to avoid orphaning results.

Verification Checklist

  • ✅ Types exported from barrel (src/lib/types/index.ts)
  • ✅ No interface declarations (all use type)
  • ✅ No hardcoded secrets or credentials
  • ✅ Proper error handling (returns, never throws)
  • ✅ Backward compatibility maintained
  • ✅ Type safety preserved (no any or double assertions)
  • ✅ Test coverage comprehensive
  • ✅ CLI vs SDK concerns properly separated

Conclusion

This is a well-designed, thoroughly tested fix that resolves a real bug in the context compaction pipeline. The implementation follows NeuroLink's patterns, maintains backward compatibility, and includes comprehensive regression tests. Ready to merge.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary for PR #1280

Decision: APPROVED ✅

This PR implements batch-aware tool call/result pairing for the context compaction pipeline, fixing a critical bug where parallel tool calls were incorrectly repaired when results arrived out of order.

Findings Summary

No issues found. All code quality, security, and correctness checks pass.

What This PR Does

  1. Batch-Aware Tool Pairing: Rewrites toolPairRepair.ts to use ID-based matching (toolCallId) instead of positional adjacency, fixing the bug where parallel batches would create orphaned placeholders over valid results.

  2. Type Updates: Adds RepairToolBatch type in src/lib/types/context.ts and extends ChatMessage with optional toolCallId field in src/lib/types/conversation.ts.

  3. Memory Persistence: Redis memory manager now persists toolCallId for both calls and results, enabling correct re-pairing after compaction.

  4. Split Protection: Batch-aware split logic in summarizationEngine.ts uses snapSplitToBatchBoundary() to prevent cutting tool batches in half during summarization.

  5. Comprehensive Tests: New test suite continuous-test-suite-tool-pairing.ts covers 19 test cases including healthy batches, genuine orphans, multiple independent batches, split boundaries, and edge cases.

Impact on Existing Code

  • Blast Radius: Changes affect 76 functions directly, 500+ nodes within 3 hops, and 67 files indirectly.
  • Key Dependencies: SummarizationEngine, ContextCompactor, RedisConversationMemoryManager, StructuredSummarizer all integrate correctly with new batch-aware logic.
  • Backward Compatibility: Fallback to positional pairing maintained for legacy sessions without toolCallId.
  • Test Coverage: Comprehensive regression tests added; no existing tests broken.

Key Design Decisions

  1. ID-Based Pairing: Uses unique toolCallId for parallel batches where position is meaningless.
  2. Sentinel Handling: Unknown/empty IDs fall back gracefully to legacy positional pairing.
  3. Non-Destructive: Healthy batches (complete pairs) are left untouched.
  4. Atomic Batches: Split boundaries snap to batch boundaries to avoid orphaning results.

Verification Checklist

  • ✅ Types exported from barrel (src/lib/types/index.ts)
  • ✅ No interface declarations (all use type)
  • ✅ No hardcoded secrets or credentials
  • ✅ Proper error handling (returns, never throws)
  • ✅ Backward compatibility maintained
  • ✅ Type safety preserved (no any or double assertions)
  • ✅ Test coverage comprehensive
  • ✅ CLI vs SDK concerns properly separated

Conclusion

This is a well-designed, thoroughly tested fix that resolves a real bug in the context compaction pipeline. The implementation follows NeuroLink's patterns, maintains backward compatibility, and includes comprehensive regression tests. Ready to merge.

@pdogra1299
pdogra1299 force-pushed the fix/tool-pair-identity branch from 0cecdbb to 8344a88 Compare August 6, 2026 05:48
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@pdogra1299

Copy link
Copy Markdown
Collaborator Author

Second pass — 0cecdbbe → 8344a885, still one commit.

@murdore — all three were real, all three fixed in the previous push

Your comment landed just before that push, so for the record, against 8344a885:

1 + 2 — snapSplitToBatchBoundary. Root cause was exactly as you diagnosed: isToolRole() cannot tell a batch end from a batch start, so a run of consecutive batches read as one indivisible batch. Fixed with a cutsBatch() predicate — a tool_result followed by a tool_call is the boundary between two batches and is now left alone; both walks use it instead of isToolRole. Your two repros now return 2 and 6. findSplitIndexByTokens also got the clamp you suggested (snapped >= messages.length → 0, declining the round) as defence in depth for the case where a batch genuinely starts at index 0.

3 — doc reference. flushPendingToolExecutions is gone from both toolPairRepair.ts:11 and types/conversation.ts:358; both now name flushPendingToolData. grep -rn flushPendingToolExecutions src/ test/ returns nothing.

Three regression tests cover it: a result→call transition is left alone, a 20-batch alternating history keeps every interior legal split, and an all-tool history never consumes the whole recent window.

@coderabbitai — one taken, one declined with evidence

Duplicate-result assertion (lines 201-210) — taken. You are right that c.calls === c.results was too weak: synthesizing a second call to pair with the duplicate would also balance, which is precisely the over-pairing the test name forbids. Now pinned exactly:

assert(c.calls === 1, "duplicate result caused a call to be synthesized");
assert(c.results === 1, "duplicate result was not dropped");
const survivor = out.messages.find((m) => m.role === "tool_result");
assert(survivor?.id === "r1", "the wrong duplicate result was kept");

The third line also pins the first-wins tie-break, which nothing asserted before.

Interpolated split index in assertion messages — declined. The hazard is real but the rule is narrower than the finding reads. CLAUDE.md forbids quoting the payload, and explicitly sanctions a structural locator:

// DON'T — dumping the actual value into the message.
assert(ok, `terminal journal wrong — got ${JSON.stringify(actual)}`);
// DO — describe the discrepancy without quoting the payload.
assert(ok, `terminal journal wrong — mismatch at ${keyPath}`);

A loop index is a locator, not a payload. Checked empirically rather than by reading — every message those two loops can emit, run through the real matcher:

$ npx tsx -e 'for (i of 0..59) isExpectedProviderError(...)'
downgraded-to-SKIP messages: 0

These are for loops over ~40 split points; dropping the index would mean a failure reports that some boundary moved but not which, and the index is the only thing that makes it debuggable. Happy to revisit if the matcher ever grows a bare-integer pattern.

Verification

pnpm run check → 4961 files, 0 errors, 0 warnings. Prettier clean. pnpm run test:tool-pairing → 18/18 pass, 0 skipped. All nine suites re-run green on the stack tip after rebasing (95 tests, 0 failures, 0 skips).

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Revision =='
git rev-parse --short HEAD

echo '== Relevant declarations and functions =='
ast-grep outline src/lib/context/toolPairRepair.ts --items all --type function
ast-grep outline src/lib/context/summarizationEngine.ts --items all --type function
ast-grep outline test/continuous-test-suite-tool-pairing.ts --items all --type function

echo '== Batch split implementation =='
rg -n -A35 -B12 'cutsBatch|snapSplitToBatchBoundary' \
  src/lib/context/toolPairRepair.ts \
  src/lib/context/summarizationEngine.ts \
  src/lib/context/stages/structuredSummarizer.ts

echo '== Duplicate-result regression assertions =='
rg -n -A22 -B12 'duplicate result|duplicate-result|first.*wins|first-wins' \
  test/continuous-test-suite-tool-pairing.ts

echo '== Stale and current documentation references =='
rg -n -i -C2 'flushPendingToolExecutions|flushPendingToolData' src test || true

echo '== Barrel export =='
rg -n -C2 'context\.js' src/lib/types/index.ts

Length of output: 33148


@pdogra1299 Verified against 8344a88.

The duplicate-result regression test now prevents both over-pairing and incorrect duplicate selection. It asserts exactly one call, exactly one result, and keeps r1.

The split logic now preserves tool_result → tool_call batch boundaries. The pointer-based path also declines a round when a forward snap reaches the full message array.

The stale flushPendingToolExecutions reference is absent from src and test. The barrel export for ./context.js is present.

I accept the clarification on interpolated loop indices. The index is a structural locator and does not expose payload content. I withdraw that concern.


✏️ Learnings added
Learnt from: pdogra1299
URL: https://github.com/juspay/neurolink/pull/1280

Timestamp: 2026-08-06T05:51:30.760Z
Learning: In this TypeScript repository, the CLAUDE.md assertion-message rule prohibits interpolating payload values, but permits structural locators such as a loop index or `keyPath`. Review interpolated assertion messages for payload disclosure, not for structural locators that improve failure diagnostics.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@murdore

murdore commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Re-checked against 0cecdbbe. Both issues I raised are fixed:

  • cutsBatch() now treats tool_result → tool_call as a real boundary, so consecutive batches no longer read as one. My three repro cases all return the right index now (previously 6→12, 2→6, 4→8; now 6→6, 2→2, 4→4).
  • The doc reference says flushPendingToolData in both places.

One residual edge

The forward walk can still return messages.length when a single batch spans the entire slice, because the backward walk cannot exit and the forward walk runs to the end:

[c1, c2, r1, r2]           split=2  ->  4   === length
[c1, c2, c3, r1, r2, r3]   split=2  ->  6   === length

Downstream that is messagesToSummarize = recentMessages.slice(0, splitIndex) with recentToKeep === 0, so the whole recent window gets summarized. Narrower than before — it needs the recent slice to begin at a batch and contain nothing else, which is reachable right after a previous summarization when the next turn is a large parallel batch. A clamp so the return can never equal messages.length would close it.

On the assertion-message thread — my guidance, applied too broadly

That thread traces back to a note I added to CLAUDE.md, and I had the mechanism wrong. Correcting it here since it is steering this PR.

The harness turns a failed assertion into a skip when isExpectedProviderError(message) matches. I said the danger was dumping payload values like stream_error or 502. Measured against the real matcher, that is not true:

safe          "legal batch boundary at index 7 was moved"
safe          "orphaned tool_result survived at split index 13"
safe          "legal batch boundary at index 404 was moved"
safe          ...got {"errorType":"stream_error","status":502}
SKIP-TRIGGER  "rate_limit"

The actual trigger in my case was the literal token rate_limit — a key name in a counts object where every value was 0. A structure reporting no rate limits silently converted a failing test into a skipped one with exit 0.

So for this PR: ${split} is completely safe. A bare integer matches nothing, and inside those for loops the index is the only thing that tells you which iteration failed. I would keep it. What is worth avoiding is interpolating whole objects whose field names happen to include provider-error tokens (rate_limit, ECONNREFUSED, timeout, …) — the values are irrelevant.

I will send a follow-up to fix the CLAUDE.md wording so the next reviewer is not led the same way.

Everything else here still looks good to me — the ID/legacy pairing split, first-result-wins on duplicate ids, and the changed flag for the dropped-duplicate case.

@murdore
murdore force-pushed the fix/tool-pair-identity branch from 8344a88 to 2271f0f Compare August 6, 2026 06:40
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@murdore
murdore merged commit a0f49e4 into release Aug 6, 2026
12 of 13 checks passed
@murdore
murdore deleted the fix/tool-pair-identity branch August 6, 2026 06:40
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 10.10.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR fixes issue #749 by implementing batch-aware, ID-based tool call/result pairing in the context compaction system. The previous adjacency-based matching would incorrectly pair tools in parallel calls (e.g., call1, call2, result1 would match call1→result1 and orphan call2). The new implementation correctly pairs within batches using tool IDs when available, falling back to positional matching within the batch.

Files changed: 9 files

  • Core implementation: src/lib/context/toolPairRepair.ts, summarizationEngine.ts, structuredSummarizer.ts
  • Integration: src/lib/utils/conversationMemory.ts
  • Tests: test/continuous-test-suite-tool-pairing.ts
  • Documentation: package.json, docs/features/context-compaction.md

Impact: All conversation memory operations now properly repair orphaned tool calls/results that occur when history is sliced mid-batch (via pointers after summarization). This affects ~500 code nodes but is a critical correctness fix for multi-turn conversations with tool use.

Code quality: All changes follow CLAUDE.md standards - proper error handling, correct type usage, logger guards on expensive serialization, comprehensive tests covering edge cases.

Impact on existing code

The change is backward-compatible - it only repairs messages that previously had orphans (which would have caused provider rejections). No API changes, no breaking changes. All callers of getConversationMessages, buildContextFromPointer, compactSession, and related functions automatically benefit from the fix.

Verdict: APPROVED

This is a well-implemented bug fix with:

  • ✓ Correct algorithm design (batch-aware, ID-first pairing)
  • ✓ Comprehensive test coverage
  • ✓ Proper error handling and logging
  • ✓ Zero breaking changes
  • ✓ Follows all project standards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants