Skip to content

Comments

feat: Add graph pattern nodes for dynamic dispatch and composition#4583

Open
drahnreb wants to merge 2 commits intogoogle:mainfrom
drahnreb:feat/graph-agent-pr2
Open

feat: Add graph pattern nodes for dynamic dispatch and composition#4583
drahnreb wants to merge 2 commits intogoogle:mainfrom
drahnreb:feat/graph-agent-pr2

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:
Common agentic patterns (dispatch, nesting, fan-out) require writing custom function nodes with boilerplate for context creation, event collection, and observability.

Solution:
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.

What's included:

  • src/google/adk/agents/graph/patterns.py
  • Updated graph_agent.py _get_node_agent with pattern isinstance checks
  • Updated graph/__init__.py with pattern exports
  • CLI viz pattern rendering (DynamicNode → diamond, DynamicParallelGroup → parallelogram, NestedGraphNode → sub-cluster)
  • test_graph_patterns.py (~25 tests), 3 pattern tests in test_agent_graph.py
  • 3 samples (pattern_dynamic_node, pattern_nested_graph, pattern_parallel_group)
  • 4 docs (graph_node_types.md, advanced_graph_patterns.md, pattern_apis.md, dynamic-topology-modification.md)

Part 2 of 5 — see tracking issue #4581. Stacked on #4582.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.
pytest tests/unittests/agents/test_graph_patterns.py -v — 25 tests ✅
pytest tests/unittests/cli/test_agent_graph.py -v — 8 tests (5 core + 3 pattern) ✅
pytest tests/unittests/agents/test_graph_agent.py -v — pattern additions pass ✅

Manual End-to-End (E2E) Tests:

3 pattern 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 2 of 5. Depends on #4582 (Core GraphAgent). patterns.py imports only GraphNode, GraphState, BaseAgent, InvocationContext — no dependency on parallel/interrupt/checkpoint.

@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 first-class support for dynamic dispatch, hierarchical composition, and variable-count parallel execution. These additions aim to streamline the development of complex agentic workflows by abstracting away common boilerplate, making it easier to build adaptive and modular agent systems. The changes also include improved observability and comprehensive examples to guide developers.

Highlights

  • New Graph Pattern Nodes: Introduced DynamicNode for runtime agent selection, NestedGraphNode for hierarchical workflow composition, and DynamicParallelGroup for variable-count concurrent execution, significantly reducing boilerplate for common agentic patterns.
  • Enhanced CLI Visualization: Updated the CLI visualization tool to render new pattern nodes with distinct shapes (diamond for DynamicNode, parallelogram for DynamicParallelGroup, sub-cluster for NestedGraphNode).
  • Comprehensive Documentation and Samples: Added extensive documentation covering advanced graph patterns, node types, and pattern APIs, alongside new sample agents demonstrating the usage of DynamicNode, NestedGraphNode, DynamicParallelGroup, and the ReAct pattern.
  • GraphAgent Core Enhancements: Modified the core GraphAgent to support these new node types, integrate with OpenTelemetry for detailed tracing and metrics, and improve state management and resumability.
  • Robust Testing: Included new unit tests for graph patterns, agent graph functionality, and comprehensive tests for GraphAgent core features, configuration, callbacks, evaluation, routing, and state management.

🧠 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 documentation for advanced GraphAgent patterns.
  • contributing/docs/graph_agent_design.md
    • Added design document for GraphAgent architecture.
  • contributing/docs/graph_node_types.md
    • Added documentation explaining GraphAgent node types and their use cases.
  • contributing/docs/pattern_apis.md
    • Added documentation detailing the new pattern APIs: DynamicNode, NestedGraphNode, and DynamicParallelGroup.
  • contributing/samples/graph_agent_basic/README.md
    • Added README for the basic GraphAgent conditional routing example.
  • contributing/samples/graph_agent_basic/agent.py
    • Added a basic GraphAgent example demonstrating conditional routing.
  • contributing/samples/graph_agent_basic/root_agent.yaml
    • Added YAML configuration for the basic GraphAgent example.
  • contributing/samples/graph_agent_dynamic_queue/README.md
    • Added README for the dynamic task queue pattern example.
  • contributing/samples/graph_agent_dynamic_queue/agent.py
    • Added a dynamic task queue pattern example for GraphAgent.
  • contributing/samples/graph_agent_pattern_dynamic_node/README.md
    • Added README for the DynamicNode pattern example.
  • contributing/samples/graph_agent_pattern_dynamic_node/agent.py
    • Added a DynamicNode pattern example for runtime agent selection.
  • contributing/samples/graph_agent_pattern_nested_graph/README.md
    • Added README for the NestedGraphNode pattern example.
  • contributing/samples/graph_agent_pattern_nested_graph/agent.py
    • Added a NestedGraphNode pattern example for hierarchical workflows.
  • contributing/samples/graph_agent_pattern_parallel_group/README.md
    • Added README for the DynamicParallelGroup pattern example.
  • contributing/samples/graph_agent_pattern_parallel_group/agent.py
    • Added a DynamicParallelGroup pattern example for variable-count concurrency.
  • contributing/samples/graph_agent_react_pattern/README.md
    • Added README for the ReAct pattern example.
  • contributing/samples/graph_agent_react_pattern/agent.py
    • Added a ReAct pattern example using GraphAgent.
  • contributing/samples/graph_examples/01_basic/init.py
    • Added initialization file for the basic graph example.
  • contributing/samples/graph_examples/01_basic/agent.py
    • Added a basic GraphAgent workflow example.
  • contributing/samples/graph_examples/02_conditional_routing/init.py
    • Added initialization file for the conditional routing example.
  • contributing/samples/graph_examples/02_conditional_routing/agent.py
    • Added a conditional routing example for GraphAgent.
  • contributing/samples/graph_examples/03_cyclic_execution/init.py
    • Added initialization file for the cyclic execution example.
  • contributing/samples/graph_examples/03_cyclic_execution/agent.py
    • Added a cyclic execution example for GraphAgent.
  • contributing/samples/graph_examples/07_callbacks/init.py
    • Added initialization file for the callbacks example.
  • contributing/samples/graph_examples/07_callbacks/agent.py
    • Added a node callbacks example for GraphAgent.
  • contributing/samples/graph_examples/08_rewind/init.py
    • Added initialization file for the rewind example.
  • contributing/samples/graph_examples/08_rewind/agent.py
    • Added a rewind integration example for GraphAgent.
  • contributing/samples/graph_examples/15_enhanced_routing/init.py
    • Added initialization 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 edges.
  • contributing/samples/graph_examples/README.md
    • Added a comprehensive README for all GraphAgent examples.
  • contributing/samples/graph_examples/init.py
    • Added initialization file for the graph examples directory.
  • contributing/samples/graph_examples/example_utils.py
    • Added utility functions for GraphAgent examples.
  • contributing/samples/graph_examples/run_all_examples.sh
    • Added a script to run all GraphAgent examples.
  • contributing/samples/graph_examples/run_example.py
    • Added a utility script to run specific graph examples.
  • docs/future-work/dynamic-topology-modification.md
    • Added a design document outlining plans for dynamic topology modification in GraphAgent.
  • src/google/adk/init.py
    • Removed Context import and export.
  • src/google/adk/agents/init.py
    • Removed Context import and added GraphAgent, GraphNode, GraphState, START, END imports and exports.
  • src/google/adk/agents/graph/init.py
    • Added initialization file for the graph module, exporting all new graph components.
  • src/google/adk/agents/graph/callbacks.py
    • Added NodeCallbackContext, EdgeCallbackContext, and callback type definitions for graph lifecycle events.
  • src/google/adk/agents/graph/evaluation_metrics.py
    • Added evaluation metrics for graph path matching, state key presence, and node execution counts.
  • src/google/adk/agents/graph/graph_agent.py
    • Modified GraphAgent to support new pattern nodes, telemetry, and YAML configuration parsing.
  • src/google/adk/agents/graph/graph_agent_config.py
    • Added Pydantic models for GraphAgent configuration, including nodes, edges, interrupts, parallel groups, and telemetry.
  • src/google/adk/agents/graph/graph_agent_state.py
    • Added GraphAgentState for tracking graph execution details.
  • src/google/adk/agents/graph/graph_edge.py
    • Added EdgeCondition class for conditional routing with priority and weight.
  • src/google/adk/agents/graph/graph_events.py
    • Added GraphEvent and GraphEventType for structured graph execution events.
  • src/google/adk/agents/graph/graph_export.py
    • Added functions to export graph structure and execution timelines for visualization.
  • src/google/adk/agents/graph/graph_node.py
    • Modified GraphNode to support new pattern nodes and enhanced routing logic.
  • src/google/adk/agents/graph/graph_rewind.py
    • Added rewind_to_node function for temporal navigation within graph execution.
  • src/google/adk/agents/graph/graph_state.py
    • Added GraphState for domain data management and StateReducer enum.
  • src/google/adk/agents/graph/graph_telemetry.py
    • Added AgentTelemetryMixin and GraphTelemetryMixin for OpenTelemetry integration.
  • src/google/adk/agents/graph/patterns.py
    • Added DynamicNode, NestedGraphNode, and DynamicParallelGroup classes for advanced graph patterns.
  • src/google/adk/agents/graph/state_utils.py
    • Added utility functions for parsing state values and a Pydantic JSON encoder.
  • src/google/adk/cli/agent_graph.py
    • Modified CLI tool to visualize new graph pattern nodes with specific shapes.
  • src/google/adk/telemetry/init.py
    • Updated __all__ export list.
  • src/google/adk/telemetry/graph_tracing.py
    • Added OpenTelemetry instrumentation for GraphAgent execution, including metrics and tracing.
  • tests/unittests/agents/test_graph_agent.py
    • Added comprehensive unit tests for GraphAgent core functionality, including cyclic execution, checkpointing, and ADK conformity.
  • tests/unittests/agents/test_graph_agent_config.py
    • Added unit tests for GraphAgent configuration models.
  • tests/unittests/agents/test_graph_agent_validation.py
    • Added unit tests for GraphAgent validation logic, including duplicate node/edge detection and output_key auto-defaulting.
  • tests/unittests/agents/test_graph_callbacks.py
    • Added unit tests for GraphAgent callback mechanisms, including event emission and error handling.
  • tests/unittests/agents/test_graph_convenience_api.py
    • Added unit tests for GraphAgent's convenience APIs for adding nodes and edges.
  • 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 metrics, focusing on intermediate data extraction.
  • tests/unittests/agents/test_graph_patterns.py
    • Added unit tests for the new DynamicNode, NestedGraphNode, and DynamicParallelGroup pattern nodes.
  • tests/unittests/agents/test_graph_resumability.py
    • Added unit tests for GraphAgent's resumability integration.
  • tests/unittests/agents/test_graph_rewind.py
    • Added unit tests for GraphAgent's rewind functionality.
  • tests/unittests/agents/test_graph_routing.py
    • Added unit tests for GraphAgent's enhanced routing features, including priority, weighted, and fallback edges.
  • tests/unittests/agents/test_graph_state.py
    • Added unit tests for GraphState accessors and state utility parsing functions.
  • tests/unittests/agents/test_graph_state_management.py
    • Added comprehensive unit tests for GraphAgent state management and reducer strategies.
Activity
  • All unit tests passed locally.
  • Manual end-to-end tests for the three pattern sample agents confirmed successful import and instantiation.
  • The pull request checklist indicates all contribution guidelines were followed, including self-review, commenting code, and adding tests.
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 advanced graph pattern nodes (DynamicNode, NestedGraphNode, DynamicParallelGroup) to the GraphAgent framework, enabling more complex and dynamic agentic workflows. The implementation is well-structured and includes comprehensive documentation and CLI support. My feedback focuses on improving the robustness of output extraction from asynchronous agent runs in the new node types, as the current logic of overwriting the output variable in each iteration may lead to data loss if agents emit multiple events or multi-part content.

Comment on lines 108 to 111
async for event in selected.run_async(node_ctx):
if event.content and event.content.parts:
output = event.content.parts[0].text or ""
return output
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 current logic for extracting output from run_async is fragile. It overwrites the output variable in every iteration where event.content is present. If an agent yields multiple events with content (e.g., intermediate thoughts followed by a final answer), only the last one's first part is captured. Additionally, if the last event has content but an empty first part, any previously captured output is lost. Consider accumulating text or only updating output if the new text is non-empty.

Suggested change
async for event in selected.run_async(node_ctx):
if event.content and event.content.parts:
output = event.content.parts[0].text or ""
return output
output = ""
async for event in selected.run_async(node_ctx):
if event.content and event.content.parts:
# Accumulate text from all parts to avoid data loss
event_text = "".join(p.text for p in event.content.parts if p.text)
if event_text:
output = event_text
return output

Copy link
Author

Choose a reason for hiding this comment

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

Fixed: DynamicNode now uses output += with join for multi-part.

Comment on lines 217 to 221
async for event in self.graph_agent.run_async(nested_ctx):
if event.content and event.content.parts:
text = event.content.parts[0].text or ""
if text and not text.startswith("[GraphMetadata]"):
final_output = text
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Similar to DynamicNode, the extraction logic here overwrites final_output in each iteration. If a nested graph produces multiple text events, only the last one is returned to the parent graph. It is safer to accumulate the output or ensure that non-empty results are not overwritten by subsequent empty content events.

Suggested change
async for event in self.graph_agent.run_async(nested_ctx):
if event.content and event.content.parts:
text = event.content.parts[0].text or ""
if text and not text.startswith("[GraphMetadata]"):
final_output = text
final_output = ""
async for event in self.graph_agent.run_async(nested_ctx):
if event.content and event.content.parts:
text = "".join(p.text for p in event.content.parts if p.text)
if text and not text.startswith("[GraphMetadata]"):
final_output = text

Copy link
Author

Choose a reason for hiding this comment

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

Fixed: NestedGraphNode now uses final_output += with join for multi-part.

Comment on lines 316 to 319
async for event in agent.run_async(node_ctx):
if event.content and event.content.parts:
output = event.content.parts[0].text or ""
return output
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 run_agent helper also overwrites the output variable in each iteration of the event stream. This can lead to incomplete results if the agent emits content across multiple events or uses multiple parts within an event.

Suggested change
async for event in agent.run_async(node_ctx):
if event.content and event.content.parts:
output = event.content.parts[0].text or ""
return output
output = ""
async for event in agent.run_async(node_ctx):
if event.content and event.content.parts:
event_text = "".join(p.text for p in event.content.parts if p.text)
if event_text:
output = event_text
return output

Copy link
Author

Choose a reason for hiding this comment

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

Fixed: DynamicParallelGroup.run_agent now uses output += with join for multi-part.

@drahnreb drahnreb force-pushed the feat/graph-agent-pr2 branch from ee39674 to 7302880 Compare February 22, 2026 10:57
@drahnreb
Copy link
Author

drahnreb commented Feb 22, 2026

Addressing review feedback

Force-pushed with the following fixes:

  • Output extraction fragility (HIGH): All three pattern nodes now accumulate output with += and join multi-part events: DynamicNode._execute_dynamic, NestedGraphNode._execute_nested, DynamicParallelGroup.run_agent.
  • run_all_examples.sh: Fixed 03_enhanced_routing15_enhanced_routing to match actual directory name.
  • Sample files: final += text / result += ... in dynamic_queue, pattern_dynamic_node, pattern_nested_graph, pattern_parallel_group.

@drahnreb drahnreb force-pushed the feat/graph-agent-pr2 branch 4 times, most recently from f76b110 to 8224d46 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 powerful new graph patterns (DynamicNode, NestedGraphNode, DynamicParallelGroup) for dynamic and hierarchical agent workflows. The implementation is well-structured, with new features integrated into the core GraphAgent, visualization, and evaluation frameworks. The accompanying examples are comprehensive and very helpful. I've found one high-severity bug in the NestedGraphNode implementation where streaming output is not correctly accumulated, and several medium-severity issues in the example files that have a similar bug. Overall, this is a great feature addition.

if event.content and event.content.parts:
text = event.content.parts[0].text or ""
if text and not text.startswith("[GraphMetadata]"):
final_output = text
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 final_output is being overwritten in each iteration of the loop. This means if the nested graph streams multiple text parts, only the last one will be returned. It should be accumulated using += to concatenate all parts of the output.

Suggested change
final_output = text
final_output += text

Copy link
Author

Choose a reason for hiding this comment

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

Fixed: NestedGraphNode accumulates with += and joins multi-part events.

result = ""
async for event in agent.run_async(agent_ctx):
if event.content and event.content.parts:
result = event.content.parts[0].text or ""
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 result variable is overwritten in each loop iteration. If the agent streams its response across multiple events, only the last part will be captured. To handle streaming responses correctly, you should accumulate the text from all events.

Suggested change
result = event.content.parts[0].text or ""
result += event.content.parts[0].text or ""

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 result += for proper accumulation.

