Skip to content

fix(#1644): enforce Anthropic tool sequencing and filter sub-agent messages#1880

Draft
aheritier wants to merge 2 commits intodocker:mainfrom
aheritier:anthropic-tools
Draft

fix(#1644): enforce Anthropic tool sequencing and filter sub-agent messages#1880
aheritier wants to merge 2 commits intodocker:mainfrom
aheritier:anthropic-tools

Conversation

@aheritier
Copy link
Contributor

Summary

Fixes #1644unexpected tool_use_id found in tool_result blocks errors 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_result blocks 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's tool_use (transfer_task) and its tool_result.

This broke Anthropic's strict tool sequencing requirement because:

  • Sub-agent assistant messages with their own tool_calls appeared between the root's tool_use and tool_result
  • Sub-agent tool_calls referenced tool_use_ids whose tool_results only existed in the sub-session, not the parent

2. trimMessages could orphan tool results

The old trimMessages removed 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/repairAnthropicSequencing that inserted synthetic tool_result blocks 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 AgentName is set and doesn't match the current agent. User messages (empty AgentName) and the current agent's messages pass through.

Layer 2 — trimMessages rewrite with atomic tool blocks (03a585a7)

Rewritten to use a reverse-scan approach that:

Layer 3 — Anthropic convertMessages strict validation (03a585a7)

Replaced the validate-then-repair approach with strict enforcement during message conversion:

  • Tracks pendingToolUseIDs and validates each tool_result against them
  • Returns immediate errors for orphaned tool results, missing tool results, ID mismatches
  • Removed validateAnthropicSequencing, repairAnthropicSequencing, and all related helpers
  • Same cleanup applied to the Beta API path

Testing

  • Updated existing trimMessages tests to reflect atomic-block and user-message-protection semantics
  • Added TestTrimMessages_AtomicToolBlocks and TestTrimMessages_OrphanedToolResults
  • Added TestGetMessages_FiltersSubAgentMessages for sub-agent message filtering
  • All Anthropic provider conversion tests updated for strict error returns

rumpl and others added 2 commits February 28, 2026 16:45
…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
@aheritier
Copy link
Contributor Author

/review

Copy link

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Found 1 critical issue: Inconsistent orphan tool result handling between beta and regular converters violates the PR's goal of strict validation.

},
},
},
if pendingToolUseIDs == nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unexpected tool_use_id found in tool_result blocks

2 participants