From 46ba91bc98ca6eeb529aa343ed905c732deea542 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:51:00 +0800 Subject: [PATCH] docs: sharpen package limitation audit --- packages/core/agent-core/README.md | 2 +- .../session-persistence-jsonl/README.md | 3 ++- .../session-persistence-jsonl/src/index.ts | 4 ++-- .../session-persistence-sqlite/README.md | 3 ++- packages/support/README.md | 4 ++-- packages/support/invariants/README.md | 6 +++--- packages/support/invariants/package.json | 2 +- packages/support/invariants/src/index.ts | 16 +++++++++------- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index 8acbc72c16..ddbc708121 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -17,7 +17,7 @@ This is the package to read to see **the whole shared plugin tree at once**: the @deepseek-ai/dsh-skill skill provider registry @deepseek-ai/dsh-skill-local local filesystem skill provider @deepseek-ai/dsh-agent agent registry + agent/* event vocabulary -@deepseek-ai/dsh-invariants dev-mode event-contract assertions +@deepseek-ai/dsh-invariants runtime event-contract assertions @deepseek-ai/dsh-tool-bash the model-facing bash/bash_output/bash_kill schemas @deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema @deepseek-ai/dsh-agent-loop THE concrete loop (gets the forwarded `agents`) diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 27d965195e..c74095e9bf 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -21,7 +21,7 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence ## Durability and crash semantics -- **Lazy materialization.** `create(meta)` writes nothing; the `.jsonl` (header + first batch) is written atomically (temp-write + `fsync` + rename) on the first `append`. A created-but-never-appended session leaves nothing on disk and is absent from `list`. +- **Lazy materialization.** `create(meta)` writes nothing; on the first `append`, the backend writes and `fsync`s a temporary file, publishes it without overwrite via a hard link, then `fsync`s the directory. A created-but-never-appended session leaves nothing on disk and is absent from `list`. - **Append-only.** Committed events (at or below a flushed `turn/end`) are never rewritten. Subsequent appends are line appends at EOF + `fsync`. - **Crash recovery — close, don't truncate.** A crash can leave a log whose final turn never closed (real events after the last `turn/end`). `load` PRESERVES those events (a turn can be huge — they are real work) and closes the orphaned turn by durably appending synthetic boundary events: an error `tool/result` for every `tool-call` the crash left unanswered (the loop logs the assistant message before running the tools, so a mid-tool crash leaves dangling calls — and `deriveMessages()` would replay an assistant tool-call with no result, which providers reject), then a `step/end` if a step was open, then `turn/end {kind:'interrupted'}`, returning a balanced log. Only a never-fully-written **torn tail fragment** (a final line with no newline / unparseable) is `ftruncate`d away before the closers are written. See [session persistence](../../../docs/rfc/implemented/architecture/2026-06-14-session-persistence.md). - **Contiguous-seq.** `load` rejects a mid-log parse error or `seq` gap (unloadable); `append` rejects a batch whose first `seq` does not continue the stored log, and rejects non-JSON-serializable `event.data` naming the offending event type. @@ -35,3 +35,4 @@ The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session - **Only the current `SESSION_FORMAT_VERSION` (v0) loads** — the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 and non-current logs are rejected; there is no migration. - **Nothing deletes session files** — logs accumulate under `root` until removed externally (the seam has no deletion surface). - **Single-process assumption** — per-session serialization and the write cursor live in this process; two processes appending to the same `root` are not coordinated. +- **Initial materialization requires hard-link support** — first append uses `link()` so same-id races fail instead of overwriting a committed log; a filesystem that cannot create hard links cannot host this backend. diff --git a/packages/session-persistence/session-persistence-jsonl/src/index.ts b/packages/session-persistence/session-persistence-jsonl/src/index.ts index a69c979756..141505644f 100644 --- a/packages/session-persistence/session-persistence-jsonl/src/index.ts +++ b/packages/session-persistence/session-persistence-jsonl/src/index.ts @@ -193,7 +193,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi // --- materialization / append / repair (file mechanics) --- - /** Atomically write the header line + first batch (temp-write, fsync, rename). */ + /** Atomically write the header line + first batch (temp-write, fsync, collision-safe hard-link publish). */ private async materialize(meta: SessionHeader, events: readonly SessionEvent[]): Promise { const dir = sessionDir(this.root, meta.cwd) await mkdir(this.root, { recursive: true, mode: 0o700 }) @@ -250,7 +250,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi } } - /** fsync a directory so a just-created/renamed entry inside it is crash-durable. */ + /** fsync a directory so a just-created or published entry inside it is crash-durable. */ private async syncDir(dir: string): Promise { const handle = await open(dir, 'r') try { diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 403b6e4432..873dc833b6 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -31,5 +31,6 @@ Like the JSONL backend, the plugin also installs the `session/event` → buffer - **Raw `node:sqlite`, pending a cordis database service** — the backend holds a `DatabaseSync` directly; if a `cordis/db` / `@cordisjs` SQL driver is adopted, the storage driver routes through it (the `SessionPersistence` contract would not change) — a marked TODO. - **`DatabaseSync` is synchronous** — every append transaction blocks the event loop for its duration; acceptable for local stores, a throughput ceiling for busy multi-session servers. -- **Only the current `SCHEMA_VERSION` opens** — a database written by any other build is rejected rather than migrated (unreleased software; no persisted user data to preserve). +- **Write contention has no wait or retry policy** — the backend sets no busy timeout and retries no locked-database error, so another connection holding a write transaction makes the operation reject immediately. +- **Only the current `SCHEMA_VERSION` opens** — a database with any other schema version is rejected rather than migrated (unreleased software; no persisted user data to preserve). - **Nothing deletes stored sessions** — rows accumulate until removed externally (the seam has no deletion surface; `ON DELETE CASCADE` is wired for such out-of-band cleanup). diff --git a/packages/support/README.md b/packages/support/README.md index c8883a89ff..b6559a1a79 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -5,8 +5,8 @@ Packages that exist to serve development, testing, and the examples rather than | Package | Role | ctx key | |---|---|---| | `acp-snapshot/` | ACP snapshot suite kit: subprocess scenario harness + golden normalizers + the `defineAcpSnapshotSuite` factory | (library — imported by example `*.snapshot.ts` suites) | -| `invariants/` | Dev-mode event-contract assertions | (listens on `session/*`, `agent/*`) | +| `invariants/` | Runtime event-contract assertions for development diagnostics | (listens on `session/*`, `agent/*`) | | `llm-replay/` | Record/replay adapter: short-circuits `llm/stream` from a recorded session JSONL (keyless snapshot tests) | (listens on `llm/stream`) | | `subagent-mock/` | Scripted `SubagentProvider` for deterministic seam/tool tests | (registers on `ctx.subagents`) | -`invariants` runs only in dev mode (contract checks, not runtime behavior). `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery so every example's suite is a scenario table over one shared, gate-covered implementation. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. +`invariants` is development support but has no environment guard: it runs wherever registered, and the default `dsh-agent-core` bundle mounts it unconditionally. `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery so every example's suite is a scenario table over one shared, gate-covered implementation. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index 2fc56fc2ae..e48eb71a85 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -1,8 +1,8 @@ # dsh-invariants -Dev-mode event-contract assertions. This pure-listener plugin checks relationships among session events, agent states, scoped dispatches, and model requests at runtime; it does not own or change product behavior. +Runtime event-contract assertions intended for development diagnostics. This pure-listener plugin checks relationships among session events, agent states, scoped dispatches, and model requests; it does not own or change product behavior. -**Off in production.** Enable it in tests and the demos, where a contract violation should fail loudly. It costs nothing when not registered, and doubles as executable documentation of the event taxonomy — the assertions *are* the contract. +The plugin has no environment guard: it is active wherever it is registered. The default [`dsh-agent-core`](../../core/agent-core/README.md) bundle mounts it unconditionally; a custom composition can omit it when the runtime cost is undesirable. It doubles as executable documentation of the event taxonomy — the assertions *are* the contract. Session itself owns immutable log storage in every composition: it takes one lossless JSON snapshot of each accepted event, deep-freezes that record, and exposes the log through immutable array snapshots. The invariants plugin checks the cross-record and cross-seam rules that storage immutability cannot express. @@ -45,7 +45,7 @@ On any violation it throws `InvariantError` (`code: 'INVARIANT'`). ## Why runtime assertions remain useful -Session enforces the per-record storage boundary at runtime, where a cast cannot bypass it. Pervasive `DeepReadonly` types would add noise across consumers without expressing relationships such as turn/step nesting, subject-correct scoped dispatch, or equality between a request and its log reconstruction. This plugin checks those relationships in development while `dsh-session` keeps history immutable in every composition. See [source-owned session immutability and dev-mode invariants](../../../docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md). +Session enforces the per-record storage boundary at runtime, where a cast cannot bypass it. Pervasive `DeepReadonly` types would add noise across consumers without expressing relationships such as turn/step nesting, subject-correct scoped dispatch, or equality between a request and its log reconstruction. This plugin checks those relationships wherever it is mounted while `dsh-session` keeps history immutable in every composition. See [source-owned session immutability and dev-mode invariants](../../../docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md). ## Seeded sessions diff --git a/packages/support/invariants/package.json b/packages/support/invariants/package.json index 588c658857..3568465d18 100644 --- a/packages/support/invariants/package.json +++ b/packages/support/invariants/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-invariants", - "description": "Dev-mode event-contract assertions for the DeepSeek Harness", + "description": "Runtime event-contract assertions for DeepSeek Harness development diagnostics", "version": "0.0.1", "private": true, "type": "module", diff --git a/packages/support/invariants/src/index.ts b/packages/support/invariants/src/index.ts index f5ef6d2b4a..15a57ab08a 100644 --- a/packages/support/invariants/src/index.ts +++ b/packages/support/invariants/src/index.ts @@ -1,13 +1,15 @@ /** - * Dev-mode invariants: a pure-listener plugin that asserts relationships in - * the harness event contract at runtime. + * Runtime invariants: a pure-listener plugin that asserts relationships in + * the harness event contract. It is intended for development diagnostics but + * has no environment guard, so it is active in every composition that mounts + * it (including the default `dsh-agent-core` bundle). * * Everything is a plugin — this is just listeners on `session/created`, * `session/event`, `agent/status`, and the scoped dispatch and request seams. - * It is **off in production**: enable it in tests and demos, where a contract - * violation should be a loud failure rather than a subtle one. It doubles as - * executable documentation of the event taxonomy: the assertions below are - * the contract. + * Custom compositions can omit it when the runtime assertion cost is + * undesirable. When mounted, a contract violation is a loud failure rather + * than a subtle one. It doubles as executable documentation of the event + * taxonomy: the assertions below are the contract. * * Session owns immutable log storage: it snapshots and deep-freezes every * accepted event at the source. This plugin checks relationships that one @@ -344,7 +346,7 @@ function checkTransition(from: AgentStatus | undefined, to: AgentStatus): void { } /** - * Register the dev-mode invariants. Contributions are effect-scoped, so + * Register the runtime invariants. Contributions are effect-scoped, so * disposing the plugin fiber removes all listeners (HMR-safe). On (re-)apply * the trace state is rebuilt by replaying each existing session's log, so a * hot reload mid-turn does not falsely reject the next event.