Skip to content

Comments

feat: Add parallel node groups with join strategies#4584

Open
drahnreb wants to merge 3 commits intogoogle:mainfrom
drahnreb:feat/graph-agent-pr3
Open

feat: Add parallel node groups with join strategies#4584
drahnreb wants to merge 3 commits intogoogle:mainfrom
drahnreb:feat/graph-agent-pr3

Conversation

@drahnreb
Copy link

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:
GraphAgent executes nodes sequentially by default. Parallel execution is needed for patterns like map-reduce, competitive search, and consensus-based decisions where multiple agents run concurrently and results are merged.

Solution:
Add ParallelNodeGroup for concurrent node execution within GraphAgent with WAIT_ALL, WAIT_ANY, and WAIT_N join strategies and configurable error policies (FAIL_FAST, CONTINUE, COLLECT). Integrates parallel group execution into GraphAgent's run loop with automatic telemetry spans per parallel group.

What's included:

  • src/google/adk/agents/graph/parallel.py
  • Updated graph_agent.py with parallel group execution, add_parallel_group, _find_parallel_group
  • Updated graph/__init__.py with parallel exports
  • 5 test files (~52 tests): test_graph_parallel.py, test_parallel_execution.py, test_parallel_state_merge.py, test_parallel_task_lookup.py, test_parallel_unit.py
  • 4 samples (graph_agent_multi_agent, examples 09-11/14)

Part 3 of 5 — see tracking issue #4581. Stacked on #4583.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.
pytest tests/unittests/agents/test_parallel_*.py tests/unittests/agents/test_graph_parallel.py -v — 52 tests ✅
All prior tests still pass (regression) ✅

Manual End-to-End (E2E) Tests:

4 parallel sample agents import and instantiate successfully.

Checklist

  • I have read the CONTRIBUTING.md document.
  • 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.

Additional context

Part 3 of 5. Depends on #4582 (Core GraphAgent) and #4583 (Graph patterns).

@google-cla
Copy link

google-cla bot commented Feb 22, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @drahnreb, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the GraphAgent's capabilities by introducing parallel execution of nodes and formalizing several advanced workflow patterns. It addresses the limitation of purely sequential execution, allowing for more complex and efficient agentic designs such as map-reduce, competitive search, and hierarchical decision-making. The changes provide developers with robust tools for building highly flexible and scalable multi-agent systems.

