Agent: assistant text blocks alongside tool_use are dropped from message history #24

Open
opened 2026-05-12 19:05:19 +00:00 by KofTwentyTwo · 0 comments
KofTwentyTwo commented 2026-05-12 19:05:19 +00:00 (Migrated from github.com)

Summary

Assistant text content blocks emitted alongside tool_use are dropped from the message history. When Anthropic streams a turn that contains text deltas BEFORE a tool_use block (which Opus 4.7 does frequently — "Let me check the time for you… [tool_use:get_time]"), the orchestrator's assistantTextSoFar captures the text for D-02 escalation, but the appended assistant message contains ONLY the tool_use block. The preceding text is gone from the model's view on the next round trip.

Evidence

  • File: packages/AgentCore/Sources/AgentOrchestrator/AgentOrchestrator.swift:575-577:
    messages.append(LLMMessage(role: .assistant, content: [
        .toolUse(id: req.id, name: req.name, argsJSON: req.argsJSON),
    ]))
    

Impact

Generally tolerable for Anthropic — the model resolves tool calls by id and doesn't re-read its own prose between turns. But:

  1. If the user submits a follow-up referencing "you said X before calling the tool", the model has no record of saying X
  2. Replay log captures text via .textDelta events, but replay viewer reconstruction won't match what the model actually saw at re-prompt

Lower-severity version of the calibration bug shape — tests pass (messages.last is the assistant message they expected), the data boundary silently loses signal.

Recommended fix

Accumulate text + tool_use blocks into a single assistant LLMMessage per round-trip, flushing on either stop_reason: .toolUse (with tool blocks present) or stop_reason: .endTurn (text only):

var assistantContent: [LLMContent] = []
if !assistantTextSoFar.isEmpty {
    assistantContent.append(.text(assistantTextSoFar))
}
assistantContent.append(.toolUse(id: req.id, name: req.name, argsJSON: req.argsJSON))
messages.append(LLMMessage(role: .assistant, content: assistantContent))

Test

  • Eval scenario: user submits "what time is it? Let me know with a friendly intro" → agent emits intro text + get_time tool_use → user follow-up "what did you say before checking the time?" → assert assistant references the prior intro

Source

  • Audit: .planning/audit-2026-05-12/mcp-agent-audit.md (2026-05-12) — finding HIGH-2
## Summary Assistant `text` content blocks emitted alongside `tool_use` are dropped from the message history. When Anthropic streams a turn that contains text deltas BEFORE a tool_use block (which Opus 4.7 does frequently — "Let me check the time for you… [tool_use:get_time]"), the orchestrator's `assistantTextSoFar` captures the text for D-02 escalation, but the appended assistant message contains ONLY the tool_use block. The preceding text is gone from the model's view on the next round trip. ## Evidence - File: `packages/AgentCore/Sources/AgentOrchestrator/AgentOrchestrator.swift:575-577`: ```swift messages.append(LLMMessage(role: .assistant, content: [ .toolUse(id: req.id, name: req.name, argsJSON: req.argsJSON), ])) ``` ## Impact Generally tolerable for Anthropic — the model resolves tool calls by id and doesn't re-read its own prose between turns. But: 1. If the user submits a follow-up referencing "you said X before calling the tool", the model has no record of saying X 2. Replay log captures text via `.textDelta` events, but replay viewer reconstruction won't match what the model actually saw at re-prompt Lower-severity version of the calibration bug shape — tests pass (`messages.last` is the assistant message they expected), the data boundary silently loses signal. ## Recommended fix Accumulate text + tool_use blocks into a single assistant `LLMMessage` per round-trip, flushing on either `stop_reason: .toolUse` (with tool blocks present) or `stop_reason: .endTurn` (text only): ```swift var assistantContent: [LLMContent] = [] if !assistantTextSoFar.isEmpty { assistantContent.append(.text(assistantTextSoFar)) } assistantContent.append(.toolUse(id: req.id, name: req.name, argsJSON: req.argsJSON)) messages.append(LLMMessage(role: .assistant, content: assistantContent)) ``` ## Test - Eval scenario: user submits "what time is it? Let me know with a friendly intro" → agent emits intro text + `get_time` tool_use → user follow-up "what did you say before checking the time?" → assert assistant references the prior intro ## Source - Audit: `.planning/audit-2026-05-12/mcp-agent-audit.md` (2026-05-12) — finding HIGH-2
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KofTwentyTwo/Jarvis#24