AI·News
뒤로

델타 채널: 장시간 실행 에이전트를 위한 런타임 진화

Delta Channels: How We’re Evolving our Runtime for Long-Running Agents

Deep Agents는 LangGraph 런타임 위에 구축되어 있으며, 매 단계마다 에이전트의 진행 상황을 체크포인트합니다. 이것이 관찰성, 인간-루프-내, 그리고 장애 복구를 가능하게 합니다: 당신은 항상 에이전트가 어디에 있는지 정확히 알 수 있고, 어떤 지점에서든 재개할 수 있습니다.

에이전트가 더 능력이 강해질수록:

  1. 그들은 더 오래 실행되며, 메시지 히스토리가 수십 개 또는 수백 개의 단계를 거쳐 증가합니다
  2. 그들은 더 많은 컨텍스트를 사용하며, 컨텍스트 관리 및 오프로딩을 위해 파일시스템을 활용합니다

Deep Agents의 경우, 메시지 히스토리와 파일은 에이전트 상태에 있으며, 매 단계마다 스냅샷을 하는 방식으로, 체크포인트 저장소는 O(N²)로 증가합니다. 200번의 턴을 실행하는 코딩 에이전트의 경우, 현재 체크포인팅 방법은 체크포인터에 5.3GB를 직렬화합니다. 델타 채널은 이를 129 MB로 줄이고, 40배 이상의 감소로, 상태 재수화에서 실제로 성능 저하가 거의 없습니다.

델타 채널은 우리가 런타임을 진화시키는 방법입니다. DeltaChannellanggraph 1.2의 새로운 프리미티브로, 누적 상태 필드가 어떻게 체크포인트되는지 변경합니다. 매 단계마다 전체 스냅샷을 직렬화하는 대신, 각 단계는 diff만 저장합니다. 전체 스냅샷은 주기적으로 작성되어 복구 비용을 제한합니다. Deep Agents의 경우, messagesfiles에 대한 델타 기반 저장소를 의미합니다. 당신은 여전히 에이전트 진행의 완전한 히스토리를 얻을 수 있으며, 단지 비용의 일부만 들게 됩니다.

LangGraph의 체크포인트 저장소는 긴 메시지 히스토리가 있는 에이전트의 경우 O(N²)로 증가합니다. 200번의 턴을 실행하는 코딩 에이전트의 경우, 이는 5.3 GB입니다. 델타 채널은 이를 129 MB로 줄입니다 — 41배 감소, 무료로.

문제: O(N²) 체크포인트 저장소

기본 LangGraph 체크포인팅 모델은 매 단계마다 에이전트 상태의 전체 스냅샷을 작성합니다. 작고 수명이 짧은 에이전트의 경우 이는 괜찮습니다. 하지만 messagesfiles추가 전용 누적기입니다 — 그들은 항상 증가합니다.

전체 스냅샷 체크포인팅에서, 체크포인트 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²) 체크포인트 증가가 실제 운영 문제가 되는 정확한 상태 형태입니다.

우리는 두 가지 작업량을 실행했습니다:

작업량 A 작업량 B
시나리오 가벼운 코딩 / 검색 에이전트 다중 파일 기능 구현
파일 쓰기 / 턴 1 × 1 KB 2 × 8 KB
검색 결과 / 턴 1 × 1 KB 1 × 5 KB
큰 검색 결과 10턴마다 82 KB 5턴마다 100KB
AI 응답 / 턴 최소 ~200 토큰

주기적인 큰 검색 결과는 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에서 기본적으로 켜져 있습니다. messagesfiles는 모두 델타 채널 지원입니다. 설정이 필요하지 않습니다.

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.6langgraph v1.2에서 제공됩니다. 업그레이드 경로는 매끄러워야 합니다.

델타 채널과 관련된 이득은 세션이 길어지면서 복합됩니다. 깊은 컨텍스트가 있는 오래 실행되는 에이전트는 이 분야가 향하는 곳이며, 델타 채널은 우리의 런타임이 그들의 요구를 충족하기 위해 어떻게 확장하는지입니다.

Deep Agents is built on the LangGraph runtime, which checkpoints agent progress at every step. That's what makes observability, human-in-the-loop, and failure recovery possible: you always know exactly where an agent is and can resume from any point.

As agents get more capable:

  1. They run longer, with message histories that grow across dozens or hundreds of steps
  2. They use more context, utilizing the filesystem for context management and offloading

For Deep Agents, message history and files live in agent state, and with a snapshot-every-step approach, checkpoint storage grows at O(N²). For a coding agent running 200 turns, current checkpointing methods serialize 5.3GB to the checkpointer. Delta channels bring it to 129 MB, over a 40x reduction, with practically no performance drop in state rehydration.

Delta channels are how we're evolving the runtime to keep up. DeltaChannel is a new primitive in langgraph 1.2 that changes how accumulating state fields are checkpointed. Rather than serializing a full snapshot at every step, each step stores only the diff. Full snapshots are written periodically to bound recovery cost. For Deep Agents, that means delta-based storage for messages and files. You still get a complete history of agent progress, just at a fraction of the cost.

Checkpoint storage in LangGraph grows at O(N²) for agents with long message histories. For a coding agent running 200 turns, that's 5.3 GB. Delta channels bring it to 129 MB — a 41× reduction, for free.

The problem: O(N²) checkpoint storage

The default LangGraph checkpointing model writes a full snapshot of agent state at every step. For small, short-lived agents this is fine. But messages and files are append-only accumulators — they only ever grow.

Under full-snapshot checkpointing, checkpoint N contains everything from steps 1 through N:

