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

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.

flowchart LR I["Inputs and injected context"] --> E["Append-only session events"] M["Model stream"] --> E T["Tool calls and results"] --> E E --> H["Derived model history"] E --> U["Trajectory UI"] E --> R["Resume or fork"] E --> O["Telemetry 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:

json
[
  { "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:

  1. Flush before an irreversible boundary. The recorded tool intent must be durable before a top-level tool body can create an outside effect.
  2. Treat flush failure as a stop signal. Do not continue the turn when the evidence needed for recovery was not durably acknowledged.
  3. 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.

sequenceDiagram participant L as Session log participant D as Derived history participant A as Agent instance participant X as External system L->>D: project ordered model-visible events D->>A: resume or fork context A->>X: propose a new action X-->>A: independently reconciled outcome A->>L: append new event evidence

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:

  1. The agent proposes create_ticket.
  2. The tool sends a request to an external service.
  3. The network fails before the result returns.
  4. 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.

  1. Define the event before the UI. Specify whether the fact is durable, model-visible, replayable, and safe to expose.
  2. Keep payloads JSON-safe and versioned. The session model validates event data as JSON; define a compatibility policy before storing a new shape.
  3. Separate confidential data from broad visibility. Store a protected reference or redacted summary when full content is unnecessary for replay.
  4. Preserve tool-call/result groups. A result without its call loses essential causal context.
  5. 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.

go
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.