Parallel tool calls persist only the first tool response and break subsequent turns
Describe the bug
When an agent invokes multiple tools in parallel, all tools execute successfully, but the agent-run message handler persists only the first tool response. The assistant message still contains every tool call, so the stored conversation history is incomplete. A subsequent user turn can then fail with an internal server error when the incomplete tool-call history is sent to the model.
To Reproduce
- Create two simple workflows that return fixed values.
- Add both workflows to an agent as tools.
- Ask the agent to invoke both tools in parallel in one model turn.
- Confirm that both workflows execute successfully.
- Send another user message in the same conversation.
- Observe that only one tool response was persisted and the following turn fails.
Protocol example
The model returns two parallel tool calls:
{
"role": "assistant",
"tool_calls": [
{"id": "call_a", "function": {"name": "workflow_a"}},
{"id": "call_b", "function": {"name": "workflow_b"}}
]
}Both tools execute successfully, so two responses should be persisted:
[
{"role": "tool", "tool_call_id": "call_a", "content": "result_a"},
{"role": "tool", "tool_call_id": "call_b", "content": "result_b"}
]Currently only the first response is persisted because handlerTooResponse reads chunk.ToolsMessage[0]. This results in an incomplete conversation history, causing subsequent turns to fail with Something error: Internal Server Error.
Expected behavior
Each tool response returned by a parallel tool invocation should be persisted as a separate tool_response message. Conversation history should keep the assistant tool-call message only when responses for all of its tool-call IDs are present, so subsequent turns receive valid model history.
Version:
upstream/main at fefb05ff (fix(infra): align statefulset servicename with actual service name (#2723)).
Environment:
- Go 1.24.0
- macOS arm64
- Docker Desktop, official Docker Compose deployment
Additional context
handlerTooResponse currently builds and saves a response from chunk.ToolsMessage[0], even though the tool execution result can contain multiple messages. The history pairing logic also assumes function-call and tool-response messages occur in even counts, which does not hold for one assistant message containing multiple tool calls.
Source: coze-dev/coze-studio