Суть
Проблема, которую решает приём: без дельта-канала полный список пересериализуется целиком при каждом сохранении.
Важно не перепутать, что именно растёт квадратично. Отдельный снимок линеен по длине истории: пятнадцатый ход записывает все пятнадцать элементов. Сумма записанного за тред при этом растёт как квадрат числа ходов — 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 — та же задача уровнем выше, в контексте агента