TL;DR
DeepSeek Harness models an agent session as an append-only stream of typed events. The model-visible conversation is derived from that stream, allowing the runtime to reconstruct a transcript, inspect a trajectory, resume or fork a session, and feed UI or telemetry consumers from the same evidence. In the dsh-v0.2.1-alpha.1 source reviewed here, live notifications can be buffered, session/flush is the durability barrier, and released formats migrate through immutable generations. Replay still is not a transaction log for outside systems.
Table of Contents
- Why sessions need an event model
- What DeepSeek Harness records
- Durability is a flush contract
- Trajectory replay fork and resume
- The boundary between a trace and a side-effect ledger
- Designing safe session extensions
- Fail closed across format versions
- Evaluation and operations
- FAQ
- Related resources
Why sessions need an event model
An agent run is not only a chat transcript. It may include a user request, injected repository instructions, model messages, tool calls, tool results, subagent activity, cancellation, and settings that changed what the model could see. A mutable text history loses the distinction between these facts and makes later diagnosis ambiguous.
DeepSeek Harness documents a session as an append-only log of typed SessionEvent values. The LLM message history is derived from the log rather than stored as a second mutable source. This creates a single ordered evidence stream for reconstruction, UI updates, telemetry, persistence, and evaluation.
The important invariant is narrower than “log everything.” The DSH architecture says model-visible inputs must be reconstructable from the session log. That makes context assembly inspectable. It does not imply that every internal implementation detail or sensitive secret should be exposed to every operator.
What DeepSeek Harness records
The documented event vocabulary includes turn and step boundaries, user messages, assistant stream chunks, assembled assistant messages, tool calls, tool results, todo snapshots, and request metadata. A step is one model request plus the tools called in that request; a turn can contain multiple steps.
| Event family | What it establishes | What it does not establish |
|---|---|---|
user/message |
A model-visible user or injected input entered the session | That the input was trustworthy or authorized |
request/header |
Effective request configuration, system prompt, and tool schemas | That the policy was correct for a tenant |
assistant/message |
The assembled model output, plus reported usage when available | Hidden internal model reasoning |
tool/call |
The model requested a named tool with raw arguments | That dispatch was authorized or completed |
tool/result |
The model-facing result and optional error identity | That an outside write is reconciled forever |
turn/* and step/* |
Execution boundaries | A business transaction boundary |
Here is a simplified log projection. It is illustrative JSON, not a promise that every DSH version emits this exact payload:
[
{ "seq": 18, "type": "turn/start", "data": { "turn": 4 } },
{
"seq": 19,
"type": "user/message",
"data": { "role": "user", "content": [{ "type": "text", "text": "Run tests" }] }
},
{
"seq": 20,
"type": "tool/call",
"data": { "turn": 4, "step": 7, "callId": "call_123", "name": "bash", "arguments": "{\"command\":\"pnpm test\"}" }
},
{
"seq": 21,
"type": "tool/result",
"data": { "turn": 4, "step": 7, "message": { "role": "tool", "content": [] } }
}
]
For actual event shapes, use the pinned project's generated session catalog or TypeScript types. Do not build a security or persistence integration around a blog snippet.
Durability Is a Flush Contract
An in-memory Session.append() and a completed durable write are different facts. The current JSONL persistence path routes each published session's synchronous session/event notifications into a bounded write-behind window. The first pending event opens a fixed batching interval; later events join it without extending the deadline.
session/flush cancels that wait, drains buffered events to quiescence, invokes the backend flush, and surfaces a write failure to its caller. A failed background drain keeps its events in order and pauses automatic writes until an explicit flush, handle close, or backend shutdown retries. A clean close drains before releasing ownership.
This yields three useful rules:
- Flush before an irreversible boundary. The recorded tool intent must be durable before a top-level tool body can create an outside effect.
- Treat flush failure as a stop signal. Do not continue the turn when the evidence needed for recovery was not durably acknowledged.
- Do not confuse batching latency with missing order. The backend may batch writes, but successful reads still return a contiguous committed prefix, and one backend instance permits one active writer per session.
The exact window and storage implementation are product details. The portable idea is that a recovery claim needs an explicit durability acknowledgement, not merely an emitted event.
Trajectory replay fork and resume
DeepSeek Harness uses the same event stream for trajectory inspection, resume, fork, and replay. This design lets a team ask concrete questions: which instructions entered a request, which tool schema was visible, which tool result the model received, and where a run diverged.
The terms are easy to confuse:
| Operation | Safe interpretation |
|---|---|
| Resume | Create or continue a runtime from retained session state, subject to current policy and compatibility checks |
| Fork | Seed a child session from a source session or boundary so a different path can be explored |
| Replay | Re-derive state, history, presentation, or evaluation inputs from recorded events |
| Re-execution | Invoke tools or external systems again; this is a new side effect and requires fresh authorization |
A replay of a tool result can reproduce what the model saw. It cannot prove whether an external system accepted a write before a timeout. The Agent Trajectory concept is therefore distinct from a business audit ledger.
The boundary between a trace and a side-effect ledger
An event-sourced session log is valuable evidence, but its durability and ordering do not automatically make it an exactly-once transaction coordinator. The difficult case is an uncertain external outcome:
- The agent proposes
create_ticket. - The tool sends a request to an external service.
- The network fails before the result returns.
- The external service may have created the ticket.
Blind replay can create a duplicate. A safe design needs an application-owned effect ledger:
| Record | Purpose |
|---|---|
| Stable operation ID | Lets the downstream system deduplicate or look up the request |
| Intent and authorization snapshot | Binds actor, resource, parameters, policy, and expiry |
| Dispatch attempt | Records when and where a request was sent |
| Reconciliation result | Establishes whether the downstream side effect committed |
| Compensation or escalation state | Handles irreversible or unknown outcomes |
The session may reference the operation ID and final outcome, but it should not silently treat a replayed tool/result as proof of completion. See Agent Harness architecture for the separation between checkpoints and external effect reconciliation.
Designing safe session extensions
When a plugin adds a new model-visible input, DeepSeek Harness requires a corresponding session event so future reconstruction is complete. That requirement has practical implications.
- Define the event before the UI. Specify whether the fact is durable, model-visible, replayable, and safe to expose.
- Keep payloads JSON-safe and versioned. The session model validates event data as JSON; define a compatibility policy before storing a new shape.
- Separate confidential data from broad visibility. Store a protected reference or redacted summary when full content is unnecessary for replay.
- Preserve tool-call/result groups. A result without its call loses essential causal context.
- Fail closed on unknown required events. A reader that silently skips an event that changes reconstruction can produce a false history.
The last item is especially important during upgrades. The official session contract allows an unknown event only when its envelope explicitly marks it ignorable. An unknown required event, malformed current record, noncontiguous sequence, or unsupported format must stop restoration; a partial replay that looks successful is worse than a visible compatibility error.
Fail Closed Across Format Versions
Session format and package version are independent. The official status record separately identifies the checkout writer, the accepted compatibility baseline, and the latest published format. Alpha, beta, and release-candidate artifacts still create obligations for persisted user data.
When a breaking change is required, DSH creates an adjacent migration and writes a new generation-named artifact. The predecessor remains byte-preserved; migration does not overwrite or delete the only historical copy. A newer reader may migrate a supported older generation, while an older reader must reject a newer one with a direction-aware error.
The following Go program demonstrates the minimum validation boundary for a reconstructed event prefix. It is not a DSH decoder; it shows the checks a storage adapter must complete before projection.
package main
import (
"errors"
"fmt"
)
type Event struct {
Seq int
Type string
Ignorable bool
}
var supported = map[string]bool{
"turn/start": true,
"user/message": true,
"assistant/message": true,
"tool/call": true,
"tool/result": true,
"turn/end": true,
}
func validatePrefix(events []Event, storedFormat, currentFormat int) error {
if storedFormat > currentFormat {
return errors.New("newer session format requires a newer reader")
}
if storedFormat < currentFormat {
return errors.New("historical generation must be migrated before replay")
}
for index, event := range events {
if event.Seq != index {
return fmt.Errorf("noncontiguous sequence at index %d", index)
}
if !supported[event.Type] && !event.Ignorable {
return fmt.Errorf("unknown required event %q", event.Type)
}
}
return nil
}
func main() {
valid := []Event{
{Seq: 0, Type: "turn/start"},
{Seq: 1, Type: "user/message"},
{Seq: 2, Type: "telemetry/note", Ignorable: true},
{Seq: 3, Type: "turn/end"},
}
requiredUnknown := append(
append([]Event(nil), valid[:2]...),
Event{Seq: 2, Type: "policy/decision"},
)
fmt.Printf("valid=%t\n", validatePrefix(valid, 4, 4) == nil)
fmt.Printf(
"required_unknown_rejected=%t\n",
validatePrefix(requiredUnknown, 4, 4) != nil,
)
fmt.Printf("newer_rejected=%t\n", validatePrefix(valid, 5, 4) != nil)
}
Keep test fixtures from every shipped generation. For each upgrade, prove either a lossless adjacent migration into the current logical form or an explicit refusal that leaves the original artifact intact.
Evaluation and operations
Trajectory data supports evaluation when the suite defines what counts as success. It can measure grounded tool selection, denied-action behavior, parameter validation, budget use, recovery decisions, and outcome quality. It cannot establish truth merely because a trace is long or a model explanation is fluent.
Use a test matrix that contains successful, rejected, interrupted, delayed, malformed, and adversarial cases:
| Scenario | Evidence to inspect |
|---|---|
| Read-only task | Ordered context, tool calls, and final answer |
| Denied write | Policy decision before dispatch and no external request |
| Tool timeout | Operation ID, reconciliation query, and no blind retry |
| Context injection | Logged source and request boundary where it became visible |
| Flush failure | Turn stops and the buffered prefix remains available for retry |
| Version upgrade | Migration preserves history or refuses without mutating the source generation |
Retention also needs an explicit policy. Sessions can include source code, user text, file paths, tool arguments, results, and system prompts. Limit access, encrypt the right storage boundary, redact where possible, set retention periods, and test deletion. Observability without data governance creates a new sensitive-data system.
FAQ
Is a DSH session the same as a chat history?
No. A chat history is usually a rendered conversation. A DSH session is a typed event stream from which model history and UI presentation are derived. It can include boundaries, tool activity, request metadata, and other facts that a chat transcript omits.
Can I replay a trajectory after a model or plugin upgrade?
You can attempt to re-derive it only if the event vocabulary, format migration, model adapter, and projections remain compatible. Pin versions and test old session fixtures. Required unknown events should cause an explicit compatibility failure rather than silent omission.
Does logging assistant chunks expose hidden chain of thought?
Do not assume it does or should. Record and expose only the event types and model fields your provider and policy permit. Evaluate actions and outcomes from observable evidence; do not rely on hidden reasoning as a production debugging interface.
How should I store session logs?
Choose a persistence backend with access control, encryption, retention, deletion, backup, single-writer ownership, flush semantics, and format migration appropriate to the session's sensitivity. The event log is a runtime source of truth, but compliance and tenancy rules are application responsibilities.
Does a fork inherit authorization?
It should not silently inherit an approval for a different resource, parameter set, or time. A fork can reuse context for exploration, but consequential actions need current identity, policy, and authorization checks.
Related resources
- DeepSeek Harness Session subsystem at the reviewed revision
- Session persistence and flush semantics
- Session format and release status
- DeepSeek Harness architecture documentation
- DeepSeek Harness: Cordis plugins and agent architecture
- DeepSeek Harness plugin development: services events and lifecycle
- Agent Observability Engineering
- Context Compression