DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Sessions and Persistence·Chapter 10

Logs, Derived History, Replay, and Forking

From raw chunks to reconstructable model input and UI state

VerifiedUpstream 47f943859bScope: Trace append, deriveMessages, raw chunks, fork boundaries, resume, transcripts, and replay fidelity.

Conclusion: there is no single “Replay,” but six reconstruction contracts that cannot substitute for one another

DeepSeek Harness builds raw streams, semantic messages, the model Surface, physical persistence prefixes, browser windows, and Fork lineage on the same seq-numbered log, but it does not collapse them into one universal replay API. A model request can be reconstructed from the log; a failed attempt remains auditable; a crash can be closed deterministically; a disconnect can converge through history plus the live stream; and a Fork copies a stable prefix rather than pointing at a dynamically changing parent branch.

The most important property is not merely that “chat history is saved.” Different consumers must declare which truth they need. The costs follow from that separation: logical history may be more complete than the physical on-disk log, the UI may deliberately hide a failed attempt, and a published Fork does not imply that parent and child were atomically persisted.

1. Six operations commonly conflated as Replay

ContractInputOutputWhat it does not guarantee
Model Surface replayThe complete event logCurrent deriveMessages()It does not retain old context shadowed by a replacement
Request reconstructionThe log prefix before request dispatchMessages plus the latest complete request headerIt does not reproduce network latency, thrown failures, or hangs
Stream replayassistant/chunkStreaming blocks, usage, finish, and partial UIA failed retry's partial content need not remain visible
Crash recoveryA valid but unclosed durable prefixDeterministic synthetic results, step end, and turn endIt cannot infer the real external outcome of a started side effect
Client convergenceA raw History window, subscribed baseline, and live framesA contiguous browser event window and UI projectionIt is not exact stream-offset resume
ForkA stable completed-Turn prefixA frozen copy with its own Session IDLater parent events never flow into the child
Reading rule

“Replayable from the log” always needs an object. Reconstructing the next Provider request does not replay an external tool side effect; recovering the UI does not mean the UI displays every raw attempt; creating a child does not provide an atomic durability transaction across two Session IDs.

2. Every model attempt records the raw stream before committing a semantic message

1

Read the current model view

Every attempt calls session.deriveMessages() again and combines it with the system prompt, tools, and request configuration already fixed for the Step.

2

Record every chunk

Each item from the Provider iterator is first appended as assistant/chunk, and its seq enters this attempt's chunkSeqs.

3

Assemble canonically

BlockAssembler folds interleaved blocks, deltas, usage, finish, and adapter-private replay state.

4

Decide the attempt's fate

An error or aborted finish enters the request-error waterfall; a retry action returns directly to the loop without appending assistant/message.

5

Commit the successful anchor

Success appends an append-origin assistant/message whose sourceEventSeqs cite every chunk in this attempt.

Production control flow

Raw observation precedes semantic commit. A failed attempt therefore leaves chunk evidence without contaminating the model Surface. The successful message carries provider, model, optional replay state, and usage before the loop decides whether to execute tool calls.LOG-ATTEMPT-ASSEMBLY

Test-source contract

A fixture producing abc requires all seven protocol chunks to remain in order and be reassembled. Dependencies are absent, so this study reviewed but did not execute the test.LOG-RAW-CHUNK-TEST

3. Retry preserves both audit truth and current user-visible truth

After a failed attemptRaw logModel SurfaceBrowser assistant node
Chunks already receivedAll remain, including the terminal failure chunkThey do not enter because there is no final assistant messagellm/retry clears and hides the partial blocks
First-token timeEvent timestamps remainIrrelevantPreserved across retry because the user really waited for and may have seen a response
Successful attemptAppends its own chunks and final messageProjects only the final semantic messageThe final message replaces partial stream state and settles
Client Definition

The Assistant Definition folds chunks under Turn and Step identity. A retry reset never deletes log events; it only resets presentation state. If the Step or Turn closes with visible chunk evidence but no final message, it can create a view-only interrupted node.LOG-CLIENT-ASSISTANT

Assessment

The user does not see two conflicting answers after an automatic retry, while an auditor can still inspect the failed attempt. These are two intentional projections of one fact source, not lost UI data.

4. “Every request is reconstructable” requires both the Surface and a complete Header

Request at dispatch boundary
= fresh Session(log prefix).deriveMessages()
+ foldRequestHeader(log prefix)
   ├─ provider / model / reasoning effort
   ├─ system prompt
   ├─ ordered tool schemas
   └─ temperature / maxTokens / stop
Theorem-style test

