Files
langgraph/libs
Nick Hollon 2ad30132a3 Add subgraph lifecycle streaming with scoped per-subagent projections
StreamingHandler now yields SubgraphRunStream handles for each nested
Pregel as it spawns. Each handle is a BaseRunStream wrapping a
mini-mux built from the same transformer factories as the root — so
sub.values, sub.messages, sub.subgraphs are populated by standard
ValuesTransformer / MessagesTransformer / SubgraphTransformer
instances at that subagent's scope. No routing or ChatModelStream
assembly duplicated across transformers.

Key pieces:

- StreamLifecycleHandler (pregel/_lifecycle.py): callback handler
  attached at pregel stream / astream sites when "lifecycle" is in
  stream_modes. Emits started / running / completed / failed /
  interrupted events per nested Pregel via metadata-based detection
  (langgraph_checkpoint_ns + name != langgraph_node, excluding
  __start__/__end__ sentinels). Carries trigger_call_id from the
  parent task id. Root subgraph terminal state is emitted eagerly at
  __init__ and by SubgraphTransformer.finalize / fail.

- StreamMux.make_child(scope) + factory-based construction. Mux takes
  a factory list; make_child produces a mini-mux at a new scope with
  fresh instances. bind_pump / bind_apump cascade through children so
  any subagent cursor drives the root pump.

- StreamTransformer.scope (base attribute) + scope_exact class flag.
  Mux skips process() for out-of-scope events when scope_exact=True
  (default), so user transformers get scope-filtered events with no
  boilerplate. SubgraphTransformer opts out to receive cross-scope
  events for forwarding.

- BaseRunStream shared base for GraphRunStream, AsyncGraphRunStream,
  and SubgraphRunStream. Provides extensions, native attrs, raw
  __iter__ / __aiter__, interleave. Root subclasses own the
  graph iterator and pump; SubgraphRunStream adds lifecycle metadata
  (path, status, error, checkpoint, trigger_call_id, graph_name).

- SubgraphTransformer (stream/transformers.py) is now a thin
  discovery + forwarding dispatcher. On lifecycle.started at its
  scope + 1, it creates a SubgraphRunStream via
  parent_mux.make_child(ns). It forwards each event matching a
  direct-child's path into that child's mini-mux. Terminal lifecycle
  closes the mini-mux.

- StreamMode literal extended with "lifecycle". pregel/remote.py
  filters it out of the SDK stream-mode list since wire-protocol
  support isn't landed yet.

Tests: 16 new in test_stream_subgraph_transformer.py (unit + sync/async
end-to-end including error + grandchild); existing Values/Messages
namespace filter tests migrated through mux.push to reflect the new
scope_exact contract.
2026-04-18 18:15:31 -04:00
..