Repository navigation
[Bug]:: Message: Invalid tool result IDs: [call_01_w7wlz4ch9Hiw4Um74zwv8117]. Expected: [call_00_OugZmGqpB71ykFO5IjxM8342] #3043
Description
(大模型调试了一天发现的问题)
Describe the bug Resuming a run with external-execution ("client/frontend") tool results can fail with IllegalStateException: Invalid tool result IDs: [...] Expected: [...], permanently wedging the session.
ReActAgent.CallExecution.validateAndAddToolResults fails the whole run whenever any submitted tool-result ID is not in the pending tool-call IDs of the agent's last assistant message. With AG-UI clients that replay their full transcript (e.g. CopilotKit headless + browser-side frontend tools), a later user message can carry stale tool results from an earlier assistant round (the round whose result was already consumed or whose required result lives mid-transcript). After the first such submission the run dies with a RUN_ERROR, and because the stale result stays in the client transcript, every subsequent user message in the thread fails the same validation forever.
Observed across multiple reproductions:
Invalid tool result IDs: [call_01_7icKHvTMy32ReIAnjkgu2856]. Expected: [call_00_EEGrx8VtxFIpkJarXNXD6198]
Invalid tool result IDs: [call_01_SxJ4HtkNkradgAcGZZBj3288]. Expected: [call_00_BibzeGa9V4O4xDeIvZju4111]
The "Expected" ID always refers to an assistant tool-call message that sits earlier in the client transcript (its result was trimmed away by AguiRequestProcessor.extractLatestUserMessage, which only forwards messages that trail the client's last assistant message), while the "Provided" ID belongs to a newer round that the server is no longer waiting for.
To Reproduce Steps to reproduce the behavior:
Stand up an AG-UI runtime (AguiMvcController) whose agent (ReActAgent, stateful per thread) exposes a mix of server tools and client-executed tools (schema-only / ToolSuspendException tools).
Drive it from a transcript-replaying client that executes client tools in the browser:
Java
// First run -> assistant message asks for a client tool "external_api" (id ext1), run suspends.
agent.streamEvents(List.of()).blockLast();
// Resume #1 -> client replies with the correct result for ext1; server resumes.
agent.streamEvents(List.of(toolResultMsg("ext1", "ok"))).blockLast();
// Later: agent produces ANOTHER assistant tool round, then the user sends a new message
// while the client transcript still replays the (already consumed) "ext1" result as trailing.
Msg stale = Msg.builder().role(MsgRole.TOOL)
.content(ToolResultBlock.builder().id("ext1")...build()).build();
Msg fresh = Msg.builder().role(MsgRole.USER).textContent("please continue").build();
agent.streamEvents(List.of(stale, fresh)).collectList().block(); // throws
See error:
IllegalStateException: Invalid tool result IDs: [ext1]. Expected: []
(In production the payload shape is the AG-UI run resume body; the stack surfaces on the client as a RUN_ERROR event, code agent_run_error_event.)
Trigger conditions observed in real traffic:
The model emits more than one tool call in a single assistant message (e.g. one client tool + one server tool in the same reply). The client splits them into separate local assistant rounds, submits them in separate resume requests, and later replays already-accepted results.
A new user message is sent while an older external-execution round is still "pending" server-side but already resolved on the client.
Expected behavior A resumed request that carries tool results should be tolerant of stale results:
Tool-result IDs that are not among the current pending IDs should be ignored (logged), not treated as a fatal protocol violation.
If the input only contains stale results plus a real user text message, the abandoned pending tool round should be cancelled (synthetic error result, same as the existing enablePendingToolRecovery behavior) and the new user message should proceed as a fresh instruction — instead of throwing forever on every later turn.
Duplicate IDs and "partial results + text" should still be rejected exactly as today.
Error messages
java.lang.IllegalStateException: Invalid tool result IDs: [call_01_7icKHvTMy32ReIAnjkgu2856]. Expected: [call_00_EEGrx8VtxFIpkJarXNXD6198]
at io.agentscope.core.ReActAgent$CallExecution.validateAndAddToolResults(ReActAgent.java:2221)
Surfaced over AG-UI as:
JSON
{
"type": "run-error",
"code": "INVALID_INPUT_ERROR",
"message": "Invalid tool result IDs: [call_01_...]. Expected: [call_00_...]"
}
Environment (please complete the following information):
AgentScope-Java Version: 2.0.3-SNAPSHOT (also confirmed affected in 2.0.2 — same ReActAgent.validateAndAddToolResults code)
Java Version: [17+ expected; reproduce under your JDK]
OS: Windows
Additional context Root-cause chain:
ReActAgent.validateAndAddToolResults (private CallExecution method) treats every provided result ID not in pendingIds as a hard error.
With server-side memory enabled, AguiRequestProcessor.extractLatestUserMessage forwards only the messages after the client's last assistant message; the earlier round's real result (which the server actually needs) sits mid-transcript and is dropped.
Because the client keeps replaying that stale round on every subsequent request, the session never recovers — one bad round bricks all later turns in the thread.
Proposed fix (minimal, in validateAndAddToolResults):
Partition provided results into valid (in pendingIds) and stale (not pending). Log stale IDs at warn and drop them instead of throwing.
If no valid results remain and the input carries a non-tool-result user message, synthesize an ToolResultBlock.error for each still-pending call (same pattern as maybePatchPendingToolCalls / the shutdown-interrupt recovery) and add only the cleaned messages, so the new user instruction is handled by a fresh reasoning step.
Only throw when no valid results remain and there is no user text.
Strip stale ToolResultBlocks from the messages before appending to context to avoid unbounded duplicate growth.
A regression test for this shape (stale result + new user text resumes successfully and cancels the abandoned pending call) was validated against a local patched build of ReActAgentNewLoopReplyTest; all related suites (ReActAgentHitlTest, ReActAgentSummarizingTest, ReActAgentNewLoopReplyTest) stayed green. Happy to open a PR with the fix and test if maintainers agree with the direction.
Error messages
Code: agent_run_error_event
Message: Invalid tool result IDs: [call_01_w7wlz4ch9Hiw4Um74zwv8117]. Expected: [call_00_OugZmGqpB71ykFO5IjxM8342]
Context: {
"source": "onRunErrorEvent",
"event": {
"type": "RUN_ERROR",
"timestamp": 1788838954967,
"message": "Invalid tool result IDs: [call_01_w7wlz4ch9Hiw4Um74zwv8117]. Expected: [call_00_OugZmGqpB71ykFO5IjxM8342]",
"code": "INVALID_INPUT_ERROR",
"threadId": "476270bb-8bd3-4d2d-a96b-7519a0cce1b5",
"runId": "937f2963-789c-4310-96c3-907c90d8c436"
},
"runtimeErrorCode": "INVALID_INPUT_ERROR",
"agentId": "f_assistant"
}
Stack:
Error: Invalid tool result IDs: [call_01_w7wlz4ch9Hiw4Um74zwv8117]. Expected: [call_00_OugZmGqpB71ykFO5IjxM8342]
at Object.onRunErrorEvent (http://localhost:5173/node_modules/.vite/deps/directive-CFNeHBg4.js?v=dab07746:20263:36)
at http://localhost:5173/node_modules/.vite/deps/directive-CFNeHBg4.js?v=dab07746:9454:88
at N (http://localhost:5173/node_modules/.vite/deps/directive-CFNeHBg4.js?v=dab07746:9032:17)
at async http://localhost:5173/node_modules/.vite/deps/directive-CFNeHBg4.js?v=dab07746:9454:39
Environment (please complete the following information):
agentscope 2.0.3-snapsho
Additional context
Add any other context about the problem here.
Activity
xueil commentedon Sep 8, 2026
大模型给的修改建议:位置:ReActAgent.validateAndAddToolResults 做法:
结果 id 不在当前 pending 集合 → 判为“陈旧/重放”,丢弃并 warn,不再抛 Invalid tool result IDs;
若只剩陈旧结果、但输入里带真实用户文本 → 给仍 pending 的工具轮生成合成 error 结果(复用 maybePatchPendingToolCalls 思路)取消该轮,让新指令继续推理,而不是整轮/后续每轮都失败;
只有当“无有效结果且无文本”时才抛错;
追加 context 前剔除陈旧 ToolResultBlock,避免重复膨胀。
oss-maintainer commentedon Sep 13, 2026
Issue Evaluation
Category:
bug| Status: ConfirmedVerified against current main (
7399ed6) by source-level analysis:ReActAgentderives the pending tool-call ID set from the last assistant message only (agentscope-core/.../ReActAgent.java:2199) and then rejects every submitted result whose ID is not in that set withIllegalStateException: Invalid tool result IDs(ReActAgent.java:2250-2266).ReActAgent.java:2258-2290). The still-pending round therefore survives unchanged, and because the stale result also stays in the client transcript, every later submission repeats the same mismatch.ReActAgent.java:2086-2090), andenablePendingToolRecoveryis off by default (ReActAgent.java:4600), so it never patches this input shape.AguiRequestProcessor.extractLatestUserMessageforwards all messages trailing the last assistant message (agentscope-extensions-protocol/agentscope-extensions-agui/.../processor/AguiRequestProcessor.java:313-347),AguiMessageConverterconverts those tool messages intoToolResultBlocks (.../AguiMessageConverter.java:289-300), andAguiAgentAdapterhands them toReActAgent(.../AguiAgentAdapter.java:149-167) — so results already consumed in an earlier round arrive alongside the newly required ones for schema-only / client-executed tools (SchemaOnlyTool.java:101-135).ReActAgent.streamEventsexternal/client-executed tool resumes over AG-UI with transcript-replaying clients (CopilotKit-style). Severity: medium (session becomes unusable, but no data corruption). Fix complexity: medium.Suggested direction (consistent with the reporter's proposal in this thread): treat a result ID outside the pending set as a stale replay — drop it with a WARN instead of throwing; when the same submission carries real user text, synthesize cancellation results for the still-pending round (reusing the
maybePatchPendingToolCallsapproach) and continue reasoning; throw only when there is neither a valid result nor user text; and strip staleToolResultBlocks before appending context so the prompt cannot re-inflate. The existing duplicate-ID and partial-result guards (ReActAgent.java:2250-2255,2268-2282) should be kept as they encode real invariants.Existing coverage: PR #3046 ("fix(agui): reconcile replayed tool results with session state") already links this issue. No automated fix PR will be generated from this evaluation — follow up on that PR.
Note: source-level verification only; no build or reproduction was run.
Automated evaluation by github-manager-bot