The test runs multiple Steps, a tool call, and prompt and configuration changes, then finds the first response chunk for each dispatch. It creates a fresh Session from the events before that chunk and requires its messages to deep-equal the real request; it folds the latest request header and compares model, reasoning, system, tools, temperature, maxTokens, and stop field by field.LOG-REQUEST-RECONSTRUCTION

Exactness comes from two complementary logs: the Surface fold determines Message history, while the last-wins fold over complete header snapshots determines non-message request parameters. Raw chunks prove what the Provider returned; they do not participate in reconstructing the input of their own request.

5. An empty completion can still be a durable success anchor

When a Provider stops normally without visible content, or max-token truncation makes the assembler discard an unsafe partial tool call, the Agent still appends an assistant/message with empty content and cites that attempt's chunks. Chapter 09 established that an empty assistant does not enter deriveMessages(). Here its purpose is to record that a request completed successfully, together with its usage and replay boundary, rather than adding an empty model turn.

Why it exists

If only a raw terminal chunk remained, a generic consumer would need to understand every adapter stream protocol to recognize the success boundary. One uniform semantic anchor lets request replay, usage, and Turn completion remain Provider-neutral while keeping the next request's Message list clean.LOG-ATTEMPT-ASSEMBLYSESSION-SURFACE-PROJECTION

6. Persistence exposes four distinct read faces

APICold source behaviorCommits recovery?Publishes a live Session?Main use
prepare(id)Read, repair, validate, and reserve the exact Session objectYesThe caller later publishes or rolls backResume ownership transaction
load(id)Return a balanced logical inspectionYesNoReaders needing a durably recovered view
inspect(id)Return a balanced logical inspectionNoNoCold history, query, and Fork sources
readFrom(id, seq)Read the stored physical suffix directlyNo, and adds no closersNoIncremental storage, replication, and raw inspection
Production semantics

The four paths share per-session serialization and format validation but deliberately choose different recovery and ownership semantics. With a seek-capable backend, readFrom reads only the suffix; otherwise it reads the complete prefix and slices it, without borrowing the prepared cache.LOG-PERSISTENCE-READ-FACES

For a live Session, inspect may return an in-memory snapshot whose Turn is still open. load flushes first and refuses to package an open live Turn as a “complete durable inspection.” It is not enough to ask whether a function returns events; its promises about logical balance and ownership matter.

7. Crash repair never invents success; it deterministically closes unknown state

Crash-tail factSynthetic resultGuidance to the later model
The assistant requested a tool but no tool/call existsTOOL_NOT_STARTED error result with no source seqRetry if the operation is still needed
A tool/call exists but no tool/result doesTOOL_OUTCOME_UNKNOWN citing the call-event seqCheck external state or ask first; retry only read-only or idempotent work
The Step remains openAdd step/endRestore structural invariants without claiming completion
The Turn remains openAdd turn/end {kind:'interrupted'}Expose an explicit interruption to the next Turn
Deterministic algorithm

The scanner resets pending calls at each Turn boundary and repairs only the final open Turn. Synthetic seqs continue after the last real seq, timestamps reuse the last real event, and Map insertion order preserves call order. A balanced or empty log produces no events.LOG-REPAIR-CLOSERS

Torn-write contract

A backend may return a valid committed prefix plus a torn marker. Recovery truncates the incomplete physical fragment, retains every committed open-Turn fact, and appends closers instead of rolling back the whole Turn.LOG-TORN-TAIL-CONTRACT

8. Cold history can be logically complete while disk is still physically open

Both are true

An inspect() result uses storedEvents + synthetic closers but does not call commitRepair. Only load() or prepare() commits recovery after the revision still matches. Contract tests explicitly require the backend revision to remain unchanged across inspect and to change after load.LOG-REPAIR-CONTRACTLOG-PREPARED-COMMIT

physical artifact:  turn/start → tool/call ───────────────┐
                                                         │ inspect only
logical history:   turn/start → tool/call → unknown result → step/end → interrupted turn/end

load / prepare:    compare revision → commit repair → reload exact committed graph

A cold browser History can therefore show a safely closed interruption while readFrom or the raw artifact still exposes an open physical tail. The former answers, “What is this conversation logically under the recovery rules?” The latter answers, “What bytes and events did the backend actually store?” Forcing them into one view would remove diagnostic information.

9. Write-behind separates hot-path commit from disk durability

Queue contract

The persistence listener clones each committed event into per-Session write-behind. A fixed deadline forms a stable batch. If a write fails, the batch returns to the front in original order and automatic retry pauses, avoiding a tight background loop. Explicit flush() cancels the timer, joins a shared barrier, and retries and drains until both the active write and pending queue are quiescent.LOG-WRITE-BEHIND

Semantic checkpoints

