[AAASM-4472] 🐛 (langgraph): Fix subgraph-as-node lineage tracking gap - #236
Conversation
_wrap_node_entry checked _is_compiled_subgraph() against the outer
PregelNode wrapper before unwrapping via .bound, so when a compiled
subgraph is used as a node (parent_graph.add_node("sub", compiled)),
the outer wrapper (which has no .nodes attribute) failed the check
and fell through to generic hook-wrapping instead of
_wrap_subgraph_spawn_node. Coarse governance hooks still fired, but
_SPAWN_CTX (parent-agent lineage) was never set inside the
subgraph's own nodes.
Reorder detection in _wrap_node_entry to unwrap PregelNode.bound
first, then run _is_compiled_subgraph() against the unwrapped
object. Since the outer PregelNode container (channels/triggers/
mapper/writers) must stay intact for the Pregel runtime, add
_wrap_subgraph_invoke_methods_in_place to mutate the bound
subgraph's own invoke/ainvoke in place (capturing the original
methods first to avoid the wrapper recursing into itself) rather
than replacing the whole node_map entry, which is only safe for
bare (non-PregelNode-wrapped) subgraphs.
AAASM-4472
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Claude Code — PR review1. CI checkRe-ran 2. Scope check (AAASM-4472 AC)Compared the ticket's AC against the diff:
PR fully covers the stated AC. 3. Side-effect check — three-case dispatch trace (core governance logic)Traced
None of the three cases fall through to the wrong branch. The reorder is safe.
The docstring's claim that capturing However, I found an asymmetry: the This isn't infinite recursion (bounded, no stack overflow), but it's silent N-fold instrumentation stacking that corrupts the lineage Suggested fix: add a matching guard, e.g. This doesn't invalidate the fix itself (the new test's scenario — single subgraph instance, single parent — is unaffected and passes correctly), but it's a latent correctness gap in the new code that should be closed, since a shared-subgraph-across-orchestrators pattern is realistic for this SDK's use case. 4. Front-end checkN/A — pure Python governance library code, no UI surface. VerdictCI is green and the AC is fully met. The core three-case dispatch reorder is correct and was verified empirically against a real langgraph 1.2.9 install, not just read from the diff. Recommend a small follow-up fix before merge: add an idempotency guard to the sync |
Review — Claude Code (AAASM-4472)Verdict: Approve. The fix cleanly addresses the ticket's root cause and matches the suggested fix direction exactly. CIAll real checks green — CI (unit + integration), CodeQL, docs-build, pip-audit, LangChain contract test all SUCCESS. Only codecov/SonarCloud are informational. Scope — does it fix the ticket?Yes. Side-effects — normal / other pathsNo regression to the non-subgraph path. A plain top-level node is still Regression testYes, and it covers both directions the AC asked for: the new ValidationRan locally in the worktree: Minor, non-blocking observations
Neither blocks merge. |



What changed
_wrap_node_entryinagent_assembly/adapters/langgraph/patch.pychecked_is_compiled_subgraph()against the outerPregelNodewrapper before unwrapping it via.bound. When a compiled subgraph is used as a node (parent_graph.add_node("sub", compiled_subgraph)) — the standard multi-agent delegation pattern — LangGraph also wraps that subgraph in aPregelNode. The outer wrapper never exposes.nodes(only the wrapped object does), so the check failed and the code fell through to generic hook-wrapping instead of the dedicated_wrap_subgraph_spawn_node()path.Effect: coarse governance hooks still fired correctly, but
_SPAWN_CTX(the parent-agent lineage/spawn-tracking context) was never set inside the subgraph's own nodes — any policy/audit logic depending on "which parent agent spawned this subgraph invocation" silently lost that lineage.This is a second, narrower instance of the same fail-open class as AAASM-4434 (which fixed the top-level-node case:
PregelNode.invokevsPregelNode.bound.invoke).Why
Governance lineage tracking must hold for the standard subgraph-as-node delegation pattern, not just top-level callable nodes.
The fix
In
_wrap_node_entry, thePregelNode-wrapper check now runs first, before thecallable()and_is_compiled_subgraph()checks. It unwraps via.boundand then runs_is_compiled_subgraph()against the unwrapped object — so subgraph detection works whether the subgraph arrives bare or alreadyPregelNode-wrapped (LangGraph's own wrapping behavior varies by call site/version).Because the outer
PregelNodecontainer (channels/triggers/mapper/writers) must stay intact for the Pregel runtime to keep dispatching correctly, the fix does not replace the whole node-map entry (which is what_wrap_subgraph_spawn_nodedoes for bare, non-wrapped subgraphs — safe there since the map entry is the subgraph). Instead, a new_wrap_subgraph_invoke_methods_in_place()mutates the bound subgraph's owninvoke/ainvokeattributes in place, capturing the original methods first so the wrapper calls the real implementation rather than recursing into itself once installed.Test evidence
Reproduced against the real installed
langgraph==1.2.9package per the ticket's repro steps (parentStateGraphwithadd_node("sub", compiled_subgraph), invoked,_SPAWN_CTXinspected from inside the subgraph's own node).Before fix (new test failing):
After fix (same test passing):
The existing AAASM-4434 real-package smoke test (top-level node case) passes unchanged. Also manually verified the async (
ainvoke) path for a subgraph-as-node case sets_SPAWN_CTXcorrectly (not covered by an existing test pattern, verified via a standalone repro script).Full check suite results
pytest test/: 780 passed, 16 skipped (pre-existing environment gaps: native_coremodule not built, optional framework deps likecrewai/google.adk/haystacknot installed — unrelated to this change), 0 failed.ruff check .: all checks passed.mypy agent_assembly: 4 pre-existing errors, all inagent_assembly/types.py,agent_assembly/__init__.py,agent_assembly/op_control.py,agent_assembly/core/runtime_interceptor.py— all related to the native_coremodule not being built / missinggrpcstubs, present identically onmasterbefore this change.mypy agent_assembly/adapters/langgraph/patch.py(the changed file): 0 issues.pre-commit run --files agent_assembly/adapters/langgraph/patch.py test/integration/test_langgraph_real_package_smoke.py: all hooks passed (isort, autoflake, black, mypy, etc.).Related
Closes AAASM-4472. Follow-up to AAASM-4434.