Skip to content

fix(runner): route combined tool responses to each parallel branch - #7698

Open
xxwtiancai wants to merge 1 commit into
google:mainfrom
xxwtiancai:fix/multi-branch-confirmation-7685
Open

xxwtiancai wants to merge 1 commit into
google:mainfrom
xxwtiancai:fix/multi-branch-confirmation-7685

Conversation

@xxwtiancai

@xxwtiancai xxwtiancai commented Oct 23, 2026 •

Copy link
Copy Markdown

Link to Issue or Description of Change

Closes #7685.

When one incoming message confirms tool calls from two parallel branches,
Runner currently assigns the whole message to the first matching call's
branch. The other branch cannot see its confirmation, so its approved tool
does not execute and its model receives the pending-confirmation placeholder.
Reversing the response order changes which tool executes.

Persist the message as one event per matching (branch, isolation_scope)
context. Each response stays with its originating call; the existing branch
and isolation filters remain intact. Apply the supplied state delta once,
preserve response order within each context, and yield all resulting user
events when requested.

Give these events a shared internal message identifier. Confirmation selection
can then collect the latest message's visible responses when an ancestor sees
multiple projected events, including after storage reorders equal-timestamp
events. Selection remains restricted to that message and invocation. Messages
whose responses all share one context retain the single-event path.

Testing Plan

Update: the branch has since been rebased onto main at
cbca417c (the seven upstream additions touch plugins, eval, CLI, descriptors,
MCP handling, McpTool auth and the MongoDB memory service — none overlap these
files).
On the rebased branch, Python 3.11.14 / macOS arm64: all 58 new cases pass, the
full runner, flows and workflow suites pass (2770 passed, 2 skipped, 4
xfailed), the base-commit control still reproduces 16 failed / 32 passed
without the fix, and pre-commit and mypy on the changed files are clean.

Unit tests

  • Added regression tests in the existing test modules.
  • All unit tests pass locally (two baseline/environment failures below).
  • Full pristine Python 3.11–3.14 tox matrix.

The 48 public-Runner cases cover Workflow and resumable ParallelAgent, both
response orders, all four approval/rejection combinations, an unaffected third
branch approved later, separate-message controls, ancestor/descendant branch
views, and a shared branch with distinct isolation scopes. Against the base
commit, this matrix has 16 failed, 32 passed. With the fix, all 48 pass.
Ten additional memory/SQLite cases check context routing, single/same-context
controls, unique event IDs, preserved metadata, storage reload, input ownership,
and applying state updates once. All 58 new cases pass.

Focused Runner, confirmation, invocation-context, workflow-interruption and
event suites, using this source tree and pre-existing local test environments:

Python Passed Skipped Xfailed
3.11.14 970 1 3
3.12.15 970 1 3
3.13.5 970 1 3
3.14.9 970 1 3

Full Python 3.11 suite, with no test exclusions:

2 failed, 21009 passed, 97 skipped, 18 xfailed, 2 xpassed,
42 subtests passed in 236.64s

Both failures reproduce on the unchanged base in the same environment:

  • test_reading_directory_raises: the macOS fixture does not raise the expected
    OSError; it also fails outside the sandbox.
  • test_completions_http_client_streams_longer_than_request_timeout: the sandbox
    rejects binding a local listener to 127.0.0.1. It passes outside the sandbox.

The full supported-version matrix in fresh tox environments has not been run;
the focused runs above reuse installed dependencies. Pre-commit checks for the
changed files and git diff --check pass. Mypy on Runner and the workflow
entrypoint reports the same 236 existing errors as the base after normalizing
line numbers, with no new diagnostics. Wheel and source distribution builds pass.
The optional addlicense executable is absent, so its hook skips the tool;
existing file headers are preserved.

Independent Astra xhigh review approves the final patch. Its separate checks
include four SQLite append/reload/resume cases with deliberately reversed event
order and three stale-message controls, all passing, plus 73 selected repository
tests. The SQLite resume probe migrates an actual paused in-memory session into
SQLite before exercising the changed persistence path; it does not claim full
dynamic-workflow startup coverage on SQLite.

Manual end-to-end tests

Use the exact scripted BaseLlm reproduction from #7685 as repro_issue_7685.py.
It requires no model service or API key. From the checkout with test dependencies
available:

PYTHONPATH="$PWD/src" python repro_issue_7685.py
PYTHONPATH="$PWD/src" python repro_issue_7685.py --one

The combined-message run before this fix prints:

tool ran for: ['a']
model a got: {'result': 'a acted'}
model b got: {'error': 'This tool call requires confirmation, please approve or reject.'}

Both commands after the fix print:

google-adk 2.12.0
tool ran for: ['a', 'b']
model a got: {'result': 'a acted'}
model b got: {'result': 'b acted'}

Also ran both commands using the extracted locally built wheel as PYTHONPATH,
with existing test dependencies; both pass. This is a wheel-source smoke check,
not a fresh dependency-install test.

Checklist

  • Read CONTRIBUTING.md and applicable repository instructions.
  • Reviewed the final change and completed independent review.
  • Added tests that fail on the base and pass with the fix.
  • Ran the offline Runner reproduction and separate-message control.
  • New and existing unit tests all pass locally (failures documented above).
  • Required pristine full supported-version tox matrix complete.

No public API or stored model schema changes, and no downstream changes are
required.

@xxwtiancai
xxwtiancai force-pushed the fix/multi-branch-confirmation-7685 branch from dd6e8f1 to 50af2d6 Compare October 23, 2026 16:55
@xxwtiancai
xxwtiancai marked this pull request as draft October 23, 2026 17:26
@xxwtiancai
xxwtiancai marked this pull request as ready for review October 23, 2026 17:26
A user message answering confirmation calls from two parallel branches was
stamped with the first matching call's branch, so the other branch never saw
its approval: its tool did not run and its model received the
pending-confirmation placeholder. Persist the message as one event per
matching (branch, isolation_scope) context, linked by an internal message
group id that confirmation selection uses to collect the whole message, and
apply the state delta once.

Fixes google#7685
@xxwtiancai
xxwtiancai force-pushed the fix/multi-branch-confirmation-7685 branch from 50af2d6 to a784ad8 Compare October 23, 2026 17:28
@xxwtiancai
xxwtiancai marked this pull request as draft October 23, 2026 17:28
@xxwtiancai
xxwtiancai marked this pull request as ready for review October 23, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants