mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 06:35:46 +02:00
Four fixes from an independent review of the reconstruction pipeline, plus a structural cleanup: 1. Ancestor walk excludes the target checkpoint itself (matches pregel: writes stored under checkpoint_id=T are pending for the NEXT step and applied separately via apply_writes). Memory saver previously included them, diverging from Postgres and causing pending writes to be folded into the reconstructed snapshot — visible via get_state during interrupts and time-travel into a non-leaf checkpoint. 2. Pre-delta blob terminator. When the walk hits an ancestor whose blob for the channel is a real value (not DELTA_SENTINEL), bind that blob as DeltaChannelWrites.seed and stop. Without this, threads migrated from pre-delta storage would replay ancestor writes to the root forever AND lose any value that lived only in the old blob (e.g. from update_state). Per-ancestor, the blob is checked BEFORE its writes — a pre-delta blob subsumes writes at the same checkpoint, so including them would double-count. 3. Base-fallback get_channel_writes follows parent_checkpoint_id instead of list(before=...). The previous form returned every tuple with id<target, including sibling branches on forked threads. 4. seed replaces the Overwrite-wrapping hack for pre-delta values. DeltaChannelWrites(writes, seed=SEED_UNSET) makes the saver's reconstruction terminator semantically explicit; drops the lazy _make_overwrite import dance. User-emitted Overwrite still reset the chain via _apply_write as before. Postgres: recursive CTE enumerates on-path ancestors and joins once against checkpoint_writes and once against checkpoint_blobs for every delta channel in the get_tuple — one roundtrip instead of the previous 3 queries × N channels. Tests added: - Pre-delta blob seeding (seed binding, no double-counting of ancestor writes at the terminator, pending-at-target excluded). - Root checkpoint returns empty writes. - Seed-based from_checkpoint replay (three scenarios: with writes, seed-only, seed=None distinct from SEED_UNSET). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>