From 5997ac62924f20ea46351437067e357e5053f1dd Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Thu, 20 Aug 2026 15:03:08 -0400 Subject: [PATCH] chore(docs): ground agent research in DeepSeek Harness evidence Signed-off-by: Yordis Prieto --- docs/research/README.md | 1 + docs/research/acp/RESEARCH_PROMPT.md | 6 +- docs/research/acp/decision-record.md | 6 +- docs/research/acp/index.md | 20 +- .../research/acp/products/deepseek-harness.md | 179 +++ docs/research/acp/synthesis.md | 28 + .../agent-instructions/harness-survey.md | 51 +- docs/research/agent-instructions/index.md | 33 +- docs/research/agent-platform/index.md | 1 + .../products/deepseek-harness.md | 293 +++++ docs/research/agent-platform/synthesis.md | 42 + docs/research/session-store/index.md | 3 + .../products/deepseek-harness/index.md | 1017 +++++++++++++++++ .../deepseek-harness/vs-session-events.md | 388 +++++++ docs/research/session-store/synthesis.md | 11 + 15 files changed, 2048 insertions(+), 31 deletions(-) create mode 100644 docs/research/acp/products/deepseek-harness.md create mode 100644 docs/research/agent-platform/products/deepseek-harness.md create mode 100644 docs/research/session-store/products/deepseek-harness/index.md create mode 100644 docs/research/session-store/products/deepseek-harness/vs-session-events.md diff --git a/docs/research/README.md b/docs/research/README.md index 76c8fe6cc3..42f9a895b6 100644 --- a/docs/research/README.md +++ b/docs/research/README.md @@ -14,6 +14,7 @@ Repos we lean on most for research. One deduplicated list across all corpora. - [block/goose](https://github.com/block/goose) - [block/buzz](https://github.com/block/buzz) - [xai-org/grok-build](https://github.com/xai-org/grok-build) +- [deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) - [NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent) - [nearai/ironclaw](https://github.com/nearai/ironclaw) - [anomalyco/opencode](https://github.com/anomalyco/opencode) diff --git a/docs/research/acp/RESEARCH_PROMPT.md b/docs/research/acp/RESEARCH_PROMPT.md index bc9ffd0476..9dba4b6be4 100644 --- a/docs/research/acp/RESEARCH_PROMPT.md +++ b/docs/research/acp/RESEARCH_PROMPT.md @@ -1,7 +1,7 @@ # Research Prompt: Agent Client Protocol (ACP) Reusable prompt for the ACP study. Output goes into `docs/research/acp/`, -following the corpus structure in [index.md](./index.md): fifteen product +following the corpus structure in [index.md](./index.md): the Tier 1 product dossiers under `products/`, a cross-cutting [synthesis](./synthesis.md), a [decision record](./decision-record.md), and a set of component-level deep dives (crate inventory, tier 2 client profiles, host role and invocation, @@ -121,6 +121,10 @@ mapping, what this platform can copy or must avoid). client boundary. - **Devin**: remote/autonomous agent with ACP support; how a cloud-hosted agent maps onto a protocol designed around local subprocesses. +- **DeepSeek Harness**: native automation-only ACP agent plus an in-repository + ACP subagent client. Study both sides of the boundary, including which side + owns the child process, how machine permission policy replaces interactive + UI, and whether the published surface is directly hostable. - **NetClaw**: full case study on how it handles channels and the agent boundary, ACP or not. - **Cline**: VS Code-native agent with ACP support; how an extension-first diff --git a/docs/research/acp/decision-record.md b/docs/research/acp/decision-record.md index b3cc495b5f..b7df0783d0 100644 --- a/docs/research/acp/decision-record.md +++ b/docs/research/acp/decision-record.md @@ -118,6 +118,6 @@ Four component-level designs support the `acp-host` build: lifecycle TTLs, a `media://` resource-link scheme resolved to fresh presigned URLs, and an inline-base64 guardrail for small payloads. -Full supporting evidence, including the callability matrix across all -fifteen products studied, is in [synthesis.md](./synthesis.md) and the -[product dossiers](./index.md#product-dossiers). +Full supporting evidence, including the original callability matrix and later +product additions, is in [synthesis.md](./synthesis.md) and the [product +dossiers](./index.md#product-dossiers). diff --git a/docs/research/acp/index.md b/docs/research/acp/index.md index cde4d1446c..ee545ec776 100644 --- a/docs/research/acp/index.md +++ b/docs/research/acp/index.md @@ -3,11 +3,12 @@ The Agent Client Protocol (ACP) is Zed's open protocol for the client-to-agent seat: an editor, IDE, gateway, or any other host drives a coding agent through a standardized JSON-RPC surface, turning the N x M client/agent integration -problem into N + M. This corpus is the frozen research input behind the -platform's ACP-related decisions: an industry study of ACP's protocol -contract, ecosystem adoption, and product integrations, plus the roadmap -analysis that followed it. Where a conclusion here differs from an accepted -record in the [ADR index](../../adr/index.md) or from the current spec +problem into N + M. This corpus preserves the frozen research input behind the +platform's ACP-related decisions and clearly marked later evidence: an +industry study of ACP's protocol contract, ecosystem adoption, and product +integrations, plus the roadmap analysis that followed it. Where a conclusion +here differs from an accepted record in the [ADR index](../../adr/index.md) or +from the current spec position in [ACP Conformance](../../architecture/acp-conformance.md), the ADR or the conformance document is authoritative. @@ -19,15 +20,18 @@ dive remain reproducible. ## Product dossiers -Fifteen products were studied for how they actually leverage ACP: native -implementation vs. adapter, process lifecycle ownership, headless -invocation and auth, and channel mapping where relevant. +The original study covered fifteen products. Later product dossiers are +marked as post-synthesis evidence rather than rewritten into the frozen +decision-time input. Every dossier examines native implementation vs. +adapter, process lifecycle ownership, headless invocation and auth, and +channel mapping where relevant. - [Buzz](./products/buzz.md) - [Claude Code](./products/claude-code.md) - [Cline](./products/cline.md) - [Codex CLI](./products/codex-cli.md) - [Cursor](./products/cursor.md) +- [DeepSeek Harness](./products/deepseek-harness.md) - [Devin](./products/devin.md) - [Gemini CLI](./products/gemini-cli.md) - [Goose](./products/goose.md) diff --git a/docs/research/acp/products/deepseek-harness.md b/docs/research/acp/products/deepseek-harness.md new file mode 100644 index 0000000000..6cd48b7d9a --- /dev/null +++ b/docs/research/acp/products/deepseek-harness.md @@ -0,0 +1,179 @@ +# DeepSeek Harness + +Post-synthesis product case study. Evidence was retrieved 2026-08-20 from +DeepSeek Harness release `dsh-v0.1.0-rc.8`, commit +`141eb6fef83422698aef7a981029e843e8161534`. Every upstream source link below +is pinned to that commit. The TrogonAI callability check used repository commit +`c8e05872b9a3b156b974d9773b9723f07493cb1d`. + +## ACP status and version + +DeepSeek Harness has a native ACP agent implementation. The +`@deepseek-ai/dsh-acp` package describes itself as an automation-only JSON-RPC +stdio server and is version `0.1.0-rc.8`; it pins +`@agentclientprotocol/sdk` `0.25.1` +([packages/acp/acp/package.json:2-4](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/package.json#L2-L4), +[packages/acp/acp/package.json:34-36](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/package.json#L34-L36)). +That SDK defines `PROTOCOL_VERSION` as wire version 1 +([typescript-sdk/src/schema/index.ts:310](https://github.com/agentclientprotocol/typescript-sdk/blob/cd8dc79b94a9d131687a2cdd02298820c32f5880/src/schema/index.ts#L310)), +and the server returns that constant during `initialize`. The wire-reported +agent identity is independently hard-coded as `deepseek-harness-acp` version +`0.0.1`, so it must not be mistaken for the package release version +([packages/acp/acp/src/index.ts:290-301](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/src/index.ts#L290-L301)). + +This is deliberately not an editor integration. Its supported surface is for +programmatic clients, while navigation, transcript replay, commands, modes, +elicitation, reasoning, plans, titles, and tool presentation remain outside +the ACP package +([packages/acp/acp/README.md:5-7](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L5-L7)). + +## Capabilities and session lifecycle + +| Surface | Behavior at the pinned release | +|---|---| +| Transport | Newline-delimited JSON-RPC over stdin/stdout. Stdout is reserved for protocol frames. | +| Prompts | Text and resource-link text are supported. Inline raster images are advertised only when a durable attachment store and the exact provider/model route support them. Audio and embedded context are false. | +| Agent capabilities | No load-session, editor, terminal, filesystem, or MCP capability is advertised. | +| Authentication | `authMethods` is empty and `authenticate` is a no-op. Provider credentials are a process-launch concern, not an ACP authentication exchange. | +| Session creation | `session/new` creates a fresh harness agent and requires an absolute `cwd`. Non-empty `additionalDirectories` or `mcpServers` are rejected. | +| Prompt concurrency | One prompt may be in flight per session. The response waits for admission, whole-agent idle, and ordered committed output delivery. | +| Output | Only committed assistant text and images become `agent_message_chunk` updates. Reasoning, tool activity, plans, and live deltas remain in the harness session log. | +| Cancellation | `session/cancel` aborts prompt admission or cancels the addressed agent after admission. Unknown session ids are no-ops. | + +The capability and lifecycle rows follow the package's method-by-method +contract +([packages/acp/acp/README.md:20-34](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L20-L34)). +One connection may own several independent sessions, but the implementation is +fresh-session only: load, list, resume, delete, fork, and per-session close are +not implemented. Disconnect or plugin disposal cancels and drains all agents +owned by that connection before releasing them +([packages/acp/acp/README.md:36-40](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L36-L40), +[packages/acp/acp/README.md:76-81](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L76-L81)). + +## Permissions and trust boundary + +DeepSeek Harness self-serves its tools inside the child process instead of +requesting client filesystem or terminal services. When a bridge-owned tool +approval has a tool-call id, the ACP agent sends one `allow_once` and one +`reject_once` option. A cancelled response fails closed, and no durable grant +is inferred +([packages/acp/acp/src/index.ts:268-285](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/src/index.ts#L268-L285)). +The runnable example selects `workspace-write` or `danger-full-access` through +`DSH_PERMISSION_MODE`; under `workspace-write`, the client decides each wider +retry and the server does not expose a picker or persist client policy +([examples/acp-agent/README.md:20-24](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/examples/acp-agent/README.md#L20-L24)). + +This boundary is narrower than the common editor-host pattern. A compatible +client may advertise no optional capabilities because the child owns its +filesystem, terminal, model route, and tools. The host still owns the final +permission answer and the OS process lifetime. + +## Process ownership, invocation, and provider authentication + +The ACP package attaches an `AgentSideConnection` directly to the harness +process's stdin and stdout +([packages/acp/acp/src/index.ts:443-448](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/src/index.ts#L443-L448)). +Therefore the ACP client host, not the agent package, must spawn and supervise +that process. The pinned release documents this exact source-checkout +invocation: + +```sh +DEEPSEEK_API_KEY=... pnpm --dir /path/to/deepseek-harness run demo:acp +``` + +The script boots the repository's composed ACP example, and its stdout carries +only protocol frames +([package.json:141-145](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/package.json#L141-L145), +[examples/acp-agent/README.md:5-16](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/examples/acp-agent/README.md#L5-L16)). +`DEEPSEEK_API_KEY` authenticates the harness to the model provider. It does not +authenticate the ACP peer. The ACP package exports library entry points and +declares no executable, while the tagged documentation gives the composed +repository demo as its runnable surface +([packages/acp/acp/package.json:13-31](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/package.json#L13-L31), +[packages/acp/acp/README.md:42-44](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L42-L44)). +A production host would therefore need a packaged profile or an equivalent +composition rather than treating the package export itself as a binary. + +## Relationship to the ACP subagent provider + +The same repository also implements the client side in +`@deepseek-ai/dsh-subagent-acp`. This is not another server mode. It is an +out-of-process subagent provider that spawns one fresh ACP child for each run, +then performs `initialize`, `session/new`, and `session/prompt`. It derives the +child cwd from the parent session unless explicitly overridden and owns the +child's cancellation, stdin closure, termination escalation, and whole-tree +exit proof +([packages/subagent/subagent-acp/README.md:5-17](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-acp/README.md#L5-L17)). + +The provider advertises no optional client capabilities, auto-answers +permission requests according to its `allow` or `reject` policy, collects only +committed `agent_message_chunk` text, and gives the remote child a fresh context +with no parent conversation +([packages/subagent/subagent-acp/README.md:19-34](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-acp/README.md#L19-L34), +[packages/subagent/subagent-acp/README.md:64-84](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-acp/README.md#L64-L84)). +It is the first-party reference client for the automation server, and it proves +the intended process boundary. It does not make the TrogonAI repository capable +of hosting DeepSeek Harness because that TypeScript provider is not wired into +TrogonAI's Rust/NATS runtime. + +## Channel mapping + +No messaging or editor channel is mapped through this ACP surface. The package +explicitly leaves interactive presentation and human questions to separate Web +host and client modules. Its in-repository consumer is machine-to-machine +subagent delegation, so this is evidence for ACP as an internal execution +boundary rather than ACP as a channel gateway +([packages/acp/acp/README.md:5-7](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/acp/acp/README.md#L5-L7)). + +## Callability from TrogonAI today + +**Verdict: wire-compatible target, not callable end to end at the checked +commit.** Both sides use ACP wire v1, and DeepSeek Harness's required core +method subset is representable by TrogonAI's pinned Rust SDK. No static +protocol-version blocker is visible. The blocker is process hosting: + +- `acp-nats-stdio` accepts ACP from a client on its own stdin/stdout and + forwards it to a NATS-backed agent. It occupies the agent-facing direction, + not the client host direction (`rsworkspace/crates/acp/acp-nats-stdio/README.md:1-18`, + `rsworkspace/crates/acp/acp-nats-stdio/src/main.rs:38-41,83-89`). +- The ACP crate family contains no child-process spawn call at the checked + commit. The current decision record likewise identifies the missing + component as a client host that spawns an ACP CLI, speaks the client role, + and bridges the session onto NATS + (`docs/research/acp/decision-record.md:33-49`). +- Provider credentials must be supplied at process launch, and the exact + upstream runnable is the repository demo command above. An eventual host + must also accept a self-served agent that advertises no client filesystem or + terminal callbacks and must send empty `mcpServers` and + `additionalDirectories`. + +The minimum integration is therefore the planned `acp-host` process boundary, +a deployable DeepSeek Harness ACP composition, an injected +`DEEPSEEK_API_KEY`, an absolute session cwd, and an explicit permission policy. +Until that host exists, documenting the command does not make the product +callable from TrogonAI. + +## Design lessons + +- Copy the explicit automation profile: a small negotiated surface, committed + output only, one in-flight prompt per session, and connection-scoped teardown + make lifecycle ownership auditable. +- Support agents that self-serve filesystem and terminal operations. A host + must not require every ACP agent to call client-owned fs or terminal methods. +- Keep ACP peer authentication distinct from provider credentials. Empty ACP + `authMethods` does not remove the need for controlled credential injection at + process launch. +- Preserve the policy decision in the host. The child can request a one-shot + permission, but a headless client still needs a fail-closed rule for choosing + or rejecting it. + +## Source manifest + +- DeepSeek Harness release `dsh-v0.1.0-rc.8`, commit + [`141eb6fef83422698aef7a981029e843e8161534`](https://github.com/deepseek-ai/deepseek-harness/tree/141eb6fef83422698aef7a981029e843e8161534), + retrieved 2026-08-20. +- ACP TypeScript SDK `0.25.1`, package git commit + [`cd8dc79b94a9d131687a2cdd02298820c32f5880`](https://github.com/agentclientprotocol/typescript-sdk/tree/cd8dc79b94a9d131687a2cdd02298820c32f5880), + retrieved 2026-08-20. +- TrogonAI repository commit + `c8e05872b9a3b156b974d9773b9723f07493cb1d`, inspected locally 2026-08-20. diff --git a/docs/research/acp/synthesis.md b/docs/research/acp/synthesis.md index f5382eb45f..9cef1bd645 100644 --- a/docs/research/acp/synthesis.md +++ b/docs/research/acp/synthesis.md @@ -163,3 +163,31 @@ products speak ACP; the one crossover to watch is ACP's proxy-chains RFD orchestration inside the client-agent seat but remains proposal-stage. Full analysis, including the seat-by-seat comparison table, in [ACP vs A2A](./acp-vs-a2a.md). + +## Post-synthesis evidence: DeepSeek Harness (2026-08-20) + +This section was added after the original fifteen-product synthesis and does +not retroactively change its frozen decision-time claims. The +[DeepSeek Harness dossier](./products/deepseek-harness.md) uses release +`dsh-v0.1.0-rc.8` at pinned commit +`141eb6fef83422698aef7a981029e843e8161534`. + +DeepSeek Harness adds a particularly clear two-sided ACP example. Its native +automation server speaks wire v1 over JSON-RPC stdio, creates fresh sessions, +streams only committed assistant messages, exposes one-shot permission +decisions, and intentionally advertises no filesystem, terminal, MCP, session +load, or interactive UI capability. Its separate `dsh-subagent-acp` provider +plays the client-host role: one fresh child per run, explicit +`initialize`/`session/new`/`session/prompt`, machine permission policy, and +owned cancellation and process teardown. + +The product is not callable from TrogonAI at the checked commit. Protocol +version is not the blocker: both sides use wire v1 and the required core method +subset overlaps. The missing boundary is still the planned client-side +`acp-host` that spawns and supervises the child and connects its stdio to the +NATS session surface. The exact upstream source-checkout invocation is +`DEEPSEEK_API_KEY=... pnpm --dir /path/to/deepseek-harness run demo:acp`; +upstream documents a composed repository demo rather than a standalone +`dsh acp` executable. This evidence strengthens the original host-role +decision while adding an important requirement: the host must support agents +that self-serve tools and advertise no client filesystem or terminal callbacks. diff --git a/docs/research/agent-instructions/harness-survey.md b/docs/research/agent-instructions/harness-survey.md index df75649d19..fb9fb22ad3 100644 --- a/docs/research/agent-instructions/harness-survey.md +++ b/docs/research/agent-instructions/harness-survey.md @@ -1,10 +1,13 @@ # Harness survey: standing instruction inputs Part of the agent instructions research corpus. -Evidence retrieved 2026-07-30 from official documentation and product -source code. Documentation is mutable; anchors name the pages and source -paths checked. The question asked of every product: what can an operator -pass in as standing agent instructions, and what is its exact shape? +Evidence for the original survey was retrieved 2026-07-30 from official +documentation and product source code. The DeepSeek Harness addition was +retrieved 2026-08-20 from release `dsh-v0.1.0-rc.8` at pinned commit +`141eb6fef83422698aef7a981029e843e8161534`. Documentation is mutable; +anchors name the pages and source paths checked. The question asked of every +product: what can an operator pass in as standing agent instructions, and what +is its exact shape? ## Summary table @@ -14,6 +17,7 @@ pass in as standing agent instructions, and what is its exact shape? | Claude Code CLI | CLAUDE.md family, `.claude/rules/*.md`, flags | markdown | | Anthropic Messages API | `system` | string or text blocks | | Codex CLI / cloud | AGENTS.md, `config.toml` keys | markdown / string | +| DeepSeek Harness | `$DSH_HOME/AGENTS.md`, project candidates and local overlays | markdown | | OpenAI Agents SDK | `instructions` | string or callable | | Cursor | `.cursor/rules/*.mdc`, User/Team Rules, AGENTS.md | markdown | | Grok Build (xAI) | AGENTS.md, CLAUDE.md, `.grok/rules/*.md` | markdown | @@ -107,6 +111,45 @@ Source-verified against `openai/codex` (`codex-rs/core/src/agents_md.rs`, versioned application code. Anchor: [prompt object migration](https://developers.openai.com/api/docs/guides/prompting/migrate-from-prompt-object). +## DeepSeek Harness + +Source-verified against DeepSeek Harness `dsh-v0.1.0-rc.8`, commit +`141eb6fef83422698aef7a981029e843e8161534`. + +- First-party support is implemented by + `@deepseek-ai/dsh-agent-instructions`. The default baseline loads the fixed + user-global `$DSH_HOME/AGENTS.md`, then every existing project candidate from + the discovered project root through the session cwd. The defaults are + `AGENTS.md` and `CLAUDE.md`, followed in each directory by + `AGENTS.local.md` and `CLAUDE.local.md`; the project root marker defaults to + `.git` + ([packages/context/agent-instructions/src/config.ts:11-14](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/src/config.ts#L11-L14), + [packages/context/agent-instructions/src/config.ts:39-46](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/src/config.ts#L39-L46), + [packages/context/agent-instructions/src/files.ts:267-308](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/src/files.ts#L267-L308)). +- Scope and precedence are additive, broad to specific. User-global content is + first, then root-to-cwd directories; base candidates precede local overlays + in each directory. Distinct files all remain visible, while same-directory + files whose whitespace-trimmed content is identical collapse to the earliest + configured candidate. The rendered reminder tells the model that more + specific instructions take precedence and that workspace files do not + override system, developer, or direct user instructions + ([packages/context/agent-instructions/README.md:9-13](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/README.md#L9-L13), + [packages/context/agent-instructions/src/render.ts:10-18](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/src/render.ts#L10-L18), + [packages/context/agent-instructions/src/render.ts:85-98](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/src/render.ts#L85-L98)). +- Binding is per session and durable. The complete baseline joins the first + eligible request as a user-role message. Nested scopes below the session cwd + bind only after a successful first-party `read`, `write`, or `edit` reaches + them. Later edits and removals append replacement or removal messages; there + is no filesystem watcher, and shell `cd` does not trigger discovery + ([packages/context/agent-instructions/README.md:7-17](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/README.md#L7-L17), + [packages/context/agent-instructions/README.md:49-55](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/README.md#L49-L55)). +- Resume retains a compatible visible baseline. A change to discovery, + precedence, project root, or budget identity appends one complete superseding + baseline. `maxBytes` is required, each source defaults to a 1 MiB cap, and + rendering preserves the most specific files first by dropping broader files + before truncating the most-specific file + ([packages/context/agent-instructions/README.md:49-78](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/context/agent-instructions/README.md#L49-L78)). + ## Cursor - Project Rules: `.cursor/rules/*.mdc`, a list of discrete rule files. diff --git a/docs/research/agent-instructions/index.md b/docs/research/agent-instructions/index.md index b332f1d368..2872c3613c 100644 --- a/docs/research/agent-instructions/index.md +++ b/docs/research/agent-instructions/index.md @@ -1,29 +1,32 @@ # Agent instructions research corpus -This corpus is the frozen research input behind the platform's decision on -how agent instructions are owned, shaped, and carried on the wire. It -surveys what shipping harnesses accept as standing agent instructions and -how prompt-management products model prompt content, as of 2026-07-30. -Where a conclusion here differs from an accepted record in the -[ADR index](../../adr/index.md), the ADR is authoritative. +This corpus preserves the frozen research input behind the platform's decision +on how agent instructions are owned, shaped, and carried on the wire, together +with clearly marked later evidence. The original study surveyed what shipping +harnesses accept as standing agent instructions and how prompt-management +products model prompt content as of 2026-07-30. Where a conclusion here differs +from an accepted record in the [ADR index](../../adr/index.md), the ADR is +authoritative. The decision this corpus feeds is [ADR#0043: Agent Instructions Ownership and Shape](../../adr/0043-agent-instructions-ownership-and-shape.md). ## Method -Findings were gathered from official documentation and, where available, -product source code, retrieved 2026-07-30. Documentation is mutable, so -each document names the pages, repositories, and source paths checked -rather than implying a fixed product version. Facts that could not be -confirmed against a primary source are marked unverified. +The original findings were gathered from official documentation and, where +available, product source code retrieved 2026-07-30. The DeepSeek Harness +addition is post-decision evidence retrieved 2026-08-20 and is labeled with its +pinned release in the survey. Documentation is mutable, so each document names +the pages, repositories, and source paths checked rather than implying a fixed +product version. Facts that could not be confirmed against a primary source +are marked unverified. ## Documents -- [Harness survey](./harness-survey.md): what Claude, Codex, Cursor, Grok, - Gemini CLI, OpenCode, Amp, Cline, Goose, Devin, and Aider accept as - standing instruction input, and the five shapes the ecosystem reduces - to. +- [Harness survey](./harness-survey.md): what Claude, Codex, DeepSeek Harness, + Cursor, Grok, Gemini CLI, OpenCode, Amp, Cline, Goose, Devin, and Aider + accept as standing instruction input, and the five shapes the ecosystem + reduces to. - [Prompt management](./prompt-management.md): how Langfuse, LangChain, and the provider APIs model prompt content, and why prompt management needs shapes that agent definitions do not. diff --git a/docs/research/agent-platform/index.md b/docs/research/agent-platform/index.md index 6d037f26fa..62ce4b7dc8 100644 --- a/docs/research/agent-platform/index.md +++ b/docs/research/agent-platform/index.md @@ -20,6 +20,7 @@ and evidence rules behind each product dossier remain reproducible. - [Claude Managed Agents](./products/claude-managed-agents.md) - [Cloudflare Agents](./products/cloudflare-agents.md) - [CrewAI](./products/crewai.md) +- [DeepSeek Harness](./products/deepseek-harness.md) - [Devin](./products/devin.md) - [Hermes Agent](./products/hermes-agent.md) - [IronClaw (NEAR AI)](./products/ironclaw.md) diff --git a/docs/research/agent-platform/products/deepseek-harness.md b/docs/research/agent-platform/products/deepseek-harness.md new file mode 100644 index 0000000000..f3f7d6a5b0 --- /dev/null +++ b/docs/research/agent-platform/products/deepseek-harness.md @@ -0,0 +1,293 @@ +--- +title: "DeepSeek Harness: what 'agent' means" +source_urls: + - https://github.com/deepseek-ai/deepseek-harness + - https://github.com/deepseek-ai/deepseek-harness/tree/141eb6fef83422698aef7a981029e843e8161534 + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/architecture.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/README.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/preset/agent-presets/README.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/system-prompt/README.md + - https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md +retrieved: 2026-08-20 +status: done +--- + +# DeepSeek Harness: what "agent" means + +Part of [Agent platform research corpus](../index.md). +Produced by running [RESEARCH_PROMPT](../RESEARCH_PROMPT.md). + +All repository evidence is fixed to Git commit +[`141eb6fef83422698aef7a981029e843e8161534`](https://github.com/deepseek-ai/deepseek-harness/tree/141eb6fef83422698aef7a981029e843e8161534), +which tag +[`dsh-v0.1.0-rc.8`](https://github.com/deepseek-ai/deepseek-harness/tree/dsh-v0.1.0-rc.8) +resolved to at retrieval. The commit is the auditable source snapshot for every +quote and code claim below. + +## The `agent` noun (primary-source quotes) + +The project-level definition is: + +> "DeepSeek Harness (`dsh`) is an open-source agent harness developed by +> DeepSeek AI." + +Source: [`README.md:5`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/README.md#L5). + +The core interface package describes its own noun as: + +> "Agent interface, registry, process-local initiator scope, and `agent/*` +> event vocabulary." + +Source: [`packages/core/agent/README.md:5`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md#L5). + +The type contract makes the identity rule explicit: + +> "The single identity shared with session." + +Source: [`packages/core/agent/src/runtime-types.ts:63-70`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/runtime-types.ts#L63-L70). + +The durable half is defined separately: + +> "A `Session` is the append-only source of truth for an agent's whole +> interaction history" + +Source: [`packages/core/session/README.md:5`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/README.md#L5). + +Operationally, `Agent` is a live process-local handle. It holds the current +status, inbox, options, scoped plugin context, and one live `Session`. The +registry tracks only live agents, and insertion enforces +`agent.id === agent.session.id`; lookup is by that shared `SessionId` +([`packages/core/agent/README.md:9-24`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md#L9-L24)). +The durable object is the Session log. Resume loads that log, creates a fresh +unpublished Agent scope, runs setup again, and publishes a replacement live +Agent under the persisted Session ID +([`packages/core/agent/README.md:37-45`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md#L37-L45)). + +There is no independent Agent record, Agent CRUD surface, or Agent-definition +version. A declarative `agents[].id` is a boot configuration label. Unless the +configuration supplies an exact `sessionId`, startup mints a fresh combined +Session ID; a stable `sessionId` can restore or create history when persistence +is present +([`packages/core/agent-loop/src/index.ts:254-310`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/src/index.ts#L254-L310), +[`packages/core/agent-loop/src/index.ts:355-380`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/src/index.ts#L355-L380)). + +Conceptual model: **agent-as-live-session-bound-handle**. The Session is the +persistent identity and history; the Agent is the currently resident driver +and scoped capability surface for exactly that Session. + +## Subagents + +Subagents are a provider-backed runtime seam, not methods on the core Agent +interface. A parent dynamically starts work through one of several named +providers, which may create a local child, use another process, or use another +transport +([`packages/subagent/subagent/README.md:5-29`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L5-L29)). +Provider registration is declarative composition, but each child identity is +created at runtime. + +The release has two child lifecycles: + +- A **one-shot** local child is an ordinary Agent and Session created for one + delegated task. The run returns the child's shared Session ID, and the run + holder must dispose it after the result settles. A remote provider can + return a lifecycle ID without any local Agent or Session + ([`packages/subagent/subagent/README.md:64-70`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L64-L70)). +- A **continuable** child has one durable Session and at most one process-local + Activation, meaning one residency epoch for a reconstructed Agent. Later + messages either reach the resident Agent or cold-resume a new Activation + from the same Session + ([`packages/subagent/subagent/README.md:72-78`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L72-L78)). + +For local children, the parent preset composition is joined first, then the +child persona and tool filter narrow or shadow it. The child inherits the +parent provider, model, output-token cap, working directory, and durable +lineage unless explicitly overridden. It does **not** inherit the parent's +runtime tool restrictions or authority as a capability set +([`packages/subagent/subagent/README.md:35-58`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L35-L58), +[`packages/subagent/subagent-in-process-driver/README.md:9-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-in-process-driver/README.md#L9-L23)). +Delegation separately snapshots the parent's explicit sandbox override and +pins child approval policy to `never`, so unattended children cannot request +an escalation +([`packages/subagent/subagent/README.md:60-62`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L60-L62)). + +Context inheritance depends on the provider: + +- `spawn` creates an empty child conversation while inheriting model and + workspace defaults + ([`packages/subagent/subagent-spawn-in-process/README.md:5-15`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-spawn-in-process/README.md#L5-L15)). +- `fork` seeds only the balanced prefix ending at the parent's last completed + turn. It excludes the in-flight turn and does not create live context + sharing + ([`packages/subagent/subagent-fork-in-process/README.md:5-19`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-fork-in-process/README.md#L5-L19), + [`packages/subagent/subagent-fork-in-process/README.md:58-61`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-fork-in-process/README.md#L58-L61)). + +Nesting is supported. Delegation depth is persisted in the child Session +header and cannot be lowered by fresh runtime options after resume. The +model-facing delegation tool defaults `maxDepth` to `3`; deployments can set +another non-negative limit or defer the limit to the provider +([`packages/subagent/subagent/src/depth.ts:11-35`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/depth.ts#L11-L35), +[`packages/subagent/tool-subagent/src/index.ts:69-99`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/tool-subagent/src/index.ts#L69-L99)). +No global fan-out limit is documented. + +One-shot communication is final-result only: the parent receives the child's +final output or stop reason, not intermediate work +([`packages/subagent/subagent-spawn-in-process/README.md:39-51`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent-spawn-in-process/README.md#L39-L51)). +Continuable children instead support parent follow-ups, child-selected reports, +interrupts, and a runtime-authored settlement notice. This is an explicit +message channel, not shared conversation state +([`packages/subagent/subagent/README.md:18-27`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L18-L27)). + +## Configuration surface (what, where, why) + +DeepSeek Harness distributes configuration across plugin-owned surfaces rather +than treating Agent as one definition object. + +| Concern | Surface and binding | Product rationale | +| --- | --- | --- | +| Deployment capabilities | A profile in Harness home stacks bundles and patch files into a plugin tree at boot. The base bundle supplies model adapters, tools, persistence, sandbox and approval policy, settings, credentials, and telemetry. | Every product part remains replaceable and higher layers can patch lower ones. See [`docs/architecture.md:11-35`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/architecture.md#L11-L35). | +| Agent bootstrap | `agents[]` config supplies a label, optional exact or resume Session ID, provider, model, output-token cap, and fresh-session `cwd`. Configured entries start automatically. | Exact IDs support restore or resume, while fresh default IDs avoid restart collisions. See [`packages/core/agent-loop/README.md:36-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md#L36-L56). | +| Per-agent tools and instructions | An agent preset is a directory containing `agent.cordis.yml`. It contributes scoped tools and prompt sections. A persona row can shadow the deployment persona or own the complete prompt. | Several differently composed agents can share one process without leaking registrations between sessions. See [`packages/preset/README.md:1-16`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/preset/README.md#L1-L16) and [`packages/preset/persona/README.md:5-21`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/preset/persona/README.md#L5-L21). | +| Programmatic Agent setup | `ctx.agents.create()` and `resume()` accept `AgentOptions`, Session metadata, and trusted setup code that registers scoped tools, prompt sections, variables, restrictions, and listeners before publication. | Observers never see a partially configured Agent, and setup failure rolls the whole unpublished transaction back. See [`packages/core/agent/src/index.ts:73-155`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/index.ts#L73-L155). | +| Prompt and tool presentation | The deployment persona, scoped persona, ordered sections, runtime contexts, variables, and visible tool schemas assemble for each step. | Prompt text and tool presentation form one coherent model-facing request while plugins retain ownership of their facts. See [`packages/core/system-prompt/README.md:5-35`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/system-prompt/README.md#L5-L35). | +| Tool concurrency | `maxParallelToolCalls` defaults to 10 and can be changed through Settings for the next tool group. | Parallel-safe work is bounded, while `1` provides serial execution. See [`packages/core/agent-loop/README.md:36-52`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md#L36-L52). | +| Child behavior | The delegation tool chooses provider, one-shot or continuable background mode, model override, persona, tool allow or deny filter, output schema, and depth cap. | The provider contract advertises capabilities so unsupported restrictions fail before a child is published. See [`packages/subagent/subagent/README.md:29-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L29-L48). | + +The base `AgentOptions` type contains only provider, model, and per-request +output-token cap; persona belongs to system-prompt sections +([`packages/core/agent/src/runtime-types.ts:23-31`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/runtime-types.ts#L23-L31)). +Memory, credentials, triggers, schedules, sandboxing, and filesystem policy are +therefore not fields on an Agent record. Where composed, they are separate +plugin capabilities or durable Session events. + +## Binding time + +- **Boot:** profiles, bundles, patches, and the declarative `agents[]` roster + compose the process. The roster is intentionally boot-only. User patch HMR + can recompose the global plugin tree, but it does not turn the roster into a + live Agent-definition store + ([`packages/boot/app-boot/README.md:36-45`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/boot/app-boot/README.md#L36-L45), + [`packages/core/agent-loop/src/index.ts:239-271`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/src/index.ts#L239-L271)). +- **Create or resume:** the exact shared ID, Session metadata, Agent options, + setup contributions, and preset generation bind before publication. A preset + file edit starts a new generation for later sessions; existing agents keep + their joined generation + ([`packages/preset/agent-presets/README.md:29-45`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/preset/agent-presets/README.md#L29-L45)). +- **Blank-session exception:** a produced-nothing Agent can switch presets. The + product forbids switching after conversation output because logged tool calls + could refer to a capability the new composition cannot provide + ([`packages/preset/agent-presets/README.md:47-51`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/preset/agent-presets/README.md#L47-L51)). +- **Each step:** the loop assembles the current system prompt, visible tool + schemas, dynamic contexts, and variables. It logs a request header containing + the exact request envelope, so later reconstruction can see what the model + actually received + ([`packages/core/agent-loop/README.md:85-99`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md#L85-L99), + [`packages/core/session/README.md:61-73`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/README.md#L61-L73)). +- **Resume:** persisted history and identity return, but the caller supplies + fresh Agent options and setup. A Session can therefore retain continuity + while its newly resident Agent uses different route or scoped composition. + There is request-level evidence of the resulting behavior, but no versioned + Agent definition pinned by the Session + ([`packages/core/agent/src/index.ts:135-155`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/index.ts#L135-L155)). + +Compatibility is explicitly pre-release. The repository warns that breaking +changes will occur, directs maintainers to prefer a correct foundation over +compatibility shims, and says old on-disk formats are rejected +([`README.md:9-11`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/README.md#L9-L11), +[`AGENTS.md:5-7`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/AGENTS.md#L5-L7)). +`SESSION_FORMAT_VERSION` remains `0`; current readers refuse unsupported older +or newer formats rather than promise a migration path +([`packages/core/session/README.md:139-144`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/README.md#L139-L144)). + +## Relationships between nouns + +| Relationship | Operational cardinality and ownership | +| --- | --- | +| Agent to Session | Every live Agent has exactly one Session, shares its exact `SessionId`, and drives only that Session. A persisted Session can have zero live Agents; resume reconstructs at most one live Agent under the same ID. | +| Config label to Agent | One boot label normally produces one freshly identified live Agent per startup. An explicit `sessionId` or `resumeSessionId` makes Session identity stable; the label itself is not the durable identity. | +| Session to turn to step | A Session contains an append-only event log. One turn contains zero or more steps; one step is one model request plus the tools it invokes ([`docs/architecture.md:63-96`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/architecture.md#L63-L96)). | +| Agent to preset | One Agent scope may join one preset generation; one standing preset mount can serve many isolated Agent and Session pairs. | +| Parent to local child | One parent can dynamically create many child Sessions. The child's immutable header records `parentSession`; live registry ownership is tracked separately from durable lineage ([`packages/core/agent/README.md:19-24`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md#L19-L24)). | +| Continuable child to Activation | One durable child Session has zero or one live Activation. Each Activation owns one reconstructed Agent residency epoch. | +| Agent to workspace or sandbox | `cwd` is immutable Session creation metadata. It does not imply a sandbox; filesystem, subprocess, sandbox, approval, and credential capabilities come from the composed plugin tree. | +| Agent to tool | Tools are scoped registry contributions resolved during prompt assembly and guarded again at execution. They are not embedded in the Agent object. | + +The exact Agent-to-Session relationship is the unusual part of this model. +It is not one definition serving many Sessions, and it is not a Session owning +several runs of an Agent object. The live Agent and durable Session are two +runtime views of one identity. The Agent can disappear and later be recreated; +the Session is the continuity boundary. + +## Lifecycle + +Creation and resume are rollback-covered transactions: construct a private +Session, Agent, and scoped context; await setup; enter both registries; announce +Session then Agent creation; emit session start; and only then start the driver +([`packages/core/agent-loop/README.md:9-26`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md#L9-L26)). +Configured agents are owned by the loop fiber. Programmatic callers receive an +`AgentHandle`; its disposer is the consumer capability that stops and drains +the loop, unregisters the Agent, removes the live Session from the store, and +unwinds scoped registrations. Provider unload is an independent structural +teardown edge +([`packages/core/agent/src/index.ts:158-175`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/index.ts#L158-L175)). + +The observable live states are only `idle` and `running`; disposal is registry +removal, not a third status. `cancel()` aborts current activity and normally +clears queued work. The core API does not define Agent pause, hibernate, +archive, or delete states +([`packages/core/agent/src/runtime-types.ts:43-50`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/runtime-types.ts#L43-L50)). +Session persistence is supplied by plugins that mirror the event log and later +reconstruct it. Model history, forks, resume, transcripts, and recovery derive +from that log +([`packages/core/session/README.md:89-93`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/README.md#L89-L93)). + +Parent teardown has two distinct outcomes. During orderly process-local +shutdown, the subagent runtime closes new admission, cancels continuable +descendants top-down, releases them child-first, and attempts a final flush +([`packages/subagent/subagent/src/index.ts:294-325`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/index.ts#L294-L325), +[`packages/subagent/subagent/src/continuation.ts:746-841`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L746-L841), +[`packages/subagent/subagent/src/continuation.ts:1332-1394`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L1332-L1394)). +That kills process-local child activity, not the durable child Session. A hard +process crash runs none of this teardown; the child Session remains persisted +with its parent lineage and no durable parent-death disposition. + +The shipped loop is Harness-owned code running in the user's process. It is +the only package containing concrete loop logic, but the interface is designed +to be replaced by another Agent implementation. Plugins add behavior at live +events and capability seams rather than patching the loop +([`packages/core/agent-loop/README.md:5-7`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent-loop/README.md#L5-L7), +[`packages/core/agent/README.md:77-81`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/README.md#L77-L81)). + +## What makes it "an agent" here (our inference) + +Our inference: in DeepSeek Harness, an agent is the live, scoped capability +and control handle that runs an autonomous model-and-tool loop for exactly one +durable Session. It differs from a plain LLM call because it owns an inbox, +multi-step turn lifecycle, tool execution, cancellation, scoped plugin world, +and event-sourced history. + +The important architecture lesson is that durable Agent identity does not +require a separate Agent-definition record. DeepSeek Harness aliases Agent +identity to Session identity, then recreates the live behavior-bearing handle +around persisted history. This makes continuity explicit, but it does not by +itself pin a versioned behavior definition across resume. + +## Open questions + +- There is no Agent-definition version or durable snapshot tying a resumed + Session to the same Agent options, preset generation, global plugin tree, or + setup code. Request headers preserve what each step observed, but the release + does not state a reproducible replay contract for future turns after upgrade. +- Session format `0` deliberately has no broad compatibility promise. The + project does not publish a migration policy for persisted sessions created by + this release candidate. +- Continuable child residency is process-local and has no multi-process lease; + the source documents this as a current limitation + ([`packages/subagent/subagent/README.md:146-155`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/README.md#L146-L155)). +- Remote one-shot providers may have no local child Session, so their runs are + absent from trace-backed child enumeration. No cross-provider durable child + identity contract is documented. +- No global subagent fan-out limit or cross-process orphan policy is specified. +- Agent archival and persisted Session deletion are outside the core lifecycle + described by the cited sources. diff --git a/docs/research/agent-platform/synthesis.md b/docs/research/agent-platform/synthesis.md index 38a9f7e955..083a267772 100644 --- a/docs/research/agent-platform/synthesis.md +++ b/docs/research/agent-platform/synthesis.md @@ -22,6 +22,11 @@ ADR is authoritative. > Divergences C, D, and E, and the conceptual and comparison tables. The > revisions are marked inline. +> [DeepSeek Harness](./products/deepseek-harness.md) was researched on +> 2026-08-20 at `dsh-v0.1.0-rc.8`. Its evidence qualifies Convergence #5, +> Divergences A, C, D, and E, and the conceptual and comparison tables. The +> revisions are marked inline. + ## Convergence **1. The behavioral definition is settled.** Every product that states one @@ -139,6 +144,13 @@ finishes after the parent has stopped caring is not dropped silently, it is recorded as a `SubagentResultTombstone` naming the disposition, so "the parent only sees the result" does not become "the result vanishes." +*Qualified after DeepSeek Harness.* The final-result contract holds for its +one-shot children, but not for its continuable children. A continuable child is +a durable Session that accepts parent follow-ups and may report selected +messages before an unconditional settlement notice. The narrower convergence +is that bounded delegation returns a result without exposing hidden reasoning; +long-lived child agents can require an explicit message channel. + **6. The `description` field is the routing protocol.** LLM-driven delegation is steered by a natural-language description everywhere it exists: Claude Code ("Claude uses each subagent's description to decide @@ -208,6 +220,13 @@ than a necessity. Its cost is also visible: with no record there is no place to enumerate agents, no natural home for a description, and no per-agent default anything, which is a real product gap and not just a purity choice. +DeepSeek Harness adds a distinct live-handle point. Its Agent is a +process-local registry entry and driver handle with exactly the same identity +as one durable Session. The persisted Session can exist with no live Agent; +resume creates a fresh Agent scope under that same ID. This is neither a +definition record serving many executions nor a fused durable actor. It is a +temporary behavior-bearing view over one durable conversation identity. + **B. Who owns the loop.** Three positions: platform-managed loop (OpenComputer's runtimes, Managed Agents, Devin, and the AgentCore harness where "Who owns the loop: AWS"), customer loop behind an infrastructure @@ -261,6 +280,13 @@ each run; thread state persists across those runs. Its useful warning is that "deployed definition" and "behavior observed by this session" are not the same fact when control-plane content remains live. +DeepSeek Harness splits binding along another boundary. A preset generation is +fixed before Agent publication and stays stable for that live instance, while +prompt sections, tool schemas, and runtime context assemble on every step. +Resume preserves the Session ID and log but accepts fresh Agent options and +setup. Session continuity therefore does not imply behavior pinning; exact +request headers provide an audit trail, not an Agent-definition version. + **D. Subagents, the least settled axis.** Declared roster with depth-1 cap (Managed Agents: 20 agents/25 threads; Hermes default; OpenClaw default, with hierarchy frameworks explicitly refused in VISION), @@ -299,6 +325,14 @@ cancel operations. The topology label alone therefore cannot determine whether a child needs independent identity, state, authorization, and lifecycle. +DeepSeek Harness makes the same lifecycle distinction through a provider seam +and then separates durable identity from residency. A one-shot local child is +an ordinary Agent and Session disposed after one result. A continuable child +has one durable Session, zero or one live Activation, parent follow-ups, child +reports, and cold resume. Remote providers may create no local Agent at all. +The additional axis is therefore not just synchronous versus asynchronous, but +whether child identity outlives a resident Agent process. + **E. Session semantics.** Session-as-task-run (OpenComputer, Managed Agents, Devin, LangGraph runs) vs session-as-conversation-lane (OpenClaw's routing-scoped lanes, Hermes' session keys, Cloudflare instances that may @@ -321,6 +355,12 @@ session, and thread-scoped sandboxes are reused across runs. Mapping the run to a session would discard the state and environment continuity that the product actually preserves. +DeepSeek Harness adds a third mapping: Agent and Session share one exact ID and +are 1:1 while live. The Session may remain persisted with no resident Agent, +and resume reconstructs a fresh Agent under the same identity. Here `Session` +is both the conversation continuity boundary and the durable identity behind +the otherwise ephemeral Agent handle. + **F. Identity scope.** Everyone has intra-org identity; only [A2A](./products/adk-a2a.md) defines cross-org identity: the AgentCard (name, skills, interfaces, security schemes, JWS @@ -352,6 +392,7 @@ from the scope plus a run profile rather than published from a definition. | agent-as-compiled-harness-graph | LangChain Deep Agents | | agent-as-code-first-definition compiled into a managed assistant and deployment | LangSmith Managed Deep Agents | | agent-as-scope-coordinate (a validated axis in a scope tuple; no stored object, persona in files, runtime shape resolved onto each run) | IronClaw | +| agent-as-live-session-bound-handle | DeepSeek Harness | These are not mutually exclusive; most products stack two or three. IronClaw stacks agent-as-scope-coordinate with agent-as-file, which is what @@ -381,6 +422,7 @@ makes it legible: the scope answers "whose," the files answer "who." | [IronClaw](./products/ironclaw.md) (added post-synthesis) | scope coordinate with no stored object; persona in markdown, runtime shape in a `ResolvedRunProfile` on the run | child runs, lineage on the run, empty grant sets, atomic descendant reservation, deny-filtered off in shipped profiles | persona per turn (file read); mechanism resolved once at admission and frozen on the run | userland loop above a kernel boundary owning authority and recovery; loop is not the security perimeter | 1:N threads under the scope; thread (transcript) and turn run (lifecycle) are separate resources | | [LangChain Deep Agents](./products/deep-agents.md) (added post-synthesis) | compiled LangGraph harness graph; no durable Agent resource | sync child = stateless nested invocation; async child = independent thread and run | construction plus per-run context; thread state via checkpointer; no definition version contract | customer process or surrounding deployment | graph serves N threads; thread is the session boundary, run is one invocation | | [LangSmith Managed Deep Agents](./products/managed-deep-agents.md) (added post-synthesis) | code-first pre-runtime definition compiled into an assistant and deployment | underlying Deep Agents sync and async models; no MDA-specific child resource | code/model/tools at build; instructions/skills on every run; state on thread | LangSmith managed harness and runtime | one assistant serves N threads; session maps to thread, with N runs | +| [DeepSeek Harness](./products/deepseek-harness.md) (added post-synthesis) | live Agent handle over one same-ID durable Session; no definition resource | provider-backed one-shot or continuable children; spawn, fork, and remote implementations; depth defaults to 3 | profile at boot; preset generation at create; prompt and tools per step; fresh setup on resume; no definition version | user-run process with a replaceable first-party loop plugin | 1:1 while live under one ID; persisted Session may later rehydrate a fresh Agent | ## Working definition diff --git a/docs/research/session-store/index.md b/docs/research/session-store/index.md index 86a330442b..4ea2d75059 100644 --- a/docs/research/session-store/index.md +++ b/docs/research/session-store/index.md @@ -132,6 +132,9 @@ artifact; this corpus nests because every product here has at least two. - [Continue compared to our session event catalog](./products/continue/vs-session-events.md) - [Crush (Charm)](./products/crush/index.md) - [Crush compared to our session event catalog](./products/crush/vs-session-events.md) +- [DeepSeek Harness](./products/deepseek-harness/index.md) + - [DeepSeek Harness compared to our session event + catalog](./products/deepseek-harness/vs-session-events.md) - [fx (Vercel)](./products/fx/index.md) - [fx session detail JSON reference](./products/fx/session-detail-json-reference.md) - [fx compared to our session event catalog](./products/fx/vs-session-events.md) diff --git a/docs/research/session-store/products/deepseek-harness/index.md b/docs/research/session-store/products/deepseek-harness/index.md new file mode 100644 index 0000000000..c6c648e026 --- /dev/null +++ b/docs/research/session-store/products/deepseek-harness/index.md @@ -0,0 +1,1017 @@ +# DeepSeek Harness: how session transcripts are stored and resumed + +Part of [Session Store Research](../../index.md). + +Produced by running [RESEARCH_PROMPT](../../RESEARCH_PROMPT.md). + +- Repository: [deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) +- Snapshot: [`141eb6fef83422698aef7a981029e843e8161534`](https://github.com/deepseek-ai/deepseek-harness/commit/141eb6fef83422698aef7a981029e843e8161534) +- Retrieved: 2026-08-20 +- Evidence boundary: repository source and generated repository documentation at the pinned commit +- Verification note: this is static source research. Upstream tests were not run. + +DeepSeek Harness separates three concerns that other products often call one session store: + +1. `Session` and `SessionStore` own the live, process-local source of truth. [`packages/core/session/src/index.ts:417-472`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L417-L472) +2. `SessionPersistence` is an optional durability seam that subscribes to live events and persists the same event log. [`packages/core/session/src/index.ts:786-794`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L786-L794) +3. Projection and query packages derive read models from live or persisted logs. Their caches and indexes are explicitly non-authoritative. [`packages/session/session-projection-cache/src/index.ts:1-12`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L1-L12) + +This boundary is stated directly in the runtime: `SessionStore` is an in-memory map and persistence is intentionally implemented by subscribing plugins. [`packages/core/session/src/index.ts:786-794`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L786-L794) The persistence package likewise identifies the existing `SessionEvent` as the persisted unit and the event log as the single source of truth. [`packages/session/session-persistence/README.md:5-7`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L5-L7) + +## The storage model + +### Authoritative runtime state + +`Session` owns an append-only in-memory `SessionEvent[]`, an immutable `SessionHeader`, and an incrementally maintained model-visible surface. A live append is synchronous and I/O-free: it allocates `seq` from the current log length, stamps `Date.now()`, validates the payload and surface operation, freezes the event, pushes it to the log, updates the surface, and then notifies observers. Observer failures are contained after the event is already committed in memory. [`packages/core/session/src/index.ts:417-472`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L417-L472) [`packages/core/session/src/index.ts:569-655`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L569-L655) + +The exact source description is: + +> “An event-sourced session: an append-only log of {@link SessionEvent}s.” + +[`packages/core/session/src/index.ts:417-425`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L417-L425) + +`SessionStore.get()` and `SessionStore.list()` expose only currently live sessions. Detaching a `Session` removes it from the map and emits the disposal lifecycle, but does not delete any durable artifact. [`packages/core/session/src/index.ts:949-958`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L949-L958) [`packages/core/session/src/index.ts:1050-1065`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1050-L1065) + +### Optional durability backends + +The abstract `SessionPersistence` service stores immutable header metadata separately from the canonical event stream. The first-party coordinator supplies buffering, contiguous cursors, per-session operation serialization, cold preparation, repair sequencing, and quiescent disposal. A backend supplies physical read, append, repair, list, and optional suffix-read primitives. [`packages/session/session-persistence/src/index.ts:1-15`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L1-L15) [`packages/session/session-persistence/src/coordinator.ts:117-215`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L117-L215) + +The repository ships two implementations: + +| Backend | Physical source of truth | Access shape | +|---|---|---| +| JSONL | One `session.jsonl.zstd` by default, or raw `session.jsonl`, per materialized session. The first logical line is the header and later records encode every logical event. | Sequential full-file scan. `readFrom()` still parses the artifact and skips earlier events. [`packages/session/session-persistence-jsonl/README.md:5-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L5-L20) [`packages/session/session-persistence/src/index.ts:202-221`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L202-L221) | +| SQLite | A strict `sessions` metadata table plus an `events` table keyed by `(session_id, seq)`. Some physical event rows pack several logical chunk events. | Full read or seekable suffix read. [`packages/session/session-persistence-sqlite/resources/sql/schema.sql:1-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/resources/sql/schema.sql#L1-L30) [`packages/session/session-persistence-sqlite/README.md:5-19`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L5-L19) | + +The best-fit conceptual model is session-as-event-log with a separate immutable header. JSONL expresses that log as a per-session file, SQLite as a keyed row set, and both decode to the same ordered logical events. [`packages/session/session-persistence/README.md:5-7`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L5-L7) [`packages/session/session-persistence-sqlite/README.md:5-19`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L5-L19) + +`create()` is deliberately lazy in both first-party backends. A session with no appended event has no durable artifact or row and is absent from persistent listing. The first append atomically materializes the header and the first batch. [`packages/session/session-persistence/src/index.ts:126-143`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L126-L143) [`packages/session/session-persistence/README.md:16-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L16-L23) + +### Derived projections and indexes + +The model transcript is a projection, not another stored message list. `deriveMessages()` reads the session surface and caches a frozen array until the surface generation changes. Surface replacement changes this projection without mutating the underlying event log. [`packages/core/session/src/index.ts:701-747`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L701-L747) + +The generic projection registry folds pure projection units over `SessionEvent`s. Each unit declares `init`, `apply`, `view`, and a `stateVersion`; the registry can snapshot all registered views and serialize per-unit checkpoints. [`packages/session/session-projection/src/index.ts:34-74`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection/src/index.ts#L34-L74) [`packages/session/session-projection/src/index.ts:88-118`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection/src/index.ts#L88-L118) + +The optional projection cache stores one record per session in the `session_projcache` domain: + +```ts +{ + identity: { createdAt: number; cwd?: string } + rows: Record +} +``` + +It is explicitly a fold shortcut rather than authority. The shipped domain version is `3`; domain-version mismatch discards the medium, and unit `stateVersion` mismatch discards that unit row. [`packages/session/session-projection-cache/src/spec.ts:16-69`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/spec.ts#L16-L69) [`packages/session/session-projection-cache/src/index.ts:1-12`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L1-L12) + +## Keying and identity + +`SessionId` is only a branded string. `SessionId(id)` performs a compile-time cast and no runtime validation or generation. IDs contain no required timestamp, project, tenant, or host component. [`packages/core/session/src/types.ts:21-31`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L21-L31) + +If a caller omits the ID at the low-level `SessionStore`, it mints `session-` from a process-local counter. Callers may instead supply any branded string. Host and SDK entry points often supply UUID-based values, but that is caller policy rather than a storage invariant. [`packages/core/session/src/index.ts:809-888`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L809-L888) [`packages/host/apiproxy/src/api-proxy.ts:2078-2087`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/host/apiproxy/src/api-proxy.ts#L2078-L2087) [`packages/sdk/client/src/api.ts:84-93`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/sdk/client/src/api.ts#L84-L93) + +The immutable `SessionHeader` is the storage identity record: + +```ts +export interface SessionHeader { + readonly version: number + readonly id: SessionId + readonly createdAt: number + readonly cwd?: string + readonly parentSession?: SessionId + readonly seedLength?: number + readonly origin?: 'subagent' + readonly delegationDepth?: number + readonly agentPreset?: string +} +``` + +This declaration is quoted verbatim. [`packages/core/session/src/types.ts:58-99`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L58-L99) + +For JSONL, `cwd` selects a readable project directory and the ID is injectively escaped to one safe path component. CWD normalization is intentionally lossy, so multiple CWDs may share a project directory; lookup scans readable project directories for the encoded ID, validates the artifact header against the selected path, and rejects duplicate IDs. Identity is therefore session-ID-wide inside one configured root, not `(cwd, id)`, even though `cwd` participates in placement. [`packages/session/session-persistence-jsonl/src/format.ts:110-179`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/format.ts#L110-L179) [`packages/session/session-persistence-jsonl/src/index.ts:773-795`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L773-L795) + +Persistent `list()` is global to the configured backend, not scoped to a CWD. JSONL discovers all readable project directories under its root and SQLite selects every session row. Callers that need a project-scoped view must filter the returned headers. [`packages/session/session-persistence/src/index.ts:223-228`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L223-L228) [`packages/session/session-persistence-jsonl/src/index.ts:472-509`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L472-L509) [`packages/session/session-persistence-sqlite/src/store.ts:241-260`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L241-L260) + +For SQLite, `id` is the primary key in `sessions`; `cwd` is metadata. Neither persistence service exposes rename, relocate, or header-update operations. Relocating a worktree does not rewrite a stored header, and JSONL additionally binds the header's ID and CWD to its derived artifact path. Any relocation is therefore a new session or out-of-band backend maintenance, not a supported session-store operation. [`packages/session/session-persistence-sqlite/resources/sql/schema.sql:6-18`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/resources/sql/schema.sql#L6-L18) [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) [`packages/session/session-persistence-jsonl/README.md:40-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L40-L48) + +## The store interface + +These supporting value declarations are quoted verbatim: + +```ts +export interface SessionPersistenceSnapshot { + header: SessionHeader + revision: SessionPersistenceRevision +} + +export interface SessionInspection { + readonly meta: SessionHeader + readonly events: readonly SessionEvent[] +} + +export interface SessionRawArtifact { + readonly meta: SessionHeader + readonly filename: string + readonly content: string +} + +export interface SessionLocation { + readonly kind: string + readonly path: string +} +``` + +[`packages/session/session-persistence/src/index.ts:17-41`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L17-L41) [`packages/session/session-persistence/src/index.ts:66-76`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L66-L76) + +The complete public `SessionPersistence` signature set is shown below. Source JSDoc and the two concrete method bodies are omitted; member names, modifiers, parameters, and return types are preserved: + +```ts +constructor(ctx: Context) +abstract locate(meta: SessionHeader): SessionLocation | undefined +abstract readonly supportsRawArtifacts: boolean +readRaw(_id: SessionId, signal?: AbortSignal): Promise +abstract create(meta: SessionHeader): Promise +abstract append(id: SessionId, events: readonly SessionEvent[]): Promise +async prepare(id: SessionId, signal?: AbortSignal): Promise +abstract load(id: SessionId): Promise +abstract inspect(id: SessionId, signal?: AbortSignal): Promise +abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> +abstract list(signal?: AbortSignal): Promise +abstract listSnapshots(signal?: AbortSignal): Promise +``` + +The constructor delegates service registration to Cordis. `readRaw()` and `prepare()` have concrete default implementations; every other callable method is abstract, as is `supportsRawArtifacts`. [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) [`docs/subsystems/persistence.md:246-380`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/subsystems/persistence.md#L246-L380) + +| Operation | Semantics | +|---|---| +| `locate` | Side-effect-free local artifact hint. SQLite returns `undefined`; a path is not an authorization token and may not exist yet. [`packages/session/session-persistence/src/index.ts:66-102`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L66-L102) | +| `readRaw` | Optional exact backend artifact text, decoded from physical compression but not reconstructed from events. JSONL supports it; SQLite does not. [`packages/session/session-persistence/src/index.ts:98-124`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L98-L124) | +| `create` | Registers immutable metadata and may remain entirely lazy. [`packages/session/session-persistence/src/index.ts:126-133`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L126-L133) | +| `append` | Requires a contiguous batch and resolves only after durability. [`packages/session/session-persistence/src/index.ts:135-143`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L135-L143) | +| `prepare` | Builds or reuses an unpublished `Session` for resume, checking that the persisted revision still matches before reservation. [`packages/session/session-persistence/src/index.ts:145-168`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L145-L168) | +| `load` | Returns a balanced immutable logical log and commits cold crash repair. It rejects a live session whose turn remains open. [`packages/session/session-persistence/src/index.ts:170-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L170-L183) | +| `inspect` | Returns a logical view without committing repair or publishing a live session. Cold repair closers exist only in the returned view. [`packages/session/session-persistence/src/index.ts:185-200`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L185-L200) | +| `readFrom` | Detached valid-prefix suffix read. It does not truncate, synthesize closers, or use preparation state. [`packages/session/session-persistence/src/index.ts:202-221`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L202-L221) | +| `list` | All materialized headers, without full-log parsing, pagination, or filtering. [`packages/session/session-persistence/src/index.ts:223-228`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L223-L228) [`packages/session/session-persistence/README.md:81-85`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L81-L85) | +| `listSnapshots` | All materialized headers plus opaque source-qualified revisions that stay equal while a stored log is unchanged. [`packages/session/session-persistence/src/index.ts:230-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L230-L240) | + +The complete exported lower-level backend signature set is quoted verbatim, with source JSDoc omitted: + +```ts +export interface PersistenceBackend { + readonly name: string + loadStored(id: SessionId, signal?: AbortSignal): Promise | undefined> + readStoredRevision(id: SessionId, signal?: AbortSignal): Promise + loadStoredFrom?(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise + appendBatch(meta: SessionHeader, events: readonly SessionEvent[], isMaterialized: boolean): Promise + commitRepair(meta: SessionHeader, tornMarker: TornMarker | undefined, closers: readonly SessionEvent[]): Promise + list(signal?: AbortSignal): Promise + locate?(meta: SessionHeader): SessionLocation | undefined + close?(): Promise +} +``` + +`name`, `loadStored`, `readStoredRevision`, `appendBatch`, `commitRepair`, and `list` are required. `loadStoredFrom`, `locate`, and `close` are optional, as shown by `?`. `appendBatch` must atomically combine first materialization with the first event batch. `commitRepair` is not required to be atomic. [`packages/session/session-persistence/src/coordinator.ts:117-215`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L117-L215) + +There is no persistent update, delete, retention, rename, pagination, text-search, arbitrary metadata query, transaction callback, or compare-and-swap method in this service. Search and projections are separate consumers. [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) + +## Write and append path (ordering, durability, concurrency, delivery) + +### Live commit and delivery + +`Session.append()` is the first commit point. It computes a contiguous sequence number in memory, validates lossless JSON and surface invariants, deep-freezes the event, and appends it before invoking observers. The event exists in the authoritative live session even if a persistence listener later fails. [`packages/core/session/src/index.ts:569-655`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L569-L655) + +Persistence is a subscriber. For each committed event, the coordinator copies the frozen event with `structuredClone()` into a per-session write-behind queue. The first pending event arms a fixed deadline; later events do not extend it. An event arriving during a write forms a later batch. [`packages/session/session-persistence/src/write-behind.ts:18-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/write-behind.ts#L18-L56) [`packages/session/session-persistence/README.md:32-38`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L32-L38) + +An explicit `SessionStore.flush(session)` dispatches `session/flush` and waits for every registered persistence listener. Concurrent flush callers on one controller share a barrier; that barrier waits for an overlapping write and then drains all work admitted before quiescence. [`packages/core/session/src/index.ts:1009-1039`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1009-L1039) [`packages/session/session-persistence/src/write-behind.ts:58-71`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/write-behind.ts#L58-L71) [`packages/session/session-persistence/src/write-behind.ts:117-136`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/write-behind.ts#L117-L136) + +The shipped `session-checkpoint-policy` chooses semantic durability barriers on top of background batching. It flushes before constructing and dispatching a model request, before a top-level tool body may perform an external side effect, and at every `agent/pre-step` boundary so the prior response and ordered tool results are durable before the next request. Model and top-level tool dispatch fail closed when their flush fails, and a pre-step failure ends the turn before another request begins. Nested tool dispatches reuse the outer call checkpoint. [`packages/session/session-checkpoint-policy/README.md:5-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-checkpoint-policy/README.md#L5-L23) + +A background write failure is reported, its batch is restored at the front of the queue, and automatic retry pauses. A later event starts a new deadline; an explicit flush or teardown retries immediately and exposes repeated failure. This is at-least-retained in memory until retry, not an at-least-once distributed-delivery promise. Sequence contiguity is the duplicate and gap guard; the event envelope has no independent write ID. [`packages/session/session-persistence/src/write-behind.ts:138-158`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/write-behind.ts#L138-L158) [`packages/session/session-persistence/README.md:25-36`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L25-L36) + +The coordinator serializes all operations for a session ID through a promise chain. This prevents interleaving inside one backend instance, and errors do not poison later operations. It is not a cross-process lease. [`packages/session/session-persistence/src/coordinator.ts:1004-1033`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L1004-L1033) + +### JSONL write shape + +The exact header record type is: + +```ts +export interface HeaderLine { + type: 'session' + version: number + id: SessionId + createdAt: number + cwd?: string + parentSession?: SessionId + seedLength?: number + origin?: 'subagent' + delegationDepth: number + agentPreset?: string +} +``` + +This declaration is quoted verbatim. [`packages/session/session-persistence-jsonl/src/format.ts:28-44`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/format.ts#L28-L44) + +Each later logical record is either a verbatim `SessionEvent` or a lossless packed run of at least three compatible `assistant/chunk` deltas. Packed rows preserve every member's sequence and timestamp and decode back into the same logical event sequence. [`packages/session/session-persistence-jsonl/README.md:17-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L17-L20) [`packages/session/session-persistence-jsonl/src/format.ts:210-224`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/format.ts#L210-L224) + +With Zstandard, the header is one checksummed frame and every durable append batch is another checksummed frame. Raw mode appends newline-delimited UTF-8. The backend fsyncs successful appends and rolls a failed append or sync back to the prior byte length. [`packages/session/session-persistence-jsonl/README.md:34-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L34-L48) [`packages/session/session-persistence-jsonl/src/index.ts:646-700`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L646-L700) + +First materialization writes and syncs a temporary file containing header plus first batch. POSIX publishes it without overwrite via a hard link and fsyncs the parent directory. Windows uses write-through `MoveFileExW` without replacement. This makes first same-ID publication collision-safe. Later concurrent writers are unsupported. [`packages/session/session-persistence-jsonl/src/index.ts:513-625`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L513-L625) [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) + +### SQLite write shape + +The complete schema is: + +```sql +CREATE TABLE persistence_state ( + singleton INTEGER PRIMARY KEY CHECK (singleton = 1), + store_id TEXT NOT NULL +) STRICT; + +CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + version INTEGER NOT NULL, + created_at INTEGER NOT NULL, + cwd TEXT, + parent_session TEXT, + seed_length INTEGER, + origin TEXT, + delegation_depth INTEGER, + agent_preset TEXT, + incarnation TEXT NOT NULL, + revision INTEGER NOT NULL +) STRICT; + +CREATE TABLE events ( + session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, + seq INTEGER NOT NULL, + type TEXT NOT NULL, + time INTEGER NOT NULL, + data ANY NOT NULL, + source_event_seqs ANY, + surface_op TEXT, + ignorable INTEGER CHECK (ignorable IS NULL OR ignorable IN (0, 1)), + PRIMARY KEY (session_id, seq) +) STRICT; +``` + +This schema is quoted verbatim. [`packages/session/session-persistence-sqlite/resources/sql/schema.sql:1-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/resources/sql/schema.sql#L1-L30) + +An append uses `BEGIN IMMEDIATE`, validates the current tail and expected first sequence, writes the session row during first materialization, inserts physical event rows, increments the session revision, and commits. Any failure rolls back the transaction. `synchronous=FULL` is set and read back. [`packages/session/session-persistence-sqlite/src/store.ts:173-199`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L173-L199) [`packages/session/session-persistence-sqlite/src/schema.ts:177-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/schema.ts#L177-L183) + +Compatible chunk runs may be packed into a physical row with limits of 1,024 logical events or 1 MiB. Payload data at least 4 KiB is Zstandard-compressed only when compression makes it smaller, and source sequences use a compact binary representation. These encodings are invisible after logical decode. [`packages/session/session-persistence-sqlite/README.md:5-19`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L5-L19) + +### Crash repair + +The recovery contract preserves every complete event. It removes only a physically torn final fragment, then closes a complete interrupted turn by appending synthetic error `tool/result` events for unanswered calls, an open `step/end` when needed, and `turn/end` with `{ kind: 'interrupted' }`. A gap, middle parse failure, committed corruption, or malformed prefix rejects instead of being repaired. [`packages/session/session-persistence/README.md:25-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L25-L30) [`packages/session/session-persistence/src/index.ts:170-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L170-L183) + +Recovery distinguishes two unanswered-call states. An assistant message that requested a call but has no durable `tool/call` receives `ToolNotStartedError` with code `TOOL_NOT_STARTED`, no source-event sequence, and text saying the Harness never recorded the call as started. A durable `tool/call` with no result receives `ToolOutcomeUnknownError` with code `TOOL_OUTCOME_UNKNOWN`, cites the call event, and warns that external side effects may already have happened. [`packages/core/session/src/repair.ts:89-123`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/repair.ts#L89-L123) + +Cold `inspect()` computes the same balanced logical view but leaves the physical tail untouched. Cold `load()` and `prepare()` commit repair. A live `load()` first flushes the exact authoritative in-memory snapshot and returns it only if balanced; it never manufactures interruption inside a still-live open turn. [`packages/session/session-persistence/src/index.ts:170-200`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L170-L200) [`packages/session/session-persistence/src/coordinator.ts:973-989`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L973-L989) + +JSONL repair truncates to a byte offset and syncs, then appends and syncs the recovered complete records and closers. SQLite repair performs stale-tail checks, deletes rows from the torn sequence onward, inserts closers, increments revision, and commits in one transaction. The abstract backend contract intentionally permits non-atomic repair, so backend-specific idempotence matters after a second crash. [`packages/session/session-persistence-jsonl/src/index.ts:646-700`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L646-L700) [`packages/session/session-persistence-sqlite/src/store.ts:201-239`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L201-L239) [`packages/session/session-persistence/src/coordinator.ts:186-193`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L186-L193) + +## Read and resume path + +The cold resume path is `prepare(id)`, not a direct mutable restore. The coordinator reads and validates the stored prefix, computes logical repair, constructs one unpublished `Session`, checks that the source revision is still current, commits pending repair, reserves that exact object, and returns a `SessionPreparation` for publication. Stale prepared objects are discarded and loaded again. [`packages/session/session-persistence/src/index.ts:145-168`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L145-L168) [`packages/session/session-persistence/src/coordinator.ts:720-775`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L720-L775) [`packages/session/session-persistence/src/coordinator.ts:891-970`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L891-L970) + +`Session.fromRestore()` validates a contiguous seed beginning at sequence zero, validates every JSON payload and surface transition, adopts the immutable header, and appends `session/end-seed` unless the seed already ends with one. The result preserves prior events exactly and marks where the new lifecycle begins. [`packages/core/session/src/index.ts:474-547`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L474-L547) [`packages/core/session/src/types.ts:315-336`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L315-L336) + +Reading has three distinct semantics: + +| Path | Repair side effect | Live-session behavior | Intended use | +|---|---|---|---| +| `load` | Commits cold tail repair. | Flushes and returns a balanced live snapshot; rejects an open live turn. | Balanced replay or compatibility callers. [`packages/session/session-persistence/src/index.ts:170-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L170-L183) | +| `inspect` | None. Synthetic closers are returned only in memory. | Returns the current immutable live snapshot, which may be open. | History inspection and preparing reusable cold state. [`packages/session/session-persistence/src/index.ts:185-200`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L185-L200) | +| `readFrom` | None. No closers and no preparation cache. | Detached stored-prefix read. | Projection tails and watermarks. [`packages/session/session-persistence/src/index.ts:202-221`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L202-L221) | + +SQLite implements a true suffix read and scans backward only far enough to include a packed row that may begin below `fromSeq`. JSONL has no seek primitive and scans the whole artifact before skipping. [`packages/session/session-persistence-sqlite/src/store.ts:317-350`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L317-L350) [`packages/session/session-persistence/src/index.ts:202-221`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L202-L221) + +There is no entry pagination, page size, or transcript-length bound in the persistence API. `prepare()`, `load()`, and `inspect()` eagerly reconstruct a complete logical log; only `readFrom()` lets a projection consumer request an unbounded suffix. Projection-cache cold reads are the lazy exception: they load a cached fold plus the durable tail, falling back to sequence zero when the cache overreaches the log. [`packages/session/session-persistence/src/index.ts:145-221`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L145-L221) [`packages/session/session-projection-cache/src/index.ts:154-197`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L154-L197) + +The model sees only the surface projection after resume. Raw chunks, request metadata, audits, lifecycle facts, and compaction records remain in the log but are not all projected into model messages. [`packages/core/session/src/types.ts:230-350`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L230-L350) [`packages/core/session/src/index.ts:701-747`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L701-L747) + +## Listing, summaries, and search + +The persistence seam lists lightweight immutable headers or header-plus-revision snapshots. Both first-party backends avoid decoding full logs for listing. JSONL validates only the header frame or line; SQLite selects session rows. There is no paging, filtering, sorting, or text query in the seam. [`packages/session/session-persistence/src/index.ts:223-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L223-L240) [`packages/session/session-persistence-jsonl/src/format.ts:396-413`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/format.ts#L396-L413) [`packages/session/session-persistence-sqlite/src/store.ts:241-260`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L241-L260) + +JSONL listing is a directory and header scan across the configured root; SQLite listing is a table query. The sources state no benchmark or scale limit, but the seam itself warns that all-session listing is unindexed at scale because it is unpaginated and unfiltered. [`packages/session/session-persistence-jsonl/src/index.ts:472-509`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L472-L509) [`packages/session/session-persistence/README.md:81-85`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L81-L85) + +The host constructs a user-facing list by starting with live `SessionStore.list()`, merging persisted `list()` rows not already live, attaching projection-cache baselines when available, and sorting the combined view by update time. Live state wins identity races. [`packages/host/apiproxy/src/api-proxy.ts:1665-1729`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/host/apiproxy/src/api-proxy.ts#L1665-L1729) + +`SessionCorpus` provides an exact logical corpus over live and persisted sessions. It merges headers with live precedence, sorts newest first, reads live logs directly, and otherwise calls persistence inspection. It rechecks live state after a persisted read to close the race where a session becomes live during I/O. [`packages/session-query/session-query/src/corpus.ts:31-116`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query/src/corpus.ts#L31-L116) + +The optional SQLite query provider is a separate FTS5 derived index. It reconciles `listSnapshots()` revisions, calls `inspect()` only for changed logs, combines persisted FTS rows with a temporary live-session overlay, and can rebuild from the authoritative corpus. The query database has one owner process and is not the session store. [`packages/session-query/session-query-sqlite/README.md:5-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query-sqlite/README.md#L5-L23) [`packages/session-query/session-query-sqlite/README.md:52-57`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query-sqlite/README.md#L52-L57) + +Projection-cache listing is intentionally stale but watermark-qualified. `cachedSnapshot()` returns only identity-matching, version-matching rows and chooses the lowest served watermark. A later cold read obtains `readFrom(floor)`, folds the tail, falls back to a full read if the stored log is shorter than the cache claims, and writes the repaired cache back fail-soft. [`packages/session/session-projection-cache/src/index.ts:91-130`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L91-L130) [`packages/session/session-projection-cache/src/index.ts:154-197`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L154-L197) + +## Entry/message structure and versioning + +### Event envelope + +The canonical event is an envelope over a declaration-merged payload map. This declaration is quoted verbatim: + +```ts +export type SessionEvent = { + [K in SessionEventType]: { + type: K + seq: number + time: number + data: SessionEventMap[K] + ignorable?: true + } & (K extends SurfaceEventType ? { + sourceEventSeqs?: number[] + surfaceOp?: SurfaceOp + } : object) +}[T] +``` + +[`packages/core/session/src/types.ts:395-440`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L395-L440) + +Only `user/message`, `assistant/message`, and `tool/result` are surface-eligible. Their `surfaceOp` is either `'append'` or `{ op: 'replace', start, end }`; `sourceEventSeqs` cites earlier events that produced the surface node. All other payloads are log-only and cannot carry those fields. [`packages/core/session/src/types.ts:339-379`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L339-L379) + +### Complete payload catalog at the snapshot + +The repository generates and verifies a full persistence catalog from the merge-extended `SessionEventMap`. The following table transcribes every event declaration at the pinned snapshot. [`docs/persistence-catalog.md:1-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1-L20) + +| Event type | `data` payload | +|---|---| +| `agent/inbox/spliced` | `{ target: InboxTarget; start: number; removedCount?: number; inserted: UserMessage[]; outcome?: 'canceled' }` [`docs/persistence-catalog.md:101-122`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L101-L122) | +| `agent-preset/selected` | `{ agentPreset: string }` [`docs/persistence-catalog.md:124-140`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L124-L140) | +| `approval/asked` | `{ id: ApprovalRequestId; toolName: string; callId?: CallId; reason?: string }` [`docs/persistence-catalog.md:142-165`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L142-L165) | +| `approval/decided` | `{ id: ApprovalRequestId; outcome: ApprovalOutcome }` [`docs/persistence-catalog.md:167-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L167-L183) | +| `approval/policy` | `{ policy: ApprovalPolicy; source?: 'delegation' }` [`docs/persistence-catalog.md:185-207`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L185-L207) | +| `assistant/chunk` | `{ turn: number; step: number; chunk: StreamChunk }` [`docs/persistence-catalog.md:209-220`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L209-L220) | +| `assistant/message` | `{ turn: number; step: number; message: AssistantMessage; usage?: TokenUsage; interrupted?: true }` [`docs/persistence-catalog.md:222-244`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L222-L244) | +| `command/done` | `{ commandId: CommandId; kind: 'success' \| 'error'; text?: string; sourceEventSeq?: number }` [`docs/persistence-catalog.md:246-265`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L246-L265) | +| `command/run` | `{ commandId: CommandId; name: string; args?: string; source: CommandSource }` [`docs/persistence-catalog.md:267-287`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L267-L287) | +| `compaction/end` | `{ compactionId: CompactionId; sourceCommandId?: CommandId; turn: number \| null; error?: string }` [`docs/persistence-catalog.md:289-301`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L289-L301) | +| `compaction/prune` | `{ shadowedRange: { start: number; end: number }; shadowedSeqs: number[]; shadowedTokenCount: number }` [`docs/persistence-catalog.md:303-327`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L303-L327) | +| `compaction/start` | `{ compactionId: CompactionId; sourceCommandId?: CommandId; turn: number \| null }` [`docs/persistence-catalog.md:329-342`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L329-L342) | +| `compaction/summary` | `{ compactionId; sourceCommandId?; summary: ContentBlock[]; shadowedRange; shadowedSeqs; shadowedTokenCount; provider; model; maxTokens?; usage? }` plus either `{ rawOutput; llmStreamCall: true }` or `{ rawOutput?; llmStreamCall?: never }` [`docs/persistence-catalog.md:344-398`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L344-L398) | +| `feedback/record` | `{ text: string }` [`docs/persistence-catalog.md:400-414`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L400-L414) | +| `goal/change` | `GoalChangeMeta` [`docs/persistence-catalog.md:416-429`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L416-L429) | +| `hook/invoked` | `{ turn: number; point: string; dialect: HookDialect; matcher?: string; handlerId: string }` [`docs/persistence-catalog.md:431-454`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L431-L454) | +| `hook/result` | `{ turn: number; point: string; handlerId: string; decision: string; exitCode?: number; stderrSummary?: string; durationMs: number }` [`docs/persistence-catalog.md:456-479`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L456-L479) | +| `llm/retry` | `LlmRetryEventData` [`docs/persistence-catalog.md:481-490`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L481-L490) | +| `llm/retry-started` | `LlmRetryStartedEventData` [`docs/persistence-catalog.md:492-503`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L492-L503) | +| `permission/preset` | `{ preset: string }` [`docs/persistence-catalog.md:505-521`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L505-L521) | +| `plan/mode` | `{ active: boolean }` [`docs/persistence-catalog.md:523-538`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L523-L538) | +| `request/context` | `{ provider: string; model: string; contextWindow?: number }` [`docs/persistence-catalog.md:540-552`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L540-L552) | +| `request/header` | `{ header: EpochHeader; reason: 'initial' \| 'resume' \| 'change' }` [`docs/persistence-catalog.md:554-568`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L554-L568) | +| `sandbox/mode` | `{ mode: SandboxMode; source?: 'delegation' }` [`docs/persistence-catalog.md:570-591`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L570-L591) | +| `schedule/change` | `ScheduleChange` [`docs/persistence-catalog.md:593-609`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L593-L609) | +| `session/end-seed` | `Record` [`docs/persistence-catalog.md:611-641`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L611-L641) | +| `session/title` | `{ title: string; messageSeqs: number[]; source: SessionTitleSource }` [`docs/persistence-catalog.md:643-657`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L643-L657) [`packages/session/session-title/src/index.ts:47-68`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-title/src/index.ts#L47-L68) | +| `session/title-llm-request` | `{ titleProvider; messageSeqs; route; system; messages; maxTokens }` [`docs/persistence-catalog.md:659-672`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L659-L672) [`packages/session/session-title-llm/src/index.ts:24-38`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-title-llm/src/index.ts#L24-L38) | +| `step/end` | `{ turn: number; step: number }` [`docs/persistence-catalog.md:674-683`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L674-L683) | +| `step/start` | `{ turn: number; step: number }` [`docs/persistence-catalog.md:685-696`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L685-L696) | +| `subagent/descriptor` | `SubagentDescriptorData` [`docs/persistence-catalog.md:698-715`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L698-L715) | +| `team/member` | `{ version: 1; teamId: TeamId; member: TeamMemberSnapshot }` [`docs/persistence-catalog.md:717-728`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L717-L728) | +| `team/message/delivered` | `{ version: 1; teamId: TeamId; messageId: TeamMessageId; targetId: SessionId }` [`docs/persistence-catalog.md:730-746`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L730-L746) | +| `team/message/queued` | `{ version: 1; teamId: TeamId; message: TeamMessageSnapshot }` [`docs/persistence-catalog.md:748-759`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L748-L759) | +| `team/task` | `{ version: 1; teamId: TeamId; task: TeamTaskSnapshot }` [`docs/persistence-catalog.md:761-774`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L761-L774) | +| `todo/write` | `{ todos: { content: string; status: 'pending' \| 'in_progress' \| 'completed' }[] }` [`docs/persistence-catalog.md:776-789`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L776-L789) [`packages/core/session/src/types.ts:179-194`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L179-L194) | +| `tool/call` | `{ turn: number; step: number; callId: CallId; name: string; arguments: string }` [`docs/persistence-catalog.md:791-806`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L791-L806) | +| `tool/code-dispatch` | `{ rootCallId; parentCallId; subCallId; name; arguments; isError; content }` [`docs/persistence-catalog.md:808-831`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L808-L831) [`packages/core/tools/src/types.ts:10-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/tools/src/types.ts#L10-L23) | +| `tool/code-dispatch-start` | `{ rootCallId; parentCallId; subCallId; name; arguments }` [`docs/persistence-catalog.md:833-854`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L833-L854) [`packages/core/tools/src/types.ts:10-17`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/tools/src/types.ts#L10-L17) | +| `tool/result` | `{ turn; step; message: ToolResultMessage; error?: { name; code }; meta?: JsonValue }` [`docs/persistence-catalog.md:856-883`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L856-L883) | +| `tool-workflow/agent-end` | `{ runId: WorkflowRunId; seq: number; outcome: WorkflowAgentOutcome }` [`docs/persistence-catalog.md:885-897`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L885-L897) [`packages/workflow/tool-workflow/src/types.ts:28-33`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/tool-workflow/src/types.ts#L28-L33) | +| `tool-workflow/agent-start` | `{ runId; seq; label; phase?; childId }` [`docs/persistence-catalog.md:899-911`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L899-L911) [`packages/workflow/tool-workflow/src/types.ts:19-26`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/tool-workflow/src/types.ts#L19-L26) | +| `tool-workflow/run-end` | `{ runId: WorkflowRunId; stopReason: WorkflowStopReason }` [`docs/persistence-catalog.md:913-925`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L913-L925) [`packages/workflow/tool-workflow/src/types.ts:35-39`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/tool-workflow/src/types.ts#L35-L39) | +| `tool-workflow/run-start` | `{ runId: WorkflowRunId; name: string }` [`docs/persistence-catalog.md:927-941`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L927-L941) [`packages/workflow/tool-workflow/src/types.ts:13-17`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/tool-workflow/src/types.ts#L13-L17) | +| `turn/end` | `{ turn: number; reason: TurnEndReason }` [`docs/persistence-catalog.md:943-961`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L943-L961) | +| `turn/start` | `{ turn: number }` [`docs/persistence-catalog.md:963-979`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L963-L979) | +| `user/message` | `UserMessage` [`docs/persistence-catalog.md:981-998`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L981-L998) | +| `web/deepseek-search-llm-request` | `{ endpoint; apiVersion; body: { model; max_tokens; messages; tools } }` [`docs/persistence-catalog.md:1000-1007`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1000-L1007) [`packages/web/web-search-deepseek/src/provider.ts:52-78`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/web/web-search-deepseek/src/provider.ts#L52-L78) | + +The important nested payloads are also durable values: + +The message payload declarations are quoted verbatim: + +```ts +export interface Message { + readonly id: MessageId + readonly role: 'system' | 'user' | 'assistant' + readonly content: ContentBlock[] + readonly source: MessageSource +} + +export interface UserMessage extends Message { + readonly role: 'user' +} + +export interface AssistantMessage extends Message { + readonly role: 'assistant' + readonly source: ModelMessageSource +} + +export interface ToolResultMessage extends Message { + readonly role: 'user' + readonly content: [ToolResultBlock] + readonly source: ToolMessageSource +} +``` + +[`packages/llm/llm/src/message.ts:96-156`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/message.ts#L96-L156) + +The content and streamed assistant payload declarations are also quoted verbatim: + +```ts +export interface TextBlock { + type: 'text' + text: string +} + +export interface ReasoningBlock { + type: 'reasoning' + text: string +} + +export interface ImageBlock { + type: 'image' + attachment: ImageAttachmentRef +} + +export interface ToolCallBlock { + type: 'tool-call' + id: CallId + name: string + arguments: string +} + +export interface ToolResultBlock { + type: 'tool-result' + toolCallId: CallId + content: ContentBlock[] + isError?: boolean +} + +export interface ContentBlockMap { + 'text': TextBlock + 'reasoning': ReasoningBlock + 'image': ImageBlock + 'tool-call': ToolCallBlock + 'tool-result': ToolResultBlock +} + +export type ContentBlock = ContentBlockMap[ContentBlockType] + +export type StreamChunk = + | { type: 'block-start'; index: number; blockType: ContentBlockType } + | { type: 'text-delta'; index: number; text: string } + | { type: 'reasoning-delta'; index: number; text: string } + | { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string } + | { type: 'block-end'; index: number; block: ContentBlock } + | { type: 'usage'; usage: TokenUsage } + | { + type: 'finish' + reason: FinishReason + replayState?: ReplayEnvelope + } + +export interface FinishReasonMap { + 'stop': { kind: 'stop' } + 'tool-calls': { kind: 'tool-calls' } + 'max-tokens': { kind: 'max-tokens' } + 'aborted': { kind: 'aborted'; failure: LlmFailure } + 'error': { kind: 'error'; failure: LlmFailure } +} + +export interface TokenUsage { + inputTokens: number + outputTokens: number + cacheReadTokens?: number + cacheWriteTokens?: number + reasoningTokens?: number +} + +export interface ReplayEnvelope { + response: unknown + blocks?: readonly unknown[] +} + +export type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' + +export interface ImageAttachmentRef { + attachmentId: AttachmentId + mediaType: ImageMediaType + bytes: number + width: number + height: number + name?: string +} +``` + +[`packages/llm/llm/src/types.ts:53-141`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/types.ts#L53-L141) [`packages/llm/llm/src/types.ts:290-324`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/types.ts#L290-L324) [`packages/attachment/attachment/src/types.ts:7-24`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/attachment/attachment/src/types.ts#L7-L24) + +The request-header, approval, and retry aliases expand to these durable fields. Source comments are omitted, while declarations and union values are reproduced exactly: + +```ts +export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable' +export type ApprovalPolicy = 'ask' | 'never' + +export interface LlmCallConfig { + provider: string + model: string + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} + +export interface LlmCallConfigAdapterDefaults { + reasoningEffort?: true + maxTokens?: true +} + +export interface ToolSchema { + name: string + description: string + parameters: Record +} + +export interface EpochHeader { + config: LlmCallConfig + adapterDefaults?: LlmCallConfigAdapterDefaults + system?: string + tools?: ToolSchema[] +} + +export interface LlmFailure { + readonly message: string + readonly code: string + readonly status?: number + readonly providerRetryAfterMs?: number + readonly requestId?: ProviderRequestId +} + +export type LlmRetryEventData = + | { + retryId: RetryId + turn: number + step: number + provider: string + mode: 'normal' + policyKey: string + retry: number + maxRetries: number + delayMs: number + failure: LlmFailure + } + | { + retryId: RetryId + turn: number + step: number + provider: string + mode: 'always' + policyKey: string + retry: number + delayMs: number + failure: LlmFailure + } + +export interface LlmRetryStartedEventData { + retryId: RetryId + turn: number + step: number + retry: number +} +``` + +[`packages/interaction/user-approval/src/types.ts:25-29`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/interaction/user-approval/src/types.ts#L25-L29) [`packages/interaction/user-approval/src/index.ts:84-94`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/interaction/user-approval/src/index.ts#L84-L94) [`packages/llm/llm/src/call-config.ts:17-39`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/call-config.ts#L17-L39) [`packages/llm/llm/src/types.ts:39-51`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/types.ts#L39-L51) [`packages/llm/llm/src/types.ts:326-338`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/types.ts#L326-L338) [`packages/core/session/src/types.ts:196-210`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L196-L210) [`packages/llm/llm-retry/src/types.ts:15-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm-retry/src/types.ts#L15-L48) + +`TurnEndReason` includes completed, aborted with cause, blocked, structured error, max-tokens, and persistence-repaired interrupted. [`packages/core/session/src/types.ts:142-177`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L142-L177) + +The goal alias includes its nested stable reference, phase, block reason, full snapshot, mutation counters, timestamps, and clear tombstone: + +```ts +export interface GoalRef { + readonly id: GoalId + readonly revision: number +} + +export type GoalPhase = + | 'active' + | 'paused' + | 'blocked' + | 'complete' + +export interface GoalBlockReason { + readonly code: string + readonly message: string +} + +export interface GoalSnapshot extends GoalRef { + readonly objective: string + readonly phase: GoalPhase + readonly blockedReason?: GoalBlockReason + readonly maxGoalRounds: number +} + +export type GoalOperation = + | 'create' + | 'edit' + | 'pause' + | 'resume' + | 'complete' + | 'block' + | 'clear' + +export interface GoalSnapshotChangeMeta { + readonly kind: 'goal/change' + readonly version: 1 + readonly operation: Exclude + readonly goal: GoalSnapshot + readonly roundsStarted: number + readonly createdAt: number + readonly updatedAt: number +} + +export interface GoalClearChangeMeta { + readonly kind: 'goal/change' + readonly version: 1 + readonly operation: 'clear' + readonly cleared: GoalRef + readonly clearedAt: number +} + +export type GoalChangeMeta = GoalSnapshotChangeMeta | GoalClearChangeMeta +``` + +[`packages/goal/goal/src/types.ts:15-68`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/goal/goal/src/types.ts#L15-L68) [`packages/goal/goal/src/domain.ts:13-44`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/goal/goal/src/domain.ts#L13-L44) + +The schedule alias is the following version-1 union. All creation record variants and dispatch fields are included: + +```ts +export interface AfterScheduleRecord { + readonly id: ScheduleId + readonly kind: 'after' + readonly prompt: string + readonly afterSeconds: number + readonly scheduledAt: string +} + +export interface AtScheduleRecord { + readonly id: ScheduleId + readonly kind: 'at' + readonly prompt: string + readonly scheduledAt: string +} + +export interface EveryScheduleRecord { + readonly id: ScheduleId + readonly kind: 'every' + readonly prompt: string + readonly everySeconds: number + readonly scheduledAt: string +} + +export type ScheduleRecord = AfterScheduleRecord | AtScheduleRecord | EveryScheduleRecord + +export interface ScheduleCreateChange { + readonly version: 1 + readonly operation: 'create' + readonly schedule: ScheduleRecord +} + +export interface ScheduleDeleteChange { + readonly version: 1 + readonly operation: 'delete' + readonly id: ScheduleId +} + +export interface OneShotScheduleDispatchChange { + readonly version: 1 + readonly operation: 'dispatch' + readonly id: ScheduleId +} + +export interface EveryScheduleDispatchChange { + readonly version: 1 + readonly operation: 'dispatch' + readonly id: ScheduleId + readonly acceptedAt: string +} + +export type ScheduleChange = + | ScheduleCreateChange + | ScheduleDeleteChange + | OneShotScheduleDispatchChange + | EveryScheduleDispatchChange +``` + +[`packages/schedule/schedule/src/types.ts:9-105`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/schedule/schedule/src/types.ts#L9-L105) + +The version-2 subagent descriptor carries every durable child-composition field. `ToolRestriction` is expanded because it is nested in the continuable variant: + +```ts +interface SubagentDescriptorBase { + readonly version: number + readonly mode: 'one-shot' | 'continuable' + readonly provider: string +} + +export interface OneShotSubagentDescriptorData extends SubagentDescriptorBase { + readonly mode: 'one-shot' + readonly label?: string +} + +export interface ContinuableSubagentDescriptorData extends SubagentDescriptorBase { + readonly mode: 'continuable' + readonly label: string + readonly agentProvider?: string + readonly agentModel?: string + readonly persona?: string + readonly toolFilter?: ToolRestriction +} + +export interface ToolRestriction { + readonly allow?: readonly string[] + readonly deny?: readonly string[] +} + +export type SubagentDescriptorData = + | OneShotSubagentDescriptorData + | ContinuableSubagentDescriptorData +``` + +[`packages/subagent/subagent/src/descriptor.ts:47-88`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/descriptor.ts#L47-L88) [`packages/core/tools/src/index.ts:676-685`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/tools/src/index.ts#L676-L685) + +The session-title request records the exact auxiliary route, system prompt, message list, and output cap. Its companion title event records the accepted title and provenance: + +```ts +export interface SessionTitleModelProvenance { + readonly provider: string + readonly model: string +} + +export type SessionTitleSource = + | { readonly kind: 'fallback' } + | { + readonly kind: 'provider' + readonly provider: SessionTitleProviderId + readonly model?: SessionTitleModelProvenance + } + | { readonly kind: 'user' } + +export interface SessionTitleEventData { + readonly title: string + readonly messageSeqs: number[] + readonly source: SessionTitleSource +} + +export interface SessionTitleLlmRequestEventData { + readonly titleProvider: SessionTitleProviderId + readonly messageSeqs: number[] + readonly route: SessionTitleModelProvenance + readonly system: string + readonly messages: Message[] + readonly maxTokens: number +} +``` + +[`packages/session/session-title/src/index.ts:39-68`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-title/src/index.ts#L39-L68) [`packages/session/session-title-llm/src/index.ts:24-38`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-title-llm/src/index.ts#L24-L38) + +The durable Team event wrappers add `version: 1` and `teamId`; their nested snapshots are: + +```ts +export type TeamMemberPhase = 'provisioning' | 'active' | 'failed' + +export interface TeamMemberSnapshot { + readonly id: SessionId + readonly name: string + readonly description: string + readonly provider: string + readonly context: 'fresh' | 'fork' + readonly phase: TeamMemberPhase + readonly error?: string +} + +export type TeamTaskStatus = 'pending' | 'in_progress' | 'completed' | 'deleted' + +export interface TeamTaskSnapshot { + readonly id: TeamTaskId + readonly revision: number + readonly subject: string + readonly description: string + readonly status: TeamTaskStatus + readonly ownerId?: SessionId + readonly blockedBy: TeamTaskId[] + readonly writeScopes: string[] +} + +export interface TeamMessageSnapshot { + readonly id: TeamMessageId + readonly senderId: SessionId + readonly senderName: string + readonly targetId: SessionId + readonly delivery: 'quiet' | 'wakeup' + readonly content: ContentBlock[] +} +``` + +[`packages/experimental/agent-team/src/types.ts:43-107`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/experimental/agent-team/src/types.ts#L43-L107) The four event wrappers, including delivered-message fields, are at [`packages/experimental/agent-team/src/types.ts:203-218`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/experimental/agent-team/src/types.ts#L203-L218). + +Code dispatch and workflow event aliases expand as follows. Workflow IDs are branded strings; the terminal unions below are their complete durable value sets: + +```ts +export interface CodeDispatchStartEventData { + rootCallId: CallId + parentCallId: CallId + subCallId: CallId + name: string + arguments: unknown +} + +export interface CodeDispatchEventData extends CodeDispatchStartEventData { + isError: boolean + content: ContentBlock[] +} + +export interface ToolWorkflowRunStartData { + readonly runId: WorkflowRunId + readonly name: string +} + +export interface ToolWorkflowAgentStartData { + readonly runId: WorkflowRunId + readonly seq: number + readonly label: string + readonly phase?: string + readonly childId: SessionId +} + +export type WorkflowAgentOutcome = 'completed' | 'failed' | 'cancelled' + +export interface ToolWorkflowAgentEndData { + readonly runId: WorkflowRunId + readonly seq: number + readonly outcome: WorkflowAgentOutcome +} + +export type WorkflowStopReason = 'completed' | 'cancelled' | 'error' + +export interface ToolWorkflowRunEndData { + readonly runId: WorkflowRunId + readonly stopReason: WorkflowStopReason +} +``` + +[`packages/core/tools/src/types.ts:10-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/tools/src/types.ts#L10-L23) [`packages/workflow/tool-workflow/src/types.ts:13-39`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/tool-workflow/src/types.ts#L13-L39) [`packages/workflow/workflow/src/types.ts:57-63`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/workflow/src/types.ts#L57-L63) [`packages/workflow/workflow/src/types.ts:97-110`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/workflow/src/types.ts#L97-L110) + +The DeepSeek search event contains this exact secret-free provider request: + +```ts +export interface DeepSeekSearchLlmRequest { + readonly endpoint: string + readonly apiVersion: string + readonly body: { + readonly model: string + readonly max_tokens: number + readonly messages: readonly [{ + readonly role: 'user' + readonly content: readonly [{ + readonly type: 'text' + readonly text: string + }] + }] + readonly tools: readonly [{ + readonly type: 'web_search_20250305' + readonly name: 'web_search' + readonly max_uses: number + }] + } +} +``` + +[`packages/web/web-search-deepseek/src/provider.ts:52-78`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/web/web-search-deepseek/src/provider.ts#L52-L78) + +The remaining named scalar and terminal aliases in the catalog are: + +```ts +export type InboxTarget = 'next-turn' | 'next-step' +export type CommandSource = { kind: 'user' } +export type HookDialect = 'claude-code' | 'codex' +export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access' +export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue } + +export type AgentCancelCause = + | { readonly kind: 'user' } + | { readonly kind: 'parent' } + | { readonly kind: 'hook'; readonly reason: string } + | { readonly kind: 'disposed' } + +export type TurnEndCancelCause = AgentCancelCause | { readonly kind: 'legacy' } + +export interface TurnEndReasonMap { + completed: { kind: 'completed' } + aborted: { kind: 'aborted'; reason: TurnEndCancelCause } + blocked: { kind: 'blocked' } + error: { kind: 'error'; error: LlmFailure } + 'max-tokens': { kind: 'max-tokens' } + interrupted: { kind: 'interrupted' } +} +``` + +`CommandSource` is written as its current single merged variant; the source declares it through `CommandSourceMap`. All catalog IDs such as `SessionId`, `MessageId`, `CallId`, `CommandId`, `CompactionId`, `RetryId`, `TeamId`, `TeamTaskId`, `TeamMessageId`, and `WorkflowRunId` are opaque branded strings rather than nested records. [`packages/core/agent/src/types.ts:7-10`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/agent/src/types.ts#L7-L10) [`packages/interaction/commands/src/types.ts:59-70`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/interaction/commands/src/types.ts#L59-L70) [`packages/hooks/hook-protocol/src/types.ts:43-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/hooks/hook-protocol/src/types.ts#L43-L48) [`packages/sandbox/sandbox/src/index.ts:23-29`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/sandbox/sandbox/src/index.ts#L23-L29) [`packages/core/session/src/json.ts:1-13`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/json.ts#L1-L13) [`packages/core/session/src/types.ts:21-31`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L21-L31) [`packages/core/session/src/types.ts:142-177`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L142-L177) [`packages/llm/llm/src/brand.ts:13-39`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm/src/brand.ts#L13-L39) [`packages/interaction/commands/src/brand.ts:13-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/interaction/commands/src/brand.ts#L13-L20) [`packages/compaction/compaction/src/brand.ts:1-4`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction/src/brand.ts#L1-L4) [`packages/llm/llm-retry/src/brand.ts:1-4`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm-retry/src/brand.ts#L1-L4) [`packages/experimental/agent-team/src/types.ts:7-40`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/experimental/agent-team/src/types.ts#L7-L40) [`packages/workflow/workflow/src/types.ts:9-21`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/workflow/workflow/src/types.ts#L9-L21) + +The store does not treat events as opaque blobs. It validates contiguous `seq`, JSON payloads, known or ignorable event types, surface transitions, turn balance, and repairable tails; the physical backends also parse or pack known record shapes. Within those rules, payload data is preserved losslessly. There is no independent event ID for deduplication, so sequence position and the expected next sequence are the append identity. [`packages/session/session-persistence/README.md:25-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L25-L30) [`packages/core/session/src/types.ts:395-440`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L395-L440) [`packages/session/session-persistence-jsonl/src/format.ts:210-224`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/format.ts#L210-L224) + +### Format and schema evolution + +`SESSION_FORMAT_VERSION` is `0`. The source explicitly states that the prerelease format provides no compatibility or migration promise. Readers reject another header version. Structural changes to the header, envelope, core event semantics, or surface mechanism require a monotonic format bump; adding an ordinary event type relies on `ignorable`. An unknown event without `ignorable: true` refuses reconstruction. [`packages/core/session/src/types.ts:33-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L33-L56) [`packages/core/session/src/types.ts:408-440`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L408-L440) + +The coordinator contains narrow in-version import normalization for older prerelease records, including deterministic legacy message IDs, removed steering records, and older turn shapes. Reads expose the normalized view, but storage stays append-only and old rows are not rewritten. This is explicitly not a general version migration system. [`packages/session/session-persistence/README.md:38-40`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L38-L40) [`packages/session/session-persistence/src/coordinator.ts:273-572`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/coordinator.ts#L273-L572) + +JSONL rejects another compression suffix, legacy flat layouts, and mixed roots; it provides no migration or dual write. SQLite has application ID `0x44534850` and physical schema version `17`; it opens schema ownership under `BEGIN IMMEDIATE` and rejects incompatible, foreign, or unversioned databases instead of migrating them. [`packages/session/session-persistence-jsonl/README.md:34-38`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L34-L38) [`packages/session/session-persistence-sqlite/src/schema.ts:17-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/schema.ts#L17-L20) [`packages/session/session-persistence-sqlite/src/schema.ts:107-149`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/schema.ts#L107-L149) + +Projection schema evolution is deliberately disposable. The cache domain version drops the whole cache and a projection unit version drops only that row, because replay from the event log can rebuild both. [`packages/session/session-projection-cache/src/spec.ts:16-69`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/spec.ts#L16-L69) + +## Compaction and history management + +Compaction is logical surface replacement, not durable-log truncation. A summarizing transaction appends `compaction/start`, obtains and records `compaction/summary`, immediately appends a new `user/message` with `surfaceOp: { op: 'replace', start, end }`, and appends `compaction/end`. The replacement cites the start, summary, and all shadowed surface sequences. [`packages/compaction/compaction-basic/src/region.ts:152-254`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction-basic/src/region.ts#L152-L254) [`packages/compaction/compaction-basic/src/region.ts:426-477`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction-basic/src/region.ts#L426-L477) + +The underlying `SessionEvent[]`, JSONL artifact, or SQLite event rows still contain the shadowed events and the replacement facts. `Session.surface` applies the replacement, and `deriveMessages()` emits only the resulting visible nodes. Resume reconstructs the same surface by folding operations in sequence. [`packages/core/session/src/types.ts:339-379`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L339-L379) [`packages/core/session/src/index.ts:701-747`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L701-L747) + +`compaction/summary` retains the exact summary blocks, replaced range and sequence set, estimated shadow price, provider, model, optional token cap and usage, and optional raw output. A separate `compaction/prune` carries equivalent shadow-price facts for model-free pruning. Both remain log-only. [`packages/compaction/compaction/src/types.ts:16-89`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction/src/types.ts#L16-L89) + +There is no backend historical compaction. JSONL files and SQLite rows grow with every logical replacement, and SQLite explicitly lists no background historical compaction. This preserves audit and replay provenance but does not bound storage. [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) [`packages/session/session-persistence-sqlite/README.md:55-63`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L55-L63) + +## Rewind, checkpoints, and fork + +There is no destructive rewind or undo operation in `SessionPersistence`. A caller can read an earlier prefix, but no service method changes the active transcript pointer or deletes later events. Persistence checkpoints are durability barriers, and projection checkpoints are fold caches, not execution or filesystem snapshots. [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) [`packages/session/session-projection/src/index.ts:88-118`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection/src/index.ts#L88-L118) + +Fork is a live-session operation. `SessionStore.fork()` selects an inclusive, contiguous prefix of a currently live source, rejects a boundary inside an open turn, creates a new child session, copies the selected event objects into the child's seed, and records `parentSession` plus `seedLength`. The child has its own complete event stream rather than a reference to a shared stored prefix. [`packages/core/session/src/index.ts:1067-1138`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1067-L1138) + +Fork does not capture filesystem state, sandbox state, environment state, or tool side effects. It branches only the event history and selected immutable metadata. Any reproducibility of external state must come from other systems. [`packages/core/session/src/index.ts:1081-1094`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1081-L1094) [`packages/core/session/src/types.ts:58-99`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L58-L99) + +## Subagents and nested sessions + +Session-backed subagents are independent sessions, not nested rows inside a parent log. Their header records `parentSession`, `origin: 'subagent'`, monotonically increasing `delegationDepth`, inherited CWD and preset, and seed length. [`packages/subagent/subagent/src/child-agent.ts:85-120`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/child-agent.ts#L85-L120) + +A continuable child receives a UUID-based session ID, its own `Session`, a required persistence seam, and one version-2 `subagent/descriptor` event that preserves provider and resumable composition. The descriptor is log-only and survives surface compaction. [`packages/subagent/subagent/src/continuation.ts:394-475`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L394-L475) [`packages/subagent/subagent/src/descriptor.ts:28-88`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/descriptor.ts#L28-L88) + +Orderly process-local shutdown uses `drainContinuableDescendants()`. It closes admission below the exact live parent trees before awaiting, waits for already admitted materializations, propagates cancellation top-down, and releases `AgentHandle`s child-first. Each activation waits for idle and attempts a final session flush, but that flush is best effort so failure cannot pin ancestor ownership. Unrelated parent trees remain live. [`packages/subagent/subagent/src/index.ts:294-325`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/index.ts#L294-L325) [`packages/subagent/subagent/src/continuation.ts:746-841`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L746-L841) [`packages/subagent/subagent/src/continuation.ts:1332-1394`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L1332-L1394) + +This drain releases process-local activations, not persisted sessions. Child artifacts and their lineage survive orderly teardown. At this snapshot, the drain path appends no durable child-disposition fact, and no persistence operation records a disposition for a hard process crash, parent terminal state, rewind, or delete. Those cases therefore leave durable child artifacts untouched unless out-of-band maintenance removes them. [`packages/subagent/subagent/src/index.ts:294-325`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/index.ts#L294-L325) [`packages/subagent/subagent/src/continuation.ts:1332-1394`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L1332-L1394) [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) + +Nesting has an optional per-delegation `maxDepth` policy rather than a universal storage limit. Child depth is parent depth plus one, the persisted header is a monotone floor after resume, and creation rejects a child beyond the supplied cap or safe-integer range. [`packages/subagent/subagent/src/child-agent.ts:38-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/child-agent.ts#L38-L56) [`packages/subagent/subagent/src/depth.ts:18-49`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/depth.ts#L18-L49) + +Child enumeration merges live sessions with persistent headers and derives descriptor projections, again preferring live state. Direct children and recursive descendants are discovered by following `parentSession`, not by scanning embedded parent messages. [`packages/subagent/subagent/src/list-children.ts:117-180`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/list-children.ts#L117-L180) [`packages/subagent/subagent/src/list-children.ts:183-239`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/list-children.ts#L183-L239) + +One-shot providers are not uniformly session-backed. The durable descriptor applies specifically to session-backed children. A provider that executes remotely without a local child `Session` does not automatically produce a local persistence record. [`packages/subagent/subagent/src/descriptor.ts:1-19`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/descriptor.ts#L1-L19) + +No cascading delete exists because no delete API exists. Parent and child retention are independent backend artifacts. Out-of-band removal of a parent can leave children whose `parentSession` no longer resolves; out-of-band removal of a child can leave durable parent or workflow facts referring to it. This is an inference from immutable lineage plus the absent deletion operation. [`packages/core/session/src/types.ts:58-99`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L58-L99) [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) + +## Retention, deletion, and multi-host + +The persistence seam has no deletion or retention API. JSONL says files accumulate until removed externally; SQLite says normal appends are insert-only and provides no deletion or background historical compaction. Persistent listing is unpaginated and unfiltered. [`packages/session/session-persistence/README.md:81-85`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L81-L85) [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) [`packages/session/session-persistence-sqlite/README.md:55-63`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L55-L63) + +The derived FTS index has a separate deletion behavior. When the persistence observation no longer contains a previously indexed session, reconciliation computes `persistentDeletes`, enters `BEGIN IMMEDIATE`, deletes that session's document and session rows, updates the observed generation, and commits the transaction. This removes missing sources from the search index without adding deletion to the authoritative persistence seam. [`packages/session-query/session-query-sqlite/src/index.ts:395-457`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query-sqlite/src/index.ts#L395-L457) [`packages/session-query/session-query-sqlite/src/index.ts:557-565`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query-sqlite/src/index.ts#L557-L565) + +The projection cache differs from the FTS index. It exposes no eviction or retention surface, so per-session projection rows accumulate until an operator prunes them out of band. [`packages/session/session-projection-cache/README.md:58-61`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/README.md#L58-L61) + +JSONL explicitly supports one live writer per session. The owning backend instance serializes operations, but a different process or backend instance must wait until that owner is quiescent. Only initial no-overwrite publication is cross-process collision-safe. [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) + +SQLite obtains database write locks with `BEGIN IMMEDIATE`, configures a busy timeout, and checks the current tail before committing. That protects individual database transactions but is not an application-level session lease. The coordinator's revision check may repeatedly retry while an external writer changes a log, and the source notes that this freshness mechanism adds no cross-process exclusion. [`packages/session/session-persistence-sqlite/src/store.ts:173-239`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L173-L239) [`packages/session/session-persistence/README.md:46-59`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L46-L59) + +The runtime `SessionStore` remains process-local, the JSONL owner rule is single writer, and the FTS index is single-owner. DeepSeek Harness therefore supplies local durability and cold resume, not a distributed active-active session service. This is an inference from the explicit ownership and concurrency boundaries. [`packages/core/session/src/index.ts:786-794`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L786-L794) [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) [`packages/session-query/session-query-sqlite/README.md:52-57`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session-query/session-query-sqlite/README.md#L52-L57) + +No source-backed network-filesystem lease, host failover protocol, remote writeback queue, or distributed crash detector was found. JSONL relies on local filesystem primitives such as hard links, rename, fsync, and a single live writer, so network-filesystem semantics remain an explicit gap rather than a supported multi-host path. [`packages/session/session-persistence-jsonl/src/index.ts:513-625`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L513-L625) [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) + +## Interop with foreign session stores + +The seam is pluggable at the logical `SessionHeader` plus contiguous `SessionEvent[]` boundary, so a third-party backend can implement the abstract service or the coordinator's backend hooks. It must still honor current format refusal, JSON serializability, contiguous sequences, cold repair, immutable inspection, trustworthy revisions, and live-session exclusion. [`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) [`packages/session/session-persistence/README.md:46-59`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L46-L59) + +There is no generic import/export protocol for another product's transcript, no foreign schema adapter, and no migration API. JSONL alone exposes exact raw artifact text through `readRaw`; SQLite intentionally does not expose a per-session raw artifact. Logical export is available only by reading headers and events and translating them externally. [`packages/session/session-persistence/src/index.ts:89-124`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L89-L124) [`packages/session/session-persistence-jsonl/src/index.ts:252-281`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/src/index.ts#L252-L281) [`packages/session/session-persistence-sqlite/src/index.ts:52-132`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/index.ts#L52-L132) + +Because the payload map is merge-extensible, a foreign consumer needs the complete non-ignorable plugin vocabulary present in the log. An unknown event marked `ignorable: true` is deliberately skippable; an unknown event without that marker must refuse reconstruction. Silently treating only core messages as the session would lose approvals, policy, goals, schedules, lineage descriptors, compaction operations, and other required reconstruction state. [`packages/core/session/src/types.ts:230-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L230-L240) [`packages/core/session/src/types.ts:408-440`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L408-L440) [`docs/persistence-catalog.md:1-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1-L20) + +## What this implies for our Session Store (our inference) + +Our inference: a stored DeepSeek Harness session comes into existence when its first event materializes an immutable header plus a contiguous event prefix. Its durable identity is that header and log, while messages, summaries, titles, listing state, projection checkpoints, and search indexes are replayable views. [`packages/session/session-persistence/src/index.ts:126-143`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L126-L143) [`packages/session/session-persistence/README.md:5-7`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L5-L7) + +1. **Keep the durable log authoritative and projections disposable.** DeepSeek Harness has a clean failure model because transcript views, titles, child summaries, listing metadata, and FTS search can be recomputed from the same ordered event stream. We should preserve this one-way dependency and never let a projection cache become an alternate write authority. [`packages/session/session-projection-cache/src/index.ts:1-12`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/index.ts#L1-L12) + +2. **Keep DeepSeek's two commit points explicit at integration boundaries, but do not copy them into our authority model.** A DeepSeek session can acknowledge an in-memory append before backend durability, so an adapter must state whether it observed live acceptance or the persistence barrier. ADR 0035 instead keeps our authoritative success boundary at the durable append; transient UI progress remains a non-authoritative projection. [`packages/core/session/src/index.ts:569-655`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L569-L655) [`packages/core/session/src/index.ts:1009-1039`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1009-L1039) [ADR 0035](../../../../adr/0035-session-store-decider-aggregate.md) + +3. **Use a typed envelope and require explicit skippability.** A format version alone cannot safely govern a plugin-extensible event vocabulary. The `ignorable: true` rule is a strong pattern: unknown required facts stop resume instead of silently producing a plausible but wrong state. [`packages/core/session/src/types.ts:408-440`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L408-L440) + +4. **Model repair as append-only semantic closure.** Preserving complete facts, dropping only torn bytes, and appending typed assistant interruption closure is safer than truncating to the last successful turn. An unmatched durable tool call must remain outcome-unknown until the operation ledger reconciles it rather than being rewritten as a failure. [`packages/session/session-persistence/README.md:25-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L25-L30) [`packages/core/session/src/repair.ts:89-123`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/repair.ts#L89-L123) + +5. **Treat local JSONL or SQLite as a recovery artifact, not the product-wide session identity authority.** DeepSeek Harness assumes a local process owner and provides no authorization, tenant scope, lease, or distributed ordering layer. Our store needs those concerns above or inside its durable service if sessions can move across workers or hosts. [`packages/session/session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) [`packages/session/session-persistence/README.md:46-59`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L46-L59) + +6. **Use DeepSeek Harness as evidence for explicit fork lineage, not as our storage design.** Its copied-prefix child validates immutable parent and boundary metadata, but ADR 0035 Decision 5 already requires atomic, self-contained fork creation whose inherited conversation prefix is resolved by explicit reference in the context projection. Physical O(history) copies and content-addressed snapshot sharing are rejected. [`packages/core/session/src/index.ts:1067-1138`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1067-L1138) [`docs/adr/0035-session-store-decider-aggregate.md:938-1025`](../../../../adr/0035-session-store-decider-aggregate.md) + +7. **Treat compaction as model-view reduction over a keep-forever log.** DeepSeek Harness's non-truncating surface replacement validates ADR 0035 Decision 7. Our Session log is never truncated or purged; replay is bounded by snapshots, while read-time `RedactionApplied` and `ArtifactErased` handle privacy without removing event facts. [`packages/compaction/compaction-basic/src/region.ts:426-477`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction-basic/src/region.ts#L426-L477) [`docs/adr/0035-session-store-decider-aggregate.md:1159-1247`](../../../../adr/0035-session-store-decider-aggregate.md) + +8. **Keep lifecycle visibility and privacy separate from byte deletion.** DeepSeek Harness exposes the cost of having no durable lifecycle API, but ADR 0035 already settles our path: `SessionHidden` removes default visibility, `RedactionApplied` masks event content, and `ArtifactErased` destroys referenced artifact bytes while the Session log remains keep-forever. Erasure-grade deletion beyond masking remains a named follow-up, not an asynchronous purge proposal. [`packages/session/session-persistence/README.md:81-85`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L81-L85) [`docs/adr/0035-session-store-decider-aggregate.md:1169-1222`](../../../../adr/0035-session-store-decider-aggregate.md) + +## Open questions + +- What is the intended stable migration path after prerelease `SESSION_FORMAT_VERSION = 0`, especially for logs carrying plugin-defined required events? [`packages/core/session/src/types.ts:33-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L33-L56) +- Will the persistence seam gain deletion, retention, pagination, and filtered listing, or remain deliberately local with out-of-band administration? [`packages/session/session-persistence/README.md:81-85`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L81-L85) +- Is SQLite expected to support multiple cooperating runtime processes, or are its lock and revision checks only defensive access around a single application owner? [`packages/session/session-persistence/README.md:46-59`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L46-L59) +- How will DeepSeek Harness bound physical event-log growth while preserving the current audit and reconstruction guarantees of surface compaction? [`packages/session/session-persistence-sqlite/README.md:55-63`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L55-L63) +- Should a future portable export package the complete required plugin event vocabulary, known ignorable records, and attachment references, rather than only a raw JSONL artifact or reconstructed messages? [`docs/persistence-catalog.md:1-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1-L20) diff --git a/docs/research/session-store/products/deepseek-harness/vs-session-events.md b/docs/research/session-store/products/deepseek-harness/vs-session-events.md new file mode 100644 index 0000000000..18b2c747db --- /dev/null +++ b/docs/research/session-store/products/deepseek-harness/vs-session-events.md @@ -0,0 +1,388 @@ +# DeepSeek Harness compared to our session event catalog + +Part of Session Store Research. +Produced by running [RESEARCH_PROMPT_COMPARISON](../../RESEARCH_PROMPT_COMPARISON.md). +Stage-one dossier: [DeepSeek Harness](./index.md). +Compared against `proto/trogonai/session/sessions/v1alpha1/` and +[ADR#0035](../../../../adr/0035-session-store-decider-aggregate.md) on +2026-08-20. Upstream is pinned to commit +[`141eb6fef83422698aef7a981029e843e8161534`](https://github.com/deepseek-ai/deepseek-harness/tree/141eb6fef83422698aef7a981029e843e8161534), +tagged `dsh-v0.1.0-rc.8`. + +**Store maturity: 5/12** - evolution scars 1/3 (the logical format remains +version `0`, limited old records are normalized in memory, SQLite is already at +physical schema `17`, and incompatible schemas are rejected rather than +migrated: [`types.ts:33-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L33-L56), +[`README.md:38-40`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L38-L40), +[`schema.ts:17-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/schema.ts#L17-L20), +[`schema.ts:107-149`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/schema.ts#L107-L149)); +operational age 0/3 (the persistence seam first landed on 2026-06-15 in +[`df4b7d3`](https://github.com/deepseek-ai/deepseek-harness/commit/df4b7d3d9adf43bdf913e17b502f042f80379394), +only 66 days before this retrieval, entirely within the prerelease window); +exposure 1/3 (it is officially distributed through `npx`, but remains a +compatibility-breaking developer preview and its first-party stores are local, +single-owner designs: +[`README.md:9-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/README.md#L9-L23), +[`session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77)); +design independence 3/3 (inference from repository history: the persistence +code was introduced as first-party DeepSeek Harness code in `df4b7d3`, with +JSONL and later SQLite implementations, not inherited from a store fork). + +Because this score is under 6, a recommendation supported only by DeepSeek +Harness is labelled **thin evidence**. It is not treated as an industry norm. + +## The one structural difference everything else follows from + +DeepSeek Harness has two commit points. `Session.append()` synchronously commits +a fact to the process-local live session, then observers enqueue it for +write-behind persistence. A persistence `append()` acknowledges only after the +batch is durable, but failure there does not undo the already-acknowledged live +fact. +[`packages/core/session/src/index.ts:569-655`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L569-L655) +[`packages/session/session-persistence/src/write-behind.ts:18-56`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/write-behind.ts#L18-L56) + +Our Session has one authoritative commit point: the durable JetStream append to +one session subject. [ADR#0035 decision 2](../../../../adr/0035-session-store-decider-aggregate.md#2-append-only-mutation-opaque-identity-ordinal-anchors-and-per-command-optimistic-concurrency) +classifies each command as `NoStream`, `At(current_position)`, or `Any`; success +means the selected durable write completed. This difference explains the rest: + +- DeepSeek sequence allocation and live exclusion are process-local first, with + backend tail checks as a second defense. Our optimistic concurrency is at the + authoritative append boundary. +- DeepSeek repairs a cold durable log to catch it up to semantic state after a + process crash. Our log already contains every acknowledged fact, so recovery + is a reconciler over durable history rather than a second commit domain. +- DeepSeek can use a mutable in-memory surface and copy fork prefixes because a + `Session` object owns the immediate view. Our projections must be deterministic + over immutable cross-stream facts. + +DeepSeek narrows the risk of its two commit points with a separate checkpoint +policy. First-party persisted runtimes flush before model dispatch, before a +top-level tool body may cause an external side effect, and before the next step. +The plugin fails those boundaries closed, but a deployment may omit it. +[`packages/session/session-checkpoint-policy/README.md:5-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-checkpoint-policy/README.md#L5-L23) +Our durable operation ordering is structural instead: `OperationReserved` +records `operation_id`, `request_digest`, and `operation_kind` before a guarded +side effect, at the same authoritative append boundary used by every command. + +## Mapping + +The [stage-one field table](./index.md#complete-payload-catalog-at-the-snapshot) +is the field-level source of record: it transcribes each event's complete `data` +payload and expands aliased nested values immediately below the table. The rows +here map every one of those event types and call out the payload fields that do +not map together. Physical SQLite packing adds no logical facts. +[`docs/persistence-catalog.md:1-20`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1-L20) + +### Header, envelope, and store mechanics + +| DeepSeek Harness persisted fact | Our equivalent | Verdict | +| --- | --- | --- | +| Header `version` | Concrete protobuf type name plus `v1alpha1` package policy in `proto/trogonai/session/sessions/v1alpha1/events.proto` | Semantic mismatch. We have no per-session format integer; promotion requires a written compatibility policy. | +| Header `id` | `session_id` and the session subject | Equivalent opaque identity. | +| Header `createdAt` | No payload equivalent in `proto/trogonai/session/sessions/v1alpha1/session_started.proto` | Gap. Stream append time is not the same occurrence fact. | +| Header `cwd` | `SessionStarted.workspace` as `WorkspaceRef` | Ours is typed and portable rather than a raw local path. | +| Header `parentSession` | `ParentLinked.parent_session_id` for delegation, or `SessionForked.source_session_id` for branching | Semantic mismatch. DeepSeek overloads one header field; we distinguish delegation from fork ancestry. | +| Header `seedLength` | `SessionForked.context_prefix_boundary` | Semantic mismatch. DeepSeek counts copied child events; ours addresses an immutable source prefix by reference. | +| Header `origin` | Presence of `ParentLinked` | Ours derives the classification from a durable relationship. | +| Header `delegationDepth` | Projection over `ParentLinked` ancestry | DeepSeek stores a denormalized depth; ours can derive it and avoids drift. | +| Header `agentPreset` | `SessionStarted.execution_plan.plan_bytes` and `plan_digest` | Ours stores the complete immutable execution plan, not a preset label. | +| Event `type`, `data` | One concrete protobuf event type and its typed fields | Ours is closed and schema-validated; DeepSeek is declaration-merge extensible. | +| Event `seq` | `SessionOrdinal.value` in `proto/trogonai/session/sessions/v1alpha1/session_ordinal.proto`, fold-derived and 1-indexed | Equivalent ordering, different base and ownership. DeepSeek has no independent event id. | +| Event `time` | Append metadata, plus typed occurrence timestamps only where event time matters | Ours deliberately does not put a generic wall clock in every payload. | +| Event `ignorable` | No direct equivalent | DeepSeek lets unknown informational events be skipped. Our concrete event type and compatibility policy fail closed at the storage boundary. | +| Event `sourceEventSeqs` | Domain-specific joins and digests, such as `Compacted.covered_input_digest` | Ours rejects a generic provenance list in favor of typed evidence. | +| Event `surfaceOp` | No generic equivalent. `Compacted`, `SessionRewound`, and `RedactionApplied` alter effective model-visible history through separate typed semantics. | Semantic mismatch. DeepSeek has a general append-or-replace surface algebra; ours keeps compaction, rewind, and privacy masking distinct. | +| SQLite `incarnation`, `revision`, and JSONL source-qualified revision | JetStream stream identity and current position | Both are backend freshness values, not session facts. DeepSeek revisions protect prepare/inspect freshness, not command-level CAS. | +| Checkpoint-policy flush before model dispatch, top-level tool side effects, and the next step | Durable fact append, especially `OperationReserved` before a side effect | Same ordering goal, different enforcement. DeepSeek policy is an optional plugin over write-behind; ours makes the reservation an aggregate fact. [`checkpoint-policy/README.md:5-23`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-checkpoint-policy/README.md#L5-L23) | +| Projection cache domain version, unit `stateVersion`, watermark, and payload | Rebuildable projection state outside the Session stream | Equivalent. Both treat projection state as disposable. [`session-projection-cache/src/spec.ts:16-69`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-projection-cache/src/spec.ts#L16-L69) | +| FTS query rows | External read model | Equivalent. Neither makes search index state authoritative. | + +### Event payload catalog + +| DeepSeek Harness event type | Our equivalent | Verdict | +| --- | --- | --- | +| `agent/inbox/spliced {target, start, removedCount?, inserted, outcome?}` | No equivalent | Every payload field belongs to a mutable pending-inbox splice; our stream records admitted messages instead. [`catalog:101-122`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L101-L122) | +| `agent-preset/selected {agentPreset}` | Immutable `StoredSessionExecutionPlan` in `SessionStarted` | Semantic mismatch: changing execution identity means a new session or fork for us. [`catalog:124-140`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L124-L140) | +| `approval/asked {id, toolName, callId?, reason?}` and `approval/decided {id, outcome}` | No direct equivalent. `ToolCallApproved` and `ToolCallDenied` overlap only approved and denied tool outcomes. | DeepSeek owns a separate `ApprovalRequestId` audit lifecycle whose outcomes also include cancelled and unavailable. `ToolCallRequested` is model intent, not an approval request. [`catalog:142-183`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L142-L183) | +| `approval/policy {policy, source?}` | No typed Session event; at most an opaque policy/configuration binding inside the immutable plan | Semantic mismatch. DeepSeek can change an explicit approval-policy snapshot in one session and records delegation as the source. [`catalog:185-207`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L185-L207) | +| `assistant/chunk {turn, step, chunk}` and `assistant/message {turn, step, message, usage?, interrupted?}` | `turn_id`, `AssistantMessageStarted`, `AssistantMessageCompleted`, `AssistantMessageFailed`, and `TokenUsage`; no step number or token delta | Completed message and usage map. DeepSeek can retain a provider-visible partial message and mark it interrupted in one event; our mutually exclusive completed or failed terminal facts cannot express both, so partial interrupted content is a gap. Ours deliberately omits raw chunks. [`catalog:209-244`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L209-L244) | +| `command/run {commandId, name, args?, source}` and `command/done {commandId, kind, text?, sourceEventSeq?}` | No direct equivalent | These are slash-command handler audit facts, not tool execution. Mapping them to `ToolCallRequested` would invent model intent. [`catalog:246-287`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L246-L287) | +| `compaction/start {compactionId, sourceCommandId?, turn}`, `summary {summary, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage?, rawOutput?, llmStreamCall?}`, `prune {shadowedRange, shadowedSeqs, shadowedTokenCount}`, and `end {compactionId, sourceCommandId?, turn, error?}` | `Compacted` maps summary, inclusive range, model, and usage; `summary_id`, `trigger`, `context_root`, `producer`, and `covered_input_digest` are ours only | DeepSeek records the procedure and mutable replacement provenance. We record one validated result and do not retain command id, raw output, or a start/end bracket. [`catalog:289-398`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L289-L398) | +| `feedback/record {text}` | No typed equivalent | Gap. `SystemNoticeRecorded` would preserve text but lose feedback semantics. [`catalog:400-414`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L400-L414) | +| `goal/change` with version, operation, goal snapshot and timestamps, or clear tombstone | No Session event equivalent | Deliberate boundary until goal ownership is assigned; none of its durable goal identity, revision, round, or tombstone fields map. [`goal/domain.ts:13-44`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/goal/goal/src/domain.ts#L13-L44) | +| `hook/invoked {turn, point, dialect, matcher?, handlerId}` and `hook/result {turn, point, handlerId, decision, exitCode?, stderrSummary?, durationMs}` | Only `turn_id` correlates; no typed hook lifecycle | Gap if hook identity, policy decision, exit, stderr, and duration must be session audit facts. [`catalog:431-479`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L431-L479) | +| `llm/retry {retryId, turn, step, provider, mode, policyKey, retry, maxRetries?, delayMs, failure}` and `llm/retry-started {retryId, turn, step, retry}` | `turn_id` and assistant failure overlap; no model-request retry event | Execution attempts are harness attempts, not provider retries. Retry identity, policy, attempt count, delay, and transition are gaps, but this store is not evidence for copying their exact schema. [`llm-retry/types.ts:15-48`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/llm/llm-retry/src/types.ts#L15-L48) | +| `permission/preset {preset}`, `plan/mode {active}`, `sandbox/mode {mode, source?}` | No typed Session events; the immutable execution plan and configuration may bind related policy | Deliberate semantic mismatch: DeepSeek mutates explicit runtime modes inside one session; our catalog fixes plan identity without naming equivalent mutable fields. [`catalog:505-538`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L505-L538) [`catalog:570-591`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L570-L591) | +| `request/context {provider, model, contextWindow?}` and `request/header {header, reason}`, where header has config, adapterDefaults?, system?, tools? | The immutable [`SessionExecutionPlan`](../../../../adr/0031-agent-implementation-and-session-plan.md) maps the resolved provider route, protocol, driver, connection, and non-secret binding; `AssistantMessageStarted` maps model and settings | Gaps are advertised context window, exact rendered prompt, effective adapter defaults, tool schemas, and initial/resume/change reason. [`types.ts:196-228`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L196-L228) | +| `schedule/change` version-1 create, delete, or dispatch over `{id, kind, prompt, scheduledAt}` plus after/every interval and dispatch time | No Session event equivalent | Deliberate boundary: none of the schedule rule, identity, or dispatch fields are part of this aggregate today. [`schedule/types.ts:9-105`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/schedule/schedule/src/types.ts#L9-L105) | +| `session/end-seed {}` | No general equivalent. `SessionForked` records the fork case only. | DeepSeek marks the end of a constructor seed for resume, fork, or replay. Our resume and replay are reads and append no lifecycle fact; fork is explicit atomic creation. [`types.ts:315-336`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L315-L336) | +| `session/title {title, messageSeqs, source}` and `session/title-llm-request {titleProvider, messageSeqs, route, system, messages, maxTokens}` | `SessionRenamed` maps the final title only | DeepSeek also retains source-message provenance and the complete derived title request; ours has no title-generation request audit. [`catalog:643-672`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L643-L672) | +| `step/start {turn, step}`, `step/end {turn, step}`, `turn/start {turn}`, `turn/end {turn, reason}` | `turn_id` on conversation and tool events; terminal message and tool facts | Turn identity maps, but our correlation does not store numeric step boundaries or one aggregate turn reason. [`catalog:674-696`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L674-L696) [`catalog:943-979`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L943-L979) | +| `subagent/descriptor {version, mode, provider, label?, agentProvider?, agentModel?, persona?, toolFilter?}` | Child `SessionStarted.execution_plan` maps composition and `ParentLinked` maps lineage; mode and label have no exact field | Ours separates executable identity from lineage. DeepSeek preserves one-shot or continuable mode and an enumeration label but has no parent-side dispatch fact. [`descriptor.ts:41-88`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/descriptor.ts#L41-L88) | +| `team/member {version, teamId, member}`, `team/task {version, teamId, task}`, `team/message/queued {version, teamId, message}`, `team/message/delivered {version, teamId, messageId, targetId}` | No Session event equivalent | The nested snapshots retain member identity, provider, context and phase; task revision, ownership, dependencies and write scopes; and mailbox sender, target, delivery and content. These are a deliberate team aggregate boundary for us. [`agent-team/types.ts:43-116`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/experimental/agent-team/src/types.ts#L43-L116) [`agent-team/types.ts:203-218`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/experimental/agent-team/src/types.ts#L203-L218) | +| `todo/write {todos[{content, status}]}` | `TodoUpdated.items[{id, content, status}]` plus `revision` | Equivalent whole-list snapshot; ours adds stable item ids and a monotonic revision. [`catalog:776-789`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L776-L789) | +| `tool/call {turn, step, callId, name, arguments}` and `tool/result {turn, step, message, error?, meta?}` | `ToolCallRequested`, `ToolCallStarted`, `ToolCallCompleted`, `ToolCallFailed` | Call identity, name, raw arguments, content, and error map. Ours adds execution id, approval, application-error status, duration, termination, artifacts, observed resources, and operation id; DeepSeek adds opaque tool presentation metadata and step number. [`catalog:791-806`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L791-L806) [`catalog:856-883`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L856-L883) | +| `tool/code-dispatch-start {rootCallId, parentCallId, subCallId, name, arguments}` and `tool/code-dispatch` with the same ids plus `isError, content` | `ToolCallRequested.parent_tool_use_id` plus nested tool lifecycle | Parent-child call correlation maps; DeepSeek also stores root identity and a specialized combined result. [`catalog:808-854`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L808-L854) | +| `tool-workflow/run-start {runId, name}`, `agent-start {runId, seq, label, phase?, childId}`, `agent-end {runId, seq, outcome}`, `run-end {runId, stopReason}` | Operation ledger and delegation facts map child dispatch and outcomes, but no workflow-run aggregate | Run identity, step sequence, label, phase, and aggregate stop reason have no direct equivalent. [`catalog:885-941`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L885-L941) | +| `user/message` with `id, role, content[], source` | `UserMessageRecorded.message` maps identity, role, and typed content; `turn_id` is ours only | DeepSeek's source attribution has no field in `CanonicalMessage`; ours adds message `created_at` and explicit turn correlation. [`catalog:981-998`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L981-L998) | +| `web/deepseek-search-llm-request {endpoint, apiVersion, body{model, max_tokens, messages, tools}}` | No provider-specific request event | Every provider request field is absent today; a generic request-envelope artifact would cover it without adding provider-specific schema. [`catalog:1000-1007`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/docs/persistence-catalog.md#L1000-L1007) | + +### Facts only our catalog records + +| Our facts | DeepSeek Harness position | Assessment | +| --- | --- | --- | +| `SessionClosed`, `SessionCancelled`, `SessionFailed`, `SessionHidden`, archive and unarchive | No durable session lifecycle or hide/archive API | Our lifecycle is materially stronger. | +| `SessionForked` and `SessionRewound` | Fork is copied header state; no rewind event or active-history pointer | Our history control is explicit and append-only. | +| `ExecutionAttemptStarted`, `ExecutionAttemptReady`, `ExecutionAttemptEnded`, `CheckpointProduced` | Process resume and projection checkpoints exist, but no durable execution-attempt aggregate or attested checkpoint | Our execution evidence is materially stronger. | +| `OperationReserved`, `OperationCancellationRequested`, `OperationOutcomeRecorded` | No general side-effect ledger | Our crash reconciliation does not infer operation identity from tool prose. | +| `DelegationDispatched`, `ParentLinked`, termination, history invalidation, and two-sided detach facts | One child header points at one parent; orderly process-local teardown drains descendants outside the durable store | Our durable lineage and cascade semantics are materially stronger. | +| `RedactionApplied`, `ArtifactErased` | No retention, redaction, or deletion API | Our decision 7 defines read-time privacy masks while retaining audit history. | +| `ArtifactRecorded`, `FileChanged`, `ResourceObservation` | Tool-private payloads can preserve some evidence, but no shared typed artifact and file ledger | Our event sizes, integrity checks, and erasure joins are stronger. | +| `ExternalDelegationDispatched` | A non-session-backed provider may leave no local child record | Our external delegation still records authorization and request evidence. | + +### Semantic mismatches that must not be mapped by name alone + +**Append.** DeepSeek's live append means accepted in memory; its persistence +append means durable. Our append acknowledgement is the authoritative durable +commit. A bridge must select one DeepSeek commit point explicitly. + +**Fork.** DeepSeek's `seedLength` addresses physically copied child events. Our +`SessionForked.context_prefix_boundary` addresses source-stream history that the +child inherits by immutable reference. Reusing the integer without the source id +changes its meaning. + +**Compaction.** DeepSeek's summary becomes active through a later generic +`surfaceOp.replace`. Our `Compacted` is itself a self-sufficient fact with exact +`covers_from`, `covers_through`, `context_root`, `producer`, and +`covered_input_digest`. A converter cannot map only the summary text. + +**Revision and OCC.** DeepSeek revisions invalidate stale prepared objects, and +SQLite checks the durable tail inside a transaction. Neither is a caller-visible +expected-version command precondition. Our `At(current_position)` is. +[`packages/session/session-persistence/README.md:46-59`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L46-L59) + +**Projection checkpoint.** DeepSeek projection checkpoints cache fold state. +Our `Checkpoint` is an attested, digest-bound execution checkpoint that may be +restored by a later execution attempt. The names are not interchangeable. + +## What we should consider changing + +### 1. Persist evidence of the exact rendered model request + +- **Change:** Add an optional `ArtifactRef request_envelope_ref` to + `proto/trogonai/session/sessions/v1alpha1/assistant_message_started.proto`, + with canonical bytes for the rendered system prompt, tool schemas, effective + adapter defaults, and other request-scoped envelope values not already bound + by the immutable plan or the event. Keep `model` and `settings` queryable in + the event and resolve route identity from the session's stored plan. +- **Evidence:** DeepSeek Harness, 5/12, persists those exact values in + `request/header`. + [`packages/core/session/src/types.ts:196-210`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/types.ts#L196-L210) +- **Blast radius:** Additive. +- **Judgment:** Consider it. This is **thin evidence** from DeepSeek alone, but + it identifies a real reproducibility gap: plan bytes plus model settings do + not prove which prompt and tool schemas were actually rendered for a call. +- **Cost:** One artifact write and digest verification per distinct request + envelope, privacy filtering for prompt content, artifact retention policy, + and a new failure mode when envelope storage succeeds or fails separately + from model dispatch. + +### 2. Add an assistant process-loss reason and safe crash-closure contract + +- **Change:** Add `ASSISTANT_MESSAGE_FAILURE_REASON_PROCESS_LOST` to + `AssistantMessageFailureReason` in `assistant_message_failed.proto`, then add + a recovery row to the ADR command matrix and backend conformance tests. A + reconciler should preserve every valid fact and may close an orphaned + assistant generation with that reason. It must not map an unmatched recorded + tool call to `ToolCallFailed`: when a reserved tool operation has no known + outcome, append `OperationOutcomeRecorded.unknown` and reconcile it before + claiming success or failure. Never truncate a complete fact. The current + assistant `INTERRUPTED` value means user steering, so `ERROR` is the only + honest existing assistant value until the new reason exists. +- **Evidence:** DeepSeek Harness, 5/12, drops only a torn final fragment and + appends semantic closure for a cold interrupted log. Its repair distinguishes + an assistant-declared call with no durable `tool/call` as `TOOL_NOT_STARTED` + from a durable `tool/call` with no result whose side effect may have occurred, + rendering the latter as `TOOL_OUTCOME_UNKNOWN` rather than declaring failure. + [`packages/session/session-persistence/README.md:25-30`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/README.md#L25-L30) + [`packages/core/session/src/repair.ts:89-123`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/repair.ts#L89-L123) +- **Blast radius:** Additive, with no migration: one protobuf enum value plus + new recovery-command, fold, projection, and operation-reconciliation + behavior. +- **Judgment:** Do it. The product evidence is **thin**, but the rule follows + our existing first-terminal-outcome fold and removes ambiguity from recovery. +- **Cost:** One enum value, a recovery detector, idempotency tests across + repeated repair, and an operation reconciliation path that may leave the + model-facing tool lifecycle open until the side effect is classified. + +### 3. Require one OCC conformance suite for every store implementation + +- **Change:** Make [ADR#0035 decision 2](../../../../adr/0035-session-store-decider-aggregate.md#2-append-only-mutation-opaque-identity-ordinal-anchors-and-per-command-optimistic-concurrency) + acceptance criteria require each backend to pass the same `NoStream`, `At`, + and `Any` race tests, including first creation, stale invariant transition, + commuting terminal facts, and cross-process writers. +- **Evidence:** DeepSeek Harness, 5/12, shares a coordinator but still documents + different concurrency limits: JSONL permits one live writer, while SQLite + uses `BEGIN IMMEDIATE` and tail checks without an application lease. + [`session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) + [`session-persistence-sqlite/src/store.ts:173-239`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/src/store.ts#L173-L239) +- **Blast radius:** Additive tests and release criteria. +- **Judgment:** Do it. DeepSeek alone is **thin evidence**, but it reinforces the + stronger cross-product conclusion already recorded in the + [stage-two synthesis](../../synthesis.md#stage-two-results-not-yet-absorbed-above). +- **Cost:** A reusable adversarial harness, at least two backend instances in + each test, deterministic race orchestration, and blocking any backend whose + advertised interface is stronger than its actual atomicity. + +### 4. Do not replace reference forks with copied prefixes + +- **Change:** Reject changing [ADR#0035 decision 5](../../../../adr/0035-session-store-decider-aggregate.md#5-fork-is-an-atomic-self-contained-creation-inheritance-is-by-explicit-reference) + or `proto/trogonai/session/sessions/v1alpha1/session_forked.proto` + to store the source prefix again on the child stream. +- **Evidence:** DeepSeek Harness, 5/12, copies an inclusive source prefix into + the child's seed and records `parentSession` plus `seedLength`. + [`packages/core/session/src/index.ts:1067-1138`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/core/session/src/index.ts#L1067-L1138) +- **Blast radius:** Breaking the decision, not the schema: decision 5, if + adopted. No change is needed to retain the current design. +- **Judgment:** Do not do it. The copied prefix makes each child self-contained, + but duplicates O(history) facts and makes privacy and artifact reachability + harder to reason about. Low maturity does not justify reversing our decision. +- **Cost:** Keeping the current design requires a cross-stream context + projection and source-prefix availability for the lifetime of the fork. + +### 5. Do not generalize `Compacted` into a mutable surface operation + +- **Change:** Reject a generic `surfaceOp { append | replace(start, end) }` on + conversation events. Keep `Compacted` in + `proto/trogonai/session/sessions/v1alpha1/compacted.proto` + as the only compaction operation and retain its digest, producer, context + root, and inclusive ordinal range. +- **Evidence:** DeepSeek Harness, 5/12, makes the model-visible summary active by + appending a `user/message` with a range replacement after the summary event. + [`packages/compaction/compaction-basic/src/region.ts:152-254`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction-basic/src/region.ts#L152-L254) + [`packages/compaction/compaction-basic/src/region.ts:426-477`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/compaction/compaction-basic/src/region.ts#L426-L477) +- **Blast radius:** Breaking the decision, not the schema: decision 4, if + adopted. No change is needed to retain the current design. +- **Judgment:** Do not do it. DeepSeek's surface algebra is compact, but a + generic replacement can become valid without proving which masked input was + summarized or which plan and attempt produced it. +- **Cost:** Our typed marker is larger and makes every compaction recalculate + `covered_input_digest`; its projection is less generic than DeepSeek's surface + fold. + +## What our design already does better + +**One durable authority.** A successful command cannot exist only in process +memory. `NoStream`, `At`, and `Any` express command intent at the same boundary +that assigns order. + +**Reference fork with explicit meaning.** `SessionForked` carries +`source_session_id`, inclusive `context_prefix_boundary`, and `reason`; the child +folds only its own stream. DeepSeek stores a complete copied log and a coarse +header boundary. + +**Self-sufficient compaction.** `Compacted` stores the summary, exact coverage, +trigger, context root, producing attempt and plan digest, and covered-input +digest. DeepSeek's replacement records range and source sequence provenance but +does not bind the active surface replacement to equivalent privacy-masked input +or execution-plan evidence. + +**Two-sided lineage and cascade.** Parent `DelegationDispatched` and child +`ParentLinked` join by `operation_id`, then typed parent termination, history +invalidation, and detach facts preserve why a child stopped or became +independent. DeepSeek has one child-to-parent header pointer. + +**Privacy lifecycle on a keep-forever stream.** `SessionHidden`, +`RedactionApplied`, and `ArtifactErased` define read-time masking and content +erasure without rewriting event history. DeepSeek has no delete or retention +operation at all. + +**Typed recovery and operation evidence.** Execution attempts, checkpoints, and +the operation ledger let recovery distinguish a retry from a new side effect. +DeepSeek repairs conversation shape well, but does not provide a general +side-effect reservation and reconciliation protocol. + +## Trade-offs, not gaps + +**Live responsiveness versus one commit boundary.** DeepSeek can update its UI +and model-visible surface without waiting for storage. It pays with an +acknowledged-live versus durable distinction. We pay durable append latency for +one unambiguous authority. + +**Copied fork versus reference fork.** A DeepSeek child can be read after its +parent artifact disappears. Our child stays small and preserves one copy of the +facts, but the context projection must resolve the source prefix. + +**Raw chunks versus coarse facts.** DeepSeek can replay token-level generation. +We avoid making high-volume deltas durable and accept that exact streaming +animation is not reconstructable from the Session stream. + +**Mutable runtime modes versus immutable execution identity.** DeepSeek can +change presets, permissions, plan mode, and sandbox mode inside one session. We +make an execution plan immutable, which improves audit and authorization at the +cost of requiring a new session or fork for a materially different runtime. + +## What not to copy + +- Do not present process-local acceptance as durable success. +- Do not use copied history as the persisted meaning of fork. +- Do not let a generic surface replacement bypass compaction provenance and + digest checks. +- Do not treat a schema-version integer as a migration system. DeepSeek's + version `0` and SQLite schema `17` currently reject incompatibility. +- Do not mistake best-effort process-local descendant drain for durable + cascade, or leave retention to out-of-band file or row deletion. +- Do not expose one pluggable store interface while allowing its backends to + disagree silently on concurrency guarantees. + +## The two gaps the industry has not closed + +### Subagent cascade + +DeepSeek Harness does not challenge [ADR#0035 decision 6](../../../../adr/0035-session-store-decider-aggregate.md#6-child-sessions-parent-first-dispatch-rewind-invalidation-distinct-from-termination-and-a-two-fact-detach-saga). +A session-backed child is an independent session with `parentSession` in its +header, and child discovery follows that pointer. During orderly process-local +shutdown, `SubagentRuntime` closes new admission, cancels continuable +descendants top-down, releases them child-first, and attempts a final flush. +That is real runtime cascade, but it appends no durable parent disposition and +does not delete child artifacts. A hard crash skips it. The persistence API has +no delete cascade, parent-termination, rewind-invalidation, or detach operation; +out-of-band parent removal can therefore still leave an orphan. +[`packages/subagent/subagent/src/child-agent.ts:85-120`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/child-agent.ts#L85-L120) +[`packages/subagent/subagent/src/index.ts:294-325`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/index.ts#L294-L325) +[`packages/subagent/subagent/src/continuation.ts:746-841`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L746-L841) +[`packages/subagent/subagent/src/continuation.ts:1332-1394`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/subagent/subagent/src/continuation.ts#L1332-L1394) +[`packages/session/session-persistence/src/index.ts:78-240`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence/src/index.ts#L78-L240) + +Result: DeepSeek validates top-down cancel plus child-first release as an +orderly runtime policy, but decision 6 remains necessary for durable recovery. +Its answer is worse after hard crash, delete, or rewind, and has no analog to +history invalidation. + +### Retention on an unbounded log + +DeepSeek Harness separates transcript compaction from storage compaction, which +supports one premise of [ADR#0035 decision 7](../../../../adr/0035-session-store-decider-aggregate.md#7-the-log-is-never-truncated-keep-forever-with-a-read-time-redaction-and-erasure-contract): +model-visible replacement should not silently delete audit history. It offers no +answer to growth, however. JSONL files and SQLite rows accumulate; there is no +delete, retention, or background historical compaction API. +[`session-persistence-jsonl/README.md:70-77`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-jsonl/README.md#L70-L77) +[`session-persistence-sqlite/README.md:55-63`](https://github.com/deepseek-ai/deepseek-harness/blob/141eb6fef83422698aef7a981029e843e8161534/packages/session/session-persistence-sqlite/README.md#L55-L63) + +Result: the product validates the distinction between context compaction and +physical retention, but neither validates nor challenges our read-time +redaction and artifact-erasure contract. Its unbounded-growth answer is worse. + +## Open questions for the ADR + +- Must `AssistantMessageStarted` commit to the exact rendered model request, or + is immutable plan identity plus `model` and `settings` intentionally enough? +- Which durable fact proves that a recovery reconciler, rather than a live + writer, closed an interrupted message or tool call? +- What exact compatibility artifact is required when `v1alpha1` becomes `v1`: + a per-stream format marker, a type-registry version, explicit migration + events, or only stable concrete type names and readers? +- Must every future store backend support cross-process writers, or may a local + backend advertise a weaker capability while still implementing the same + interface? diff --git a/docs/research/session-store/synthesis.md b/docs/research/session-store/synthesis.md index 635238f9a7..7c39d56f18 100644 --- a/docs/research/session-store/synthesis.md +++ b/docs/research/session-store/synthesis.md @@ -718,3 +718,14 @@ nothing in the corpus enumerates what would go through it, so whether seven arms are the right seven is still open. The queued stage three in the [backlog](./backlog.md) takes a provider rather than a product as its unit of study for that reason. + +**DeepSeek Harness is an additional result beyond the 55 recommendations +counted above.** Its 5/12 store reinforces the strongest stage-two conclusion: +even two first-party implementations behind one persistence seam need an +explicit conformance suite, because JSONL and SQLite provide different writer +coordination. Its copied-prefix fork, generic mutable surface replacement, and +out-of-band retention are weaker than decisions 5, 4, and 7 respectively, while +its append-only crash closure is worth specifying as recovery behavior. Its two +plausible additive catalog changes are to bind each assistant generation to the +exact rendered request envelope and to distinguish process loss from user +steering; at 5/12 both remain thin evidence, not industry norms.