fix(#1644): enforce Anthropic tool sequencing and filter sub-agent messages#1880
fix(#1644): enforce Anthropic tool sequencing and filter sub-agent messages#1880aheritier wants to merge 2 commits intodocker:mainfrom
Conversation
…ation Stop inserting synthetic tool_result blocks and remove preflight sequencing validators. Instead, enforce Anthropic tool_use/tool_result adjacency and ID matching during message conversion (and preserve tool-call blocks during history trimming) so invalid histories fail fast with clear errors. Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
…l sequencing errors GetMessages() included all messages from session items regardless of which agent they belonged to. When sub-agent messages were present in the parent session (from historical persistence bugs before 28bec8d, or from in-memory sub-session expansion), they were interleaved between the parent agent's tool_use (transfer_task) and its tool_result. This broke Anthropic's strict tool sequencing requirement because: 1. Sub-agent assistant messages with their own tool_calls appeared between the root's tool_use and tool_result 2. Sub-agent tool_calls referenced tool_use_ids whose tool_results only existed in the sub-session, not the parent Filter messages in GetMessages by agent name: skip any message whose AgentName is set and doesn't match the current agent. User messages (empty AgentName) and the current agent's messages pass through. Refs docker#1644 Assisted-By: cagent
|
/review |
| }, | ||
| }, | ||
| }, | ||
| if pendingToolUseIDs == nil { |
There was a problem hiding this comment.
Beta converter silently drops orphan tool results while regular converter returns error
When pendingToolUseIDs == nil (indicating no preceding assistant tool_use in the current window), the beta converter silently skips tool messages without validation (lines 112-120). However, the regular converter in client.go:457-459 explicitly returns an error for this exact scenario:
if pendingToolUseIDs == nil {
return nil, fmt.Errorf("unexpected tool result without preceding tool_use (tool_use_id=%q)", messages[i].ToolCallID)
}Issue: This inconsistency violates the PR's stated goal of "strict enforcement" and "removing self-repair". Orphan tool results are a protocol violation per Anthropic's requirements — they should be caught and reported in both converters, not silently dropped in one.
Impact: Malformed message sequences (e.g., assistant with no tool calls followed by tool results) pass through undetected in the beta API path, potentially causing downstream issues or silent data loss.
Recommendation: Change the beta converter to return an error like the regular converter does, maintaining consistent strict validation across both API paths.
Summary
Fixes #1644 —
unexpected tool_use_id found in tool_result blockserrors when using Anthropic models with multi-agent sessions.Includes #1680 (provider/anthropic: enforce tool sequencing, remove self-repair/validation).
Root Causes
Two independent issues caused orphaned
tool_resultblocks to reach the Anthropic API:1. Sub-agent message interleaving in parent session
GetMessages()included all messages from session items regardless of which agent they belonged to. When sub-agent messages were present in the parent session (from historical persistence bugs or from in-memory sub-session expansion), they were interleaved between the parent agent'stool_use(transfer_task) and itstool_result.This broke Anthropic's strict tool sequencing requirement because:
tool_callsappeared between the root'stool_useandtool_resulttool_callsreferencedtool_use_ids whosetool_results only existed in the sub-session, not the parent2.
trimMessagescould orphan tool resultsThe old
trimMessagesremoved individual messages without treating[assistant(tool_calls) + tool results]as atomic blocks. Trimming an assistant message left its tool results behind, creating invalid sequences.3. Silent self-repair masked the real problem
The Anthropic provider had
validateAnthropicSequencing/repairAnthropicSequencingthat inserted synthetictool_resultblocks to paper over broken sequences. This prevented fast failure and made the underlying issues harder to diagnose.Changes
Layer 1 —
GetMessages()sub-agent filtering (237b0069)Filter messages by agent name: skip any message whose
AgentNameis set and doesn't match the current agent. User messages (emptyAgentName) and the current agent's messages pass through.Layer 2 —
trimMessagesrewrite with atomic tool blocks (03a585a7)Rewritten to use a reverse-scan approach that:
[assistant(tool_calls) + tool results]as atomic blocks — kept or dropped togetherLayer 3 — Anthropic
convertMessagesstrict validation (03a585a7)Replaced the validate-then-repair approach with strict enforcement during message conversion:
pendingToolUseIDsand validates eachtool_resultagainst themvalidateAnthropicSequencing,repairAnthropicSequencing, and all related helpersTesting
trimMessagestests to reflect atomic-block and user-message-protection semanticsTestTrimMessages_AtomicToolBlocksandTestTrimMessages_OrphanedToolResultsTestGetMessages_FiltersSubAgentMessagesfor sub-agent message filtering