The standard policy flushes before the model stream is actually constructed, before a top-level tool body, and before every pre-step. Failure prevents dispatch of the adapter or side-effect body; nested tool calls reuse the already durable outer call.LOG-CHECKPOINT-POLICY

BoundaryFacts that must already be durableAmbiguity prevented
Before a Provider requestUser input, request header, Step start, and dynamic contextThe model was called but disk cannot reconstruct its input
Before a top-level tool bodyAssistant tool call and tool/callA side effect occurred but the log does not even record its start
Before the next StepThe previous Step's response and tool resultsThe next request depends on history that is not durable

10. Core Fork is an inclusive prefix copy from a live source

Core primitive

SessionStore.fork accepts only the exact live instance currently in the store. An omitted boundary means the final event; an explicit value must be an existing contiguous seq, and the inclusive slice may not end inside an open Turn. The child header inherits cwd and records parentSession plus the exact copied-prefix seedLength.LOG-CORE-FORK

  • An empty source can produce an empty child with lineage.
  • If the source now has an open Turn, an older stable boundary before that tail remains selectable.
  • Title events, plugin markers, and other log-only events after a Turn can enter the prefix.
  • Turn brackets are the hard stability boundary; a plugin bracket crossing the prefix can be inherited as seed, with the new end-seed marker separating ownership.
  • The seed is detached, snapshotted, and frozen; parent and child do not share a mutable event array.
Test-source matrix

The tests cover empty forks, frozen seeds, an earlier cut before an open parent tail, every Turn-end reason, trailing log-only events, and rejection of missing or stale instances, invalid boundaries, open Turns, and duplicate child IDs.LOG-FORK-CONTRACT

11. Host Fork lifts a message anchor into “include the whole Turn” product semantics

Input caseSelected completed boundary
atSeq lies in a completed TurnThe first turn/end.seq ≥ atSeq, so a message's Fork button does not clip the latter half of its Turn
atSeq lies in the current open TurnReject; never silently fall back to an earlier Turn
atSeq is omitted or beyond the log tailThe last completed Turn
Standalone events follow the boundaryInclude them through the event before the next turn/start

The Host can read attached live state or cold inspect state without resuming an Agent. It copies the prefix, inherits cwd, lineage, seedLength, and the source's current composition, creates the new Agent, and then attempts to inherit the Workspace. If Workspace attachment fails, the response explicitly carries the already created child ID; the child is not implicitly rolled back because peripheral grouping failed.LOG-HOST-FORKLOG-HISTORY-CONTRACT

Semantics

This is a snapshot copy, not a Git-like shared object database or an event-branch pointer. Parent and child each own a complete prefix beginning at seq zero, then append independently; only the Header links their lineage.

12. A clear crash window separates Fork publication from cross-Session durability

High-confidence source inference; no fault injection performed

The Host does not flush a live parent before reading it. After child creation, the session/created listener starts seed persistence through asynchronous initFor(). Parent and child writes serialize only on their respective Session-ID chains; no cross-ID transaction exists. A successful “forked” response therefore proves that the child was published in the live registry, not that the parent prefix and child seed are atomically durable together.LOG-PERSISTENCE-ADMISSIONLOG-HOST-FORK

This does not corrupt child content during a healthy process: the child already copied the complete in-memory prefix. The risk exists only when the process crashes or a backend write fails at the relevant moment. The child may not yet be materialized, or the child may be stored while the corresponding parent tail remains in write-behind. Explicit flush is the authoritative durability primitive, and the Fork API does not run a two-sided barrier before returning.

13. History pagination preserves a contiguous raw range instead of returning Message DTOs

Pagination algorithm

The Host counts append-origin user and assistant messages backward from the window tail. Model-only replacements consume no maxMessages quota. An assistant message's sourceEventSeqs moves the cut to the earliest chunk in that attempt, so a page never begins halfway through a stream. The result remains a contiguous raw event range containing all event types.LOG-HISTORY-PAGINATION

The tail page naturally contains partial chunks that do not yet have a final message; older pages are cut only by beforeSeq. Each entry may carry a tool view that the Host computes from the current presenter registry, but that view is never persisted and is not part of the event envelope. If call arguments live on an older page, the current-page backscan can miss them and intentionally fall back to generic presentation; the fact itself remains intact.LOG-HISTORY-VIEW

One-moment response

After all asynchronous source and composition preparation, the Host synchronously copies events and reads the projection watermark so one History response cannot combine events at N with a baseline at N+1. A cold source uses logical inspect, and the entire read never publishes an Agent.LOG-HISTORY-CUTLOG-HISTORY-CONTRACT

14. The browser converges through “fetch tail + buffer live + repair by seq”

