fix: accumulate endOfAgent in EventActions.merge so a parallel tool's stop request survives#1376
Merged
copybara-service[bot] merged 1 commit intoJul 24, 2026
Conversation
…parallel stop requests
Contributor
|
Hi @svetanis, thank you for your contribution! We appreciate you taking the time to submit this pull request. Currently this PR is under review by our team, we will keep you posted if any additional information is required. thank you. |
kvmilos
approved these changes
Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
EventActions.Builder.mergeoverwritesendOfAgentinstead of accumulating it(
EventActions.java:396):endOfAgentis the stop signal a tool sets to end an invocation — a public contract (the repo's ownEndInvocationActionTestexercises it). Being a primitiveboolean, "never set" and "explicitlyfalse" are indistinguishable. Its one caller that matters,
Functions.mergeParallelFunctionResponseEvents, folds the responses of parallel tool calls — sowhen one tool asks to stop and a later tool in the same turn did not, the later
falseclobbers therequest and the agent keeps running. Whether the stop survives depends purely on the order the model
listed the calls.
Solution:
OR the flag instead of overwriting it, so it accumulates like every other field in the method:
Once any merged event has requested the stop, it stays requested — order-independent, which the
parallel fold needs. OR is the only operator that works here: AND would let any silent tool veto the
stop; plain assignment is order-dependent — the bug.
The change is that one line, in
EventActions.Builder.merge— nothing else.Known limitation (intended): a tool can no longer withdraw a stop another tool requested in the
same turn.
Testing Plan
Unit Tests:
New test in
EventActionsTest(order-independence of the merged flag), asserting both orderings yieldtrue:The existing
EventActionsTest.merge_mergesAllFields(mergesendOfAgent(true)into an unset builderand asserts
true) is unaffected:false || true == true. No existing test depends onlast-writer-wins.
Ran
EventActionsTest: 12/12 pass, including the newmerge_endOfAgentIsOrderIndependent.Manual End-to-End (E2E) Tests:
Offline, deterministic, no API key, Node-free. A real
LlmAgent+FunctionTools +InMemoryRunnerrun across a permutation table, differing only in the order/count of the parallel calls the (stub)
model lists. The observable is the model-call count: a tool asked to stop, so a second call means the
stop was lost. Runs with the stop requested last are controls.
Before fix (
main):The stop survives only when it is requested last (B, E); every other order — including three-tool
turns and two stop requests (F) — keeps running.
After fix (same demo, rebuilt jar):
Every run now stops after one model call, whatever the order or tool count.
Checklist