Highlights

  • Parallel Node Groups: Introduced ParallelNodeGroup to enable concurrent execution of nodes within GraphAgent, supporting WAIT_ALL, WAIT_ANY, and WAIT_N join strategies, along with configurable error policies (FAIL_FAST, CONTINUE, COLLECT).
  • Advanced Graph Patterns: Added first-class APIs for advanced patterns: DynamicNode for runtime agent selection, NestedGraphNode for hierarchical sub-workflows, and DynamicParallelGroup for variable-count concurrent execution.
  • Comprehensive Documentation & Examples: New documentation files (advanced_graph_patterns.md, graph_agent_design.md, graph_node_types.md, pattern_apis.md) and numerous examples (graph_agent_basic, graph_agent_multi_agent, graph_agent_dynamic_queue, and various graph_examples) were added to illustrate GraphAgent's capabilities and new patterns.
  • Telemetry and Resumability Enhancements: Integrated automatic telemetry spans for parallel groups and improved GraphAgent's resumability, including handling of long-running tool events and state consistency across pause/resume cycles.
  • Safe Condition Evaluation: Implemented AST-based validation for condition strings in graph edges, ensuring safe evaluation of dynamic routing logic while preventing arbitrary code execution.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • contributing/docs/advanced_graph_patterns.md
    • Added a new document detailing advanced GraphAgent patterns, including dynamic task queues, nested graph invocation, and conditional parallel groups.
  • contributing/docs/graph_agent_design.md
    • Added a new design document for GraphAgent, explaining its motivation, key capabilities, architecture, and comparison to other agents.
  • contributing/docs/graph_node_types.md
    • Added a new document explaining different GraphAgent node types, including GraphNode, ParallelNodeGroup, DynamicNode, NestedGraphNode, and DynamicParallelGroup.
  • contributing/docs/pattern_apis.md
    • Added a new document describing first-class pattern APIs for GraphAgent, specifically DynamicNode, NestedGraphNode, and DynamicParallelGroup, with comparisons to previous function node approaches.
  • contributing/samples/graph_agent_basic/README.md
    • Added a README for the basic GraphAgent example, explaining conditional routing.
  • contributing/samples/graph_agent_basic/agent.py
    • Added a basic GraphAgent example demonstrating conditional routing for a data validation pipeline.
  • contributing/samples/graph_agent_basic/root_agent.yaml
    • Added a YAML configuration file for the basic GraphAgent example.
  • contributing/samples/graph_agent_dynamic_queue/README.md
    • Added a README for the dynamic task queue example, explaining the AI Co-Scientist pattern.
  • contributing/samples/graph_agent_dynamic_queue/agent.py
    • Added a dynamic task queue example demonstrating the AI Co-Scientist pattern with runtime agent dispatch and dynamic task generation.
  • contributing/samples/graph_agent_multi_agent/README.md
    • Added a README for the multi-agent research workflow example, detailing parallel research branches and a quality-review loop.
  • contributing/samples/graph_agent_multi_agent/agent.py
    • Added a multi-agent research workflow example demonstrating parallel execution and a conditional quality-review loop.
  • contributing/samples/graph_agent_pattern_dynamic_node/README.md
    • Added a README for the DynamicNode pattern example, explaining runtime agent selection.
  • contributing/samples/graph_agent_pattern_dynamic_node/agent.py
    • Added a DynamicNode pattern example demonstrating runtime agent selection for a mixture-of-experts approach.
  • contributing/samples/graph_agent_pattern_nested_graph/README.md
    • Added a README for the NestedGraphNode pattern example, explaining hierarchical workflow decomposition.
  • contributing/samples/graph_agent_pattern_nested_graph/agent.py
    • Added a NestedGraphNode pattern example demonstrating hierarchical workflow composition with a three-step research sub-graph.
  • contributing/samples/graph_agent_pattern_parallel_group/README.md
    • Added a README for the DynamicParallelGroup pattern example, explaining the Tree of Thoughts pattern.
  • contributing/samples/graph_agent_pattern_parallel_group/agent.py
    • Added a DynamicParallelGroup pattern example demonstrating the Tree of Thoughts pattern with dynamic concurrency.
  • contributing/samples/graph_agent_react_pattern/README.md
    • Added a README for the ReAct pattern example, explaining the Reasoning + Acting loop.
  • contributing/samples/graph_agent_react_pattern/agent.py
    • Added a ReAct pattern example demonstrating the Reasoning + Acting loop with conditional routing.
  • contributing/samples/graph_examples/01_basic/init.py
    • Added an __init__.py file for the basic GraphAgent example.
  • contributing/samples/graph_examples/01_basic/agent.py
    • Added a basic GraphAgent example demonstrating simple directed graph workflows.
  • contributing/samples/graph_examples/02_conditional_routing/init.py
    • Added an __init__.py file for the conditional routing example.
  • contributing/samples/graph_examples/02_conditional_routing/agent.py
    • Added a conditional routing example demonstrating state-based routing decisions.
  • contributing/samples/graph_examples/03_cyclic_execution/init.py
    • Added an __init__.py file for the cyclic execution example.
  • contributing/samples/graph_examples/03_cyclic_execution/agent.py
    • Added a cyclic execution example demonstrating loops and iteration control.
  • contributing/samples/graph_examples/07_callbacks/init.py
    • Added an __init__.py file for the callbacks example.
  • contributing/samples/graph_examples/07_callbacks/agent.py
    • Added a callbacks example demonstrating node lifecycle hooks.
  • contributing/samples/graph_examples/08_rewind/init.py
    • Added an __init__.py file for the rewind example.
  • contributing/samples/graph_examples/08_rewind/agent.py
    • Added a rewind example demonstrating time-travel debugging.
  • contributing/samples/graph_examples/09_parallel_wait_all/init.py
    • Added an __init__.py file for the parallel WAIT_ALL example.
  • contributing/samples/graph_examples/09_parallel_wait_all/agent.py
    • Added a parallel execution example demonstrating the WAIT_ALL join strategy.
  • contributing/samples/graph_examples/10_parallel_wait_any/init.py
    • Added an __init__.py file for the parallel WAIT_ANY example.
  • contributing/samples/graph_examples/10_parallel_wait_any/agent.py
    • Added a parallel execution example demonstrating the WAIT_ANY join strategy.
  • contributing/samples/graph_examples/11_parallel_wait_n/init.py
    • Added an __init__.py file for the parallel WAIT_N example.
  • contributing/samples/graph_examples/11_parallel_wait_n/agent.py
    • Added a parallel execution example demonstrating the WAIT_N join strategy.
  • contributing/samples/graph_examples/14_parallel_rewind/init.py
    • Added an __init__.py file for the parallel rewind example.
  • contributing/samples/graph_examples/14_parallel_rewind/agent.py
    • Added a parallel execution and rewind example demonstrating their integration.
  • contributing/samples/graph_examples/15_enhanced_routing/init.py
    • Added an __init__.py file for the enhanced routing example.
  • contributing/samples/graph_examples/15_enhanced_routing/agent.py
    • Added an enhanced routing example demonstrating priority, weighted, and fallback routing.
  • contributing/samples/graph_examples/README.md
    • Added a comprehensive README for GraphAgent examples, detailing API overview, quick start, and feature matrix.
  • contributing/samples/graph_examples/init.py
    • Added an __init__.py file for the graph examples directory.
  • contributing/samples/graph_examples/example_utils.py
    • Added utility functions for GraphAgent examples, including LLM mode toggling.
  • contributing/samples/graph_examples/run_all_examples.sh
    • Added a shell script to run all GraphAgent examples.
  • contributing/samples/graph_examples/run_example.py
    • Added a Python utility to run specific GraphAgent examples with optional tracing and LLM mode.
  • docs/future-work/dynamic-topology-modification.md
    • Added a detailed design document for future dynamic topology modification in GraphAgent.
  • src/google/adk/init.py
    • Removed Context from __all__ export.
  • src/google/adk/agents/init.py
    • Imported and exported GraphAgent related classes (END, GraphAgent, GraphNode, GraphState, START) and removed Context.
  • src/google/adk/agents/graph/init.py
    • Added a new __init__.py file to define the graph-based agent components, including new pattern nodes and constants.
  • src/google/adk/agents/graph/callbacks.py
    • Added a new module for callback infrastructure, defining NodeCallbackContext, EdgeCallbackContext, and related types.
  • src/google/adk/agents/graph/evaluation_metrics.py
    • Added a new module for custom evaluation metrics specific to GraphAgent workflows.
  • src/google/adk/agents/graph/graph_agent.py
    • Implemented core GraphAgent logic, including parallel group execution, telemetry, and configuration parsing.
  • src/google/adk/agents/graph/graph_agent_config.py
    • Added a new module for GraphAgent configuration, defining GraphNodeConfig, GraphEdgeConfig, ParallelGroupConfig, and TelemetryConfig.
  • src/google/adk/agents/graph/graph_agent_state.py
    • Added a new module for GraphAgent's execution tracking state.
  • src/google/adk/agents/graph/graph_edge.py
    • Added a new module for conditional edges in the graph.
  • src/google/adk/agents/graph/graph_events.py
    • Added a new module for typed event streams in GraphAgent execution.
  • src/google/adk/agents/graph/graph_export.py
    • Added a new module for exporting graph structure and execution data for visualization.
  • src/google/adk/agents/graph/graph_node.py
    • Added a new module for defining graph nodes that wrap agents or functions.
  • src/google/adk/agents/graph/graph_rewind.py
    • Added a new module for graph rewind functionality.
  • src/google/adk/agents/graph/graph_state.py
    • Added a new module for graph state management and reducers.
  • src/google/adk/agents/graph/graph_telemetry.py
    • Added a new module for telemetry mixins for agent observability.
  • src/google/adk/agents/graph/parallel.py
    • Added a new module for parallel execution support in GraphAgent, defining JoinStrategy and ErrorPolicy.
  • src/google/adk/agents/graph/patterns.py
    • Added a new module for advanced graph patterns, including DynamicNode, NestedGraphNode, and DynamicParallelGroup.
  • src/google/adk/agents/graph/state_utils.py
    • Added a new module for reusable state parsing utilities.
  • src/google/adk/cli/agent_graph.py
    • Updated the CLI tool to support visualizing GraphAgent, DynamicNode, DynamicParallelGroup, and NestedGraphNode.
  • src/google/adk/telemetry/init.py
    • Updated __all__ export.
  • src/google/adk/telemetry/graph_tracing.py
    • Added a new module for OpenTelemetry instrumentation specific to GraphAgent.
  • tests/unittests/agents/test_graph_agent.py
    • Added comprehensive unit tests for GraphAgent features, including cyclic execution, checkpointing, and agent type support.
  • tests/unittests/agents/test_graph_agent_config.py
    • Added unit tests for GraphAgent configuration classes.
  • tests/unittests/agents/test_graph_agent_validation.py
    • Added unit tests for GraphAgent validation features, including duplicate nodes/edges and output_key auto-defaulting.
  • tests/unittests/agents/test_graph_callbacks.py
    • Added unit tests for GraphAgent callback infrastructure.
  • tests/unittests/agents/test_graph_convenience_api.py
    • Added unit tests for GraphAgent convenience API methods.
  • tests/unittests/agents/test_graph_evaluation.py
    • Added unit tests for GraphAgent evaluation metrics.
  • tests/unittests/agents/test_graph_evaluation_integration.py
    • Added integration tests for GraphAgent evaluation with intermediate_data extraction.
  • tests/unittests/agents/test_graph_parallel.py
    • Added unit tests for GraphAgent parallel node execution.
  • tests/unittests/agents/test_graph_patterns.py
    • Added unit tests for GraphAgent first-class pattern APIs.
  • tests/unittests/agents/test_graph_resumability.py
    • Added unit tests for GraphAgent ADK resumability integration.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces parallel node execution to the GraphAgent framework, supporting various join strategies (WAIT_ALL, WAIT_ANY, WAIT_N) and error policies. It also adds first-class pattern APIs like DynamicNode, NestedGraphNode, and DynamicParallelGroup to simplify common agentic workflows. The implementation includes comprehensive telemetry integration and a robust set of tests and samples. My feedback focuses on ensuring deterministic state merging in parallel groups and fixing a potential issue with parallel group re-execution in cyclic graphs.

f"Executing parallel group '{group_id}' with nodes:"
f" {parallel_group.nodes}"
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The executed_parallel_groups check prevents a parallel group from re-executing if the graph loops back to it. Since executed_parallel_groups is persisted in agent_state, it will remain populated across iterations. This check should be iteration-scoped to allow parallel groups to run again in cyclic workflows.

          # Check if current node is part of a parallel group
          parallel_group_info = self._find_parallel_group(current_node_name)
          if parallel_group_info:
            group_id, parallel_group = parallel_group_info

            # Track group execution per iteration to support loops
            group_iteration_key = f"{group_id}_{iteration}"
            if group_iteration_key in executed_parallel_groups:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: executed_parallel_groups.clear() on cycle revisit.

branch_state = result["state"]

# Merge data keys with conflict detection
for key, value in branch_state.data.items():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current state merging logic iterates over results.items(), which is ordered by completion time. This makes the "last write wins" behavior non-deterministic if multiple parallel branches modify the same state key. To ensure deterministic behavior, state should be merged in the order defined in group.nodes.

    for node_name in group.nodes:
      if node_name not in results:
        continue
      result = results[node_name]
      branch_state = result["state"]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Merge order now uses group.nodes (definition order) instead of completion order.

@drahnreb drahnreb force-pushed the feat/graph-agent-pr3 branch from 5f65d99 to b42592f Compare February 22, 2026 10:57
@drahnreb
Copy link
Author

drahnreb commented Feb 22, 2026

Addressing review feedback

Force-pushed with the following fixes:

Critical:

  • Shared InvocationContext race: Added branch_ctx = ctx.model_copy() per parallel branch (mirrors ADK's own ParallelAgent pattern).

High:

  • after_node_callback skipped for parallel trigger: Now fires before continue.
  • before_node_callback fires for skipped nodes: Moved after parallel group skip check.
  • executed_parallel_groups scope: Reset on cycle revisit.

Medium:

  • Exception → RuntimeError: raise Exception(error_msg)raise RuntimeError(error_msg) in ErrorPolicy.COLLECT.
  • State merge order: Uses group.nodes (definition order) instead of completion order.

@drahnreb drahnreb force-pushed the feat/graph-agent-pr3 branch 4 times, most recently from 0e56742 to 329f7e6 Compare February 22, 2026 16:34
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces parallel node execution to the GraphAgent with support for various join strategies (WAIT_ALL, WAIT_ANY, WAIT_N) and error policies. The implementation follows the ParallelAgent pattern, ensuring state isolation via deep copies and providing deterministic state merging with conflict detection. The integration into the GraphAgent run loop correctly handles cyclic workflows by resetting parallel group tracking. I have identified a few critical issues regarding observability consistency and potential race conditions in shared session state that should be addressed to ensure the robustness of the parallel execution model.


# Create generator for each node
node = nodes[node_name]
node_generators[node_name] = execute_node_fn(node, branch_state, ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

While branch_state (the GraphState object) is isolated via deepcopy, the InvocationContext (ctx) is shared across all parallel branches. Since ADK agents frequently interact with ctx.session.state, concurrent writes to the underlying session state by multiple branches will lead to race conditions and non-deterministic behavior. The context or at least the session state should be isolated per branch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Added branch_ctx = ctx.model_copy() per parallel branch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — implemented diff-based state merge. Now captures original_data = deepcopy(state.data) before merge and skips keys unchanged from original, preventing stale branch copies from overwriting other branches' modifications. Added test_parallel_unchanged_keys_not_overwritten test.

Comment on lines 1459 to 1467
# Invoke after_node_callback (custom observability)
if self.after_node_callback:
event = await self._execute_callback(
self.after_node_callback, "after_node", current_node,
current_node_name, state, iteration, ctx, agent_state,
effective_config, output=output)
if event:
yield event

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The after_node_callback is skipped for nodes that trigger a parallel group execution because of the continue statement at line 1409. This creates a significant gap in observability for parallel workflows, as lifecycle events for the triggering node are incomplete.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: after_node_callback now fires before continue for parallel trigger nodes.

Comment on lines 1317 to 1324
if self.before_node_callback:
event = await self._execute_callback(
self.before_node_callback, "before_node", current_node,
current_node_name, state, iteration, ctx, agent_state,
effective_config)
if event:
yield event

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The before_node_callback is invoked before checking if a node should be skipped as part of an already executed parallel group. This results in the callback firing for nodes that aren't actually being executed individually, which is inconsistent with the after_node_callback behavior (which is skipped).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: before_node_callback moved after parallel group skip check.


# Handle collected errors
if errors and group.error_policy == ErrorPolicy.COLLECT:
error_msg = f"Errors in parallel execution: {errors}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Raising a generic Exception is discouraged as it makes it difficult for callers to catch and handle specific error conditions. Please use a more specific exception type like RuntimeError or a custom ParallelExecutionError.

Suggested change
error_msg = f"Errors in parallel execution: {errors}"
raise RuntimeError(error_msg)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Changed to raise RuntimeError(error_msg).

@drahnreb drahnreb force-pushed the feat/graph-agent-pr3 branch 3 times, most recently from 053e9e5 to fd2ba4d Compare February 22, 2026 23:43
@drahnreb
Copy link
Author

@gemini-code-assist please re-review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and well-designed feature: parallel execution capabilities for GraphAgent. The implementation is comprehensive, covering various join strategies, error policies, and advanced patterns like dynamic and nested nodes. The code is well-structured with a clear separation of concerns. The extensive set of examples and tests is particularly commendable. My review focuses on minor improvements in the example files to enhance robustness, clarity, and consistency, as the core logic appears very solid.

Comment on lines 69 to 73
def is_valid_json(state) -> bool:
"""Check if JSON is valid from structured output."""
# Convention: Access via agent.name (auto-defaulted as output_key)
result = state.data.get("validator", {})
return result.get("valid", False) is True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved robustness and type safety, it's better to use state.get_parsed() to safely access structured data from the agent's output. This method handles cases where the output might not be a valid dictionary, preventing potential AttributeError exceptions.

To use this, you'll need to add from google.adk.agents.graph import GraphState to your imports and add the type hint to the function signature.

Suggested change
def is_valid_json(state) -> bool:
"""Check if JSON is valid from structured output."""
# Convention: Access via agent.name (auto-defaulted as output_key)
result = state.data.get("validator", {})
return result.get("valid", False) is True
def is_valid_json(state: GraphState) -> bool:
"""Check if JSON is valid from structured output."""
# Use get_parsed for safe parsing of structured output.
result = state.get_parsed("validator", ValidationResult)
return result.valid if result else False

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — now uses state.get_parsed("validator", ValidationResult) for type-safe access.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — narrowed to except ValidationError: with explicit import.

Comment on lines 126 to 129
def _is_approved(state: GraphState) -> bool:
"""Check if critic approved using structured output."""
review = state.get_parsed("critic", ReviewResult)
return review.decision.lower() == "approve" if review else False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function _is_approved is defined but never used in the graph's logic. It can be safely removed to improve code clarity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — removed unused _is_approved function.

Comment on lines 96 to 99
def _is_complete(state: GraphState) -> bool:
"""Check if ReAct loop is complete using structured output."""
obs = state.get_parsed("observer", ObservationResult)
return obs.status.lower() == "complete" if obs else False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function _is_complete is defined but never used. The graph's exit condition is handled implicitly when _should_continue returns False. This function can be removed to clean up the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — removed unused _is_complete function.

Comment on lines 284 to 295
if not final_counter:
graph_data_raw = session.state.get("graph_data")
if graph_data_raw:
try:
data = (
json.loads(graph_data_raw)
if isinstance(graph_data_raw, str)
else graph_data_raw
)
final_counter = data.get("counter", 0)
except (json.JSONDecodeError, TypeError):
final_counter = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check if not final_counter: is a bit brittle because it would incorrectly trigger if the loop could validly finish with counter=0. A more robust approach is to check if the key is missing from the session state before attempting to re-parse from graph_data.

Suggested change
if not final_counter:
graph_data_raw = session.state.get("graph_data")
if graph_data_raw:
try:
data = (
json.loads(graph_data_raw)
if isinstance(graph_data_raw, str)
else graph_data_raw
)
final_counter = data.get("counter", 0)
except (json.JSONDecodeError, TypeError):
final_counter = 0
final_counter = session.state.get("counter")
if final_counter is None:
graph_data_raw = session.state.get("graph_data")
if graph_data_raw:
try:
data = (
json.loads(graph_data_raw)
if isinstance(graph_data_raw, str)
else graph_data_raw
)
final_counter = data.get("counter", 0)
except (json.JSONDecodeError, TypeError):
final_counter = 0
final_counter = final_counter or 0

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed to if final_counter is None: to correctly handle counter value of 0.

"""

import asyncio

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There appears to be a typo in the example number. This file is 15_enhanced_routing, but the docstring refers to it as "Example 3". This should be updated for consistency.

Suggested change
"""Example 15: Enhanced Routing (Priority, Weight, Fallback)."""


async def main():
print("\n" + "=" * 60)
print("Example 3: Enhanced Routing")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There appears to be a typo in the example number. This file is 15_enhanced_routing, but the output says "Example 3". This should be updated for consistency.

Suggested change
print("Example 3: Enhanced Routing")
print("Example 15: Enhanced Routing")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — corrected 'Example 3' to 'Example 15'.

Add GraphAgent for building directed-graph workflows with conditional
routing, cyclic execution, state management with reducers, typed events,
streaming, callbacks, rewind, resumability, telemetry with OpenTelemetry
tracing, evaluation metrics, and CLI graph visualization for GraphAgent
topologies. Includes samples and design documentation.
Add DynamicNode (runtime agent selection), NestedGraphNode (hierarchical
workflow composition), and DynamicParallelGroup (variable-count concurrent
execution). Extends CLI visualization with pattern-aware rendering
(diamond, parallelogram, sub-cluster shapes). Includes pattern samples,
node type reference, and design documentation.
@drahnreb drahnreb force-pushed the feat/graph-agent-pr3 branch from fd2ba4d to 20d6a1f Compare February 23, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant