# Agent Note: 子系统目录与 `ts type-equiv` 漂移门禁

Status: implemented

[English](2026-06-20-core-data-structures-catalog.md) | 中文

## 问题

试图理解 harness 的读者可以在 [architecture.md](../../../../docs/architecture.md) 中找到它的*行为*（服务图、会话/轮次/步骤生命周期、事件分类体系），却找不到一个统一描述其*词汇*的地方，也就是这些行为所传递的数据结构。类型定义只存在于源码中，散落在 `packages/*/src/types.ts` 各处，因此要理解「什么是 `Message`、`SessionEvent`、`StreamChunk`」，就必须直接阅读声明。文字目录会有所帮助，但复述或复制粘贴类型定义的目录会在字段发生变化时立即腐化，而不同步的类型文档比没有文档更糟，因为读者会信任它。

因此，这项工作有两个相互交织的问题：**这样的目录应包含什么**（范围问题——harness 有数十种跨包边界的类型，把它们全部罗列出来对谁都没有帮助），以及**如何避免粘贴的类型定义发生漂移**（持久性问题）。本 Agent Note 记下了这两项决策。与它历史上配套的[已归档的 Cordis 事件与服务目录自动生成决策](../../archived/process/2026-06-20-generated-cordis-catalog.md)从*接线*维度形成补充：本文对数据结构编目，另一篇则对传递这些结构的事件和服务编目。

## 决策

新增的 `docs/subsystems/` 目录对这些词汇编目，并配有新的 `verify-type-equiv` doc-sync（文档同步门禁），使每个粘贴的类型声明及其 JSDoc 与源码保持同步。

### 何为「核心」——主干与子系统的分界线

> **作为页面范围界定规则已被取代**，见[按包锚定的子系统页面](2026-08-03-package-anchored-subsystem-pages.md)：每页现在锚定到声明其词汇的包分组。下文的 `ts type-equiv` 机制仍然有效。

范围界定的决定性测试是 `ShellExecRequest`/`ShellExecSpec`/`ShellRunResult`：bash 是一个能力 *seam*，不属于 agent loop（智能体循环）主干；如果这些算「核心」，那么「核心」就意味着*所有跨包词汇*，目录沦为平铺罗列；如果不算，「核心」就意味着*中央主干*，bash 词汇归入其自身的子系统页面。后者胜出，由此确定了整体结构：一个**分层文件夹**，而非一份平铺文档。

确定其余案例的规则是：***你编写、持有或接收的类型是核心；为其提供类型推导、渲染或持久化的机制是子系统页面细节。*** 逐一验证如下：

- 一个数据结构是**核心**的，如果它流经 agent loop 主干——无论加载了哪些插件，循环在每个轮次都会持有、派生、流式输出或记录它（`Message`、`StreamChunk`、`SessionEvent`、`Agent` 句柄）——**或者**它是插件作者面对某条流水线时编写的唯一标志性类型（`ToolDefinition`）。
- `ToolDefinition` 是核心（它是每个工具作者编写的东西），**即使循环从不持有它**——对于这一个标志性类型，编写层面的重要性压过了严格的「流经主干」规则。但它的类型推导机制——`ValueSchemaSpec`、`ParameterSchemaSpec`、`InferValue` 与 `InferArgs`——是子系统页面细节。这就是主干与子系统分界线的精确表述。
- `ToolSchema` 是核心（它是流经每个步骤的模型请求 `GenerateOptions` 的一个字段），即使它在概念上属于工具流水线——当*流经主干*与*概念归属*冲突时，前者胜出。
- 工具展示词汇（`ToolCallView`/`ToolResultView` 等）、`SessionPersistence` 持久性 seam 以及 bash 词汇归入子系统页面。

`core.md` 是一份**自包含的主干文档**：它给出每个主干结构的确切类型定义，辅以最少的行文，并链接到同级子系统页面获取包所拥有的细节；目录的 [README](../../../../docs/subsystems/README.md) 索引全部页面。最初的子系统页面包括 `llm-streaming.md`、`session.md`、`persistence.md`（沿内存模型与持久性 seam 的分界线从会话页面拆出）、`tools.md` 和 `shell.md`。

### `ts type-equiv` 机制——既逐字又防漂移

持久性要求很具体：文档展示当前类型声明与原始 JSDoc 的**逐字**内容（让读者看到真实形状和源码约定，而非复述），**并且**以机械方式保证其与源码匹配。仓库已经会编译 ` ```ts ` 围栏块（`doc-typecheck`），但真正接受类型检查的块需要导入噪音，而且只能证明*可赋值性*——字段改名或 JSDoc 变化仍可能通过。因此：

- 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载类的源码等价环境声明投影，用于实现体不应进入目录的类。`doc-typecheck` 会识别并跳过这两种围栏（裸声明无法独立编译），并且**将它们排除在 opt-out 比例之外**——它们是单独受检的类别，而不是未经检查的草图。
- 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块，并断言其声明结构和每条 JSDoc 注释都与所声明的符号匹配，只忽略格式空白和非 JSDoc 注释。普通块保留完整声明。`public-api` 投影保留类的公共字段、构造函数、访问器和方法及其原始 JSDoc，同时移除实现体以及私有或受保护成员。之所以选择它而非编译式 `_Check` 断言，是因为目录所保留的是源码名称与文档一致性，而不是可赋值性。
- 每个类型块的文档、symbol 和源文件都记录在集中的 `scripts/type-equiv.manifest.json`（`{ doc, symbol, source }` 条目）中，**而非**行文中的指令注释。脚本在每个主 type-equiv 块与一条 manifest（元数据清单）条目之间强制执行 **1:1 对应**，因此一个块永远不会被静默漏检，一条条目也永远不会腐烂。只有当配对 `.zh.md` 块的完整受跟踪围栏序列在顺序、类型和按字节精确的正文上均与无后缀兄弟文件匹配时，才会复用后者的条目；否则门禁会独立检查该块，发现没有 manifest 条目后失败。
- 接入 `doc-sync`，因此相关文档变更时会在本地运行它，CI 也会与其他文档检查一起运行它。

### 维护是作者的职责，门禁作为兜底

`verify-type-equiv` 能捕获已记录类型的*粘贴漂移*，但无法告诉你一个全新的核心类型没有被记录。因此 AGENTS.md 和 `dsh-code-review` skill（技能）已更新，要求在变更添加或重塑已记录类型时同步更新目录——门禁处理漂移，人处理新类型。

## 曾考虑的替代方案

- **平铺罗列所有跨包词汇**：`ShellExecRequest` 测试案例否决了它。如果 seam 词汇算「核心」，目录对谁都没帮助；分层的主干与子系统结构胜出。
- **用编译式 `_Check` 可赋值性断言**代替源码匹配：否决。可赋值性不会保留名称或 JSDoc；同类型字段改名或约定注释变化仍会通过。
- **将每个类型块的源文件写进指令注释**：否决，改用集中 manifest；其强制的 1:1 对应确保一个块永远不会被静默漏检，一条条目也永远不会腐烂。

## 验证教训

`verify-type-equiv` 必须扫描完整的 Markdown 范围，而不仅是 manifest 点名的文档。否则，未列入清单的 `type-equiv` 块就会逃过所宣称的一一检查。因此，门禁会将此类块报告为未列入清单的块。本 Agent Note 将这条默认拒绝放行的扫描规则，连同主干与子系统的分界决策及逐字匹配决策一并记录；生成的 Cordis 目录在[其已归档的 Agent Note](../../archived/process/2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。

## 后果

- 这些词汇现在有一个**无法悄然漂移**的唯一归属：源码中的字段或公共类成员发生变化后，`doc-sync` 和 CI 中的 `verify-type-equiv` 会持续失败，直至粘贴内容刷新。Cordis 服务方法仍由生成的服务目录负责，而不会在此重复。
- 主干与子系统分界线是一个可复用的范围界定工具，而非一次性的：同一条「你编写/持有/接收的东西是核心；为其提供类型推导/渲染/持久化的机制是细节」规则，后来也被用于界定事件/服务目录的 harness 层与继承层分层。
- `ts type-equiv` 围栏是继 ` ```ts `（编译）和 ` ```ts ignore-check `（草稿）之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `（生成签名），复用了相同的跳过并排除处理。
- 添加或重塑核心类型现在附带一项文档义务，作者必须履行（门禁无法检测缺失的*新*类型），由 `dsh-code-review` 检查清单兜底。
- 自 2026-07-27 起，子系统页面层级覆盖每个承载服务的子系统：九个精简页面（权限预设、计划模式、运行时不变式、HTTP 载体、存储——同时拥有 `ctx.storage` 与 `ctx.storageDomain`——终端扩展、工作区、客户端模块、遥测）覆盖了原先没有页面的十个 `ctx` 服务，于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面（而非平铺目录）的前提。