if event.content and event.content.parts:
text = event.content.parts[0].text or ""
if text and not text.startswith("[GraphMetadata]"):
final = text
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 final variable is being overwritten in each iteration. If the agent streams its response, only the last chunk of text will be returned. You should accumulate the text using += to capture the full response.

Suggested change
final = text
final += text

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 final += for proper accumulation.

# Show node execution trace (from create_nested_observability_callback)
print(f" → {text}")
elif not text.startswith("[GraphMetadata]"):
final = text
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 final variable is overwritten in each loop iteration, which means only the last piece of text from the event stream will be captured. To get the complete output, you should accumulate the text using +=.

Suggested change
final = text
final += text

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 final += for proper accumulation.

# Show node execution trace (from create_nested_observability_callback)
print(f" → {text}")
elif not text.startswith("[GraphMetadata]"):
final = text
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 final variable is being overwritten in each iteration of the loop. If the agent streams its output, this will only capture the last chunk. You should use += to accumulate the full response.

Suggested change
final = text
final += text

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 final += for proper accumulation.

@drahnreb drahnreb force-pushed the feat/graph-agent-pr2 branch 3 times, most recently from 518a5fa to 443997b 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 powerful set of features for building complex agentic workflows using graph patterns. The addition of DynamicNode, NestedGraphNode, and DynamicParallelGroup provides first-class support for common patterns like dynamic dispatch, hierarchical composition, and parallel fan-out, significantly reducing boilerplate. The implementation is comprehensive, including core logic, extensive tests, clear documentation, and practical samples. The code is well-structured and demonstrates a deep understanding of agentic design principles. My review focuses on minor improvements in the sample code to enhance robustness and clarity for developers using these new patterns.

fresh_session = await session_service.get_session(
app_name="dynamic_queue_demo", user_id="demo_user", session_id=session.id
)
final_session = fresh_session or session
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 fallback or session can be misleading. If session_service.get_session returns None (e.g., the session was deleted), final_session will become the original, stale session object from create_session. This object does not contain the final state of the graph execution, so the statistics printed will be incorrect (likely showing zero completed tasks). It would be safer to handle the None case explicitly, for instance by raising an error or logging a warning that the final state could not be retrieved.

Suggested change
final_session = fresh_session or session
if not fresh_session:
print("\n⚠️ Could not retrieve final session state to print statistics.")
return
final_session = fresh_session

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 from warn+fallback to early return: if fresh_session is None: print(...); return.

Comment on lines 191 to 195
try:
obs = ObservationResult.model_validate_json(text.strip())
status = obs.status.upper()
except Exception:
status = "UNKNOWN (parse error)"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. It's better to catch a more specific exception, like pydantic.ValidationError, which is what model_validate_json raises on failure. This makes the error handling more robust and predictable. You'll need to add ValidationError to your pydantic import.

Suggested change
try:
obs = ObservationResult.model_validate_json(text.strip())
status = obs.status.upper()
except Exception:
status = "UNKNOWN (parse error)"
try:
obs = ObservationResult.model_validate_json(text.strip())
status = obs.status.upper()
except ValidationError:
status = "UNKNOWN (parse error)"

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 from pydantic.

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-pr2 branch from 443997b to a461d14 Compare February 23, 2026 13:38
@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 powerful new capabilities for graph-based agent orchestration with DynamicNode, NestedGraphNode, and DynamicParallelGroup. The implementation is robust, well-structured, and includes comprehensive examples, tests, and documentation. The attention to detail, such as the secure evaluation of string-based conditions and the thoughtful API design, is commendable. I've found one minor issue in an example YAML configuration, but overall this is an excellent contribution that significantly enhances the framework's workflow capabilities.

# If validation passes -> process
- source_node: validate
target_node: process
condition: "data.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.

high

The condition data.get('valid', False) is True appears to be incorrect. The output of the validate node is a ValidationResult object, which is stored under the key validate in the graph's state data. The condition should access the valid attribute from this nested object.

    condition: "data.get('validate').valid is True"

# If validation fails -> error handler
- source_node: validate
target_node: error
condition: "data.get('valid', False) is False"
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 condition data.get('valid', False) is False appears to be incorrect. The output of the validate node is a ValidationResult object, which is stored under the key validate in the graph's state data. The condition should access the valid attribute from this nested object.

    condition: "data.get('validate').valid is False"

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