mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-12 20:57:52 +02:00
## Summary Fixes two bugs when time-traveling to a subgraph checkpoint (`graph.invoke(None, sub_config)`): 1. **Wrong checkpoint loaded:** The old `__enter__` checked `replay_state` before explicit `checkpoint_id` for nested graphs. When time-traveling, `checkpoint_map` resolves a specific checkpoint_id for the target subgraph, but `replay_state.get_checkpoint()` would find an *earlier* checkpoint (via `list(before=parent_ckpt_id)`), causing the subgraph to re-execute from the beginning (step_a, ask_1, etc. all re-ran). Fix: check for explicit `checkpoint_id` first. 2. **Stale RESUME writes kept:** The parent sets `RESUMING=True` on subgraph configs — it can't distinguish time-travel from normal resume. The subgraph sees `RESUMING=True` and preserves old RESUME writes, so `interrupt()` returns cached answers instead of re-firing. Fix: check whether the subgraph's own namespace appears in `checkpoint_map` (it only does during time-travel — normally the map only has ancestor entries). When present, force-strip RESUME writes. ## Test plan Tests cover time travel (replay + fork) at different interrupt points for both single-nested and double-nested subgraphs, including cases where the middle subgraph itself has interrupts. All tests have sync and async variants. ### Single nested (2 levels: parent → executor subgraph) - `test_subgraph_time_travel_to_first_interrupt` — time travel to 1st interrupt checkpoint; verifies step_a doesn't re-run, ask_1 re-fires (replay + fork) - `test_subgraph_time_travel_to_second_interrupt` — time travel to 2nd interrupt checkpoint; verifies step_a and ask_1 don't re-run, ask_2 re-fires (replay + fork) - `test_subgraph_time_travel_after_completion` — replay from final parent checkpoint after full completion; verifies no nodes re-run and all state values preserved ### Double nested (3 levels: parent → outer → inner subgraph) - `test_3_levels_deep_time_travel_to_first_interrupt` — time travel to innermost checkpoint at 1st interrupt (replay + fork) - `test_3_levels_deep_time_travel_to_second_interrupt` — time travel to innermost checkpoint at 2nd interrupt (replay + fork) - `test_3_levels_deep_time_travel_to_middle_subgraph` — time travel to middle-level subgraph checkpoint (replay + fork) - `test_3_levels_deep_middle_has_interrupts` — middle subgraph has its own `interrupt()` calls (in `pre` node) plus an inner subgraph with interrupts; time travel to middle checkpoint at each interrupt point (replay + fork at both the middle's own interrupt and the inner's interrupt)