Deep Agents는 LangGraph 런타임 위에 구축되어 있으며, 매 단계마다 에이전트의 진행 상황을 체크포인트합니다. 이것이 관찰성, 인간-루프-내, 그리고 장애 복구를 가능하게 합니다: 당신은 항상 에이전트가 어디에 있는지 정확히 알 수 있고, 어떤 지점에서든 재개할 수 있습니다.
에이전트가 더 능력이 강해질수록:
- 그들은 더 오래 실행되며, 메시지 히스토리가 수십 개 또는 수백 개의 단계를 거쳐 증가합니다
- 그들은 더 많은 컨텍스트를 사용하며, 컨텍스트 관리 및 오프로딩을 위해 파일시스템을 활용합니다
Deep Agents의 경우, 메시지 히스토리와 파일은 에이전트 상태에 있으며, 매 단계마다 스냅샷을 하는 방식으로, 체크포인트 저장소는 O(N²)로 증가합니다. 200번의 턴을 실행하는 코딩 에이전트의 경우, 현재 체크포인팅 방법은 체크포인터에 5.3GB를 직렬화합니다. 델타 채널은 이를 129 MB로 줄이고, 40배 이상의 감소로, 상태 재수화에서 실제로 성능 저하가 거의 없습니다.
델타 채널은 우리가 런타임을 진화시키는 방법입니다. DeltaChannel은 langgraph 1.2의 새로운 프리미티브로, 누적 상태 필드가 어떻게 체크포인트되는지 변경합니다. 매 단계마다 전체 스냅샷을 직렬화하는 대신, 각 단계는 diff만 저장합니다. 전체 스냅샷은 주기적으로 작성되어 복구 비용을 제한합니다. Deep Agents의 경우, messages와 files에 대한 델타 기반 저장소를 의미합니다. 당신은 여전히 에이전트 진행의 완전한 히스토리를 얻을 수 있으며, 단지 비용의 일부만 들게 됩니다.
LangGraph의 체크포인트 저장소는 긴 메시지 히스토리가 있는 에이전트의 경우 O(N²)로 증가합니다. 200번의 턴을 실행하는 코딩 에이전트의 경우, 이는 5.3 GB입니다. 델타 채널은 이를 129 MB로 줄입니다 — 41배 감소, 무료로.
문제: O(N²) 체크포인트 저장소
기본 LangGraph 체크포인팅 모델은 매 단계마다 에이전트 상태의 전체 스냅샷을 작성합니다. 작고 수명이 짧은 에이전트의 경우 이는 괜찮습니다. 하지만 messages와 files는 추가 전용 누적기입니다 — 그들은 항상 증가합니다.
전체 스냅샷 체크포인팅에서, 체크포인트 N은 단계 1부터 N까지의 모든 것을 포함합니다:
증가는 체크포인트 레이어 전체에 걸쳐 복합됩니다: 각 단계는 이전보다 더 많은 데이터를 직렬화하고, 더 큰 blob을 체크포인터에 작성하며, 더 많은 메모리를 사용합니다. 당신은 직렬화 시간, 쓰기 증폭, 그리고 중복 저장소로 비용을 지불하고 있습니다.
솔루션: 델타 채널
Channels는 그래프 상태의 "필드"를 나타내는 데 사용되는 LangGraph 프리미티브입니다. 다양한 채널 타입은 데이터가 체크포인트를 통해 어떻게 전달되는지를 제어합니다.
DeltaChannel은 새로운 LangGraph 채널 타입(1.2 기준 베타)으로, 누적 필드에 대한 체크포인트 표현을 변경합니다.
정상적인 단계에서, DeltaChannel은 그 단계에서 추가된 새로운 업데이트만 작성하며, 작은 델타입니다.
전체 스냅샷은 매 snapshot_frequency=K 단계마다 작성됩니다(기본값: deepagents의 경우 50). 이는 재개시 상태를 재구성하는 비용을 제한합니다: 세션 시작 이후의 모든 델타 쓰기를 재생하는 대신, 런타임은 가장 가까운 스냅샷으로 돌아가기만 하면 됩니다 — 최대 K단계. 정기적인 스냅샷이 없으면, 매우 긴 세션은 매우 느린 재개를 의미할 것입니다.
기본 증가는 여전히 이차입니다(스냅샷이 매 K단계마다 발생하기 때문), 하지만 계수는 기본선의 ~1/K입니다. O(N) 델타 항이 실제 세션 길이에서 지배적이며, 재구성 비용이 K로 제한되기 때문에 재개 지연은 평평합니다. 저장소 이득은 효과적으로 무료입니다.
표준 스냅샷팅 방식과 델타 방식의 나란한 비교입니다:
벤치마크 결과
DeltaChannel은 LangGraph 프리미티브이지만, 그것을 동기부여한 작업량과 여기서 벤치마킹하고 있는 것은 Deep Agents 코딩 세션입니다. 긴 메시지 히스토리와 파일시스템 지원 컨텍스트 오프로드는 O(N²) 체크포인트 증가가 실제 운영 문제가 되는 정확한 상태 형태입니다.
우리는 두 가지 작업량을 실행했습니다:
주기적인 큰 검색 결과는 FilesystemMiddleware의 20k-토큰 제거 임계값을 초과했으며 messages에서 files로 오프로드됩니다.
방법론
모든 벤치마크는 완전히 모의된 작업량을 사용합니다 — 실제 LLM 호출 없음, InMemorySaver, 결정론적 모의 모델, 완전히 재현 가능. 테이블은 총 체크포인터 저장소를 보고합니다: 전체 세션에서 세이버에 누적된 모든 바이트. 토큰 개수는 FilesystemMiddleware가 제거 임계값에 대해 내부적으로 사용하는 total_message_chars / 4 근사를 사용합니다.
설정은 다음과 같았습니다:
```py
checkpointer = InMemorySaver()
agent = create_deep_agent(
model=_MockModel(), # 결정론적 모의, API 호출 없음
tools=[external_search],
checkpointer=checkpointer,
)
for i in range(turns):
agent.invoke({"messages": [HumanMessage(...)]}, config)
```작업량 A: 가벼운 코딩 및 검색
저장소는 처음에는 천천히 증가하다가, 전체 스냅샷 크기가 복합되면서 급격히 가속됩니다. 500번의 턴에서 기본선은 4 GB를 누적했습니다; 델타 채널은 110 MB 미만입니다.
절감 비율은 10번의 턴에서 6배에서 500번의 턴에서 41배로 증가합니다 — 계속 상승하지만, 이론적 ~K배 상한선에 접근하면서 감소합니다. 그 상한선은 고정되어 있지 않습니다: snapshot_frequency는 구성 가능하므로, 당신은 작업량에 따라 재개 지연을 저장소 절감으로 교환할 수 있습니다. 더 높은 K는 세션당 더 적은 전체 쓰기 및 더 높은 저장소 감소를 의미하지만, 재개시 약간 더 많은 델타 재생의 비용으로.
작업량 B: 다중 파일 코딩 세션
더 무거운 턴당 상태는 O(N²) 곡선이 더 빠르게 가팔라짐을 의미합니다. 기본선은 200번의 턴에서 단 5.3 GB에 도달합니다 — 에이전트 작업의 현실적인 오후.
절감 비율은 200번의 턴에서 41배에 도달하고 계속 상승합니다 — 두 작업량은 모두 동일한 ~K배 점근선으로 수렴하지만, 더 무거운 작업량은 더 큰 턴당 쓰기가 이차 계수를 더 공격적으로 증폭하기 때문에 더 빨리 도달합니다.
절감 비율은 각 턴 개수에서 작업량 B에 대해 지속적으로 높습니다. 왜냐하면 더 큰 턴당 상태가 O(N²) 계수를 더 빠르게 증폭하기 때문입니다. 두 작업량은 모두 동일한 점근선(~snapshot_frequency×)으로 수렴하지만, 더 무거운 작업량이 더 빨리 도달합니다.
API
Deep Agents에서
델타 채널은 deepagents v0.6에서 기본적으로 켜져 있습니다. messages와 files는 모두 델타 채널 지원입니다. 설정이 필요하지 않습니다.
LangGraph에서
DeltaChannel은 LangGraph의 첫 번째 클래스 프리미티브로, 모든 상태 필드에 사용할 수 있습니다.
from typing_extensions import Annotated
from langgraph.channels.delta import DeltaChannel
def append(state: list[str], writes: list[list[str]]) -> list[str]:
return state + [item for batch in writes for item in batch]
class MyAgentState(TypedDict):
items: Annotated[list[str], DeltaChannel(reducer=append, snapshot_frequency=50)]두 가지 매개변수:
reducer— 배치 불변이어야 하는 순수 함수(state, list[writes]) -> new_state:reducer(reducer(s, xs), ys) == reducer(s, xs + ys). 아래의 reducer 계약을 참조하세요.snapshot_frequency— 전체 스냅샷을 얼마나 자주 작성할지(기본값: 1000). 더 높은 값은 세션당 더 적은 전체 쓰기를 의미하지만 재개시 더 많은 델타 재생을 의미합니다.deepagents는 50을 사용합니다.
그것이 전체 API 표면 변경입니다. 기존 도구, 인터럽트 처리, 그리고 시간 여행이 모두 계속 작동합니다.
reducer 계약: 폴드 간 결합성
DeltaChannel은 이전 BinaryOperatorAggregate 채널보다 reducer에 더 엄격한 요구사항을 부과합니다. 이것은 자신의 델타 지원 상태를 정의할 때 올바르게 해야 할 유일한 것입니다.
이전 계약
def reducer(existing: T, update: T) -> T: ...새로운 계약
# 배치 폴드 — 모든 누적된 쓰기와 함께 한 번에 호출됨
def reducer(state: T, writes: list[T]) -> T: ...
DeltaChannel은 마지막 로드 이후 누적된 모든 쓰기를 단일 호출로 전달합니다. 재구성된 결과는 해당 쓰기가 어떻게 배치되든 동일해야 합니다:
reducer(reducer(state, [w1, w2]), [w3, w4]) == reducer(state, [w1, w2, w3, w4])이를 배치 불변성이라고 합니다. reducer가 이를 위반하면, 델타 채널 상태는 전체 스냅샷에서 벗어나고, 조용히, 스냅샷 경계를 넘는 세션에만 해당합니다.
사전 델타 스레드에서 마이그레이션
데이터 마이그레이션이 필요하지 않습니다. DeltaChannel.from_checkpoint이 일반 상태 값(_DeltaSnapshot이 아닌)을 만나면, 이를 직접 기본 상태로 사용합니다. 기존 스레드는 계속 작동합니다 — 업그레이드 후 첫 번째 새로운 체크포인트는 그 일반 값 시드의 위에 델타 작성을 시작합니다.
다음은 무엇인가
델타 채널은 deepagents v0.6 및 langgraph v1.2에서 제공됩니다. 업그레이드 경로는 매끄러워야 합니다.
델타 채널과 관련된 이득은 세션이 길어지면서 복합됩니다. 깊은 컨텍스트가 있는 오래 실행되는 에이전트는 이 분야가 향하는 곳이며, 델타 채널은 우리의 런타임이 그들의 요구를 충족하기 위해 어떻게 확장하는지입니다.