What happens
When an agent runs a tool (e.g. list_upload_sessions) and then hands off to another agent in the same turn, the request can fail with:
400 Unexpected role 'user' after role 'tool'
Why
For the receiving agent, handoff instructions are added as a HumanMessage (user role). If the last message in the filtered history is a ToolMessage (from the tool the router just ran), the sequence becomes … tool → user. Many chat APIs require an assistant turn after a tool message, so they reject this order.
When it appears
Only when the router runs a non-handoff tool and then does a handoff. If the router hands off without running another tool first, the last message is not a tool message and the error does not occur.
Suggested fix
When building messages for the receiving agent, if the last message in filteredMessages is already a ToolMessage, append the handoff instructions to that message’s content instead of adding a new HumanMessage. That keeps the sequence as … tool (with instructions in the tool message) and the next step is the assistant (receiving agent), which is valid.
Steps to reproduce
- Start with Universal (or any router that can call tools and hand off).
- Ask for something that triggers a tool call and then a handoff (e.g. “I want to visualize CSV data, please provide an upload” → router runs
list_upload_sessions, then transfers to Data Analysis).
- After the transfer, the 400 error can appear.
What happens
When an agent runs a tool (e.g.
list_upload_sessions) and then hands off to another agent in the same turn, the request can fail with:400 Unexpected role 'user' after role 'tool'Why
For the receiving agent, handoff instructions are added as a HumanMessage (user role). If the last message in the filtered history is a ToolMessage (from the tool the router just ran), the sequence becomes
… tool → user. Many chat APIs require an assistant turn after a tool message, so they reject this order.When it appears
Only when the router runs a non-handoff tool and then does a handoff. If the router hands off without running another tool first, the last message is not a tool message and the error does not occur.
Suggested fix
When building messages for the receiving agent, if the last message in
filteredMessagesis already a ToolMessage, append the handoff instructions to that message’s content instead of adding a new HumanMessage. That keeps the sequence as… tool(with instructions in the tool message) and the next step is the assistant (receiving agent), which is valid.Steps to reproduce
list_upload_sessions, then transfers to Data Analysis).