The growth compounds across the checkpoint layer: each step serializes more data than the last, writes a larger blob to the checkpointer, and consumes more memory to hold it. You're paying in serialization time, write amplification, and redundant storage.

The solution: Delta Channels

Channels are the LangGraph primitive used to represent a “field” in graph state. Different channel types control how data is passed through checkpoints.

DeltaChannel is a new LangGraph channel type (in beta as of 1.2) that changes the checkpoint representation for accumulating fields.

On a normal step, a DeltaChannel writes only the new updates added that step, a tiny delta.

Full snapshots are written every snapshot_frequency=K steps (default: 50 for deepagents). This bounds the cost of reconstructing state on resume: rather than replaying every delta write since the beginning of the session, the runtime only needs to walk back to the nearest snapshot — at most K steps. Without periodic snapshots, a very long session would mean very slow resumes.

The underlying growth is still quadratic (because snapshots happen every K steps), but the coefficient is ~1/K of the baseline. The O(N) delta term dominates at practical session lengths, and because reconstruction cost is bounded by K, resume latency stays flat. The storage win is effectively free.

Here’s a side by side comparison of the standard snapshotting approach vs the delta approach:

Benchmark Results

DeltaChannel is a LangGraph primitive, but the workload that motivated it, and the one we're benchmarking here, is a Deep Agents coding session. Long message histories and filesystem-backed context offload are exactly the state shape where O(N²) checkpoint growth becomes a real operational problem.

We ran two workloads:

Workload A Workload B
Scenario light coding / search agent multi-file feature implementation
File writes / turn 1 × 1 KB 2 × 8 KB
Search result / turn 1 × 1 KB 1 × 5 KB
Large search result 82 KB every 10 turns 100KB every 5 turns
AI response / turn minimal ~200 tokens

The periodic large search results exceeded FilesystemMiddleware's 20k-token eviction threshold and gets offloaded from messages to files.

Methodology

All benchmarks use fully mocked workloads — no real LLM calls, InMemorySaver, deterministic mock model, fully reproducible. Tables report total checkpointer storage: all bytes accumulated in the saver across the entire session. Token counts use the total_message_chars / 4 approximation that FilesystemMiddleware uses internally for its eviction threshold.

The setup looked like the following:

```py

checkpointer = InMemorySaver()
agent = create_deep_agent(    
	model=_MockModel(),   # deterministic mock, no API calls    
	tools=[external_search],    
	checkpointer=checkpointer,
)
for i in range(turns):    
	agent.invoke({"messages": [HumanMessage(...)]}, config)
```

Workload A: light coding and search

Storage grows slowly at first, then accelerates sharply as the full snapshot size compounds. At 500 turns the baseline has accumulated 4 GB; delta channels stay under 110 MB.

The savings ratio grows from 6× at 10 turns to 41× at 500 — still climbing, but decelerating as it approaches the theoretical ~K× ceiling. That ceiling isn't fixed: snapshot_frequency is configurable, so you can trade resume latency for storage savings based on your workload. A higher K means fewer full writes per session and a higher storage reduction, at the cost of slightly more delta replay on resume.

Workload B: multi file coding session

Heavier per-turn state means the O(N²) curve steepens faster. The baseline hits 5.3 GB at just 200 turns — a realistic afternoon of agent work.

The savings ratio reaches 41× at 200 turns and is still climbing — both workloads converge toward the same ~K× asymptote, but the heavier workload gets there faster because larger per-turn writes amplify the quadratic coefficient more aggressively.

The savings ratio is consistently higher for Workload B at each turn count because larger per-turn state amplifies the O(N²) coefficient faster. Both workloads converge toward the same asymptote (~snapshot_frequency×), but the heavier workload gets there sooner.

The API

In Deep Agents

Delta channels are on by default in deepagents v0.6 Both messages and files are delta-channel-backed. No configuration required.

In LangGraph

DeltaChannel is a first class primitive in LangGraph that you can use for any state field.

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)]

Two parameters:

  • reducer — a pure function (state, list[writes]) -> new_state that must be batching-invariant: reducer(reducer(s, xs), ys) == reducer(s, xs + ys). See the reducer contract below.
  • snapshot_frequency — how often to write a full snapshot (default: 1000). Higher values mean fewer full writes per session but more delta replay on resume. deepagents uses 50.

That's the entire API surface change. Existing tools, interrupt handling, and time-travel all continue to work.

The reducer contract: associativity across folds

DeltaChannel imposes a stricter requirement on the reducer than the old BinaryOperatorAggregate channel. This is the one thing to get right when defining your own delta-backed state.

The old contract

def reducer(existing: T, update: T) -> T: ...

The new contract

# Batch fold — called with ALL accumulated writes at once
def reducer(state: T, writes: list[T]) -> T: ...


DeltaChannel passes all writes accumulated since the last load in a single call. The reconstructed result must be identical regardless of how those writes are batched:

reducer(reducer(state, [w1, w2]), [w3, w4]) == reducer(state, [w1, w2, w3, w4])

This is called batching-invariance. If your reducer violates it, delta channel state will diverge from a full snapshot, silently, and only for sessions that span a snapshot boundary.

Migration from pre-delta threads

No data migration required. When DeltaChannel.from_checkpoint encounters a plain state value (not a _DeltaSnapshot), it uses it directly as the base state. Existing threads continue to work — the first new checkpoint after the upgrade begins writing deltas on top of that plain-value seed.

What’s next

Delta channels ship in deepagents v0.6 and langgraph v1.2. The upgrade path should be seamless.

The gains associated with delta channels compound as sessions get longer. Long-running agents with deep context are where the field is heading, and delta channels are how our runtime scales to meet their needs.