LangGraph Delta Channels

Способ не пересериализовывать весь канал состояния в каждый чекпоинт: пишется только приращение. Экономия на записи покупается ценой на чтении — восстановление значения требует проигрывания всей истории записей, то есть линейно от числа шагов. Механизм помечен документацией как бета: представление на диске может измениться, гарантий отката нет.

Суть

Проблема, которую решает приём: без дельта-канала полный список пересериализуется целиком при каждом сохранении.

Важно не перепутать, что именно растёт квадратично. Отдельный снимок линеен по длине истории: пятнадцатый ход записывает все пятнадцать элементов. Сумма записанного за тред при этом растёт как квадрат числа ходов — 1 + 2 + … + N.

Различие практическое. Из «квадратично растёт сам чекпоинт» следовало бы, что неподъёмным станет отдельный снимок и лечить надо размер записи. На деле отдельная запись остаётся посильной, а больно становится от их количества: по диску, по стоимости записи и по времени выборки последнего состояния. Первым деградирует чтение, потому что выборка последнего состояния стоит на пути каждого вызова агента.

С дельта-каналом сохраняется только то, что добавилось.

Обмен, который надо понимать до включения

Цена — чтение. Восстановление значения дельта-канала требует проигрывания полной истории записей, то есть O(N) для треда из N шагов. Приём выгоден для полей, в которые часто пишут и редко читают, и невыгоден для тех, что читаются на каждом шаге.

Глубину ограничивает периодический полный снимок. Параметр snapshot_frequency — по умолчанию 1000 обновлений канала. Системный потолок — 5000 супершагов без снимка (DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT), после чего снимок пишется принудительно, даже если в канал давно не писали. Второй счётчик существует именно для того, чтобы ограничить глубину проигрывания у каналов, в которые писать перестали.

Отключить снимки нельзя. В сигнатуре параметр объявлен как snapshot_frequency: int = 1000, без варианта None; способа выключить снимки совсем документация не даёт. Глубину проигрывания можно только задрать, подняв частоту, — и упереться в потолок 5000 супершагов.

Требований к редьюсеру два, и обычно помнят только второе. Документация формулирует их вместе: редьюсер должен быть детерминированным и инвариантным к разбиению на батчи (ассоциативным по сверткам). Инвариантность записывается как reducer(reducer(state, xs), ys) == reducer(state, xs + ys); без неё проигрывание истории по частям даст не тот результат, что проигрывание целиком (LangGraph Reducers). Детерминизм — отдельное требование, и нарушить его проще: редьюсер, подмешивающий текущее время, случайность или порядок из множества, ассоциативность формально сохраняет, а воспроизводимость теряет.

Механизм в бете, и это меняет решение

Свойство, которое стоит знать до включения, а не после: документация помечает DeltaChannel как бета. Прямо сказано, что API и представление на диске могут измениться в будущих выпусках, а окружающий контракт ещё не стабилен. Треды, записанные в этом формате, читаться продолжат — но гарантий отката документация не даёт вовсе.

Разница с формулировкой «откат объявлен неподдерживаемым» существенная, и путать их не стоит. «Не поддерживается» — это утверждение о поведении, на которое можно опереться при планировании. «Гарантий нет» — это отсутствие утверждения: сегодня откат может сработать, завтра сломаться, и претензий предъявить будет не к чему.

Практический вывод от этого не смягчается, а становится жёстче: включение дельта-каналов — решение уровня ADR, и принимать его стоит там, где выигрыш на записи измерен, а не предположен. Определения каналов при этом держат неизменными всю жизнь треда.

Связано с

  • LangGraph Checkpointers — что и куда сохраняется
  • LangGraph Reducers — требование ассоциативности идёт отсюда
  • LangGraph State — какие поля вообще живут в состоянии
  • LangGraph Pending Writes — соседний механизм экономии на записи
  • Durable Execution — общий обмен «стоимость записи против живучести»
  • Context Compaction — та же задача уровнем выше, в контексте агента