subscribe starts ────────────────┐
                               ├─ liveBuffer
history tail request ──────────┘
            ↓ install raw window
drop seq overlap → append contiguous live tail
            ↓
if next.seq > tail.seq + 1: buffer + refetch tail + restitch
Session client

Open first fetches History. If session/subscribed.lastSeq is already beyond the page tail, it immediately fetches again. After installing the window, it stitches frames received in the meantime by seq: overlap is dropped, while a hole is buffered and triggers a tail repair instead of being appended directly.LOG-CLIENT-STITCH

Incremental UI engine

ConversationNodeAssembler rebuilds Contexts on full replacement, matches only one tail event on live append, and preserves existing Context and View identity while locally replaying dependencies when older pages prepend. Business Definitions decide how assistant, tool, Turn, and other events become visible nodes.LOG-CONVERSATION-ASSEMBLER

Reconnect therefore does not mean “continue from a server stream offset.” It establishes a durable raw-History baseline, then uses seq to deduplicate and repair live frames. Seq simultaneously provides ordering, deduplication, gap detection, and UI correlation.

15. Query trace and raw export answer different audit questions

CapabilityWhat it doesEvidence boundary
Event recordsAfter a canonical fold, label every event current, shadowed, or log-onlyStatus belongs to this complete-log observation
Event traceReturn the direct replacement chain, removed nodes, direct sources, and later direct citersExcept for replacement chains, it does not compute full transitive provenance
Session traceBuild ancestors and recursive descendants from Header parent linksA missing parent returns complete:false rather than inventing a root
Raw ZIP exportAfter flushing live logs, export backend artifacts byte-for-byte with optional descendants and referenced mediaIt never rewrites UI views or the current Surface into the artifact

Query is an explainable computation over the logical event graph; Export preserves physical evidence. The former answers why a node was shadowed and where it came from, while the latter lets independent tooling inspect the original files again.LOG-QUERY-TRACELOG-RAW-EXPORT

16. Confirmed boundaries, gaps, and scale risks

Gap 1: History requests have no upper bound

The wire schema requires maxMessages only to be a positive integer and sets no maximum. Pagination also first copies or filters the complete event log, while one message group can itself contain many raw chunks. A caller can therefore request a very large page, and one huge attempt can make a response much larger than its “message count” suggests. This is a source-visible resource boundary; practical exploitability still depends on upstream log and transport limits.LOG-HISTORY-SCHEMALOG-HISTORY-PAGINATION

Gap 2: no timed active retry follows one failed gap repair

The client catch path only logs the error and clears stitching; buffered live events remain. If no later frame triggers the gap again and no reconnect or resync occurs, they can remain invisible indefinitely. A later relevant event or reconnect converges again, but the current implementation has no backoff timer.LOG-CLIENT-STITCH

Gap 3: Fork has no cross-ID durability transaction

This is the crash-window inference from the prior section. If product language treats the response as “permanently created,” it needs another parent-and-child flush or backend transaction. If it promises only live child admission, the API documentation should state that boundary clearly.

  • Intentional tradeoff: failed-retry content is hidden in the UI and retained in the raw log.
  • Intentional tradeoff: a tool render view is a current-code projection and may use a generic fallback when call arguments sit on another page.
  • Intentional tradeoff: logical inspection can expose deterministic recovery without forcing a read-only page to mutate disk.
  • Unknown: this study did not execute fault injection, oversized-History, or real disconnect experiments; source deductions must not be presented as reproduced incidents.

17. Design assessment and verification status

ChoiceBenefitCost
Record raw attempts and semantic messages separatelyFailures remain auditable without contaminating model contextConsumers must know which layer is current truth
Repair expresses only unknown and interrupted statesIt never guesses that a side effect succeeded or failedThe recovered model still has to inspect the external world
Separate logical inspection from physical readsCold reads stay side-effect-free while presenting balanced UI“Visible in history” does not always mean “committed to disk”
Fork copies the complete prefixThe child is self-contained, independently recoverable, and exportableStorage is duplicated and cross-ID atomicity requires another mechanism
History convergence instead of stream-offset resumeDisconnects, overlap, and missing frames return to durable truthPages may be fetched again, and repair retry policy remains incomplete
Chapter verification scope

This chapter cross-checked production control flow in the Agent loop, repair, SessionStore Fork, Host History and Fork, persistence coordinator and write-behind, client Session and Conversation assembler, query, and export. It also reviewed request-reconstruction, Fork, repair, torn-tail, and persistence-contract test source. Upstream dependencies are not installed, so it does not claim those tests passed locally. The next chapter dissects backends, database schemas, and data ownership.

My Learning Notes

Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.