Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2731

# Conflicts:
#	packages/util/brand/README.i18n.yaml
#	packages/util/brand/README.md
#	packages/util/brand/README.zh.md
This commit is contained in:
_Kerman
2026-08-26 10:35:38 +08:00
1275 changed files with 62320 additions and 14129 deletions
@@ -1,30 +0,0 @@
# Agent Note: Session log versioning — one integer, an upgrade chain, and a per-event ignorable marker
Status: implemented
English | [中文](2026-08-10-session-log-version-mechanism.zh.md)
## Problem
Session logs must be upgradable after release, and the runtime that ships first is the floor for every later decision: whatever refusal and degradation behavior is missing from the first released reader can never be added to the copies users already run. Release issue #1901 required at minimum that an old runtime reading a newer session format reports "unsupported" instead of misreading it. The pre-change reader did the opposite on both axes: `assertVersion` rejected any version mismatch with one direction-blind message, and the JSONL decoder passed unknown event types through untouched, so reconstruction silently skipped them — resuming a gutted session with no diagnostic at all.
## Decision
**One monotonic integer, no major/minor split.** Whether a version step is auto-upgradable is a property of that step — expressed by whether its upgrader exists — not something a two-level numbering scheme should promise in advance (you rarely know at design time whether the next change will turn out "major"). This matches the SQLite backend's `SCHEMA_VERSION` precedent.
**The writer decides bumps, not the reader.** A bump is required exactly when an old runtime could no longer handle a new log with full semantic correctness. "Parses without error" is not the bar: silently skipping content that shapes reconstruction is a wrong read. Only structural changes qualify — header shape, event envelope, core event semantics, the surface mechanism (`SurfaceEventType` set, `SurfaceOp` variants). When unsure, bump: a near-identity upgrader is almost free, a missed bump silently corrupts old readers.
**Read rules by direction.** Equal version: read normally. Newer than the reader: refuse, name the direction ("written by a newer harness — upgrade"), and point at the raw log artifact so the user can still see the text (`SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged). Older than the reader: convert in memory through the chain of n→n+1 upgraders for viewing; persist the converted log only when the session is actually continued (atomic temp-file replace, original kept as backup). A step whose upgrader cannot be written is left empty, which cuts off every version at or below it — those degrade to raw-text viewing.
**A per-event `ignorable` marker covers vocabulary growth, so ordinary event additions never bump the version.** The event vocabulary is decided by which plugins are mounted, which a single version integer cannot describe. A reader meeting an unrecognized event type refuses to interpret the log unless the event carries `ignorable: true` in its envelope. The default is *required*: forgetting the marker over-refuses a resumable session (an inconvenience), while a default of ignorable would make the same mistake silently resume a gutted one (a safety failure). The architecture makes this sound: model-visible content flows only through the three `surfaceOp`-marked surface event types plus the `request/header`/`request/context` folds, so the dangerous unknowns are exactly the non-surface events that change how the rest of the log is read (`session/end-seed` is the existing example).
## Consequences
What shipped in v0 (release 0812): direction-aware refusal with the raw-log path; the unknown-event guard against a generated known-vocabulary list (`KNOWN_SESSION_EVENT_TYPES`, emitted by `gen-persistence-catalog` from every `SessionEventMap` merge and kept fresh by `verify-persistence-catalog`); the `ignorable` envelope field accepted by seed validation, both backends (a dedicated SQLite column, `SCHEMA_VERSION` 15), and the BFF wire schema. The upgrader chain itself is deferred until the first real v0→v1 step exists to test it against; writers do not yet set `ignorable` (no producer needs it), so `Session.append` gains that surface with its first user. Until a registration surface exists, an out-of-repo plugin's events refuse resume under first-party readers — the pre-release stance accepts that, and the refusal is loud rather than silent. The unknown-type guard is read-side only: `appendCore` keeps rejecting retired legacy shapes but does not vocabulary-check new types, because an append-time refusal would stall a live session's durability mid-flight, which costs more than a loud refusal at the log's next load. The JSONL backend additionally refuses a foreign version from the raw header line before validating this format version's header shape or decoding any event row, so a structurally different future format still reports the upgrade direction instead of "corrupt"; SQLite gates whole-file structure through its own `SCHEMA_VERSION` pragma first.
## Alternatives considered
- **Major/minor versioning** — the "is it convertible" bit lives on each step's upgrader, and pre-committing it into a number shape invites wrong promises.
- **Default-ignorable unknown events** — inverts the failure mode of a forgotten marker from visible over-refusal into silent corruption.
- **Auto-migrating on view** — rewriting the artifact on open turns a read into a destructive write: a converter bug corrupts logs at browse time, and a same-directory older runtime loses access because a newer one merely looked.
- **Per-plugin runtime registration of known event types** — would make the known set composition-dependent, so a leaner same-version composition would refuse logs a fuller one wrote. The generated repo-wide list keeps same-version reads uniform; out-of-repo plugin events are outside it by construction, and a registration surface for them is deferred until such a consumer exists.
@@ -1,30 +0,0 @@
# Agent Note: Session log 版本机制:单调整数、升级器链、逐事件可忽略标记
Status: implemented
[English](2026-08-10-session-log-version-mechanism.md) | 中文
## 问题
Session log 在发布后必须能升级格式,而最先发布的运行时决定了此后一切的下限:第一个发布版的读取器缺少哪种拒绝和降级行为,用户手里已经装上的副本就永远补不上。发布 issue #1901 的最低要求是老运行时读到新 Session 格式时明确报不支持,而不是读错。改动前的读取器在两个方向上都做反了:`assertVersion` 对任何版本不匹配抛出同一条不区分方向的消息;JSONL 解码器把不认识的事件类型原样放行,重建时静默跳过,恢复出一个内容残缺的会话且没有任何诊断。
## 决定
**一个单调递增的整数,不分大小版本。**某一步能不能自动升级是那一步自己的属性,由它的升级器存在与否表达,不该由两级编号方案提前承诺(设计时很少能预知下一个变更算不算"大")。这与 SQLite 后端 `SCHEMA_VERSION` 的先例一致。
**升不升版本由写入方决定,与读取方能力无关。**当且仅当老运行时无法在语义上完全正确地处理新日志时才必须升版本。"解析不报错"不是标准:静默跳过影响重建的内容就是读错。只有结构性变更够得上这条线:header 形状、事件信封、核心事件语义、surface 机制(`SurfaceEventType` 集合、`SurfaceOp` 变体)。拿不准就升:近似恒等的升级器几乎没有成本,漏升一次会让老读取器静默读坏。
**读取规则按方向区分。**版本相等:正常读。比读取器新:拒绝,说明方向("由更新的 harness 写入,请升级"),并给出原始日志文件的路径,用户仍能看到文本(`SessionFormatUnsupportedError`,与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏)。比读取器旧:查看时经 n→n+1 升级器链在内存中逐级转换;只有会话真正被继续时才把转换落盘(临时文件原子替换,原文件留备份)。写不出升级器的那一步留空,这会切断该步及更早所有版本的升级路径,它们降级为只能看原文。
**逐事件的 `ignorable` 标记吸收词汇表增长,普通的新增事件永远不用升版本。**事件词汇表由挂载了哪些插件决定,单个版本整数描述不了它。读取器遇到不认识的事件类型时拒绝解读日志,除非该事件的信封带 `ignorable: true`。默认为必需:忘写标记的后果是把一个本可恢复的会话拒绝过头(体验问题),而默认可忽略会让同样的疏忽静默恢复出残缺会话(安全事故)。架构保证了这条规则成立:模型可见内容只经三种带 `surfaceOp` 标记的 surface 事件加 `request/header``request/context` 折叠进入重建,危险的未知事件恰好是那些不进 surface 但改变日志其余部分解读方式的事件(`session/end-seed` 是现存例子)。
## 影响
v0(0812 发布)交付的内容:分方向的拒绝并带原始日志路径;基于生成的已知词汇清单(`KNOWN_SESSION_EVENT_TYPES`,由 `gen-persistence-catalog` 从所有 `SessionEventMap` 声明合并生成,`verify-persistence-catalog` 保证新鲜)的未知事件守卫;`ignorable` 信封字段被种子校验、两个后端(SQLite 专用列,`SCHEMA_VERSION` 升到 15)和 BFF 线上 schema 接受。升级器链本身推迟到第一个真实的 v0→v1 变更出现、有真实对象可测时再建;写入侧目前不写 `ignorable`(还没有生产者需要它),`Session.append` 的这一表面随第一个使用者一起落地。在注册表面出现之前,仓库外插件的事件在第一方读取器下无法恢复会话,预发布立场接受这一点,而且拒绝是显式的而非静默的。未知类型守卫只在读取侧生效:`appendCore` 继续拒绝已淘汰的 legacy 形状,但不对新类型做词汇检查,因为写入时拒绝会让活跃会话的持久化中途停摆,代价大于下次加载时的显式拒绝。JSONL 后端还会在校验本格式版本的 header 形状、解码任何事件行之前,直接从原始 header 行拒绝外来版本,因此结构完全不同的未来格式仍会报告升级方向而不是"损坏";SQLite 则先由自己的 `SCHEMA_VERSION` pragma 把关整个文件的结构。
## 曾考虑的替代方案
- **大小两级版本号**:能否转换这一位信息属于每一步的升级器,把它预先固化进编号形状会做出错误承诺。
- **未知事件默认可忽略**:把忘写标记的后果从可见的过度拒绝反转成静默损坏。
- **查看时自动迁移落盘**:打开即改写把读操作变成破坏性写操作,转换器的 bug 会在浏览时损坏日志,同目录的旧版本运行时也会因为新版本只是看了一眼就失去访问能力。
- **插件运行时注册已知事件类型**:会让已知集依赖插件组合,同版本的精简组合会拒绝完整组合写出的日志。生成的全仓库清单保证同版本读取行为一致;仓库外插件的事件按构造就在清单之外,为它们提供注册表面推迟到真有这样的消费者时再做。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md
2026-08-18-experimental-agent-teams-packages.md: cfaf7b23d03a1adecf8acc18a110717a62aa9ed1
2026-08-18-experimental-agent-teams-packages.zh.md: 1d06addff6c8f347726408d3bb02bc72374492cb
2026-08-18-experimental-agent-teams-packages.md: 495922d57bd88a78f0ca6b61367ef49c820db632
2026-08-18-experimental-agent-teams-packages.zh.md: 65aed57e223d8fa9b8ff97e2fcc109057b51474d
@@ -12,12 +12,16 @@ An experimental directory without a current package previously imposed placement
## Decision
`packages/experimental/agent-team` and `packages/experimental/tool-agent-team` are private workspace packages. The [experimental package naming decision](2026-08-19-experimental-package-name-prefix.md) owns their npm names and promotion rename; this note owns their placement, release exclusion, and dependency isolation.
`packages/experimental/agent-team`, `packages/experimental/tool-agent-team`, and `packages/experimental/agent-team-profile` are private workspace packages. The [experimental package naming decision](2026-08-19-experimental-package-name-prefix.md) owns their npm names and promotion rename; this note owns their placement, release exclusion, and dependency isolation.
The dsh pack and publish set and the local baseline publisher exclude every manifest below `packages/experimental/`. `release:dsh` still advances their manifest versions with the shared dsh version without creating release tags. Workspace constraints require each experimental package to set `private: true` and omit `publishConfig`. The same top-level check rejects `dependencies`, `optionalDependencies`, and `peerDependencies` from release packages, release apps, or the Python runtime to an experimental package. Experimental packages may depend on release packages and each other; tests may use them through `devDependencies`, and examples may load them explicitly.
The generic caller-reserved continuable child identity and selective direct-child drain remain in the stable Subagent service. They own Subagent identity and Activation lifecycle without importing or naming Agent Teams; the experimental Team service consumes them in the permitted direction.
The private Agent Teams profile bundle depends on the Team packages and applies after `dsh-base`. It inserts the Team rows, disables the global continuable-child controls whose model-visible names overlap the Team tools, and leaves the shipped base, CLI, Web, and Python runtime dependency graphs unchanged.
Profile startup resolves selected bundles before healing module fallbacks. The shared fallback retains the dsh installation's carrier-specific entries: symlinks under plain Node and ESM proxies in a packaged executable. Missing packages from selected bundle closures are linked under the current profile's own `node_modules`, while pnpm-managed profile entries remain authoritative. Closure discovery starts from each explicit external bundle's real package directory and traverses every listed root even when an earlier dependency has the same package name. It excludes dsh-owned profile projections from later discovery, so a projected dependency cannot feed back into its own closure. Link ownership compares canonical parent paths so junction-normalized targets remain removable. A private profile layer can therefore carry experimental plugin rows without adding those plugins to a release app, requiring profile users to install transitive packages directly, weakening packaged-runtime module identity, or changing another profile's resolution.
Experimental status changes publication and compatibility expectations only. The packages retain the repository's ordinary documentation, invariant, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion requires review of the public contracts, limitations, test evidence, release payload, runtime dependents, and a named owner accepting stable-package obligations.
## Alternatives considered
@@ -30,6 +34,6 @@ Experimental status changes publication and compatibility expectations only. The
## Consequences
Agent Teams can use the full repository graph and quality checks without entering official tarballs or becoming a supported runtime dependency. A release package cannot expose Team until the Team packages are promoted, so CLI and Web experiments use explicit example or experimental compositions instead of the shipped base bundles.
Agent Teams can use the full repository graph and quality checks without entering official tarballs or becoming a supported runtime dependency. A release package cannot expose Team until the Team packages are promoted, so the CLI experiment installs an explicit private profile layer instead of changing shipped bundles. The generic profile launcher accepts that layer without making its plugin dependencies part of the dsh release closure.
The product-role grouping is less direct while the packages incubate. Promotion creates path and npm-name churn as specified by the experimental package naming decision.
@@ -12,12 +12,16 @@ Agent Teams 的服务与工具约定仍在变化,但它需要使用真实 Sess
## 决策
`packages/experimental/agent-team``packages/experimental/tool-agent-team` 是私有 workspace 包。[实验性包命名决策](2026-08-19-experimental-package-name-prefix.zh.md)负责其 npm 名和 promotion 重命名;本记录负责其目录归属、发布排除与依赖隔离。
`packages/experimental/agent-team``packages/experimental/tool-agent-team``packages/experimental/agent-team-profile` 是私有 workspace 包。[实验性包命名决策](2026-08-19-experimental-package-name-prefix.zh.md)负责其 npm 名和 promotion 重命名;本记录负责其目录归属、发布排除与依赖隔离。
dsh pack 与 publish 集合以及本地 baseline 发布器均排除 `packages/experimental/` 下的所有 manifest。`release:dsh` 仍会让这些 manifest 跟随 dsh 共享版本递增,但不会创建发布 tag。workspace 约束要求每个实验性包设置 `private: true` 并省略 `publishConfig`。同一个顶层检查会拒绝发布包、发布 app 或 Python runtime 通过 `dependencies``optionalDependencies``peerDependencies` 依赖实验性包。实验性包可以依赖发布包和其他实验性包;测试可以通过 `devDependencies` 使用它们,示例可以显式加载它们。
通用的调用方预留 continuable child 身份和精确 direct-child drain 仍属于稳定 Subagent 服务。它们负责 Subagent 身份与 Activation 生命周期,不 import 或命名 Agent Teams;实验性 Team 服务沿允许的方向消费这些能力。
私有 Agent Teams profile bundle 依赖 Team 包,并应用在 `dsh-base` 之后。它插入 Team 配置行,禁用模型可见名称与 Team 工具重叠的全局 continuable-child control,并保持已发布 base、CLI、Web 与 Python runtime 的依赖图不变。
profile 启动会先解析所选 bundle,再修复模块 fallback。共享 fallback 保留 dsh 安装的载体专用条目:普通 Node 下使用 symlink,打包 executable 中使用 ESM proxy。仅由所选 bundle 闭包携带的缺失包会链接到当前 profile 自己的 `node_modules` 下,而 pnpm 管理的 profile 条目仍具有优先权。闭包发现从每个显式外部 bundle 的真实包目录开始;即使前一个依赖具有相同包名,也会遍历所有列出的根。后续发现会排除 dsh 所有的 profile projection,避免投影后的依赖重新进入自己的闭包。link ownership 通过规范化父路径比较,使 junction 规范化后的 target 仍可删除。因此,私有 profile 层可以携带实验性 plugin 配置行,而无需把这些 plugin 加入发布 app、要求 profile 用户直接安装传递依赖、破坏 packaged-runtime 的模块身份,或改变其他 profile 的解析结果。
实验性状态只改变发布与兼容性预期。这些包仍须满足仓库的一般文档、不变式、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前必须评审公开约定、限制、测试证据、发布 payload、运行时依赖方,并由一名具名 owner 接受稳定包义务。
## 曾考虑的替代方案
@@ -30,6 +34,6 @@ dsh pack 与 publish 集合以及本地 baseline 发布器均排除 `packages/ex
## 后果
Agent Teams 可以使用完整仓库依赖图与质量检查,而不进入正式 tarball,也不会成为受支持的运行时依赖。在 Team 包 promotion 前,发布包不能暴露 Team,因此 CLI 和 Web 实验使用显式示例或实验性组合,而不是已发布的基础组合包。
Agent Teams 可以使用完整仓库依赖图与质量检查,而不进入正式 tarball,也不会成为受支持的运行时依赖。在 Team 包 promotion 前,发布包不能暴露 Team,因此 CLI 实验会安装显式的私有 profile 层,而不是修改已发布 bundle。通用 profile launcher 可以接受该层,而不会让它的 plugin 依赖进入 dsh 发布闭包。
孵化期间的产品职责分组不够直接。promotion 会按照实验性包命名决策产生路径和 npm 名改动。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md
2026-08-18-sqlite-physical-chunk-row-compression.md: e46adf26ab4ce0a495f3509977ab0835631c16a9
2026-08-18-sqlite-physical-chunk-row-compression.zh.md: d93aa64a53effa9d456b3eba2e1681b9478c448d
2026-08-18-sqlite-physical-chunk-row-compression.md: 34aac2f183d386ffe22f86a6b62fe5e3105b3dfa
2026-08-18-sqlite-physical-chunk-row-compression.zh.md: 1845185d543f565b55ace6adac973dad5535ad7b
@@ -12,15 +12,15 @@ A physical row that represents several events affects append contiguity, crash r
## Decision
`@deepseek-ai/dsh-session-persistence-sqlite` uses the packed schema-17 implementation. It is the only SQLite persistence package and provider; the predecessor scalar layout and the temporary versioned sibling are not retained. SQLite remains an opt-in switch, while shipped default compositions continue to use JSONL. Both backends implement the same `SessionPersistence` service through `PersistenceCoordinator`, so physical packing changes neither live event delivery nor the logical session API.
`@deepseek-ai/dsh-session-persistence-sqlite` uses the packed schema-18 implementation. It is the only SQLite persistence package and provider; the predecessor scalar layout and the temporary versioned sibling are not retained. SQLite remains an opt-in switch, while shipped default compositions continue to use JSONL. Both backends implement the same `SessionPersistence` service through `PersistenceCoordinator`, so physical packing changes neither live event delivery nor the logical session API.
Schema 17 keeps ordinary ROWID tables and the composite `events(session_id, seq)` primary-key index. Scalar rows represent one logical event. Packed rows use the storage tags `text-chunks`, `reasoning-chunks`, and `tool-call-chunks`; the SQL `seq` and `time` columns hold the first logical member, and `data` holds the packed payload. Packed rows set `ignorable=0` as a physical discriminator and leave `source_event_seqs` and `surface_op` as `NULL`; scalar rows use `ignorable=1` only for logical ignorable events and `NULL` otherwise. A future ignorable logical event may therefore reuse a storage-tag name without being decoded as a packed row. The tags are storage vocabulary, not `SessionEventMap` members.
Schema 18 keeps ordinary ROWID tables and the composite `events(session_id, seq)` primary-key index. Scalar rows represent one logical event. Packed rows use the storage tags `text-chunks`, `reasoning-chunks`, and `tool-call-chunks`; the SQL `seq` and `time` columns hold the first logical member, and `data` holds the packed payload. Packed rows set `is_packed=1`, while scalar rows set `is_packed=0`; the explicit discriminator prevents a scalar event whose type matches a storage tag from being decoded as packed. The tags are storage vocabulary, not `SessionEventMap` members.
SQLite owns chunk encoding and validation inside the schema-17 package. Exact-field whitelisting means unknown fields, surface metadata, incompatible chunk identity, sequence gaps, and unsafe timestamps remain scalar rather than losing information. One packed row represents at most 1,024 events and 1 MiB of uncompressed UTF-8 `data`; the encoder partitions longer runs, and the decoder rejects rows outside those format limits.
SQLite owns chunk encoding and validation inside the schema-18 package. Exact-field whitelisting means unknown fields, surface metadata, incompatible chunk identity, sequence gaps, and unsafe timestamps remain scalar rather than losing information. One packed row represents at most 1,024 events and 1 MiB of uncompressed UTF-8 `data`; the encoder partitions longer runs, and the decoder rejects rows outside those format limits.
The `data` column accepts `TEXT` or `BLOB`. Serialized values below 4 KiB remain text. At or above the threshold, the writer uses Zstandard level 3 and retains the frame only when it is smaller than the text; the reader decompresses the blob before strict UTF-8 decoding and JSON parsing. The fixed moderate level and threshold limit frame overhead and synchronous CPU work while capturing the repeated payloads that dominate retained bytes.
`source_event_seqs` remains the complete ordered list of earlier events cited by a surface node, including every streamed chunk behind an assembled assistant message. Schema 17 stores the first sequence as an unsigned varint and every subsequent signed difference as a ZigZag varint. This preserves arbitrary order and every sequence while exploiting the overwhelmingly consecutive lists produced by streaming. An empty list is an empty non-null blob, distinct from absent provenance.
`source_event_seqs` remains the complete ordered list of earlier events cited by a surface node, including every streamed chunk behind an assembled assistant message. Schema 18 stores the first sequence as an unsigned varint and every subsequent signed difference as a ZigZag varint. This preserves arbitrary order and every sequence while exploiting the overwhelmingly consecutive lists produced by streaming. An empty list is an empty non-null blob, distinct from absent provenance.
### Transactional append packing
@@ -32,11 +32,11 @@ Normal append never deletes or replaces an earlier event row. Fixed write-behind
Full reads decode each physical row as one all-or-nothing logical span and validate contiguous logical sequences. A reverse pass identifies the last valid `turn/end` without retaining a second decoded copy of the full physical scan; the forward pass decodes one row at a time into the required logical result. A malformed row or gap before that committed boundary is corruption; a malformed final physical row becomes the opaque repair marker at that row's base sequence. Recovery re-reads and validates that marker while holding the write lock, then deletes the whole physical row and any later rows before binding synthetic closers as scalar events. A stale repair cannot delete a newer writer's valid suffix.
`readFrom(id, fromSeq)` examines packed predecessors only within the maximum schema-17 row span, then reads from the earliest candidate that may contain `fromSeq`. The decoder filters reconstructed members below `fromSeq`, so a suffix may begin inside a packed row without parsing an unrelated earlier scalar row. Reading from that candidate also exposes an overlapping scalar row to contiguity validation instead of letting it hide the packed member. Packed data exceeding the uncompressed format byte limit rejects before JSON parsing.
`readFrom(id, fromSeq)` examines packed predecessors only within the maximum schema-18 row span, then reads from the earliest candidate that may contain `fromSeq`. The decoder filters reconstructed members below `fromSeq`, so a suffix may begin inside a packed row without parsing an unrelated earlier scalar row. Reading from that candidate also exposes an overlapping scalar row to contiguity validation instead of letting it hide the packed member. Packed data exceeding the uncompressed format byte limit rejects before JSON parsing.
### Schema ownership
A pristine database initializes at schema 17. Older physical schemas, foreign application identities, non-pristine unversioned databases, and incompatible schema objects reject; the pre-release package supplies no migration. Every connection disables trusted schemas and memory-mapped I/O before inspecting durable schema, then reads both settings back. After selecting and verifying the journal mode, the provider pins `synchronous=FULL` and verifies it so SQLite build defaults cannot weaken committed-append durability. Package code loads every statement and fixed pragma from closed-name `.sql` resources and binds runtime values as parameters.
A pristine database initializes at schema 18. Older physical schemas, foreign application identities, non-pristine unversioned databases, and incompatible schema objects reject; the pre-release package supplies no migration. Every connection disables trusted schemas and memory-mapped I/O before inspecting durable schema, then reads both settings back. After selecting and verifying the journal mode, the provider pins `synchronous=FULL` and verifies it so SQLite build defaults cannot weaken committed-append durability. Package code loads every statement and fixed pragma from closed-name `.sql` resources and binds runtime values as parameters.
### Physical-write regression
@@ -58,11 +58,11 @@ The repository regression guard writes 1,000 streamed deltas in 40-event durable
**Compress every payload.** Rejected because small independent Zstandard frames add headers and synchronous CPU work while losing the cross-record dictionary opportunity of a whole-file stream. On the 105-session comparison corpus, a threshold sweep produced 75.01 MB at 4 KiB, versus 93.87 MB at 16 KiB and 60.92 MB at 1 KiB. The writer fixes level 3 rather than inheriting a library default, matching the moderate level used by [Codex cold-rollout compression](https://github.com/openai/codex/blob/main/codex-rs/rollout/src/compression.rs) while retaining independent row access.
The final frozen comparison used 105 sessions, 2,507,860 logical events, 512-event durable batches, three independent builds per backend, and three read passes per build. SQLite used 75.01 MB, wrote in 8.58 s, read complete sessions at 3.95/21.58 ms p50/p95, read 50-event tails at 0.253/0.378 ms, and forked every session in 13.10 s. Zstandard JSONL used 30.65 MB and measured 28.21 s, 4.49/23.36 ms, 10.58/80.90 ms, and 14.48 s. The predecessor scalar SQLite layout used 709.57 MB and measured 10.64 s, 9.02/69.16 ms, 0.189/0.293 ms, and 19.30 s. The packed layout is 89.4% smaller than the predecessor, writes 19.4% faster, improves complete-read p50/p95 by 56.2%/68.8%, and reduces 2,507,860 physical event rows to 65,810. Scalar tail-50 and list micro-latency are lower, but the packed provider remains materially faster than JSONL on those paths and wins the dominant size, write, full-read, and fork costs. The 4 KiB threshold is the accepted balance rather than a strict dominance claim.
The final frozen comparison used 105 sessions, 2,507,860 logical events, 512-event durable batches, three independent builds per backend, and three read passes per build. SQLite used 75.01 MB, wrote in 8.58 s, read complete sessions at 3.95/21.58 ms p50/p95, read 50-event tails at 0.253/0.378 ms, and forked every session in 13.10 s. Zstandard JSONL used 30.65 MB and measured 28.21 s, 4.49/23.36 ms, 10.58/80.90 ms, and 14.48 s. The predecessor scalar SQLite layout used 709.57 MB and measured 10.64 s, 9.02/69.16 ms, 0.189/0.293 ms, and 19.30 s. The packed layout is 89.4% smaller than the predecessor, writes 19.4% faster, improves complete-read p50/p95 by 56.2%/68.8%, and reduces 2,507,860 physical event rows to 65,810. Scalar tail-50 and list micro-latency are lower, but the packed provider remains materially faster than JSONL on those paths and wins the dominant size, write, full-read, and fork costs. The 4 KiB threshold is the accepted balance rather than a strict dominance claim. This comparison measured schema 17; schema 18 retains the chunk codec and bounds but changes the row discriminator, so the exact size and timing values remain schema-17 evidence until schema 18 is remeasured.
**Store packed payloads under the logical `assistant/chunk` type.** Rejected because payload heuristics make malformed rows ambiguous and couple physical decoding to future logical payload fields. Explicit tags fail loudly.
**Store `SessionHeader` fields in an extensible metadata blob.** Rejected for schema 17 because `agentPreset` is a typed core resume invariant shared by JSONL and SQLite, not provider extension metadata. Persisting validated core fields directly keeps both backends aligned; an untyped catch-all would add another compatibility mechanism without a current producer. Revisit this only with a core-owned, namespaced `SessionHeader` extension protocol implemented by every backend.
**Store `SessionHeader` fields in an extensible metadata blob.** Rejected for schema 18 because `agentPreset` is a typed core resume invariant shared by JSONL and SQLite, not provider extension metadata. Persisting validated core fields directly keeps both backends aligned; an untyped catch-all would add another compatibility mechanism without a current producer. Revisit this only with a core-owned, namespaced `SessionHeader` extension protocol implemented by every backend.
**Expose compression rules through configuration or a live registry.** Rejected because same-version databases must be readable independently of runtime topology. The codec is modular source code, but the durable rule set is fixed by schema version.
@@ -12,15 +12,15 @@ Status: implemented
## 决策
`@deepseek-ai/dsh-session-persistence-sqlite` 使用打包后的 schema 17 实现。它是唯一的 SQLite 持久化包和提供方;仓库不保留此前的标量布局与临时版本化同级包。SQLite 仍是可选开关,随产品交付的默认组合继续使用 JSONL。两个后端都通过 `PersistenceCoordinator` 实现同一 `SessionPersistence` 服务,因此物理打包既不改变实时事件投递,也不改变逻辑会话 API。
`@deepseek-ai/dsh-session-persistence-sqlite` 使用打包后的 schema 18 实现。它是唯一的 SQLite 持久化包和提供方;仓库不保留此前的标量布局与临时版本化同级包。SQLite 仍是可选开关,随产品交付的默认组合继续使用 JSONL。两个后端都通过 `PersistenceCoordinator` 实现同一 `SessionPersistence` 服务,因此物理打包既不改变实时事件投递,也不改变逻辑会话 API。
Schema 17 保留普通 ROWID 表以及复合主键索引 `events(session_id, seq)`。标量行表示一个逻辑事件。打包行使用存储标签 `text-chunks``reasoning-chunks``tool-call-chunks`SQL 的 `seq``time` 列保存第一个逻辑成员,`data` 保存打包 payload。打包行 `ignorable=0` 用作物理判别值,并让 `source_event_seqs``surface_op` 保持 `NULL`标量行仅在逻辑事件可忽略时使用 `ignorable=1`,否则使用 `NULL`。因此,未来的可忽略逻辑事件即使复用了某个存储标签名称,也不会被解码为打包行。这些标签属于存储词汇,而不是 `SessionEventMap` 成员。
Schema 18 保留普通 ROWID 表以及复合主键索引 `events(session_id, seq)`。标量行表示一个逻辑事件。打包行使用存储标签 `text-chunks``reasoning-chunks``tool-call-chunks`SQL 的 `seq``time` 列保存第一个逻辑成员,`data` 保存打包 payload。打包行设置 `is_packed=1`标量行设置 `is_packed=0`;显式判别值可防止类型与存储标签同名的标量事件被解码为打包行。这些标签属于存储词汇,而不是 `SessionEventMap` 成员。
SQLite 在 schema 17 包内拥有分片编码和验证。字段完全匹配的白名单意味着未知字段、surface 元数据、不兼容的分片身份、序列缺口和不安全时间戳仍保持标量表示,不会丢失信息。一个打包行最多表示 1,024 个事件和 1 MiB 未压缩 UTF-8 `data`;编码器会分割更长的连续段,解码器则拒绝超出这些格式上限的行。
SQLite 在 schema 18 包内拥有分片编码和验证。字段完全匹配的白名单意味着未知字段、surface 元数据、不兼容的分片身份、序列缺口和不安全时间戳仍保持标量表示,不会丢失信息。一个打包行最多表示 1,024 个事件和 1 MiB 未压缩 UTF-8 `data`;编码器会分割更长的连续段,解码器则拒绝超出这些格式上限的行。
`data` 列接受 `TEXT``BLOB`。序列化值小于 4 KiB 时保持为文本。达到或超过该阈值时,写入方使用 Zstandard level 3,并且只在 frame 小于原文本时保留该 frame;读取方会先解压,再进行严格 UTF-8 解码和 JSON 解析。固定的适中级别与阈值限制 frame 开销与同步 CPU 工作,同时覆盖占据大部分保留字节的重复 payload。
`source_event_seqs` 是 surface 节点引用的早期事件的完整有序列表,包括组装后的 assistant 消息背后的每个流式分片。Schema 17 把第一个序列存为无符号 varint,把后续每个有符号差值存为 ZigZag varint。这样既能保留任意顺序和每个序列,又能利用流式处理所产生的绝大多数连续列表。空列表表示为空的非 `NULL` blob,与不存在来源区分开来。
`source_event_seqs` 是 surface 节点引用的早期事件的完整有序列表,包括组装后的 assistant 消息背后的每个流式分片。Schema 18 把第一个序列存为无符号 varint,把后续每个有符号差值存为 ZigZag varint。这样既能保留任意顺序和每个序列,又能利用流式处理所产生的绝大多数连续列表。空列表表示为空的非 `NULL` blob,与不存在来源区分开来。
### 事务化追加打包
@@ -32,11 +32,11 @@ SQLite 在 schema 17 包内拥有分片编码和验证。字段完全匹配的
完整读取把每个物理行解码为全有或全无的逻辑范围,并验证逻辑序列连续。反向扫描会定位最后一个有效 `turn/end`,但不会保留完整物理扫描的第二份解码副本;正向扫描则逐行解码并写入必需的逻辑结果。在该已提交边界之前出现的畸形行或缺口属于损坏;畸形最终物理行则以该行的起始序列作为不透明修复标记。恢复会在持有写锁时重新读取并验证该 marker,再删除整个物理行及其后所有行,然后把合成 closers 绑定为标量事件。陈旧修复无法删除较新写入方的有效后缀。
`readFrom(id, fromSeq)` 只检查 schema 17 最大行跨度内的打包前驱,再从可能包含 `fromSeq` 的最早候选项开始读取。解码器会过滤重建后序列小于 `fromSeq` 的成员,因此后缀可以从打包行内部开始,而无需解析无关的更早标量行。从该候选项开始读取,还会让连续性验证看到相互重叠的标量行,而不是让它隐藏打包成员。打包数据超出未压缩格式字节上限时,会在解析 JSON 前拒绝。
`readFrom(id, fromSeq)` 只检查 schema 18 最大行跨度内的打包前驱,再从可能包含 `fromSeq` 的最早候选项开始读取。解码器会过滤重建后序列小于 `fromSeq` 的成员,因此后缀可以从打包行内部开始,而无需解析无关的更早标量行。从该候选项开始读取,还会让连续性验证看到相互重叠的标量行,而不是让它隐藏打包成员。打包数据超出未压缩格式字节上限时,会在解析 JSON 前拒绝。
### Schema 所有权
全新数据库初始化为 schema 17。旧物理 schema、外部 application identity、非空未版本化数据库以及不兼容 schema 对象都会被拒绝;该预发布提供方不提供迁移。每个连接都会在检查持久 schema 前禁用可信 schema 和内存映射 I/O,然后读回这两项设置。选择并验证 journal mode 后,提供方会把 `synchronous` 固定为 `FULL` 并验证该设置,避免 SQLite 构建默认值削弱已提交追加的持久性。包代码通过封闭名称的 `.sql` 资源加载每条语句和固定 pragma,并把运行时值作为参数绑定。
全新数据库初始化为 schema 18。旧物理 schema、外部 application identity、非空未版本化数据库以及不兼容 schema 对象都会被拒绝;该预发布提供方不提供迁移。每个连接都会在检查持久 schema 前禁用可信 schema 和内存映射 I/O,然后读回这两项设置。选择并验证 journal mode 后,提供方会把 `synchronous` 固定为 `FULL` 并验证该设置,避免 SQLite 构建默认值削弱已提交追加的持久性。包代码通过封闭名称的 `.sql` 资源加载每条语句和固定 pragma,并把运行时值作为参数绑定。
### 物理写入回归
@@ -58,11 +58,11 @@ SQLite 在 schema 17 包内拥有分片编码和验证。字段完全匹配的
**压缩每个 payload。** 不予采用,因为小型独立 Zstandard frame 会增加 header 和同步 CPU 工作,也无法利用整文件流的跨记录字典。在 105 个会话的对比语料上,阈值扫描结果为:4 KiB 生成 75.01 MB16 KiB 为 93.87 MB1 KiB 为 60.92 MB。写入方固定使用 level 3,而不是继承库默认值;这与 [Codex 冷 rollout 压缩](https://github.com/openai/codex/blob/main/codex-rs/rollout/src/compression.rs)所用的适中级别一致,同时保留独立行访问。
最终冻结对比包含 105 个会话、2,507,860 个逻辑事件,以 512 个事件为持久批次;每个后端独立构建三次,每次构建执行三轮读取。SQLite 使用 75.01 MB,写入耗时 8.58 秒,完整读取 p50/p95 为 3.95/21.58 毫秒,读取最后 50 个事件为 0.253/0.378 毫秒,对所有会话执行 fork 为 13.10 秒。Zstandard JSONL 使用 30.65 MB,对应指标为 28.21 秒、4.49/23.36 毫秒、10.58/80.90 毫秒和 14.48 秒。此前的标量 SQLite 布局使用 709.57 MB,对应指标为 10.64 秒、9.02/69.16 毫秒、0.189/0.293 毫秒和 19.30 秒。打包布局比此前布局小 89.4%,写入快 19.4%,完整读取 p50/p95 改善 56.2%/68.8%,并把 2,507,860 个物理事件行减少到 65,810 行。标量布局的最后 50 个事件读取与 list 微延迟更低,但打包提供方在这些路径上仍明显快于 JSONL,并改善主要的空间、写入、完整读取和 fork 成本。4 KiB 阈值是接受的平衡点,而不是严格支配所有指标的结论。
最终冻结对比包含 105 个会话、2,507,860 个逻辑事件,以 512 个事件为持久批次;每个后端独立构建三次,每次构建执行三轮读取。SQLite 使用 75.01 MB,写入耗时 8.58 秒,完整读取 p50/p95 为 3.95/21.58 毫秒,读取最后 50 个事件为 0.253/0.378 毫秒,对所有会话执行 fork 为 13.10 秒。Zstandard JSONL 使用 30.65 MB,对应指标为 28.21 秒、4.49/23.36 毫秒、10.58/80.90 毫秒和 14.48 秒。此前的标量 SQLite 布局使用 709.57 MB,对应指标为 10.64 秒、9.02/69.16 毫秒、0.189/0.293 毫秒和 19.30 秒。打包布局比此前布局小 89.4%,写入快 19.4%,完整读取 p50/p95 改善 56.2%/68.8%,并把 2,507,860 个物理事件行减少到 65,810 行。标量布局的最后 50 个事件读取与 list 微延迟更低,但打包提供方在这些路径上仍明显快于 JSONL,并改善主要的空间、写入、完整读取和 fork 成本。4 KiB 阈值是接受的平衡点,而不是严格支配所有指标的结论。该对比测量 schema 17schema 18 保留分片 codec 与上限,但改变行判别值,因此在重新测量 schema 18 前,精确的大小与时延值仍是 schema 17 证据。
**把打包 payload 存在逻辑 `assistant/chunk` 类型下。** 不予采用,因为 payload 启发式判断会使畸形行产生歧义,并把物理解码耦合到未来逻辑 payload 字段。显式标签会明确失败。
**把 `SessionHeader` 字段存入可扩展元数据 blob。** Schema 17 不采用该方案,因为 `agentPreset` 是 JSONL 与 SQLite 共同使用的强类型核心恢复不变量,而不是提供方扩展元数据。直接持久化已校验的核心字段可使两个后端保持一致;在没有当前生产方的情况下加入无类型兜底字段,只会增加另一套兼容机制。只有核心层定义由所有后端实现、带命名空间的 `SessionHeader` 扩展协议后,才应重新考虑该方案。
**把 `SessionHeader` 字段存入可扩展元数据 blob。** Schema 18 不采用该方案,因为 `agentPreset` 是 JSONL 与 SQLite 共同使用的强类型核心恢复不变量,而不是提供方扩展元数据。直接持久化已校验的核心字段可使两个后端保持一致;在没有当前生产方的情况下加入无类型兜底字段,只会增加另一套兼容机制。只有核心层定义由所有后端实现、带命名空间的 `SessionHeader` 扩展协议后,才应重新考虑该方案。
**通过配置或实时注册表暴露压缩规则。** 不予采用,因为同一版本数据库必须能独立于运行时拓扑被读取。Codec 在源码层保持模块化,但持久规则集由 schema 版本固定。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.md
2026-08-19-projection-cache-per-session-files.md: 9e102e786a6c06d82d1a0f45cc2f96a50c8abcd8
2026-08-19-projection-cache-per-session-files.zh.md: d875c3f57800936f66fbf65233637df9bf300e2d
@@ -0,0 +1,31 @@
# Agent Note: Projection cache as per-session files
Status: implemented
English | [中文](2026-08-19-projection-cache-per-session-files.zh.md)
## Problem
The persisted projection cache was one global `session_projcache.json` — a `sessions` table in a single file at the storage root. Every throttled checkpoint rewrote the whole file containing every session's rows, so write amplification grew with session count, and one malformed file took the entire cache down at once.
## Decision
The cache opens the `session_projcache` storage domain in the new `per-record` layout, added to the json backend: one version-stamped document per session at `<root>/session_projcache/sessions/<id>.json`, owned by the storage stack — `storage` / `storage-json` / `storage-domain` live in the shared base bundle alongside the cache, and the cache itself is a plain domain consumer again. Every shipped base-backed profile keeps the cache enabled, so the session producer records checkpoints independently of whether its current application exposes a listing interface; `sdk-minimal`, which does not use the base bundle, remains outside this composition. The cache never consults the persistence layer: no `locate`, no dependency on which backend is mounted.
Reads and writes share ONE coherent state: every read (`cachedSnapshot`) is a synchronous lookup in the domain's in-memory tables (zero I/O), and every write queues on the domain's per-unit write chain, mutating memory only after durability — no direct disk reads that could lag the throttled writes. The cache keeps every other responsibility: checkpoint fold, write policy (turn/end + disposal mandatory, count/interval throttle), fail-soft durability, and the listing read. `cachedSnapshot(meta)` is synchronous. The cache runs no cold-refold ladder (that would require reading the session log, which belongs to the persistence layer); a consumer that needs a guaranteed cold snapshot refolds from the log itself. The json backend creates its tree owner-only (`0o700`).
## Consequences
- Per-session write isolation: each throttled write replaces only that session's small document, removing the global write amplification. The domain write chain serializes writes, so a newer cut never lands before an older one; domain close drains in-flight writes.
- Listing is a synchronous in-memory read; a session without a record document simply lacks the projection column.
- ACP, headless, SDK, and Web sessions publish cache rows for later consumers. The log-leading durability barrier may flush a covered prefix at the cache cadence and split otherwise coalesced physical JSONL runs; recorded profile snapshots re-pack the logical event stream so cache timing does not define fixture layout.
- The per-record contract scopes failure: a malformed or stale-version document reads as an absent record at open, so one bad file never bricks the cache, and a checkpoint schema bump discards stale sessions per record instead of rejecting the whole domain.
- The json backend bootstraps the per-record tree from the legacy whole-unit cache only when enumeration finds no new-layout document path. Any new document path, including an unreadable or stale file, suppresses the bootstrap for the whole unit; missing session rows refold from the log. The legacy file remains untouched.
- The cache record is bound to the same log lifecycle as before: the stored `{createdAt, cwd}` identity guards against a recreated id.
## Alternatives considered
- **Keep the global sessions table.** Preserves one-load listing, but keeps the global write amplification and single-file blast radius that motivated the change.
- **Cache-owned per-session files** (`<root>/<session-id>/projection_cache.json`, the first revision of this change). Tried and reverted in review: the cache hand-rolled the medium — paths, per-path write chains, in-flight tracking, owner-only file modes, and a sqlite no-path special case — and its listing read hit the disk directly on every call while writes were throttled, so reads and writes were never consistent.
- **Resolve the path through `sessionPersistence.locate(meta)`** (the file beside the session log). Rejected: the cache would have to guess "beside the log" from a log artifact path (`dirname` + fixed filename), coupling the cache to the persistence service and to a backend's layout.
- **Make `per-record` a mode of the existing unit instead of a separate unit class.** Rejected: the two layouts have genuinely different state models — `single` is memory-authoritative with whole-file publish, `per-record` is stateless (the directory is the state; `loadAll` re-reads the tree) — so they are separate small classes behind one backend, with record keys validated path-safe instead of encoded.
@@ -0,0 +1,31 @@
# Agent Note:投影缓存改为每会话文件
Status: implemented
[English](2026-08-19-projection-cache-per-session-files.md) | 中文
## Problem
持久投影缓存曾是单个全局 `session_projcache.json`——存储根目录下一个文件里的 `sessions` 表。每次节流检查点都会重写包含所有会话行的整个文件,写放大随会话数量增长;且一个畸形文件会让整个缓存一起失效。
## Decision
缓存打开采用新增 `per-record` 布局的 `session_projcache` 存储域:每个会话一个带版本戳的文档,位于 `<root>/session_projcache/sessions/<id>.json`,介质归存储栈所有——`storage` / `storage-json` / `storage-domain` 与缓存一起落在共享 base 装配里,缓存重新变回纯粹的域消费方。所有随附且基于 base 的 profile 都保持启用缓存,因此会话生产方会记录检查点,不取决于当前应用是否提供列表接口;不使用 base 组合包的 `sdk-minimal` 不在此装配范围内。缓存绝不咨询持久化层:没有 `locate`、不依赖挂载的是哪个后端。
读写共享同一份一致状态:每次读取(`cachedSnapshot`)都是对域内存表的同步查找(零 I/O);每次写入排进该域的单条写链,先落盘成功才改内存——不再有落后于节流写入的直读磁盘。缓存保留其余全部职责:检查点折叠、写策略(turn/end + dispose 强制点、count/interval 节流)、fail-soft 持久化与列表读。`cachedSnapshot(meta)` 是同步的。缓存不运行冷重折叠阶梯(那需要读取会话日志,属于持久化层的职责);需要保证冷快照的消费方自行从日志重折叠。json 后端以仅属主权限(`0o700`)创建自己的目录树。
## Consequences
- 每会话写入隔离:每次节流写入只替换该会话的小文档,消除全局写放大。域写链将写入串行化,新切面绝不会先于旧切面落盘;域关闭时会排空在途写入。
- 列表读取是同步内存读;没有记录文档的会话只是缺少投影列。
- ACP、headless、SDK 与 Web 会话都会发布缓存行,供后续消费方使用。确保日志领先的持久性屏障可能按缓存节奏 flush 已覆盖的前缀,并拆分原本会合并的物理 JSONL 行;各 profile 的录制快照会重新 pack 逻辑事件流,因此缓存时序不会决定 fixture 布局。
- per-record 契约把故障范围缩小到单记录:畸形或过期版本的文档在打开时读作"无此记录",单个坏文件不会拖垮整个缓存;检查点 schema 升级按会话丢弃过期行,而不是拒绝整个域。
- json 后端仅在枚举时没有发现任何新布局文档路径,才从旧整单元缓存引导 per-record 目录树。只要存在任意新文档路径,即使文件不可读或版本陈旧,也会对整个单元禁用引导;缺失的会话行从日志重折叠。旧文件保持不变。
- 缓存记录仍绑定同一日志生命周期:存储的 `{createdAt, cwd}` 身份防止被重建的 id 误导。
## Alternatives considered
- **保留全局 sessions 表。** 保留一次加载式列表,但保留了促成此改动的全局写放大与单文件爆炸半径。
- **缓存自持的每会话文件**`<root>/<session-id>/projection_cache.json`,本改动的第一版)。试过并在评审中回退:缓存手搓了介质——路径、按路径的写链、在途跟踪、仅属主文件权限,以及 sqlite 无路径特判——而且它的列表读每次调用都直读磁盘、写却在节流,读写永不一致。
- **经 `sessionPersistence.locate(meta)` 解析路径**(文件放在会话日志旁)。未采用:缓存得从日志 artifact 路径"猜"日志旁边(`dirname` + 固定文件名),把缓存耦合到持久化服务与后端的布局。
- **把 `per-record` 做成既有单元的一种模式而非独立单元类。** 未采用:两种布局的状态模型本质不同——`single` 内存权威、整文件发布;`per-record` 无状态(目录即状态,`loadAll` 重扫目录树)——所以它们是同一后端下的两个小型独立类,记录键做路径安全校验而非编码。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-resume-selector-batch-projection.md
2026-07-31-resume-selector-batch-projection.md: 5b0b33c34137efb95f31e07e7fce7622aeb2dbce
2026-07-31-resume-selector-batch-projection.zh.md: a8e89c2e989680a1d1bf98f0cd2817b5900d36d0
2026-07-31-resume-selector-batch-projection.md: 387d05e055c2b90f3aa7ee39d624c117ba54b4b1
2026-07-31-resume-selector-batch-projection.zh.md: febd744b3f7ec58dab94f5d8437feaa270dfffcf
@@ -12,7 +12,7 @@ Opening the TUI `/resume` selector called `sessionQuery.readSession()` once per
Selector rows fold nothing but titles, and everything else a row shows comes from metadata:
- Titles come from the projection system: `session-title` already registers a `title` unit, so a live row reads the registry snapshot, a persisted row reads the durable checkpoint row (`sessionProjectionCache.cachedSnapshot`, zero I/O), and only a row without a usable checkpoint pays a `coldSnapshot` — checkpoint plus a `readFrom` tail, written back so the next scan is zero-I/O. Cold reads are bounded by the TUI `resumeScanConcurrency` config. A composition without the cache falls back to one bounded `readTitleSnapshots` batch over the logs; either path isolates a per-row failure into the disabled "Unreadable session" fallback.
- Titles come from the projection system: `session-title` already registers a `title` unit, so a live row reads the registry snapshot, a persisted row reads the durable checkpoint row (`sessionProjectionCache.cachedSnapshot`, one file read per session), and only a row without a usable checkpoint pays a `coldSnapshot` — checkpoint plus a `readFrom` tail, written back so the next scan is zero-I/O. Cold reads are bounded by the TUI `resumeScanConcurrency` config. A composition without the cache falls back to one bounded `readTitleSnapshots` batch over the logs; either path isolates a per-row failure into the disabled "Unreadable session" fallback.
- The activity timestamp never reads a log: a live session uses its last in-memory event time; a persisted session stats the artifact named by the optional `sessionPersistence.locate()` (mtime), falling back to the header's creation time when the backend locates no per-session artifact (SQLite) or the stat fails. Any append moves the mtime, so a mere pickup boundary now floats a browsed session up — accepted as the price of a metadata-only timestamp.
- The last-turn label, provider/model route, and goal phase columns are gone from rows. Route availability is now enforced by the Enter-time preflight, which fully reads and replay-validates the one chosen log through `readSession` before handoff.
@@ -12,7 +12,7 @@ Status: implemented
选择器行除标题外不折叠任何内容,行内其余信息全部来自元数据:
- 标题来自投影系统:`session-title` 已注册 `title` 投影单元,因此实时行读取注册表快照,持久化行读取持久 checkpoint 行(`sessionProjectionCache.cachedSnapshot`零 I/O),只有没有可用 checkpoint 的行才付出一次 `coldSnapshot`——checkpoint 加 `readFrom` 尾部折叠,并写回使下次扫描零 I/O。冷读取受 TUI `resumeScanConcurrency` 配置约束。未挂载缓存的组合回退到一次对日志的有界 `readTitleSnapshots` 批量读取;两条路径都把单行失败隔离为禁用的「Unreadable session」回退。
- 标题来自投影系统:`session-title` 已注册 `title` 投影单元,因此实时行读取注册表快照,持久化行读取持久 checkpoint 行(`sessionProjectionCache.cachedSnapshot`每会话一次文件读取),只有没有可用 checkpoint 的行才付出一次 `coldSnapshot`——checkpoint 加 `readFrom` 尾部折叠,并写回使下次扫描每会话一次文件读取。冷读取受 TUI `resumeScanConcurrency` 配置约束。未挂载缓存的组合回退到一次对日志的有界 `readTitleSnapshots` 批量读取;两条路径都把单行失败隔离为禁用的「Unreadable session」回退。
- 活动时间戳从不读取日志:实时会话取内存中最后一个事件的时间;持久化会话对可选 `sessionPersistence.locate()` 命名的产物做 stat(mtime),当后端定位不到按会话的产物(SQLite)或 stat 失败时回退到 header 的创建时间。任何追加都会移动 mtime,因此仅仅一次 pickup 边界也会让浏览过的会话上浮——这是元数据时间戳的代价,予以接受。
- 行内不再有最后轮次标签、提供方/模型路由和目标阶段列。路由可用性改由 Enter 时的预检强制:预检通过 `readSession` 完整读取并回放验证选中的那一份日志后才移交。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-05-agent-teams.md
2026-08-05-agent-teams.md: d37777a0cca467edcec5d38999aee53aaf14dc36
2026-08-05-agent-teams.zh.md: 91571bd28aa44a21bebe1ef9cfc8434c2e8850a3
2026-08-05-agent-teams.md: 9924550a04b636535ce1daa329865beb1c9e951d
2026-08-05-agent-teams.zh.md: 91ae807f3005c173a61f9fc32661d2b650692a04
@@ -10,7 +10,7 @@ The subagent seam supplies fresh/fork providers, durable child Sessions, FIFO fo
All same-process Agents also share one checkout. Filesystem edit tools can reject an observed stale version, but Bash, formatters, generators, and external writers bypass that fence. Treating a teammate name or task owner as a file lock would hide rather than solve this concurrency boundary.
The model-visible Team tools remain opt-in so the default tool catalog and simple-task behavior do not change. An explicitly requested Team must survive child Activation settlement and mailbox delivery races long enough for the Lead to aggregate the result before process teardown.
Agent Teams needs an explicit source-checkout composition before its public contracts are stable enough for released CLI or Web bundles. The default tool catalog and simple-task behavior must remain unchanged, while an explicitly requested Team must survive child Activation settlement and mailbox delivery races long enough for the Lead to aggregate the result before process teardown.
## Decision
@@ -54,7 +54,7 @@ Worktree isolation is not a harness runtime behavior. A deployment or prompt may
**Create isolated worktrees automatically.** Rejected because worktree creation, branch naming, merge policy, ignored files, build artifacts, and cleanup are deployment choices. It also changes the same-world behavior existing subagents and sandboxes expose.
**Enable Teams in the default catalog.** Rejected because scoped Team controls would shadow same-named legacy globals and unsolicited delegation would add latency and token cost to simple tasks. Explicit composition keeps model-visible ownership unambiguous without changing shipped requests.
**Enable Teams in the default catalog.** Rejected because scoped Team controls would shadow same-named legacy globals and unsolicited delegation would add latency and token cost to simple tasks. A private profile bundle inserts Team and disables the legacy controls without adding Team packages to shipped dependency graphs.
**Use an in-memory board and mailbox.** Rejected because child settlement, HMR, and process interruption would lose accepted coordination state and make retries ambiguous.
@@ -62,7 +62,7 @@ Worktree isolation is not a harness runtime behavior. A deployment or prompt may
## Testing
Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL/SQLite reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless headless Loader snapshot assembles the real Team plugins and records teammate creation, peer mail, dependent tasks, waiting, and Lead aggregation.
Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL/SQLite reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless product snapshot loads the private Agent Teams profile bundle through `dsh --profile headless` and pins its complete model-visible tool list, Team policy, and durable workflow projection for two teammates, dependent tasks, peer delivery, waiting, completion, and aggregation. A CLI e2e reuses the same deterministic adapter and verifies normal process exit with persisted Team and child logs.
## Consequences
@@ -10,7 +10,7 @@ subagent seam 已提供 freshfork provider、持久 child Session、FIFO foll
同进程 Agent 还共享一个 checkout。文件系统 edit 工具可以拒绝已观察到的陈旧版本,但 Bash、formatter、generator 与外部 writer 会绕过该屏障。把 teammate name 或 task owner 当作文件锁只会掩盖而不是解决该并发边界。
面向模型的 Team 工具保持显式启用,使默认工具目录与简单任务行为不变。显式请求的 Team 必须能跨越 child Activation settlement 与 mailbox 投递竞争,使 Lead 在进程 teardown 前汇总结果。
在公开约定稳定到足以进入已发布 CLI 或 Web bundle 前,Agent Teams 需要显式的源码 checkout 组合。默认工具目录与简单任务行为必须保持不变;而显式请求的 Team 必须能跨越 child Activation settlement 与 mailbox 投递竞争,使 Lead 在进程 teardown 前汇总结果。
## Decision
@@ -54,7 +54,7 @@ Worktree isolation 不是 harness runtime 行为。deployment 或 prompt 可以
**自动创建隔离 worktree。** 拒绝,因为 worktree 创建、branch 命名、merge 策略、ignored file、构建产物与 cleanup 都是 deployment 选择;它也会改变既有 subagent 与 sandbox 暴露的 same-world 行为。
**在默认工具目录中启用 Team。** 拒绝,因为 scoped Team control 会覆盖同名旧全局工具,主动 delegation 也会给简单任务增加延迟和 token 成本。显式组合可以保持面向模型的归属明确,同时不改变默认 request
**在默认工具目录中启用 Team。** 拒绝,因为 scoped Team control 会覆盖同名旧全局工具,主动 delegation 也会给简单任务增加延迟和 token 成本。私有 profile bundle 会插入 Team 并禁用旧 control,同时不向已发布依赖图添加 Team 包
**使用内存 task board 与 mailbox。** 拒绝,因为 child settlement、HMR 与进程中断会丢失已接受协调状态,并让重试变得含糊。
@@ -62,7 +62,7 @@ Worktree isolation 不是 harness runtime 行为。deployment 或 prompt 可以
## Testing
Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONLSQLite 对账、target-local 并发顺序、pendinghistory 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait canceltimeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless headless Loader 快照会组合真实 Team 插件,并记录 teammate 创建、peer mail、依赖任务、等待与 Lead 汇总
Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONLSQLite 对账、target-local 并发顺序、pendinghistory 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait canceltimeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless 产品快照会通过 `dsh --profile headless` 加载私有 Agent Teams profile bundle,并为两个 teammate、依赖任务、peer 投递、等待、完成和汇总固定完整的面向模型工具列表、Team policy 与持久 workflow 投影。CLI e2e 会复用同一个确定性 adapter,并验证带持久 Team 与 child 日志的正常退出
## Consequences
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-18-conversation-adaptive-content-width.md
2026-08-18-conversation-adaptive-content-width.md: 9b0e7afbded296b60b7c283030a537321b36684e
2026-08-18-conversation-adaptive-content-width.zh.md: f1f424cf9d4ac087518e9f79a365d0052069b999
@@ -0,0 +1,35 @@
# Agent Note: Adaptive and drag-resizable conversation content width
Status: implemented
English | [中文](2026-08-18-conversation-adaptive-content-width.zh.md)
## Problem
The conversation column's shared width axis (`--dsh-chat-content-width`) was the fixed figma constant 748px. On wide monitors (a 4000px display leaves a ~3500px column) the transcript occupied under a quarter of the column with dead margins on both sides. Every derived surface — the input card (W + 32px), dock cards, takeover panels, StatsLine, the back-to-bottom padding formula — rides this one variable, so any change had to keep the whole column's alignment relations intact. Alongside the adaptive default, users asked for direct control: hover the transcript's side margins to get a col-resize cursor and drag either edge, with both edges moving symmetrically.
## Decision
**The axis becomes a user override over an adaptive clamp.** `ConversationRoot.module.css` declares `--dsh-chat-content-width: var(--dsh-chat-user-width, clamp(680px, calc(var(--dsh-conversation-column-width, 0px) * 0.64), 920px))`. The floor is 680px — one step under the figma 748px, after full-width reading felt wide on every screen — wider columns take 64% of the column, and 920px caps line length for readability (~113 characters at the base font). A dragged preference replaces the adaptive term wholesale.
**The column width is published by a ResizeObserver, not container queries.** The component publishes the root's `offsetWidth` as `--dsh-conversation-column-width` in px (the same callback-ref pattern as the existing composer seat height observer). `container-type: inline-size` was rejected: the conversation subtree contains portal-free `position: fixed` descendants (Tooltip, Menu, JsonTree copy anchors) whose viewport anchoring a size container would capture — the same class of trap the `.composerHero` comment records for transforms. A bare `%` in the variable was rejected because custom-property percentages resolve per consumer against different containing blocks, breaking the input-card = W + 32px invariant; `vw` was rejected because the column is not the viewport (sidebar fold changes the column only).
**Drag handles are 40px strips beside the transcript, symmetric by construction.** Each strip's inner edge sits 24px outside the content column and extends 40px outward, with the outer edge clamped to keep a 24px safe zone from the column edges (24 + 40 + 24 = the 88px-per-side budget below); when the margin cannot fit inset + strip + safe zone the computed width goes negative and the strip resolves to zero. Both handles write the one centered width — outward travel widens by 2× the pointer distance — reusing AppFrame's DragHandle capture model (pointer capture + rAF throttle + drag-start snapshot); only a gesture with actual pointer travel commits to storage, so a bare press-and-release on a window-clamped width cannot overwrite the wider stored preference. The hover indicator is a 3px glow riding the pointer's Y (published as `--dsh-width-handle-pointer-y` on pointermove): a 24px solid core fading over 40px each side, in the scrollbar hover tint because border-token alphas disappear against the base fill. Handles render only in the active phase; views that elect a composer overlay (trajectory) hide them, and the header lifts above them (z-index 9) to stay clickable.
**The preference persists in `localStorage` (`dsh.conversation.contentWidth`) and clamps without rewriting.** The displayed width re-clamps to `[640px, column 176px]` when the column shrinks (88px per side keeps the handles fully placeable — a wider drag would push its own handles off the column), but the stored preference survives — widening the window restores it, the same rule AppFrame's sidebar drag follows. The handle carries no reset affordance and no tooltip; a stored preference is only ever replaced by another drag.
**The user bubble cap follows the axis.** `min(525px, 82%)` becomes `min(calc(var(--dsh-chat-content-width, 748px) * 0.702), 82%)` (0.702 = 525/748, the figma bubble share of the figma column) in both `ui-conversation` MessageItem and the symmetric `ui-goal` command bubble, so bubbles scale with the column. The 748px fallback covers mounts outside the conversation column.
## Alternatives considered
**Raise the constant (748 → ~850).** Rejected: every mid-width window's line length grows too, hurting readability where most users live.
**Wide-content bleed (code blocks and tool cards break out of the prose column).** Best reading ergonomics but touches MarkdownText and every tool card's layout; deferred as a possible second phase.
**A settings-backed "wide mode" toggle.** Adds a persistent settings surface for what drag already covers; not needed.
**A 12px handle strip beside the input card.** Shipped first and unusable in practice: on a wide screen the strip was a sliver in a thousand-plus pixels of margin, and the sticky input card overlapped it. Replaced by the 40px strip anchored to the glow line's position.
## Consequences
Ordinary windows read slightly narrower than the figma baseline (680px floor). Wide columns widen the transcript to at most 920px, and a drag can take it anywhere in `[640px, column 176px]`, both without touching any derived surface: input card, dock cards, takeover panels, and the back-to-bottom formula follow the axis they already consumed. A known ~4px centering offset between the handle (column-centered) and the content box (centered after scrollbar-gutter reservation) stays well inside the 40px strip. The 680px / 64% / 920px numbers are one declaration in `ConversationRoot.module.css` mirrored by `resolveContentWidth` in the component; retuning them touches nothing else.
@@ -0,0 +1,35 @@
# Agent Note:会话正文宽度自适应与拖拽调宽
Status: implemented
[English](2026-08-18-conversation-adaptive-content-width.md) | 中文
## 问题
会话列的共享宽度轴(`--dsh-chat-content-width`)是 figma 定值 748px。在宽显示器上(4000px 屏幕的会话列约 3500px)正文只占列宽不到四分之一,两侧是大片空白边距。所有派生表面——输入卡(W + 32px)、dock 卡片、takeover 面板、StatsLine、回底按钮的 padding 公式——都由这一个变量推导,任何改动都必须保持整列的对齐关系。在自适应默认值之外,用户还要求直接控制:hover 正文两侧边距出现 col-resize 光标,拖任一侧、两侧对称联动。
## 决策
**宽度轴变为"用户覆盖 + 自适应 clamp"。** `ConversationRoot.module.css` 声明 `--dsh-chat-content-width: var(--dsh-chat-user-width, clamp(680px, calc(var(--dsh-conversation-column-width, 0px) * 0.64), 920px))`。下限 680px——比 figma 的 748px 低一档,因为满宽阅读在各种屏幕上都显宽——更宽的列取列宽的 64%,920px 封顶保证行长可读性(基准字号下约 113 字符)。拖拽偏好存在时整体替换自适应项。
**列宽由 ResizeObserver 发布,不用容器查询。** 组件把根节点的 `offsetWidth` 以 px 发布为 `--dsh-conversation-column-width`(与既有 composer seat 高度 observer 相同的 callback-ref 模式)。拒绝 `container-type: inline-size`:会话子树内有不经 portal 的 `position: fixed` 后代(Tooltip、Menu、JsonTree 复制锚点),尺寸容器会捕获它们的视口定位——与 `.composerHero` 注释记录的 transform 陷阱同类。拒绝变量里的裸 `%`:自定义属性百分比在各消费点按不同包含块解析,破坏输入卡 = W + 32px 不变量;拒绝 `vw`:列不等于视口(侧栏折叠只改列宽)。
**拖拽手柄是正文两侧 40px 宽的条,对称是构造性的。** 每条内边缘位于内容列外 24px、向外延伸 40px,外边缘被钳制在距列缘至少 24px 的安全区(24 + 40 + 24 = 下文每侧 88px 的预算);边距装不下"内偏移 + 热区 + 安全区"时计算宽度为负、热区解析为零。两个手柄写同一个居中宽度——向外拖按指针位移 2 倍变宽——复用 AppFrame DragHandle 的捕获模型(指针捕获 + rAF 节流 + 拖拽起点快照);只有指针确实产生位移的手势才提交存储,因此在被窗口钳制的宽度上按下即松开不会用钳制后的显示值覆盖更宽的已存偏好。hover 提示是跟随指针 Y 的 3px 光带(pointermove 发布 `--dsh-width-handle-pointer-y`):24px 实色核心、两侧各 40px 渐变,用滚动条 hover 色——border token 的透明度在底色上几乎不可见。手柄只在 active 阶段渲染;选举了 composer overlay 的视图(trajectory)隐藏手柄,header 提升到手柄之上(z-index 9)保持可点。
**偏好持久化在 `localStorage``dsh.conversation.contentWidth`),钳制不改写。** 列收窄时显示宽度重新钳制到 `[640px, 列宽 176px]`(每侧预留 88px 保证手柄永远放得下),但存储的偏好保留——拉宽窗口自动恢复,与 AppFrame 侧栏拖拽同规则。手柄不带重置操作也不带 tooltip;已存储的偏好只会被下一次拖拽替换。
**用户气泡上限跟随宽度轴。** `min(525px, 82%)` 改为 `min(calc(var(--dsh-chat-content-width, 748px) * 0.702), 82%)`0.702 = 525/748,即 figma 气泡占 figma 列宽的比例),`ui-conversation` MessageItem 与对称的 `ui-goal` 命令气泡同步,气泡随列缩放。748px 缺省值覆盖会话列之外的挂载。
## 备选方案
**调大常量(748 → 约 850)。** 拒绝:所有中等宽度窗口的行长一起变长,伤及多数用户的可读性。
**宽内容出血(代码块、工具卡片突破散文列)。** 阅读工效最佳但涉及 MarkdownText 和所有工具卡片布局;作为可能的二期推迟。
**settings 支持的"宽屏模式"开关。** 为拖拽已覆盖的能力增加持久设置面;不需要。
**输入卡旁 12px 手柄条。** 首版实现,实践中不可用:宽屏上千余像素的边距里只有一条细缝,且 sticky 输入卡遮挡它。改为锚定在光带位置的 40px 条。
## 影响
普通窗口的阅读宽度比 figma 基线略窄(下限 680px)。宽列正文最多放宽到 920px,拖拽可取 `[640px, 列宽 176px]` 内任意值,两者都不触碰任何派生表面:输入卡、dock 卡片、takeover 面板和回底公式沿用它们本就消费的宽度轴。手柄(按列居中)与内容盒(按滚动条预留后居中)之间约 4px 的已知偏差完全落在 40px 热区内。680px / 64% / 920px 三个数值在 `ConversationRoot.module.css` 一处声明、由组件内 `resolveContentWidth` 镜像;重调它们不影响其他代码。
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md
2026-08-10-session-log-version-mechanism.md: 81108ceaf23405c8f2def9aaef88505d635808a3
2026-08-10-session-log-version-mechanism.zh.md: cbb127420e2695853fdc2ad0bb98a7a0bf230b5b
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-18-settings-font-size-control.md
2026-08-18-settings-font-size-control.md: 34ee7f28f58a0a5a5dc8cd39bc4cecf5e081359f
2026-08-18-settings-font-size-control.zh.md: 2c2ea714d50ca1f934d78ede6175935338488331
@@ -0,0 +1,31 @@
# Agent Note: Settings-backed conversation content font size
Status: implemented
English | [中文](2026-08-18-settings-font-size-control.zh.md)
## Problem
The conversation's body text size was fixed (14px after the 0.875 markdown-ladder rescale). Users asked for a Settings control: a "字号大小" row under General → Appearance with a stepper, range 1217, default 14, that resizes the transcript body text and the composer input text together.
## Decision
**The theme plugin owns the setting.** `ThemeSettingsSchema` gains `fontSize` (`z.number().step(1).min(12).max(17).default(14)`) beside `preference` in the existing `ui-theme` namespace — one durable section, one settings scope, one adoption path. `ThemeRuntime` carries `fontSize` in `ThemeSnapshot`, exposes `setFontSize(px)` (integer-and-range validated, throws a teaching error), and republishes on `theme/change`. The same plugin registers the FontSizeRow into `settings.general.item` at order 11, directly under the Appearance cubes (order 10).
**Presentation rides the existing snapshot pipeline.** The service never touches the DOM: ui-layout's `ThemePresenter` writes `--dsh-content-font-size` on `body` from each snapshot (and retracts it on dispose), and the Host boot script embeds the durable value in the index response so first paint uses the chosen size — the same pre-plugin path the dark-mode attribute takes, avoiding a font-size flash.
**One CSS delta variable moves the ladder.** `gradient-shadow-text.css` derives `--dsh-content-font-delta: calc(var(--dsh-content-font-size, 14px) - 14px)` and shifts the markdown h1h4 and base variants (size and line height) by that same px increment, preserving the heading hierarchy and each variant's leading. Table, small, and code variants stay fixed — as does the interrupted-turn `.stopped` tag (11px): they are dense secondary text whose defaults would fall below legibility when stepped down. Consumers outside the token ladder read `var(--dsh-content-font-size, 14px)` (or `calc(<own default> + var(--dsh-content-font-delta, 0px))` for smaller steps) and `calc(<default line-height> + var(--dsh-content-font-delta, 0px))` directly: the assistant narration root, the user bubble (reference summaries and their inline glyphs included), the composer card (whose textarea/mirror/backdrop stack inherits font metrics from the card by design), and the flow chrome around them — the shared DisclosureRow header (tool calls, think, commands; row height, title, and leading box all move) with its expanded bodies' `22px + delta` indent keeping content aligned under the shifted title start, ToolRow/bash-row summaries and file links, think text (12px keeping its 2px step under the body), compaction/context/retry/error rows, StatsLine, the chat hint and open-error strips, the workflow-run panel (run/phase headers and expanded member rows), the message clock and icon actions (slot-injected message-feedback actions match through the same variables), and the turn status line. Flow icons scale through each leading box's CSS edge (`svg` width/height overriding the glyph attributes); StateDot is exempt via its `data-state` attribute — a status mark, not text furniture. The 14px fallbacks keep every surface pixel-identical when the variable is absent (tests, storybook-like mounts, remote compositions before adoption).
**The stepper is a pill, not a menu.** The row reuses the selector-pill geometry (h36 r18 module fill) with the value centered in the pill, the up/down arrow column revealed on hover/focus-within and absolutely anchored to the pill's right edge (so revealing never moves the value), and a `px` unit label after the pill. A tertiary description line under the title states the scope — the size only affects conversation content, not the application chrome. Arrows disable at the bounds; the display follows the store mirror, never the click echo — the same store/face pattern as the Appearance row.
## Alternatives considered
**A separate settings namespace or plugin.** Rejected: the font size is an appearance preference with the same persistence, adoption, and remote-browser semantics as the theme preference; a second namespace duplicates the scope machinery for one integer.
**Scaling via a multiplier (`em`/percentage) instead of a px delta.** Rejected: multiplying spreads the 1217px range disproportionately across the ladder (21px h1 would swing ~1825.5px) and produces fractional line heights; the fixed px shift keeps every step integer and the hierarchy's px gaps intact.
**Scaling every font token (tables, code, small).** Rejected: those variants are secondary/dense by design; at 2 the small ladder would hit 10px and code 9px, below legibility.
## Consequences
The 0.875 markdown-ladder rescale (body 16 → 14) ships with this change as the new default rendering; at delta 0 every axis consumer is pixel-identical to that rescaled baseline, and surfaces without the variable fall back to the same 14px. A changed size persists in `$DSH_HOME/settings.yaml`, survives reloads without flashing (the boot script writes the durable value pre-hydration and `ThemeRuntime` seeds its initial snapshot from it), applies live across transcript and composer, and remote browsers keep the process-local-selection rule the theme preference already has. `setFontSize` joins the model-visible cordis client API catalog beside `setTheme`.
@@ -0,0 +1,31 @@
# Agent NoteSettings 支撑的会话正文字号
状态:已实现
[English](2026-08-18-settings-font-size-control.md) | 中文
## 问题
会话正文字号是固定的(markdown 阶梯按 0.875 重缩放后为 14px)。用户需要一个设置项:在 General → Appearance 下加一行「字号大小」,用步进器交互,范围 12–17,默认 14,同时调整转录正文与 composer 输入框的文字大小。
## 决策
**主题插件拥有该设置。**`ThemeSettingsSchema` 在既有 `ui-theme` namespace 的 `preference` 旁新增 `fontSize``z.number().step(1).min(12).max(17).default(14)`)——一个持久化 section、一个 settings scope、一条采纳路径。`ThemeRuntime``ThemeSnapshot` 中携带 `fontSize`,暴露 `setFontSize(px)`(校验整数与范围,越界抛教学式错误),并通过 `theme/change` 重新发布。同一插件把 FontSizeRow 注册进 `settings.general.item`order 11,紧挨外观方块(order 10)之下。
**呈现走既有快照管线。**服务绝不接触 DOM:ui-layout 的 `ThemePresenter` 依据每份快照在 `body` 上写 `--dsh-content-font-size`dispose 时收回),Host 引导脚本把持久化值嵌入 index 响应,让首帧就使用所选字号——与暗色属性同一条插件前路径,避免字号闪变。
**一个 CSS 增量变量平移阶梯。**`gradient-shadow-text.css` 派生 `--dsh-content-font-delta: calc(var(--dsh-content-font-size, 14px) - 14px)`,把 markdown h1h4 与 base 各变体(字号与行高)按同一像素增量平移,保持标题层级与各变体的行距。表格、small 与 code 变体保持固定——中断回合的 `.stopped` 标签(11px)同样固定:它们是密集次级文本,其默认值在字号下调时会低于可读下限。token 阶梯之外的消费方直接读取 `var(--dsh-content-font-size, 14px)`(较小档位则用 `calc(<自身默认> + var(--dsh-content-font-delta, 0px))`)与 `calc(<默认行高> + var(--dsh-content-font-delta, 0px))`:助手正文根节点、用户气泡(含引用摘要及其行内字形)、composer 卡片(其 textarea/mirror/backdrop 三层按设计从卡片继承字体度量),以及围绕它们的流内 chrome——共享的 DisclosureRow 头部(工具调用、think、命令;行高、标题与 leading 盒同步移动),其展开内容以 `22px + delta` 缩进跟随平移后的标题起点保持对齐、ToolRow/bash 行的 summary 与文件链接、think 正文(12px,保持比正文小 2px 的层级)、compaction/context/retry/错误行、StatsLine、chat 提示与打开失败条、workflow-run 面板(run/phase 头部与展开的成员行)、消息时钟与图标操作(slot 注入的消息反馈操作经同一对变量同步缩放),以及回合状态行。流内图标经由各 leading 盒的 CSS 边长缩放(`svg` width/height 覆盖字形自身属性);StateDot 通过其 `data-state` 属性豁免——它是状态标记,不是文字组件。14px 回退让变量缺席时(测试、独立挂载、采纳前的远程组合)所有表面逐像素不变。
**步进器是药丸控件,不是菜单。**该行复用选择器药丸几何(h36 r18 模块填充),数值在药丸内居中,上下箭头列在 hover/focus-within 时显示并绝对定位在药丸右缘(显示时数值不移动),药丸后带 `px` 单位标签。标题下方的三级说明行标明作用范围——字号仅影响会话内容,不影响应用外框。到达边界时对应箭头禁用;显示跟随 store 镜像,绝不跟随点击回声——与外观行相同的 store/face 模式。
## 已考虑的替代方案
**独立 settings namespace 或独立插件。**否决:字号与主题偏好具有相同的持久化、采纳与远程浏览器语义,属外观偏好;为一个整数复制一套 scope 机制不值得。
**用倍率(`em`/百分比)而非像素增量缩放。**否决:乘法会让 12–17px 的范围在阶梯上不成比例地放大(21px 的 h1 会摆动到约 18–25.5px),并产生小数行高;固定像素平移让每一档都是整数,层级间的像素差保持不变。
**缩放全部字体 token(表格、code、small)。**否决:这些变体按设计就是次级/密集文本;−2 档时 small 阶梯会降到 10px、code 降到 9px,低于可读下限。
## 后果
0.875 的 markdown 阶梯重缩放(正文 16 → 14)作为新的默认渲染随本变更一同交付;增量为 0 时所有轴消费方与该重缩放基线逐像素一致,无变量的表面回退到同样的 14px。修改后的字号持久化在 `$DSH_HOME/settings.yaml`,重载不闪变(引导脚本在 hydration 前写入持久化值,`ThemeRuntime` 以它为初始快照种子),在转录与 composer 上实时生效;远程浏览器沿用主题偏好既有的进程内选择规则。`setFontSize``setTheme` 一同进入模型可见的 cordis 客户端 API 目录。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md
2026-07-04-doc-tiers-and-budgets.md: 3f263864b9b6ee9479d1133b908617f10073dd66
2026-07-04-doc-tiers-and-budgets.zh.md: d0745d2ac7aacea0f61ea6b699fb86fa39326881
2026-07-04-doc-tiers-and-budgets.md: 378da8f8fddafa32dc7450bfac1c5376f2c7a065
2026-07-04-doc-tiers-and-budgets.zh.md: 1d92ed7fbbec8a9a15bf94a2d320ee88f65a9fa8
@@ -15,7 +15,7 @@ Standing docs accumulated repeated rules, retold incidents, duplicated package m
- **One product onboarding path.** The root README owns the recommended package-run path, the source-run alternative, and compact `dsh plugin --profile` usage. The published user guide starts with tasks inside the running Web UI, then links to distinct tutorials or reference owners for other interfaces, plugin development, and advanced configuration instead of repeating Web startup.
- **A narrow, hard budget gate.** [scripts/verify-doc-budgets.ts](../../../../scripts/verify-doc-budgets.ts) joins `doc-sync`: every doc listed in [scripts/doc-budgets.manifest.json](../../../../scripts/doc-budgets.manifest.json) must stay under its word ceiling (`wc -w` semantics, whole file), and a budgeted file that is missing fails the gate so a rename cannot silently orphan its budget. Scope is deliberately only the accretion-prone standing docs — the root and subtree `AGENTS.md` files, `architecture.md`, `packages/README.md`, and the standing policy docs they evict content into (`docs/testing.md`, `docs/defensive-patterns.md`). Reference docs, Agent Notes, and package READMEs are unbudgeted: length is legitimate there when every row is a fact, and review plus the slop checklist govern them.
- **Ceilings are an enforcement frontier that ratchets.** A doc at or below its target keeps at least 5% headroom as its ceiling ratchets down; a doc above target keeps a frozen ceiling that prevents growth until it reaches the target (root `AGENTS.md` ≤ 1,600 words; `architecture.md` ≤ 1,800; subtree `AGENTS.md` ≤ 600 except `packages/AGENTS.md` ≤ 650 and `docs/AGENTS.md` ≤ 1,250; `packages/README.md` ≤ 600). When the gate goes red, relocate or condense; raise a ceiling only with explicit PR justification.
- **A thin workflow skill, contracts in docs.** [.agents/skills/dsh-doc-standards](../../../skills/dsh-doc-standards/SKILL.md) carries the placement/audit/red-gate workflow and defers to the standard as its source of truth, the same split as [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) over the i18n contract.
- **A thin workflow skill, contracts in docs.** [.agents/skills/dsh-doc](../../../skills/dsh-doc/SKILL.md) carries the placement, audit, budget, and website workflow and defers to the standard as its source of truth, the same split as [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) over the i18n contract.
## Alternatives considered
@@ -15,7 +15,7 @@ Status: implemented
- **单一产品入门路径。**根 README 负责推荐的包运行路径、从源码运行的备选路径和简要的 `dsh plugin --profile` 用法。已发布的用户指南从运行中的 Web UI 内部任务开始,再链接到其他界面的独立教程或插件开发与进阶配置的参考文档归属处,而不会重复介绍 Web 启动步骤。
- **范围窄且严格的预算门禁。**[scripts/verify-doc-budgets.ts](../../../../scripts/verify-doc-budgets.ts) 接入 `doc-sync`[scripts/doc-budgets.manifest.json](../../../../scripts/doc-budgets.manifest.json) 列出的每份文档都必须低于其词数上限(采用 `wc -w` 语义,统计整个文件);预算内文件缺失也会使门禁失败,使重命名无法悄然遗落其预算。范围刻意只涵盖容易膨胀的常设文档——根目录和子树中的 `AGENTS.md` 文件、`architecture.md``packages/README.md`,以及它们将内容移入的常设策略文档(`docs/testing.md``docs/defensive-patterns.md`)。参考文档、Agent Note 和包 README 不设预算:只要每一行都是事实,长度在这些位置就是合理的;评审和赘余检查清单负责约束它们。
- **上限是只进不退的执行红线。** 达到或低于目标的文档在上限逐步下调时保留至少 5% 的余量;高于目标的文档则维持冻结的上限,在达到目标之前不得增长(根 `AGENTS.md` ≤ 1,600 词;`architecture.md` ≤ 1,800;子树 `AGENTS.md` ≤ 600,但 `packages/AGENTS.md` ≤ 650、`docs/AGENTS.md` ≤ 1,250`packages/README.md` ≤ 600)。门禁变红时,迁移或压缩内容;只有在 PR(Pull Request)描述中给出明确理由时才提高上限。
- **精简的工作流 skill(技能),约定归文档。**[.agents/skills/dsh-doc-standards](../../../skills/dsh-doc-standards/SKILL.md) 承载文档放置、审计和门禁失败处理工作流,并以文档标准为真源,与 [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 和 i18n 约定之间的分工相同。
- **精简的工作流 skill(技能),约定归文档。**[.agents/skills/dsh-doc](../../../skills/dsh-doc/SKILL.md) 承载文档放置、审计、预算与站点发布工作流,并以文档标准为真源,与 [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 和 i18n 约定之间的分工相同。
## 曾考虑的替代方案
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-22-product-first-root-readme.md
2026-07-22-product-first-root-readme.md: 24c6dc04f24bd758d8955824a17b1d99801ecd16
2026-07-22-product-first-root-readme.zh.md: f5c0d821e916423c92258649c22ceed222c06439
2026-07-22-product-first-root-readme.md: 662ff334536227e7eeac4c8328936bb57e0aecc9
2026-07-22-product-first-root-readme.zh.md: 3295794339d6348219ab8db68bad1077d3e54ee2
@@ -10,13 +10,11 @@ The root README is the repository's product entry point. Its product-first struc
## Decision
The root README preserves its existing structure, order, and wording wherever the underlying fact remains correct. A refresh changes only stale claims and adds material needed to represent shipped surfaces; it does not use repository growth as a reason to reframe the whole page.
The root README is a compact product and contributor entry point. It states the product identity and plugin architecture, links the documentation site, marks the developer-preview and safety status, and then gives the supported npm and source launch paths.
A note before installation thanks internal testers, states that features and experience remain unfinished, and asks for direct reports of failures, confusion, and friction through the WeCom group. The existing development-stage statement identifies DeepSeek Harness as being in internal testing.
Both launch paths start the Web UI through the `dsh` profile entry point. The source path builds the checkout before it runs `pnpm dsh web`. Detailed ACP, TUI, SDK, capability, and package guidance stays in the user guide, architecture documentation, and package map instead of being repeated on the landing page.
The user-surface section adds the ACP automation server and Python/JSON-RPC SDK beside the existing Web, TUI, and headless entries. The installed TUI remains the single `dsh` command; the Web instructions build the active checkout before running `dsh web`, and custom or reused checkout paths stay explicit. These launch paths must remain executable through a real PTY and a production build/HTTP smoke, respectively. The capability paragraph keeps its compact inventory style while adding the shipped PTY, LSP, web, goal, planning, task, sandbox, approval, settings, credentials, session-query, and telemetry families and stating that compositions select subsets. One adjacent bullet records the authoritative-session-log rule because persistence, replay, queries, telemetry, and interfaces depend on it.
Detailed package and service inventories remain at their owning documentation. The English and Chinese README sides share the same technical structure, while their community sections continue to point to the primary channel for each language audience. The documentation website keeps a separate [quick-start entry route](../../../../docs/user/index.md) instead of presenting another product landing page.
The remaining sections link community support, contribution guidance, development documentation, agent instructions, the license, and third-party notices. The English and Chinese README sides keep the same technical structure while their community links serve their language audiences. The documentation website keeps a separate [quick-start entry route](../../../../docs/user/index.md).
## Alternatives considered
@@ -10,13 +10,11 @@ Status: implemented
## 决策
只要背后的事实仍然正确,根 README 就保留既有结构、顺序和措辞。刷新时只修正陈旧声明,并补充呈现已交付内容所需的信息;不会因为仓库规模增长就重构整篇叙事
根 README 是简短的产品和贡献者入口。它说明产品定位与插件架构,链接文档站,标明开发者预览与安全状态,然后给出受支持的 npm 和源码启动路径
安装说明之前的一则文字感谢内测用户,说明功能和体验仍待完善,并邀请大家通过企业微信群直接反馈失败、困惑和不顺手之处。既有的开发阶段声明明确说明 DeepSeek Harness 处于内测阶段
两条启动路径都通过 `dsh` profile 入口启动 Web UI。源码路径先构建当前检出,再运行 `pnpm dsh web`。ACP、TUI、SDK、能力和包的详细说明由用户指南、架构文档与包索引维护,不在入口页重复
用户入口章节在已有的 Web、TUI 和 Headless 入口旁补充 ACPAgent Client Protocol)自动化服务器和 Python/JSON-RPC SDK。安装后的 TUI 仍只需执行一条 `dsh` 命令;Web 说明要求先构建当前检出,再运行 `dsh web`,并明确处理自定义或复用的检出路径。这两条启动路径必须分别能在真实 PTY 与生产构建/HTTP 冒烟中原样执行。能力段落沿用简洁清单的写法,补充已经交付的 PTY、LSP、Web、目标、规划、任务、沙箱、审批、设置、凭据、会话查询和遥测等能力类别,并说明不同组合只选用其中一部分。相邻的一条列表项说明权威会话日志规则,因为持久化、回放、查询、遥测和各类接口都依赖它
包与服务的完整清单仍由各自的归属文档维护。中英文 README 采用相同的技术结构,但社区章节仍分别指向各自语言受众的主要交流渠道。文档网站保留独立的[快速开始入口路由](../../../../docs/user/index.zh.md),不另行呈现产品首页。
其余章节链接社区支持、贡献指南、开发文档、agent 指令、许可证与第三方声明。中英文 README 保持相同技术结构,社区链接分别服务各自语言受众。文档网站保留独立的[快速开始入口路由](../../../../docs/user/index.zh.md)
## 考虑过的替代方案
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-explicit-change-scope-report.md
2026-07-27-explicit-change-scope-report.md: ed09ffc44252e1e571d50537b3471cad8d68d8f9
2026-07-27-explicit-change-scope-report.zh.md: 783f89b9b42f1706da218997a06bfe7c28a0936b
2026-07-27-explicit-change-scope-report.md: 51f79039f23408f4463d08c08774089a96ce15ab
2026-07-27-explicit-change-scope-report.zh.md: 4ea99f15c05123ae774b5bc758abe5b79b54c4c1
@@ -6,7 +6,7 @@ English | [中文](2026-07-27-explicit-change-scope-report.zh.md)
## Problem
The [pre-push workflow](../../../skills/dsh-pre-push-checks/SKILL.md) needs the diff against the actual base, but constructing `origin/<current-branch>` fails for a new worktree branch that tracks `origin/master` before its first push and misstates a stacked branch whose PR targets another feature branch. The [code-review](../../../skills/dsh-code-review/SKILL.md) and [documentation-audit](../../../skills/dsh-doc-standards/SKILL.md) workflows need the same current-base judgment.
The [pre-push workflow](../../../skills/dsh-pre-push-checks/SKILL.md) needs the diff against the actual base, but constructing `origin/<current-branch>` fails for a new worktree branch that tracks `origin/master` before its first push and misstates a stacked branch whose PR targets another feature branch. The [code-review](../../../skills/dsh-code-review/SKILL.md) and [documentation-audit](../../../skills/dsh-doc/SKILL.md) workflows need the same current-base judgment.
An incorrect range undermines evidence selection because it can omit affected paths. A three-dot committed diff also says nothing about Git's separate staged, unstaged, and untracked layers.
@@ -6,7 +6,7 @@ Status: implemented
## 问题
[pre-push 工作流](../../../skills/dsh-pre-push-checks/SKILL.md)需要取得相对于实际基准的 diff,但按 `origin/<current-branch>` 构造引用存在两类问题:对于第一次推送前跟踪 `origin/master`、尚无同名远端分支的新 worktree 分支,该引用无法解析;对于 PR(Pull Request)以另一功能分支为基准的堆叠分支,该引用会错误描述基准。[代码评审](../../../skills/dsh-code-review/SKILL.md)与[文档审计](../../../skills/dsh-doc-standards/SKILL.md)工作流同样需要判断当前基准。
[pre-push 工作流](../../../skills/dsh-pre-push-checks/SKILL.md)需要取得相对于实际基准的 diff,但按 `origin/<current-branch>` 构造引用存在两类问题:对于第一次推送前跟踪 `origin/master`、尚无同名远端分支的新 worktree 分支,该引用无法解析;对于 PR(Pull Request)以另一功能分支为基准的堆叠分支,该引用会错误描述基准。[代码评审](../../../skills/dsh-code-review/SKILL.md)与[文档审计](../../../skills/dsh-doc/SKILL.md)工作流同样需要判断当前基准。
错误的范围可能遗漏受影响的路径,从而削弱证据选择。三点范围产生的已提交 diff 也完全无法说明 Git 中彼此独立的已暂存、未暂存与未跟踪层。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md
2026-07-31-coverage-exempt-heavy-suites.md: 1f468a69321b451593a9279cfebc1b457fb08a47
2026-07-31-coverage-exempt-heavy-suites.zh.md: 7e519f44c8321b6b99c04c6af56c4cfa5b641663
2026-07-31-coverage-exempt-heavy-suites.md: fe33308cfcbd709a563e697a5e62585be58f214d
2026-07-31-coverage-exempt-heavy-suites.zh.md: 233fdc403fe734b21d116240b8067eaef119cd45
@@ -21,6 +21,8 @@ Linux coverage CI and native Windows CI use [in-job partitioned coverage](2026-0
`scripts/coverage-exempt.ts` is the single roster point, holding the membership contract and the filter/exclude pairs so the two sides cannot drift.
The roster also contains the packed-image loadability suite. That suite reads built workspace artifacts while the packer and Web Worker runtime sources it imports are threshold-excluded. Native Windows makes the uninstrumented gate wait for `build`, so the suite cannot observe a partially emitted dependency closure.
### The roster, reconciled entry by entry
A suite contributes to coverage exactly when it executes measured files in-process (`coverage.include` spans the package src trees). The current roster, audited:
@@ -30,6 +32,8 @@ A suite contributes to coverage exactly when it executes measured files in-proce
| All 6 typert generator specs | The generator's own src | Generator src is threshold-excluded as a package (`vitest.config.ts`) — outside the threshold scope to begin with |
| tools-catalog.spec additionally imports | `typert-registry` and `tool-cordis` src | Each package's own tests cover them fully (verified with focused coverage runs, zero threshold errors) |
| `scripts/install-lefthook.spec.ts`, `scripts/oxlint-contract.spec.ts`, `scripts/change-scope.spec.ts`, `scripts/translation-pairing-merge.spec.ts` | None — they test `scripts/` sources (never in `coverage.include`) and work by spawning child processes | Nothing to carry |
| `packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts` | None — it spawns a child process that transforms and imports every built bundle (Node's ESM loader is the oracle) | webworker-runtime src is threshold-excluded as a package (`vitest.config.ts`) — outside the threshold scope to begin with |
| `packages/experimental/webworker-packer/tests/image-loadable.spec.ts` | Packer and Web Worker runtime src, both threshold-excluded in `vitest.config.ts` | The suite is correctness evidence over built artifacts; native Windows runs it after build in the uninstrumented gate |
### Membership contract
@@ -58,6 +62,7 @@ Measured on CI (16-core runner): the gate segment went from 424 seconds to the t
## Consequences
- The exempt suites execute without adding instrumentation cost to the thresholded gate; partitioned wall-clock measurements belong to the [in-job partitioning decision](2026-08-18-in-job-partitioned-coverage.md).
- Native Windows makes the exempt gate wait for build, so the packed-image suite reads a complete workspace artifact tree.
- `DSH_GATE_CONCURRENCY` has two schedulable gates in this lane again, so the aggregate scheduler is no longer a pass-through.
- Adding a heavy suite to the roster requires the membership audit above; a wrong entry fails the instrumented gate loudly rather than eroding coverage silently.
- The exempt suites no longer appear in the coverage report's file list of contributors; their correctness signal lives solely in the uninstrumented gate's pass/fail.
@@ -21,6 +21,8 @@ Linux 覆盖率 CI 与原生 Windows CI 在插桩门禁内部使用 [job 内分
`scripts/coverage-exempt.ts` 是唯一名单点,集中持有成员资格约定与 filter/exclude 配对,防止两侧漂移。
该名单还包含构建镜像可加载性套件。这个套件读取工作区构建产物,而它导入的 packer 与 Web Worker runtime 源码已排除在阈值外。原生 Windows 会让无插桩门禁等待 `build`,因此该套件不会观察到只完成部分输出的依赖闭包。
### 豁免名单与逐项对账
一个套件对覆盖率有贡献,当且仅当它在进程内执行了被度量的文件(`coverage.include` = 包 src 树)。现行名单逐项核对:
@@ -30,6 +32,8 @@ Linux 覆盖率 CI 与原生 Windows CI 在插桩门禁内部使用 [job 内分
| typert generator 全部 6 个 spec | generator 自身 src | generator src 已整包 threshold-excluded`vitest.config.ts`),本不在阈值口径内 |
| 其中 tools-catalog.spec 额外 import | `typert-registry``tool-cordis` 的 src | 两包各自的测试独立满覆盖(focused coverage 实测无阈值错误) |
| `scripts/install-lefthook.spec.ts``scripts/oxlint-contract.spec.ts``scripts/change-scope.spec.ts``scripts/translation-pairing-merge.spec.ts` | 无——被测对象是 `scripts/` 源码(从不在 coverage.include),执行方式是 spawn 子进程 | 无需接 |
| `packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts` | 无——spawn 子进程对全部已构建 bundle 做 transform 并 importoracle 是 Node ESM loader | webworker-runtime src 已整包 threshold-excluded`vitest.config.ts`),本不在阈值口径内 |
| `packages/experimental/webworker-packer/tests/image-loadable.spec.ts` | packer 与 Web Worker runtime 源码,两者都在 `vitest.config.ts` 中排除阈值 | 该套件为构建产物提供正确性证据;原生 Windows 在构建后通过无插桩门禁运行它 |
### 成员资格约定
@@ -58,6 +62,7 @@ CI 实测(16 核 runner):拆分前 gate 段 424 秒,拆分后两 gate
## Consequences
- 豁免套件在执行时不会向阈值门禁叠加插桩开销;分区墙钟数据由 [job 内分区决策](2026-08-18-in-job-partitioned-coverage.zh.md)负责记录。
- 原生 Windows 让豁免门禁等待构建,因此构建镜像套件会读取完整的工作区产物树。
- `DSH_GATE_CONCURRENCY` 在本 lane 重新拥有两个可调度对象,聚合调度器不再是直通。
- 向名单新增重型套件必须完成上述成员资格对账;错误条目会让插桩 gate 大声失败,而不是静默侵蚀覆盖率。
- 豁免套件不再出现在覆盖率报告的贡献文件列表中;其正确性信号完全由无插桩 gate 的红绿承载。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
2026-08-08-native-windows-pull-request-ci.md: dad23f1a49393fbfc3c0798407ed9abe21ff3df2
2026-08-08-native-windows-pull-request-ci.zh.md: 0d81b160fd40ae0a26569351bca6e631aab9a8de
2026-08-08-native-windows-pull-request-ci.md: 98f48029a86a8b07e53cd4498b27d637508e450b
2026-08-08-native-windows-pull-request-ci.zh.md: b2dba91e4a88d0da637521aae2de937672b869a3
@@ -18,7 +18,7 @@ Every pull request also starts an ordinary independent `windows-native` job name
The native job is deliberately absent from `all-checks-passed.needs` and does not use `continue-on-error`: the aggregate neither waits for it nor changes conclusion because of it, while the job retains its own unmasked result. Workspace build, production-site, and 100%-per-file coverage failures make the native job fail. Static, documentation, package, built-artifact, lint, and snapshot inventories run in the same job as observational gates: their failures remain visible without changing the native aggregate result because Linux owns their blocking verdict.
The 16-core lane admits four concurrent outer gates. Workspace build and production-site validation start immediately. Instrumented and exempt-heavy coverage both wait for the complete build: the instrumented corpus includes packer assertions over built `lib/` output, while the exempt gate's temporary Oxlint contract probes must not race source compilation. Every observational gate waits for both coverage gates to settle, regardless of outcome, before entering an available slot; its own `needs` edges still require their predecessors to pass. This also keeps later static gates that create temporary contract files from racing either coverage scan. [In-job partitioned coverage](2026-08-18-in-job-partitioned-coverage.md) uses four single-worker shards, while the exempt-heavy gate receives two workers from `DSH_COVERAGE_MAX_WORKERS=6`, for about six active coverage execution units after build. `publint` is capped at eight workers when the observational inventory starts. Every Vitest project uses forked workers because Node 24's CJS lexer fatal reproduced in shared worker threads on Windows and POSIX. Both coverage gates set Vitest's default per-test and polling budgets to 30 seconds because unrelated process, Git, SQLite, watcher, grammar, and static-gate fixtures can exceed 15 seconds only under the complete lane's concurrent Windows instrumentation. The SQLite busy-journal pacing fixture injects two busy results followed by success under the normal busy budget and observes each inter-attempt delay, keeping schema-setup scheduling outside its timing assertion. The script-only translation-pairing merge suite runs in the exempt-heavy gate because it imports only `scripts/` sources and child processes; V8 instrumentation contributes no threshold coverage there but magnifies Git-process latency. Lefthook concurrency fixtures retain their outcomes with 30-second case budgets and a 10-second process-ready probe, while the installer allows five seconds for a preempted lock owner to publish its record after exclusive creation. Directory-picker composition gives its debounced config write an explicit 15-second poll budget; workspace-context composition fixtures use a test-owned signal without an unrelated one-second deadline. These lane-scoped budgets preserve asserted outcomes, while the 120-minute job deadline still bounds a stuck run. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform.
The 16-core lane admits four concurrent outer gates. Workspace build and production-site validation start immediately. Instrumented and exempt-heavy coverage both wait for the complete build: the instrumented corpus includes packer assertions over built `lib/` output, while the exempt gate's temporary Oxlint contract probes must not race source compilation and its packed-image suite must read a complete artifact tree. Every observational gate waits for both coverage gates to settle, regardless of outcome, before entering an available slot; its own `needs` edges still require their predecessors to pass. This also keeps later static gates that create temporary contract files from racing either coverage scan. [In-job partitioned coverage](2026-08-18-in-job-partitioned-coverage.md) uses four single-worker shards, while the exempt-heavy gate receives two workers from `DSH_COVERAGE_MAX_WORKERS=6`, for about six active coverage execution units after build. `publint` is capped at eight workers when the observational inventory starts. Every Vitest project uses forked workers because Node 24's CJS lexer fatal reproduced in shared worker threads on Windows and POSIX. Both coverage gates set Vitest's default per-test and polling budgets to 30 seconds because unrelated process, Git, SQLite, watcher, grammar, and static-gate fixtures can exceed 15 seconds only under the complete lane's concurrent Windows instrumentation. The SQLite busy-journal pacing fixture injects two busy results followed by success under the normal busy budget and observes each inter-attempt delay, keeping schema-setup scheduling outside its timing assertion. The script-only translation-pairing merge suite runs in the exempt-heavy gate because it imports only `scripts/` sources and child processes; V8 instrumentation contributes no threshold coverage there but magnifies Git-process latency. Lefthook concurrency fixtures retain their outcomes with 30-second case budgets and a 10-second process-ready probe, while the installer allows five seconds for a preempted lock owner to publish its record after exclusive creation. Directory-picker composition gives its debounced config write an explicit 15-second poll budget; workspace-context composition fixtures use a test-owned signal without an unrelated one-second deadline. These lane-scoped budgets preserve asserted outcomes, while the 120-minute job deadline still bounds a stuck run. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform.
The 16-core allocation is the measured capacity point for this inventory. Six-worker coverage trials produced complete passes in 6 minutes 27 seconds and 7 minutes 50 seconds, while exact-head trials with four, three, and two concurrent workers inside one instrumented Vitest process exposed unreliable fixtures and worker exits. Separate single-worker child processes retain process isolation. Historical sixteen-shard samples reduced instrumented coverage to 112.66122.01 seconds. Under the current post-build graph, sixteen instrumented shards plus two exempt workers would schedule eighteen coverage execution units on a 16-core runner before any production-site tail or system overhead; four shards plus two exempt workers schedule six. Four deliberately trades some single-job latency for lower process-creation pressure under high self-hosted concurrency. A 32-core comparison reduced aggregate gate time by only 1.47 seconds and still triggered the CJS-lexer fatal inside a fork worker, so additional cores did not provide a reliable wall-clock improvement.
@@ -18,7 +18,7 @@ Status: implemented
原生作业被刻意排除在 `all-checks-passed.needs` 之外,且不使用 `continue-on-error`:聚合流程既不等待它,也不会因它改变结论;该作业则保留自身未被掩盖的结果。工作区构建、生产网站和逐文件 100% 覆盖率检查失败会使原生作业失败。静态检查、文档、包、构建产物、lint 与快照清单在同一作业内作为观测性门禁运行;其失败保持可见,但不会改变原生聚合结果,因为这些检查的阻断性判定由 Linux 负责。
16 核通道最多同时运行 4 道外层门禁。工作区构建与生产网站验证会立即启动。插桩覆盖率与豁免重型覆盖率都等待完整构建:插桩语料包含针对已构建 `lib/` 输出的打包器断言,豁免门禁的临时 Oxlint 约定探针则不得与源码编译竞态。每道观测性门禁只等待两道覆盖率门禁以任意结果结算后再进入可用槽位;各门禁自身的 `needs` 边仍要求前置门禁通过。这也使随后创建临时约定文件的静态门禁不会与任一覆盖率扫描竞态。[job 内分区覆盖率](2026-08-18-in-job-partitioned-coverage.zh.md)使用 4 个单 worker 分片,豁免重型门禁则从 `DSH_COVERAGE_MAX_WORKERS=6` 获得 2 个 worker,因此构建完成后约有 6 个活动覆盖率执行单元。观测性清单启动时,`publint` 最多使用 8 个 worker。每个 Vitest 项目都使用 fork worker,因为 Node 24 的 CJS lexer 致命故障可在 Windows 与 POSIX 的共享 worker 中复现。两项覆盖率门禁都将 Vitest 默认的单测试和轮询时间预算设为 30 秒,因为在完整通道并发的 Windows 插桩下,多个互不相关的进程、Git、SQLite、watcher、语法和静态门禁 fixture(测试前置数据)可能超过 15 秒。SQLite busy-journal 节奏 fixture 会在普通 busy 预算内先注入两次 busy 结果,再返回成功,并观察每次尝试之间的延迟,使 schema 设置的调度时间不进入该断言。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。这些只属于该通道的预算保留了原有断言结果,120 分钟的 job 截止时间仍会约束卡死的运行。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
16 核通道最多同时运行 4 道外层门禁。工作区构建与生产网站验证会立即启动。插桩覆盖率与豁免重型覆盖率都等待完整构建:插桩语料包含针对已构建 `lib/` 输出的打包器断言,豁免门禁的临时 Oxlint 约定探针则不得与源码编译竞态,并且其 packed-image 套件必须读取完整的产物树。每道观测性门禁只等待两道覆盖率门禁以任意结果结算后再进入可用槽位;各门禁自身的 `needs` 边仍要求前置门禁通过。这也使随后创建临时约定文件的静态门禁不会与任一覆盖率扫描竞态。[job 内分区覆盖率](2026-08-18-in-job-partitioned-coverage.zh.md)使用 4 个单 worker 分片,豁免重型门禁则从 `DSH_COVERAGE_MAX_WORKERS=6` 获得 2 个 worker,因此构建完成后约有 6 个活动覆盖率执行单元。观测性清单启动时,`publint` 最多使用 8 个 worker。每个 Vitest 项目都使用 fork worker,因为 Node 24 的 CJS lexer 致命故障可在 Windows 与 POSIX 的共享 worker 中复现。两项覆盖率门禁都将 Vitest 默认的单测试和轮询时间预算设为 30 秒,因为在完整通道并发的 Windows 插桩下,多个互不相关的进程、Git、SQLite、watcher、语法和静态门禁 fixture(测试前置数据)可能超过 15 秒。SQLite busy-journal 节奏 fixture 会在普通 busy 预算内先注入两次 busy 结果,再返回成功,并观察每次尝试之间的延迟,使 schema 设置的调度时间不进入该断言。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。这些只属于该通道的预算保留了原有断言结果,120 分钟的 job 截止时间仍会约束卡死的运行。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
16 核配置是这项清单经实测选定的容量规格。使用 6 个 coverage worker 的试验分别以 6 分 27 秒和 7 分 50 秒跑出完整通过结果,而在单个插桩 Vitest 进程内使用 4 个、3 个和 2 个并发 worker 的分支头精确试验暴露出不稳定的 fixture 与 worker 退出。相互独立的单 worker 子进程保留进程隔离。历史上的 16 分片样本把插桩覆盖率缩短到 112.66–122.01 秒。在当前的构建后拓扑中,16 个插桩分片加 2 个豁免 worker 会在 16 核运行器上调度 18 个覆盖率执行单元,且尚未计入生产网站的尾部工作或系统开销;4 个分片加 2 个豁免 worker 则调度 6 个。4 个分片刻意用部分单 job 延迟换取自托管高并发下更低的进程创建压力。32 核对比仅将聚合门禁时间缩短 1.47 秒,且仍在 fork worker 内触发 CJS lexer 致命故障,因此增加核心数没有带来可靠的墙钟时间改善。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-md-fragment-anchor-gate.md
2026-08-09-md-fragment-anchor-gate.md: a5c4029f5b11f464e09356915d5d2f9134bee616
2026-08-09-md-fragment-anchor-gate.zh.md: 50c941b3e2d43e4991ee3a748b1b50b36611e34d
2026-08-09-md-fragment-anchor-gate.md: de0d02318b63c4892233c8c2ebe8457152def10c
2026-08-09-md-fragment-anchor-gate.zh.md: 54ac751900b508fb0dc6fa0e20f0b50b11e252a9
@@ -14,7 +14,7 @@ English | [中文](2026-08-09-md-fragment-anchor-gate.zh.md)
The slug function differs from `gen-cordis-catalog`'s region-anchor slugger (which drops underscores): the generator's headings are always reachable through its explicit `<a id>` anchors, so the two need not share one rule. Chinese pair sides follow the existing repository convention (`docs/glossary.zh.md`, `docs/cordis-primer.zh.md`): keep the English fragment in the link and place an explicit `<a id>` before the Chinese heading, so both language sides expose identical anchors.
The 15 broken fragments are fixed in the same change: stale slugs retargeted to the current headings, the relocated no-timeout contract now linked at its owning group README, and four zh documents given explicit anchors. `docs/AGENTS.md` and the `dsh-doc-standards` skill no longer prescribe the manual anchor grep for Markdown links; it survives only for anchors cited from TypeScript strings whose output never reaches gate-scanned Markdown (the three scanned references all render into scanned pages, so the gate covers them through the committed output).
The 15 broken fragments are fixed in the same change: stale slugs retargeted to the current headings, the relocated no-timeout contract now linked at its owning group README, and four zh documents given explicit anchors. `docs/AGENTS.md` and the `dsh-doc` skill no longer prescribe the manual anchor grep for Markdown links; it survives only for anchors cited from TypeScript strings whose output never reaches gate-scanned Markdown (the three scanned references all render into scanned pages, so the gate covers them through the committed output).
## Verification
@@ -14,7 +14,7 @@ Status: implemented
slug 函数与 `gen-cordis-catalog` 的区块锚点 slugger 不同(后者丢弃下划线):生成器的标题总能通过其显式 `<a id>` 锚点到达,两者无需共享一条规则。中文侧沿用既有语料惯例(`docs/glossary.zh.md``docs/cordis-primer.zh.md`):链接保留英文 fragment,在中文标题前放置显式 `<a id>`,使两个语言侧暴露相同的锚点。
15 条坏 fragment 在同一变更中修复:陈旧 slug 重定向到当前标题,搬迁的无超时约定改链其属主 group README,四份中文文档补上显式锚点。`docs/AGENTS.md``dsh-doc-standards` skill 不再要求为 Markdown 链接手工 grep 锚点;人工 grep 只对输出从不进入受检 Markdown 的 TypeScript 字符串锚点保留(扫描到的三处全部渲染进受检页面,gate 经由提交的产物覆盖它们)。
15 条坏 fragment 在同一变更中修复:陈旧 slug 重定向到当前标题,搬迁的无超时约定改链其属主 group README,四份中文文档补上显式锚点。`docs/AGENTS.md``dsh-doc` skill 不再要求为 Markdown 链接手工 grep 锚点;人工 grep 只对输出从不进入受检 Markdown 的 TypeScript 字符串锚点保留(三处受扫描引用全部渲染进受检页面,因此 gate 经由提交的产物覆盖它们)。
## 验证
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-25-fail-closed-session-event-vocabulary.md
2026-08-25-fail-closed-session-event-vocabulary.md: 537e9a754f7034067d1da31ba2a1bed5bc70cb7e
2026-08-25-fail-closed-session-event-vocabulary.zh.md: f37bcf34bef3d503aca712d99122e334ff29c258
@@ -0,0 +1,41 @@
# Agent Note: Require known session event types on read
Status: implemented
English | [中文](2026-08-25-fail-closed-session-event-vocabulary.zh.md)
## Problem
A session reader must not silently omit a durable event it does not understand. An unknown event can change later request reconstruction, policy state, recovery, or another plugin-owned projection, so successful JSON parsing is not enough to establish a faithful read. The reader before [issue #1901](https://github.com/deepseek-ai/deepseek-harness/issues/1901) passed unknown event types through while core folds ignored them, allowing a resumed session to lose semantics without a diagnostic.
The first refusal mechanism combined a generated known-event set with an optional per-record `ignorable: true` assertion intended for informational event additions. No production writer used the assertion, and `Session.append()` did not expose a way to set it. Event types added after the mechanism remained required-on-read. The unused field nevertheless expanded the canonical event type, seed validation, persistence formats, SQLite schema, session transport, DeepSeek request extension, generated catalogs, documentation, and tests.
## Decision
Every session event type is required-on-read. After supported legacy records are normalized, `PersistenceCoordinator` compares each event type with `KNOWN_SESSION_EVENT_TYPES`, the generated set of every `SessionEventMap` member declared in this repository. Any unknown type refuses reconstruction with `SessionFormatUnsupportedError`; the diagnostic names the event and sequence, identifies the likely newer writer, and includes the raw artifact path when the backend has one. The guard remains read-side only because rejecting an append after a live event is committed would interrupt durability before the session can report the unsupported log on its next load.
`SessionEvent` has no optional unknown-event skip field. JSONL continues to serialize the same event objects because no production append path emitted that field, and `SESSION_FORMAT_VERSION` remains `0`. The SQLite provider replaces the overloaded `ignorable` column with the schema-18 `is_packed` discriminator: scalar logical events store `0`, packed chunk rows store `1`, and an event name equal to a physical chunk tag remains unambiguous before the coordinator applies the known-type guard.
`SESSION_FORMAT_VERSION` remains one monotonic integer. A writer bumps it when an older runtime cannot interpret a structural or semantic change with full correctness: session header fields, event envelope fields, core event semantics, or the `SurfaceEventType`/`SurfaceOp` mechanism. Adding an event type alone does not require a bump because an older reader refuses that exact unknown type instead of misreading the log. Equal versions read normally; unequal versions currently refuse with a directional diagnostic. The n→n+1 upgrader chain remains deferred until a real v0→v1 step provides an input and output to test. A future view upgrade belongs in memory, with durable replacement only when the user continues the session; a missing step leaves the source artifact available for raw viewing.
Repository-external `SessionEventMap` members remain outside the generated set. They can run and persist during the live process, but a first-party persistence reader refuses them on reload until a real external-event consumer justifies a registration mechanism. This preserves the existing loud pre-release limitation without a composition-dependent known set.
## Alternatives considered
**Keep the per-record skip assertion.** Rejected because it has no production producer, is not expressible through `Session.append()`, and requires every storage and transport representation to preserve a speculative choice. A real need should first define which event type is safe to omit, then make the append implementation emit that classification consistently instead of relying on each call site.
**Ignore every unknown event.** Rejected because a reader cannot infer that an unknown durable fact is informational. Silent omission can resume a session with incorrect model input or plugin state.
**Bump the session format for every new event type.** Rejected because the generated type guard already makes older readers fail safely at the exact unsupported record, while newer readers continue to accept older logs. The format integer remains reserved for changes that alter how known records must be interpreted.
**Register known event names from mounted plugins.** Rejected without a current external consumer because the same build would accept or reject one stored log according to runtime composition. A future registration design must distinguish required plugin state from genuinely optional records and preserve that distinction on disk.
**Use major/minor versions or rewrite on view.** Rejected because upgrade availability is a property of each version step, not a promise encoded by two counters, and opening a session must not destructively rewrite its only artifact. A converter defect must not turn browsing into data loss or make an older runtime lose access merely because a newer one viewed the log.
## Consequences
An older build cannot resume a newer same-version log once that log contains any event type it does not know, even when the new event is informational. This is a deliberate loss of unused forward-degradation behavior in exchange for one event envelope and one failure rule. If a real producer later requires older readers to continue around an optional event, the design must classify the event type once, make `Session.append()` emit the persisted classification automatically, and cover both persistence backends and the wire representation.
First-party JSONL session bytes remain unchanged, including packed rows and `SESSION_FORMAT_VERSION = 0`. Existing first-party JSONL sessions remain readable. SQLite is opt-in and follows the pre-release schema policy: schema 18 has no migration from schema 17, and incompatible databases refuse rather than being rewritten. The [SQLite physical compression decision](../architecture/2026-08-18-sqlite-physical-chunk-row-compression.md) owns that backend's packed-row representation.
The assembled headless refusal test proves that a user sees the unknown type, sequence, newer-writer direction, and raw JSONL path. Core seed tests reject fields outside the current event envelope; persistence contract tests reject every unknown type; SQLite codec and differential tests cover scalar and packed discrimination, suffix reads, repair, and cross-backend logical equality. The generated persistence catalog and known-event module keep the reader's set synchronized with repository-owned declarations.
@@ -0,0 +1,41 @@
# Agent Note: 读取时要求会话事件类型已知
Status: implemented
[English](2026-08-25-fail-closed-session-event-vocabulary.md) | 中文
## 问题
会话读取器不得静默省略自己无法理解的持久事件。未知事件可能改变后续请求重建、策略状态、恢复或其他插件所有的投影,因此 JSON 解析成功不足以证明读取保真。[问题 #1901](https://github.com/deepseek-ai/deepseek-harness/issues/1901) 之前的读取器会放行未知事件类型,而核心折叠会忽略它们,使恢复的会话可能在没有诊断的情况下丢失语义。
最初的拒绝机制将生成的已知事件集合与可选的逐记录 `ignorable: true` 声明结合,该声明原本用于信息性新增事件。没有任何生产写入方使用该声明,`Session.append()` 也没有暴露设置方式。机制落地后新增的事件类型仍然都是读取必需项。但这个未使用字段仍然扩大了权威事件类型、seed 校验、持久化格式、SQLite schema、会话传输、DeepSeek 请求扩展、生成目录、文档与测试。
## 决策
每个会话事件类型都是读取必需项。受支持的 legacy 记录归一化后,`PersistenceCoordinator` 会将每个事件类型与 `KNOWN_SESSION_EVENT_TYPES` 比较;后者是从本仓库声明的所有 `SessionEventMap` 成员生成的集合。任何未知类型都以 `SessionFormatUnsupportedError` 拒绝重建;诊断会列出事件与序号,指明日志可能由更新的写入方生成,并在后端拥有独立原始产物时附上该路径。该守卫仍只在读取侧生效,因为在实时事件已提交后拒绝追加会中断持久化,使会话无法在下次加载时报告不受支持的日志。
`SessionEvent` 没有可选的未知事件跳过字段。JSONL 继续序列化相同的事件对象,因为生产追加路径从未发出该字段,`SESSION_FORMAT_VERSION` 仍为 `0`。SQLite 提供方将被复用的 `ignorable` 列替换为 schema 18 的 `is_packed` 判别值:标量逻辑事件存储 `0`,打包分片行存储 `1`,与物理分片标签同名的事件在协调器应用已知类型守卫之前仍可明确解码。
`SESSION_FORMAT_VERSION` 仍是单个单调整数。当较旧运行时无法完全正确地解释某项结构或语义变更时,写入方必须升版本:会话 header 字段、事件 envelope 字段、核心事件语义或 `SurfaceEventType`/`SurfaceOp` 机制。仅新增事件类型无需升版本,因为较旧读取器会拒绝该确切的未知类型,而不是误读日志。版本相等时正常读取;版本不等时当前以分方向诊断拒绝。n→n+1 升级器链仍推迟到第一个真实 v0→v1 步骤提供可测的输入和输出时建立。未来的查看升级属于内存转换,只有用户继续会话时才持久替换;缺失的步骤会保留源产物以供原始查看。
仓库外的 `SessionEventMap` 成员仍不在生成集合内。它们可在实时进程中运行并持久化,但第一方持久化读取器在重新加载时会拒绝它们,直到真实的外部事件消费方证明需要注册机制。这保留了现有的预发布显式限制,同时避免已知集合依赖运行时组合。
## 考虑过的替代方案
**保留逐记录跳过声明。**不予采用,因为它没有生产使用方,无法通过 `Session.append()` 表达,并且要求每种存储与传输表示都保留一项推测性选择。真实需求应先定义可安全省略的事件类型,再让追加实现统一发出该分类,而不是依赖每个调用点。
**忽略每个未知事件。**不予采用,因为读取器无法推断一项未知持久事实是否仅用于信息。静默省略可能使会话以错误的模型输入或插件状态恢复。
**为每个新事件类型升级会话格式。**不予采用,因为生成的类型守卫已使较旧读取器在确切的不受支持记录处安全失败,而较新读取器仍可接受较旧日志。格式整数仍保留给会改变已知记录解读方式的变更。
**从已挂载插件注册已知事件名称。**在没有当前外部消费方时不予采用,因为同一构建会根据运行时组合接受或拒绝同一份存储日志。未来的注册设计必须区分必需插件状态与真正可选的记录,并将该区分持久保存。
**使用主版本/次版本或在查看时改写。**不予采用,因为升级可用性是每个版本步骤的属性,不是两个计数器编码的承诺;打开会话也不得破坏性地改写其唯一产物。转换器缺陷不得让浏览变成数据丢失,也不得仅因较新运行时查看过日志就使较旧运行时失去访问权。
## 后果
较旧构建在较新的同版本日志包含任何未知事件类型后都无法恢复该日志,即使新事件仅用于信息。这是对未使用的前向降级行为的有意放弃,换取单一事件 envelope 与单一失败规则。如果真实生产方以后需要较旧读取器跳过可选事件并继续会话,设计必须只对事件类型分类一次,让 `Session.append()` 自动发出持久分类,并覆盖两个持久化后端和线上表示。
第一方 JSONL 会话字节保持不变,包括打包行与 `SESSION_FORMAT_VERSION = 0`。现有第一方 JSONL 会话仍可读。SQLite 是可选功能,并遵循预发布 schema 策略:schema 18 不从 schema 17 迁移,不兼容数据库会被拒绝而不是改写。[SQLite 物理压缩决策](../architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md)拥有该后端的打包行表示。
组装后的 headless 拒绝测试证明用户会看到未知类型、序号、更新写入方方向与原始 JSONL 路径。核心 seed 测试拒绝当前事件 envelope 以外的字段;持久化约定测试拒绝每个未知类型;SQLite codec 与差分测试覆盖标量与打包判别、后缀读取、修复与跨后端逻辑相等。生成的持久化目录与已知事件模块使读取器集合与仓库所有的声明保持同步。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md
2026-07-28-storage-root-and-derived-medium-recovery.md: cfed831be7eb0fceb5ef7d9778803c602e809179
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: e535cf703e55b03ccd9a767e03aa73f621b17466
2026-07-28-storage-root-and-derived-medium-recovery.md: 1505be1c58d5cf829327b2919113bb2e42798ce7
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 1bab5ab663df1419cc826c3d6acb59bd8bff7de0
@@ -10,7 +10,7 @@ The persisted projection cache ([note](2026-07-27-session-projection-and-command
**Where the files actually live (root mismatch closed; resolve-once residual still open).** The shared base defaults the session store to the global harness home (`$DSH_HOME/sessions`, default `~/.dsh/sessions`), while the shipped Web overlay used to give the json backend the relative root `./.storages`: `workspace.json` and `session_projcache.json` landed under `<launch dir>/.storages/` — two launches from different directories shared their sessions yet saw different workspace registries and different projection caches, and the cache exists precisely to serve the cross-session cold listing, which missed for every session last cached under another launch directory. That mismatch is now closed: the overlay anchors `storage-json.root` to `$DSH_HOME/storages` with the same `!!js` expression the session root uses (`apps/cli/config/web.cordis.yml`). The residual hazard: `JsonStorageBackend` still never resolves its root — each unit open joins the path against whatever `process.cwd()` is at that moment (packages/storage/storage-json/src/index.ts); the shipped overlay root is already absolute and unaffected, but any relative root (bare Loader boots, tests) still splits on a later cwd change — the exact hazard the JSONL session backend resolves-once to prevent ("later process.cwd() changes cannot split one backend across roots", packages/session/session-persistence-jsonl/src/index.ts).
**Recovery behavior.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which describes an aspiration, not the implementation. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change.
**Recovery behavior.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which describes an aspiration, not the implementation. Partially superseded for the projection cache: [the per-session cache files note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.md) removed the global `session_projcache` domain, so the cache half of this proposal (recovery on that domain) no longer applies; the `workspace.json` half remains current. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change.
## Proposal
@@ -10,7 +10,7 @@ Status: proposed
**文件到底存在哪(根错位已收口,resolve-once 残余仍开放)。** 共享 base 将会话存储默认为全局 harness home`$DSH_HOME/sessions`,默认 `~/.dsh/sessions`),而出厂 Web overlay 曾给 json 后端相对根 `./.storages``workspace.json``session_projcache.json` 落在 `<启动目录>/.storages/` 下——从两个不同目录启动,会话相同,工作区注册表和投影缓存却各是一份,而缓存存在的意义恰恰是跨会话冷列表,凡上次在别的启动目录下缓存过的会话全部 miss。这一错位已消除:overlay 现以与会话根同一段 `!!js` 表达式把 `storage-json.root` 锚定到 `$DSH_HOME/storages``apps/cli/config/web.cordis.yml`)。残余隐患:`JsonStorageBackend` 仍从不 resolve 根——每次打开 unit 都把路径 join 到当时的 `process.cwd()` 上(packages/storage/storage-json/src/index.ts);出厂 overlay 的根已是绝对路径不受影响,但任何相对根(裸 Loader 启动、测试)仍会被后续 cwd 变化劈开,JSONL 会话后端用「构造时 resolve 一次」防住的正是它("later process.cwd() changes cannot split one backend across roots"packages/session/session-persistence-jsonl/src/index.ts)。
**恢复行为。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit``malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc"version bumps discard the whole medium")相矛盾——后者描述的是愿望而非实现。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
**恢复行为。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit``malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc"version bumps discard the whole medium")相矛盾——后者描述的是愿望而非实现。投影缓存半边已被[每会话缓存文件 note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.zh.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
## 提案
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-08-20-audience-first-documentation-quality.md
2026-08-20-audience-first-documentation-quality.md: ebf9a30a7096f99b0ffa2f2bc0b61be6a178772a
2026-08-20-audience-first-documentation-quality.zh.md: d7d897c592d0ebfa225074978b8b78d7994900a4
@@ -0,0 +1,138 @@
# Agent Note: Audience-first documentation quality criteria
Status: proposed
English | [中文](2026-08-20-audience-first-documentation-quality.zh.md)
## Problem
The documentation system has strong placement, freshness, linking, bilingual, and source-equivalence checks, but it does not define “brief, intuitive, and friendly” as reviewable outcomes for users, newcomers, developers, and agents. All `doc-sync` checks and translation pairs pass, while the following design problems remain. The first three findings are the design priorities; the capacity finding explains why adding more standing rules will not solve them.
### Semantic correctness can pass without a current owner
The gates prove structure and generated freshness, not that maintained prose still names the live mechanism. The former `dsh-doc-site-sync` skill told authors to reuse a nonexistent `en-docs` sidebar and to add sections to a removed `sectionOrder`; [website/docs.ts](../../../../website/docs.ts) owns `en-guide`, `en-develop`, `en-reference`, and `sections`. The implemented [product-first README decision](../../implemented/process/2026-07-22-product-first-root-readme.md) describes an internal-testing notice and ACP, Python, and JSON-RPC surface sections absent from the [root README](../../../../README.md), although implemented Agent Notes must track shipped facts.
The budget policy has the same split. [docs/AGENTS.md](../../../../docs/AGENTS.md#wordcount-budgets) states a 1,800-word target and 5% headroom for `architecture.md`, but the [budget manifest](../../../../scripts/doc-budgets.manifest.json) allows 2,400 words while the file contains 1,313. The budget gate passes because it checks the manifest ceiling, not the target or ratchet rule. High-impact prose therefore needs a named source or a focused check that consumes the source; a second hand-written copy is not a freshness mechanism.
### Reader success is implicit rather than testable
The standard classifies pages as tutorials or references and asks authors to classify a tutorial reader privately. It does not require a reviewable statement of the readers starting state, desired outcome, shortest successful path, likely failure, or next useful page. A document can therefore satisfy tier placement, links, word limits, and Markdown structure without proving that its intended reader can complete the task.
The public site makes the pressure visible. Each locale publishes 84 pages: 3 guide pages, 17 developer pages, and 63 reference pages. The 13 English files under `docs/user/` contain 7,540 words, while 47 subsystem pages contain 100,759 words. The short Web quick start is a good product entry, but no corpus-level criterion verifies that a first-time user, a plugin newcomer, and a maintainer each has one obvious path from entry to outcome and recovery.
### Generated accuracy and retrieval quality are conflated
The repository contains 19 fully generated English Markdown files with 49,611 words. Forty-four of 47 subsystem pages also contain generated Cordis regions; those regions contribute 34,622 of the subsystem tiers 100,759 words. `config-catalog.md` has 14,807 words, `tool-catalog.md` has 10,599, and the largest mixed subsystem pages contain 5,6007,781 words.
These are legitimate exhaustive references, so a blanket word limit would delete value. Their generators prove completeness and freshness, but the standard has no separate retrieval criterion for an agent with a limited context window or a human looking for one answer. A generated reference needs a compact entry layer, stable grouping, direct anchors, and a split rule based on lookup cost; exhaustive detail can remain exhaustive behind that entry layer.
### The standard has no room for its next rule
The standing documentation file is 1,320 words against a 1,320-word ceiling and a stated 1,250-word target. Root `AGENTS.md` is 1,936 words against a 1,600-word target, `packages/AGENTS.md` is 672 against 650, and `packages/README.md` is 969 against 600. The frozen ceilings prevent further growth but do not create a place for audience and outcome criteria. Adding more standing prose would deepen the problem the standard is meant to prevent.
### Baseline
The audit excludes `vendor/`, frozen `.agents/notes/archived/`, recorded snapshots, and fixtures. It counts 1,042 English Markdown files and 986 Chinese counterparts in the maintained corpus, with 1,106,138 English words. Active Agent Notes account for 580 files and 637,850 words; Markdown under `packages/` accounts for 276 files and 225,630 words; `docs/` accounts for 112 files and 193,456 words. These quantities describe maintenance and retrieval pressure, not defects by themselves.
The systems strongest properties should remain: one fact owner by tier, canonical Markdown projected into the website without copies, complete bilingual pairing, generated catalogs that fail when source changes, type-equivalent declarations, compilable TypeScript examples, checked links and anchors, and package-local model-experience and limitation contracts. The proposal changes quality criteria and entry structure, not those guarantees.
## Proposal
Adopt one audience-first quality contract with five definitions:
- **Brief** means the common path contains only the facts needed for its outcome. Exhaustive contracts remain available through a direct link or generated detail; brevity never means deleting required behavior, failures, ownership, or limitations.
- **Intuitive** means the page establishes its readers starting state, introduces prerequisites before dependent concepts, offers one obvious next action, and uses the product or domain terms a reader will search for.
- **Friendly** means a reader can recognize success, understand material risk before acting, recover from the likely failure, and reach the next relevant depth without first learning unrelated architecture.
- **Accurate** means every durable claim has one owner and a verification path appropriate to its risk. Generated facts derive from source; hand-written workflow values link to or consume their owner instead of copying enums and paths.
- **Agent-readable** means headings, anchors, terminology, ownership, and current-versus-proposed status are explicit enough to retrieve the needed section without loading an entire corpus or reconstructing review history.
### Prototype rules
The [dsh-doc skill](../../../skills/dsh-doc/SKILL.md) owns the first executable version of these rules. The SQLite README pair uses the shipped packed-row implementation as evidence rather than treating its prior prose as authority.
- Every authored package README starts with searchable YAML. A Skill-style `description` and mechanically derived `kind` are required. Four kinds map one-to-one to four skill templates: `package-group` (group map), `package-reference` (plugin or service package), `package-library` (plain module entry), and `package-bundle` (`dsh.bundle.patch`). The counterpart path, hashes, and physical line alignment belong to the merge-safe sidecar and its gate, so README frontmatter contains no `i18n` block. The title or package manifest already owns the name, the document job expresses its audience, and tags remain absent until a governed taxonomy and search consumer proves value beyond full-text search.
- Authored pages start with a three-to-five-sentence `Summary`, then a linked `Table of Contents`. Format-owned Agent Notes, postmortems, generated fragments, and machine files keep their required skeletons.
- Each substantive section starts with a short orientation before subsections, tables, or code, and the page progresses from basic user use to advanced developer and maintainer detail.
- English technical prose uses an ASD-STE100-inspired, non-certified clarity review: explicit actors and actions, stable terms, direct verbs, separated instructions and conditions, and preserved modality, exceptions, timing, and numbers. The 20-word instruction and 25-word description limits are review prompts. Precision overrides them.
- Package contracts remain beside code. Cross-package material moves deliberately toward `docs/learn/overview/`, `docs/learn/cordis/`, `docs/learn/practices/`, `docs/user/`, `docs/developer/`, `docs/developer/discussion/`, `docs/scratch/`, and the parallel `docs/subsystems/` tier.
- English and Chinese pages keep equal authority, matching structure, links, code, frontmatter layout, and exact physical line count.
- Inline pair metadata is the target replacement for sidecars. The prototype may carry both until the verifier, merge driver, recovery flow, generated-region recorder, and archive checks consume a non-self-referential pair digest.
- Repository-root internal links are the target authoring model. The prototype keeps renderer-valid relative links because leading `/` currently leaves the repository on GitHub, bypasses `verify-md-links`, and remains unprojected by the website.
- `Further Exploration` is an optional newcomer route to three to seven adjacent pages.
- Every authored page ends with `Dev Note`, the sole place for active rough context. It remains non-authoritative, links rather than duplicates task state, and is promoted or cleaned when work closes.
- Independently searchable rules, practices, examples, and decisions use small files under descriptive folders when they have distinct owners or change cadence; tightly coupled obligations stay together.
### Criteria by document job
| Job | Primary outcome | Required entry information | Verification |
|---|---|---|---|
| Product quick start | Complete one representative task | Prerequisites, one launch path, first success, safety boundary, next step | Built or packaged smoke for the documented path plus link/site checks |
| User task guide | Complete or recover one user task | Starting UI/API state, ordered actions, observable result, likely failure and recovery | Behavior test, screenshot review when visual state matters, or named manual owner |
| Contributor tutorial | Reach a checked development state | Supported runtime, setup commands, expected result, narrow follow-up commands | Clean-checkout command smoke on a supported environment |
| Architecture overview | Reconstruct the system from one page | Product composition, owners, dependency direction, extension points, links to detail | Source-backed package or graph checks plus focused human review |
| Package or subsystem reference | Look up one contract without reading implementation | Scope, owned types or behavior, failures, lifecycle, limitations, related owners | Existing JSDoc, type-equivalence, generated-region, README, and link checks |
| Generated reference | Locate one exact item and trust its completeness | Scope, generation owner, grouping/index, stable anchors, related conceptual guide | Deterministic `--check`, completeness fixture, site build, and retrieval-size report |
| Agent instruction or skill | Apply one workflow without stale copied values | Scope, authority links, required decisions, exact commands only when owned here | Metadata/link checks and focused tests for copied machine values |
| Proposed or implemented Agent Note | Understand a decision, trade-off, and state | Problem, proposal or decision, alternatives, acceptance or consequences | Existing lifecycle, format, pairing, and supersession checks; review owns semantic currency |
The table belongs in one canonical quality reference. `docs/AGENTS.md` should retain only the short standing orders needed whenever documentation is edited and link to that reference. This creates budget headroom instead of placing another complete standard inside agent context.
### Generated-reference entry and detail layers
Every generated reference should expose a compact entry layer before exhaustive output: scope, intended lookup, grouping or index, direct links to conceptual guidance, and the generator/check command. Generators should report page words, entry count, heading count, and largest section. A page crosses a review threshold when one lookup requires scanning unrelated groups or when one page dominates agent context; the owner then splits it by a stable domain already present in source metadata rather than by an arbitrary word slice.
The first prototype should use one large catalog and one mixed subsystem page. It should compare lookup steps, generated diff size, build time, route stability, and agent context needed for representative questions before any corpus-wide split. Existing anchors need aliases when routes move.
### Enforcement slices
1. Create and validate `dsh-doc`, then rewrite the `session-persistence-sqlite` README pair as a line-aligned, metadata-bearing prototype without changing runtime claims.
2. Review the rendered prototype with newcomer, user, developer, and agent tasks; revise the skill before enforcing the format elsewhere.
3. Add narrow metadata, section-order, line-alignment, link-resolution, and pairing fixtures. Keep sidecars until every merge and recovery consumer has replacement support.
4. Extract accepted standing rules into one canonical quality reference, condense `docs/AGENTS.md` below its target, and organize one coherent `docs/` topic at a time with atomic link/navigation repair.
5. Prototype generated-reference entry/detail separation on `config-catalog.md` and `docs/subsystems/core.md`; apply confirmed patterns elsewhere only after measured lookup cost falls without lost facts or route churn.
This sequence keeps each change independently reviewable. The first three slices improve criteria and correctness without rewriting the corpus; the generated-doc prototype supplies evidence before a broader information-architecture change.
Slices 13 have shipped in this form: `dsh-doc` is the consolidated standard (`dsh-doc-standards` and `dsh-doc-site-sync` are folded into it, and the site workflow carries the corrected sidebar values), the `session-persistence-sqlite` README pair is the reference example, and `pnpm run test:docs` enforces the metadata, pairing, and quick documentation checks. Slices 45 remain open.
### Non-goals
This proposal does not shorten exhaustive facts, merge audience tiers, publish internal decision records, restore an Agent Note index, split tightly coupled rules for file-count symmetry, or treat the audit as user research. It does not delete current pairing or link infrastructure before its replacement passes equivalent recovery and rendering checks.
## Alternatives considered
**Apply one word ceiling to every document.** Rejected because exhaustive reference rows, public contracts, and decision rationale can be long and correct. Entry-path length and lookup cost are the relevant constraints for those jobs.
**Require one universal page template or audience frontmatter.** Rejected because it would add ceremony to generated pages, package references, and short instructions without proving reader success. The standard defines outcomes by document job, uses `kind` only where it selects a concrete package-document standard, and adds only fields that a focused check or reviewer consumes.
**Use readability scores as the quality gate.** Rejected because formulas penalize exact technical terms and cannot detect wrong ownership, missing failure behavior, stale commands, or a broken reader journey.
**Rewrite or split the full corpus immediately.** Rejected because the current system is mechanically healthy and many long references are appropriately exhaustive. A prototype should prove a retrieval improvement before route and translation churn spreads.
**Keep the existing gates and rely on review for friendliness.** Rejected because the stale workflow values and budget-policy mismatch show that review alone does not preserve copied semantic claims, and the current gates do not ask whether a reader can complete a task.
## Acceptance criteria
- One canonical quality reference defines brief, intuitive, friendly, accurate, and agent-readable documentation by document job.
- `.agents/skills/dsh-doc` validates and directly links its metadata, structure/hierarchy, and review/prototype references without duplicating their detailed rules in `SKILL.md`.
- The SQLite README pair demonstrates searchable YAML, Summary, Table of Contents, user-to-developer progression, Further Exploration, final Dev Note, structural parity, and exact line-count equality while preserving verified package contracts.
- `docs/AGENTS.md` links that reference, remains sufficient as standing instruction, and is below its target with at least 5% headroom.
- The root user path, Web quick start, first-plugin tutorial, contributor setup, and architecture overview each name an observable outcome and a verification owner without duplicating implementation detail.
- The budget manifest records both target and temporary ceiling, and its check reports or rejects a violated headroom/ratchet state.
- The docs-site workflow contains no copied invalid sidebar name or section-owner claim, and a focused test prevents recurrence.
- The sidecar remains the single consistency record because it preserves equal authority, last-confirmed-text recovery, automatic merge safety, generated-region recording, and archive sealing without creating owner-file conflicts.
- An accepted repository-root link form renders correctly on GitHub and the documentation site and remains locally target/anchor checked before relative links are migrated.
- One large standalone catalog and one mixed subsystem page demonstrate a compact entry layer and lower measured lookup cost while preserving exhaustive generated truth, stable links, bilingual pairing, and deterministic freshness.
- `pnpm run doc-sync`, `pnpm run lint`, the focused new checks, and `git diff --check` pass.
## Risks
- Metadata can become boilerplate; the package README check therefore permits only fields with current retrieval, template-selection, or bilingual-consistency consumers.
- Hard sentence limits can fragment explanations or separate a condition from its consequence. The controlled-English word counts remain review prompts, and exact contracts override them.
- Exact line alignment can pressure translators into unnatural prose; review must protect meaning and may revise both sides together rather than weaken one.
- Splitting generated references can increase routes and link maintenance; prototypes must preserve aliases and measure the trade-off.
- A semantic check can become a repository-topology scanner that blocks legitimate changes; checks should cover high-risk copied values and representative journeys, while review owns prose meaning.
- Package README quick-reference tables manually repeat selected configuration defaults; until a source-driven check owns them, reviewers must verify changed values against source and the generated config catalog and keep the tables selected rather than exhaustive.
- Optimizing for short agent context can make human references fragmented; each split needs one stable conceptual owner and one obvious navigation path.
- A permanent Dev Note can become a second queue or stale history dump; completion must promote durable truth and remove resolved chatter.
- The audit uses repository structure, gates, and representative pages rather than user research. Before broad rollout, maintainers should validate the proposed reader outcomes with actual newcomer, user, developer, and agent tasks.
@@ -0,0 +1,138 @@
# Agent Note: 以受众为先的文档质量标准
Status: proposed
[English](2026-08-20-audience-first-documentation-quality.md) | 中文
## 问题
文档系统拥有健全的放置、新鲜度、链接、双语和源等价性检查,却没有把「简短、直观、友好」定义成可供用户、新人、开发者和 agent(智能体)评审的结果。全部 `doc-sync`(文档同步门禁)检查和全部翻译配对均通过,但仍存在以下设计问题。前三项发现是设计重点;容量问题则解释了为什么增加更多常驻规则无法解决它们。
### 语义正确性可以在没有现行归属者的情况下通过检查
这些门禁证明结构和生成内容的新鲜度,却不能证明维护中的正文仍指向实际机制。以前的 `dsh-doc-site-sync` 技能曾要求作者复用并不存在的 `en-docs` 侧边栏,还要求把章节加入已经移除的 `sectionOrder`[website/docs.ts](../../../../website/docs.ts)实际拥有 `en-guide``en-develop``en-reference``sections`。已实现的[产品优先 README 决策](../../implemented/process/2026-07-22-product-first-root-readme.zh.md)描述了内部测试说明,以及 ACP、Python 与 JSON-RPC 界面章节,但[根 README](../../../../README.zh.md)并无这些内容;与此同时,已实现 Agent Note 必须跟随已交付事实。
预算策略也存在相同的分裂。[docs/AGENTS.md](../../../../docs/AGENTS.md#wordcount-budgets)为 `architecture.md` 规定 1,800 词目标和 5% 余量,但[预算 manifest(元数据清单)](../../../../scripts/doc-budgets.manifest.json)允许 2,400 词,而该文件实际包含 1,313 词。预算门禁之所以通过,是因为它只检查 manifest 上限,不检查目标或棘轮规则。因此,高影响正文需要一个具名真源或消费真源的聚焦检查;第二份手写副本不是新鲜度机制。
### 读者成功与否是隐含判断,而不是可测试结果
标准把页面分成教程和参考,并要求作者私下判断教程读者的起始水平。标准没有要求留下可供评审的读者起始状态、预期结果、最短成功路径、常见失败或下一篇有用页面。因此,一份文档可以满足层级放置、链接、词数限制和 Markdown 结构,却没有证明目标读者能完成任务。
公共站点直观呈现了这种压力。每种语言发布 84 个页面:3 个指南页面、17 个开发页面和 63 个参考页面。`docs/user/` 下的 13 个英文文件共有 7,540 词,而 47 个子系统页面共有 100,759 词。简短的 Web 快速开始是良好的产品入口,但全语料没有标准来验证首次使用者、插件新人和维护者是否都能沿一条明确路径从入口走到结果与故障恢复。
### 生成内容的准确性与检索质量混为一谈
仓库包含 19 个完全生成的英文 Markdown 文件,共 49,611 词。47 个子系统页面中有 44 个也包含生成的 Cordis 区域;这些区域占子系统层级 100,759 词中的 34,622 词。`config-catalog.md` 有 14,807 词,`tool-catalog.md` 有 10,599 词,最大的混合子系统页面则有 5,600–7,781 词。
这些内容是正当的穷尽式参考,因此统一词数限制反而会删除价值。生成器证明完整性和新鲜度,但标准没有为上下文窗口有限的 agent,或只寻找一个答案的人类读者另设检索标准。生成参考需要紧凑的入口层、稳定分组、直接锚点,以及根据查询成本触发的拆分规则;穷尽式细节可以在该入口层之后继续保持穷尽。
### 标准没有容纳下一条规则的空间
常驻文档标准有 1,320 词,等于 1,320 词上限,并超过声明的 1,250 词目标。根 `AGENTS.md` 有 1,936 词,目标为 1,600`packages/AGENTS.md` 有 672 词,目标为 650`packages/README.md` 有 969 词,目标为 600。冻结的上限能阻止进一步增长,却没有为受众和结果标准创造位置。继续增加常驻正文会加深该标准本应防止的问题。
### 基线
本次审计排除 `vendor/`、冻结的 `.agents/notes/archived/`、录制快照和 fixture(测试前置数据)。受维护语料包含 1,042 个英文 Markdown 文件和 986 个中文对侧文件,共 1,106,138 个英文词。活跃 Agent Note 占 580 个文件和 637,850 词;`packages/` 下的 Markdown 占 276 个文件和 225,630 词;`docs/` 占 112 个文件和 193,456 词。这些数量描述维护和检索压力,本身并不构成缺陷。
系统最强的性质应予保留:按层级为每项事实指定一个归属者;把规范 Markdown 投影到站点而不创建副本;完整双语配对;源变更时快速失败的生成目录;源等价声明;可编译的 TypeScript 示例;受检查的链接与锚点;以及包局部的模型体验与限制约定。提案改变的是质量标准和入口结构,而不是这些保证。
## 提案
采用一套以受众为先的质量约定,并给出五项定义:
- **简短**表示常用路径只包含达成结果所需的事实。穷尽式约定仍可通过直接链接或生成细节访问;简短绝不意味着删除必要行为、失败、所有权或限制。
- **直观**表示页面会确定读者的起始状态,在依赖概念之前介绍前置知识,提供一个明确的下一步操作,并使用读者会搜索的产品或领域术语。
- **友好**表示读者能识别成功,在操作前理解实质风险,从常见失败中恢复,并在无需先学习无关架构的情况下进入下一层相关细节。
- **准确**表示每项持久事实都有一个归属者和与风险相称的验证路径。生成事实来自源;手写工作流值链接或消费其归属者,而不是复制枚举和路径。
- **便于 agent 阅读**表示标题、锚点、术语、所有权以及当前与提议状态足够明确,无需加载整个语料或重建评审历史即可检索所需章节。
### 原型规则
[dsh-doc skill](../../../skills/dsh-doc/SKILL.md) 负责这些规则的首个可执行版本。SQLite README 对以已交付的分片行实现为证据,而不把其旧版正文当作权威。
- 每个撰写型包 README 都以可搜索 YAML 开头。Skill 风格的 `description` 与按机制推导的 `kind` 为必填字段。四种 kind 与四个技能模板一一对应:`package-group`(组地图)、`package-reference`(插件或服务包)、`package-library`(纯模块入口)与 `package-bundle``dsh.bundle.patch`)。对照文件路径、哈希与物理行对齐由支持自动合并的 sidecar 及其门禁负责,因此 README frontmatter 不包含 `i18n` 块。名称已由标题或包 manifest 归属,受众已由文档职责表达;在受治理的标签分类与搜索消费方证明其价值超过全文检索之前,不加入标签。
- 撰写型页面先写三至五句的 `Summary`,再写带链接的 `Table of Contents`。由格式约束的 Agent Note、事故复盘、生成片段和机器文件保留其必需骨架。
- 每个实质章节在子章节、表格或代码之前先给出简短引导,页面则从基础用户用法逐步进入高级开发者与维护者细节。
- 英文技术正文采用受 ASD-STE100 启发但不宣称认证的清晰度评审:明确行动者与动作,稳定使用术语,使用直接动词,拆分指令与条件,并完整保留情态、例外、时序与数值。指令 20 词和描述 25 词的限制仅作评审提示。准确性高于句长。
- 包约定留在代码旁。跨包材料有计划地向 `docs/learn/overview/``docs/learn/cordis/``docs/learn/practices/``docs/user/``docs/developer/``docs/developer/discussion/``docs/scratch/` 和平行的 `docs/subsystems/` 层级迁移。
- 英文和中文页面保持同等权威,并在结构、链接、代码、frontmatter 布局和精确物理行数上一一对应。
- 内联配对元数据是伴随文件的目标替代方案。在验证器、合并驱动、恢复流程、生成区域记录器和归档检查消费非自引用配对摘要之前,原型可以同时保留两者。
- 仓库根级内部链接是目标撰写模型。原型保留渲染器可用的相对链接,因为前导 `/` 当前会在 GitHub 上离开仓库、绕过 `verify-md-links`,并且不会由站点投影。
- `Further Exploration` 是可选的新人路径,链接三至七个相邻页面。
- 每个撰写型页面都以 `Dev Note` 结尾,作为活跃粗略上下文的唯一位置。它保持非权威状态,只链接而不复制任务状态,并在工作结束时完成提升或清理。
- 可独立搜索的规则、实践、示例和决策在拥有不同归属者或变更节奏时,使用描述性目录下的小文件;紧密耦合的义务保留在一起。
### 按文档职责划分的标准
| 职责 | 主要结果 | 必需入口信息 | 验证 |
|---|---|---|---|
| 产品快速开始 | 完成一项有代表性的任务 | 前置条件、一个启动路径、首次成功、安全边界、下一步 | 文档路径的构建或打包冒烟测试,加链接和站点检查 |
| 用户任务指南 | 完成一项用户任务或从中恢复 | 起始 UI/API 状态、有序操作、可观察结果、常见失败及恢复 | 行为测试;涉及视觉状态时评审截图;或指定人工归属者 |
| 贡献者教程 | 进入通过检查的开发状态 | 支持的运行时、设置命令、预期结果、聚焦的后续命令 | 在受支持环境中对干净检出运行命令冒烟测试 |
| 架构概览 | 从一个页面重建系统 | 产品组合、归属者、依赖方向、扩展点、细节链接 | 由源支持的包或图检查,加聚焦人工评审 |
| 包或子系统参考 | 无需阅读实现即可查到一项约定 | 范围、归属的类型或行为、失败、生命周期、限制、相关归属者 | 既有 JSDoc、类型等价、生成区域、README 和链接检查 |
| 生成参考 | 找到一个精确条目并信任其完整性 | 范围、生成归属者、分组或索引、稳定锚点、相关概念指南 | 确定性 `--check`、完整性 fixture、站点构建和检索规模报告 |
| Agent 指令或 skill(技能) | 在没有陈旧复制值的情况下执行一项工作流 | 范围、权威链接、必需决策、仅在此处归属时写入精确命令 | 元数据或链接检查,以及针对复制机器值的聚焦测试 |
| 提议或已实现 Agent Note | 理解决策、取舍与状态 | 问题、提案或决策、备选方案、验收或后果 | 既有生命周期、格式、配对和取代检查;语义时效性由评审负责 |
该表应归属一份规范质量参考。`docs/AGENTS.md` 只保留每次编辑文档都需要的简短常驻规则,并链接到该参考。这样可以创造预算余量,而不是把另一份完整标准放进 agent 上下文。
### 生成参考的入口层与细节层
每份生成参考都应在穷尽式输出之前提供紧凑入口层:范围、预期查询、分组或索引、概念指南的直接链接,以及生成器或检查命令。生成器应报告页面词数、条目数、标题数和最大章节。当一次查询需要扫描无关分组,或单页主导 agent 上下文时,该页面便跨过评审阈值;随后,归属者按照源元数据中已有的稳定领域拆分页面,而不是按任意词数切片。
首个原型应选择一个大型目录和一个混合子系统页面。在进行全语料拆分前,它应比较查询步骤、生成 diff 大小、构建时间、路由稳定性,以及代表性问题所需的 agent 上下文。路由移动时,既有锚点需要保留别名。
### 执行切片
1. 创建并验证 `dsh-doc`,再把 `session-persistence-sqlite` README 对改写为行对齐、带元数据的原型,同时不改变运行时事实。
2. 用新人、用户、开发者和 agent 任务评审渲染后的原型;先修订 skill,再在其他位置强制执行该格式。
3. 添加聚焦的元数据、章节顺序、行对齐、链接解析和配对 fixture。在每个合并与恢复消费方都有替代支持前,保留伴随文件。
4. 把已接受的常驻规则提取到一份规范质量参考,将 `docs/AGENTS.md` 精简到目标以下,并且一次只组织一个内聚的 `docs/` 主题,同时原子地修复链接与导航。
5.`config-catalog.md``docs/subsystems/core.md` 上制作生成参考入口层与细节层分离的原型;只有实测查询成本下降且没有丢失事实或造成路由扰动,才把确认后的模式应用到其他位置。
该顺序使每项变更都能独立评审。前三个切片在不重写语料的情况下改进标准与正确性;生成文档原型则在更广的信息架构变更前提供证据。
切片 13 已按此形式交付:`dsh-doc` 成为合并后的标准(`dsh-doc-standards``dsh-doc-site-sync` 已并入其中,站点工作流携带修正后的侧边栏值),`session-persistence-sqlite` README 对是参考示例,`pnpm run test:docs` 强制执行元数据、配对与快速文档检查。切片 4–5 仍待完成。
### 非目标
本提案不缩减穷尽式事实,不合并受众层级,不发布内部决策记录,不恢复 Agent Note 索引,不为了文件数对称而拆分紧密耦合的规则,也不把此次审计当作用户研究。在替代方案通过等价的恢复与渲染检查前,本提案不删除现有配对或链接基础设施。
## 考虑过的备选方案
**为每份文档设置统一词数上限。**不予采纳,因为穷尽式参考条目、公开约定和决策理由可以既长又正确。对这些文档职责而言,入口路径长度与查询成本才是相关约束。
**强制使用统一页面模板或受众前置元数据。**不予采纳,因为这会给生成页面、包参考和简短指令增加形式,却不能证明读者成功。标准按文档职责定义结果,仅在 `kind` 能选择具体包文档标准时使用它,并且只添加聚焦检查或评审会消费的字段。
**把可读性分数作为质量门禁。**不予采纳,因为公式会惩罚精确技术术语,却无法发现错误所有权、遗漏失败行为、陈旧命令或破损的读者路径。
**立即重写或拆分全部语料。**不予采纳,因为现有系统在机制上健康,许多长参考也确实应保持穷尽。原型应先证明检索有所改善,再扩散路由和翻译扰动。
**保留现有门禁,让评审负责友好程度。**不予采纳,因为陈旧工作流值和预算策略不一致说明,仅凭评审无法保留复制的语义事实,而现有门禁也不询问读者是否能完成任务。
## 验收标准
- 一份规范质量参考按文档职责定义简短、直观、友好、准确和便于 agent 阅读的文档。
- `.agents/skills/dsh-doc` 通过验证,并直接链接其元数据、结构或层级及评审或原型参考,而不在 `SKILL.md` 中复制这些参考的详细规则。
- SQLite README 对展示可搜索 YAML、Summary、Table of Contents、从用户到开发者的渐进结构、Further Exploration、结尾 Dev Note、结构一致性和精确行数相等,同时保留已验证的包约定。
- `docs/AGENTS.md` 链接该参考,仍足以充当常驻指令,并低于其目标且至少保留 5% 余量。
- 根级用户路径、Web 快速开始、第一个插件教程、贡献者设置和架构概览各自给出一个可观察结果与验证归属者,同时不复制实现细节。
- 预算 manifest 同时记录目标与临时上限,其检查会报告或拒绝违反余量或棘轮规则的状态。
- 文档站工作流不再包含复制的无效侧边栏名称或章节归属声明,并有聚焦测试防止复发。
- sidecar 继续作为唯一一致性记录,因为它能保留同等权威、上次确认文本恢复、自动合并安全、生成区域记录和归档封存,同时不会在正文文件中制造冲突。
- 一个已接受的仓库根级链接格式在迁移相对链接前能在 GitHub 与文档站正确渲染,并继续接受本地目标或锚点检查。
- 一个大型独立生成目录页和一个混合子系统页面展示紧凑入口层与更低的实测查询成本,同时保留穷尽式生成事实、稳定链接、双语配对和确定性新鲜度。
- `pnpm run doc-sync``pnpm run lint`、聚焦的新检查和 `git diff --check` 均通过。
## 风险
- 元数据可能沦为样板;因此包 README 检查只允许具有现行检索、模板选择或双语一致性消费方的字段。
- 硬性句长限制可能割裂说明,或把条件与后果分开。受控英语的词数限制仅作评审提示,精确约定优先于句长。
- 精确行对齐可能迫使译者写出不自然的正文;评审必须保护含义,并可同时修订两侧,而不是削弱其中一侧。
- 拆分生成参考可能增加路由与链接维护;原型必须保留别名并衡量取舍。
- 语义检查可能膨胀成阻塞正当变更的仓库拓扑扫描器;检查应覆盖高风险复制值和代表性路径,而正文含义仍由评审负责。
- 包 README 的快速参考表会手工重复部分配置默认值;在源驱动检查接管之前,评审者必须对照源码和生成配置目录验证变更值,并让这些表保持精选而非穷尽。
- 为缩短 agent 上下文而优化可能使人类参考变得碎片化;每次拆分都需要一个稳定概念归属者和一条明确导航路径。
- 永久的 Dev Note 可能变成第二份队列或陈旧历史堆积;完成工作时必须提升持久事实并删除已解决的过程内容。
- 本次审计使用仓库结构、门禁和代表性页面,而不是用户研究。在广泛推广之前,维护者应通过真实的新人、用户、开发者和 agent 任务验证提议的读者结果。
+1
View File
@@ -0,0 +1 @@
*/agents/openai.yaml
-56
View File
@@ -1,56 +0,0 @@
---
name: dsh-doc-standards
description: 'Use when writing, moving, reviewing, or auditing documentation in the deepseek-harness repo — choosing hierarchy and detail, separating tutorials from references, checking tutorial progression, trimming doc slop, responding to a verify-doc-budgets failure, or requests like "improve the docs", "audit the docs", "where should this be documented", or "this doc is too long".'
---
# Applying the DeepSeek Harness Documentation Standard
The documentation rules live in [docs/AGENTS.md](../../../docs/AGENTS.md). This workflow covers placement, corpus audits, budgets, and validation across Markdown, JSDoc, and code comments. It is guidance, not a script; use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) for required coverage and editorial judgment, and never treat length alone as a defect.
## Sources of truth (read, don't re-summarize)
- [docs/AGENTS.md](../../../docs/AGENTS.md) — hierarchy, tutorial/reference forms, taxonomy, budgets, and slop checklist.
- [.agents/notes/README.md](../../notes/README.md) — when a decision earns an Agent Note, how to file it, and what goes inside one (the header block, per-lifecycle skeleton, and Alternatives-considered mandate, gated by `verify-agent-note-format`); [docs/postmortem/README.md](../../../docs/postmortem/README.md) — when an incident earns a postmortem.
- [docs/i18n/README.md](../../../docs/i18n/README.md) — the bilingual pairing rules; editing either side of a pair obligates the counterpart in the same change.
- Root [AGENTS.md](../../../AGENTS.md) — the standing orders whose budget discipline this skill protects.
- [Archived Agent Notes](../../notes/archived/AGENTS.md) — frozen historical snapshots excluded from editorial maintenance and evolving documentation gates.
## Review structure before prose
Apply the standard's authoring order to every human-facing document in scope. Do not apply this structural pass to Agent Notes. Classify a postmortem as a reference scoped to one incident; preserve its required chronological evidence without treating chronology as a teaching sequence.
1. Locate the document in the repository and navigation trees. State its own subject and identify its direct children.
2. Set the permitted level of detail. Keep full detail about the document's subject, summarize direct children by purpose, responsibility, and high-level behavior, and move deeper explanations to their owning descendants with links. Treat test infrastructure as descendant-owned unless it is the document's subject.
3. Classify the document from its intended use, not its path or title. A tutorial must lead through ordered work to an observable outcome; a reference must support lookup within an explicit scope without requiring sequential reading.
4. For a tutorial, privately classify the starting reader and concepts as beginner, intermediate, or advanced. Trace each concept to its prerequisites, reorder premature material, and move optional advanced detail to a later tutorial or reference.
5. Split substantial mixed forms. Put a small secondary form in a clearly labeled section.
Then check constraints that make placement expensive or wrong:
- Paired docs (`pnpm run verify-translation-pairing --list`) cost a zh counterpart update and a `--write` re-record on every edit — prefer an unpaired home for content that will churn. Verbatim code blocks are byte-exact across the pair: copy a corrected fence into both files instead of translating its comments independently.
- Generated catalogs are never hand-edited; if the fact belongs there, change the generator's source.
- Before renaming or moving any doc, grep for inbound references: `verify-md-links` catches Markdown link targets AND `#fragment` anchors onto Markdown files (heading slugs and explicit `<a id>`), and `verify-doc-refs` catches `docs/*.md` citations in TypeScript comments; anchors cited from TypeScript strings still need a manual grep when their output never reaches gate-scanned Markdown.
- A move is atomic: remove from the old home, add to the new home, and fix every inbound link in the same change.
## Audit the corpus
After the structural pass, hunt the standard's slop checklist with the cheapest probes first. Verify and fetch the PR's live base, then run `pnpm --silent run change-scope --base <verified-base-ref>` to identify committed and dirty paths before applying semantic judgment. After a retarget or base merge, rerun the report and audit prose introduced by the new base.
1. Measure: `pnpm run verify-doc-budgets --list`, then `git ls-files '*.md' ':(exclude)vendor/**' | xargs wc -w | sort -rn | head -30` to spot unbudgeted outliers.
2. Hunt reasoning-transcript leakage — narrated history, dead design-session citations, review choreography, control-flow narration, test walkthroughs — with [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md), which defines the taxonomy, recall batteries, and rules for what to keep or delete. Preserve only a non-obvious contract or durable rationale; the same rationale repeated beside sibling methods keeps one home.
3. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links.
4. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference.
5. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps.
6. If removing prose changes a promised behavior rather than its explanation, use a proposed Agent Note first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)).
Exclude `.agents/notes/archived/` from corpus audits and edits. Active prose may repair, redirect, or delete an inbound link, but never follow an archive-wide cleanup into the frozen target.
Keep every load-bearing rule, preferably as one to three lines plus a link to its rationale. Cut stories, duplicates, status notes, and the path used to derive the rule. Do not create a new explanation merely to relocate disposable reasoning.
## When verify-doc-budgets goes red
Apply the ordered relocate-condense-raise policy in [docs/AGENTS.md](../../../docs/AGENTS.md); this skill only supplies the workflow probes above.
## Validation and PR hygiene
Run at least `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`; JSDoc changes may regenerate catalogs. If a paired doc changed, follow the [lightweight routine path](../../../docs/AGENTS.md#writing-rules) and run `pnpm run verify-translation-pairing --write <pair>`. The PR body should give word deltas, explain any deliberately long exception, and list checks.
+128
View File
@@ -0,0 +1,128 @@
---
name: dsh-doc
description: Create, restructure, review, audit, or migrate DeepSeek Harness Markdown documentation, package READMEs, and the documentation website using audience-first hierarchy, kind-mapped YAML metadata, bilingual line alignment, summary/contents navigation, progressive user-to-developer detail, executed-operation fact-checking, and repository validation. Use for new or revised DSH docs, docs-tree organization, documentation-quality audits and budgets, website page publishing, and bilingual documentation structure changes.
---
# DeepSeek Harness documentation
## Summary
The DeepSeek Harness documentation standard: make every page searchable, newcomer-readable, and exact enough for agents and maintainers, and keep the documentation website a tested projection of repository Markdown. Apply repository `AGENTS.md` files and executed gates first, then this workflow for kind-mapped metadata, progressive detail, line-aligned bilingual pages, corpus audits, and website publication. Preserve one owner per fact: source, tests, generated catalogs, package READMEs, guides, Agent Notes, and scratch each keep their own kind of truth. The `session-persistence-sqlite` README pair is the reference example of the format.
## Table of Contents
- [Workflow](#workflow)
- [Fact-check procedure: test, do not assume](#fact-check-procedure-test-do-not-assume)
- [Kind system and templates](#kind-system-and-templates)
- [Voice rules](#voice-rules)
- [Quality criteria](#quality-criteria)
- [Audit the corpus](#audit-the-corpus)
- [Wordcount budgets](#wordcount-budgets)
- [Website publication](#website-publication)
- [Detailed references](#detailed-references)
- [Validation](#validation)
- [Dev Note](#dev-note)
## Workflow
Follow this sequence for each requested scope. Keep the common reader path brief, but do not delete failures, ownership, limitations, or other required contracts merely to reduce words.
1. Read root and more-specific `AGENTS.md`, [the documentation standard](../../../docs/AGENTS.md), the target page, its source/tests, navigation owner, and bilingual record.
2. Classify the page by one primary job and reader: product quick start, user task guide, contributor tutorial, architecture overview, package/subsystem reference, generated reference, agent instruction, decision record, or scratch.
3. Place the page at its nearest owner. Keep package contracts beside package code; use `docs/` for cross-package learning, user, developer, architecture, discussion, and expiring scratch material.
4. Define the reader's starting state, observable outcome, likely failure, recovery path, and next useful depth before writing details.
5. Add or revise YAML metadata — assign the `kind` that maps to the template for this document's job — then write `Summary`, `Table of Contents`, user-facing content, developer-facing content, optional `Further Exploration`, and final `Dev Note` in that order where the document type permits.
6. Update the bilingual counterpart in the same pass. Keep headings, lists, tables, code, links, frontmatter layout, and physical line count aligned.
7. Verify every claim against code, tests, generators, package metadata, or a current decision owner — and run the operations the page instructs, per the fact-check procedure below. Update the owner before any derivative artifact.
8. Run focused checks, then `pnpm run test:docs`, `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`; re-read the complete diff for correctness and then for brevity and repository fit.
## Fact-check procedure: test, do not assume
Documentation states how the product behaves today, and the only admissible evidence for an operation claim is having run it. This procedure is mandatory for every new document and every new paragraph that claims an operation, command, default, error, or platform difference.
1. **Classify the subject before writing install guidance.** Read the facts, never the folder name: `package.json` for a `dsh.bundle.patch` declaration, and the entry file for the plugin shape (`apply` export or a default service export is a plugin; a plain module API is a library). A bundle installs with `dsh plugin --profile <name> add <package>` and is the only package shape for which that command activates a profile layer; a plugin mounts as a `cordis.yml` row; a library is a dependency with no install path of its own. Packages with special status (libraries, bundles) get their own README template — never a plugin README with install guidance that does not apply.
2. **Run every claimed operation against the current checkout.** Execute each CLI command, config snippet, and profile or patch example exactly as the document will show it; write down only what you observed, including the exact output, warnings, and failure modes. If a claim depends on a key or a network you do not have, say so and name the verification owner instead of asserting the behavior.
3. **Delete what you could not reproduce.** Never carry a command, field, default value, or behavior from memory, analogy, or a neighboring package's README. When a claim fails to reproduce, fix the claim — not the test.
4. **Check old docs against latest master.** Before revising pre-existing pages, `git fetch origin` and compare the section against `origin/master`; the pairing sidecar recovers the last-confirmed text of either side. A stale statement on master is still wrong: correct it against the code, not against the old prose.
5. **Re-record the pair after every edit.** Each paired edit re-runs `pnpm run verify-translation-pairing --write <pair>` so the sidecar tracks the confirmed pair.
## Kind system and templates
The `kind` frontmatter field selects exactly one README template. Every kind in [the metadata reference](references/metadata-links-i18n.md#the-kind-system) maps to one template file in [`templates/`](templates/), and every template backs exactly one kind; the documentation check derives the expected kind from the same mechanical facts.
- `package-group` → [templates/package-group.md](templates/package-group.md): group maps (`packages/README.md`, `packages/<group>/README.md`) — orient the family, map its direct packages, link package-owned details.
- `package-reference` → [templates/package-reference.md](templates/package-reference.md): a Cordis plugin or service package — mount configuration, the config table, folded implementation, Model Experience and Known Limitations in the gate-owned forms.
- `package-library` → [templates/package-library.md](templates/package-library.md): a package with no plugin surface — consumer entry points, no profile-install path, no mount configuration.
- `package-bundle` → [templates/package-bundle.md](templates/package-bundle.md): a package declaring `dsh.bundle.patch` — the verified `dsh plugin` install path, layer semantics, patch document.
Open the template before writing and follow its skeleton and rules; it states what the kind is, how the page is structured, and the fact checks each section owes. Add a new kind only together with a distinct template file, a documented repository position or declared owner, and a focused check that maps documents to it.
## Voice rules
These rules decide what a section may say. They apply to every authored human-facing page, and to package READMEs with particular force.
- **Summary says what the subject does.** The opening `Summary` and the user-facing sections describe what a user or agent can DO with the subject — outcomes, benefits, when to choose it, main cost — never its role, type, or internal identity. "The seam registers `ctx.x` and appends `x/event` records" is identity narration; "you can save a note per message and it survives restarts" is what it does.
- **Developer sections explain, never enumerate.** Folded implementation content covers the overall design concept, architecture, and hand-waving dataflow — enough to understand how the package works — and links code for exact detail. No full API catalogs, exhaustive column lists, event-payload enumerations, or JSDoc restatement inside the folds.
- **Dev Note is the only slop zone.** Partial ideas, scratches, undecided directions, measured artifacts, and working hypotheses live only in the final Dev Note, marked explicitly non-authoritative. Every other section is polished, current-state prose.
- **Current state only.** No compatibility shims, migration talk, or history ("previously", "now", "no longer", renamed) outside the Dev Note; the codebase as it is today is the only subject.
- **Use controlled technical English.** Give each sentence an explicit actor and one main action when ambiguity can change behavior. Reuse one term per concept, prefer direct verbs, split stacked instructions and conditions, and preserve modality and exceptions. Apply the non-certified, ASD-STE100-inspired discipline in [the page-style reference](references/style.md#controlled-technical-english). Do not force a shorter sentence when precision would fall.
## Quality criteria
Use these definitions in review. Each section opens with a short orienting paragraph before subsections or exhaustive detail.
- **Brief:** the common path contains only facts needed for its outcome; exhaustive truth remains one direct link or detail layer away.
- **Intuitive:** prerequisites precede dependent concepts, one next action is obvious, and headings use terms readers search for.
- **Friendly:** readers can recognize success, understand risk before acting, recover from likely failure, and choose whether to continue deeper.
- **Accurate:** each durable claim has one owner and a verification path proportionate to its risk.
- **Agent-readable:** metadata, stable headings, anchors, terminology, ownership, and current/proposed status support targeted retrieval without loading the corpus.
- **Newcomer-complete:** a professional engineer with no repository context can reconstruct the relevant architecture or feature through three to five linked pages.
Do not apply a universal word limit to exhaustive references. Measure entry-path length, unrelated material scanned for one lookup, largest section, heading count, and page size; split by an existing domain owner when retrieval cost is high.
## Audit the corpus
Read, do not re-summarize, the owning contracts: [docs/AGENTS.md](../../../docs/AGENTS.md) for hierarchy, tutorial/reference forms, taxonomy, budgets, and the slop checklist; [.agents/notes/README.md](../../notes/README.md) for Agent Note lifecycle; [docs/i18n/README.md](../../../docs/i18n/README.md) for the bilingual pairing rules; and [root AGENTS.md](../../../AGENTS.md) for standing orders. Exclude `.agents/notes/archived/` from audits and edits — archived notes are frozen history.
Apply the standard's authoring order to every human-facing document in scope (not to Agent Notes): locate the document and state its own subject; set the permitted detail level and move deeper explanations to owning descendants with links; classify tutorial or reference from intended use, not path; for a tutorial, order concepts by prerequisite and difficulty; split substantial mixed forms. Then check placement constraints: paired docs cost a counterpart update and a `--write` re-record on every edit; generated catalogs are never hand-edited; a move is atomic with every inbound link repaired in the same change.
After the structural pass, hunt the slop checklist with the cheapest probes first. Use [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for reasoning-transcript leakage, grep distinctive phrases to find duplicated rules, replace hand-written catalogs and status inventories with their authoritative owners, and remove migration plans and future-tense spec language from implemented Agent Notes. Measure outliers with `pnpm run verify-doc-budgets --list` and a word-count scan; if removing prose changes a promised behavior rather than its explanation, propose the behavior change first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)). Keep every load-bearing rule, preferably as one to three lines plus a link to its rationale; do not create a new explanation merely to relocate disposable reasoning.
## Wordcount budgets
`pnpm run verify-doc-budgets` compares standing documents against ceilings in [scripts/doc-budgets.manifest.json](../../../scripts/doc-budgets.manifest.json); a red gate follows the ordered relocate-condense-raise policy in [docs/AGENTS.md](../../../docs/AGENTS.md#wordcount-budgets). Ceilings are guardrails, not reduction targets: at or below target, retain at least 5% headroom; raise a ceiling only when the words need the space, and justify the manifest diff in the PR.
## Website publication
The website is a tested projection, never a second copy: [website/docs.ts](../../../website/docs.ts) is the explicit public allowlist mapping canonical `docs/` sources into route trees, [scripts/project-doc-site.ts](../../../scripts/project-doc-site.ts) rewrites them into the disposable `website/.generated/` tree, and VitePress builds that tree. Repository Markdown stays the only editable content source; translations stay sibling pairs (`foo.md`, `foo.zh.md`, `foo.i18n.yaml`), never locale directories. Edit an already published page in its canonical source only; add one manifest entry for a new page; update source, manifest entry, and inbound links atomically for a move or removal; never edit `website/.generated/`, `website/.cache/`, or `website/.dist/`. Set every `DocsPage` field deliberately and honor the projector's link rules; see [references/website-sync.md](references/website-sync.md) for the fields, sidebar collections, and preview commands. Synchronizing content into the build does not publish it: deployment stays a separate, explicitly requested step.
## Detailed references
Load only the reference needed for the task. Each reference links directly from this file so the skill has no deep reference chain.
- [Metadata, links, and bilingual pairs](references/metadata-links-i18n.md): README frontmatter, the kind system and its derivation, description semantics, repository paths, line alignment, and the sidecar record.
- [Page structure and hierarchy](references/structure-hierarchy.md): mandatory section order, section summaries, user-to-developer progression, docs tree placement, small rule files, Further Exploration, and Dev Note ownership.
- [Page style](references/style.md): short Summary, `-----` section separators, foldable content sections, and emphasis discipline.
- [Review criteria](references/review.md): newcomer test, evidence checks, package README review, the reference example, and verification commands.
- [Website publication](references/website-sync.md): manifest fields, projector link rules, preview and validation, and deployment separation.
The four README templates in [`templates/`](templates/) are the working skeletons for the four `kind` labels; open the one your document's kind names before writing.
Use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) for sentence-level contract coverage and editorial judgment. The `session-persistence-sqlite` README pair ([English](../../../packages/session/session-persistence-sqlite/README.md), [Chinese](../../../packages/session/session-persistence-sqlite/README.zh.md)) is the reference example: searchable YAML, Summary and Table of Contents, user-to-developer progression with a folded developer section, Further Exploration, canonical Model Experience and Known Limitations sections, and a final Dev Note.
## Validation
Validate the affected format, not merely Markdown syntax. A strong promise needs a focused valid fixture and an invalid fixture that proves the top-level gate can fail.
- README metadata: parse YAML, map `kind` to its template and document standard, reject `name`, `audience`, ungoverned `tags`, and README-local `i18n` metadata, and reject missing or advertisement-style descriptions.
- Bilingual pages: verify structure, exact line count, terminology, link parity, and the sidecar record.
- Tutorials: exercise the documented entry path or name an explicit manual verification owner.
- Generated references: run the deterministic freshness check and report retrieval-size measures.
- Package READMEs: run model-experience and limitation checks, then package-focused tests when behavior claims changed; re-run every command the README instructs before merging a claim about it.
- Skills: run the repository's skill-invocation metadata check.
Run `pnpm run test:docs` for the quick comprehensive documentation checks (pairing, wrap, links, README gates, budgets, skill metadata, Agent Note gates) before the full `pnpm run doc-sync`.
## Dev Note
None.
@@ -0,0 +1,74 @@
# Metadata, links, and bilingual pairs
## Summary
README metadata is a retrieval and template-selection interface, not a miniature report or advertisement. The `kind` field selects exactly one README template that exists in this skill and maps to the document standard; the frontmatter carries no field that a filename convention or an executed gate already owns. Bilingual pages keep equal authority, one-to-one structure, and exact physical line alignment. The `*.i18n.yaml` sidecar records the last-confirmed pair and supports automatic merges. Link syntax must render correctly on GitHub and the documentation site, so repository links stay renderer-valid relative URLs.
## Table of Contents
- [README metadata](#readme-metadata)
- [The kind system](#the-kind-system)
- [Description quality](#description-quality)
- [Repository links and path mentions](#repository-links-and-path-mentions)
- [Bilingual line alignment](#bilingual-line-alignment)
- [Bilingual consistency records](#bilingual-consistency-records)
- [Dev Note](#dev-note)
## README metadata
Start every authored README with YAML frontmatter. Permit custom fields, but keep common fields stable enough for search and indexing.
```yaml
---
description: "Example capability for users and maintainers choosing, configuring, or debugging the package."
kind: "package-reference"
---
```
`description` and `kind` are required for package README pairs. The page title and package manifest already own the name, while the document job and its reader path express the audience; duplicating either in frontmatter adds no retrieval value. The counterpart path comes from the sibling filename (`README.zh.md`), and the sidecar owns pair state, so README-local `i18n` metadata is redundant. Do not add `tags` until a repository-owned taxonomy and search consumer justify them beyond description and full-text search. Keep keys lowercase and hyphenated unless an existing owner defines another spelling, and do not copy volatile code inventories into frontmatter.
## The kind system
`kind` selects the document template directly; every kind maps to exactly one template that exists in this skill, and no template exists without a kind. Derive the kind mechanically, in this order:
1. The README is `packages/README.md` or `packages/<group>/README.md``package-group`.
2. The package manifest declares `dsh.bundle.patch``package-bundle`.
3. The package is in the audited library registry of `scripts/doc-standard.spec.ts``package-library`.
4. Everything else — a service default export or an `apply` plugin — is `package-reference`.
| `kind` | Repository position | Template | Standard |
|---|---|---|---|
| `package-group` | `packages/README.md`, `packages/<group>/README.md` | [package-group.md](../templates/package-group.md) | Group map: orient the capability family, map its direct packages, explain composition relationships, and link package-owned details. |
| `package-reference` | `packages/<group>/<package>/README.md` with a plugin entry | [package-reference.md](../templates/package-reference.md) | Package contract: follow the [package README review standard](review.md#package-readme-review) and the canonical [package documentation requirements](../../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme). |
| `package-library` | `packages/<group>/<package>/README.md` with a plain module entry | [package-library.md](../templates/package-library.md) | Library contract: consumer entry points and boundaries; no profile-install path and no mount configuration. |
| `package-bundle` | `packages/<group>/<package>/README.md` declaring `dsh.bundle.patch` | [package-bundle.md](../templates/package-bundle.md) | Installable layer: the verified `dsh plugin` install path, layer semantics, and patch document. |
Before assigning `package-library` or `package-bundle`, inspect the facts: read `package.json` for `dsh.bundle.patch` and `src/index.ts` for the entry shape (`apply` export or a default service export is a plugin; a plain module API is a library). `dsh plugin --profile <name> add <package>` installs any npm dependency, but the profile reconcile activates a layer only for a package that declares `dsh.bundle`; never present that command as an install path for a library or a plain plugin. The documentation check derives the expected kind from these same facts, rejects another value, and rejects `name`, `audience`, `tags`, and README-local `i18n` metadata. Add a new kind only with a distinct template, an unambiguous repository position or declared owner, and a focused check that maps documents to it.
## Description quality
Agents search frontmatter `description` values to shortlist pages before loading full documents. Write each value like a Skill description: state what the page covers and when a reader should open it. Use one or two concrete sentences, include searchable domain terms, and distinguish the page from nearby owners. Do not summarize every section, claim superiority, repeat the title, advertise vaguely, preserve change history, or write a technical status report.
Good: `SQLite session persistence for deployments and maintainers choosing, configuring, or debugging the opt-in packed-row backend.`
Weak: `The best and most advanced SQLite storage implementation with lots of optimizations.`
## Repository links and path mentions
Keep link destinations machine-checkable and mentions context-relative. Use fragment-only links for the current page's menu. Use full URLs for external resources.
The desired internal-link model names a target from the repository root, but a leading `/docs/...` Markdown URL resolves outside the repository on GitHub, remains untouched by the website projector, and is skipped by `verify-md-links`. Until a repository-owned resolver supports root paths in every renderer, use the current renderer-valid relative URL in Markdown links and write logical path mentions such as `docs/` or `packages/session/` relative to the discussion. Never adopt an unchecked leading-slash link merely to resemble an absolute path.
## Bilingual line alignment
Keep English and Simplified Chinese equally authoritative. Match frontmatter key order, headings, blank lines, paragraphs, list items, tables, code fences, link targets, and total physical line count one to one. The English side points every relative link at the `.md` target; the Chinese side points it at the `.zh.md` sibling when that counterpart exists and falls back to the `.md` target otherwise — the pairing gate compares `.md` and `.zh.md` targets as the same document. Translate prose naturally within its corresponding line; do not hard-wrap either language. Keep code blocks byte-identical and reposition first-use terminology annotations without changing line structure.
Line equality is a structural check, not proof of faithful meaning. Review still owns completeness, terminology, natural language, and whether each line expresses the same proposition.
## Bilingual consistency records
Keep the `*.i18n.yaml` sidecar for every bilingual pair. `verify-translation-pairing` consumes its Git blob hashes for last-confirmed-text recovery, verifies structure and exact line alignment, supports automatic merging, records generated regions, and seals archives. Re-record it with `pnpm run verify-translation-pairing --write <pair>` after either language changes. Do not copy content hashes into README frontmatter: independent edits would change the same header line and turn otherwise mergeable prose into an owner-file conflict.
## Dev Note
None.
@@ -0,0 +1,67 @@
# Review criteria
## Summary
Review documentation by whether a reader completes an outcome, not by whether every template heading exists. Verify prose against code and tests, preserve exact contracts, and keep package READMEs useful to consumers while exposing enough implementation detail for maintainers. Run current repository gates; the `session-persistence-sqlite` README pair is the reference example of the format.
## Table of Contents
- [Newcomer test](#newcomer-test)
- [Evidence review](#evidence-review)
- [Package README review](#package-readme-review)
- [Reference example](#reference-example)
- [Verification](#verification)
- [Dev Note](#dev-note)
## Newcomer test
A professional engineer with no repository context should answer the following after three to five linked pages: what the product or feature does, how to run or use it safely, where its state lives, which component owns it, how it fails, and where to change it. If the reader must inspect source merely to discover the public flow, restore the missing explanation. If the reader must absorb unrelated internals, move those details deeper.
## Evidence review
Check each material statement against its strongest owner. Use package metadata for names and entry points, public types and JSDoc for API contracts, runtime code for behavior, tests for exercised failure paths, generated catalogs for exhaustive inventories, and active Agent Notes for rationale. Never treat a prior README, discussion, or report as stronger than current code and tests.
For every operational claim — a CLI command, a config snippet, a default value, an error message, a platform difference — the evidence is running it, not reading it. Execute the exact command or mount the exact configuration against the current checkout before the page may state its behavior; quote only observed output, warnings, and failures. Claims that depend on unavailable keys or networks name their verification owner instead of asserting behavior. For pre-existing pages, compare against latest `origin/master` and re-verify stale statements against code.
Classify the package before reviewing its install guidance: `dsh.bundle.patch` in `package.json` makes it a bundle (installable via `dsh plugin --profile <name> add <package>`, the only shape that command activates as a layer); an `apply` export or default service export makes it a plugin (mounted as a `cordis.yml` row); a plain module API makes it a library (a dependency with no install path). Reject install guidance written for another shape.
Retain a statement only when it helps the target reader act, reason, or avoid misuse. Move rationale, history, test walkthroughs, duplicate catalogs, and unrelated package detail to their owners.
## Package README review
Require the following without forcing one universal internal heading set:
- searchable YAML metadata with a precise `description` and the mechanically derived `kind` (`package-group`, `package-reference`, `package-library`, or `package-bundle`);
- a three-to-five-sentence Summary that says what the subject DOES for its user or agent reader, with a linked Table of Contents;
- controlled English with explicit actors, stable terms, direct verbs, separated instructions and conditions, and unchanged modality;
- when to choose or avoid the package;
- a smallest safe configuration or usage path when one exists — for a bundle, the verified `dsh plugin` install path; for a library, the consumer entry point; never profile-install guidance for a shape that does not take it;
- observable behavior, failures, durability, security, and performance limits relevant to consumers;
- developer-facing ownership and data/lifecycle design at concept level — overall design, architecture, hand-waving dataflow — that cannot be recovered cheaply from public types, with code links for exact detail;
- canonical Model Experience and Known Limitations sections required by package policy;
- newcomer-facing Further Exploration where adjacent docs materially help;
- a final non-authoritative Dev Note as the only home for partial ideas, scratches, and undecided directions.
Do not restate JSDoc or generated catalogs. Link the owner and explain only the decision or relationship needed locally. Reject any user-facing section that narrates internals (function subjects, event streams, data flow) and any fold that enumerates APIs instead of explaining the concept.
## Reference example
The `session-persistence-sqlite` README pair ([English](../../../../packages/session/session-persistence-sqlite/README.md), [Chinese](../../../../packages/session/session-persistence-sqlite/README.zh.md)) demonstrates the format in production: searchable YAML whose `kind` selects this package-reference standard, a five-sentence Summary, a linked Table of Contents, a user-facing use section (choice, sizing, configuration, migration, safe operation) separated by horizontal rules and followed by a GitHub-native `<details>` fold under the developer section title (design philosophy, source map, schema tables, write path, read and recovery), Further Exploration, canonical Model Experience and Known Limitations sections, and a final folded Dev Note holding non-authoritative working context such as the annotated benchmark artifact and undecided future directions. Use its structure, evidence standards, and bilingual alignment as the model for package READMEs and cross-package pages; ground every claim the way it grounds the benchmark numbers in the Agent Note.
## Verification
Run the smallest focused checks while iterating, then the standing documentation checks:
```sh
pnpm run test:docs
pnpm run verify-translation-pairing --write <pair>
pnpm run doc-sync
pnpm run lint
git diff --check
```
Also run the repository's skill-invocation metadata check for skill changes and compare English/Chinese physical line counts for a line-aligned pair. Re-read the final diff once for factual completeness and once for brevity, navigation, and ownership.
## Dev Note
None.
@@ -0,0 +1,87 @@
# Page structure and hierarchy
## Summary
Each page gives a newcomer a short front door before it exposes operational or implementation depth. Cross-package learning and engineering material lives under a deliberate `docs/` hierarchy, while package contracts stay beside code. Small rule files own one independently searchable requirement, but arbitrary fragmentation is not a goal. The final Dev Note isolates active working context from the stable explanation above it.
## Table of Contents
- [Page order](#page-order)
- [Section progression](#section-progression)
- [Documentation hierarchy](#documentation-hierarchy)
- [Small rule files](#small-rule-files)
- [Further Exploration](#further-exploration)
- [Dev Note ownership](#dev-note-ownership)
- [Dev Note](#dev-note)
## Page order
Use this order for authored human-facing pages when the format owner permits it. Generated artifacts may generate the same entry sections, while Agent Notes and postmortems retain their repository-defined skeletons.
1. YAML metadata.
2. H1 title.
3. Language switcher for a bilingual page.
4. `## Summary`: three to five explanatory sentences stating what the subject is, why a reader would care, the main operating model, and the most important boundary.
5. `## Table of Contents`: links to the page's H2 sections; keep it navigational rather than descriptive.
6. Stable content, ordered from user-facing use to developer-facing design and operational detail.
7. Optional `## Further Exploration` for newcomer-oriented links to adjacent subjects.
8. Final `## Dev Note` for non-authoritative active working context.
Do not force a Summary/Table of Contents wrapper around tiny machine-owned files, generated fragments, or formats whose executed parser defines another header. State the exception in the format owner rather than creating invalid output. The package README gate requires `Model Experience` and `Known Limitations and Deferred Work` as the final two H2 sections: place `Further Exploration` before them and end with a final `### Dev Note` inside the limitations H2.
## Section progression
Open every substantive H2 with a short orienting paragraph before tables, code, or H3 subsections. Explain the section's subject and decision-relevant point; do not repeat its complete contents.
Within a page, order content by reader depth:
1. Basic use: when to choose the feature, required inputs, shortest safe example, observable success, and likely recovery.
2. Advanced use: configuration choices, limits, operations, and integration behavior.
3. Developer detail: ownership, lifecycle, data model, failures, performance, security, and extension points worth maintaining.
Fold heavy developer detail and the final Dev Note behind `<details>` blocks with the section titles visible (mechanics in [style.md](style.md)).
Folded developer detail is concept-level by requirement: the overall design concept, the architecture of the main components, and hand-waving dataflow — enough to understand how the package works — plus source-map tables and links to code for exact detail. It never becomes an exhaustive catalog: no full API inventories, column lists, event-payload enumerations, or JSDoc restatement. The Dev Note is the only place allowed to hold partial ideas, scratches, and undecided directions; everything else, folds included, is polished current-state prose.
Keep exhaustive generated types, schemas, or catalogs behind a compact entry paragraph and stable index. Split them by an existing domain owner when one lookup requires scanning unrelated groups.
## Documentation hierarchy
Use package-local READMEs for package contracts and keep them next to source. Organize cross-package Markdown under audience and learning intent instead of leaving unrelated pages flat at `docs/`.
```text
docs/
learn/
overview/
cordis/
practices/
user/
developer/
discussion/
scratch/
subsystems/
```
Treat this as a target map, not permission for an opportunistic mass move. Move one coherent topic at a time, repair every inbound link and website mapping atomically, preserve public routes or aliases, and keep `subsystems/` flat because its pages are logically parallel.
`docs/scratch/` contains tracked, expiring discussion that must survive a handoff. Each scratch page names its owner, creation date, expiry, and promotion target. Local disposable notes remain ignored and uncommitted.
## Small rule files
Give an independently searchable rule, practice, example family, or decision one small file when it has its own owner, change cadence, inbound links, or validation. Group related files under a descriptive hierarchy such as `docs/developer/code-quality/`. Keep tightly coupled rules together when splitting would force readers to open several files to understand one obligation.
An index page explains the folder in three to five sentences and links its direct children by purpose. It does not restate each child's rule.
## Further Exploration
Use this optional section for a newcomer who finished the page and wants adjacent understanding. Link three to seven directly related pages, order them from closest prerequisite to deeper exploration, and say in a short phrase what each adds. Do not turn it into a complete site index.
## Dev Note ownership
End authored pages with Dev Note, but keep it explicitly non-authoritative. Active hypotheses, compatibility concerns, rough alternatives, progress pointers, and unresolved questions may live there; stable behavior, required limitations, and accepted rationale belong in their ordinary owners.
Dev Note may mirror or link task progress but must not become a second writable queue. When work closes, promote durable conclusions, move reusable rationale to an Agent Note, keep incident chronology in a postmortem, and delete resolved chatter. Git history preserves old iterations.
## Dev Note
The mandatory final section is intentionally the least polished part of an authored page, but it still has lifecycle discipline. A blank Dev Note should say `None.` rather than accumulate placeholder prose; generated and parser-owned formats may omit it through a named exception.
@@ -0,0 +1,49 @@
# Page style
## Summary
Page-level style preferences that make DSH pages scannable and difficult to misread: a short Summary, controlled technical English, `-----` separators between major parts, `<details>` folds that keep section titles visible, and disciplined emphasis. The template is the `session-persistence-sqlite` README pair.
## Table of Contents
- [Short summary](#short-summary)
- [Controlled technical English](#controlled-technical-english)
- [Section separators](#section-separators)
- [Foldable content sections](#foldable-content-sections)
- [Emphasis discipline](#emphasis-discipline)
- [Dev Note](#dev-note)
## Short summary
Open every authored page with a short `Summary`: three to five sentences in one paragraph stating what the subject is, why the reader cares, the operating model, and the most important boundary. The Table of Contents and the sections carry the detail; placement and section order live in [structure-hierarchy.md](structure-hierarchy.md).
## Controlled technical English
Use an [ASD-STE100](https://www.asd-ste100.org/)-inspired review pass for English prose that an agent, translator, or non-native reader must parse. This is a clarity discipline, not certified ASD-STE100 compliance. The repository does not reproduce or validate the standard's controlled dictionary.
- Name the actor and action. Prefer active voice when the actor matters.
- Use one stable term for each concept. Do not rotate synonyms for variety.
- Prefer direct verbs. Replace nominalizations and ambiguous phrasal verbs when a precise verb exists.
- Put one instruction in each sentence. Use a list for three or more steps or conditions.
- Split semicolons and long clause chains. Keep each paragraph on one topic.
- Remove unsupported quality adjectives and stacked hedges. Preserve every fact and degree of uncertainty from the source.
Treat 20 words for an instruction and 25 words for a description as review prompts, not mechanical gates. Keep a longer sentence when a split would hide a condition or relationship. Never remove or strengthen `must`, `may`, `never`, timing, exceptions, numbers, or other contract terms to meet a length target. The [prose standard](../../dsh-prose-standard/SKILL.md) owns the complete-proposition rule.
## Section separators
Separate the major parts of a page with a `-----` horizontal rule on its own line, with a blank line before and after it. A rule directly after a paragraph would parse as a Setext heading, and a rule inside the final two H2 sections of a package README would break the Model Experience gate. The template separates: front matter → use section → folded developer section → Further Exploration → Model Experience.
## Foldable content sections
Fold developer-facing detail and the final Dev Note behind GitHub-native `<details>`/`<summary>` blocks. Keep the section title (H2 or H3) and its `<a id>` anchor visible; fold only the content under the title. Inside the block, put a blank line after `<summary>`, keep every Markdown line at column 0 (indented content becomes a code block), and close with `</details>` after a blank line. Headings, lists, tables, and links inside the fold parse normally and keep their anchors. The `session-persistence-sqlite` README pair demonstrates both folds: the implementation section and the Dev Note.
In a package README, keep `## Model Experience` and `## Known Limitations and Deferred Work` as the final two H2 headings. Put the limitations anchor immediately after its H2 so it does not become part of the preceding Model Experience body. Place the final Dev Note under the limitations section as an anchored H3. A package that is explicitly exempt from the limitations section can use an H2 Dev Note.
## Emphasis discipline
Reserve bold for the clause that changes behavior or for the comparison that matters. In benchmark tables, bold the column headers and the best value in each row, as in the reference example.
## Dev Note
None.
@@ -1,20 +1,24 @@
---
name: dsh-doc-site-sync
description: Use when publishing, updating, moving, or removing DeepSeek Harness documentation website pages; editing website/docs.ts mappings or navigation; diagnosing a page missing from the VitePress site; fixing projected documentation links; or running the docs:dev, docs:check, and doc-sync workflow after website-content changes.
---
# Website publication
# Synchronizing the DeepSeek Harness Documentation Site
## Summary
Keep repository Markdown as the only editable content source. Treat the website as a tested projection: [website/docs.ts](../../../website/docs.ts) selects public pages, [scripts/project-doc-site.ts](../../../scripts/project-doc-site.ts) rewrites them into the disposable `website/.generated/` tree, and VitePress builds that tree. The build additionally emits a raw-Markdown twin of every route (page URL minus any trailing slash, plus `.md`; index routes also get a parent-level alias) and a root `llms.txt` index; both derive from the same manifest and projector, so publishing, moving, or removing a page updates them automatically and `docs:build` fails when one is missing.
The documentation website is a tested projection of repository Markdown, never a second copy. [website/docs.ts](../../../../website/docs.ts) is the explicit public allowlist, [scripts/project-doc-site.ts](../../../../scripts/project-doc-site.ts) rewrites mapped sources into the disposable `website/.generated/` tree, and VitePress builds that tree. The build also emits a raw-Markdown twin of every route and a root `llms.txt` index from the same manifest. This reference owns the manifest fields, the projector's link rules, preview and validation commands, and the deployment boundary.
Repository translations follow the sibling pairing contract: English `foo.md`, Chinese `foo.zh.md`, and `foo.i18n.yaml` live together. Never create `zh-CN/` or other locale directories for website content. The site route trees are independent of that source layout: `foo.zh.md` projects to the root route and `foo.md` projects to the matching `/en/` route.
## Table of Contents
## Read the owning contracts
- [Manifest ownership](#manifest-ownership)
- [Classify the change](#classify-the-change)
- [DocsPage fields](#docspage-fields)
- [Preserve link behavior](#preserve-link-behavior)
- [Preview and validate](#preview-and-validate)
- [Keep deployment separate](#keep-deployment-separate)
- [Dev Note](#dev-note)
- Read [docs/AGENTS.md](../../../docs/AGENTS.md) and use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) when deciding where content belongs or changing product documentation prose.
- For an edited bilingual source, follow the lightweight routine path in [docs/AGENTS.md](../../../docs/AGENTS.md#writing-rules) and the [pairing contract](../../../docs/i18n/README.md); never invoke the extended translation skill automatically.
- Read the current `DocsPage` type and entries in [website/docs.ts](../../../website/docs.ts) before changing the manifest; do not rely on a remembered field set.
- Read [website/.vitepress/config.ts](../../../website/.vitepress/config.ts) before adding a new section, sidebar collection, locale, or top-level navigation item.
## Manifest ownership
Read [docs/AGENTS.md](../../../../docs/AGENTS.md) and the current `DocsPage` type and entries in [website/docs.ts](../../../../website/docs.ts) before changing the manifest; do not rely on a remembered field set. Read [website/.vitepress/config.ts](../../../../website/.vitepress/config.ts) before adding a new section, sidebar collection, locale, or top-level navigation item. For an edited bilingual source, follow the lightweight routine path in [docs/AGENTS.md](../../../../docs/AGENTS.md#writing-rules) and the [pairing contract](../../../../docs/i18n/README.md); never invoke the extended translation skill automatically.
Never edit or commit `website/.generated/`, `website/.cache/`, or `website/.dist/`. Except for `website/AGENTS.md`, never add Markdown under `website/`; locale and route directories such as `website/zh-CN/`, `website/en/`, and `website/api/` are invalid source layouts. Keep generated catalogs under `docs/`, freshness-gate them there, and publish them through the manifest.
## Classify the change
@@ -24,21 +28,21 @@ Repository translations follow the sibling pairing contract: English `foo.md`, C
- **Publish a generated catalog:** map the generated `docs/` file, but change its generator or source metadata rather than editing the catalog by hand.
- **Change site structure:** update the manifest for ordinary pages; update VitePress configuration only when the existing sidebar, section, or locale model cannot express the change.
Never edit or commit `website/.generated/`, `website/.cache/`, or `website/.dist/`. Except for `website/AGENTS.md`, never add Markdown under `website/`; locale and route directories such as `website/zh-CN/`, `website/en/`, and `website/api/` are invalid source layouts. Keep generated catalogs under `docs/`, freshness-gate them there, and publish them through the manifest.
Keep the manifest an explicit public allowlist. Do not publish RFCs, postmortems, testing guides, `AGENTS.md`, or maintainer workflows merely because they exist under `docs/`; add internal material only when the user explicitly expands what the site publishes.
## Add or update a manifest entry
## DocsPage fields
Set every `DocsPage` field deliberately:
Set every `DocsPage` field deliberately. The canonical field set and the `DocsSidebar` union live in [website/docs.ts](../../../../website/docs.ts) — read them there rather than copying values into prose; sections are owned by the `sections` record in that file, with no separate order list in the VitePress config.
- `source`: repository-relative canonical Markdown path. For a complete bilingual pair, add the English `.md` path through `pairedPages()`; it derives the sibling `.zh.md`, the content locales, and counterpart aliases.
- `route`: public VitePress path including the `.md` suffix.
- `label`: sidebar label, not necessarily the document H1.
- `sidebar`: reuse `zh-guide`, `zh-develop`, or `en-docs` unless the information architecture genuinely needs another collection.
- `section`: reuse an existing section when possible. If adding one, also place it in `sectionOrder` in the VitePress config.
- `sidebar`: reuse an existing `DocsSidebar` collection unless the information architecture genuinely needs another one.
- `section`: reuse an existing section when possible. If adding one, also define it in the `sections` record.
- `order`: stable order within the section.
- `sourceAliases`: optional additional repository paths that should resolve to this page when links are projected. It does not create another public route.
Use `mirroredPages()` only for a source that intentionally falls back to the same available language in both route trees. Convert that entry to `pairedPages()` when its counterpart is added. Keep the manifest an explicit public allowlist. Do not publish RFCs, postmortems, testing guides, `AGENTS.md`, or maintainer workflows merely because they exist under `docs/`; add internal material only when the user explicitly expands what the site publishes.
Use `mirroredPages()` only for a source that intentionally falls back to the same available language in both route trees. Convert that entry to `pairedPages()` when its counterpart is added. The site route trees are independent of the source layout: `foo.zh.md` projects to the root route and `foo.md` projects to the matching `/en/` route.
## Preserve link behavior
@@ -74,13 +78,18 @@ If Markdown link checks pass but the site build reports a missing fragment, foll
Before committing a documentation-site change, run:
```sh
pnpm run test:docs
pnpm run doc-sync
pnpm run lint
git diff --check
```
Use [dsh-pre-push-checks](../dsh-pre-push-checks/SKILL.md) before pushing. Report the canonical files changed, manifest entries added or removed, public routes affected, and the exact checks run.
Use [dsh-pre-push-checks](../../dsh-pre-push-checks/SKILL.md) before pushing. Report the canonical files changed, manifest entries added or removed, public routes affected, and the exact checks run.
## Keep deployment separate
Synchronizing content into the VitePress build does not publish it to the internet. Do not add GitHub Pages permissions, deployment workflows, custom domains, or public hosting unless the user explicitly requests deployment and confirms the hosting policy.
## Dev Note
None.
@@ -0,0 +1,103 @@
# Template: package-bundle
Use this template for a package whose manifest declares `dsh.bundle.patch` — an installable profile layer: `packages/bundle/*`, `dsh-subagent-codex`, `dsh-subagent-claude-code`. The `bundle/base` README pair is the worked example.
A bundle README leads with the profile-install path and the layer semantics; the implementation fold explains the patch document. It never presents the package as a library to import or as a single plugin to mount.
## Frontmatter
```yaml
---
description: "What the bundle layer adds to a dsh --profile surface, for users composing or customizing a profile."
kind: "package-bundle"
---
```
## Skeleton
```markdown
# @deepseek-ai/dsh-<name>
English | [中文](README.zh.md)
## Summary
Three to five sentences: what a profile gains from this layer, which profiles already include it, how a user adds or removes it, and the main boundary.
## Table of Contents
- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Further Exploration](#further-exploration)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
-----
<a id="use-this-package"></a>
## Use this package
### Install into a profile
The verified install path — run it against the current checkout before writing:
```text
dsh plugin --profile <name> add @deepseek-ai/dsh-<name>
dsh plugin --profile <name> remove @deepseek-ai/dsh-<name>
```
State where in-box bundles resolve from, what the reconcile step activates, and what fails when the patch declaration is missing.
### What you get
The observable surface this layer adds: tools, providers, or UI rows, and which package owns each row's behavior.
-----
<a id="understand-the-implementation"></a>
## Understand the implementation
<details>
<summary>Implementation internals — click to expand</summary>
The patch document: insert list, row ids, platform gating, override semantics. Source-map table links `cordis.patch.yml` and `src/`. No API catalogs.
</details>
-----
<a id="further-exploration"></a>
## Further Exploration
Adjacent pages: the group map, the profile contract, the composition graph.
-----
<a id="model-experience"></a>
## Model Experience
The form the verify-package-readme-model-experience gate assigns (bundle carriers are `indirect` or `none`: each inserted row's package owns its model-facing behavior).
## Known Limitations and Deferred Work
<a id="known-limitations-and-deferred-work"></a>
Current constraints: override semantics, platform gates, and conflict rules a user must respect.
<a id="dev-note"></a>
### Dev Note
<details>
<summary>Working context for maintainers — click to expand</summary>
None.
</details>
```
## Rules
- **Only `dsh.bundle.patch` packages use this template.** Verify the declaration in `package.json` before classifying; the `dsh plugin` reconcile activates a layer for exactly these packages.
- **Test the install path.** Run `dsh plugin --profile <name> add <this-package>` in a scratch profile and reproduce the documented warning, layer activation, and failure modes before writing them.
- Re-run `pnpm run verify-translation-pairing --write packages/<group>/<pkg>/README.md` after editing the pair.
@@ -0,0 +1,59 @@
# Template: package-group
Use this template for `packages/README.md` and every `packages/<group>/README.md`. The page is a map: it orients the capability family, lists its direct packages with one-line roles, and links package-owned details. It never restates a package's contract.
## Frontmatter
```yaml
---
description: "The <group> package group: what the packages under packages/<group>/ own, for readers choosing or navigating the family."
kind: "package-group"
---
```
## Skeleton
```markdown
# <group>/ — <one-line subject>
English | [中文](README.zh.md)
## Summary
Three to five sentences: what the family provides, what a reader can DO with it, which package owns which half, and the main boundary.
## Table of Contents
- [Packages](#packages)
- [Related documentation](#related-documentation)
- [Dev Note](#dev-note)
-----
<a id="packages"></a>
## Packages
One short orienting sentence, then the package map:
| Package | Role |
|---|---|
| [`<pkg>`](<pkg>/README.md) | One-line role: what it contributes |
<a id="related-documentation"></a>
## Related documentation
- [Adjacent owner](../../<path>.md) — what it adds to this family.
<a id="dev-note"></a>
## Dev Note
None.
```
## Rules
- One row per direct package; role text states the package's contribution, never its internals.
- Add a `ctx key`, package shape, or npm-name column only when that distinction helps readers choose among the direct packages.
- Related documentation links adjacent owners (group maps, subsystem pages, Agent Notes) with a short phrase per link.
- Do not add a Model Experience or Known Limitations section; the group map owns no runtime behavior.
- Re-run `pnpm run verify-translation-pairing --write packages/<group>/README.md` after editing the pair.
@@ -0,0 +1,96 @@
# Template: package-library
Use this template for a package with no plugin surface: its entry exports a plain module API and it registers nothing into a composition. Examples: `boot/app-boot`, `util/*`, `sdk/protocol`, `typert/generator`. The `boot/app-boot` README pair is the worked example.
A library README differs from a package reference in three ways: no "install into a profile" guidance (a library is a dependency, not a layer), no mount configuration (there is no `cordis.yml` row), and a Model Experience section only in the audited form the gate assigns (most libraries are `none` or `indirect`).
## Frontmatter
```yaml
---
description: "What the library lets a caller build, in one or two concrete sentences with the consuming packages or searchable domain terms."
kind: "package-library"
---
```
## Skeleton
```markdown
# @deepseek-ai/dsh-<name>
English | [中文](README.zh.md)
## Summary
Three to five sentences: what a caller can DO with the library, who consumes it, the smallest entry point, and the main boundary.
## Table of Contents
- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Further Exploration](#further-exploration)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
-----
<a id="use-this-package"></a>
## Use this package
### When to use it
Name the consuming call sites (which bins, packages, or runtimes import it) and when a caller should reach for it instead of a plugin.
### Entry point
The smallest import-plus-call that works, in a `text` or `ts` fence, followed by what success and failure look like. Link the owning contracts in `src/index.ts` for exact detail instead of restating them.
-----
<a id="understand-the-implementation"></a>
## Understand the implementation
<details>
<summary>Implementation internals — click to expand</summary>
Design notes and a source-map table. No API catalogs.
</details>
-----
<a id="further-exploration"></a>
## Further Exploration
Adjacent pages, closest prerequisite first.
-----
<a id="model-experience"></a>
## Model Experience
Only the form the verify-package-readme-model-experience gate assigns this package (`none`, `indirect`, or the canonical blocks). A library never invents model effects it does not have.
## Known Limitations and Deferred Work
<a id="known-limitations-and-deferred-work"></a>
Current package constraints as top-level bullets; allowlist the package in scripts/verify-package-readme-limitations.ts when none exist.
<a id="dev-note"></a>
### Dev Note
<details>
<summary>Working context for maintainers — click to expand</summary>
None.
</details>
```
## Rules
- **Classify by the entry, not the folder.** Read `src/index.ts` before choosing this template: `export default` a service class or an `apply` export makes the package a `package-reference`, and `dsh.bundle.patch` in `package.json` makes it a `package-bundle`. A plain module API without those is a library.
- **Never write profile-install guidance.** `dsh plugin --profile <name> add <package>` installs any npm dependency but activates a profile layer only for `dsh.bundle`-declaring packages; for a library it is at best a no-op dependency and must not appear as an install path.
- Re-run `pnpm run verify-translation-pairing --write packages/<group>/<pkg>/README.md` after editing the pair.
@@ -0,0 +1,103 @@
# Template: package-reference
Use this template for a package whose entry is a Cordis plugin — a service default export or an `apply` function — mounted in a composition. This is the default for `packages/<group>/<pkg>/README.md`. The `session-persistence-sqlite` README pair is the worked example of this template.
## Frontmatter
```yaml
---
description: "What the package lets a reader choose, configure, or debug, in one or two concrete sentences with searchable domain terms."
kind: "package-reference"
---
```
## Skeleton
```markdown
# @deepseek-ai/dsh-<name>
English | [中文](README.zh.md)
## Summary
Three to five sentences on what a user or agent can DO with the package: outcomes, when to choose it, main cost, most important boundary. Never its role, type, or internal identity.
## Table of Contents
- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Further Exploration](#further-exploration)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
-----
<a id="use-this-package"></a>
## Use this package
One orienting sentence: the common path.
### When to choose it
Choose or avoid the package: one paragraph naming the deciding conditions and the fallback package.
### Minimal configuration
The smallest mount that works, as a `cordis.yml` snippet, plus the config table:
| Field | Default | Meaning |
|---|---|---|
| `<field>` | `<default>` or `required` | One-line meaning |
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-<name>) is the exhaustive source for every accepted field.
-----
<a id="understand-the-implementation"></a>
## Understand the implementation
<details>
<summary>Implementation internals — click to expand</summary>
Design concept, component architecture, and hand-waving dataflow — enough to understand the package. A source-map table links files for exact detail. No API catalogs or JSDoc restatement.
</details>
-----
<a id="further-exploration"></a>
## Further Exploration
Three to seven adjacent pages, closest prerequisite first, one short phrase each.
-----
<a id="model-experience"></a>
## Model Experience
<per the Model Experience contract in docs/cookbook/adding-a-package.md#4-write-the-package-readme; the verify-package-readme-model-experience gate owns the required form>
## Known Limitations and Deferred Work
<a id="known-limitations-and-deferred-work"></a>
One orienting sentence, then top-level bullets naming current package constraints. Packages with none use the allowlist in scripts/verify-package-readme-limitations.ts.
<a id="dev-note"></a>
### Dev Note
<details>
<summary>Working context for maintainers — click to expand</summary>
None.
</details>
```
## Rules
- **Fact-check before writing.** Mount the package in a test composition and run every command, config field, default, and behavior claim this README makes. Delete anything you did not reproduce; link the generated config catalog instead of restating fields.
- **Installation guidance.** A plugin package mounts through `cordis.yml` rows. Only a package declaring `dsh.bundle.patch` installs as a profile layer via `dsh plugin --profile <name> add <package>` — if this package lacks that declaration, say how it mounts in a composition, never `dsh plugin add`.
- **Model Experience and Known Limitations are gate-owned.** Match the exact headings and per-package forms the two gates enforce; update the gates' audited lists in the same change when behavior moves a package between forms.
- Re-run `pnpm run verify-translation-pairing --write packages/<group>/<pkg>/README.md` after editing the pair.
@@ -28,7 +28,7 @@ A strong simplification removes, folds, or demotes something real and has clear
- Hand-rolled code reimplements what a well-maintained external package or a Node builtin at the engine floor already provides, and the swap would delete the implementation plus its dedicated tests ([dependency policy](../../notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md)).
- The simplified behavior may differ slightly, but the new behavior is still reasonable and easier to explain.
Thin candidates are usually not enough for an Agent Note: deleting one typo, running `knip` once, removing an intentionally documented backend/adapter, or flagging "this looks complex" without call-site proof.
Thin candidates are not enough for an Agent Note: deleting one typo, running `knip` once, removing an intentionally documented backend/adapter, or flagging "this looks complex" without call-site proof.
## Survey Broadly
+1 -1
View File
@@ -5,7 +5,7 @@ description: Use when writing, reviewing, restoring, trimming, or auditing prose
# DeepSeek Harness Prose Standard
Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. A contract is an obligation, invariant, precondition, postcondition, or compatibility promise that a caller, callee, implementer, producer, or consumer relies on. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script.
Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. A contract is an obligation, invariant, precondition, postcondition, or compatibility promise that a caller, callee, implementer, producer, or consumer relies on. This skill owns editorial judgment and required prose coverage; use [dsh-doc](../dsh-doc/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script.
Treat `contract`, `boundary`, `shape`, `surface`, `seam`, `gate`, and `vocabulary` as terms to check before use, not banned words. First ask whether the exact rule, API, field set, type, validation, timing point, component split, or failure states the fact better. Keep a term when it names the exact technical subject, including caller/callee contracts and security/process boundaries.
+1 -1
View File
@@ -40,6 +40,6 @@ Unaided citation passes fail in both directions by deleting durable references a
1. Scope and exclusions per [dsh-prose-standard](../dsh-prose-standard/SKILL.md): require an explicit scope; never touch `vendor/` or `.agents/notes/archived/`. Recorded fixtures and snapshots are derivatives, not prose targets: change the owning source or scenario and regenerate them only when an authorized behavior change requires new evidence.
2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), calibrating each probe against a known positive and a near-miss negative before trusting its output, then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge found cases the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand.
3. Fix owner-first per surface: generated catalogs → trace every consumer, fix the source JSDoc or generator template, then regenerate all derivatives; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual prose → update the counterpart minimally and re-record it through the [lightweight routine](../../../docs/AGENTS.md#writing-rules); bilingual fences → copy the corrected verbatim block byte-for-byte into both sides per [dsh-doc-standards](../dsh-doc-standards/SKILL.md), then re-record the pair; model- or user-visible strings → route through [dsh-prose-standard](../dsh-prose-standard/SKILL.md) and change only with owning behavior evidence, otherwise leave unchanged and report the deferral.
3. Fix owner-first per surface: generated catalogs → trace every consumer, fix the source JSDoc or generator template, then regenerate all derivatives; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual prose → update the counterpart minimally and re-record it through the [lightweight routine](../../../docs/AGENTS.md#writing-rules); bilingual fences → copy the corrected verbatim block byte-for-byte into both sides per [dsh-doc](../dsh-doc/SKILL.md), then re-record the pair; model- or user-visible strings → route through [dsh-prose-standard](../dsh-prose-standard/SKILL.md) and change only with owning behavior evidence, otherwise leave unchanged and report the deferral.
4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop provenance.
5. Verify: re-run the batteries expecting only sanctioned keeps, this skill's own directory, and the owning note's quoted evidence; confirm every remaining citation resolves at HEAD; run the gates for touched surfaces (`doc-sync` for docs, `verify-type-equiv`, `verify-translation-pairing`).
+11 -8
View File
@@ -1,14 +1,12 @@
# AGENTS.md
DeepSeek Harness is an all-plugin agent harness on vendored Cordis. Read [docs/architecture.md](docs/architecture.md) before changing `packages/`; follow [docs/AGENTS.md](docs/AGENTS.md) for documentation.
DeepSeek Harness is an all-plugin Cordis agent harness. Read [docs/architecture.md](docs/architecture.md) before changing `packages/`; follow [docs/AGENTS.md](docs/AGENTS.md) for documentation.
## Pre-release stance: foundation over blast radius
**Remove at the first tagged release.** Until then, prefer correct foundations over compatibility shims and update every reference together. Backends reject old disk formats; SQLite increments `SCHEMA_VERSION`, while `dsh-session` holds `SESSION_FORMAT_VERSION` at `0` without a compatibility promise.
**Remove at the first tagged release.** Until then, prefer correct foundations to compatibility shims: rename or repackage freely and update every reference. Backends reject old on-disk formats. SQLite uses monotonic `SCHEMA_VERSION`; `dsh-session` keeps `SESSION_FORMAT_VERSION` at `0` with no compatibility promise.
## Application launch
Supported Node applications launch only through `dsh` profiles; application-package bins, demos, and public SDK argv escape hatches are forbidden. [Architecture](docs/architecture.md#application-launch) owns the launch set; `pnpm run verify-application-entrypoints` enforces it.
**Application launch.** Only `dsh` profiles launch supported Node apps; package bins, demos, and public SDK argv escapes are forbidden ([rule](docs/architecture.md#application-launch)).
## Repository layout
@@ -46,7 +44,7 @@ packages/ @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/
acp/ automation-only Agent Client Protocol server
interaction/ approval/interaction capabilities, permission, commands, ask-user
boot/ shared profile/application boot glue
sdk/ JSON-RPC protocol, server, and TypeScript client
sdk/ JSON-RPC protocol + TypeScript client/server
examples/ reusable composition bundles (agent-spine)
experimental/ private prototypes excluded from official releases
support/ dev/test infrastructure
@@ -79,6 +77,7 @@ pnpm run build # tsc emits lib/types, tsdown bundles runtime
pnpm run hygiene # knip + publint + workspace constraints + NodeNext consumer check
pnpm run check:windows-wine # ONLY when diagnosing a known Windows failure (needs wine); CI owns this signal
pnpm run doc-sync # all documentation gates; leaf list in scripts/run-gates.ts
pnpm run test:docs # quick documentation checks (no build; doc-quick aggregate)
pnpm run website:build # VitePress build (doubles as dead-link check)
pnpm dsh --profile headless "task" # run one task from source (needs DEEPSEEK_API_KEY)
pnpm run demo:code-mode -- "task" # headless Code Mode run (needs key)
@@ -90,7 +89,11 @@ If a required `gh`, `pnpm`, build, test, or generator command fails because the
### Run relevant checks locally
Before pushes, use [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/SKILL.md) to choose the smallest diff-covering checks; after `gh stack sync`, validate immediately and never merge before they pass. Report commands only. Match evidence to its surface: focused behavior tests, model/user snapshots, `doc-sync`, build/hygiene plus built smokes for published paths, and real-API e2e for provider behavior. CI owns exhaustive coverage and the platform matrix; run them locally only by request, for CI diagnosis, or for an irreducibly repository-wide change. `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)).
Run checks before pushes via [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/SKILL.md); report only commands run. After `gh stack sync`, validate immediately; do not merge before checks pass.
- Match evidence to the surface: focused behavior tests, model/user-output snapshots, `doc-sync` for docs, built smokes for published paths, and real-API e2e for providers.
- Never default to the full suite or repeat a passing check for commit or push. CI owns exhaustive coverage and the platform matrix; rehearse all locally only by explicit request, for CI diagnosis, or for an irreducibly repository-wide change.
- `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)).
## Secrets / .env
@@ -102,7 +105,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`,
- ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) — Node's native TypeScript modes are unavailable across the engines range ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it.
- **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer.
- **Runtime invariants assert owned relationships.** Check authoritative event streams or mutable data, not service or method presence, plugin metadata or effects, or fixed pure examples. Without a plausible relationship, an explained empty companion is correct ([package invariant rules](packages/AGENTS.md)).
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`; scoped keys absent from payloads need `@dshScopeScan unsupported`. Public service methods document parameters and non-void returns. A `SessionEventMap` member is required-on-read by default — builds that do not know its type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)).
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`; scoped keys absent from payloads need `@dshScopeScan unsupported`. Public service methods document parameters and non-void returns. Every `SessionEventMap` member is required-on-read: builds that do not know its type refuse the log; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/simplification/2026-08-25-fail-closed-session-event-vocabulary.md)).
- **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default.
- **Waterfall listeners MUST call `next()`** to delegate; returning without it short-circuits the chain ([semantics](docs/cordis-primer.md#cordis-waterfall-semantics)).
- **Model-visible ⟺ logged**: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event.
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write README.md
README.md: 9847d1fc35d5eceea484c229872dc8614ba3654a
README.zh.md: 6838b7c712e181dc44ca467225adf2aadf7ad947
README.md: 8fe2204c765dbccfd79a438a3a58900b7b21f52e
README.zh.md: 3c7ad619303dad747cd5114375647b13a7629f8b
+3 -3
View File
@@ -4,13 +4,13 @@ English | [中文](README.zh.md)
DeepSeek Harness (`dsh`) is an open-source agent harness developed by [DeepSeek AI](https://deepseek.com).
It uses an architecture where **everything is a plugin**, and is powered by [Cordis](https://github.com/cordiverse/cordis), whose design is described in [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper).
It is built on an **everything-is-a-plugin** architecture and powered by [Cordis](https://github.com/cordiverse/cordis), whose design is described in [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper).
Documentation: [https://deepseek-harness.github.io/deepseek-harness/](https://deepseek-harness.github.io/deepseek-harness/)
## Developer preview
DeepSeek Harness is currently in _developer preview_ and is iterating rapidly. **THERE WILL BE COMPATIBILITY-BREAKING CHANGES.**
DeepSeek Harness is in _developer preview_ and iterating rapidly. **THERE WILL BE COMPATIBILITY-BREAKING CHANGES.**
Review the [safety notice](SAFETY.md) before running the project.
@@ -42,7 +42,7 @@ pnpm dsh web
## Community and support
- Feel free to submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).
- Submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).
- Add the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to your plugin repository for discoverability.
- Join <a href="https://discord.gg/Ycq5dCaS4">DeepSeek Harness Discord community</a>.
+3 -3
View File
@@ -4,13 +4,13 @@
DeepSeek Harness`dsh`)是由 [DeepSeek AI](https://deepseek.com) 开发的开源 agent harness(智能体框架)。
采用**一切皆插件**的架构,由 [Cordis](https://github.com/cordiverse/cordis) 驱动,其设计参见论文 [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper)。
构建于**一切皆插件**的架构之上,由 [Cordis](https://github.com/cordiverse/cordis) 驱动,其设计参见论文 [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper)。
文档:[https://deepseek-harness.github.io/deepseek-harness/](https://deepseek-harness.github.io/deepseek-harness/)
## 开发者预览
DeepSeek Harness 目前处于 _开发者预览_ 阶段,正在快速迭代。**未来将出现破坏兼容性的变更。**
DeepSeek Harness 处于 _开发者预览_ 阶段,正在快速迭代。**未来将出现破坏兼容性的变更。**
运行本项目前,请阅读[安全说明](SAFETY.zh.md)。
@@ -46,7 +46,7 @@ pnpm dsh web
## 社区与支持
- 欢迎通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。
- 通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。
- 为你的插件仓库添加 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题,便于被发现。
- 欢迎加入 DeepSeek Harness 企微群:扫码添加企微小助手并填写入群问卷,完成后小助手会邀请你入群。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/cli/README.md
README.md: 4cbb60483d24d350f53bffa517dcb514e353f492
README.zh.md: 4ac4bda47cfd5dee843422c854f4306740968746
README.md: 850a9371c1515d1e0219e9a685b638063c498ff4
README.zh.md: 02de213d7a7158971b445511a00661183b37234c
+2 -1
View File
@@ -20,7 +20,7 @@ The invoking directory is the default workspace root. The `web`, `headless`, `sd
## App arguments
The launcher parses only its own flags and hands everything after them to the booted profile, where any injected app plugin may parse the shared immutable snapshot ([`dsh-cmdline`](../../packages/boot/cmdline/README.md)). Launcher flags therefore come first, and the first token the launcher does not recognize starts the app's arguments:
The launcher parses only its own flags and hands everything after them to the booted profile, where any injected app plugin may parse the shared immutable snapshot ([`dsh-cmdline`](../../packages/boot/cmdline/README.md)). The first token the launcher does not recognize starts the app's arguments:
```sh
dsh --profile web --port 8080 # --port belongs to the web app
@@ -30,6 +30,7 @@ dsh --profile web --help # the web app's flags, not the launcher's
dsh --help # the launcher's own help
```
<a id="profiles"></a>
## Profiles
A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list and `patchReload` lifecycle) and a `cordis.patch.yml` (the user's own patch layer). `patchReload: live` watches the profile and home-level patch files; `startup` applies them once.
+1 -2
View File
@@ -20,7 +20,7 @@
## 应用参数
启动器只解析自身的 flag,并将其后的所有内容交给已启动的 profile;注入该 profile 的任意应用插件都可以解析这份共享的不可变快照([`dsh-cmdline`](../../packages/boot/cmdline/README.zh.md))。因此,启动器的 flag 必须写在最前面;启动器无法识别的第一个 token 标志着应用参数的开始:
启动器只解析自身的 flag,并将其后的所有内容交给已启动的 profile;注入该 profile 的任意应用插件都可以解析这份共享的不可变快照([`dsh-cmdline`](../../packages/boot/cmdline/README.zh.md))。启动器无法识别的第一个 token 标志着应用参数的开始:
```sh
dsh --profile web --port 8080 # --port belongs to the web app
@@ -31,7 +31,6 @@ dsh --help # the launcher's own help
```
<a id="profiles"></a>
## Profile
profile 目录包含一个 `package.json`,其中记录树外插件依赖,以及 profile manifest(元数据清单)`dsh.profile`、其中按顺序排列的 `bundles` 列表与 `patchReload` 生命周期;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。`patchReload: live` 监视 profile 与 home 级 patch 文件,`startup` 则只应用一次。
+12
View File
@@ -56,6 +56,14 @@ flowchart LR
cfg --> plugin_dsh_base_session_query_sqlite
plugin_dsh_base_session_projection["session-projection<br/>@deepseek-ai/dsh-session-projection"]
cfg --> plugin_dsh_base_session_projection
plugin_dsh_base_storage["storage<br/>@deepseek-ai/dsh-storage"]
cfg --> plugin_dsh_base_storage
plugin_dsh_base_storage_json["storage-json<br/>@deepseek-ai/dsh-storage-json"]
cfg --> plugin_dsh_base_storage_json
plugin_dsh_base_storage_domain["storage-domain<br/>@deepseek-ai/dsh-storage-domain"]
cfg --> plugin_dsh_base_storage_domain
plugin_dsh_base_session_projection_cache["session-projection-cache<br/>@deepseek-ai/dsh-session-projection-cache"]
cfg --> plugin_dsh_base_session_projection_cache
plugin_dsh_base_session_telemetry_otel["session-telemetry-otel<br/>@deepseek-ai/dsh-session-telemetry-otel"]
cfg --> plugin_dsh_base_session_telemetry_otel
plugin_dsh_base_subprocess["subprocess<br/>@deepseek-ai/dsh-subprocess-local"]
@@ -200,6 +208,10 @@ flowchart LR
| `attachment-local` | `@deepseek-ai/dsh-attachment-local` |
| `session-query-sqlite` | `@deepseek-ai/dsh-session-query-sqlite` |
| `session-projection` | `@deepseek-ai/dsh-session-projection` |
| `storage` | `@deepseek-ai/dsh-storage` |
| `storage-json` | `@deepseek-ai/dsh-storage-json` |
| `storage-domain` | `@deepseek-ai/dsh-storage-domain` |
| `session-projection-cache` | `@deepseek-ai/dsh-session-projection-cache` |
| `session-telemetry-otel` | `@deepseek-ai/dsh-session-telemetry-otel` |
| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `sandbox` | `@deepseek-ai/dsh-sandbox-local` |
+1
View File
@@ -105,6 +105,7 @@
"@deepseek-ai/dsh-credentials-local": "workspace:^",
"@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^",
"@deepseek-ai/dsh-experimental-agent-team": "workspace:^",
"@deepseek-ai/dsh-experimental-agent-team-profile": "workspace:^",
"@deepseek-ai/dsh-experimental-tool-agent-team": "workspace:^",
"@deepseek-ai/dsh-fs-observation-policy": "workspace:^",
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
README.md: fe8d6ef0bb296f0807de4a3ec2756016bbb510c2
README.zh.md: e8c353f33bc9760fd6da74af33a85111cf9012aa
README.md: 0391cb18d89263d6b53319d8b125731178ac5132
README.zh.md: 13a4873bbd19401991d2a62f50602e133330aa35
+3 -2
View File
@@ -8,7 +8,7 @@ This reference defines the profile, web-alias, plugin-management, and config-dum
`dsh --profile <name>` boots the profile at `$DSH_HOME/profiles/<name>`. The effective tree is composed over an empty root by applying, in order: each bundle patch named in the profile manifest's `dsh.profile.bundles` list, the profile's own `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml` (machine-local preferences shared by every profile, so it outranks the per-profile layer), and each `--patch <path>` overlay in argv order. Later layers win per row; a patch replaces the targeted row's complete `config` value rather than deep-merging keys, and may insert new rows. `dsh.profile.patchReload` selects `live` patch-file watching or `startup` one-time loading; omission defaults a custom profile to `live`. A parse, schema, resolution, or plugin boot failure is reported and exits nonzero. SIGINT and SIGTERM dispose the mounted root before exit.
Bundle names resolve from the dsh installation first, then from the profile directory. In-box bundles (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`, `@deepseek-ai/dsh-sdk-app`, `@deepseek-ai/dsh-sdk-minimal`, `@deepseek-ai/dsh-acp-app`) therefore always come from the same installation as the running `dsh`; out-of-tree bundles come from the profile's pnpm-managed `node_modules`. A bare plugin `name` in any patch row resolves through the profile directory's Node parent walk, which reaches the maintained installation fallback `$DSH_HOME/profiles/node_modules`. Plain Node installations place one healed symlink there per dependency-closure package. A pkg executable instead places a real ESM proxy that mirrors explicit exports and re-exports the virtual package URL, because operating-system symlinks cannot enter pkg's `/snapshot` filesystem.
Bundle names resolve from the dsh installation first, then from the profile directory. In-box bundles (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`, `@deepseek-ai/dsh-sdk-app`, `@deepseek-ai/dsh-sdk-minimal`, `@deepseek-ai/dsh-acp-app`) therefore always come from the same installation as the running `dsh`; out-of-tree bundles come from the profile's pnpm-managed `node_modules`. A bare plugin `name` in any patch row resolves through the profile directory's Node parent walk, which reaches the maintained installation fallback `$DSH_HOME/profiles/node_modules`. Plain Node installations place one healed symlink there per dependency-closure package. A pkg executable instead places a real ESM proxy that mirrors explicit exports and re-exports the virtual package URL, because operating-system symlinks cannot enter pkg's `/snapshot` filesystem. Every launch also links packages carried only by selected external bundles through a dsh-owned directory into the current profile's `node_modules`; existing pnpm entries win, and each profile owns its links independently.
The `web`, `headless`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize from shipped templates on first use (`web`: base + web-app with live patches; `headless`: base + headless with startup-only patches; `sdk`: base + sdk-app with startup-only patches; `sdk-minimal`: its standalone bundle with startup-only patches; `acp`: base + acp-app with startup-only patches). Any other missing profile fails loud with a hint to run `dsh plugin --profile <name> add <package>`.
@@ -77,7 +77,7 @@ dsh web --dump-config
dsh web --help
```
The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default and, for a local launch, opens that canonical host URL only after the complete Loader tree settles. A non-empty inherited `SSH_CONNECTION` or `SSH_TTY` suppresses the browser handoff because the SSH client or editor owns the local forwarded address; the host URL is still printed. The CLI intentionally does not support `--host 0.0.0.0` yet and exits with a usage error. Immediately before a local handoff it prints `dsh web: opening the default browser; pass --no-open to disable`; if the operating-system handoff fails, a diagnostic on stderr states the reason, leaves the server running, and names the URL for manual use. `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence.
The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default and, for a local launch, opens that canonical host URL only after the complete Loader tree settles. A non-empty inherited `SSH_CONNECTION` or `SSH_TTY` suppresses the browser handoff because the SSH client or editor owns the local forwarded address; the host URL is still printed. The CLI intentionally does not support `--host 0.0.0.0` and exits with a usage error. Immediately before a local handoff it prints `dsh web: opening the default browser; pass --no-open to disable`; if the operating-system handoff fails, a diagnostic on stderr states the reason, leaves the server running, and names the URL for manual use. `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence.
Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain — `SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.
@@ -95,6 +95,7 @@ Session telemetry stays local by default. `DSH_TELEMETRY_MODE=FULL` streams ever
Install external plugin bundles through `dsh plugin --profile <name> add <package-or-git-spec>`. The installed package owns its dependencies and contributes its declared `cordis.patch.yml` layer. The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox.
<a id="source-execution"></a>
## Source execution
From the repository root, run `pnpm run build` separately after a fresh checkout and whenever artifacts need updating, then use `pnpm dsh <args...>`. The `package.json` script launches `apps/cli/src/bin.ts` with `node --import tsx/esm` without building and forwards every argument. Missing Typert host artifacts fail profile boot through module-resolution errors without a build instruction. Once those host artifacts exist, missing frontend or client-plugin bundles fail at startup with an instruction to run `pnpm run build`. The launcher does not check freshness, so existing stale bundles can run older browser code until rebuilt. The process inherits the launch environment; set `NODE_USE_ENV_PROXY=1` when a supporting Node version must honor `HTTP_PROXY` and `HTTPS_PROXY`. The installed form launches the built `apps/cli/lib/bin.js` without rebuilding the repository.
+2 -3
View File
@@ -8,7 +8,7 @@
`dsh --profile <name>` 启动位于 `$DSH_HOME/profiles/<name>` 的 profile。生效配置树以空根节点为起点,依次叠加 profile manifest(元数据清单)的 `dsh.profile.bundles` 列表中指定的各组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(这是各 profile 共享的机器本地偏好,因此优先于逐 profile 配置层),以及按 argv 顺序指定的各个 `--patch <path>` 覆盖层。对同一配置行,后应用的层优先。patch 会替换目标行的整个 `config` 值,而不是深度合并其中的键;patch 也可以插入新行。`dsh.profile.patchReload` 可选择 `live` patch 文件监视或 `startup` 单次加载;自定义 profile 省略该值时默认使用 `live`。配置解析、schema 校验、模块解析或插件启动失败时,系统会报告错误并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。
组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base``@deepseek-ai/dsh-web-app``@deepseek-ai/dsh-headless``@deepseek-ai/dsh-sdk-app``@deepseek-ai/dsh-sdk-minimal``@deepseek-ai/dsh-acp-app`)始终来自当前运行的 `dsh` 所属的安装;树外组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。普通 Node 安装会为依赖闭包中的每个包放置并修复一个符号链接。pkg 可执行程序则放置真实 ESM 代理,镜像显式 exports 并重新导出虚拟包 URL,因为操作系统符号链接无法进入 pkg 的 `/snapshot` 文件系统。
组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base``@deepseek-ai/dsh-web-app``@deepseek-ai/dsh-headless``@deepseek-ai/dsh-sdk-app``@deepseek-ai/dsh-sdk-minimal``@deepseek-ai/dsh-acp-app`)始终来自当前运行的 `dsh` 所属的安装;树外组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。普通 Node 安装会为依赖闭包中的每个包放置并修复一个符号链接。pkg 可执行程序则放置真实 ESM 代理,镜像显式 exports 并重新导出虚拟包 URL,因为操作系统符号链接无法进入 pkg 的 `/snapshot` 文件系统。每次启动还会把仅由所选外部 bundle 携带的包经 dsh 自有目录链接到当前 profile 的 `node_modules`;已有 pnpm 条目优先,且每个 profile 独立拥有自己的链接。
`web``headless``sdk``sdk-minimal``acp` profile 首次使用时会从随附模板自动初始化(`web`base + web-app,实时应用 patch`headless`base + headless,只在启动时应用 patch`sdk`base + sdk-app,只在启动时应用 patch`sdk-minimal`:独立组合包,只在启动时应用 patch;`acp`base + acp-app,只在启动时应用 patch)。其他缺失的 profile 会显式报错,并提示运行 `dsh plugin --profile <name> add <package>`
@@ -77,7 +77,7 @@ dsh web --dump-config
dsh web --help
```
生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`;本机启动时,只在完整 Loader 配置树结算后才用默认浏览器打开该规范宿主机 URL。继承的 `SSH_CONNECTION``SSH_TTY` 非空时会跳过浏览器交接,因为本地转发地址由 SSH 客户端或编辑器持有;宿主机 URL 仍会打印。CLI 目前有意不支持 `--host 0.0.0.0`,并会以用法错误退出。本机交接前会打印英文提示 `dsh web: opening the default browser; pass --no-open to disable`;若操作系统交接失败,stderr 诊断会说明原因、给出 URL 供手动访问,服务器仍继续运行。`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。
生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`;本机启动时,只在完整 Loader 配置树结算后才用默认浏览器打开该规范宿主机 URL。继承的 `SSH_CONNECTION``SSH_TTY` 非空时会跳过浏览器交接,因为本地转发地址由 SSH 客户端或编辑器持有;宿主机 URL 仍会打印。CLI 有意不支持 `--host 0.0.0.0`,并会以用法错误退出。本机交接前会打印英文提示 `dsh web: opening the default browser; pass --no-open to disable`;若操作系统交接失败,stderr 诊断会说明原因、给出 URL 供手动访问,服务器仍继续运行。`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。
进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT``SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。
@@ -96,7 +96,6 @@ dsh web --help
通过 `dsh plugin --profile <name> add <package-or-git-spec>` 安装外部插件组合包。安装的包拥有其依赖,并贡献其声明的 `cordis.patch.yml` 层。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。
<a id="source-execution"></a>
## 源码执行
请在仓库根目录中,于全新 checkout 之后及产物需要更新时单独运行 `pnpm run build`,然后使用 `pnpm dsh <args...>``package.json` 中的脚本不会构建,而是通过 `node --import tsx/esm` 启动 `apps/cli/src/bin.ts`,并转发所有参数。Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解析错误而失败。这些 Host 产物存在后,如果前端或 Client plugin 组合包缺失,启动会失败并提示运行 `pnpm run build`。启动器不会检查产物是否为最新,因此已有的陈旧组合包可能继续运行旧版浏览器代码,直至重新构建。该进程会继承启动环境;当支持环境代理的 Node 版本必须遵循 `HTTP_PROXY``HTTPS_PROXY` 时,请设置 `NODE_USE_ENV_PROXY=1`。安装形式会直接启动构建后的 `apps/cli/lib/bin.js`,不会重新构建仓库。
+1 -1
View File
@@ -157,8 +157,8 @@ async function composeProfile(
name: string,
patchFiles: readonly string[],
): Promise<ComposedProfile> {
await healProfilesModuleFallback(INSTALL_ANCHOR)
const profile = prepareProfile(name)
await healProfilesModuleFallback({ installAnchor: INSTALL_ANCHOR, profile })
const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? []
const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)))
const bundlePatches = profile.layers.flatMap(layer => layer.patches)
+114
View File
@@ -0,0 +1,114 @@
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it } from 'vitest'
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
const dshBinScript = fileURLToPath(new URL('../src/bin.ts', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
const fixturePlugin = pathToFileURL(fileURLToPath(
new URL('./profiles/headless/tests/fixtures/team-llm.mjs', import.meta.url),
)).href
function records(content: string): Record<string, unknown>[] {
return content.split('\n').filter(Boolean).map(line => JSON.parse(line) as Record<string, unknown>)
}
describe('dsh run with Agent Teams enabled', () => {
it('runs two teammates, durable peer mail, dependent tasks, waiting, and final aggregation', async () => {
const cwd = await mkdtemp(join(tmpdir(), 'dsh-agent-team-headless-'))
try {
const home = join(cwd, '.dsh')
const sessions = join(home, 'sessions')
const profileDir = join(home, 'profiles', 'headless')
await mkdir(profileDir, { recursive: true })
await writeFile(join(profileDir, 'package.json'), JSON.stringify({
name: 'dsh-profile-headless',
private: true,
dependencies: {
'@deepseek-ai/dsh-experimental-agent-team-profile': 'workspace:^',
},
dsh: {
profile: {
bundles: [
'@deepseek-ai/dsh-base',
'@deepseek-ai/dsh-headless',
'@deepseek-ai/dsh-experimental-agent-team-profile',
],
},
},
}, undefined, 2) + '\n')
await writeFile(join(profileDir, 'cordis.patch.yml'), [
'- id: llm-deepseek',
' disabled: true',
'- id: session-persistence-jsonl',
' config:',
` root: '${sessions}'`,
' compression: none',
'- insert:',
' - id: team-fixture-llm',
` name: '${fixturePlugin}'`,
'',
].join('\n'))
const launch = resolveExampleLaunch({
srcBin: dshBinScript,
configArgs: ['--profile', 'headless', '请明确使用 Agent Teams,把调研和实现拆给两个 teammate,等待完成后汇总。'],
tsconfigPath,
env: {
DSH_HOME: home,
DSH_AGENTS_HOME: join(cwd, '.agents'),
DSH_TELEMETRY_DISABLED: '1',
DEEPSEEK_API_KEY: '',
NODE_OPTIONS: [
process.env.NODE_OPTIONS,
'--disable-warning=ExperimentalWarning',
'--disable-warning=MODULE_TYPELESS_PACKAGE_JSON',
].filter(Boolean).join(' '),
},
})
const result = await execa(launch.command, launch.args, {
cwd,
env: launch.env,
input: '',
timeout: 90_000,
killSignal: 'SIGKILL',
reject: false,
})
expect(
result.exitCode,
`dsh headless profile exited unexpectedly.\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
).toBe(0)
expect(result.stderr).toBe('')
expect(result.stdout).toContain('TEAM_WORKFLOW_OK')
const files = (await readdir(sessions, { recursive: true }))
.filter(file => file.endsWith('.jsonl'))
expect(files).toHaveLength(3)
const logs = await Promise.all(files.map(file => readFile(join(sessions, file), 'utf8')))
const parsed = logs.map(records)
const root = parsed.find((log) => {
const header = log[0]
return header?.type === 'session' && typeof header.parentSession !== 'string'
})
expect(root).toBeDefined()
const eventTypes = root!.map(record => record.type)
expect(eventTypes.filter(type => type === 'team/member')).toHaveLength(4)
expect(eventTypes).toContain('team/message/queued')
expect(eventTypes).toContain('team/message/delivered')
const taskEvents = root!.filter(record => record.type === 'team/task')
expect(taskEvents.filter((record) => {
const data = record.data as { task?: { status?: string } } | undefined
return data?.task?.status === 'completed'
})).toHaveLength(2)
const toolNames = root!.filter(record => record.type === 'tool/call')
.map(record => (record.data as { name?: string } | undefined)?.name)
expect(toolNames).toContain('wait_agent')
expect(toolNames).toContain('team_task_list')
expect(toolNames).toContain('list_agents')
} finally {
await rm(cwd, { recursive: true, force: true })
}
}, 105_000)
})
@@ -10,6 +10,9 @@
- id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control'
disabled: true
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
disabled: true
- id: tool-subagent-report
name: '@deepseek-ai/dsh-tool-subagent-report'
disabled: true
@@ -26,11 +29,21 @@
provider: fork
toolName: subagent_fork
backgroundMode: one-shot
enableRunInBackground: false
maxDepth: 1
- insert:
- id: agent-team
name: '@deepseek-ai/dsh-experimental-agent-team'
config:
maxMembers: 8
maxTasks: 256
maxPendingMessagesPerMember: 64
maxMessageBytes: 65536
disposalTimeoutMs: 5000
- id: tool-agent-team
name: '@deepseek-ai/dsh-experimental-tool-agent-team'
config:
freshProvider: spawn
forkProvider: fork
- id: team-fixture-llm
name: './tests/fixtures/team-llm.mjs'
@@ -1,4 +1,4 @@
/** Deterministic keyless Agent Teams adapter for the real headless Loader snapshot. */
/** Deterministic keyless Agent Teams adapter shared by profile snapshot and CLI e2e. */
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
@@ -97,7 +97,7 @@ describe('session format guard through the assembled app', () => {
},
})
expect(result.stderr).toContain(
`session "${sessionId}" contains event type "future/event" (seq 2) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`,
`session "${sessionId}" contains event type "future/event" (seq 2) unknown to this harness; refusing to interpret the log — it was likely written by a newer harness`,
)
// macOS reports the temp dir via the /private symlink parent; assert the
// stable path suffix instead of the realpath-dependent prefix.
+1 -1
View File
@@ -108,7 +108,7 @@ async function bootWeb(
// upward walk. The flat fallback the preset boot maintains is what makes
// them resolvable — the same mechanism, not a test-only shim.
const home = dirname(settingsFile)
await healProfilesModuleFallback(INSTALL_ANCHOR, home)
await healProfilesModuleFallback({ installAnchor: INSTALL_ANCHOR, home })
const profileDir = join(home, 'profiles', 'spec')
await mkdir(profileDir, { recursive: true })
// Product Bundles are installed into the Profile, not the dsh app. Model
+4 -4
View File
@@ -15,7 +15,7 @@ import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionId,
SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionHeader, type SessionId,
} from '@deepseek-ai/dsh-session'
import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
import {
@@ -87,7 +87,7 @@ function seedLog(): string {
async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise<void> {
const childId = sessionId('agent-preset-selection-child')
const createdAt = 1784974100100
await scaffold.ctx.sessionPersistence.create({
const header: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: childId,
createdAt,
@@ -96,7 +96,8 @@ async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise
origin: 'subagent',
delegationDepth: 1,
agentPreset: 'minimal',
})
}
await scaffold.ctx.sessionPersistence.create(header)
await scaffold.ctx.sessionPersistence.append(childId, [
{
type: 'turn/start',
@@ -129,7 +130,6 @@ async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise
data: { turn: 1, reason: { kind: 'completed' } },
},
] as SessionEvent[])
await scaffold.ctx.sessionProjectionCache.coldSnapshot(childId)
}
/**
@@ -39,7 +39,12 @@
- button "System" [pressed]:
- img
- text: System
- text: Enter behavior while busy Busy only; Cmd/Ctrl+Enter uses the other behavior
- text: Font size Only affects conversation content 14
- button "Increase font size":
- img
- button "Decrease font size":
- img
- text: px Enter behavior while busy Busy only; Cmd/Ctrl+Enter uses the other behavior
- button "Queue":
- text: Queue
- img
@@ -39,7 +39,12 @@
- button "跟随系统" [pressed]:
- img
- text: 跟随系统
- text: 繁忙时 Enter 键行为 仅在智能体运行时生效;Cmd/Ctrl+Enter 使用另一行为
- text: 字号大小 仅影响会话内容的字号 14
- button "增大字号":
- img
- button "减小字号":
- img
- text: px 繁忙时 Enter 键行为 仅在智能体运行时生效;Cmd/Ctrl+Enter 使用另一行为
- button "排队发送":
- text: 排队发送
- img
+1 -1
View File
@@ -572,7 +572,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// The production module-resolution setup: an empty profile root inside the temp
// harness home, with bare plugin names resolving through the flat module
// fallback the launcher heals under <home>/profiles.
await healProfilesModuleFallback(INSTALL_ANCHOR, harnessHome)
await healProfilesModuleFallback({ installAnchor: INSTALL_ANCHOR, home: harnessHome })
const profileDir = join(harnessHome, 'profiles', 'scaffold')
await mkdir(profileDir, { recursive: true })
const rootConfig = join(profileDir, 'cordis.yml')
+45
View File
@@ -346,6 +346,51 @@ describe('web e2e: settings modal and General preferences', () => {
expect(tripwire.pageErrors).toEqual([])
}, 90_000)
it('steps the content font size, applies it to body, and persists across reload', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-font-size'))
const readFontSize = async (target: Page = page): Promise<string> => await target.evaluate(
() => document.body.style.getPropertyValue('--dsh-content-font-size'),
)
expect(await readFontSize()).toBe('14px')
await page.getByRole('button', { name: '设置', exact: true }).click()
const dialog = page.getByRole('dialog', { name: '设置' })
await dialog.waitFor({ timeout: 10_000 })
// The stepper reveals its arrows on hover; the up arrow steps 14 → 15 → 16.
await dialog.getByText('14', { exact: true }).hover()
const increase = dialog.getByRole('button', { name: '增大字号' })
await increase.click()
await dialog.getByText('15', { exact: true }).waitFor({ timeout: 5_000 })
await increase.click()
await dialog.getByText('16', { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('16px')
await expect.poll(async () => readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8'), { timeout: 5_000 })
.toMatch(/ui-theme:\n(?:\s+\w+: .*\n)*?\s+fontSize: 16/)
await page.keyboard.press('Escape')
// Reload: the boot script embeds the durable size and ThemeRuntime seeds
// its initial snapshot from the boot-written body variable, so activation
// never flashes the default while the settings read is in flight.
const warningStart = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, warningStart)
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('16px')
// Restore the default for the specs that follow (and the dialog golden).
await page.getByRole('button', { name: '设置', exact: true }).click()
const restored = page.getByRole('dialog', { name: '设置' })
await restored.waitFor({ timeout: 10_000 })
await restored.getByText('16', { exact: true }).hover()
const decrease = restored.getByRole('button', { name: '减小字号' })
await decrease.click()
await restored.getByText('15', { exact: true }).waitFor({ timeout: 5_000 })
await decrease.click()
await restored.getByText('14', { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('14px')
await page.keyboard.press('Escape')
expect(tripwire.pageErrors).toEqual([])
}, 90_000)
it('persists the busy-state Enter behavior across reload and a distinct port', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-enter-behavior'))
await page.getByRole('button', { name: '设置', exact: true }).click()
+29 -11
View File
@@ -6,7 +6,7 @@ import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionId,
SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionHeader, type SessionId,
} from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-agent'
import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
@@ -58,6 +58,18 @@ async function waitForAgentToSettle(scaffold: WebScaffold, id: SessionId): Promi
}
}
/** Poll until the cold-read write-back of {@link coldSnapshot} lands a visible row. */
async function waitForCacheRow(
scaffold: WebScaffold,
header: SessionHeader,
): Promise<void> {
const deadline = Date.now() + 10_000
while (scaffold.ctx.sessionProjectionCache.cachedSnapshot(header) === undefined) {
if (Date.now() >= deadline) throw new Error(`cache row for "${header.id}" did not land`)
await new Promise<void>(resolve => setTimeout(resolve, 10))
}
}
describe('web e2e: persisted subagent conversation and human continuation', () => {
let scaffold: WebScaffold
let browser: Browser
@@ -114,7 +126,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
oneShotId = sessionId('recorded-one-shot')
const oneShotDurationMs = 192 * 24 * 60 * 60 * 1_000
const oneShotAt = Date.now() - oneShotDurationMs
await scaffold.ctx.sessionPersistence.create({
const oneShotHeader: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: oneShotId,
createdAt: oneShotAt,
@@ -122,8 +134,9 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
parentSession: parent.id,
origin: 'subagent',
delegationDepth: 1,
})
await scaffold.ctx.sessionPersistence.append(oneShotId, [
}
await scaffold.ctx.sessionPersistence.create(oneShotHeader)
const oneShotEvents = [
{
type: 'turn/start',
seq: 0,
@@ -154,11 +167,13 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
time: oneShotAt + oneShotDurationMs,
data: { turn: 1, reason: { kind: 'completed' } },
},
] as SessionEvent[])
await scaffold.ctx.sessionProjectionCache.coldSnapshot(oneShotId)
] as SessionEvent[]
await scaffold.ctx.sessionPersistence.append(oneShotId, oneShotEvents)
scaffold.ctx.sessionProjectionCache.coldSnapshot(oneShotHeader, oneShotEvents)
await waitForCacheRow(scaffold, oneShotHeader)
grandchildId = sessionId('recorded-grandchild')
const authoredAt = Date.now()
await scaffold.ctx.sessionPersistence.create({
const grandchildHeader: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: grandchildId,
createdAt: authoredAt,
@@ -166,8 +181,9 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
parentSession: childId,
origin: 'subagent',
delegationDepth: 2,
})
await scaffold.ctx.sessionPersistence.append(grandchildId, [
}
await scaffold.ctx.sessionPersistence.create(grandchildHeader)
const grandchildEvents = [
{
type: 'turn/start',
seq: 0,
@@ -198,8 +214,10 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
time: authoredAt + 3,
data: { turn: 1, reason: { kind: 'completed' } },
},
] as SessionEvent[])
await scaffold.ctx.sessionProjectionCache.coldSnapshot(grandchildId)
] as SessionEvent[]
await scaffold.ctx.sessionPersistence.append(grandchildId, grandchildEvents)
scaffold.ctx.sessionProjectionCache.coldSnapshot(grandchildHeader, grandchildEvents)
await waitForCacheRow(scaffold, grandchildHeader)
expect(scaffold.ctx.agents.get(childId)).toBeUndefined()
expect(scaffold.ctx.agents.get(oneShotId)).toBeUndefined()
expect(scaffold.ctx.agents.get(grandchildId)).toBeUndefined()
+3 -3
View File
@@ -1,6 +1,6 @@
# AGENTS.md — The documentation standard
This file defines document structure, Markdown tiers, writing rules, and `verify-doc-budgets` ceilings. Use [dsh-doc-standards](../.agents/skills/dsh-doc-standards/SKILL.md) for placement and validation, and [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for required coverage and editorial judgment; the [doc-tiers Agent Note](../.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md) owns rationale.
This file defines document structure, Markdown tiers, writing rules, and `verify-doc-budgets` ceilings. Use [dsh-doc](../.agents/skills/dsh-doc/SKILL.md) for placement and validation, and [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for required coverage and editorial judgment; the [doc-tiers Agent Note](../.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md) owns rationale.
## Document structure
@@ -54,11 +54,11 @@ When the gate goes red:
2. **Condense** content that belongs here but can be shorter.
3. **Raise** the ceiling only when the words need the space; justify the manifest diff in the PR. A too-low ceiling is a budget bug.
Ceilings are guardrails, not reduction targets. At or below target, retain at least 5% headroom; above target, freeze the ceiling until relocation or condensation brings the document under target. Lower a ceiling only when the document still has room, and raise it when content would otherwise be deleted. Targets: root `AGENTS.md` ≤ 1,600 words; `architecture.md`1,800; subtree `AGENTS.md` ≤ 600, except `packages/AGENTS.md` ≤ 650 and this file ≤ 1,250; `packages/README.md`600. Review governs unbudgeted tiers.
Ceilings are guardrails, not reduction targets. At or below target, retain at least 5% headroom; above target, freeze the ceiling until relocation or condensation brings the document under target. Lower a ceiling only when the document still has room. Targets: root `AGENTS.md` ≤ 1,950; `architecture.md`2,400; subtree `AGENTS.md` ≤ 600, except `packages/AGENTS.md` ≤ 675 and this file ≤ 1,320; `packages/README.md`994; plus `cordis-primer.md` 600, `defensive-patterns.md` 550, `testing.md` 1,150, `examples/AGENTS.md` 310. Review governs unbudgeted tiers.
## The slop checklist
Hunt these in any doc; [dsh-doc-standards](../.agents/skills/dsh-doc-standards/SKILL.md) runs this list as an audit:
Hunt these in any doc; [dsh-doc](../.agents/skills/dsh-doc/SKILL.md) runs this list as an audit:
- The same rule stated in more than one home. Grep a distinctive phrase; keep one home and link the rest.
- Narrated history or war stories: "previously", "now", "no longer", "used to", "renamed", "was moved", PRs, or commits. State the current fact; link an Agent Note or postmortem when needed.
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/api-gateway.md
api-gateway.md: 60b9893675ad965c3f88677eac32352acfffeb31
api-gateway.zh.md: fc217ce3a976fd8cf045848aa331c2115c3a4d65
api-gateway.md: 86c43ccc5719107ded1e4004d6fabbc32b760520
api-gateway.zh.md: 6fa170b84129eb96a70d0831158a9f9c43db256c
+4 -4
View File
@@ -84,7 +84,7 @@ The `api-remotes` assembly and the `ctx.remote` contract are React-independent;
| Shared | `@deepseek-ai/dsh-typert-protocol` | Declares decorators, Gateway bindings, merge-extensible protocol maps, invocation descriptors, and provider types; starts no TypeScript analysis and registers no Cordis services |
| Build | `@deepseek-ai/dsh-typert-generator` | Strictly analyzes Remote signatures, the type graph, lookups, Contexts, and source locations from the Host `ts.Program`, then generates Host and Host-for-Client artifacts |
| Host | `@deepseek-ai/dsh-typert-registry` and Loader | Places generated Host descriptors, schemas, and business-package registrations in `ctx.typert`, and holds lookup and Context providers |
| Host | `@deepseek-ai/dsh-api-remotes` | Owns the application Agent/Session identity policy and configures the corresponding Typert lookups |
| Host | `@deepseek-ai/dsh-api-session-controller` | Owns the application Agent/Session identity policy and configures the corresponding Typert lookups |
| Host | `@deepseek-ai/dsh-api-gateway` | Provides `ctx.typertGateway`, claims Remote endpoints, resolves objects or Contexts, invokes live Cordis services, and validates request and return values |
| Client | `@deepseek-ai/dsh-api-gateway/client` | Provides `ctx.remote` and `remote.<namespace>` child Services, mounts generated descriptors as concrete methods, and initiates, validates, and cancels calls through the Connection |
| Client | `@deepseek-ai/dsh-api-remotes/client` | Explicitly selects and mounts the `/remote` contributions allowed by the application and brings the corresponding declaration merges into business code |
@@ -98,7 +98,7 @@ The root build runs `build:lib:host`, `build:lib:client`, and `build:web` in ord
Both tsdown passes receive the complete workspace and bundle only JavaScript emitted to `lib/types` by the corresponding tsc phase. The root config does not scan Client artifacts, classify package names, or pass a maintained filter to tsdown; package-local configs return entries for the current phase based on `DSH_BUILD_FACE`. An ordinary Client plugin produces both its Node loader entry and browser bundle during the Client phase.
`api-remotes` is the only package with split TypeScript faces. Its Host project owns the Agent/Session lookup policy, while its Client project depends on `/remote` declarations generated for business packages during Host tsdown; root aggregates and direct consumers must reference `api/remotes/tsconfig.host.json` or `api/remotes/tsconfig.client.json` respectively. The package's `clientBundle(..., { hostPhase: true })` produces its Host entry during Host tsdown and leaves only the browser entry for Client tsdown. Every other package remains registered in one aggregate.
`api/remotes`, `api/gateway`, `api/session-controller`, and `api/workspace-controller` (plus `client/connection`) split TypeScript faces. `api/remotes`' Client project depends on `/remote` declarations generated for business packages during Host tsdown; root aggregates and direct consumers must reference each split package's `tsconfig.host.json` or `tsconfig.client.json` respectively. `api-remotes`' `clientBundle(..., { hostPhase: true })` produces its Host entry during Host tsdown and leaves only the browser entry for Client tsdown. The Agent/Session lookup policy lives in `@deepseek-ai/dsh-api-session-controller`, not in `api-remotes`.
Each contributing business package writes generated files to its own `lib/` directory, not to its source directory:
@@ -120,11 +120,11 @@ Strict analysis requires a Remote to be a public, non-static instance method wit
Remote and API Proxy share the Connection's `/api` route. The Client Remote calls `connection.rpc.call('/api', '<namespace>/<method>', { args }, signal)`; the HTTP carrier maps this to `POST /api/<namespace>/<method>`, with a payload containing only a named `args` object.
The Connection performs the unified trust check for `/api` before the HTTP bridge, then dispatches inside the shared FetchHandler in interceptor order. The Typert Gateway claims only two-segment endpoints that have a strict descriptor or active SRC marker; unclaimed requests fall back to the existing API Proxy. The Connection owns transport, RPC ids, response envelopes, and request cancellation, while the Gateway owns only the Remote data protocol and business dispatch. Replacing the Connection carrier in the future does not require changes to Remote descriptors or the Client programming interface.
The Connection performs the unified trust check for `/api` before the HTTP bridge, then dispatches inside the shared FetchHandler in interceptor order. The Typert Gateway claims only two-segment endpoints that have a strict descriptor or active SRC marker; unclaimed requests fall back to the existing API Proxy. The Connection owns transport, RPC ids, response envelopes, and request cancellation, while the Gateway owns only the Remote data protocol and business dispatch. Replacing the Connection carrier does not require changes to Remote descriptors or the Client programming interface.
For every call, the Gateway resolves the descriptor and live service from the current registries instead of caching business objects. It requires the fields in `args` to match the descriptor exactly, validates wire values with codecs, resolves objects or receivers through registered lookup or Context providers, invokes the service method targeted by the binding, and validates the return value. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails before entering or after leaving business code.
The lookup provider's `register()` supplies both the stable declaration and the default resolver; `configure()` supplies a resolver owned by Host composition that may execute asynchronously and is scoped to an effect lifetime. Configuration may precede provider mounting; without a provider, invocation still fails with `lookup-unavailable`, and unloading the configuration restores the provider's default policy. API Remotes owns the standard `agentFor()` semantics for `agent` and `session`: it reuses a live Agent, automatically resumes ordinary cold sessions, deduplicates concurrent resumes, and rejects identities owned by subagent routing; the `session` lookup returns that Agent's Session. The Web API Proxy supplies its Agent defaults and scope setup, then consumes the same resolver for legacy methods. Resume failures and ownership fences pass through unchanged as existing RPC errors rather than being collapsed into the Gateway's `internal` error.
The lookup provider's `register()` supplies both the stable declaration and the default resolver; `configure()` supplies a resolver owned by Host composition that may execute asynchronously and is scoped to an effect lifetime. Configuration may precede provider mounting; without a provider, invocation still fails with `lookup-unavailable`, and unloading the configuration restores the provider's default policy. The Session Controller owns the standard `agentFor()` semantics for `agent` and `session`: it reuses a live Agent, automatically resumes ordinary cold sessions, deduplicates concurrent resumes, and rejects identities owned by subagent routing; the `session` lookup returns that Agent's Session. The Web API Proxy supplies its Agent defaults and scope setup, then consumes the same resolver for legacy methods. Resume failures and ownership fences pass through unchanged as existing RPC errors rather than being collapsed into the Gateway's `internal` error.
Unloading a Client contribution removes its descriptors and concrete methods together, aborts its in-flight calls, and makes stale method handles retained by external code reject further calls. A strict endpoint withdrawn on the Host also does not degrade to SRC inference, preventing a hot unload from silently weakening validation.
+4 -4
View File
@@ -84,7 +84,7 @@ Client 应用只装配 `@deepseek-ai/dsh-api-remotes`。该包以运行时值导
| 共享 | `@deepseek-ai/dsh-typert-protocol` | 声明 decorator、Gateway binding、可合并协议映射、调用描述符及提供方类型;不启动 TypeScript 分析,也不注册 Cordis 服务 |
| 构建 | `@deepseek-ai/dsh-typert-generator` | 从 Host `ts.Program` 严格分析 Remote 签名、类型图、lookup、Context 与源码位置,并生成 Host 和 Host-for-Client 产物 |
| Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 |
| Host | `@deepseek-ai/dsh-api-remotes` | 负责应用的 Agent/Session 身份策略,并配置对应的 Typert lookup |
| Host | `@deepseek-ai/dsh-api-session-controller` | 负责应用的 Agent/Session 身份策略,并配置对应的 Typert lookup |
| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis 服务,并校验请求值和返回值 |
| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.remote``remote.<namespace>` 子服务,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 |
| Client | `@deepseek-ai/dsh-api-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 |
@@ -98,7 +98,7 @@ API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对
两次 tsdown 都接收完整 workspace,且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按包名分类,也不向 tsdown 传维护式 filter;各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client 插件在 Client 阶段一起生成 Node loader 入口与 browser bundle。
`api-remotes` 是唯一拆分 TypeScript face 的包特例。它的 Host project 负责 Agent/Session lookup 策略,Client project 依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用 `api/remotes/tsconfig.host.json``api/remotes/tsconfig.client.json`包内 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。其他包仍只登记在一个 aggregate 中
`api/remotes``api/gateway``api/session-controller``api/workspace-controller`(外加 `client/connection`)都拆分 TypeScript face。`api/remotes`Client project 依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用各拆分包自己的 `tsconfig.host.json``tsconfig.client.json``api-remotes` `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。Agent/Session lookup 策略位于 `@deepseek-ai/dsh-api-session-controller`,而非 `api-remotes`
每个贡献业务包把生成文件写入自己的 `lib/`,而不是源码目录:
@@ -120,11 +120,11 @@ Remote Client 声明中的参数名来自 wire 字段,参数和返回类型则
Remote 与 API Proxy 共用 Connection 的 `/api` 路由。Client Remote 调用 `connection.rpc.call('/api', '<namespace>/<method>', { args }, signal)`HTTP carrier 对应 `POST /api/<namespace>/<method>`payload 只包含一个具名 `args` 对象。
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。Typert Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和请求取消,Gateway 只拥有 Remote 数据协议和业务分发。未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程接口。
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。Typert Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和请求取消,Gateway 只拥有 Remote 数据协议和业务分发。替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程接口。
Gateway 每次调用都从当前注册表解析描述符和实时服务,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context 提供方解析对象或接收者,最后调用 binding 指向的服务方法并校验返回值。缺少提供方、identity 未命中、binding 不一致、参数缺失或多余、schema 失败和方法不存在都会在进入业务代码前或离开业务代码后失败。
lookup 提供方的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于提供方挂载;没有提供方时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复提供方默认策略。API Remotes 负责 `agent``session` 的标准 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。
lookup 提供方的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于提供方挂载;没有提供方时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复提供方默认策略。Session Controller 负责 `agent``session` 的标准 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。
Client 卸载一个贡献时会一起移除描述符和具体方法,中止其进行中的调用,并使外部仍持有的陈旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/architecture.md
architecture.md: c6e01b8c30486d292694cbc26836e83522e3e760
architecture.zh.md: 21d60d0c962097ee6853bf7a3831a2c0b727e9c9
architecture.md: 20d03c079fa8e1f73f733992b6e938f0f539a60f
architecture.zh.md: 5448036ad6e11902e32f89d0a65be99230e27e02
+2 -2
View File
@@ -8,7 +8,7 @@ We recommend using an agent to explore the codebase and understand its architect
## Cordis
[Cordis](cordis-primer.md) is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration.
[Cordis](cordis-primer.md) is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so each is replaceable from configuration.
There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.
@@ -28,7 +28,7 @@ Layers apply to an empty entry list in this order: each bundle in the profile's
Custom profiles default to live patch reload. The shipped `web` profile is live; `headless`, `sdk`, `sdk-minimal`, and `acp` apply all layers once at startup because replacing a one-shot or stdio application's dependencies after it owns work would invalidate that lifecycle.
To see the tree your machine actually boots:
To see the tree your machine boots:
```sh
dsh --profile web --dump-config
+2 -2
View File
@@ -8,7 +8,7 @@
## Cordis
[Cordis](cordis-primer.zh.md) 是 dsh 底层的框架:插件向共享上下文贡献服务、类型化事件和可逆的副作用。产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身,因此每一部分都可以从配置替换。
[Cordis](cordis-primer.zh.md) 是 dsh 底层的框架:插件向共享上下文贡献服务、类型化事件和可逆的副作用。产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身,因此每都可以从配置替换。
不存在需要打补丁的特权内核:扩展 dsh 的方式是把插件挂载到其他插件旁边,而各项注册都是副作用,会在其插件卸载时撤销。
@@ -28,7 +28,7 @@
自定义 profile 默认实时重载 patch。随附的 `web` profile 使用实时重载;`headless``sdk``sdk-minimal``acp` 则只在启动时应用一次所有配置层,因为一次性应用或 stdio 应用拥有工作之后,替换其依赖会破坏该生命周期。
要查看你的机器实际启动的配置树:
要查看你的机器启动的配置树:
```sh
dsh --profile web --dump-config
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/capability-seams.md
capability-seams.md: 0994b186f7daa6afbff0f1484216e55fc79170ff
capability-seams.zh.md: 48aa0a5353bba8d56f63abfcfc4cd2c601569cb6
capability-seams.md: d86a05e240cd64a0c8a172b00fe9e1df809fe408
capability-seams.zh.md: 1050191d696fd5a46aa28101beef118b07c29ed2
+60 -59
View File
@@ -10,11 +10,13 @@ flowchart LR
pkg_attachment["attachment"]
svc_attachments["ctx.attachments<br/>Durable binary attachment storage"]
pkg_attachment_local["attachment-local"]
pkg_host_runtime["host-runtime"]
pkg_api_session_controller["api-session-controller"]
pkg_host_apiproxy["host-apiproxy"]
pkg_tool_fs["tool-fs"]
pkg_llm_pi_ai["llm-pi-ai"]
pkg_llm_deepseek["llm-deepseek"]
pkg_llm["llm"]
svc_llm["ctx.llm<br/>LLM adapter registry"]
pkg_llm_deepseek["llm-deepseek"]
pkg_llm_replay["llm-replay"]
pkg_agent_loop["agent-loop"]
pkg_compaction_basic["compaction-basic"]
@@ -32,12 +34,10 @@ flowchart LR
pkg_session_persistence["session-persistence"]
pkg_session_query["session-query"]
pkg_session_query_sqlite["session-query-sqlite"]
pkg_subagent_inprocess["subagent-inprocess"]
pkg_subagent_in_process_driver["subagent-in-process-driver"]
pkg_invariants["invariants"]
pkg_message_feedback["message-feedback"]
pkg_api_session_controller["api-session-controller"]
svc_sessionController["ctx.sessionController<br/>Host Session Remote controller"]
pkg_apiproxy["apiproxy"]
pkg_api_workspace_controller["api-workspace-controller"]
svc_workspaceController["ctx.workspaceController<br/>Host Workspace Remote controller"]
svc_invariants["ctx.invariants<br/>Package-owned invariant registry"]
@@ -89,7 +89,6 @@ flowchart LR
pkg_system_prompt["system-prompt"]
svc_systemPrompt["ctx.systemPrompt<br/>System prompt assembly registry"]
pkg_tools["tools"]
pkg_tool_fs["tool-fs"]
pkg_tool_terminal["tool-terminal"]
pkg_tool_web["tool-web"]
svc_tools["ctx.tools<br/>Tool registry and guarded execution pipeline"]
@@ -107,7 +106,6 @@ flowchart LR
svc_commands["ctx.commands<br/>Human command registry"]
pkg_session_projection["session-projection"]
svc_sessionProjections["ctx.sessionProjections<br/>Session projection units"]
pkg_host_apiproxy["host-apiproxy"]
pkg_session_projection_cache["session-projection-cache"]
svc_sessionProjectionCache["ctx.sessionProjectionCache<br/>Persisted projection cache"]
pkg_skill["skill"]
@@ -151,13 +149,13 @@ flowchart LR
pkg_sandbox_policy["sandbox-policy"]
svc_sandboxPolicy["ctx.sandboxPolicy<br/>Sandbox policy home"]
pkg_fs_sandbox["fs-sandbox"]
pkg_approval["approval"]
pkg_user_approval["user-approval"]
svc_approval["ctx.approval<br/>Approval seam"]
pkg_permission_presets["permission-presets"]
svc_permissionPresets["ctx.permissionPresets<br/>Permission presets"]
pkg_code_runtime["code-runtime"]
svc_codeRuntime["ctx.codeRuntime<br/>Code-execution seam"]
pkg_code_runtime_worker["code-runtime-worker"]
pkg_code_runtime_worker_thread["code-runtime-worker-thread"]
pkg_fs["fs"]
svc_fs["ctx.fs<br/>Filesystem provider seam"]
pkg_fs_local["fs-local"]
@@ -171,9 +169,9 @@ flowchart LR
pkg_subagent_dsh_sdk["subagent-dsh-sdk"]
pkg_tool_subagent_control["tool-subagent-control"]
pkg_tool_ralph["tool-ralph"]
pkg_agent_team["agent-team"]
pkg_experimental_agent_team["experimental-agent-team"]
svc_agentTeams["ctx.agentTeams<br/>Agent Teams coordination domain"]
pkg_tool_agent_team["tool-agent-team"]
pkg_experimental_tool_agent_team["experimental-tool-agent-team"]
pkg_jobs["jobs"]
svc_jobs["ctx.jobs<br/>Background job registry"]
pkg_jobs_local["jobs-local"]
@@ -188,15 +186,15 @@ flowchart LR
svc_spillStore["ctx.spillStore<br/>Spill storage seam"]
pkg_spill_local["spill-local"]
pkg_spill_policy["spill-policy"]
pkg_directory_picker["directory-picker"]
pkg_host_directory_picker["host-directory-picker"]
svc_directoryPicker["ctx.directoryPicker<br/>Workspace-directory picking seam"]
pkg_directory_picker_native["directory-picker-native"]
pkg_directory_picker_browse["directory-picker-browse"]
pkg_webserver["webserver"]
pkg_host_directory_picker_native["host-directory-picker-native"]
pkg_host_directory_picker_browse["host-directory-picker-browse"]
pkg_host_webserver["host-webserver"]
svc_webServer["ctx.webServer<br/>HTTP route registration"]
pkg_connection["connection"]
pkg_modules["modules"]
pkg_hmr["hmr"]
pkg_client_connection["client-connection"]
pkg_client_modules["client-modules"]
pkg_client_hmr["client-hmr"]
svc_clientModules["ctx.clientModules<br/>Client plugin graph host"]
pkg_workflow["workflow"]
svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
@@ -207,30 +205,26 @@ flowchart LR
pkg_webhook_github["webhook-github"]
pkg_lsp["lsp"]
svc_lsp["ctx.lsp<br/>Language-server navigation seam"]
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
pkg_acp --> svc_approval
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_agent_team --> svc_agentTeams
pkg_api_gateway --> svc_typertGateway
pkg_api_session_controller --> svc_sessionController
pkg_api_workspace_controller --> svc_workspaceController
pkg_apiproxy --> svc_apiProxy
pkg_approval --> svc_approval
pkg_attachment --> svc_attachments
pkg_attachment_local --> svc_attachments
pkg_authorization --> svc_authorization
pkg_bash_local --> svc_shell
pkg_bash_sandbox --> svc_shell
pkg_client_modules --> svc_clientModules
pkg_code_runtime --> svc_codeRuntime
pkg_code_runtime_worker --> svc_codeRuntime
pkg_code_runtime_worker_thread --> svc_codeRuntime
pkg_commands --> svc_commands
pkg_compaction --> svc_compaction
pkg_compaction_basic --> svc_compaction
@@ -240,10 +234,8 @@ flowchart LR
pkg_credentials --> svc_credentials
pkg_credentials_local --> svc_credentials
pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions
pkg_directory_picker --> svc_directoryPicker
pkg_directory_picker_browse --> svc_directoryPicker
pkg_directory_picker_native --> svc_directoryPicker
pkg_e2b --> svc_e2b
pkg_experimental_agent_team --> svc_agentTeams
pkg_file_reference --> svc_fileReferences
pkg_file_reference_local --> svc_fileReferences
pkg_fs --> svc_fs
@@ -251,6 +243,11 @@ flowchart LR
pkg_fs_local --> svc_fs
pkg_fs_sandbox --> svc_fs
pkg_goal --> svc_goals
pkg_host_apiproxy --> svc_apiProxy
pkg_host_directory_picker --> svc_directoryPicker
pkg_host_directory_picker_browse --> svc_directoryPicker
pkg_host_directory_picker_native --> svc_directoryPicker
pkg_host_webserver --> svc_webServer
pkg_invariants --> svc_invariants
pkg_jobs --> svc_jobs
pkg_jobs_local --> svc_jobs
@@ -259,9 +256,8 @@ flowchart LR
pkg_llm_pi_ai --> svc_llm
pkg_llm_replay --> svc_llm
pkg_lsp --> svc_lsp
pkg_lsp_local --> svc_lsp
pkg_lsp_stdio --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_permission_presets --> svc_permissionPresets
pkg_plan_mode --> svc_planMode
pkg_plugin_package_inventory_deepseek --> svc_deepseekLlmApiExtensions
@@ -314,6 +310,7 @@ flowchart LR
pkg_tool_subagent --> svc_subagentModelSelection
pkg_tools --> svc_tools
pkg_typert_registry --> svc_typert
pkg_user_approval --> svc_approval
pkg_user_questions --> svc_userQuestions
pkg_web --> svc_web
pkg_web_fetch_http --> svc_web
@@ -321,32 +318,35 @@ flowchart LR
pkg_web_search_exa --> svc_web
pkg_web_search_perplexity --> svc_web
pkg_webhook --> svc_webhookRuntime
pkg_webserver --> svc_webServer
pkg_workflow --> svc_workflowEngine
pkg_workflow_worker_thread --> svc_workflowEngine
pkg_workspace --> svc_workspaceRegistry
svc_agentDefaultModel --> pkg_headless
svc_agentDefaultModel --> pkg_host_apiproxy
svc_agentLoop --> pkg_agent_spine_demo
svc_agentTeams --> pkg_tool_agent_team
svc_agentTeams --> pkg_experimental_tool_agent_team
svc_agents --> pkg_acp
svc_agents --> pkg_agent_loop
svc_agents --> pkg_subagent_inprocess
svc_apiProxy --> pkg_connection
svc_agents --> pkg_subagent_in_process_driver
svc_apiProxy --> pkg_client_connection
svc_approval --> pkg_acp
svc_approval --> pkg_tool_bash
svc_approval --> pkg_tools
svc_attachments --> pkg_host_runtime
svc_attachments --> pkg_api_session_controller
svc_attachments --> pkg_host_apiproxy
svc_attachments --> pkg_llm_deepseek
svc_attachments --> pkg_llm_pi_ai
svc_attachments --> pkg_tool_fs
svc_authorization --> pkg_llm_pi_ai
svc_clientModules --> pkg_hmr
svc_clientModules --> pkg_client_hmr
svc_codeRuntime --> pkg_tools
svc_compaction --> pkg_compaction_basic
svc_cordisInspect --> pkg_tool_cordis
svc_credentials --> pkg_apiproxy
svc_credentials --> pkg_host_apiproxy
svc_credentials --> pkg_llm_deepseek
svc_credentials --> pkg_llm_pi_ai
svc_deepseekLlmApiExtensions --> pkg_llm_deepseek
svc_directoryPicker --> pkg_apiproxy
svc_directoryPicker --> pkg_host_apiproxy
svc_dynamicCordisRunner --> pkg_tool_cordis
svc_e2b --> pkg_fs_e2b
svc_e2b --> pkg_subprocess_e2b
@@ -367,7 +367,7 @@ flowchart LR
svc_sandboxPolicy --> pkg_bash_sandbox
svc_sandboxPolicy --> pkg_fs_sandbox
svc_sandboxPolicy --> pkg_terminal_bash
svc_sessionController --> pkg_apiproxy
svc_sessionController --> pkg_host_apiproxy
svc_sessionPersistence --> pkg_agent_loop
svc_sessionPersistence --> pkg_hooks_claude_code
svc_sessionPersistence --> pkg_hooks_codex
@@ -388,8 +388,8 @@ flowchart LR
svc_sessions --> pkg_session_persistence
svc_sessions --> pkg_session_query
svc_sessions --> pkg_session_query_sqlite
svc_sessions --> pkg_subagent_inprocess
svc_settings --> pkg_apiproxy
svc_sessions --> pkg_subagent_in_process_driver
svc_settings --> pkg_host_apiproxy
svc_settings --> pkg_llm_deepseek
svc_settings --> pkg_llm_pi_ai
svc_shell --> pkg_hooks_claude_code
@@ -436,39 +436,40 @@ flowchart LR
svc_typert --> pkg_typert_loader
svc_userQuestions --> pkg_tool_ask_user
svc_web --> pkg_tool_web
svc_webServer --> pkg_connection
svc_webServer --> pkg_hmr
svc_webServer --> pkg_modules
svc_webServer --> pkg_client_connection
svc_webServer --> pkg_client_hmr
svc_webServer --> pkg_client_modules
svc_webhookRuntime --> pkg_webhook_github
svc_workflowEngine --> pkg_tool_ralph
svc_workflowEngine --> pkg_tool_workflow
svc_workspaceRegistry --> pkg_apiproxy
svc_workspaceRegistry --> pkg_api_session_controller
svc_workspaceRegistry --> pkg_api_workspace_controller
svc_fs -. event gate .-> pkg_fs_observation_policy
```
| ctx key | Role | Owner | Implementations | Direct consumers | Companion plugins | Note |
| --- | --- | --- | --- | --- | --- | --- |
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | `host-runtime`, [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | The host commits accepted images before session events; provider adapters resolve authorized durable references into provider-native content. |
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | [`api-session-controller`](../packages/api/session-controller), [`host-apiproxy`](../packages/host/apiproxy), [`tool-fs`](../packages/fs/tool-fs), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | The host commits accepted images before session events; provider adapters resolve authorized durable references into provider-native content. |
| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. |
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance. |
| `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. |
| `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. |
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), `subagent-inprocess`, [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. |
| `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | `apiproxy` | - | Owns Session commands, cold reads, durable-event following, live control state, and Agent activation policy; apiProxy reuses its inspection and Agent-resolution operations for Session-aware domains. |
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. |
| `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Owns Session commands, cold reads, durable-event following, live control state, and Agent activation policy; apiProxy reuses its inspection and Agent-resolution operations for Session-aware domains. |
| `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Owns Workspace commands and reconnect-safe Workspace state delivery through the generated Remote namespace. |
| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. |
| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. |
| `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. |
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. |
| `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-file`](../packages/settings/settings-file) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer. |
| `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-file`](../packages/settings/settings-file) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`host-apiproxy`](../packages/host/apiproxy) | - | Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer. |
| `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Owns the default-off settings namespace that Agent-scoped delegation tools sample when composing a new top-level Session. |
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage. |
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`host-apiproxy`](../packages/host/apiproxy) | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage. |
| `ctx.authorization` | `seam` | [`authorization`](../packages/credentials/authorization) | - | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | Flows are registered by the plugin that knows how to obtain one credential and keyed by the record they write; the seam owns the conversation and the one-attempt-per-key lifecycle, never the protocol. |
| `ctx.sessionTelemetry` | `seam` | [`session-telemetry`](../packages/session/session-telemetry) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | - | - | The seam captures, redacts, and hands session records to one backend; nothing else consumes the service — its output leaves the process. |
| `ctx.storage` | `seam` | [`storage`](../packages/storage/storage) | [`storage-json`](../packages/storage/storage-json), [`storage-sqlite`](../packages/storage/storage-sqlite) | [`storage-domain`](../packages/storage/storage-domain) | - | Backends register side by side under names; data forms (domain first) mount on the hub and translate typed operations into opaque KV-unit primitives. |
| `ctx.storageDomain` | `core` | [`storage-domain`](../packages/storage/storage-domain) | - | [`workspace`](../packages/workspace/workspace), [`message-feedback`](../packages/feedback/message-feedback) | - | Waits for every configured backend, then publishes the domain form as one lifecycle-bound service for typed durable state. |
| `ctx.messageFeedback` | `core` | [`message-feedback`](../packages/feedback/message-feedback) | - | - | - | Owns local per-assistant-message feedback, lifecycle and target validation, per-item compare-and-set, and the Host unary Remote contract without entering Session history or telemetry. |
| `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | `apiproxy` | - | Owns WorkspaceId-branded records over the domain facility; stable sessionIds accounts drive Host RPC and GUI projections. |
| `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | [`api-workspace-controller`](../packages/api/workspace-controller), [`api-session-controller`](../packages/api/session-controller) | - | Owns WorkspaceId-branded records over the domain facility; stable sessionIds accounts drive Host RPC and GUI projections. |
| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/session-query/tool-session-query) | - | The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering. |
| `ctx.fileReferences` | `seam` | [`file-reference`](../packages/context/file-reference) | [`file-reference-local`](../packages/context/file-reference-local) | - | - | The interface returns path-only completion candidates within the addressed Agent cwd through its unary Remote contract; providers own namespace access and ranking without reading file contents. |
| `ctx.sessionReferenceResolver` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax. |
@@ -482,7 +483,7 @@ flowchart LR
| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. |
| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. |
| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. |
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), `subagent-inprocess` | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
| `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`headless`](../packages/bundle/headless), [`host-apiproxy`](../packages/host/apiproxy) | - | Layers the default ModelSelection through settings so direct and Host-backed Agent entry points share one state owner. |
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
| `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. |
@@ -493,23 +494,23 @@ flowchart LR
| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools. |
| `ctx.sandbox` | `seam` | [`sandbox`](../packages/sandbox/sandbox) | [`sandbox-local`](../packages/sandbox/sandbox-local) | [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | Consumers hand over the exact argv they are about to spawn; same-world backends wrap it under a per-call policy and report enforcement. |
| `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | The one home for the deployment default mode + workspace root; only the sandboxed executor and provider read the service (the tool layers use the pure `sandbox/mode` fold it also exports). Both enforcing families read it so bash and fs cannot confine to different roots. |
| `ctx.approval` | `seam` | `approval` | [`acp`](../packages/acp/acp) | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. |
| `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. |
| `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events. |
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime-worker` | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode). |
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode). |
| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. |
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. |
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. |
| `ctx.agentTeams` | `core` | `agent-team` | - | `tool-agent-team` | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes the scoped model policy and controls. |
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes the scoped model policy and controls. |
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | Producers (background bash, PTY sends, and subagent delegations) register running work; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. |
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. |
| `ctx.directoryPicker` | `seam` | `directory-picker` | `directory-picker-native`, `directory-picker-browse` | `apiproxy` | - | Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement). |
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. |
| `ctx.clientModules` | `core` | `modules` | - | `hmr` | - | Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers. |
| `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`host-apiproxy`](../packages/host/apiproxy) | - | Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement). |
| `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. |
| `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers. |
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | Provider adapters dispatch authenticated deliveries; trusted plugins register independent process-local rules, and the runtime turns non-null results into ordinary Workspace-backed Sessions without delivery or completion state. |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | The transport-agnostic host gateway face: it dispatches browser API calls, and each open host stream subscribes to the events it forwards rather than being pushed to through a broadcast verb. |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. |
| `ctx.apiProxy` | `core` | [`host-apiproxy`](../packages/host/apiproxy) | - | [`client-connection`](../packages/client/connection) | - | The transport-agnostic host gateway face: it dispatches browser API calls, and each open host stream subscribes to the events it forwards rather than being pushed to through a broadcast verb. |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace. |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport. |
+60 -59
View File
@@ -12,11 +12,13 @@ flowchart LR
pkg_attachment["attachment"]
svc_attachments["ctx.attachments<br/>Durable binary attachment storage"]
pkg_attachment_local["attachment-local"]
pkg_host_runtime["host-runtime"]
pkg_api_session_controller["api-session-controller"]
pkg_host_apiproxy["host-apiproxy"]
pkg_tool_fs["tool-fs"]
pkg_llm_pi_ai["llm-pi-ai"]
pkg_llm_deepseek["llm-deepseek"]
pkg_llm["llm"]
svc_llm["ctx.llm<br/>LLM adapter registry"]
pkg_llm_deepseek["llm-deepseek"]
pkg_llm_replay["llm-replay"]
pkg_agent_loop["agent-loop"]
pkg_compaction_basic["compaction-basic"]
@@ -34,12 +36,10 @@ flowchart LR
pkg_session_persistence["session-persistence"]
pkg_session_query["session-query"]
pkg_session_query_sqlite["session-query-sqlite"]
pkg_subagent_inprocess["subagent-inprocess"]
pkg_subagent_in_process_driver["subagent-in-process-driver"]
pkg_invariants["invariants"]
pkg_message_feedback["message-feedback"]
pkg_api_session_controller["api-session-controller"]
svc_sessionController["ctx.sessionController<br/>Host Session Remote controller"]
pkg_apiproxy["apiproxy"]
pkg_api_workspace_controller["api-workspace-controller"]
svc_workspaceController["ctx.workspaceController<br/>Host Workspace Remote controller"]
svc_invariants["ctx.invariants<br/>Package-owned invariant registry"]
@@ -91,7 +91,6 @@ flowchart LR
pkg_system_prompt["system-prompt"]
svc_systemPrompt["ctx.systemPrompt<br/>System prompt assembly registry"]
pkg_tools["tools"]
pkg_tool_fs["tool-fs"]
pkg_tool_terminal["tool-terminal"]
pkg_tool_web["tool-web"]
svc_tools["ctx.tools<br/>Tool registry and guarded execution pipeline"]
@@ -109,7 +108,6 @@ flowchart LR
svc_commands["ctx.commands<br/>Human command registry"]
pkg_session_projection["session-projection"]
svc_sessionProjections["ctx.sessionProjections<br/>Session projection units"]
pkg_host_apiproxy["host-apiproxy"]
pkg_session_projection_cache["session-projection-cache"]
svc_sessionProjectionCache["ctx.sessionProjectionCache<br/>Persisted projection cache"]
pkg_skill["skill"]
@@ -153,13 +151,13 @@ flowchart LR
pkg_sandbox_policy["sandbox-policy"]
svc_sandboxPolicy["ctx.sandboxPolicy<br/>Sandbox policy home"]
pkg_fs_sandbox["fs-sandbox"]
pkg_approval["approval"]
pkg_user_approval["user-approval"]
svc_approval["ctx.approval<br/>Approval seam"]
pkg_permission_presets["permission-presets"]
svc_permissionPresets["ctx.permissionPresets<br/>Permission presets"]
pkg_code_runtime["code-runtime"]
svc_codeRuntime["ctx.codeRuntime<br/>Code-execution seam"]
pkg_code_runtime_worker["code-runtime-worker"]
pkg_code_runtime_worker_thread["code-runtime-worker-thread"]
pkg_fs["fs"]
svc_fs["ctx.fs<br/>Filesystem provider seam"]
pkg_fs_local["fs-local"]
@@ -173,9 +171,9 @@ flowchart LR
pkg_subagent_dsh_sdk["subagent-dsh-sdk"]
pkg_tool_subagent_control["tool-subagent-control"]
pkg_tool_ralph["tool-ralph"]
pkg_agent_team["agent-team"]
pkg_experimental_agent_team["experimental-agent-team"]
svc_agentTeams["ctx.agentTeams<br/>Agent Teams coordination domain"]
pkg_tool_agent_team["tool-agent-team"]
pkg_experimental_tool_agent_team["experimental-tool-agent-team"]
pkg_jobs["jobs"]
svc_jobs["ctx.jobs<br/>Background job registry"]
pkg_jobs_local["jobs-local"]
@@ -190,15 +188,15 @@ flowchart LR
svc_spillStore["ctx.spillStore<br/>Spill storage seam"]
pkg_spill_local["spill-local"]
pkg_spill_policy["spill-policy"]
pkg_directory_picker["directory-picker"]
pkg_host_directory_picker["host-directory-picker"]
svc_directoryPicker["ctx.directoryPicker<br/>Workspace-directory picking seam"]
pkg_directory_picker_native["directory-picker-native"]
pkg_directory_picker_browse["directory-picker-browse"]
pkg_webserver["webserver"]
pkg_host_directory_picker_native["host-directory-picker-native"]
pkg_host_directory_picker_browse["host-directory-picker-browse"]
pkg_host_webserver["host-webserver"]
svc_webServer["ctx.webServer<br/>HTTP route registration"]
pkg_connection["connection"]
pkg_modules["modules"]
pkg_hmr["hmr"]
pkg_client_connection["client-connection"]
pkg_client_modules["client-modules"]
pkg_client_hmr["client-hmr"]
svc_clientModules["ctx.clientModules<br/>Client plugin graph host"]
pkg_workflow["workflow"]
svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
@@ -209,30 +207,26 @@ flowchart LR
pkg_webhook_github["webhook-github"]
pkg_lsp["lsp"]
svc_lsp["ctx.lsp<br/>Language-server navigation seam"]
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
pkg_acp --> svc_approval
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_agent_team --> svc_agentTeams
pkg_api_gateway --> svc_typertGateway
pkg_api_session_controller --> svc_sessionController
pkg_api_workspace_controller --> svc_workspaceController
pkg_apiproxy --> svc_apiProxy
pkg_approval --> svc_approval
pkg_attachment --> svc_attachments
pkg_attachment_local --> svc_attachments
pkg_authorization --> svc_authorization
pkg_bash_local --> svc_shell
pkg_bash_sandbox --> svc_shell
pkg_client_modules --> svc_clientModules
pkg_code_runtime --> svc_codeRuntime
pkg_code_runtime_worker --> svc_codeRuntime
pkg_code_runtime_worker_thread --> svc_codeRuntime
pkg_commands --> svc_commands
pkg_compaction --> svc_compaction
pkg_compaction_basic --> svc_compaction
@@ -242,10 +236,8 @@ flowchart LR
pkg_credentials --> svc_credentials
pkg_credentials_local --> svc_credentials
pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions
pkg_directory_picker --> svc_directoryPicker
pkg_directory_picker_browse --> svc_directoryPicker
pkg_directory_picker_native --> svc_directoryPicker
pkg_e2b --> svc_e2b
pkg_experimental_agent_team --> svc_agentTeams
pkg_file_reference --> svc_fileReferences
pkg_file_reference_local --> svc_fileReferences
pkg_fs --> svc_fs
@@ -253,6 +245,11 @@ flowchart LR
pkg_fs_local --> svc_fs
pkg_fs_sandbox --> svc_fs
pkg_goal --> svc_goals
pkg_host_apiproxy --> svc_apiProxy
pkg_host_directory_picker --> svc_directoryPicker
pkg_host_directory_picker_browse --> svc_directoryPicker
pkg_host_directory_picker_native --> svc_directoryPicker
pkg_host_webserver --> svc_webServer
pkg_invariants --> svc_invariants
pkg_jobs --> svc_jobs
pkg_jobs_local --> svc_jobs
@@ -261,9 +258,8 @@ flowchart LR
pkg_llm_pi_ai --> svc_llm
pkg_llm_replay --> svc_llm
pkg_lsp --> svc_lsp
pkg_lsp_local --> svc_lsp
pkg_lsp_stdio --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_permission_presets --> svc_permissionPresets
pkg_plan_mode --> svc_planMode
pkg_plugin_package_inventory_deepseek --> svc_deepseekLlmApiExtensions
@@ -316,6 +312,7 @@ flowchart LR
pkg_tool_subagent --> svc_subagentModelSelection
pkg_tools --> svc_tools
pkg_typert_registry --> svc_typert
pkg_user_approval --> svc_approval
pkg_user_questions --> svc_userQuestions
pkg_web --> svc_web
pkg_web_fetch_http --> svc_web
@@ -323,32 +320,35 @@ flowchart LR
pkg_web_search_exa --> svc_web
pkg_web_search_perplexity --> svc_web
pkg_webhook --> svc_webhookRuntime
pkg_webserver --> svc_webServer
pkg_workflow --> svc_workflowEngine
pkg_workflow_worker_thread --> svc_workflowEngine
pkg_workspace --> svc_workspaceRegistry
svc_agentDefaultModel --> pkg_headless
svc_agentDefaultModel --> pkg_host_apiproxy
svc_agentLoop --> pkg_agent_spine_demo
svc_agentTeams --> pkg_tool_agent_team
svc_agentTeams --> pkg_experimental_tool_agent_team
svc_agents --> pkg_acp
svc_agents --> pkg_agent_loop
svc_agents --> pkg_subagent_inprocess
svc_apiProxy --> pkg_connection
svc_agents --> pkg_subagent_in_process_driver
svc_apiProxy --> pkg_client_connection
svc_approval --> pkg_acp
svc_approval --> pkg_tool_bash
svc_approval --> pkg_tools
svc_attachments --> pkg_host_runtime
svc_attachments --> pkg_api_session_controller
svc_attachments --> pkg_host_apiproxy
svc_attachments --> pkg_llm_deepseek
svc_attachments --> pkg_llm_pi_ai
svc_attachments --> pkg_tool_fs
svc_authorization --> pkg_llm_pi_ai
svc_clientModules --> pkg_hmr
svc_clientModules --> pkg_client_hmr
svc_codeRuntime --> pkg_tools
svc_compaction --> pkg_compaction_basic
svc_cordisInspect --> pkg_tool_cordis
svc_credentials --> pkg_apiproxy
svc_credentials --> pkg_host_apiproxy
svc_credentials --> pkg_llm_deepseek
svc_credentials --> pkg_llm_pi_ai
svc_deepseekLlmApiExtensions --> pkg_llm_deepseek
svc_directoryPicker --> pkg_apiproxy
svc_directoryPicker --> pkg_host_apiproxy
svc_dynamicCordisRunner --> pkg_tool_cordis
svc_e2b --> pkg_fs_e2b
svc_e2b --> pkg_subprocess_e2b
@@ -369,7 +369,7 @@ flowchart LR
svc_sandboxPolicy --> pkg_bash_sandbox
svc_sandboxPolicy --> pkg_fs_sandbox
svc_sandboxPolicy --> pkg_terminal_bash
svc_sessionController --> pkg_apiproxy
svc_sessionController --> pkg_host_apiproxy
svc_sessionPersistence --> pkg_agent_loop
svc_sessionPersistence --> pkg_hooks_claude_code
svc_sessionPersistence --> pkg_hooks_codex
@@ -390,8 +390,8 @@ flowchart LR
svc_sessions --> pkg_session_persistence
svc_sessions --> pkg_session_query
svc_sessions --> pkg_session_query_sqlite
svc_sessions --> pkg_subagent_inprocess
svc_settings --> pkg_apiproxy
svc_sessions --> pkg_subagent_in_process_driver
svc_settings --> pkg_host_apiproxy
svc_settings --> pkg_llm_deepseek
svc_settings --> pkg_llm_pi_ai
svc_shell --> pkg_hooks_claude_code
@@ -438,39 +438,40 @@ flowchart LR
svc_typert --> pkg_typert_loader
svc_userQuestions --> pkg_tool_ask_user
svc_web --> pkg_tool_web
svc_webServer --> pkg_connection
svc_webServer --> pkg_hmr
svc_webServer --> pkg_modules
svc_webServer --> pkg_client_connection
svc_webServer --> pkg_client_hmr
svc_webServer --> pkg_client_modules
svc_webhookRuntime --> pkg_webhook_github
svc_workflowEngine --> pkg_tool_ralph
svc_workflowEngine --> pkg_tool_workflow
svc_workspaceRegistry --> pkg_apiproxy
svc_workspaceRegistry --> pkg_api_session_controller
svc_workspaceRegistry --> pkg_api_workspace_controller
svc_fs -. event gate .-> pkg_fs_observation_policy
```
| ctx 键 | 角色 | 所属包 | 实现 | 直接消费方 | 配套插件 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | `host-runtime`, [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | 宿主会在会话事件之前提交已接受的图片;提供方适配器将已授权的持久引用解析为提供方原生内容。 |
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | [`api-session-controller`](../packages/api/session-controller), [`host-apiproxy`](../packages/host/apiproxy), [`tool-fs`](../packages/fs/tool-fs), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 宿主会在会话事件之前提交已接受的图片;提供方适配器将已授权的持久引用解析为提供方原生内容。 |
| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | 适配器注册提供方实现;agent loop(智能体循环)与压缩功能调用提供方无关的流服务。 |
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 插件准备彼此独立的顶层字段;官方适配器会合并这些字段,并在 HTTP 接受后提交其交付状态。 |
| `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 拥有按会话隔离的回放折叠区;压力消费方共享不可变且带修订版本的测量结果。 |
| `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 在摘要压缩前,通过可回放的单节点表层替换来改写过大的当前工具结果。 |
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), `subagent-inprocess`, [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 |
| `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | `apiproxy` | - | 负责 Session 命令、冷读取、持久事件跟随、实时控制状态与 Agent 激活策略;apiProxy 在需要 Session 上下文的领域中复用其检查和 Agent 解析操作。 |
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 |
| `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | 负责 Session 命令、冷读取、持久事件跟随、实时控制状态与 Agent 激活策略;apiProxy 在需要 Session 上下文的领域中复用其检查和 Agent 解析操作。 |
| `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 通过生成的 Remote namespace 负责 Workspace 命令和可在重连后收敛的 Workspace 状态投递。 |
| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | 配套子路径注册所属包本地的检查;该服务负责选择、唯一性、子 fiber,以及标明所属包的失败。 |
| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | 插件直接或通过 dsh-typert-loader 注册实时 zod 贡献;API 网关消费调用描述符和提供方,其他运行时消费方则在各自边界查询 schema 与反射元数据。 |
| `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | 将生成的 Remote 描述符与实时 Cordis 服务关联,解析已注册的身份,并通过共享的 Connection RPC 载体提供一元调用。 |
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | 各后端持久化同一套 SessionEvent 词汇;应用在组合时选择后端。 |
| `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-file`](../packages/settings/settings-file) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | 插件注册命名空间 schema 并解析分层值;提供方存储原始文档。LLM(大语言模型)适配器在用户分区下将其入口配置注册为组合基础;Web 网关提供经过脱敏的分层描述符,并写入用户层。 |
| `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-file`](../packages/settings/settings-file) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`host-apiproxy`](../packages/host/apiproxy) | - | 插件注册命名空间 schema 并解析分层值;提供方存储原始文档。LLM(大语言模型)适配器在用户分区下将其入口配置注册为组合基础;Web 网关提供经过脱敏的分层描述符,并写入用户层。 |
| `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | 拥有默认关闭的设置命名空间;Agent 作用域的委派工具会在组合新顶层 Session 时读取它。 |
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | 配置携带对机密信息的引用;提供方拥有实际值。消费方按操作解析,因此轮换后的凭据会在紧接着的下一次请求中生效;Web 网关提供不含实际值的视图和只写存储。 |
| `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`host-apiproxy`](../packages/host/apiproxy) | - | 配置携带对机密信息的引用;提供方拥有实际值。消费方按操作解析,因此轮换后的凭据会在紧接着的下一次请求中生效;Web 网关提供不含实际值的视图和只写存储。 |
| `ctx.authorization` | `seam` | [`authorization`](../packages/credentials/authorization) | - | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | flow 由知道如何取得某份凭据的插件注册,并以其写入的记录为键;seam 拥有这段对话与"每个键同时只跑一次尝试"的生命周期,而非协议本身。 |
| `ctx.sessionTelemetry` | `seam` | [`session-telemetry`](../packages/session/session-telemetry) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | - | - | 该 seam 捕获会话记录、进行脱敏并交给一个后端;没有其他组件消费该服务,其输出会离开当前进程。 |
| `ctx.storage` | `seam` | [`storage`](../packages/storage/storage) | [`storage-json`](../packages/storage/storage-json), [`storage-sqlite`](../packages/storage/storage-sqlite) | [`storage-domain`](../packages/storage/storage-domain) | - | 各后端以不同名称并列注册;数据形态(领域优先)挂载到枢纽上,并将类型化操作转换为不透明的 KV 单元原语。 |
| `ctx.storageDomain` | `core` | [`storage-domain`](../packages/storage/storage-domain) | - | [`workspace`](../packages/workspace/workspace), [`message-feedback`](../packages/feedback/message-feedback) | - | 等待所有已配置后端就绪,然后将领域形态发布为一个受生命周期约束的服务,用于类型化持久状态。 |
| `ctx.messageFeedback` | `core` | [`message-feedback`](../packages/feedback/message-feedback) | - | - | - | 拥有本地逐 assistant 消息反馈、生命周期与目标校验、逐条目 compare-and-set 及 Host 一元 Remote 契约,且不进入 Session 历史或遥测。 |
| `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | `apiproxy` | - | 通过领域设施拥有带 WorkspaceId 品牌类型的记录;稳定的 sessionIds 账户驱动 Host RPC 与 GUI 投影。 |
| `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | [`api-workspace-controller`](../packages/api/workspace-controller), [`api-session-controller`](../packages/api/session-controller) | - | 通过领域设施拥有带 WorkspaceId 品牌类型的记录;稳定的 sessionIds 账户驱动 Host RPC 与 GUI 投影。 |
| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/session-query/tool-session-query) | - | 该接口提供精确读取、过滤和追踪;具体后端还提供全文协调、排序、摘要片段和游标世代,而模型消费方负责工作区权限与不含游标的渲染。 |
| `ctx.fileReferences` | `seam` | [`file-reference`](../packages/context/file-reference) | [`file-reference-local`](../packages/context/file-reference-local) | - | - | 该接口通过其一元 Remote 契约返回指定 Agent cwd 内仅含路径的补全候选;提供方负责命名空间访问和排序,但不会读取文件内容。 |
| `ctx.sessionReferenceResolver` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | 将当前表层中有界的对话快照投影为持久但不可信的消息上下文;Host 适配器负责提及语法。 |
@@ -484,7 +485,7 @@ flowchart LR
| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | 各领域注册由状态驱动的折叠单元;主动驱动过程维护每个会话的水位状态,api-proxy 提供基线并推送发生变化的值。 |
| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | 按会话持久保存投影单元状态的检查点(节流检查点,以及轮次/结束/分离时的必选检查点),并提供冷读取阶梯:缓存行加持久化尾部回放,因此列表读取永远不需要加载完整日志。 |
| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem) | [`tool-skill`](../packages/skill/tool-skill) | - | 合并提供方的 skill(技能)目录;tool-skill 渲染会话前缀目录,并加载完整的 skill 正文。 |
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), `subagent-inprocess` | - | 拥有实时 Agent 句柄、创建/恢复工厂 seam,以及进程本地的发起方传播。 |
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | 拥有实时 Agent 句柄、创建/恢复工厂 seam,以及进程本地的发起方传播。 |
| `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`headless`](../packages/bundle/headless), [`host-apiproxy`](../packages/host/apiproxy) | - | 通过 settings 分层默认 `ModelSelection`,让直接入口与 Host 支撑的 Agent 入口共享同一个状态所有者。 |
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | 唯一的具体循环插件;扩展包依赖 dsh-agent 的事件和服务,而不依赖此包。 |
| `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | 从会话日志折叠带修订版本的目标状态,并将实时延续激活保留在进程本地。 |
@@ -495,23 +496,23 @@ flowchart LR
| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | 注册表负责精确到 Agent 的会话身份和清理;后端负责终端机制,tool-terminal 则提供限定于所有者作用域的模型接口。 |
| `ctx.sandbox` | `seam` | [`sandbox`](../packages/sandbox/sandbox) | [`sandbox-local`](../packages/sandbox/sandbox-local) | [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | 消费方交出即将执行 spawn 的确切 argv;与宿主共享文件系统和内核的后端按每次调用的策略包装该 argv,并报告强制执行情况。 |
| `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | 统一保存部署默认模式和工作区根目录;只有沙箱执行器和提供方读取该服务(工具层使用它同时导出的纯 `sandbox/mode` 折叠区)。两类强制执行组件都读取该服务,因此 bash 与 fs 不会限制到不同的根目录。 |
| `ctx.approval` | `seam` | `approval` | [`acp`](../packages/acp/acp) | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash) | - | 一次性权限决策通过 `approval/request` waterfall(瀑布式事件)分派;回答方是监听器(即 ACP 为自身 agent 提供的桥接),没有回答方时以 `unavailable` 关闭失败。 |
| `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | 一次性权限决策通过 `approval/request` waterfall(瀑布式事件)分派;回答方是监听器(即 ACP 为自身 agent 提供的桥接),没有回答方时以 `unavailable` 关闭失败。 |
| `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | 面向用户的预设表(`workspace-write``danger-full-access`),将沙箱模式与审批策略选项组合在一起;一次切换会写入一个 `permission/preset` 事件,并贯通到两个选项事件。 |
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime-worker` | [`tools`](../packages/core/tools) | - | 使用 Host 提供的异步绑定运行一段由模型编写的程序;各后端采用不同的基础环境和语言(工具注册表在 Code Mode 下消费该服务)。 |
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | [`tools`](../packages/core/tools) | - | 使用 Host 提供的异步绑定运行一段由模型编写的程序;各后端采用不同的基础环境和语言(工具注册表在 Code Mode 下消费该服务)。 |
| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 |
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 |
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 |
| `ctx.agentTeams` | `core` | `agent-team` | - | `tool-agent-team` | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供作用域化模型策略和控制工具。 |
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供作用域化模型策略和控制工具。 |
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | 生产方(后台 bash、PTY 发送和 subagent 委派)登记正在运行的工作;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seamtool-web 负责稳定的面向模型名称。 |
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | 后端保存过大的工具文本,并返回面向模型的定位信息和取回提示;spill-policy 是 tools/post-execute 消费方,负责决定何时 spill。 |
| `ctx.directoryPicker` | `seam` | `directory-picker` | `directory-picker-native`, `directory-picker-browse` | `apiproxy` | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 |
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 |
| `ctx.clientModules` | `core` | `modules` | - | `hmr` | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 |
| `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`host-apiproxy`](../packages/host/apiproxy) | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 |
| `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 |
| `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 |
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 |
| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | 提供方适配器分派已认证交付;可信插件注册独立的进程本地规则,runtime 把非 null 结果转换为普通的 Workspace-backed Session,不保留交付或完成状态。 |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | 与传输无关的 Host 网关接口:它分派浏览器 API 调用,每条打开的 Host 流自行订阅转发事件,而不是由广播方法向其推送。 |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 |
| `ctx.apiProxy` | `core` | [`host-apiproxy`](../packages/host/apiproxy) | - | [`client-connection`](../packages/client/connection) | - | 与传输无关的 Host 网关接口:它分派浏览器 API 调用,每条打开的 Host 流自行订阅转发事件,而不是由广播方法向其推送。 |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 拥有内存定义注册表、Host 半的 vm 沙箱和 request-run 往返流程;浏览器页面通过其 Remote 命名空间在线访问同一服务。 |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 注册 Host inspect 提供方、镜像 Client 提供方 manifest,并通过动态 Cordis 传输路由 Client 查询。 |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: ac22d6b660a975f1252c3ce852d932f9c6f7d3c4
config-catalog.zh.md: 1357f546281c0ee78674cbb1773937f9bdeeeaee
config-catalog.md: e4d60a02367bb35965564ee88dcb4065c97a5cf4
config-catalog.zh.md: 9dc302705780297a4dd177174613ad3346516e2a

Some files were not shown because too many files have changed in this diff Show More