Skip to content

feat: pass invocation_state to edge condition calls - #2642

Merged
zastrowm merged 5 commits into
strands-agents:mainfrom
yananym:feat/edge-condition-invocation-state
Jun 13, 2026
Merged

feat: pass invocation_state to edge condition calls#2642
zastrowm merged 5 commits into
strands-agents:mainfrom
yananym:feat/edge-condition-invocation-state

Conversation

@yananym

@yananym yananym commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Description

Re-submission of #2305 (which was merged then reverted due to serialization failures in CI).

Add support for edge conditions that receive invocation_state, enabling conditional routing based on runtime context (feature flags, user roles, environment config) passed during graph invocation.

Also fixes a deadlock in _compute_ready_nodes_for_resume() where conditional edges evaluating to False would block downstream nodes from ever becoming ready on interrupt/resume workflows.

Change from #2305: invocation_state is NOT serialized

The reverted PR failed because invocation_state was serialized to session storage, causing TypeError: Object of type Agent is not JSON serializable when non-serializable objects (callbacks, agent references) were present.

Fix (Option B): Skip serializing invocation_state entirely. On resume, the caller must pass invocation_state again. The edge conditions get whatever the caller passes on each invocation.

This is the cleanest approach because:

  1. It's the caller's data — they should own providing it on every call
  2. No need for JSON-serialization concerns at all
  3. No deep-copy overhead

Resume flow: serialize_state() pre-computes next_nodes_to_execute (evaluating edge conditions while invocation_state is still in memory). On resume, the concrete node IDs are loaded from session — no re-evaluation needed for the initial batch. Any subsequent routing decisions use the caller's fresh invocation_state.

Public API Changes

GraphBuilder.add_edge() now accepts conditions with an extended signature via the EdgeConditionWithContext Protocol:

# Legacy (still works, no changes needed)
def my_condition(state: GraphState) -> bool:
    return len(state.completed_nodes) > 0

# New: receives invocation_state for runtime routing decisions
def my_condition(state: GraphState, *, invocation_state: dict, **kwargs) -> bool:
    return invocation_state.get("role") == "admin"

builder.add_edge("router", "admin_path", condition=my_condition)
graph("task", invocation_state={"role": "admin"})

Both signatures are supported indefinitely — no deprecation, no breaking changes.

Related Issues

Resolves #1346
Partial for #2387 (enables invocation_state foundation for cycle/loop support)

Documentation PR

strands-agents/docs#847

Type of Change

New feature (re-submission with fix)

Testing

  • 18 new unit tests covering protocol dispatch, invocation_state propagation, resume deadlock fix, and condition signature detection
  • 6 new integration tests covering conditional routing, backwards compat, combined conditions, diamond convergence, persistence/resume, and streaming events
  • All existing graph unit tests pass unchanged (67 total, backwards compatibility verified)
  • ruff check and mypy pass clean
  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions Bot added the size/l label Jun 5, 2026
@yananym
yananym temporarily deployed to manual-approval June 5, 2026 18:42 — with GitHub Actions Inactive
@yonib05 yonib05 added area-hil Human in the loop and suspend/resume area-multiagent Multi-agent related enhancement New feature or request labels Jun 9, 2026
Comment thread strands-py/src/strands/multiagent/graph.py Outdated
Comment thread strands-py/src/strands/multiagent/graph.py
Comment thread strands-py/src/strands/multiagent/graph.py
Comment thread strands-py/src/strands/multiagent/graph.py Outdated
Comment thread strands-py/src/strands/multiagent/graph.py
Comment thread strands-py/tests/strands/multiagent/test_graph.py
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Comment

Solid re-submission — the "don't serialize invocation_state" approach is clean and the backwards-compat surface is well-tested. The Protocol + **kwargs design follows the repo's own STYLE_GUIDE mandate over Callable. Two substantive items are worth resolving before merge: a documented behavioral inconsistency on resume, and a missing API-review label for the new public surface.

Review Themes
  • Resume semantics: The docstring on _is_node_ready_for_resume claims fresh-invocation_state re-evaluation, but the initial resume batch uses node IDs pre-computed at serialize time. Also, resume uses AND-join readiness while live execution uses OR-join — please confirm intentional and document.
  • API governance: New public exports (EdgeCondition, EdgeConditionWithContext) and an extended add_edge contract warrant a needs-api-review label per team/API_BAR_RAISING.md. PR description already has the needed materials.
  • Minor consistency: Internal should_traverse signature (dict | None) diverges from the public protocol (non-optional dict); signature detection keys only on the parameter name — worth a docstring note.
  • Tests: Comprehensive coverage of dispatch, propagation, and the deadlock fix. One helper bypasses __init__, coupling tests to internal layout.

Nice work threading invocation_state through without breaking the legacy condition signature.

yananym added 5 commits June 12, 2026 12:52
Add support for edge conditions that receive invocation_state,
enabling conditional routing based on runtime context (feature flags,
user roles, environment config) passed during graph invocation.

Also fixes a deadlock in _compute_ready_nodes_for_resume() where
conditional edges evaluating to False would block downstream nodes
from ever becoming ready on interrupt/resume workflows.

Resolves strands-agents#1346
- Cache inspect.signature() results via WeakKeyDictionary
- Remove unused @runtime_checkable decorator
- Gate serialization validation on session_manager presence
- Add validation on deserialization path for symmetry
- Move json import to module level
- Add inline comments for short-circuit and cache behavior
- Extract _make_graph() test helper, fix list->set type consistency
Cache the result of _is_context_condition() on each GraphEdge instance
instead of a module-level WeakKeyDictionary. Simpler, no global state,
and directly co-located with the edge that owns the condition.
invocation_state can contain non-serializable objects (callbacks, agent
refs). Rather than validating or deep-copying, the caller is now
responsible for passing invocation_state on every invocation including
resume. Edge conditions receive whatever the caller passes each time.

This fixes TypeError: Object of type Agent is not JSON serializable
that occurred when the session manager persisted graph state.
…arify docstrings

- Remove None default from should_traverse's invocation_state parameter
  to align with the EdgeConditionWithContext protocol contract
- Clarify _is_node_ready_for_resume docstring: distinguish pre-computed
  initial resume batch from re-evaluated subsequent routing, and document
  intentional AND-join vs OR-join semantics difference
- Add note to _is_context_condition about name-based parameter detection
- Update tests to pass invocation_state={} explicitly
@yananym
yananym force-pushed the feat/edge-condition-invocation-state branch from fd5370c to 528e559 Compare June 12, 2026 20:04
@yananym

yananym commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main (63 commits), addressed all automated review feedback, and force-pushed.

Changes in response to review:

  • Removed None default from should_traverse's invocation_state parameter to align with the EdgeConditionWithContext protocol contract
  • Clarified _is_node_ready_for_resume docstring: distinguishes pre-computed initial resume batch from re-evaluated subsequent routing, documents intentional AND-join vs OR-join semantics
  • Added note to _is_context_condition about name-based parameter detection
  • Updated tests accordingly

Re: integration test failures: All 20+ failures are in OpenAI model tests (test_model_openai.py, test_conformance[openai], test_session[server_side_conversation]) — these are timing out across all PRs in the repo currently. Not related to this PR's graph changes.

Re: needs-api-review label: The label doesn't exist in this repo. This PR exports EdgeCondition and EdgeConditionWithContext as new public symbols — flagging for API review consideration.

@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Approve

Thanks for the quick turnaround @yananym — re-reviewed commit 528e559 and all the actionable feedback from the previous round is resolved. Verified locally: 66 graph unit tests pass, the 15 condition/resume/invocation_state tests pass, and mypy is clean on graph.py.

Resolution of prior comments
  • should_traverse signature ✅ — None default removed; now invocation_state: dict[str, Any], aligned with the EdgeConditionWithContext protocol. All callers pass self._current_invocation_state (always a dict).
  • Resume docstring ✅ — _is_node_ready_for_resume now clearly separates the serialize-time pre-computed initial batch from re-evaluated subsequent routing, and explicitly documents the AND-join (resume) vs OR-join (live) difference as intentional with a sound rationale ("on resume we know the full set of completed work"). This was my main concern and the explanation is convincing.
  • Signature detection note ✅ — _is_context_condition now documents name-based detection and the keyword-passability requirement.
  • API-review label ℹ️ — Acknowledged the label doesn't exist in this repo; flagging for API-review consideration is the right call given it's outside your control. Recommend a maintainer confirms the public surface (EdgeCondition, EdgeConditionWithContext) before merge.

The only remaining item is the maintainer-side API sign-off on the new public symbols — not a code change. Nice, clean iteration on this. 🚀

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

Labels

area-hil Human in the loop and suspend/resume area-multiagent Multi-agent related enhancement New feature or request size/l

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Pass invocation_state to edge condition call

3 participants