fix(session): Automatically detect and remove dangling tool calls whe…#873
fix(session): Automatically detect and remove dangling tool calls whe…#873liuxh613 wants to merge 2 commits intoagentscope-ai:mainfrom
Conversation
…n resuming a conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
857e491 to
ee96e85
Compare
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Pull request overview
Fixes session restoration failures for ReActAgent when persisted memory contains incomplete (dangling) tool calls, preventing IllegalStateException on the next user interaction.
Changes:
- Add automatic cleanup on
ReActAgent.loadFrom()to remove dangling tool-call state after restoring memory. - Add unit tests covering cleanup behavior and non-cleanup behavior on restore.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java | Runs cleanup after memory restoration to avoid pending-tool-call illegal state. |
| agentscope-core/src/test/java/io/agentscope/core/agent/ReActAgentTest.java | Adds tests for cleanup of pending tool calls across session restore scenarios. |
| Msg toolResultMsg = | ||
| Msg.builder() | ||
| .name("User") | ||
| .role(MsgRole.USER) | ||
| .content( | ||
| List.of( | ||
| ToolResultBlock.builder() | ||
| .id("call_complete_456") | ||
| .output( | ||
| List.of( | ||
| TextBlock.builder() | ||
| .text("Sunny, 25°C") | ||
| .build())) | ||
| .build())) | ||
| .build(); | ||
| memory.addMessage(toolResultMsg); |
| * session. This method removes the last assistant message if it contains tool calls without | ||
| * corresponding results, preventing IllegalStateException on the next user input. | ||
| */ | ||
| private void cleanupPendingToolCalls() { | ||
| Set<String> pendingIds = getPendingToolUseIds(); | ||
| if (!pendingIds.isEmpty()) { | ||
| // Find and remove the last assistant message with pending tool calls | ||
| List<Msg> messages = memory.getMessages(); | ||
| for (int i = messages.size() - 1; i >= 0; i--) { | ||
| Msg msg = messages.get(i); | ||
| if (msg.getRole() == MsgRole.ASSISTANT) { | ||
| memory.deleteMessage(i); | ||
| log.warn( | ||
| "Removed incomplete tool calls from restored session. Pending IDs: {}", | ||
| pendingIds); | ||
| break; | ||
| } | ||
| } |
| Set<String> pendingIds = getPendingToolUseIds(); | ||
| if (!pendingIds.isEmpty()) { | ||
| // Find and remove the last assistant message with pending tool calls | ||
| List<Msg> messages = memory.getMessages(); |
There was a problem hiding this comment.
getMessages() returns a filtered copy; indices might be out of sync with the internal list.
|
In my view, storing unexpected messages in the session shouldn't be handled at the framework level; instead, they should be surfaced to the developer. This might warrant further discussion. |
|
FYI (#956) |
thanks,This seems like a solution approach. |
#871
Describe the bug
When restoring Agent state from a persistent session (such as a database), if the Memory contains incomplete tool calls (ToolUseBlock without a corresponding ToolResultBlock), it causes the following error:
java.lang.IllegalStateException: Cannot add messages without tool results when pending tool calls exist.
Pending IDs: [call_ba1efbfa083f4323adb4f9]
To Reproduce
Steps to reproduce the behavior:
You code
How to execute
See error
Expected behavior
The ReActAgent.loadFrom() method can include automatic cleanup logic.
Error messages
2026-03-05 12:22:57.040 INFO [] io.agentscope.core.tool.Toolkit Line:239 - Registered tool 'context_reload' in group 'ungrouped'
2026-03-05 12:22:57.040 INFO [] io.agentscope.core.memory.autocontext.AutoContextHook Line:185 - AutoContextMemory integration completed for agent: ReActAgent
Error during execution: Cannot add messages without tool results when pending tool calls exist. Pending IDs: [call_ba1efbfa083f4323adb4f9]
java.lang.IllegalStateException: Cannot add messages without tool results when pending tool calls exist. Pending IDs: [call_ba1efbfa083f4323adb4f9]
at io.agentscope.core.ReActAgent.validateAndAddToolResults(ReActAgent.java:335)
at io.agentscope.core.ReActAgent.doCall(ReActAgent.java:256)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:133)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:130)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:246)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:307)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2565)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onSubscribe(MonoFlatMap.java:293)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:56)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:75)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:166)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:246)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:307)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2565)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onSubscribe(MonoFlatMap.java:293)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:56)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:75)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:166)
at io.agentscope.core.tracing.TracerRegistry$1.lambda$onNext$0(TracerRegistry.java:92)
at io.opentelemetry.context.Context.lambda$wrapSupplier$8(Context.java:344)
at io.agentscope.core.tracing.telemetry.TelemetryTracer.runWithContext(TelemetryTracer.java:226)
at io.agentscope.core.tracing.TracerRegistry$1.onNext(TracerRegistry.java:89)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2565)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.request(FluxHide.java:152)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:195)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.request(FluxHide.java:152)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:195)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.request(FluxHide.java:152)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:172)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.request(FluxHide.java:152)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:195)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.request(FluxHide.java:152)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:195)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2361)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onSubscribe(FluxOnErrorResume.java:75)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:118)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onSubscribe(FluxHide.java:123)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:118)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onSubscribe(FluxHide.java:123)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onSubscribe(FluxHide.java:123)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:118)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onSubscribe(FluxHide.java:123)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:118)
at io.agentscope.core.tracing.TracerRegistry$1.onSubscribe(TracerRegistry.java:83)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onSubscribe(FluxHide.java:123)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:56)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:75)
at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:56)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:75)
at reactor.core.publisher.MonoUsing.subscribe(MonoUsing.java:109)
at reactor.core.publisher.Mono.subscribe(Mono.java:4569)
at reactor.core.publisher.Mono.block(Mono.java:1772)
at io.agentscope.examples.advanced.DaMengRAGFlowStudioExample.main(DaMengRAGFlowStudioExample.java:318)
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104)
at reactor.core.publisher.Mono.block(Mono.java:1773)
... 1 more
Saving final session...
Closing resources...
2026-03-05 12:22:57.116 INFO [] com.zaxxer.hikari.HikariDataSource Line:349 - HikariPool-1 - Shutdown initiated...
2026-03-05 12:22:57.118 INFO [] com.zaxxer.hikari.HikariDataSource Line:351 - HikariPool-1 - Shutdown completed.
Shutting down Studio...
✓ Done
2026-03-05 12:22:57.120 INFO [] io.agentscope.core.studio.StudioWebSocketClient Line:154 - Socket.IO disconnected from /python namespace
2026-03-05 12:23:57.137 INFO [] io.agentscope.core.model.transport.HttpTransportFactory Line:185 - Shutting down 1 managed HttpTransport(s)
Environment (please complete the following information):
AgentScope-Java Version: 1.0.10-SNAPSHOT
Java Version: 21
OS: windows11
Additional context
Add any other context about the problem here.