Pi organizes its Session primarily around how a branching history is restored into the current Context. DeepSeek Harness (DSH) takes a stronger persistence boundary: the Session itself is an append-only, typed SessionEvent log, and model messages, UI, Replay, and recovery state are all derived from that same log. [1]
The official documentation defines this log as the single source of truth for the Agent’s entire interaction history. Model messages are not maintained separately as another authoritative state; they are instead projected from the Session Log through deriveMessages(). [1]
This design first solves the consistency problem among multiple sources of state.
When Messages and execution history are persisted separately
A common Agent storage model looks like this:
messages table
runs table
tool_calls table
stream_events table
Each table is reasonable on its own, but a single execution updates several pieces of state at once.
For example, when the model starts responding:
创建 Run
追加 streaming chunks
生成 Assistant Message
写 Tool Call
写 Tool Result
更新 Run 状态
If any one of those writes fails, the system has to determine which state has already been committed. Recovery logic must also decide which table is the more trustworthy source.
DSH first unifies facts of this kind into a single ordered event stream:
turn/start
step/start
user/message
request/header
assistant/chunk
assistant/chunk
assistant/message
tool/call
tool/result
step/end
turn/end
Every event has a monotonically increasing seq. A normal log requires contiguous seq values, so every event occupies a deterministic position in the Session. [1]
With that in place, recovery becomes:
读取 SessionEvent[]
↓
按 seq Replay
↓
重新构造需要的 Projection
There is only one source of truth for the core state.
SessionEvent Records More Than Messages
DSH’s event vocabulary includes several kinds of facts: [1]
This event set shows that DSH’s definition of a Session now extends beyond a Conversation Transcript.
For example, assistant/chunk does not directly enter the next LLM History, but it preserves the raw streaming output trace. turn/start and step/start express execution boundaries, while request/header stores the model configuration, System Prompt, and Tool schemas used for a request.
As a result, a single Event Log can serve multiple consumers:
This is the main value of Event Sourcing in Agent scenarios: Tool activity, Streams, model messages, and run boundaries are inherently time series.
Surface: the intermediate layer between the Event Log and model history
Converting every SessionEvent directly into model messages creates obvious problems.
The model does not need to see:
turn/start
step/end
assistant/chunk
request/context
So DSH defines a Surface over the full Event Log.
Currently, the core allows only three event types into the model-visible Surface: [2]
The deriveEventMessage() logic is equally straightforward:
user/message → UserMessage
assistant/message → AssistantMessage
tool/result → ToolResultMessage
其他事件 → null
Which yields:
Full Session Log
↓
Surface Fold
↓
Surface Nodes
↓
deriveEventMessage()
↓
Model Messages
This layer matters because it lets a Session retain far richer facts than the Context while keeping the Model History structure stable.
surfaceOp Keeps Compaction Append-Only
Ordinary messages enter the Surface using append:
M1 → M2 → M3 → M4
Compaction introduces a special problem. Later, the model needs to see a Summary in place of a stretch of older history, but the Event Log should not delete the original events.
DSH’s Surface supports a replacement operation: [1][2]
Suppose the current Surface contains:
10 15 21 30 35
A new Summary Event can declare:
replace 10..30
The new Surface becomes:
42(summary) 35
Yet the Session Log still retains the original 10, 15, 21, and 30.
Log
只追加事实
Surface
允许改变模型可见顺序和范围
This resembles the idea behind Pi Compaction: both retain the original history and change how the model’s subsequent Context is constructed. DSH takes the principle one step further by formalizing it as a general Surface Operation.
sourceEventSeqs Preserves Derivation Relationships
DSH’s Surface Event can also record sourceEventSeqs. [1]
The clearest example is Streaming:
assistant/chunk seq=20
assistant/chunk seq=21
assistant/chunk seq=22
↓ assemble
assistant/message seq=23
sourceEventSeqs=[20,21,22]
This allows the complete Message and the original Chunks to coexist without losing the source relationship between them.
The field is just as important during a Surface Replace. When a Summary replaces several old Surface Nodes, it must declare which obscured nodes it references. The Surface fold validates these provenance relationships. [2]
This makes the Event Log more than just a place to “store JSON in chronological order”; it also preserves part of the derivation graph:
Raw Events
↓ provenance
Derived Surface Event
For debugging, replaying, and auditing, this is more reliable than storing only the final Message.
Why Record Even the Request Header
DSH’s architecture document imposes a strict requirement: the inputs that reach a model request must be reconstructable from the log. [3]
Storing only Messages is not enough, because a real LLM Request also includes:
provider / model
reasoning config
sampling config
System Prompt
Tool schemas
These details are stored by request/header. [1]
The current EpochHeader roughly includes:
Each Loop instance records the complete Header at startup; whenever the configuration changes, a new Snapshot is appended. During recovery, reading the latest Header reconstructs the request environment. [1]
This solves a commonly overlooked problem:
消息完全相同
+
System Prompt / Tools 不同
=
实际模型语义可能完全不同
If a system promises “exact Replay,” storing Messages alone is not enough.
DSH therefore treats “reconstructable model-visible state” as a runtime invariant of the Session. [3]
This completeness increases the log’s size and schema complexity, but it makes the semantics of Resume, Fork, and Replay clearer.
Persistence Is Only Responsible for Making the Same Log Durable
Event Sourcing can easily introduce another layer of complexity: memory has one set of Events, while the database defines a separate Persistence Event model.
DSH currently deliberately avoids this. The Persistence seam directly persists the existing SessionEvent, with no parallel event model. [4]
After the runtime appends an Event, it synchronously emits a session/event notification; the Persistence Plugin copies the event into a per-session buffer and then writes events to the backend in batches. An Agent Loop producing Events does not have to wait for each disk write. [4]
The flow is roughly:
Session.append(event)
↓
In-memory log committed
↓
session/event
↓
Persistence buffer
↓ batch
JSONL / SQLite
To establish a persistence boundary, session/flush drains the buffer.
The design keeps two concepts clearly separated:
Session
定义事实和顺序
Persistence
决定这些事实如何耐久化
JSONL and SQLite can therefore serve as interchangeable Backends, while the upper-layer Event Model remains unchanged. [4]
Crash Recovery Does Not Delete an Interrupted Turn
An Agent process may crash partway through a Turn.
The disk may already contain:
turn/start
step/start
user/message
assistant/message
tool/call
but not:
tool/result
step/end
turn/end
One recovery strategy is to truncate the last incomplete Turn. DSH currently preserves facts that have already been durably persisted and appends a synthetic event during cold recovery:
turn/end {
reason: interrupted
}
to close the unbalanced Turn. [4]
The recovered log still clearly expresses:
这个 Turn 曾经发生
其中一部分工作已经完成
进程在结束前被中断
This fits the append-only fact model better than deleting the entire Turn.
This matters especially for long-running tasks. A Turn may contain many Steps and a large number of Tool Results, and facts produced earlier must not disappear just because the process crashed at the end.
Why the Session Header Sits Outside the Event Log
DSH still does not turn all data into Events.
The Session’s storage metadata is kept in a separate SessionHeader, for example: [4]
version
id
createdAt
cwd
parentSession
seedLength
origin
delegationDepth
agentPreset
These fields describe the Session itself and its storage lineage; they do not belong to the interaction timeline.
For example:
parentSession
这份 Session 从哪个 Session fork
seedLength
前多少条 Event 属于继承前缀
agentPreset
恢复这个 Session 应使用哪种 Agent 组合
This preserves a boundary worth keeping: the Event Log records “what happened within the session,” while the Header stores “what this session record is.”
Event Sourcing does not require every piece of Metadata to be converted into an event.
DSH’s Fork Is Closer to Session Lineage
Pi can form a tree within a single Session file.
DSH’s Session Log itself remains linear:
seq 0
seq 1
seq 2
...
During a Fork, DSH creates a new Session and uses the stable prefix as the Seed. The Header records the lineage through parentSession and seedLength. [4]
This can be represented as:
Session A
0 1 2 3 4 5
│
└──── fork at prefix
↓
Session B
0 1 2 3 | 4' 5' 6'
So Pi and DSH provide two notable models for Branching:
Pi
一个 Session 内保存 Entry Tree
DSH
每个 Session 保持线性 Event Log
分支形成 Session Lineage
The former is well suited to navigating history frequently within a Coding Session; the latter keeps the Event seq linear and the Replay model simple, at the cost of creating a new Session Identity for each Fork.
It is difficult to determine which is better without looking at product behavior.
The Cost of an Event Log
DSH’s design provides strong reconstruction capabilities, but it also incurs clear costs.
First, the event count is high. Streaming Chunks also enter the log, so a single response may generate a large number of Events.
Second, the Event Schema is a persistence protocol. Once it ships, type changes must account for old logs. The current Persistence implementation strictly validates the format version and any unknown required events, so events that affect reconstruction semantics are never silently ignored. [4]
Third, every Projection must be deterministic. If Surface, Message, UI, and Transcript apply different interpretation rules, the consistency problem will return.
Fourth, an Event Log needs mechanisms such as Compaction or Surface Replace to keep the model Context under control, but the raw log may still continue to grow over time.
Fifth, developers must distinguish between:
事实 Event
实时 Runtime Event
模型 Surface Event
UI-only Projection
DSH’s architecture documentation also explicitly separates Session Events, Agent Events, and capability events. Facts that need to survive a reload enter the Session Event stream; work intercepted during execution uses agent/*. [3]
This boundary is crucial. Keeping every callback as a persistent Event turns the log into a low-level debugging Trace; keeping every execution fact only in an in-memory callback makes recovery impossible.
When This Design Is Worth It
A simple Chat Bot generally has no need to store turn/start, Raw Chunks, or Request Headers.
The benefits of an Event Log become much more apparent when the following needs arise at the same time:
If the system only needs to:
展示聊天历史
把最近消息发送给模型
then messages[] + metadata is still the lower-cost design.
The value DSH provides does not lie in Event Sourcing itself. Rather, it unifies several sources of state in an Agent that would otherwise easily diverge:
SessionEvent Log
↓
Surface
↓
Model Messages
SessionEvent Log
↓
UI / Replay / Transcript
SessionEvent Log
↓
Persistence / Recovery
The whole system then works from the same stream of facts.
In the next article, we’ll scale this model down into a runnable Demo. The Demo won’t replicate every DSH event or implement Pi’s Tree; instead, it will preserve the four most critical mechanisms: append-only Events, contiguous seq values, Message Projection, and Crash Repair. The goal is to answer one question: if we store only an Event Log, can the process deterministically restore the Conversation and the next Model Context after it restarts?
References
[1] DeepSeek Harness Session: https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/session.md
[2] DSH Session Surface: https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/surface.ts
[3] DeepSeek Harness Architecture: https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.zh.md
[4] DeepSeek Harness Persistence: https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/persistence.zh.md
[5] DSH Session implementation: https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts