fix(daemon): preserve parentToolCallId in compaction engine for parallel subagent streams#4765
Conversation
…lel subagent streams TurnBoundaryCompactionEngine.mergeTextSlot merged all consecutive agent_thought_chunk / agent_message_chunk events into a single slot regardless of parentToolCallId, destroying per-subagent attribution. When 9+ parallel subagents streamed concurrently, the compacted replay contained garbled text with no parentToolCallId — the downstream transcript reducer (fixed in #4689) could not route blocks to the correct subagent tool call. - Add parentToolCallId-aware dual-path merging: subagent chunks use an indexed lookup (textSlotIndex) to merge by (kind, parentToolCallId) even when interleaved; top-level chunks preserve the original consecutive-only merge to maintain text segmentation around tool calls. - Evict textSlotIndex entries when a same-parent tool_call arrives, mirroring the transcript reducer's clearActiveText(parentToolCallId) so compacted replay block segmentation matches live behavior. - Defensive backfill: ensure parentToolCallId survives in the compacted event's _meta even if the last chunk's _meta lost it. - Harden seed() to clear in-flight state (slots, indexes, liveJournal). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
📋 Review SummaryThis PR fixes a critical bug in the 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
There was a problem hiding this comment.
Pull request overview
This PR fixes a compaction bug in the daemon’s TurnBoundaryCompactionEngine where text/thought chunks from parallel subagent streams could be merged together and lose correct parentToolCallId association during replay compaction, resulting in garbled output and incorrect attribution on session load.
Changes:
- Extend text/thought slot compaction to track
parentToolCallIdand merge subagent chunks by(kind, parentToolCallId)instead of blindly merging bykind. - Evict subagent text/thought merge indices when a tool call with the same
parentToolCallIdarrives, keeping compaction segmentation aligned with live transcript behavior. - Add a focused test suite covering parentToolCallId-aware merging scenarios and boundary cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/acp-bridge/src/compactionEngine.ts | Adds parentToolCallId-aware slotting/merging and eviction logic for parallel subagent text/thought streams. |
| packages/acp-bridge/src/compactionEngine.test.ts | Adds new unit tests validating subagent separation/merging behavior under parallel/interleaved streams. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review correctly noted the "defensive backfill" test name was inaccurate — it actually tests that chunks without parentToolCallId separate into the top-level path. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
…e, backfill tests - Use bracket notation for _meta access in test helpers (TS4111 fix) - Move textSlotIndex eviction into the new-tool-only branch so tool_call_update does not over-segment subagent text - Add tests for meta backfill and tool_call_update non-eviction 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Review round — wenshao (fe46e37)
All 3 items addressed. 38/38 tests passing. |
Verify that seed() clears in-flight slots, liveJournal, and index maps so stale pre-seed data does not leak into post-seed compaction. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Review round 2 — wenshao (5be1f3d)
1/1 item addressed. 39/39 tests passing. |
PR #4765 Local Verification ReportBranch: Changes Summary
Test Results
Pre-existing Errors (on
|
… comment, add thought eviction test - Remove dead parentToolCallId fallback in tool eviction (emitters always use _meta), aligning with mergeTextSlot extraction - Reword eviction comment to be self-describing - Add thought slot eviction test coverage 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Review round 3 — wenshao (5f88fcd)
2/3 fixed, 1 pushed back. 40/40 tests passing. |
wenshao
left a comment
There was a problem hiding this comment.
R4 review (5f88fcd): all R1-R3 findings resolved. Dual-source fallback removed, thought eviction test added. 40/40 tests pass, ESLint clean, zero new type errors. No high-confidence issues found. LGTM! ✅ — qwen3.7-max via Qwen Code /review
The defensive parentToolCallId backfill in compactCurrentTurn was unreachable: the routing invariant in mergeTextSlot guarantees that any chunk reaching the subagent path has parentToolCallId in _meta, so slot.lastMeta always contains it. Remove the dead code and rename tests to describe what they actually verify. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Review round 4 — wenshao (fde8474)
1/1 addressed. 40/40 tests passing. Code simplified. |
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
Summary
TurnBoundaryCompactionEngine.mergeTextSlot将所有连续同类 chunk 合并为一个 slot,不区分parentToolCallId,导致并行子 agent 的 thought/content 混成乱码、归属丢失(kind, parentToolCallId)indexed merge,主 agent chunk 保持原有连续合并;同父 tool_call 到达时驱逐 textSlotIndex 条目,保持 compacted replay 与 live stream 的 block 分段一致_meta.parentToolCallId不丢失;seed() 加固清理 in-flight stateTest plan
🤖 Generated with Qwen Code