Repository navigation
Conversation
When a run was paused on a permission prompt, the resume branch of ReActAgent only read the ConfirmResults out of the incoming messages and dropped everything else, so a user message sent in the same request never reached the model, in this turn or in later ones. Hold those messages until the resumed tool calls have their results and append them at the next reasoning step, so a user turn never sits between a tool_use and its tool_result. If tool calls are still pending when the turn ends (for example a second permission prompt), log a warning instead of dropping them silently. Fixes agentscope-ai#3294
|
CLA Not Signed The Contributor License Agreement (CLA) check is currently pending on this PR ( @Ashfaqbs please sign the CLA via the CLA assistant badge in the comment above, or visit https://cla-assistant.io/agentscope-ai/agentscope-java. Once signed, the Automated check by github-manager-bot |
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
Fixes a real HITL gap: user messages sent together with a permission resume are now deferred until the resumed tool calls have results, so they never land between a tool_use and its tool_result. The approach (defer → flush at the next reasoning step → discard on turn end) is sound and well tested.
Two things keep me from approving this run:
- CLA is not signed yet (
license/cla: pending) — the PR cannot be merged until it is; see the reminder comment. - CI —
build (ubuntu-latest)failed onToolConfirmationCoordinatorTest.replacementTurnLeaseCannotReleaseOldTicketAndMayReuseToolUseId(CannotStubVoidMethodWithReturnValueonappendSessionEvent), inagentscope-service, i.e. outside this PR's diff.mainis green atea78c3172, so please rebase onto the latestmainand re-run; if the failure persists after rebase, flag it — it would look like a cross-module flake worth its own issue.
Inline notes cover the ArrayList cross-thread mutation, the silent-discard data-loss path, and a missing discard-path test.
Automated review by github-manager-bot
| * enter the context before the resumed tool calls have their results, so they wait here | ||
| * until the ReAct loop reaches its next reasoning step. | ||
| */ | ||
| private final List<Msg> deferredResumeMsgs = new ArrayList<>(); |
There was a problem hiding this comment.
[Warning] deferredResumeMsgs is a plain ArrayList mutated from more than one reactive path: addAll runs on the caller thread in doCallInner, while flushDeferredResumeMsgs() runs from doOnNext (possibly a scheduler/IO thread) and from reasoning(). ArrayList gives no cross-thread visibility or atomicity guarantee. If the resumed stream can emit or run the next reasoning step on different threads, this is a data race that could duplicate or miss messages. Consider CopyOnWriteArrayList (small lists, read-mostly) or draining via synchronized + List.copyOf, and note the chosen concurrency contract in the field javadoc.
| */ | ||
| private void discardDeferredResumeMsgs() { | ||
| if (!deferredResumeMsgs.isEmpty()) { | ||
| log.warn( |
There was a problem hiding this comment.
[Warning] discardDeferredResumeMsgs() drops user input with a WARN log as the only trace. If the resumed tool calls raise a second permission prompt, the user's accompanying message disappears from the conversation permanently. Suggestion: keep the deferred messages in the persisted pending-resume state (or attach them to the returned Msg / an event) so the next resume can flush them, instead of deleting them. If dropping is intentional product behavior, please say so in the PR description — it is a silent data-loss path otherwise.
| } | ||
|
|
||
| @Test | ||
| void userMessageSentWithDeniedResumeReachesTheNextModelRequest() { |
There was a problem hiding this comment.
[Info] Test coverage is good (approved + denied resume, context and model-request assertions). One gap: there is no test for the discard path — a resumed tool call that triggers a second ask, so the turn ends with pending tool calls. Worth pinning whatever the intended behavior is (drop with warn vs. carry over), because it exercises doOnSuccess + discardDeferredResumeMsgs.
Review summary (accompanies my inline review comments: #3295 (review))SummaryFixes a real HITL gap: user messages sent together with a permission resume are now deferred until the resumed tool calls have results, so they never land between a Two things keep me from approving this run:
Inline notes cover the Automated review by github-manager-bot |
Access deferredResumeMsgs through synchronized methods, and stop discarding the messages when the turn ends with pending tool calls: they stay queued until the next flush. Add a test for a resume that raises a second prompt.
|
Pushed e706863 for the review points.
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
Re-review of e706863e (previous review was on 91486570). The fix is correct and all earlier review points are handled: deferredResumeMsgs is now only touched through synchronized deferResumeMsgs() / flushDeferredResumeMsgs(), so the caller-thread vs. doOnNext race is gone; the discard path (silent loss of a user message with only a WARN) was replaced by keep-and-flush-after-the-next-resume; and userMessageIsPlacedBeforeTheSecondPromptWhenTheResumedCallAsksAgain now covers the second-prompt case that had no coverage before. The flush guard on MessageUtils.pendingToolUseIds() keeps the user turn from landing between a tool_use and its tool_result, which is the Anroid/Anthropic-style ordering constraint this PR targets.
- [Info]
agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java:1964— the deferred list is per-CallExecutionand in-memory only, which the javadoc now states explicitly. Worth being aware that a resume which terminates before reaching the next reasoning step (exception path, or areturnDirecttool that ends the turn) leaves those messages unflushed with the execution instance; the current tests cover the ask/ask/deny paths, not that edge. Not blocking.
LGTM, CLA signed.
CI
build (ubuntu-latest), Check License, codecov/patch ✅; build (windows-latest) still in progress at the time of this review.
Automated review by github-manager-bot
…ps-user-messages # Conflicts: # agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java
|
Merged main to resolve the conflict. Only one file conflicted ( Verified: |
AgentScope-Java Version
2.0.4-SNAPSHOT (main @ ea78c31)
Description
Fixes #3294.
When a run is paused on a permission prompt, the
askingToolCalls()branch ofReActAgentonly pulled theConfirmResults out of the incoming messages and discarded the rest. A user message sent in the same request as the resume never reached the model, in that turn or any later one, and no error was raised.This follows option A from the maintainer evaluation on the issue, with its ordering constraint:
deferredResumeMsgs). They are not written beforeresumeAgent(), because the approved tools' results are written byacting(0)and a user turn would end up between the assistanttool_useand itstool_result.reasoning()step, and only once no tool call is pending.WARNlog instead of silently. I did not try to solve that case here; say if you would rather reject the request in that situation.Tests (
ReActAgentHitlTest): the scripted model now records the messages of each request, and two new tests cover an approved and a denied resume that carry a user message. They assert the text is in the next model request and in the persisted context, and that it comes after the tool result. Both fail on main (user message sent with the resume must not be dropped) and pass with this change.Not covered: the AG-UI level case suggested in the evaluation (
AguiPermissionResumeTestresuming with a user message). This PR only touchesReActAgent; I have not run that path end to end.Checklist
mvn spotless:applymvn test) - I ranReActAgent*Test,*Permission*Testand*Hitl*Testinagentscope-core, all green, not the full suite