Repository navigation
Conversation
…every chunk Each ACP subagent chunk emitted a message.updated and a turn_item.updated with the full reply so far. Both are stored as finished (streaming: false), so they skipped the paragraph throttle and every chunk was persisted. A message's first chunk is still emitted at once. Later chunks coalesce into one snapshot per 400 ms, and any subagent status update flushes pending text first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — ACP subagent replies now batch intermediate snapshots at a 400 ms cadence instead of rewriting storage on every chunk. Initial output and replacements remain immediate, and pending text is flushed before status updates, keeping the performance change narrowly scoped. You can add or adjust custom eligibility rules. Learn more. |
Real provider check: Grok (
|
| this branch | main |
|
|---|---|---|
| Turn / subagent status | completed / completed | completed / completed |
Child-thread assistant message.updated |
1 | 1 |
Child-thread assistant turn-item.updated |
1 | 1 |
| Final subagent reply | 3,825 chars, completed |
3,987 chars, completed |
Result: no regression. The subagent's full answer is stored and completed, and the parent receives the subagent completion and replies.
But this run doesn't show the improvement. Grok delivered the whole subagent answer in a single update, so on main too it was written only once, and the per-chunk path this PR changes never ran. Some other ACP agent must stream subagent chunks for the gain to apply. The Devin replay fixture in AcpAdapterV2.test.ts does, which is the case the extended test and the measurement table cover. The PR description lists Grok among the affected agents. That's inaccurate for this Grok model and CLI version.
🤖 Generated with Claude Code (Claude Opus 5.5 in wisp)
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Snapshot emission and pending state packages/provider-acp/src/server/adapter.ts |
The adapter tracks pending assistant messages. It emits initial and replacement text immediately and coalesces appended text on a 400 ms cadence. Writes use the specific message’s text and the shared text-emission permit. |
Flush lifecycle and replay assertions packages/provider-acp/src/server/adapter.ts, apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts, apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts, apps/server/src/orchestration-v2/messageSearchIndex.ts |
The adapter flushes pending messages before result handling, terminal status projection, child-state resets, and turn finalization. Replay tests check chunked output, failed-turn text, and coalesced text after child and root sessions become idle. The comment describes rewriting a finished answer while streaming. |
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix
Suggested reviewers: maria-rcks, t3dotgg
Merge Risk: 
Minimal · up to 57957
-
The identified text-flush ordering gaps have been addressed, and no remaining issue is established that should block merging after normal checks.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the affected behavior, implementation, testing, measured results, limitations, and unverified runtimes.
-
Finishing Touches
-
Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Correction: real Grok runs do show the improvementMy earlier comment was wrong to conclude Grok is unaffected. That run used a foreground subagent, which Grok returns as one update. When Grok's built-in Same prompt,
That's 27–56x fewer stored events and 27–58x fewer stored bytes. In this PR's run #2, the stored final answer equals the total length of the chunks received (4,929 chars), so no text is lost. The PR description's Verification section now has these numbers. 🤖 Generated with Claude Code (Claude Opus 5.5 in wisp) |
A failed turn's terminal can stop event ingestion before the delayed subagent flush runs, and carryover terminalization projected status without flushing. Both now emit pending subagent text first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t resets it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
When an ACP agent streams a subagent's reply (Grok native subagents, as measured below; Devin's
subagent_contextchunks),emitSubagentAssistantemits amessage.updatedand aturn_item.updatedcarrying the whole reply so far for everyagent_message_chunk.makeSubagentConversationArtifactsmarks bothstreaming: false, so they skip the server's paragraph throttle (assistantStreaming.ts) and live coalescing (ThreadLiveEventCoalescer.ts). Every chunk becomes two stored events, projection rewrites and a WebSocket push. Bytes written grow quadratically with reply length.messageSearchIndex.tsalready noted this ("ACP subagents rewrite their finished answer on every streamed chunk").Change
packages/provider-acp/src/server/adapter.ts, following the pattern ofpublishTextSegmentfor the root stream in the same file:ACP_SUBAGENT_TEXT_FLUSH_INTERVAL(400 ms). That is the cadence at which root replies are already persisted under the defaultparagraphstreaming mode.replaceupdates (agent_message) emit immediately and cancel the pending flush.textEmissionPermit, so a late flush cannot overwrite newer text.subagent.assistantTextfrom memory and is unaffected.The
messageSearchIndex.tscomment is updated to match.Scope and approval
A focused performance fix inside one adapter function with no new behavior or settings. The quadratic write was already acknowledged in
messageSearchIndex.ts, and the fix reuses the coalescing pattern this adapter already applies to the root text stream.Verification
AcpAdapterV2.test.tsso the child answer arrives in three chunks. Onmainit fails with['O', 'ON', 'ONE']; with the fix the emitted texts are['O', 'ONE']. The intermediate snapshot is coalesced and completion flushes the final text.src/orchestration-v2/Adapters/,ProviderSwitch.integration.test.ts,src/provider/acp/).vp run check:changedpasses.paragraphfilter, then written to a SQLite file (one insert plus one upsert per event, a simplified layout, so treat the timings as rough):mainThe final persisted text is 20,000 characters in every run.
Real agent: Grok (
grok-4.7-build-fast), native background subagent. I ran the same prompt against an isolated dev server, twice with this branch and twice withmain'sprovider-acpadapter. The prompt had the built-intasktool spawn one backgroundgeneral-purposesubagent that writes ~600 words. A temporary trace counted the chunks on the ACP connection; the stored events come fromorchestration_eventsin the dev database:message.updated+turn-item.updated)mainoxc stack #1mainAdd open-in-editor feature with Cmd+O shortcut #2On
main, every chunk is two stored events, each carrying the whole answer so far. With this change, stored events drop 27–56x and stored bytes 27–58x. In run Add open-in-editor feature with Cmd+O shortcut #2 with this change, the stored final answer (4,929 chars) equals the total length of the chunks received over the wire.A foreground Grok subagent and Claude via
claude-acpsend the subagent's answer as one update, so this change does not affect them. Copilot (403for this account) and Antigravity (the separate ACP runtime is not installed here) were not checked.While a subagent streams, its persisted text can now lag by up to 400 ms.
🤖 Generated with Claude Code (Claude Opus 5.5 in wisp)