#16275: fix(agents): prevent rapid-fire duplicate messages during tool execution
agents
size: M
trusted-contributor
Cluster:
Tool Result Handling Improvements
## Summary
Tool-heavy replies were being split into multiple outbound messages because block coalescing could idle-flush while a tool call was still running. This PR pauses idle-based coalescing during tool execution and resumes it when the tool ends, so pre-tool and post-tool text can remain in one coherent streamed reply.
## What Changed
### Block coalescer hold and resume during tool execution
When a tool call takes longer than the coalescer idle timeout, buffered assistant text flushes mid-tool. Subsequent text then arrives as additional messages, which appears as fragmented or duplicate reply behavior across channels.
- `src/auto-reply/reply/block-reply-coalescer.ts`: Added hold/resume semantics to the coalescer with new `hold()` and `resume()` APIs.
- `src/auto-reply/reply/block-reply-coalescer.ts`: Added ref-counted `holdCount` so overlapping holds are handled safely.
- `src/auto-reply/reply/block-reply-coalescer.ts`: `scheduleIdleFlush()` now skips scheduling while held.
- `src/auto-reply/reply/block-reply-coalescer.ts`: `hold()` clears any active idle timer, and `resume()` reschedules only when all holds are released and text is buffered.
- `src/auto-reply/reply/block-reply-pipeline.ts`: Exposed pass-through hold/resume on the pipeline.
- `src/auto-reply/reply/agent-runner-execution.ts`, `src/agents/pi-embedded-runner/run.ts`, `src/agents/pi-embedded-runner/run/attempt.ts`, `src/agents/pi-embedded-runner/run/params.ts`, `src/agents/pi-embedded-subscribe.types.ts`, and `src/agents/pi-embedded-subscribe.handlers.types.ts`: Wired callbacks from auto-reply execution into embedded runner plumbing.
- `src/agents/pi-embedded-subscribe.handlers.tools.ts`: `handleToolExecutionStart(...)` now flushes existing buffered block output, then calls `onBlockReplyHold()`.
- `src/agents/pi-embedded-subscribe.handlers.tools.ts`: `handleToolExecutionEnd(...)` now calls `onBlockReplyResume()` before processing results.
## Design Notes
- Ref-counted hold state (instead of a boolean) was chosen so parallel and overlapping tool execution cannot resume coalescing too early.
- The change intentionally pauses only idle-timer flush behavior; force flush paths remain available for explicit boundaries and media payload behavior.
- New callbacks are optional, preserving compatibility for call paths that do not enable block streaming.
## Testing
- Added `src/auto-reply/reply/block-reply-coalescer.test.ts` with coverage for idle suppression while held, timer restart on resume, nested/parallel hold balancing, force flush behavior while held, and media enqueue behavior during hold.
- Added `src/auto-reply/reply/block-reply-pipeline.test.ts` for hold/resume passthrough.
- Updated `src/agents/pi-embedded-subscribe.handlers.tools.test.ts` to validate hold at tool start and resume at tool end.
- Ran: `pnpm test src/auto-reply/reply/block-reply-coalescer.test.ts src/auto-reply/reply/block-reply-pipeline.test.ts src/agents/pi-embedded-subscribe.handlers.tools.test.ts`
- Result: 3 files passed, 20 tests passed.
## AI Disclosure
- [x] AI-assisted
- [x] Fully tested
## Related Issues
- #12659
- #13788
- #20226
---
Closes #3549
Closes #13944
Closes #15022
Closes #15968
Closes #20740
Closes #22258
Most Similar PRs
#15920: fix: await coalescer flush before tool execution
by Bridgerz · 2026-02-14
75.5%
#19932: feat(agents): suppressPreToolText config + onBlockReply buffering
by Milofax · 2026-02-18
73.8%
#19518: fix: await block reply flush before tool execution
by katalabut · 2026-02-17
71.9%
#15996: fix(agents): messages arrive out of order — tool output beats narra...
by yinghaosang · 2026-02-14
71.6%
#9171: Fix: Route tool result deliveries through BlockReplyPipeline for pr...
by vishaltandale00 · 2026-02-04
71.6%
#20235: fix(reply): prevent duplicate final payloads in block pipeline
by PeterShanxin · 2026-02-18
70.0%
#22886: fix: await onBlockReplyFlush before tool execution to preserve mess...
by botverse · 2026-02-21
69.7%
#19181: feat(gateway): tool pause/resume interrupts with durable approvals
by evgenyyy · 2026-02-17
69.1%
#5080: fix(reply): fix duplicate block replies by unblocking coalesced pay...
by yassine20011 · 2026-01-31
68.8%
#20052: fix(agents): suppress narration text when assistant message has too...
by lailoo · 2026-02-18
68.5%