From 58f0c564f06701a2d6a97c7b893fe8b38fa7887a Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:01:17 -0500 Subject: [PATCH 1/8] Update contents.py --- src/google/adk/flows/llm_flows/contents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/flows/llm_flows/contents.py b/src/google/adk/flows/llm_flows/contents.py index 75f5ae8dad..8a70a7ff20 100644 --- a/src/google/adk/flows/llm_flows/contents.py +++ b/src/google/adk/flows/llm_flows/contents.py @@ -621,8 +621,8 @@ def _is_event_belongs_to_branch( # We use dot to delimit branch nodes. To avoid simple prefix match # (e.g. agent_0 unexpectedly matching agent_00), require either perfect branch # match, or match prefix with an additional explicit '.' - return invocation_branch == event.branch or invocation_branch.startswith( - f'{event.branch}.' + return invocation_branch == event.branch or event.branch.startswith( + f'{invocation_branch}.' ) From 0a28654795b78b321a60be9fe5b89c97fb0289c2 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:51:40 -0500 Subject: [PATCH 2/8] fix: allow bidirectional branch filtering (parent and child branches) --- src/google/adk/flows/llm_flows/contents.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/google/adk/flows/llm_flows/contents.py b/src/google/adk/flows/llm_flows/contents.py index 8a70a7ff20..ee2a01059b 100644 --- a/src/google/adk/flows/llm_flows/contents.py +++ b/src/google/adk/flows/llm_flows/contents.py @@ -621,9 +621,10 @@ def _is_event_belongs_to_branch( # We use dot to delimit branch nodes. To avoid simple prefix match # (e.g. agent_0 unexpectedly matching agent_00), require either perfect branch # match, or match prefix with an additional explicit '.' - return invocation_branch == event.branch or event.branch.startswith( - f'{invocation_branch}.' - ) + return (invocation_branch == event.branch + or event.branch.startswith(f'{invocation_branch}.') + or invocation_branch.startswith(f'{event.branch}.')) + def _is_function_call_event(event: Event, function_name: str) -> bool: From 4a7f375a60e1cdc046d88b1259c015fd25103404 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:53:06 -0500 Subject: [PATCH 3/8] test: update tests for branch contents --- .../flows/llm_flows/test_contents_branch.py | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/unittests/flows/llm_flows/test_contents_branch.py b/tests/unittests/flows/llm_flows/test_contents_branch.py index 2347354127..840fb578b9 100644 --- a/tests/unittests/flows/llm_flows/test_contents_branch.py +++ b/tests/unittests/flows/llm_flows/test_contents_branch.py @@ -15,7 +15,7 @@ """Tests for branch filtering in contents module. Branch format: agent_1.agent_2.agent_3 (parent.child.grandchild) -Child agents can see parent agents' events, but not sibling agents' events. +Agents can see events that happened earlier in their their own branch/sub-branch. But not sibling branches. """ from google.adk.agents.llm_agent import Agent @@ -29,8 +29,8 @@ @pytest.mark.asyncio -async def test_branch_filtering_child_sees_parent(): - """Test that child agents can see parent agents' events.""" +async def test_branch_filtering_child_sees_events_from_earlier_in_same_branch(): + """Test that child agents can see events from earlier in their own branch.""" agent = Agent(model="gemini-2.5-flash", name="child_agent") llm_request = LlmRequest(model="gemini-2.5-flash") invocation_context = await testing_utils.create_invocation_context( @@ -68,17 +68,24 @@ async def test_branch_filtering_child_sees_parent(): invocation_id="inv5", author="child_agent", content=types.ModelContent("Excluded response 2"), - branch="parent_agent.child", # Prefix match BUT not itself/ancestor - should be excluded + branch="parent_agent.child", # Prefix doesn't match - should be excluded ), + Event( + invocation_id="inv6", + author="grandchild_agent", + content=types.ModelContent("Grandchild agent response"), + branch="parent_agent.child_agent.grandchild_agent", # Grandchild - should be included + ) ] invocation_context.session.events = events # Process the request async for _ in request_processor.run_async(invocation_context, llm_request): pass + - # Verify child can see user message and parent events, but not sibling events - assert len(llm_request.contents) == 3 + # Verify child can see user message, parent events, own events, and grandchild events + assert len(llm_request.contents) == 4 assert llm_request.contents[0] == types.UserContent("User message") assert llm_request.contents[1].role == "user" assert llm_request.contents[1].parts == [ @@ -86,6 +93,11 @@ async def test_branch_filtering_child_sees_parent(): types.Part(text="[parent_agent] said: Parent agent response"), ] assert llm_request.contents[2] == types.ModelContent("Child agent response") + assert llm_request.contents[3].role == "user" + assert llm_request.contents[3].parts == [ + types.Part(text="For context:"), + types.Part(text="[grandchild_agent] said: Grandchild agent response"), + ] @pytest.mark.asyncio @@ -251,8 +263,8 @@ async def test_branch_filtering_grandchild_sees_grandparent(): @pytest.mark.asyncio -async def test_branch_filtering_parent_cannot_see_child(): - """Test that parent agents cannot see child agents' events.""" +async def test_branch_filtering_parent_can_see_child(): + """Test that parent agents CAN see events from sub-branches.""" agent = Agent(model="gemini-2.5-flash", name="parent_agent") llm_request = LlmRequest(model="gemini-2.5-flash") invocation_context = await testing_utils.create_invocation_context( @@ -293,8 +305,18 @@ async def test_branch_filtering_parent_cannot_see_child(): async for _ in request_processor.run_async(invocation_context, llm_request): pass - # Verify parent cannot see child or grandchild events - assert llm_request.contents == [ - types.UserContent("User message"), - types.ModelContent("Parent response"), + # Verify parent CAN see child and grandchild events + assert len(llm_request.contents) == 4 + assert llm_request.contents[0] == types.UserContent("User message") + assert llm_request.contents[1] == types.ModelContent("Parent response") + # Child and grandchild events are reformatted as user context + assert llm_request.contents[2].role == "user" + assert llm_request.contents[2].parts == [ + types.Part(text="For context:"), + types.Part(text="[child_agent] said: Child response"), + ] + assert llm_request.contents[3].role == "user" + assert llm_request.contents[3].parts == [ + types.Part(text="For context:"), + types.Part(text="[grandchild_agent] said: Grandchild response"), ] From 3fc5a9b9e1deedfcfc01177ed7be462c07654253 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:29:03 -0500 Subject: [PATCH 4/8] Update tests/unittests/flows/llm_flows/test_contents_branch.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/unittests/flows/llm_flows/test_contents_branch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/flows/llm_flows/test_contents_branch.py b/tests/unittests/flows/llm_flows/test_contents_branch.py index 840fb578b9..0cdd6dee59 100644 --- a/tests/unittests/flows/llm_flows/test_contents_branch.py +++ b/tests/unittests/flows/llm_flows/test_contents_branch.py @@ -15,7 +15,7 @@ """Tests for branch filtering in contents module. Branch format: agent_1.agent_2.agent_3 (parent.child.grandchild) -Agents can see events that happened earlier in their their own branch/sub-branch. But not sibling branches. +Agents can see events from their own branch, as well as ancestor and descendant branches, but not sibling branches. """ from google.adk.agents.llm_agent import Agent From 84f3d3ade0a559b4ba87c9af2825e7777b20c41f Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:29:12 -0500 Subject: [PATCH 5/8] Update tests/unittests/flows/llm_flows/test_contents_branch.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/unittests/flows/llm_flows/test_contents_branch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unittests/flows/llm_flows/test_contents_branch.py b/tests/unittests/flows/llm_flows/test_contents_branch.py index 0cdd6dee59..f1f0febde3 100644 --- a/tests/unittests/flows/llm_flows/test_contents_branch.py +++ b/tests/unittests/flows/llm_flows/test_contents_branch.py @@ -29,8 +29,8 @@ @pytest.mark.asyncio -async def test_branch_filtering_child_sees_events_from_earlier_in_same_branch(): - """Test that child agents can see events from earlier in their own branch.""" +async def test_branch_filtering_agent_sees_ancestors_and_descendants(): + """Test that an agent can see events from its ancestors and descendants.""" agent = Agent(model="gemini-2.5-flash", name="child_agent") llm_request = LlmRequest(model="gemini-2.5-flash") invocation_context = await testing_utils.create_invocation_context( From 639e3227f46033f65ad6881087921344cf028e22 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:33:25 -0500 Subject: [PATCH 6/8] fix: formatting in contents.py --- src/google/adk/flows/llm_flows/contents.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/google/adk/flows/llm_flows/contents.py b/src/google/adk/flows/llm_flows/contents.py index 320c5891a1..8336621360 100644 --- a/src/google/adk/flows/llm_flows/contents.py +++ b/src/google/adk/flows/llm_flows/contents.py @@ -621,9 +621,11 @@ def _is_event_belongs_to_branch( # We use dot to delimit branch nodes. To avoid simple prefix match # (e.g. agent_0 unexpectedly matching agent_00), require either perfect branch # match, or match prefix with an additional explicit '.' - return (invocation_branch == event.branch - or event.branch.startswith(f'{invocation_branch}.') - or invocation_branch.startswith(f'{event.branch}.')) + return ( + invocation_branch == event.branch + or event.branch.startswith(f'{invocation_branch}.') + or invocation_branch.startswith(f'{event.branch}.') + ) From 0f8e036e16039cfa492208744078b8a5ac765bd2 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:34:51 -0500 Subject: [PATCH 7/8] Update test_contents_branch.py --- .../unittests/flows/llm_flows/test_contents_branch.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/unittests/flows/llm_flows/test_contents_branch.py b/tests/unittests/flows/llm_flows/test_contents_branch.py index f1f0febde3..7f08ec6ba3 100644 --- a/tests/unittests/flows/llm_flows/test_contents_branch.py +++ b/tests/unittests/flows/llm_flows/test_contents_branch.py @@ -71,18 +71,17 @@ async def test_branch_filtering_agent_sees_ancestors_and_descendants(): branch="parent_agent.child", # Prefix doesn't match - should be excluded ), Event( - invocation_id="inv6", - author="grandchild_agent", - content=types.ModelContent("Grandchild agent response"), - branch="parent_agent.child_agent.grandchild_agent", # Grandchild - should be included - ) + invocation_id="inv6", + author="grandchild_agent", + content=types.ModelContent("Grandchild agent response"), + branch="parent_agent.child_agent.grandchild_agent", # Grandchild - should be included + ), ] invocation_context.session.events = events # Process the request async for _ in request_processor.run_async(invocation_context, llm_request): pass - # Verify child can see user message, parent events, own events, and grandchild events assert len(llm_request.contents) == 4 From 2980c341e9f59168c1121f2e2e509e84015bd370 Mon Sep 17 00:00:00 2001 From: Daniel Novikov <36117344+dannovikov@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:37:00 -0500 Subject: [PATCH 8/8] fix: formatting contents.py --- src/google/adk/flows/llm_flows/contents.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/google/adk/flows/llm_flows/contents.py b/src/google/adk/flows/llm_flows/contents.py index 8336621360..529a57c4ba 100644 --- a/src/google/adk/flows/llm_flows/contents.py +++ b/src/google/adk/flows/llm_flows/contents.py @@ -628,7 +628,6 @@ def _is_event_belongs_to_branch( ) - def _is_function_call_event(event: Event, function_name: str) -> bool: """Checks if an event is a function call/response for a given function name.""" if not event.content or not event.content.parts: