langgraph 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {langgraph-1.2.3 → langgraph-1.2.4}/PKG-INFO +1 -1
  2. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/main.py +23 -2
  3. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/transformers.py +17 -12
  4. {langgraph-1.2.3 → langgraph-1.2.4}/pyproject.toml +1 -1
  5. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_runtime.py +54 -0
  6. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_lifecycle_transformer.py +32 -0
  7. {langgraph-1.2.3 → langgraph-1.2.4}/uv.lock +1 -1
  8. {langgraph-1.2.3 → langgraph-1.2.4}/.gitignore +0 -0
  9. {langgraph-1.2.3 → langgraph-1.2.4}/LICENSE +0 -0
  10. {langgraph-1.2.3 → langgraph-1.2.4}/Makefile +0 -0
  11. {langgraph-1.2.3 → langgraph-1.2.4}/README.md +0 -0
  12. {langgraph-1.2.3 → langgraph-1.2.4}/bench/__init__.py +0 -0
  13. {langgraph-1.2.3 → langgraph-1.2.4}/bench/__main__.py +0 -0
  14. {langgraph-1.2.3 → langgraph-1.2.4}/bench/fanout_to_subgraph.py +0 -0
  15. {langgraph-1.2.3 → langgraph-1.2.4}/bench/pydantic_state.py +0 -0
  16. {langgraph-1.2.3 → langgraph-1.2.4}/bench/react_agent.py +0 -0
  17. {langgraph-1.2.3 → langgraph-1.2.4}/bench/sequential.py +0 -0
  18. {langgraph-1.2.3 → langgraph-1.2.4}/bench/serde_allowlist.py +0 -0
  19. {langgraph-1.2.3 → langgraph-1.2.4}/bench/wide_dict.py +0 -0
  20. {langgraph-1.2.3 → langgraph-1.2.4}/bench/wide_state.py +0 -0
  21. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/__init__.py +0 -0
  22. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_cache.py +0 -0
  23. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_config.py +0 -0
  24. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_constants.py +0 -0
  25. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_fields.py +0 -0
  26. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_future.py +0 -0
  27. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_pydantic.py +0 -0
  28. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_queue.py +0 -0
  29. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_replay.py +0 -0
  30. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_retry.py +0 -0
  31. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_runnable.py +0 -0
  32. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_scratchpad.py +0 -0
  33. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_serde.py +0 -0
  34. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_timeout.py +0 -0
  35. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/_internal/_typing.py +0 -0
  36. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/callbacks.py +0 -0
  37. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/__init__.py +0 -0
  38. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/any_value.py +0 -0
  39. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/base.py +0 -0
  40. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/binop.py +0 -0
  41. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/delta.py +0 -0
  42. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/ephemeral_value.py +0 -0
  43. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/last_value.py +0 -0
  44. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/named_barrier_value.py +0 -0
  45. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/topic.py +0 -0
  46. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/channels/untracked_value.py +0 -0
  47. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/config.py +0 -0
  48. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/constants.py +0 -0
  49. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/errors.py +0 -0
  50. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/func/__init__.py +0 -0
  51. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/__init__.py +0 -0
  52. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/_branch.py +0 -0
  53. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/_node.py +0 -0
  54. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/message.py +0 -0
  55. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/state.py +0 -0
  56. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/graph/ui.py +0 -0
  57. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/managed/__init__.py +0 -0
  58. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/managed/base.py +0 -0
  59. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/managed/is_last_step.py +0 -0
  60. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/__init__.py +0 -0
  61. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_algo.py +0 -0
  62. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_call.py +0 -0
  63. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_checkpoint.py +0 -0
  64. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_config.py +0 -0
  65. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_draw.py +0 -0
  66. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_executor.py +0 -0
  67. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_io.py +0 -0
  68. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_log.py +0 -0
  69. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_loop.py +0 -0
  70. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_messages.py +0 -0
  71. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_read.py +0 -0
  72. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_remote_run_stream.py +0 -0
  73. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_retry.py +0 -0
  74. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_runner.py +0 -0
  75. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_tools.py +0 -0
  76. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_utils.py +0 -0
  77. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_validate.py +0 -0
  78. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/_write.py +0 -0
  79. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/debug.py +0 -0
  80. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/protocol.py +0 -0
  81. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/remote.py +0 -0
  82. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/pregel/types.py +0 -0
  83. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/py.typed +0 -0
  84. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/runtime.py +0 -0
  85. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/__init__.py +0 -0
  86. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/_convert.py +0 -0
  87. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/_mux.py +0 -0
  88. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/_types.py +0 -0
  89. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/run_stream.py +0 -0
  90. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/stream/stream_channel.py +0 -0
  91. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/types.py +0 -0
  92. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/typing.py +0 -0
  93. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/utils/__init__.py +0 -0
  94. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/utils/config.py +0 -0
  95. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/utils/runnable.py +0 -0
  96. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/version.py +0 -0
  97. {langgraph-1.2.3 → langgraph-1.2.4}/langgraph/warnings.py +0 -0
  98. {langgraph-1.2.3 → langgraph-1.2.4}/tests/__init__.py +0 -0
  99. {langgraph-1.2.3 → langgraph-1.2.4}/tests/__snapshots__/test_large_cases.ambr +0 -0
  100. {langgraph-1.2.3 → langgraph-1.2.4}/tests/__snapshots__/test_pregel.ambr +0 -0
  101. {langgraph-1.2.3 → langgraph-1.2.4}/tests/__snapshots__/test_pregel_async.ambr +0 -0
  102. {langgraph-1.2.3 → langgraph-1.2.4}/tests/agents.py +0 -0
  103. {langgraph-1.2.3 → langgraph-1.2.4}/tests/any_int.py +0 -0
  104. {langgraph-1.2.3 → langgraph-1.2.4}/tests/any_str.py +0 -0
  105. {langgraph-1.2.3 → langgraph-1.2.4}/tests/compose-postgres.yml +0 -0
  106. {langgraph-1.2.3 → langgraph-1.2.4}/tests/compose-redis.yml +0 -0
  107. {langgraph-1.2.3 → langgraph-1.2.4}/tests/conftest.py +0 -0
  108. {langgraph-1.2.3 → langgraph-1.2.4}/tests/conftest_checkpointer.py +0 -0
  109. {langgraph-1.2.3 → langgraph-1.2.4}/tests/conftest_store.py +0 -0
  110. {langgraph-1.2.3 → langgraph-1.2.4}/tests/example_app/example_graph.py +0 -0
  111. {langgraph-1.2.3 → langgraph-1.2.4}/tests/example_app/langgraph.json +0 -0
  112. {langgraph-1.2.3 → langgraph-1.2.4}/tests/example_app/requirements.txt +0 -0
  113. {langgraph-1.2.3 → langgraph-1.2.4}/tests/fake_chat.py +0 -0
  114. {langgraph-1.2.3 → langgraph-1.2.4}/tests/fake_tracer.py +0 -0
  115. {langgraph-1.2.3 → langgraph-1.2.4}/tests/memory_assert.py +0 -0
  116. {langgraph-1.2.3 → langgraph-1.2.4}/tests/messages.py +0 -0
  117. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_algo.py +0 -0
  118. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_channels.py +0 -0
  119. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_checkpoint_migration.py +0 -0
  120. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_config_async.py +0 -0
  121. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_delta_channel_benchmark.py +0 -0
  122. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_delta_channel_exit_mode.py +0 -0
  123. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_delta_channel_id_stability.py +0 -0
  124. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_delta_channel_migration.py +0 -0
  125. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_delta_channel_supersteps_bound.py +0 -0
  126. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_deprecation.py +0 -0
  127. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_graph_callbacks.py +0 -0
  128. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_interleave_arrival_order.py +0 -0
  129. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_interrupt_migration.py +0 -0
  130. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_interruption.py +0 -0
  131. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_large_cases.py +0 -0
  132. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_large_cases_async.py +0 -0
  133. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_managed_values.py +0 -0
  134. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_messages_state.py +0 -0
  135. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_parent_command.py +0 -0
  136. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_parent_command_async.py +0 -0
  137. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_pregel.py +0 -0
  138. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_pregel_async.py +0 -0
  139. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_pregel_debug.py +0 -0
  140. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_pregel_stream_events_v3.py +0 -0
  141. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_pydantic.py +0 -0
  142. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_remote_graph.py +0 -0
  143. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_remote_graph_v3.py +0 -0
  144. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_retry.py +0 -0
  145. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_runnable.py +0 -0
  146. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_serde_allowlist.py +0 -0
  147. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_state.py +0 -0
  148. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_before_builtins.py +0 -0
  149. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_data_transformers.py +0 -0
  150. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_events_v3.py +0 -0
  151. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_events_v3_e2e.py +0 -0
  152. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_events_v3_kwarg_forwarding.py +0 -0
  153. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_messages_transformer.py +0 -0
  154. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_stream_subgraph_transformer.py +0 -0
  155. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_subgraph_persistence.py +0 -0
  156. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_subgraph_persistence_async.py +0 -0
  157. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_time_travel.py +0 -0
  158. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_time_travel_async.py +0 -0
  159. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_tool_stream_handler.py +0 -0
  160. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_tracing_interops.py +0 -0
  161. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_type_checking.py +0 -0
  162. {langgraph-1.2.3 → langgraph-1.2.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Building stateful, multi-actor applications with LLMs
5
5
  Project-URL: Homepage, https://docs.langchain.com/oss/python/langgraph/overview
6
6
  Project-URL: Documentation, https://reference.langchain.com/python/langgraph/
@@ -2835,7 +2835,9 @@ class Pregel(
2835
2835
  config[CONF][CONFIG_KEY_DURABILITY] = durability_
2836
2836
 
2837
2837
  # build server_info from metadata + parent runtime
2838
- parent_runtime = config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
2838
+ parent_runtime = _coerce_parent_runtime(
2839
+ config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
2840
+ )
2839
2841
  server_info = _build_server_info(config, parent_runtime)
2840
2842
 
2841
2843
  runtime = Runtime(
@@ -3276,7 +3278,9 @@ class Pregel(
3276
3278
  config[CONF][CONFIG_KEY_DURABILITY] = durability_
3277
3279
 
3278
3280
  # build server_info from metadata + parent runtime
3279
- parent_runtime = config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
3281
+ parent_runtime = _coerce_parent_runtime(
3282
+ config[CONF].get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
3283
+ )
3280
3284
  server_info = _build_server_info(config, parent_runtime)
3281
3285
 
3282
3286
  runtime = Runtime(
@@ -4253,6 +4257,23 @@ def _resolve_parent_ns(
4253
4257
  return tuple(ns.split(NS_SEP))
4254
4258
 
4255
4259
 
4260
+ def _coerce_parent_runtime(value: Any) -> Runtime[Any]:
4261
+ """Normalize the value stored under `CONFIG_KEY_RUNTIME` into a `Runtime`.
4262
+
4263
+ During a graph run this is always a `Runtime` the framework created and
4264
+ published for child tasks to inherit. Layers outside the run (for example a
4265
+ server's graph-factory path) may instead seed an object that only carries
4266
+ `context`/`store`. Adopt its `context` so context set at the graph level
4267
+ plumbs through (`merge` lets the run's own `context` take precedence when
4268
+ one is provided). `store` is resolved separately (passed to the graph
4269
+ directly), so it is not read off here. `merge` then combines this with the
4270
+ run's own runtime, including the framework's `control`.
4271
+ """
4272
+ if isinstance(value, Runtime):
4273
+ return value
4274
+ return Runtime(context=getattr(value, "context", None))
4275
+
4276
+
4256
4277
  def _build_server_info(
4257
4278
  config: RunnableConfig, parent_runtime: Runtime[Any]
4258
4279
  ) -> ServerInfo | None:
@@ -419,6 +419,11 @@ class _TasksLifecycleBase(StreamTransformer):
419
419
  # `node:<task_id>` shares this task_id, so a subagent recovers the tool
420
420
  # call that spawned it (cross-payload).
421
421
  self._pending_tool_calls: dict[str, str] = {}
422
+ # `cause` for the current `_on_started` dispatch, set immediately before
423
+ # the call and read by overrides that surface it. Keeping `cause` off the
424
+ # `_on_started` signature means overrides predating it (e.g. deepagents'
425
+ # `SubagentTransformer`) don't break.
426
+ self._pending_cause: LifecycleCause | None = None
422
427
 
423
428
  # --- Template-method hooks (subclass overrides) ---
424
429
 
@@ -431,10 +436,11 @@ class _TasksLifecycleBase(StreamTransformer):
431
436
  ns: tuple[str, ...],
432
437
  graph_name: str | None,
433
438
  trigger_call_id: str | None,
434
- *,
435
- cause: LifecycleCause | None = None,
436
439
  ) -> None:
437
- """Fired once per discovered namespace (first observed task event)."""
440
+ """Fired once per discovered namespace (first observed task event).
441
+
442
+ The triggering `cause` (if any) is available as `self._pending_cause`.
443
+ """
438
444
  raise NotImplementedError
439
445
 
440
446
  def _on_terminal(
@@ -532,7 +538,10 @@ class _TasksLifecycleBase(StreamTransformer):
532
538
  tool_call_id = self._pending_tool_calls.get(trigger_call_id)
533
539
  if tool_call_id:
534
540
  cause = {"type": "toolCall", "tool_call_id": str(tool_call_id)}
535
- self._on_started(ns, graph_name, trigger_call_id, cause=cause)
541
+ # Deliver `cause` via instance state, not the call signature, so
542
+ # `_on_started` stays backward-compatible with overrides predating it.
543
+ self._pending_cause = cause
544
+ self._on_started(ns, graph_name, trigger_call_id)
536
545
  if trigger_call_id is not None:
537
546
  self._open[ns] = trigger_call_id
538
547
 
@@ -631,8 +640,6 @@ class LifecycleTransformer(_TasksLifecycleBase):
631
640
  ns: tuple[str, ...],
632
641
  graph_name: str | None,
633
642
  trigger_call_id: str | None,
634
- *,
635
- cause: LifecycleCause | None = None,
636
643
  ) -> None:
637
644
  if trigger_call_id is None:
638
645
  # Without a task id we can't correlate a parent-result
@@ -643,6 +650,7 @@ class LifecycleTransformer(_TasksLifecycleBase):
643
650
  if graph_name:
644
651
  payload["graph_name"] = graph_name
645
652
  payload["trigger_call_id"] = trigger_call_id
653
+ cause = self._pending_cause
646
654
  if cause is not None:
647
655
  payload["cause"] = cause
648
656
  self._channel.push(payload)
@@ -707,8 +715,6 @@ class SubgraphTransformer(_TasksLifecycleBase):
707
715
  ns: tuple[str, ...],
708
716
  graph_name: str | None,
709
717
  trigger_call_id: str | None,
710
- *,
711
- cause: LifecycleCause | None = None,
712
718
  ) -> None:
713
719
  if self._mux is None:
714
720
  return
@@ -717,10 +723,9 @@ class SubgraphTransformer(_TasksLifecycleBase):
717
723
  except RuntimeError:
718
724
  return
719
725
  handle_cls = AsyncSubgraphRunStream if child_mux.is_async else SubgraphRunStream
720
- # `cause` is intentionally ignored here: it is a wire/lifecycle-channel
721
- # concern (carried on `LifecyclePayload`), not something the in-process
722
- # subgraph navigation handle exposes. The argument is accepted only to
723
- # keep the `_on_started` template signature uniform across transformers.
726
+ # The triggering `cause` (on `self._pending_cause`) is a wire/lifecycle
727
+ # concern carried on `LifecyclePayload`; the in-process subgraph
728
+ # navigation handle does not expose it.
724
729
  handle = handle_cls(
725
730
  mux=child_mux,
726
731
  path=ns,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langgraph"
7
- version = "1.2.3"
7
+ version = "1.2.4"
8
8
  description = "Building stateful, multi-actor applications with LLMs"
9
9
  authors = []
10
10
  requires-python = ">=3.10"
@@ -1164,3 +1164,57 @@ def test_execution_info_inherited_by_subgraph() -> None:
1164
1164
  # task_id appears in its own namespace segment
1165
1165
  assert main_info.task_id in main_info.checkpoint_ns
1166
1166
  assert sub_info.task_id in sub_info.checkpoint_ns
1167
+
1168
+
1169
+ def test_foreign_object_in_runtime_slot_is_coerced() -> None:
1170
+ """A non-`Runtime` under `CONFIG_KEY_RUNTIME` is adopted, not crashed on,
1171
+ and the `context` it carries is plumbed through.
1172
+
1173
+ Layers outside a run (e.g. a server's graph-factory path, like LangGraph
1174
+ API) seed an object carrying `context`/`store` into the runtime slot. The
1175
+ run must still execute (regression for `AttributeError: '...' object has no
1176
+ attribute 'control'`), and `context` set at that level must reach nodes via
1177
+ `merge` when no per-run `context` is provided. `store` is resolved
1178
+ separately, so it is not read off the foreign object in the coercion.
1179
+ """
1180
+ from langgraph.store.memory import InMemoryStore
1181
+
1182
+ from langgraph._internal._constants import CONFIG_KEY_RUNTIME
1183
+
1184
+ store = InMemoryStore()
1185
+ graph_level_context = {"source": "graph-level"}
1186
+
1187
+ class _ServerLikeRuntime:
1188
+ """Carries context/store but is not a `Runtime` (no control/merge)."""
1189
+
1190
+ def __init__(self) -> None:
1191
+ self.context = graph_level_context
1192
+ self.store = store
1193
+
1194
+ seen: dict[str, Any] = {}
1195
+
1196
+ class State(TypedDict, total=False):
1197
+ text: str
1198
+
1199
+ def echo(state: State, runtime: Runtime) -> State:
1200
+ seen["context"] = runtime.context
1201
+ seen["store"] = runtime.store
1202
+ return {"text": (state.get("text") or "") + " echoed"}
1203
+
1204
+ builder = StateGraph(State)
1205
+ builder.add_node("echo", echo)
1206
+ builder.add_edge(START, "echo")
1207
+ builder.add_edge("echo", END)
1208
+ graph = builder.compile()
1209
+
1210
+ # No per-run `context=`: the graph-level context on the slot must plumb through.
1211
+ result = graph.invoke(
1212
+ {"text": "hi"},
1213
+ {"configurable": {CONFIG_KEY_RUNTIME: _ServerLikeRuntime()}},
1214
+ )
1215
+
1216
+ assert result == {"text": "hi echoed"}
1217
+ # context set at the graph level is plumbed through to the node via merge
1218
+ assert seen["context"] == graph_level_context
1219
+ # store still reaches the node (resolved separately, not via the coercion)
1220
+ assert seen["store"] is store
@@ -25,6 +25,7 @@ from langgraph.stream._mux import StreamMux
25
25
  from langgraph.stream.transformers import (
26
26
  LifecyclePayload,
27
27
  LifecycleTransformer,
28
+ _TasksLifecycleBase,
28
29
  )
29
30
 
30
31
  TS = int(time.time() * 1000)
@@ -678,3 +679,34 @@ def test_lifecycle_subagent_terminal_roundtrip() -> None:
678
679
  started, _completed = subagent
679
680
  assert started["graph_name"] == "weather_agent"
680
681
  assert started["cause"] == {"type": "toolCall", "tool_call_id": "call_w"}
682
+
683
+
684
+ def test_on_started_override_without_cause_is_backward_compatible() -> None:
685
+ """An `_on_started` override with the original 3-arg signature must work.
686
+
687
+ `cause` is delivered via `self._pending_cause`, not the call signature, so
688
+ older/third-party subclasses (e.g. deepagents' `SubagentTransformer`) that
689
+ override `_on_started(self, ns, graph_name, trigger_call_id)` keep working —
690
+ no `TypeError: _on_started() got an unexpected keyword argument 'cause'`.
691
+ """
692
+ seen: list[tuple] = []
693
+
694
+ class _LegacyTransformer(_TasksLifecycleBase):
695
+ def init(self) -> dict[str, Any]:
696
+ return {}
697
+
698
+ def _should_track(self, ns: tuple[str, ...]) -> bool:
699
+ return len(ns) == 1 # direct child of the root scope
700
+
701
+ # Deliberately omits `cause` — mirrors a pre-`cause` override.
702
+ def _on_started(self, ns, graph_name, trigger_call_id) -> None: # type: ignore[override]
703
+ seen.append((ns, graph_name, trigger_call_id))
704
+
705
+ def _on_terminal(self, ns, status, error) -> None:
706
+ pass
707
+
708
+ transformer = _LegacyTransformer(scope=())
709
+ # Must not raise: the base calls `_on_started` without a `cause` kwarg.
710
+ transformer.process(_tasks_start(["agent:abc123"], task_id="abc123", name="agent"))
711
+
712
+ assert seen == [(("agent:abc123",), "agent", "abc123")]
@@ -1382,7 +1382,7 @@ wheels = [
1382
1382
 
1383
1383
  [[package]]
1384
1384
  name = "langgraph"
1385
- version = "1.2.3"
1385
+ version = "1.2.4"
1386
1386
  source = { editable = "." }
1387
1387
  dependencies = [
1388
1388
  { name = "langchain-core" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes