Summary
Two related bugs corrupt the canonical turn history that feeds cross-provider switching: (1) subagent assistant text is emitted as primary assistant text, and (2) the canonical accumulator keeps only the last text_done of a turn instead of concatenating.
Location
src/daemon/providers/claude/index.ts:495-504 (assistant branch emits text_done unconditionally, ignoring parent_tool_use_id)
src/daemon/providers/claude/index.ts:508-537 (stream_event branch never inspects parent_tool_use_id)
src/daemon/providers/canonical.ts:268-270 (case "text_done": this.#currentText = event.content — assign, not append)
- consumed in production at
src/daemon/session.ts:154
Root cause
(1) ProviderEvent.text_delta/text_done carry no parent field, so Session cannot filter subagent output downstream — yet the emit path already reads parent_tool_use_id for usage splitting (isPrimary, :491) and could gate on it. (2) Claude emits one text_done per assistant SDK message; a normal agentic turn is text → tool → text → tool → final text, so assigning drops every block but the last.
Failure scenario
Agent spawns a Task subagent and does interleaved commentary across 5 tool calls. Canonical history records only the final "done" utterance (or the subagent's last line), with all intermediate reasoning gone but the tool calls it referenced still present. On provider switch, Gemini/OpenAI receive a misleading, truncated history.
Fix
Add a parentToolUseId/isPrimary field to the text provider events and drop non-primary text before it reaches the canonical accumulator; change text_done handling to append (reset at turn/message boundaries, not per SDK message).
Summary
Two related bugs corrupt the canonical turn history that feeds cross-provider switching: (1) subagent assistant text is emitted as primary assistant text, and (2) the canonical accumulator keeps only the last
text_doneof a turn instead of concatenating.Location
src/daemon/providers/claude/index.ts:495-504(assistant branch emitstext_doneunconditionally, ignoringparent_tool_use_id)src/daemon/providers/claude/index.ts:508-537(stream_event branch never inspectsparent_tool_use_id)src/daemon/providers/canonical.ts:268-270(case "text_done": this.#currentText = event.content— assign, not append)src/daemon/session.ts:154Root cause
(1)
ProviderEvent.text_delta/text_donecarry no parent field, so Session cannot filter subagent output downstream — yet the emit path already readsparent_tool_use_idfor usage splitting (isPrimary,:491) and could gate on it. (2) Claude emits onetext_doneper assistant SDK message; a normal agentic turn is text → tool → text → tool → final text, so assigning drops every block but the last.Failure scenario
Agent spawns a Task subagent and does interleaved commentary across 5 tool calls. Canonical history records only the final "done" utterance (or the subagent's last line), with all intermediate reasoning gone but the tool calls it referenced still present. On provider switch, Gemini/OpenAI receive a misleading, truncated history.
Fix
Add a
parentToolUseId/isPrimaryfield to the text provider events and drop non-primary text before it reaches the canonical accumulator; changetext_donehandling to append (reset at turn/message boundaries, not per SDK message).