Skip to content

[FEATURE] Pass invocation_state to edge condition call #1346

@MElkady

Description

@MElkady

Overview

Add support for passing invocation_state to edge condition functions in Graph execution, enabling conditional edge traversal based on runtime context provided during graph invocation.

Problem Statement

Currently, edge condition functions only have access to GraphState, limiting their ability to make decisions based on runtime context passed during graph invocation. This makes it difficult to conditionally enable nodes based on the invocation_state received in the graph invocation.

Implementation Requirements

Based on discussion and repository analysis, the implementation should use a Protocol-based Union type for backwards compatibility and future extensibility.

Technical Approach

1. Define Edge Condition Protocol

Create a new Protocol in src/strands/multiagent/graph.py that allows future expansion with kwargs:

from typing import Protocol, runtime_checkable

@runtime_checkable
class EdgeConditionWithContext(Protocol):
    """Protocol for edge conditions that receive invocation context.
    
    This protocol allows conditions to access both GraphState and invocation_state,
    and is designed to be expanded with additional kwargs in the future.
    """
    def __call__(
        self, 
        state: GraphState, 
        *, 
        invocation_state: dict[str, Any] | None = None,
        **kwargs: Any
    ) -> bool: ...

# Legacy condition type (backwards compatible)
LegacyEdgeCondition = Callable[[GraphState], bool]

# Union type supporting both old and new signatures
EdgeCondition = LegacyEdgeCondition | EdgeConditionWithContext

2. Update GraphEdge Dataclass

@dataclass
class GraphEdge:
        ...
        if new style:
            return self.condition(state, invocation_state=invocation_state)
        
        # Legacy: single positional parameter only
        return self.condition(state)

3. Update Call Sites

Update all locations that call should_traverse() to pass invocation_state:

  • Graph._is_node_ready_with_conditions() (line ~837)
  • Graph._compute_ready_nodes_for_resume() (line ~1190)
  • Graph._build_node_input() (line ~1080)

4. Update GraphBuilder Type Hint

def add_edge(
    self,
    from_node: str | GraphNode,
    to_node: str | GraphNode,
    condition: EdgeCondition | None = None,
) -> GraphEdge:

Files to Modify

File Changes
src/strands/multiagent/graph.py Add Protocol, update types, update should_traverse(), update all call sites
tests/strands/multiagent/test_graph.py Add tests for new-style conditions with invocation_state
tests_integ/test_multiagent_graph.py Add integration tests for conditions with invocation_state

Backwards Compatibility

  • Old-style conditions (def condition(state: GraphState) -> bool) continue to work unchanged
  • New-style conditions (def condition(state: GraphState, *, invocation_state=None, **kwargs) -> bool) receive invocation context
  • Runtime signature inspection ensures correct calling convention
  • No deprecation warnings - both styles supported indefinitely

Example Usage

# Old-style (still works)
def check_completion(state: GraphState) -> bool:
    return "data_processor" in {n.node_id for n in state.completed_nodes}

# New-style (with invocation context)
def should_run_premium_node(
    state: GraphState, 
    *, 
    invocation_state: dict[str, Any] | None = None,
    **kwargs: Any
) -> bool:
    if invocation_state is None:
        return False
    return invocation_state.get("user_tier") == "premium"

# Graph setup
builder = GraphBuilder()
builder.add_node(start_agent, "start")
builder.add_node(premium_agent, "premium_processor")
builder.add_edge("start", "premium_processor", condition=should_run_premium_node)
graph = builder.build()

# Invocation with context
result = graph(task="process data", invocation_state={"user_tier": "premium"})

Acceptance Criteria

  • EdgeConditionWithContext Protocol defined with kwargs support for future expansion
  • EdgeCondition Union type supports both legacy and new condition signatures
  • GraphEdge.should_traverse() accepts and passes invocation_state
  • All call sites updated to pass invocation_state through the execution flow
  • Existing tests pass without modification (backwards compatibility)
  • New unit tests for conditions that use invocation_state
  • New integration test demonstrating the use case
  • Type hints pass mypy strict mode

Implementation Notes

  • Use inspect.signature() for runtime detection of condition signature
  • The Protocol approach allows adding more context parameters in the future without breaking changes
  • Consider caching signature inspection results for performance if needed

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions