Skip to content

fix(flows): compare branch paths on segment boundaries, not raw string prefixes - #1415

Open
svetanis wants to merge 1 commit into
google:mainfrom
svetanis:fix/contents-branch-prefix-match
Open

fix(flows): compare branch paths on segment boundaries, not raw string prefixes#1415
svetanis wants to merge 1 commit into
google:mainfrom
svetanis:fix/contents-branch-prefix-match

Conversation

@svetanis

@svetanis svetanis commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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:

Contents.isEventBelongsToBranch gates which past events an agent sees, using a raw
invocationBranch.startsWith(eventBranch). A branch is a path of agent-name segments — BaseAgent
builds it as branch + "." + name() — so the test matches inside a segment:
"root.agent_10".startsWith("root.agent_1") is true. Peer agents whose names share a prefix
(agent_1/agent_10, search/search_v2) therefore see each other's output, which is what
Event.branch()'s javadoc says branches exist to prevent. The leaked event arrives re-authored as a
user-role message, with no error or warning.

Solution:

Require an exact match, or a prefix that ends on a segment boundary:

return Strings.isNullOrEmpty(invocationBranch)
    || Strings.isNullOrEmpty(eventBranch)
    || invocationBranch.equals(eventBranch)
    || invocationBranch.startsWith(eventBranch + ".");

Ancestor and same-branch events stay visible; only the within-segment match is removed, and the
null/empty short-circuits are unchanged.

File Change
core/src/main/java/…/flows/llmflows/Contents.java the comparison above, plus a comment naming the case
core/src/test/java/…/flows/llmflows/ContentsTest.java 5 tests

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

The first test fails on main; the other four pass before and after, pinning every remaining arm of
the predicate so the filter is visibly not tightened further:

Test Asserts
processRequest_siblingBranchSharesNamePrefix_excludesSiblingEvent agent_10 on root.agent_10 does not see an agent_1 event on root.agent_1
processRequest_sameBranch_includesEvent an exact branch match stays visible
processRequest_ancestorBranch_includesEvent a genuine ancestor (root) stays visible
processRequest_eventWithoutBranch_includesEvent an unbranched event (user input) stays visible on any branch
processRequest_noInvocationBranch_includesBranchedEvent an invocation with no branch still sees branched events

On main the first one fails with the peer's output present, and note the form it arrives in:

expected to be empty
but was : [Content{parts=[Part{text=For context:},
                          Part{text=[agent_1] said: sibling output}], role=user}]

Manual End-to-End (E2E) Tests:

A real ParallelAgent with two LlmAgent sub-agents on gemini-3.5-flash, run before and after the
change. Each agent's secret code lives in its own instruction (system instruction, never
contents) and carries a per-run token, so a code reaching a sibling can only have come from leaked
history. A second pair with no shared name prefix runs as a control.

Before:

--- Colliding names - agent_1 and agent_10 ---
  agent_10 was shown it     : true
  agent_10 repeated it      : true
  agent_10's model read     : [Begin., Begin., NONE, For context:, [agent_1] said: SECRET-FROM-agent_1-4A8D0189, ...]
  agent_10's model answered : [NONE, SECRET-FROM-agent_1-4A8D0189]
--- Control - agent_1 and agent_2 ---
  agent_2 was shown it      : false

After:

--- Colliding names - agent_1 and agent_10 ---
  agent_10 was shown it     : false
  agent_10 repeated it      : false
  agent_10's model answered : [NONE, NONE]
--- Control - agent_1 and agent_2 ---
  agent_2 was shown it      : false

The colliding pair now reads exactly like the control.

Checklist

  • I have read the CONTRIBUTING.md document.
  • My pull request contains a single commit.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

@hemasekhar-p hemasekhar-p self-assigned this Aug 10, 2026
@hemasekhar-p

Copy link
Copy Markdown
Contributor

Hi @svetanis, thank you for your contribution! We appreciate you taking the time to submit this pull request. The team is currently reviewing your changes, and we will let you know if any further information or updates are needed. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Branch isolation uses a raw string prefix, leaking peer agents' output

2 participants