Repository navigation
fix(acp): assistant message fragmentation on background tool updates & leaked harness telemetry #13133
Description
Activity
Triage
Partly confirmed on current
main. One of the three claims is a real bug in the shared ACP session runtime. The system-message leak is already tracked. The doubleassistant:id is real and harmless.There is no version, no provider event log, and no screenshot on this report. The repro is a prompt the model has to follow, not a captured session.
Segment close
handleSessionUpdateinapps/server/src/provider/acp/AcpSessionRuntime.tscloses the active assistant segment on every parsedToolCallUpdated, and it does that beforedecideToolCallUpdateEmission.Both ACP
tool_callandtool_call_updateare parsed asToolCallUpdated(parseSessionUpdateEventinAcpRuntimeModel.ts). A progress notification that the coalesce logic then drops (emit: false) has already ended the bubble. The nextagent_message_chunkopens a new item. Nothing is inserted between the two bubbles, because the tool event was not emitted.Closing on the initial
tool_callis intentional.AcpJsonRpcConnection.test.ts("segments assistant text around ACP tool calls") expects text, then the tool, then a new assistant item. That is how a tool card lands between two answers.A later
tool_call_updateis different. A background command that reachescompleted(or any coalesced stdout tick) while the same answer is still streaming cuts that answer in two. A markdown table or link split at that boundary will not render. This is shared ACP runtime code, so Cursor, Grok, and Antigravity all do it. It has been the close-on-ToolCallUpdatedbehavior since the ACP runtime landed; the coalesce check was added in front of the queue write and the close was left ahead of it.Whitespace-only deltas do not open a segment when none is active. Empty bubbles are not what this path creates. The bubbles are the two halves of the text.
System-message text
This repo does not generate
<SYSTEM_MESSAGE>or<system_message>. Whatever text the ACP agent puts inagent_message_chunkis stored as assistant text. There is no sanitizer.That leak is #11432: Antigravity, Gemini 3.8 Flash, with screenshots. The payload there is lowercase
<system_message> Background task … has completed. Task exit code: …, delivered as assistant text, onv0.0.41-nightly.20260912.1599. #13074 is an open, unmerged attempt to strip that form. It does not change segment closing.The wrapper in this report ("The following is a
<SYSTEM_MESSAGE>not actually sent by the user…") is a different string. Nothing in-tree emits it, and this report does not show a trace of the model echoing it. If that text does arrive as content deltas, the close above can put each piece in its own bubble. The 40-bubble count is not evidenced here.Message ids
ACP item ids are
assistant:<sessionId>:runtime:<runtimeId>:segment:<n>(assistantItemId).assistantSegmentMessageIdthen prefixesassistant:again, so the row id isassistant:assistant:<sessionId>:runtime:<runtimeId>:segment:<n>. The completion fallback uses the sameassistant:${itemId}formula, so the two paths agree.The
assistant:on the item id is from #3932 / #3791: segment counters restart at 0 when the runtime is recreated, and message ids are primary keys. The runtime uuid is what keeps a resumed session from appending to an old row. A secondassistant:does not change that and does not affect markdown. Renaming it would mint new primary keys for no user-visible fix.Next step
Leave this open as the segment-close bug: do not close an in-progress assistant item on a
tool_call_updatethat is not emitted, and do not treat a background completion as a new prose boundary while that item is still streaming. Keep the close on the initialtool_call.Follow the harness-text leak on #11432. Do not fold the id prefix into the fix.
@adeebahmad01 — a provider trace around the split (session update types and timestamps, not just the prompt) would show whether Antigravity is sending
tool_call_updatemid-chunk. The code will split if it does.- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 22, 2026 A pull request addressing this issue has been opened: #13137
Thanks for taking the time to report this and provide the details. We revisited it during the orchestrator V2 cleanup.
The accepted segment-close bug is fixed in current ACP runtime: coalesced updates are skipped before segmentation, and only a newly shown tool call closes prose. Updates/completion for already-shown calls preserve the active segment.
I’m closing this based on the current source and the evidence in this thread.
Closure applies to the accepted prose segmentation scope. Harness telemetry remains separately tracked; double assistant prefix was harmless and is not claimed repaired.
If you still hit this on a current build, please reply with the app/server versions and the steps that reproduce it. We can reopen this if the original problem is still there.
Description
When using ACP-backed providers (such as Antigravity) with asynchronous background tools, two interrelated issues corrupt assistant message formatting and clutter thread history:
Abrupt Assistant Segment Severing on Background Tool Updates:
In
AcpSessionRuntime.ts,closeActiveAssistantSegmentis invoked unconditionally upon receiving anyToolCallUpdatedevent:If a background command finishes while the assistant is actively streaming a response (such as a markdown table, code block, or link), the active segment is severed mid-stream. The remainder of the response is placed in a new segment/bubble without the preceding context, breaking Markdown formatting (e.g. unclosed links
[text](http://...cut off before closing parenthesis, and tables split mid-cell where succeeding rows become raw pipe-separated text| row | data |).Harness Telemetry Parroting (
<SYSTEM_MESSAGE>) & Ghost Segments:When background tasks complete, reactive system notifications are delivered into the provider context:
Certain models (e.g.
gemini-3.8-flash-high) occasionally parrot this telemetry into their response deltas. Because each tool invocation or update resets the segment lifecycle, this results in an explosion of separate chat bubbles (e.g. 40+ bubbles in a single turn) containing only raw<SYSTEM_MESSAGE>XML boilerplate.Double Message ID Prefix Bug:
In
ProviderRuntimeIngestion.ts,assistantSegmentMessageIdprependsassistant:tobaseKey. Since ACP'sitemIdalready begins withassistant:, this results in duplicated prefixes in the database (assistant:assistant:c19d...).Reproduction Example
Send the following prompt in a thread with an ACP provider supporting background execution:
Actual Behavior:
sleep 3finishes,ToolCallUpdatedis fired.closeActiveAssistantSegmentcuts Bubble 1 off mid-table.|characters and broken elements.<SYSTEM_MESSAGE>notifications spam the chat UI as individual bubbles.Expected Behavior:
<SYSTEM_MESSAGE>...</SYSTEM_MESSAGE>.assistant:assistant:prefixes.