docs(i18n): refresh Agent Note translations after merge
This commit is contained in:
+2
-2
@@ -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
|
||||
2026-06-19-drop-mutable-session-summary.md: 0f005a78045869d62eb141c9bce8af037671b687
|
||||
2026-06-19-drop-mutable-session-summary.zh.md: a33b057c2177258e0e3e2c0c3f78176835acb070
|
||||
2026-06-19-drop-mutable-session-summary.md: 80fe043e365352b17d2a5b3efa1ab8d396d311c4
|
||||
2026-06-19-drop-mutable-session-summary.zh.md: ec98c19093369400b9b42a5c1a0590b5c6913ab9
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除可变的会话摘要
|
||||
# Agent Note: 移除可变的会话摘要
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
- `SessionPersistence.update()` **零个生产调用方**(所有 `.update(` 匹配都是 `createHash().update()` 或测试代码)。
|
||||
- `firstPrompt` 在生产代码中**从未被读取**。
|
||||
- `title` *确实*在 ACP 桥接层被读取过,但读的是工具调用的 **presenter**(`present.title`),从未读取存储的会话元数据。
|
||||
- `title` *确实*在 ACP(Agent Client Protocol)桥接层被读取过,但读的是工具调用的 **presenter**(`present.title`),从未读取存储的会话元数据。
|
||||
- `updatedAt` **没有消费方**:`list()` 唯一的生产调用方读取的是 `meta.cwd`(`SessionHeader` 字段),用于在 `session/load` 时校验工作区;恢复会话读取的是 `createdAt`/`cwd`/`parentSession`——全是 header 字段。
|
||||
- 决定性的一点:活跃的 `Session.header` 类型本来就是 `SessionHeader` 而非 `SessionMeta`——摘要从未存在于活跃会话对象上;它只存在于持久化层,除了自身的契约测试外无人写入、无人读取。
|
||||
|
||||
@@ -22,7 +22,7 @@ Status: implemented
|
||||
|
||||
摘要原本要提供的一切,在消费方真正需要时都**可从仅追加日志中派生**(`firstPrompt` = 第一条 `user/message`;近期度 = 最后一个事件的 `time` 或文件 mtime),或者已经存在于不可变 header 中(`createdAt`、`cwd`)。唯一*不可*派生的是用户*手动编辑*的标题,但它从未实现,纯属 YAGNI;如果未来真有功能需要,它可以作为独立的日志事件或 header 字段回归。
|
||||
|
||||
将此记录为决策,原因有三:**持久性**(它收窄了一个公开服务契约和跨两个后端的磁盘格式)、**争议性**(摘要是有意的前瞻性设计,而非意外产物)、**意外性**(未来读者看到 `SessionHeader` 而原始 RFC 描述的是 `SessionMeta`,否则会疑惑摘要为何消失)。它还为 [shared persistence write coordinator](../architecture/2026-06-18-shared-persistence-write-coordinator.md) 扫清了障碍:没有可变摘要后,协调器的钩子接口无需 `updateSummary` 钩子,JSONL 伴随文件与 SQLite 列之间的持久性分歧也随之消失,两个后端的写入路径得以统一。
|
||||
这被记录为一项决策,因为它具有**持久性**(它同时收窄两个后端的公共服务契约和磁盘格式)、**争议性**(summary 是有意为未来设计的结果,而非意外),也具有**意外性**(未来读者在原 Agent Note(agent 决策记录)描述 `SessionMeta` 的位置发现 `SessionHeader`,否则会追问 summary 为何消失)。它还为[共享持久化写入协调器](../architecture/2026-06-18-shared-persistence-write-coordinator.md)扫清障碍:不再有可变 summary 后,协调器的 hook 接口不需要 `updateSummary` hook,JSONL sidecar 与 SQLite 列之间的持久性分歧也随之消失,使两个后端的写入路径趋于一致。
|
||||
|
||||
## 无需迁移
|
||||
|
||||
@@ -32,4 +32,4 @@ Status: implemented
|
||||
|
||||
未来的会话选择器现在必须从日志派生预览/排序信息(或重新引入一个类型化字段),而不能直接读取现成的摘要行。这是正确的代价:为一个尚不存在的功能维护缓存,是每个后端都要付出维护成本、每个契约测试都要付出断言成本的死重。这一原则——**通过的测试固定的是当前行为,不一定是正确行为;行为可能是过去妥协的产物**——现已作为独立约定记录在[根 AGENTS.md](../../../../AGENTS.md) 中,本次变更即为其实例。
|
||||
|
||||
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->
|
||||
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-collapse-trace-only-session-events.md: c446f43d887088fcf562305fcc2dad37465fb124
|
||||
2026-06-20-collapse-trace-only-session-events.zh.md: eda7f738abbaced393f2588867f9dcd8e86e2386
|
||||
2026-06-20-collapse-trace-only-session-events.md: fce5c48ef6fcb1abc6e2fbb95dc7e83d22956660
|
||||
2026-06-20-collapse-trace-only-session-events.zh.md: 4302646b78ea0b22b479c7068f0f6259bf79edec
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# RFC: 将仅用于追踪的会话事实折叠进承载性事件
|
||||
# Agent Note: 将仅用于追踪的会话事实折叠进承载性事件
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -35,7 +35,7 @@ Status: implemented
|
||||
|
||||
## 实现说明
|
||||
|
||||
按提案交付,有一处范围细化(遵循 AGENTS.md「RFC 是提案,不是金科玉律」):
|
||||
按提案落地,但有一处范围细化(遵循 AGENTS.md 所述“Agent Note(agent 决策记录)是提案,而非绝对真理”):
|
||||
|
||||
- **空内容 `assistant/message` 承载 usage,无数据丢失。** 提案要求的证明(不会有已持久化的 usage 分片无处安放)落在 max-tokens 路径上:一个被截断的步骤有 usage 但内容为空(例如只有一个被丢弃的工具调用),以前会发出独立的 `usage`。现在它记录一个空内容的 `assistant/message { content: [], usage }`。为防止这向 provider transcript 注入一个无内容的虚假 assistant 轮次,`deriveMessages()` 跳过空内容的 `assistant/message` 事件。回归测试断言 usage 仍被表示,且派生历史未被破坏。
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-drop-unconsumed-llm-adapter-change-event.md: 657e5f08c02e1e03eedeccf8a30b8bc851201615
|
||||
2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md: fbc2248c8357dbff3f5f5008647c14c23a66d5b0
|
||||
2026-06-20-drop-unconsumed-llm-adapter-change-event.md: a3c7c089d7dfa1a4cd6a891c416bf270dc7eff3d
|
||||
2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md: 8839a4c263462f2bae75d8698b20007e8348d903
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除未被消费的 `llm/adapter-change` 事件
|
||||
# Agent Note: 移除未被消费的 `llm/adapter-change` 事件
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -8,25 +8,25 @@ Status: implemented
|
||||
|
||||
`LlmService.registerAdapter()` 在注册和 dispose(资源释放)时发出 `llm/adapter-change` 事件([packages/llm/llm/src/index.ts](../../../../packages/llm/llm/src/index.ts))。在 `packages/*/src` 和 `examples/*/src` 中搜索 `llm/adapter-change`,只能找到声明、emit 站点、文档和测试;没有任何生产环境的监听器订阅它。
|
||||
|
||||
这与 `tools/change` 和 `system-prompt/change` 不同。后两个事件目前同样未被消费,但它们是合理的注册表变更信号,未来可能服务于实时工具/提示词 UI。LLM(大语言模型)适配器注册更接近启动时的实现细节:适配器不是用户可见的面板,真正的模型调用拦截 seam 是 `llm/stream`。保留一个没有监听器的 adapter-change 事件,是在更小规模上重复 [drop-the-dead-summary](../../implemented/simplification/2026-06-19-drop-mutable-session-summary.md) 的模式。
|
||||
这与 `tools/change` 和 `system-prompt/change` 不同。如今这两个事件同样没有消费者,但它们有望成为未来实时工具/prompt UI 的注册表变更信号。LLM(大语言模型)adapter 注册更像是启动时的实现细节:adapter 不是用户可见的选项面板,真正的模型调用拦截接缝是 `llm/stream`。保留一个没有监听器的 adapter 变更事件,只是在更小范围内重复[删除无用 summary](2026-06-19-drop-mutable-session-summary.md) 的模式。
|
||||
|
||||
这个事件并非零成本。`registerAdapter()` 在发出 `llm/adapter-change` 之前先 yield 回滚 disposer,这样抛出异常的监听器会回退变更而非泄漏适配器条目;包内还有针对该监听器抛出路径的测试。这种防御性排序保护的是一个只有测试才能触发的失败模式。
|
||||
|
||||
## 决策
|
||||
|
||||
仅移除 `llm/adapter-change`:`dsh-llm` 的 `interface Events` 中的声明、`ctx.emit('llm/adapter-change')` 调用,以及 `LlmService.registerAdapter` JSDoc 中的 "Emits `llm/adapter-change` on registration and disposal" 语句。`registerAdapter()` 的 effect generator 保留变更与回滚 disposer 以支持 HMR(热模块替换)/dispose,但去掉了仅为已移除事件而存在的监听器抛出回滚排序。适配器 disposer 测试断言返回的 disposer 能移除适配器,而不再订阅该事件;监听器抛出回滚测试随其主题一同移除。[docs/architecture.md](../../../architecture.md) 和 [packages/llm/llm/README.md](../../../../packages/llm/llm/README.md) 中的事件分类体系在同一个变更中更新。
|
||||
只移除 `llm/adapter-change`:包括 `dsh-llm` 的 `interface Events` 中的声明、`ctx.emit('llm/adapter-change')` 调用,以及 `LlmService.registerAdapter` JSDoc 中“在注册和释放时发出 `llm/adapter-change`”的句子。`registerAdapter()` 的效应生成器为 HMR(热模块替换)/释放保留变更与回滚 disposer,但移除仅因该事件而存在的监听器抛错回滚顺序。adapter disposer 测试断言返回的 disposer 会移除 adapter,不再订阅事件;监听器抛错回滚测试则随其测试对象一起消失。[docs/architecture.md](../../../../docs/architecture.md) 和 [packages/llm/llm/README.md](../../../../packages/llm/llm/README.md) 中的事件分类也在同一变更中更新。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
### 为什么不移除所有注册表变更事件?
|
||||
|
||||
一个注册表主动广播变更的微内核是一种自洽的约定。`tools/change` 和 `system-prompt/change` 在 UI 能实时刷新可用工具或提示词段落时可能变得有用。本 RFC 保留该约定中有合理的面向用户消费方的部分,仅裁掉当前和可预见未来消费方都不明确的 adapter-change 事件。
|
||||
由注册表通告变更的微内核是一种一致的约定。当 UI 能够实时刷新可用工具或 prompt 章节时,`tools/change` 和 `system-prompt/change` 可能会有用。本 Agent Note(agent 决策记录)在存在合理用户侧消费者的位置保留该约定,只删除当前及可能的未来消费者都不明确的 adapter 变更事件。
|
||||
|
||||
如果将来需要 LLM 适配器浏览器或动态模型选择器用到此信号,届时再连同消费方一起重新引入,并提供比「something changed」更清晰的 payload。
|
||||
|
||||
## 验证
|
||||
|
||||
`llm/adapter-change` 及其 emit 已移除,重新生成的 cordis catalog 是最新的;HMR 安全性保持(dispose 一个贡献 fiber 会移除对应适配器);`tools/change` 和 `system-prompt/change` 仍有文档和测试;没有任何生产路径的可观察行为发生变化——ACP(Agent Client Protocol)快照 golden 和 echo-agent 冒烟测试逐字节未变。
|
||||
`llm/adapter-change` 及其 emit 已消失,重新生成的 Cordis 目录保持新鲜;HMR 安全性仍成立(释放贡献该 adapter 的 fiber 会移除它);`tools/change` 和 `system-prompt/change` 仍有文档与测试;ACP(Agent Client Protocol)快照和无密钥 Headless Loader 冒烟则固定了未变的生产路径。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-drop-unconsumed-llm-assembled-surfaces.md: ead3a8c094b0fc0b4bd01671bd4a1165dd555ea2
|
||||
2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md: 16704b511151c329622e58c3b5f6b2cff330f366
|
||||
2026-06-20-drop-unconsumed-llm-assembled-surfaces.md: b6b596e822b4bd6fd1bd891c336c622ad675ad45
|
||||
2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md: 5a4e378958f5fb594345305804a3b90e493f0560
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除未被消费的 LLM 组装便捷接口
|
||||
# Agent Note: 移除未被消费的 LLM 组装便捷接口
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
LLM(大语言模型)服务唯一的生产消费方是 agent loop(智能体循环),它只使用 `stream()`:将原始分片送入自己的 `BlockAssembler`,以便在并行组装的同时记录分片,保证回放保真度([packages/core/agent-loop/src/loop.ts](../../../../packages/core/agent-loop/src/loop.ts),`ctx.llm.stream(req)` 步骤)。在 `packages/*/src` 和 `examples/*/src` 中 grep `streamBlocks` 与 `ctx.llm.generate`,找不到任何生产调用方。仅有的引用来自服务方法定义、文档和测试;适配器测试用 `generate()` 作为便捷驱动,但它们完全可以通过同一个 assembler 辅助函数手动消费 `stream()`,无需为此保留一个公开的生产 API。
|
||||
|
||||
这与 [drop-mutable-session-summary](../../implemented/simplification/2026-06-19-drop-mutable-session-summary.md) 是同一模式:拥有经过测试的契约的组装视图 API,消费方却只有测试而非生产代码。它们是为「不关心 token 级增量」的消费方预设的,但唯一的真实消费方恰恰需要增量,以便持久化高保真回放数据。
|
||||
这属于[删除可变 session summary](2026-06-19-drop-mutable-session-summary.md) 的同类模式:带有受测契约的组装视图 API,由测试而非生产代码消费。它们是为不关心 token 级增量的消费者推测性构建的,但唯一的真实消费者恰恰关心增量,以便持久化高保真重放数据。
|
||||
|
||||
`streamBlocks()` 拖带了 `BlockAssembler` 的一块专用逻辑:`flushReady()` 与 `flushRemaining()`([packages/llm/llm/src/assembler.ts:138-168](../../../../packages/llm/llm/src/assembler.ts))以及 `flushed` 游标字段,仅为支持按序增量产出而存在。`generate()` 拖带了 `GenerateResult`、`BlockAssembler.result()` 以及 `llm/generate` waterfall——在同一底层流之上的第二个拦截面。agent loop 对 assembler 的使用仅限于 `push()` / `message()` / `usage` / `finish`,不涉及流式 flush 或一次性服务组装。
|
||||
|
||||
@@ -28,7 +28,7 @@ LLM(大语言模型)服务唯一的生产消费方是 agent loop(智能体
|
||||
|
||||
## 验证
|
||||
|
||||
`streamBlocks`、`generate`、`llm/generate` 及其独占的 assembler 辅助方法已移除,无新增死导出;两个真实适配器通过 `stream()` 和共享 assembler 得到验证;agent loop 行为不变(ACP 快照 golden 文件无变化);README、架构文档与模块文档中不再提及已移除的接口。
|
||||
`streamBlocks`、`generate`、`llm/generate` 及仅供它们使用的 assembler 辅助函数均已移除,且未产生新的无用导出;两个真实 adapter 都通过 `stream()` 和共享 assembler 接受测试;循环行为保持一致(ACP(Agent Client Protocol)快照预期输出未变);README、架构文档和模块文档也不再提及已删除表面。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-prune-dead-seam-methods.md: fc656f4fc46837a75fa6c34c2de18cffde954ef3
|
||||
2026-06-20-prune-dead-seam-methods.zh.md: 2b8b2afea97aa28d32d55a9def217a593e9dbc7d
|
||||
2026-06-20-prune-dead-seam-methods.md: bb91194ed0483ca4c43acdde8370e7152ac876ea
|
||||
2026-06-20-prune-dead-seam-methods.zh.md: d64e9b5b6d40fbb0f52705a3385123b072360607
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-06-20-prune-dead-seam-methods.zh.md)
|
||||
|
||||
> **Implementation note:** Only `SessionPersistence.has()` and `.delete()` were removed. `BashExecutor.get()` and `.list()` remain because removing their one-line lookup surface required substantially more completion-tracking machinery in consumers. Their id branding is covered by the [branded-ids Agent Note](../architecture/2026-06-20-branded-ids.md).
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
# RFC: 从 persistence seam 中移除无用方法
|
||||
# Agent Note: 从 persistence seam 中移除无用方法
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-06-20-prune-dead-seam-methods.md) | 中文
|
||||
|
||||
> **实现说明:** 最终只移除了 `SessionPersistence.has()` 和 `.delete()`。`BashExecutor.get()` 和 `.list()` 保留,因为移除它们的单行查询接口需要在消费方引入大量额外的完成状态追踪机制。它们的 id 品牌化由 [branded-ids RFC](../architecture/2026-06-20-branded-ids.md) 覆盖。
|
||||
> **实现说明:** 仅移除了 `SessionPersistence.has()` 和 `.delete()`。`BashExecutor.get()` 和 `.list()` 仍然保留,因为删除它们的单行查找表面会要求消费者增加显著更多的完成跟踪机制。其 id 品牌化由[品牌化 id Agent Note(agent 决策记录)](../architecture/2026-06-20-branded-ids.md)负责。
|
||||
|
||||
## 问题
|
||||
|
||||
一个能力 seam([接口/实现/消费方](../../implemented/architecture/2026-06-13-capability-seams.md))承载着没有任何消费方调用的抽象方法。seam 的存在是为了让实现与消费方独立演进,但一个没有消费方编程依赖的方法不是 seam,而是每个实现仍须实现和测试的投机性接口面。
|
||||
能力接缝([接口 / 实现 / 消费者](../architecture/2026-06-13-capability-seams.md))承载了没有消费者调用的抽象方法。接缝的存在是为了让实现和消费者独立演进——但没有消费者以之编程的方法不是接缝,而是每个实现仍必须实现和测试的推测性表面。
|
||||
|
||||
### `SessionPersistence.has()` 与 `.delete()`
|
||||
|
||||
该抽象服务在 create/append 之外声明了更多操作:`load`、`list`、`has`、`delete`。`ctx.sessionPersistence` 的生产消费方只用了两个:agent loop(智能体循环)的恢复路径调用 `load()`([packages/core/agent-loop/src/index.ts:176](../../../../packages/core/agent-loop/src/index.ts)),ACP(Agent Client Protocol)桥接层为 `session/list` 调用 `list()`([packages/ui/acp/src/index.ts:494](../../../../packages/ui/acp/src/index.ts))。在 `packages/*/src` 和 `examples/` 中 grep 所有 `sessionPersistence.*` / `persistence.*` 的使用,找不到对该服务的 `has(` 或 `delete(` 调用。`packages/ui/acp/src/index.ts` 中的 `.has(`/`.delete(` 调用作用于内存中的 `SessionStore` 和一个本地的 loading id `Set`,而非 persistence。`has`/`delete` 的唯一调用者是契约测试套件和各后端的 spec。
|
||||
|
||||
`has()` 不仅是未使用——它还是共享协调器中最复杂的分支:一个 tracked-vs-untracked 双探测(`loadLive(id, cwd)` 用于活跃追踪的会话,`loadStored(id)` 用于未追踪的会话),附带多行注释说明理由。`delete()` 则拖带了 `deleteStored` 后端钩子,每个后端都必须实现它。这与 [drop-mutable-session-summary](../../implemented/simplification/2026-06-19-drop-mutable-session-summary.md) 是同一模式:契约测试覆盖了两者,但没有任何发布代码会问「这个会话是否已持久化?」或删除一个会话。
|
||||
`has()` 不仅没有被使用——它还是共享协调器中最复杂的分支:带有多行理由说明的“已跟踪/未跟踪”双重探测(对实时跟踪的 session 使用 `loadLive(id, cwd)`,对未跟踪 session 使用 `loadStored(id)`)。`delete()` 则拖入每个后端都必须实现的 `deleteStored` 后端 hook。这属于[删除可变 session summary](2026-06-19-drop-mutable-session-summary.md) 的同类模式:契约测试覆盖了两者,但已发布代码从不会询问“这个 session 是否已持久化?”或删除某个 session。
|
||||
|
||||
## 决策
|
||||
|
||||
没有消费方使用的方法被移除——从抽象 seam、实现,以及仅为覆盖它们而存在的契约/spec 测试套件中移除:
|
||||
|
||||
- `SessionPersistence.has()` / `.delete()` 已移除:抽象声明、协调器的 `has`/`delete`/`deleteCore`,以及 `PersistenceBackend.deleteStored` 钩子(jsonl 和 sqlite 各自实现 `deleteStored` 仅为满足该钩子——这些实现也一并移除)。后端属于[双后端](../../implemented/architecture/2026-06-14-session-persistence.md)设计,本身不在本次范围内;移除它们为无消费方实现的钩子是移除钩子的一部分,而非后端重新设计。
|
||||
- 所有文档和源码注释中的引用都已更新为存留的四方法、仅含 `list()` 的契约——不仅是字面的 `has(`/`delete(`/`deleteStored` 拼写,还包括 `{@link has}`/`{@link delete}` JSDoc 链接和「六个公开方法」之类的计数——涉及 seam 和后端 README、[docs/architecture.md](../../../architecture.md)、[session-persistence](../../implemented/architecture/2026-06-14-session-persistence.md) 和 [write-coordinator](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md) RFC,以及协调器/后端的 JSDoc。
|
||||
- `SessionPersistence.has()` / `.delete()` 已移除:抽象声明、协调器的 `has`/`delete`/`deleteCore`,以及 `PersistenceBackend.deleteStored` hook 均消失(jsonl 和 sqlite 都只是为了满足该 hook 才实现 `deleteStored`,这些实现也一并移除)。后端属于[双后端](../architecture/2026-06-14-session-persistence.md)设计,其他方面不在范围内;删除它们为没有消费者的 hook 所做的实现,是删除 hook 的一部分,而非重新设计后端。
|
||||
- 所有文档和源码注释引用都已更新为保留下来的四方法、仅含 `list()` 的契约——不仅包括字面上的 `has(`/`delete(`/`deleteStored` 拼写,还包括 `{@link has}`/`{@link delete}` JSDoc 链接和“六个公共方法”的计数——涉及接缝和后端 README、[docs/architecture.md](../../../../docs/architecture.md)、[session persistence](../architecture/2026-06-14-session-persistence.md) 与[写入协调器](../architecture/2026-06-18-shared-persistence-write-coordinator.md) Agent Note,以及协调器/后端 JSDoc。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-public-agent-stop-surface.md: d34f21be66892e261f1435070aaf9b478c8dc7cd
|
||||
2026-06-20-public-agent-stop-surface.zh.md: a510d39044048637c2462fd1d97eb3474931761a
|
||||
2026-06-20-public-agent-stop-surface.md: 81a21de30bfbc25688069efbffb21647889b1bdb
|
||||
2026-06-20-public-agent-stop-surface.zh.md: 4ad4b6b0beb01bc55f10c046023c7851558ecb20
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RFC: 保留单一公开停止原语
|
||||
# Agent Note: 保留单一公开停止原语
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -8,19 +8,19 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
公开的 `Agent` 句柄暴露了两种重叠的方式来停止进行中的工作:`abort(reason?)` 和 `cancel(reason?)`。`abort()` 仅终止当前步骤,不影响队列中的工作;`cancel()` 清除队列中的工作和 steering(中途引导)工作、中止正在运行的步骤,并处理步骤前竞态。在生产环境中,ACP(Agent Client Protocol)使用 `cancel()` 实现 `session/cancel`,而生命周期所有者通过 `AgentHandle.dispose()` 销毁 agent(智能体)。没有生产调用方需要裸 `abort()`。
|
||||
公共 `Agent` handle 暴露了两种相互重叠的在途工作停止方式:仅针对 step 的 `abort()` 和感知队列的 `cancel()`。前者保留已排队输入,后者则清除已排队和 steering(中途引导)工作,并中止活动 turn。在生产中,ACP(Agent Client Protocol)对 `session/cancel` 使用 `cancel()`,生命周期拥有者则通过 `AgentHandle.dispose()` 拆除 agent(智能体)。没有生产调用方需要一个裸的、仅针对 step 的 abort。
|
||||
|
||||
`abort()`/`cancel()` 的区别是真实存在的:`abort()` 保留队列中的提示词和 steering,而 `cancel()` 丢弃它们。但没有任何已上线的代码调用过公开的 `abort()` 动词。循环自身的停止路径(`cancel()` 和 disposal)直接中止当前 `AbortController`,而不经由 `Agent.abort()` 路由。大多数调用 `abort()` 的测试中断的是空队列,可以改用 `cancel(reason)`;那个刻意依赖队列保留的 steering 重投递测试则直接驱动进行中的 `AbortController`,因为 `cancel()` 会丢弃它试图证明在步骤中止后仍存活的已排队 steering。无参 `abort()` 的默认原因(`'aborted'`)随该动词一起删除,而非被意外保留;`cancel()` 保留自己的 `'cancelled'` 默认值。
|
||||
行为差异确实存在,但已发布代码不需要较窄的操作。AgentLoop 改为为整个 turn 拥有一个私有取消 holder。`cancel(cause?)` 携带类型化的 `user` 或 `parent` 原因,默认为 `user`,并丢弃待处理输入;释放仍是单独的生命周期中断。完整的归属与传播契约位于[显式 turn 取消 Agent Note(agent 决策记录)](../architecture/2026-07-16-explicit-turn-cancellation.md)。
|
||||
|
||||
多余的公开接口使得循环不得不承载一个本质上属于内部拆卸的公开动词:`abort()` 必须被文档描述为有别于队列感知的取消,尽管 UI 取消几乎总是需要更广泛的操作。
|
||||
|
||||
## 决策
|
||||
|
||||
`cancel()` 是 `Agent` 上唯一的公开*停止*原语。生命周期所有者使用 `AgentHandle.dispose()` 停止并注销 agent;非所有者使用 `cancel()` 放弃当前和队列中的工作。实现内部保留一个私有的 abort controller,但它不属于面向插件的 `Agent` 契约。
|
||||
`cancel()` 是 `Agent` 上唯一的公共*停止*原语。生命周期拥有者使用 `AgentHandle.dispose()` 停止并注销 agent;非拥有者使用 `cancel()` 放弃当前和已排队工作。实现保留一个私有 turn 取消 holder,但它不属于面向插件的 `Agent` 契约。
|
||||
|
||||
`whenIdle()` **保留**为公开的静默观测原语(agent 从 `running` 状态稳定后 resolve,已处于 idle 时立即 resolve,dispose 后等待循环退出)。它不是停止动词;它是非所有者在不 dispose agent 的前提下观测停止*完成*的方式。它的活跃消费方是 ACP 和通过此公开 seam 等待结算的 agent 测试(`packages/ui/acp/tests`、`packages/core/agent-loop/tests`);生产环境的 ACP 桥接层拥有其 agent 并通过 `AgentHandle.dispose()` 销毁它们,因此 `packages/ui/acp/src` 本身没有 `whenIdle()` 调用。
|
||||
|
||||
公开的 `abort()` 被删除,连同将其作为独立 API 测试的用例以及将步骤级中止描述为嵌入特性的文档。空队列中止测试迁移到 `cancel(reason)`,仍然验证取消行为;以循环内部 `AbortController` 为测试对象的用例通过包内类型转换直接驱动该 controller 的私有字段;仅固定已移除的无参 `abort()` 默认值的测试随方法一起删除。disposer 仍为异步,仍等待循环停止。
|
||||
公共 `abort()` 已不存在,disposer 仍为异步并等待循环停止。测试通过公共类型化原因和显式 signal 接缝验证取消,而不会伸入 holder 内部。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -36,4 +36,4 @@ Status: implemented
|
||||
|
||||
## 相关
|
||||
|
||||
本 RFC 仅移除冗余的停止动词。中途 steering 仍是有意保留的消息路径;静默观测仍通过 `whenIdle()` 提供。最终的公开接口为 `send()`、`steer()`、`inject()`、`cancel()`、`whenIdle()`、status、options、session 和 identity。
|
||||
本 Agent Note 只移除冗余的停止动词。turn 中途 steering 仍是一条有意保留的消息路径;静止观察仍通过 `whenIdle()` 完成。最终公共表面包括 `send()`、`steer()`、`inject()`、`cancel()`、`whenIdle()`、status、options、session 和 identity。
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-remove-agent-boundary-mirror-events.md: 0ea8512ca6b66b2e8ab8cf2746bdc65d79caa9d4
|
||||
2026-06-20-remove-agent-boundary-mirror-events.zh.md: 30632bf223cb41c18f62c18a544ff42f70af1136
|
||||
2026-06-20-remove-agent-boundary-mirror-events.md: 8c5bb74f2347fe0269cbb9c6504137de761ab919
|
||||
2026-06-20-remove-agent-boundary-mirror-events.zh.md: feed8239b6a07c2e27866f72954cfb95830541f4
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-06-20-remove-agent-boundary-mirror-events.zh.md)
|
||||
|
||||
<!-- Shipped in AMENDED, narrowed form: the four turn/step BOUNDARY mirrors are
|
||||
removed; `agent/steering` and `agent/stream-chunk` were RETAINED here (they
|
||||
are not durable-boundary mirrors — see "Scope: what is and isn't removed").
|
||||
|
||||
+23
-8
@@ -1,12 +1,21 @@
|
||||
# RFC: 停止将持久化边界镜像为 agent 事件
|
||||
# Agent Note: 停止将持久化边界镜像为 agent 事件
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-06-20-remove-agent-boundary-mirror-events.md) | 中文
|
||||
|
||||
<!-- 以修订、收窄后的形式落地:
|
||||
移除了四个 turn/step 边界镜像;此处保留了 `agent/steering` 和
|
||||
`agent/stream-chunk`(它们不是持久边界镜像——参见
|
||||
“范围:移除什么、不移除什么”)。原始提案将 `agent/steering` 与其他项一并
|
||||
移除;把它排除在外,使本 Agent Note 的范围保持在边界上。后来每个保留事件
|
||||
都由各自的决策移除——参见
|
||||
[停止将 token 流镜像为 agent 事件](2026-07-02-remove-stream-chunk-mirror.md)
|
||||
和[移除 `agent/steering` 镜像 emit](2026-07-04-remove-agent-steering-mirror.md)。 -->
|
||||
|
||||
## 问题
|
||||
|
||||
agent loop(智能体循环)通过可回放的 `SessionEvent` 日志和实时 `agent/*` 镜像两条路径暴露持久化的轮次与步骤边界。消费方不得不在同一事实的两个来源之间做选择,并协调二者的时序。ACP(Agent Client Protocol)和持久化层已经使用日志;stdio UI 是唯一仍在消费镜像的组件,而它已经从 `session/event` 渲染工具调用和工具结果。
|
||||
循环在 `SessionEvent` 中记录规范 transcript(文本记录),同时还发出一组并行的实时 `agent/*` 边界镜像事件:`agent/turn-start`、`agent/turn-end`、`agent/step-start` 和 `agent/step-end`。这些镜像迫使消费者在同一持久事实的两个事实来源之间做选择。ACP(Agent Client Protocol)已经为面向编辑器的 transcript 选择 session log,因为它是唯一持久、可重放的记录;消费实时镜像需要把它的时序与日志中已经存储的边界进行调和。stdio UI 是唯一仍从镜像事件渲染 turn 边界的生产消费者;它已经从 `session/event` 渲染工具调用和结果。
|
||||
|
||||
这种重复并非零成本。每次生命周期变更都需要同时更新会话事件、镜像事件、文档、不变式、测试和快照预期。重复的边界事件还使失败排序变得微妙:一个轮次可能在实时 `agent/turn-end` 监听器运行之前就已被持久化关闭,因此边界之后的监听器失败在日志中已没有合法位置可以插入,只能带外上报。
|
||||
|
||||
@@ -14,19 +23,25 @@ agent loop(智能体循环)通过可回放的 `SessionEvent` 日志和实时
|
||||
|
||||
将 `session/event` 作为唯一的实时边界/transcript(文本记录)流。需要渲染轮次、工具调用、工具结果、助手消息和持久化边界的消费方统一订阅 `session/event`,从持久化层使用的同一套事件词汇中派生 UI。
|
||||
|
||||
移除 `agent/turn-start`、`agent/turn-end`、`agent/step-start` 和 `agent/step-end`。边界消费方改为订阅 `session/event`。如果 UI 需要 agent 标签,则通过 `agent/created` 和 `agent/disposed` 维护一份 session 到 agent 的映射,因为持久化的 `turn/start` 携带轮次编号但不携带 agent id。
|
||||
四个持久边界镜像——`agent/turn-start`、`agent/turn-end`、`agent/step-start`、`agent/step-end`——已从 agent(智能体)事件分类中移除。希望在边界处取得 agent handle 的 UI 会保留来自 `agent/created`/`agent/disposed` 的实时目标对象,并直接比较其 session;`dsh-ui-stdio` 据此为应用拥有的 agent 标记 `[main turn N]` 头部,其他 session 则渲染其持久 id。规范记录仍是事件溯源 session log。
|
||||
|
||||
步骤镜像已无消费方,由 [event-domain-semantics RFC](../architecture/2026-06-30-event-domain-semantics.md) 先行移除。该决策保留了轮次镜像供 stdio UI 使用;本 RFC 在将测试 REPL 迁移到 `session/event` 加 id 映射之后,将轮次镜像也一并移除。
|
||||
step 镜像(完全没有消费者)最先在[事件域语义 Agent Note(agent 决策记录)](../architecture/2026-06-30-event-domain-semantics.md) 中移除;该 Agent Note 当时以 stdio UI 需要在 turn 边界取得 `Agent` handle 为由,保留了 turn 镜像。本 Agent Note 完成余下工作:`dsh-ui-stdio` 是可随时丢弃的测试 REPL,其渲染可以自由变化,因此“ui-stdio 需要它”并不是保留镜像的理由——它读取 `session/event`,只保留自己的实时目标对象。
|
||||
|
||||
## 范围:移除什么、不移除什么
|
||||
|
||||
本决策仅涉及持久化的轮次与步骤边界。`agent/steering` 镜像的是一条控制记录,`agent/stream-chunk` 镜像的是 token 流,因此各自单独处理:[steering](2026-07-04-remove-agent-steering-mirror.md) 与 [stream chunks](2026-07-02-remove-stream-chunk-mirror.md)。`agent/created`、`agent/disposed`、`agent/status`、`agent/error` 和 `agent/queued` 仍作为实时生命周期或控制事件保留,而非 transcript 镜像;排队中的输入可能在任何持久化事件产生之前就被取消。
|
||||
已移除(持久边界镜像——每项都以 session log 为权威):`agent/turn-start`、`agent/turn-end`、`agent/step-start`、`agent/step-end`。
|
||||
|
||||
保留——不是持久边界镜像,因此不在本决策范围内:
|
||||
|
||||
- `agent/steering`——不是边界,因此不在本决策范围内(原始提案将其一并移除;在此会造成范围蔓延)。它镜像持久的 `steering/message` 控制记录,而非边界,后来由自己的后续决策移除:[移除 `agent/steering` 镜像 emit](2026-07-04-remove-agent-steering-mirror.md)。
|
||||
- `agent/stream-chunk`——实时 token 流。不在本决策范围内(它镜像持久的 `assistant/chunk`,而非边界),后来由自己的后续决策移除:[停止将 token 流镜像为 agent 事件](2026-07-02-remove-stream-chunk-mirror.md)。
|
||||
- `agent/created`、`agent/disposed`、`agent/status`、`agent/error`、`agent/queued`——不属于 transcript 数据的生命周期/控制事件。尤其是 `agent/queued`,它是在任何持久事件存在之前触发的 inbox 确认(取消的排队工作可能永远不会进入日志),所以有意只保留为实时事件。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **在同一个变更中一并移除 `agent/steering`**:否决,因为它是控制记录的镜像而非边界镜像。
|
||||
- **为 stdio UI 保留轮次镜像**:否决,因为 UI 可以渲染 `session/event` 并通过 id 映射恢复 agent 标签。
|
||||
- **将 `agent/steering` 一并移除**——原始提案的形状;作为范围蔓延被排除:它镜像持久的 `steering/message` 控制记录,而非边界,后来由[自己的决策](2026-07-04-remove-agent-steering-mirror.md)移除(`agent/stream-chunk` 也由 [stream chunk 镜像 Agent Note](2026-07-02-remove-stream-chunk-mirror.md) 移除)。
|
||||
- **为 stdio UI 保留 turn 镜像**——[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.md) 的原始立场;在此否决,因为 `dsh-ui-stdio` 是可随时丢弃的测试 REPL,而非承载关键约束的消费者,并且它改为根据 `session/event` 加自己的实时目标对象渲染边界。
|
||||
|
||||
## 后果
|
||||
|
||||
插件不再能从便捷的 `Agent` 优先事件中观察轮次/步骤边界,必须订阅 `session/event` 或自行维护 session 到 agent 的关联。这是可接受的取舍:边界消费方不应依赖一条可能与持久化日志产生漂移的第二事件源。
|
||||
插件不能再从便捷的 `Agent` 优先事件观察 turn/step 边界。它需要订阅 `session/event`;如果需要实时对象,则通过 `ctx.agents` 解析共享 id,或保留自己已经拥有的对象。这是可以接受的取舍:边界消费者不应依赖可能与持久日志发生漂移的第二条事件 feed。
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-06-20-unify-agent-and-session-id.md: 9cec898a2df9418b3533c776779c88fc50bc7dcb
|
||||
2026-06-20-unify-agent-and-session-id.zh.md: 39e78db0b886d1e0b13afe2337697a184af478ed
|
||||
2026-06-20-unify-agent-and-session-id.md: c55152f4f13fe0acb530503e84f465799007cff7
|
||||
2026-06-20-unify-agent-and-session-id.zh.md: 46e786a8f65b33e071fe689922ab396be90a184a
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-06-20-unify-agent-and-session-id.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A live agent/session pair needs one identity for registry routing, event sourcing, and persistence. Giving the factory independent `agentId` and `sessionId` inputs would permit pairings no production path can use, while forcing every consumer to choose or translate between two names for the same lifecycle.
|
||||
|
||||
+18
-20
@@ -1,42 +1,40 @@
|
||||
# RFC: 统一 agent id 与 session id
|
||||
# Agent Note: 统一 agent id 与 session id
|
||||
|
||||
Status: proposed
|
||||
Status: implemented
|
||||
|
||||
[English](2026-06-20-unify-agent-and-session-id.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
agent 工厂为每个活跃的 agent/session 对维护两个 id:`agentId`(`AgentRegistry` 的路由句柄)和 `sessionId`(事件溯源与持久化日志的标识)。`CreateAgentOptions` 接收两者;`ResumeAgentOptions` 接收 `agentId` 加 `resumeSessionId`;进程内 subagent 各自铸造两个独立的 UUID,尽管血缘关系另行记录。
|
||||
一个实时 agent(智能体)/session 对需要使用同一 identity 完成注册表路由、事件溯源和持久化。让 factory 接受相互独立的 `agentId` 和 `sessionId` 输入,会允许任何生产路径都无法使用的配对,同时迫使每个消费者为同一生命周期在两个名称之间选择或转换。
|
||||
|
||||
ACP(Agent Client Protocol)已经对这两个标识使用同一个值。它们在配置创建的 agent(智能体)、恢复的会话和进程内子 agent 中才出现分歧,但没有任何生产路径会把一个活跃 agent 重新关联到多个会话,或让一个会话经过多个 agent id。Stdio 保留 `labelBySession` 仅仅是为了从会话事件中恢复 agent 标签,而钩子同时暴露两个值让使用者自行调和。
|
||||
ACP(Agent Client Protocol)对两种 identity 使用相同值。Stdio 和 hook 也在 session 事件流上工作,并且直接需要对应的实时 agent;没有生产路径会把一个实时 agent 对象重新附着到多个 session,或通过多个 agent id 驱动一个 session。
|
||||
|
||||
[agent-scope 运行时](../../implemented/architecture/2026-07-12-agent-scope-runtime-design.md)没有与标识相关的保留状态:创建和恢复使用同一个 `AgentCreationTransaction`,两个注册表条目都使用相同的 final-entry 碰撞规则。分离的 id 并不会使活跃性、回滚或静默机制产生重复。统一后删除一个调用方提供的 id、每个进程内子 agent 的一个 UUID 以及剩余的转换路径,而不改变事务生命周期;同时使活跃 agent 注册表强制执行后台任务所有权所使用的会话标识。
|
||||
[agent 范围运行时](../architecture/2026-07-12-agent-scope-runtime-design.md)使用同一个 `AgentCreationTransaction` 执行创建和恢复,agent/session 条目共享相同的最终条目冲突规则。第二个 identity 并不代表单独的存活性、回滚或静止状态;它只会围绕同一事务增加 API 与转换状态。
|
||||
|
||||
`Session` 另外同时暴露 `Session.id` 和 `Session.header.id`,尽管构造时要求二者必须一致。持久化边界必须校验这个重复值,消费方必须在同一事实的两个归属位置之间做选择。
|
||||
Session identity 同样只有一个归属,即 `Session.header.id`;`Session.id` 是派生访问器,而非需要重复验证的独立状态。
|
||||
|
||||
## 提案
|
||||
## 决策
|
||||
|
||||
对 agent 注册表条目和 `session.header.id` 使用同一个 id。`CreateAgentOptions` 为两个最终条目接收一个标识;恢复操作以被恢复的 session id 注册 agent;subagent 创建铸造一个合并后的 id;`Session` 只保留一个标识归属位置。保留当前的事务、final-entry 碰撞检查、exact-entry 摘除、回滚与静默机制;仅移除唯一职责是在两个 id 之间做转换的 map 和字段。
|
||||
agent 的注册表 id 等于其 session id。`CreateAgentOptions` 接受一个 `sessionId`,同时用于两个最终注册表条目;恢复时以 `resumeSessionId` 注册 agent;进程内 subagent 创建使用子 session id;`Session.id` 则派生自 `header.id`。远程 ACP 运行没有本地 agent/session 对:它保留一个由父项铸造的生命周期 id,而子服务器线协议内的 session id 仅用于 ACP 调用。现有创建事务、最终条目冲突检查和精确条目分离语义保持不变;唯一职责是在本地 id 之间转换的 map 与字段已经消失。
|
||||
|
||||
配置驱动的路径必须先确定其恢复还是创建的策略。当前它使用一个稳定的 agent 标签加一个带 UUID 后缀的新 session id,以避免在下次运行时与已有的持久化日志碰撞。统一后,它必须明确选择:恢复一个固定 id、铸造一个新的合并 id,还是将该策略暴露出来;实现不得默默做出选择。
|
||||
配置驱动路径保留 `agents[].id` 作为稳定配置标签,而非实时路由 identity。普通的全新启动会铸造组合 id `${label}-session-${randomUUID()}`,使持久重启不会冲突。耦合应用可以预先铸造并传入精确的 `sessionId`:首次使用时创建它,而当持久化服务已经存在时,AgentLoop 重新挂载会在同一 identity 下恢复已物化历史。`resumeSessionId` 则要求已有的持久化 identity。两个精确 id 输入互斥。Stdio 使用“恢复或创建”形式,使配置创建的 agent 和 UI 在循环重载之间共享一个不透明 identity,而不是根据前缀猜测。日志可以使用稳定标签,而所有实时与持久查找都使用同一个 `SessionId`。
|
||||
|
||||
`agent/created` 和 `agent/disposed` 不在本提案范围内。它们是发布生命周期事件而非标识别名;移除它们需要单独的生产方-消费方审计与决策。
|
||||
`agent/created` 和 `agent/disposed` 保留。它们是成对的发布生命周期事件,而非 identity 别名;以后若发现没有消费者并要移除,必须先重新搜索,再提出独立提案。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**保留分离的路由标识与日志标识。** 一个稳定的配置 agent 标签配合一个新的对话,是这种区分的真实用途。如果确实需要该显示或路由标识,请否决本提案,转而显式强制 session id 唯一性,而不是把转换隐藏在另一个 map 中。
|
||||
**保持路由与日志 identity 分离。** 稳定的配置标签加全新的持久对话确实有用,但不需要两个实时 identity:标签可以继续作为配置/显示元数据,而每次运行的组合 `SessionId` 负责路由和持久化。保留两个 id 会让转换 map 持续存在,允许不可能的配对,却不会增加生命周期功能。
|
||||
|
||||
## 验收标准
|
||||
## 验证
|
||||
|
||||
- agent 创建/恢复与 subagent 创建只携带一个标识;`Session` 将其存储在一个位置。
|
||||
- 创建事务在不依赖标识相关生命周期状态的前提下,保留 final-entry 碰撞、exact-entry 摘除、回滚与静默保证。
|
||||
- ACP、stdio、钩子、bash 所有权、持久化与血缘关系无需进行 agent/session id 转换。
|
||||
- Agent 创建/恢复和 subagent 创建只携带一个 identity,`Session` 也只在一个位置存储它。
|
||||
- 创建事务继续覆盖最终条目冲突、精确条目分离、回滚和静止状态,无需 identity 特有的生命周期状态。
|
||||
- ACP、stdio、hook、bash 归属、持久化和 lineage 直接使用共享 `SessionId`。ACP subagent 后端在父命名空间中铸造其生命周期 id,因为子服务器返回的 session id 仅在服务器本地有效;ACP bridge 根据正向 session map 验证精确的 `Agent` 归属;JSON-RPC 只转发生命周期事件中由服务快照保存的 `local` 标记为 true 的事件,从带范围的事件 carrier 取得委托父项,并且不保留子 identity 或 lineage cache。
|
||||
- 配置驱动的恢复还是创建策略是显式的,并在持久化重启场景下得到覆盖。
|
||||
- `agent/created` 和 `agent/disposed` 仅在单独的生产方-消费方审计之后才变更。
|
||||
- 生产监听器搜索确认保留 `agent/created`/`agent/disposed` 及其发布语义。
|
||||
- 类型检查、覆盖率、快照、doc-sync、module-graph 校验、构建与 hygiene 全部通过。
|
||||
|
||||
## 风险
|
||||
## 后果
|
||||
|
||||
统一后将无法再拥有一个跨多个会话日志的稳定 actor 标识,包括未来可能出现的、在保留 actor 的同时切换会话的 handoff 或 fork 场景。重新引入该设计将需要一个新的显式 actor 标识。统一还使一个持久化的、可能由客户端选定的 session id 成为注册表句柄,并改变每个创建/恢复的调用点与 fixture(测试前置数据)。
|
||||
|
||||
配置重启策略是阻塞性的设计决策:固定的合并 id 可能与已有日志碰撞,而每次运行生成新 id 则放弃了稳定的配置标签。如果确实需要独立的 actor 标识或稳定标签/新会话的配对,请否决本提案,保留分离的 id 并加上显式的唯一性守卫。
|
||||
这排除了潜在的多 session actor 和 session 交接设计,并使由客户端选择、已持久化的 session identity 成为注册表 identity。如果独立路由 identity 成为真实需求,就需要显式的生命周期设计,而不是由调用方提供一对不受约束的值。
|
||||
|
||||
@@ -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
|
||||
2026-06-26-fsspec-style-fs-seam.md: 7b5e61481eeca9556de58cf3d6f8fe935b2eefb5
|
||||
2026-06-26-fsspec-style-fs-seam.zh.md: 72d37c196df99d110ea59c5108dc9de084669c8b
|
||||
2026-06-26-fsspec-style-fs-seam.md: d496f273e2635624e0ab8e70e06c8729563c5466
|
||||
2026-06-26-fsspec-style-fs-seam.zh.md: d6217e768fe4a11a7f6aacf8a17bb2e9e232a44d
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RFC: 拆分文件系统 seam——提供方文本变更操作与 `dsh-fs-policy` 插件
|
||||
# Agent Note: 拆分文件系统 seam——提供方文本变更操作与 `dsh-fs-policy` 插件
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
[filesystem-capability-seam](../../implemented/architecture/2026-06-17-filesystem-capability-seam.md) 中引入的文件系统能力目前让一个抽象的 `FileSystem` 服务承担两类不同的职责:
|
||||
[文件系统能力接缝](../architecture/2026-06-17-filesystem-capability-seam.md)中的文件系统能力目前让一个抽象 `FileSystem` 服务同时负责两项不同工作:
|
||||
|
||||
1. **提供方操作**——解析目标、stat/版本元数据、文本读取/流式读取、原子写入,以及受保护的字面编辑。
|
||||
2. **面向 agent(智能体)的策略**——行窗口、字面编辑语义,以及读后写/编辑的观测状态。
|
||||
@@ -15,7 +15,7 @@ Status: implemented
|
||||
|
||||
这还造成了一个真实的用户体验死胡同:窗口化读取记录 `view: partial`,而 partial 视图无法授权 `edit`。一个模型读取了大文件的第 100-150 行,如果想编辑第 120 行,就必须先获取一次 `full` 读取,而对于超过读取上限的文件这可能做不到。字面编辑实际上只需要新鲜度:被匹配的字节仍然来自模型所读取的那个版本即可。
|
||||
|
||||
旧 RFC 已经推迟了独立的 `@deepseek-ai/dsh-fs-policy` 包(package)。本 RFC 构建该层,并让 `ctx.fs` 贴近 fsspec 风格的存储原语(`info`/`cat`/`open`),但不将其变成完整的 fsspec。
|
||||
旧 Agent Note(agent 决策记录)已经推迟了独立的 `@deepseek-ai/dsh-fs-policy` 包。本 Agent Note 构建该层,使 `ctx.fs` 保持接近 fsspec 风格的存储原语(`info`/`cat`/`open`),但不把它变成完整的 fsspec。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -30,14 +30,14 @@ provider dsh-fs-local local implementation of ctx.fs
|
||||
|
||||
`dsh-tool-fs` 保持相同的面向模型的 `read`/`write`/`edit` schema。它是执行器:注入 `fs`(不是策略服务)并直接访问 `ctx.fs`,拥有读取窗口化逻辑,并分发 `fs/*` 事件以便 `dsh-fs-policy` 进行门控和记录。
|
||||
|
||||
本 RFC 决定了四层拆分、提供方契约和新鲜度策略。工具↔策略的耦合方式随后由[事件门控 RFC](../architecture/2026-06-26-file-context-as-event-gate.md) 细化:`dsh-fs-policy` 是一个门控插件,通过 `fs/*` 事件参与而非提供 `ctx.fileContext` 方法服务,因此工具不与它产生方法耦合,读取窗口化与 fs I/O 留在 `dsh-tool-fs` 中。本文描述的是最终落地的事件门控形态;提供方的版本守卫是可选的(省略 = 无条件裸提供方)。
|
||||
本 Agent Note 决定了四层拆分、provider 契约和新鲜度策略。随后,[事件门禁 Agent Note](../architecture/2026-06-26-file-context-as-event-gate.md) 细化了工具↔策略耦合:`dsh-fs-policy` 是通过 `fs/*` 事件参与的门禁插件,而非 `ctx.fileContext` 方法服务,因此工具不会在方法层与其耦合;读取窗口和 fs I/O 位于 `dsh-tool-fs`。本文描述已经落地的事件门禁形状;provider 的版本守卫可选(省略即无条件裸 provider)。
|
||||
|
||||
## 提供方契约
|
||||
|
||||
`@deepseek-ai/dsh-fs` 收缩为提供方文本 IO 加受保护的文本变更:
|
||||
|
||||
```ts ignore-check
|
||||
abstract resolve(path: string): Promise<FsTarget>
|
||||
abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget>
|
||||
abstract stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>
|
||||
abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
|
||||
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
|
||||
@@ -65,11 +65,11 @@ type FsWriteIntent =
|
||||
|
||||
这是一个*文本存储* seam,刻意比字节级 fsspec(`cat`/`open` 返回原始字节)高半个层次。UTF-8 解码、二进制/NUL 拒绝、受保护的全文件写入和受保护的字面文本编辑都在提供方内完成,因此策略层从不接触原始字节、不重新实现跨分片解码、也不将陈旧检查与变更临界区分离。面向模型的概念仍然不下沉到提供方:行窗口、带行号的行、渲染的页脚、观测状态存储都不会泄漏下去。
|
||||
|
||||
从 `dsh-fs` 中删除的内容:`readPage`、`FsExpectation`、`FsView`、`FsStateSource`、`FsReadRequest`、`FsTextLine`、行/窗口常量、`formatReadBody`,以及观测状态 `WeakMap`。`applyEdit` 被更窄的提供方原语 `editText` 取代,后者的契约是版本守卫的字面文本变更,而非策略层的读取授权。`FS_PARTIAL_OBSERVATION` 错误码也从 `FsErrorCode` 分类体系中移除:新鲜度授权没有 partial/full 之分,因此没有什么能触发它。`FsTargetKey` 和 `FsVersion` 按照既有的 [branded-ids RFC](../../implemented/architecture/2026-06-20-branded-ids.md) 成为品牌化的不透明 id。
|
||||
从 `dsh-fs` 删除:`readPage`、`FsExpectation`、`FsView`、`FsStateSource`、`FsReadRequest`、`FsTextLine`、行/窗口常量、`formatReadBody` 和 observed-state `WeakMap`。`applyEdit` 由更窄的 provider 原语 `editText` 取代,其契约是带版本守卫的字面文本变更,而非策略层读取授权。`FS_PARTIAL_OBSERVATION` code 也从 `FsErrorCode` 分类中移除:新鲜度授权没有部分/完整之分,因此没有任何路径会抛出它。`FsTargetKey` 和 `FsVersion` 按现有[品牌化 id Agent Note](../architecture/2026-06-20-branded-ids.md) 成为品牌化不透明 id。
|
||||
|
||||
## 策略契约
|
||||
|
||||
`@deepseek-ai/dsh-fs-policy` 是一个插件,不是服务:它不注册任何 `ctx.*` 键,也不注入任何东西。它拥有写入/编辑新鲜度策略和观测状态,这些不属于 `FileSystem` 提供方基类(否则沙箱/远程后端会继承它无需承载的面向模型的观测策略)。它通过执行器分发的 `fs/*` 事件门控贡献该策略。(本 RFC 最初提出了一个具体的 `ctx.fileContext` 方法服务,带有 `read`/`write`/`edit` 方法;[事件门控 RFC](../architecture/2026-06-26-file-context-as-event-gate.md) 将其改造为此处描述的门控插件,使工具从不与策略产生方法耦合。)
|
||||
`@deepseek-ai/dsh-fs-policy` 是插件,而非服务:它不注册任何 `ctx.*` 键,也不注入任何内容。它拥有不应位于 `FileSystem` provider 基类上的写入/编辑新鲜度策略和 observed state(否则 sandbox/远程后端会继承不该由其承载的面向模型观察策略)。它通过 executor 分派的 `fs/*` 事件门禁贡献该策略。(本 Agent Note 最初提议带有 `read`/`write`/`edit` 方法的具体 `ctx.fileContext` 服务;[事件门禁 Agent Note](../architecture/2026-06-26-file-context-as-event-gate.md) 将其细化为本文所述插件,使工具永远不会在方法层与策略耦合。)
|
||||
|
||||
观测状态以 `WeakMap<owner, Map<targetKey, FsVersion>>` 的形式存放于此。当且仅当 owner 读取、写入或编辑过该目标时,条目才存在(每次成功都会发出 `fs/observed`),因此条目的存在*本身就是*先前观测的记录——没有单独的 `hasRead` 标志。owner 从不透明的事件 actor(`{ agent?: { session? } }`)结构化派生,该形状定义在 `dsh-fs-policy` 中而非 `dsh-fs` 中。
|
||||
|
||||
@@ -99,7 +99,7 @@ type FsWriteIntent =
|
||||
|
||||
## 取代
|
||||
|
||||
本 RFC 逆转了 [filesystem-capability-seam](../../implemented/architecture/2026-06-17-filesystem-capability-seam.md) 中的两项决策,并收窄了第三项:
|
||||
本 Agent Note 推翻[文件系统能力接缝](../architecture/2026-06-17-filesystem-capability-seam.md)中的两项决策,并收窄第三项:
|
||||
|
||||
- 读后写/编辑策略从 `ctx.fs` 移出,进入 `dsh-fs-policy` 插件(通过 `fs/*` 事件门控)。
|
||||
- 文本读取不再返回后端编号的行记录或 `full`/`partial` 视图;授权基于版本新鲜度,因此窗口化读取在文件未变时即可授权编辑。
|
||||
@@ -113,12 +113,12 @@ type FsWriteIntent =
|
||||
|
||||
## 后续扩展
|
||||
|
||||
该 seam 后来由 [Add direct directory listing to the filesystem seam](../architecture/2026-07-03-filesystem-directory-listing-seam.md) 扩展了直接目录列表功能。该后续工作单独跟踪,以使本 RFC 的验收标准继续描述最初交付的 fsspec 风格重构。
|
||||
后来,[为文件系统接缝添加直接目录列表](../architecture/2026-07-03-filesystem-directory-listing-seam.md)进一步扩展了该接缝。该后续工作单独跟踪,使本 Agent Note 的验收标准继续描述最初落地的 fsspec 风格改造。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **字节级 fsspec(`cat`/`open` 返回原始字节)**:否决。该 seam 刻意定位为文本存储,比字节级高半个层次,这样 UTF-8 解码、二进制/NUL 拒绝和受保护的文本变更只在提供方实现一次,策略层从不接触原始字节,也不将陈旧检查与变更临界区分离。
|
||||
- **具体的 `ctx.fileContext` 方法服务**:本 RFC 最初的策略形态;被[事件门控 RFC](../architecture/2026-06-26-file-context-as-event-gate.md) 改造为门控插件,使工具从不与策略产生方法耦合。
|
||||
- **具体的 `ctx.fileContext` 方法服务**——本 Agent Note 最初的策略形状;[事件门禁 Agent Note](../architecture/2026-06-26-file-context-as-event-gate.md) 将其重做为门禁插件,使工具永远不会在方法层与策略耦合。
|
||||
- **在提供方保留 `readPage` 和 `full`/`partial` 视图授权**:「取代」一节所逆转的重构前形态。视图完整性不是编辑安全所需的,版本新鲜度才是;而视图规则使超过读取上限的大文件无法编辑。
|
||||
|
||||
## 后果
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-02-remove-stream-chunk-mirror.md: 74843cf49c043d46d44266a7bb0d8c953a749b6f
|
||||
2026-07-02-remove-stream-chunk-mirror.zh.md: 1b460b4600442535d2572770449ce4dbcc836fe8
|
||||
2026-07-02-remove-stream-chunk-mirror.md: 5dd816940a4b2c2b63980e01f1bd4e0aac53a3e2
|
||||
2026-07-02-remove-stream-chunk-mirror.zh.md: d4bfe540ecb3f938721f2b387f302caf163b0f94
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
# RFC: 停止将 token 流镜像为 agent 事件
|
||||
# Agent Note: 停止将 token 流镜像为 agent 事件
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -19,7 +19,7 @@ ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
|
||||
|
||||
实时发射相比会话事件唯一多出的东西是实时的 `Agent` 句柄,而唯一的消费方直接丢弃了它(其处理函数签名为 `(_agent, _turn, _step, chunk)`)。
|
||||
|
||||
这与[边界镜像移除](2026-06-20-remove-agent-boundary-mirror-events.md)为 turn/step 边界消除的重复如出一辙:消费方对同一个持久事实有两个真源,每次变更都要同时修改两处。那份 RFC 将 chunk 流推迟处理(「`assistant/chunk` 的持久化仍然是承重的,因此 chunk 流后续可以作为镜像来评估,但那是一个独立决策」),而非一并纳入。本 RFC 即是那个独立决策。
|
||||
这与[移除边界镜像](2026-06-20-remove-agent-boundary-mirror-events.md)为 turn/step 边界消除的重复相同:消费者面对同一持久事实的两个事实来源,每次变更都必须同时触及两者。该 Agent Note(agent 决策记录)没有把 chunk 流一并纳入,而是推迟处理(“`assistant/chunk` 持久化仍承载关键约束,所以以后可以将 chunk 流作为镜像评估,但那是一项独立决策”)。本 Agent Note 就是那项独立决策。
|
||||
|
||||
推迟所依赖的前提已经明确:chunk 持久化是权威的,且将保留。停止持久化 chunk、仅保留瞬态实时流事件的提案已被[否决](../../rejected/simplification/2026-06-20-assembled-assistant-messages-only.md)——高保真回放、部分失败的流以及快照回放都依赖持久化的 `assistant/chunk` 序列。因此 `session/event` 上的 `assistant/chunk` 是持久的、承重的 token 流,而 `agent/stream-chunk` 是它的纯冗余镜像。
|
||||
|
||||
@@ -27,15 +27,15 @@ ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
|
||||
|
||||
从 agent 事件分类体系中移除 `agent/stream-chunk`。token 流通过 `session/event` 以 `assistant/chunk` 的形式读取——持久化与回放已经使用的正是同一个序列。`session/event` 是唯一的实时 transcript(文本记录)流(assistant chunk、turn/step 边界、工具活动、todo)。
|
||||
|
||||
**消费方。** 唯一重要的生产消费方——ACP 桥接(`dsh-acp`,面向编辑器的真实流式输出接口)——已经从 `session/event` 渲染 `assistant/chunk`,从未使用 `agent/stream-chunk`,因此不受影响。stdio UI(`dsh-ui-stdio`,一个一次性的测试 REPL)是唯一的实时消费方;它在边界迁移时已经有了 `session/event` 监听器,因此其 chunk 渲染被折叠进该监听器作为 `assistant/chunk` 分支。合并为一个监听器还消除了一个潜在隐患:`inReasoning` dim-SGR 标志此前在两个独立监听器(`agent/stream-chunk` 和 `session/event`)之间共享,chunk 与边界在该标志上竞争时没有确定的顺序;单一监听器按追加顺序处理,使交错变为确定性的。
|
||||
**消费方。** 唯一重要的生产消费方——ACP(Agent Client Protocol)桥接(`dsh-acp`,面向编辑器的真实流式输出接口)——已经从 `session/event` 渲染 `assistant/chunk`,从未使用 `agent/stream-chunk`,因此不受影响。stdio UI(`dsh-ui-stdio`,一个一次性的测试 REPL)是唯一的实时消费方;它在边界迁移时已经有了 `session/event` 监听器,因此其 chunk 渲染被折叠进该监听器作为 `assistant/chunk` 分支。合并为一个监听器还消除了一个潜在隐患:`inReasoning` dim-SGR 标志此前在两个独立监听器(`agent/stream-chunk` 和 `session/event`)之间共享,chunk 与边界在该标志上竞争时没有确定的顺序;单一监听器按追加顺序处理,使交错变为确定性的。
|
||||
|
||||
## 范围
|
||||
|
||||
移除:`agent/stream-chunk`。
|
||||
|
||||
未触及:
|
||||
- `assistant/chunk`(持久会话事件)——权威的 token 流,原样保留。本 RFC 移除的是实时镜像,而非持久化(持久化移除提案已被单独否决,见上文)。
|
||||
- `agent/steering`——本决策未触及(它是控制信号,不是 token 流)。其持久孪生事件是 `steering/message`,镜像发射由其自身的后续 RFC 移除:[移除 `agent/steering` 镜像发射](2026-07-04-remove-agent-steering-mirror.md)。
|
||||
- `assistant/chunk`(持久 session 事件)——权威 token 流,原样保留。本 Agent Note 移除的是实时镜像,而非持久化(移除持久化的提案已单独遭到拒绝——见上文)。
|
||||
- `agent/steering`——本决策未触及(它是控制信号,不是 token 流)。其持久孪生事件是 `steering/message`,镜像发射由其自身的后续 Agent Note 移除:[移除 `agent/steering` 镜像发射](2026-07-04-remove-agent-steering-mirror.md)。
|
||||
- `agent/status`、`agent/error`、`agent/created`/`agent/disposed`、`agent/queued`、`agent/session-start`——生命周期/控制事件,不是 transcript 数据,也没有持久副本。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
@@ -44,4 +44,4 @@ ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
|
||||
|
||||
## 后果
|
||||
|
||||
插件不再能通过以 `Agent` 为首参的事件观察 token delta。它需要订阅 `session/event` 并过滤 `assistant/chunk`(如需 `Agent` 句柄,可通过 `agent/created`/`agent/disposed` 构建的 session-id→agent 映射恢复,与边界消费方已有的做法完全一致)。没有任何生产消费方在 chunk 时需要实时的 `Agent`;这与边界镜像移除所做的权衡相同,是可接受的。
|
||||
插件不能再从 `Agent` 优先事件观察 token 增量。它需要订阅 `session/event`、过滤 `assistant/chunk`,并在需要时通过 `ctx.agents.get(session.id)` 直接查找对应的实时 handle。没有生产消费者需要在 chunk 时刻取得实时 `Agent`;这与移除边界镜像所作的取舍相同,均可接受。
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-drop-image-content-block.md: 145f805cbe335d3b8275bef6a2bb1fcbe1bd3df3
|
||||
2026-07-04-drop-image-content-block.zh.md: ba77caeba4cb1fdd3ff95f4cd498c87cdf1aa227
|
||||
2026-07-04-drop-image-content-block.md: 566803ab5b9f213b7dc87fcf779ed7a963d988fd
|
||||
2026-07-04-drop-image-content-block.zh.md: 27282bbf8768cafaf3fff696559b81756af5a45e
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除 `image` 内容块,直到有路径能真正处理它
|
||||
# Agent Note: 移除 `image` 内容块,直到有路径能真正处理它
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
`ImageBlock`(`packages/llm/llm/src/types.ts`)没有任何生产环境的生产者,而每条路径上的每个消费方都将其丢弃:deepseek 适配器的序列化器跳过 image 块(这是文档中注明的 MVP 限制);pi-ai 转换器因无法表示而跳过;ACP 编解码器既不宣告 image prompt 能力、也不向外转发 image 块,并且会拒绝入站的 image prompt 内容;压缩(compaction)估算器对其收取一个固定 token 常量并渲染为 `[image]`。此时构造的 `ImageBlock` 会在协议格式(wire format)上静默消失——词汇宣告了一种没有任何路径兑现的能力,这正是 AGENTS.md 防御性模式所警告的静默数据丢失形态。唯一的构造调用出现在测试中,用于覆盖 skip/drop/estimate 分支。
|
||||
`ImageBlock`(`packages/llm/llm/src/types.ts`)没有任何生产环境的生产者,而每条路径上的每个消费方都将其丢弃:deepseek 适配器的序列化器跳过 image 块(这是文档中注明的 MVP 限制);pi-ai 转换器因无法表示而跳过;ACP(Agent Client Protocol)编解码器既不宣告 image prompt 能力、也不向外转发 image 块,并且会拒绝入站的 image prompt 内容;压缩(compaction)估算器对其收取一个固定 token 常量并渲染为 `[image]`。此时构造的 `ImageBlock` 会在协议格式(wire format)上静默消失——词汇宣告了一种没有任何路径兑现的能力,这正是 AGENTS.md 防御性模式所警告的静默数据丢失形态。唯一的构造调用出现在测试中,用于覆盖 skip/drop/estimate 分支。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -22,7 +22,7 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
RFC 记录之外没有任何地方构造 harness 的 `ImageBlock`。ACP 独立的入站 image 拒绝仍有测试覆盖,适配器、编解码器和压缩的默认分支则通过插件定义的块类型来覆盖。
|
||||
除 Agent Note(agent 决策记录)之外,没有任何地方构造 harness `ImageBlock`。ACP 独立的入站图像拒绝路径仍有测试;adapter、codec 和压缩的默认分支则使用插件定义的块类型覆盖。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-drop-inert-request-knobs.md: 86d0dfefe1bdfb0c49b5b9080935441d16dd2223
|
||||
2026-07-04-drop-inert-request-knobs.zh.md: f7d969378af7e070ecee82e8ea1a569c61208208
|
||||
2026-07-04-drop-inert-request-knobs.md: 06fa6c1c539f9ff0cfabf76bc41c53800bd46c8c
|
||||
2026-07-04-drop-inert-request-knobs.zh.md: 69a8b0ca498f9fe11df5eb1f3207d88f66a7b70b
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除 `GenerateOptions.prefill` 与 `ToolSchema.strict`——无端到端可用路径的请求旋钮
|
||||
# Agent Note: 移除 `GenerateOptions.prefill` 与 `ToolSchema.strict`——无端到端可用路径的请求旋钮
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -15,10 +15,10 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
- 从 `GenerateOptions` 中移除 `prefill`,同时移除两个适配器的 UNSUPPORTED 守卫、固定这些 throw 的测试、[core.md](../../../core-data-structures/core.md) 中的粘贴行,以及适配器 README 中记录拒绝行为的行。实操手册(cookbook)中的 UNSUPPORTED 指引([adding-an-llm-adapter.md](../../../cookbook/adding-an-llm-adapter.md))改为泛化表述——你的 provider 无法兑现的 `GenerateOptions` 字段应抛出 `LlmError(..., 'UNSUPPORTED')`——而不再以 prefill 为例。[内容块词汇 RFC](../architecture/2026-06-11-content-block-vocabulary.md) 的后果部分将 prefill 记录为「受 producer 门控」而非「已有归属」,依据 [implemented/AGENTS.md](../AGENTS.md)。
|
||||
- 从 `GenerateOptions` 中移除 `prefill`,同时移除两个 adapter 的 UNSUPPORTED 守卫、固定抛错行为的测试、[core.md](../../../../docs/core-data-structures/core.md) 中的粘贴行,以及记录该拒绝行为的 adapter README 表格行。cookbook 中的 UNSUPPORTED 指导([adding-an-llm-adapter.md](../../../../docs/cookbook/adding-an-llm-adapter.md))改为通用表述规则——provider 无法遵守的 `GenerateOptions` 字段应抛出 `LlmError(..., 'UNSUPPORTED')`——而不再以 prefill 为例。[内容块词汇 Agent Note(agent 决策记录)](../architecture/2026-06-11-content-block-vocabulary.md)的后果按照 [implemented/AGENTS.md](../AGENTS.md),将 prefill 记录为由生产者门控,而不是已有归属。
|
||||
- 从 `ToolSchema`、`DefineToolOptions`、`defineTool`、`schemas()` 允许列表、deepseek 序列化分支及其 wire-type 字段,以及 tool-catalog 渲染器的 `Strict:` 行中移除 `strict`。pi-ai 的 payload 修补逻辑简化为对 pi-ai 自身逐工具 strict 默认值的无条件清除(pi-ai 在每个序列化的工具上打 `strict: false`;手写的孪生适配器不发送此字段,因此清除逻辑为保持协议格式对等而保留,由其序列化器测试固定)。setter 测试和 core.md 粘贴行已移除;`GenerateOptions` 与 `ToolSchema` 在 `scripts/type-equiv.manifest.json` 中保留各自的行,因为两个类型只是少了一个字段,本身仍然存在。
|
||||
|
||||
本 RFC 有意不触碰 `temperature`、`stop` 或 `maxTokens`:它们在两个适配器中都被端到端地兑现,是 `agent/request` 上请求变更钩子插件的自然首选目标。
|
||||
本 Agent Note 刻意不触及 `temperature`、`stop` 或 `maxTokens`:两个 adapter 都会端到端遵守它们,而且它们自然是 `agent/request` 上修改请求的 hook 插件首批目标。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -28,7 +28,7 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
`rg prefill` 仅返回 RFC 记录(本 RFC 与[内容块词汇 RFC](../architecture/2026-06-11-content-block-vocabulary.md) 中 producer-gated 的后果);在 tool-schema 范围内执行 `rg strict` 仅返回本 RFC、保留的 pi-ai 清除逻辑,以及 `strictEqual` 等无关文本。两个适配器的契约测试在移除守卫后通过,pi-ai 修补逻辑仍然清除库的 strict 默认值——协议格式对等由其序列化器测试固定。
|
||||
`rg prefill` 只返回 Agent Note 记录(本文及[内容块词汇 Agent Note](../architecture/2026-06-11-content-block-vocabulary.md)中由生产者门控的后果);限定在工具 schema 范围内的 `rg strict` 只返回本 Agent Note、保留下来的 pi-ai 清理逻辑,以及 `strictEqual` 等无关正文。两个 adapter 的契约测试都能在没有守卫的情况下通过,pi-ai 修正仍会清理库的 strict 默认值——其 serializer 测试固定了线协议一致性。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-drop-unconsumed-web-observation-surface.md: ba97076eef385cd218517f233ed44f86d3f7eb7a
|
||||
2026-07-04-drop-unconsumed-web-observation-surface.zh.md: 83c2e786d4edde7b7c94cf2c028b14e20da842b4
|
||||
2026-07-04-drop-unconsumed-web-observation-surface.md: 5b1cb1307c63ef7c298200ee1655119026b9ebf5
|
||||
2026-07-04-drop-unconsumed-web-observation-surface.zh.md: a48e74eeb0f718c3511e2b1ad8e2dcde635350e1
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除未被消费的 web 观测接口——`providers-change` 事件与 status 方法
|
||||
# Agent Note: 移除未被消费的 web 观测接口——`providers-change` 事件与 status 方法
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -9,11 +9,11 @@ Status: implemented
|
||||
`WebService` 暴露了一组没有任何生产代码观测的观测接口:
|
||||
|
||||
- **`web/providers-change`**(`packages/web/web/src/index.ts`)在每次 provider 注册和 dispose(资源释放)时声明并发出,且每个注册 effect 的回滚 yield 被刻意排在 emit 之前,唯一目的是让抛出异常的 change listener 能回退注册。在该包自身的两个单元测试之外没有任何 listener(其中一个测试的存在仅仅是为了固定那个回滚顺序)。
|
||||
- **`searchStatus()` / `fetchStatus()` 与 `WebCapabilityStatus` 联合类型**(同一个包)没有任何生产调用方:`dsh-tool-web` 通过 `ctx.web.search()`/`fetch()` 直接执行,并将不可用性表现为 seam 在执行时抛出的结构化 `WebError` 错误码(`packages/web/tool-web/src/search.ts`、`packages/web/tool-web/src/fetch.ts`);唯一的 status 调用方是 web 包自身的测试。`packages/web/tool-web/README.md` 和 [architecture.md](../../../architecture.md) 中的行文声称该工具「只读取聚合的 `searchStatus()`/`fetchStatus()`」——这是一处漂移,仅因没有机制检查行文与调用点的一致性而幸存。
|
||||
- **`searchStatus()` / `fetchStatus()` 与 `WebCapabilityStatus` 联合类型**(同一包)没有生产调用方:`dsh-tool-web` 直接通过 `ctx.web.search()`/`fetch()` 执行,并把不可用性呈现为接缝在执行时抛出的结构化 `WebError` code(`packages/web/tool-web/src/search.ts`、`packages/web/tool-web/src/fetch.ts`);唯一的 status 调用方是 web 包自己的测试。`packages/web/tool-web/README.md` 和 [architecture.md](../../../../docs/architecture.md) 中的正文声称工具“只读取聚合的 `searchStatus()`/`fetchStatus()`”——这种漂移之所以存续,只是因为没有机制对照调用位置检查正文。
|
||||
|
||||
seam 自身的设计使这两个接口天然没有消费方:工具注册跟随产品 ENABLEMENT 而非 provider 可用性(`packages/web/tool-web/src/index.ts`),provider 选择在执行时解析且从不缓存——因此没有需要失效的缓存、没有需要重算的注册集合、也没有调用方需要一个有别于「执行并路由结构化错误」的可用性探测。HMR(热模块替换)清理由 effect disposer 自身承载。
|
||||
|
||||
这与 [移除未被消费的 `llm/adapter-change` 事件](../../implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md) 如出一辙:那个 RFC 从 `LlmService` 中移除了相同的通知形态、相同的 rollback-before-emit 机制和相同的 listener-throw 测试。该 RFC 的保留/裁剪判据——为 `tools/change` 保留其合理的面向用户的工具列表消费方,裁剪启动时的后端注册表信号——将 web provider 注册表明确归入裁剪一侧;status 方法是同一判断应用于 pull 接口而非 push 接口。
|
||||
这与[删除无人消费的 `llm/adapter-change` 事件](2026-06-20-drop-unconsumed-llm-adapter-change-event.md)相呼应;后者从 `LlmService` 移除了相同的通知形状、相同的 emit 前回滚机制和相同的监听器抛错测试。该 Agent Note(agent 决策记录)的保留/删除标准是:为可能面向用户的工具列表消费者保留 `tools/change`,删除启动时后端注册表信号。按这一标准,web provider 注册表明确属于删除一侧;status 方法则是把同一判断应用于拉取表面,而非推送表面。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -23,11 +23,11 @@ seam 自身的设计使这两个接口天然没有消费方:工具注册跟随
|
||||
|
||||
### 为什么不保留?
|
||||
|
||||
web seam RFC 有意指定了两者——事件作为最小的 HMR 可见性信号,status 方法作为工具的聚合诊断——且未来的 provider 状态面板是可以想象的。但同一 RFC 的其他设计选择使它们失去了消费方:按需派生的选择与基于 enablement 的注册使得没有消费方能需要这两者;已交付的工具展示了真实模式(执行并路由结构化错误);漂移的 README 语句表明承诺的消费方从未实现。按 AGENTS.md「RFC 是提案,不是金科玉律」的原则,这些是该提案中代码已证明过度设计的部分;未来的观测者按其实际消费的需求重新引入最小的信号或查询,由该消费方塑造其形态。
|
||||
web 接缝 Agent Note 刻意规定了两者——事件作为最小 HMR 可见性信号,status 方法作为工具的聚合诊断——未来也可以设想 provider 状态面板。但同一 Agent Note 的其他选择让它们失去了生存条件:调用时派生选择和基于启用状态的注册,使任何消费者都不可能需要其中任一项;已发布工具展示了真实模式(执行并路由结构化错误);发生漂移的 README 句子则表明承诺中的消费者从未出现。按照 AGENTS.md 所述“Agent Note 是提案,而非绝对真理”,代码后来证明提案中的这些部分超出了需要;未来的观察者应根据真实消费者的形状,重新引入它实际消费的最小信号或查询。
|
||||
|
||||
## 验证
|
||||
|
||||
在 RFC 历史之外不再有 `providers-change`、`searchStatus`、`fetchStatus` 或 `WebCapabilityStatus` 的拼写残留;catalog 是最新的(`verify-cordis-catalog` 绿色);注册/释放的 HMR 安全测试通过执行行为证明清理正确;tool-web README 与 architecture 段落描述了工具实际拥有的执行时错误路由契约。
|
||||
除 Agent Note 历史外,不再存在 `providers-change`、`searchStatus`、`fetchStatus` 或 `WebCapabilityStatus` 拼写;目录保持新鲜(`verify-cordis-catalog` 为绿色);注册/释放 HMR 安全性测试通过执行行为证明清理;tool-web README 和架构段落也描述了工具实际拥有的执行时错误路由契约。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -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
|
||||
2026-07-04-fold-stdio-ui-helper.md: ab42f1d131f6c657edf078d953c646b7970e9782
|
||||
2026-07-04-fold-stdio-ui-helper.zh.md: 2ad6abf3d5fb5ae6cdd852f8b5ec7e061e62b8ed
|
||||
2026-07-04-fold-stdio-ui-helper.md: 01165df19942e8e0adf84ede3a371f53ad12e464
|
||||
2026-07-04-fold-stdio-ui-helper.zh.md: 5efaf47694092b4ba02208330dccf6b9bb086fca
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-04-fold-stdio-ui-helper.zh.md)
|
||||
|
||||
The later [redundant-agent removal](2026-07-20-remove-stdio-and-echo-agents.md) supersedes this package-placement decision and removes the folded package, app, and line-oriented surface entirely.
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# RFC: 将 stdio UI 辅助模块折入 stdio 应用
|
||||
# Agent Note: 将 stdio UI 辅助模块折入 stdio 应用
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-04-fold-stdio-ui-helper.md) | 中文
|
||||
|
||||
后来的[冗余 agent(智能体)移除](2026-07-20-remove-stdio-and-echo-agents.md)取代了这项包放置决策,并完整移除合并后的包、应用和面向行的表面。
|
||||
|
||||
## 问题
|
||||
|
||||
readline UI 曾是一个完整的包(`packages/support/` 下的 `@deepseek-ai/dsh-ui-stdio`),其唯一的运行时导入方是应用包 `@deepseek-ai/dsh-stdio-demo`。示例通过加载应用来使用 readline UI,从不自行组合该辅助模块;仓库中所有其他引用都是因为包边界存在而存在的机械性或描述性表面:manifest(元数据清单)与 tsconfig 条目、生成的 module-graph 行、依赖图与 README 行,以及命名该包的文档注释。ui 组 README 记录了 support 放置的理由("主要为示例和覆盖率门禁而存在,`ui/` 保留给作为产品交付的界面"),这留下了一个持续的张力:一个已交付的产品应用依赖一个被明确标注为非产品表面的 support 包。
|
||||
@@ -12,15 +14,15 @@ readline UI 曾是一个完整的包(`packages/support/` 下的 `@deepseek-ai/
|
||||
|
||||
## 决策
|
||||
|
||||
该辅助模块作为终端通道插件存放在 `@deepseek-ai/dsh-stdio` 中(`packages/ui/stdio/src/index.ts`):`createStdioChat`、其 `StdioRuntime` 测试 seam 及单元测试(`packages/ui/stdio/tests/stdio.spec.ts`、`readline.spec.ts`)一并迁入,因此 EOF 处理、渲染、dispose(资源释放)以及管道/TTY 行为在按文件覆盖率门禁下仍有单元测试覆盖,且无需劫持进程全局对象。该模块保留具名的 `name`/`inject`/`Config`/`apply` 导出形状——即应用的 `ctx.plugin(uiStdio, …)` 挂载所消费的契约——而 `examples/echo-agent` 与 `examples/coding-agent` 中的 keyless Loader 路径冒烟测试继续证明组合树能通过真实 Loader 启动(stdio 包的插件形状单元测试套件固定了显式的 `unwrapExports` 断言,因为缺少 `inject` 的 bundle 会跳过一个意外的 default 导出而不是崩溃)。
|
||||
当时,该辅助函数移入 `@deepseek-ai/dsh-stdio`,成为终端通道插件。`createStdioChat`、其 `StdioRuntime` 测试接缝和单元测试随之一同迁移,使 EOF 处理、渲染、释放以及管道/TTY 行为继续受逐文件覆盖率门禁约束,而不会劫持进程全局量。该模块保留应用挂载所消费的具名 `name`/`inject`/`Config`/`apply` 导出形状;当时的 Echo 和 REPL Loader 冒烟证明组合树,插件形状套件则固定显式 `unwrapExports` 行为。上方取代本文的移除记录负责当前包和示例状态。
|
||||
|
||||
`packages/support/ui-stdio` 包已移除:manifest、tsconfig 引用、module-graph 行与 README 行均已删除;曾命名该包的文档注释(示例 e2e 模块文档、`packages/README.md`、support 与 todo README、[ui 组 README](../../../../packages/ui/README.md))现在描述的是包内模块。
|
||||
早期的支持辅助包已移除:其清单、tsconfig 引用、模块图行和 README 行均已消失,其余文档改为描述包内模块。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
### 为什么不将其提升到 `ui/` 而是折入?
|
||||
|
||||
提升可以解决 support 与 product 之间的错位,同时保留边界——只有在 readline UI 是一个可独立替换的集成或有第二个组合方时才是正确选择,而消费方普查表明两者皆非。结构化的 ACP 桥接保留为独立包,因为它是具有自身契约和快照层级的产品协议表面;readline 辅助模块只是一个应用前门的脚手架。在发布前重新拆分成本很低:如果将来有第二个产品应用需要 readline UI,届时再拆出来,由那个消费方来塑造包契约。
|
||||
提升可以解决 support 与 product 之间的错位,同时保留边界——只有在 readline UI 是一个可独立替换的集成或有第二个组合方时才是正确选择,而消费方普查表明两者皆非。结构化的 ACP(Agent Client Protocol)桥接保留为独立包,因为它是具有自身契约和快照层级的产品协议表面;readline 辅助模块只是一个应用前门的脚手架。在发布前重新拆分成本很低:如果将来有第二个产品应用需要 readline UI,届时再拆出来,由那个消费方来塑造包契约。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-prune-producerless-vocabulary-variants.md: f1b80e35b9004e40fb0ffdd08310310848912d09
|
||||
2026-07-04-prune-producerless-vocabulary-variants.zh.md: 9e7b55256ba5bda0474fd9056eea9a96beaf8096
|
||||
2026-07-04-prune-producerless-vocabulary-variants.md: 34492e6906cd2d795f880310b1bcd120e3953fcf
|
||||
2026-07-04-prune-producerless-vocabulary-variants.zh.md: 710f64d7e848bcabb8a1bc0c0c8be9af7b2ca1b0
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-04-prune-producerless-vocabulary-variants.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The merge-extensible vocabulary maps are designed to grow by declaration merging, and the codebase already states the admission policy on `TurnEndReasonMap` (`packages/core/session/src/types.ts`): a variant like `refusal` is "deliberately omitted until" an adapter or loop first emits it. Three declared vocabulary items violated that policy — each had no producer and no consumer, and two had not even a test:
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
# RFC: 裁剪无生产者的词汇变体(块缓存提示、`agent` 消息来源、`continuation` 轮次触发器)
|
||||
# Agent Note: 裁剪无生产者的词汇变体(块缓存提示、`agent` 消息来源、`continuation` 轮次触发器)
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -8,13 +8,13 @@ Status: implemented
|
||||
|
||||
可合并扩展的词汇映射表设计上通过声明合并来增长,代码库已在 `TurnEndReasonMap`(`packages/core/session/src/types.ts`)上明确了准入策略:像 `refusal` 这样的变体「在适配器或循环首次发出它之前,有意不纳入」。三个已声明的词汇项违反了该策略——每个都既无生产者也无消费方,其中两个甚至没有测试:
|
||||
|
||||
- **`CacheHint` 及其 `cache?: CacheHint` 块字段**,位于 `TextBlock`/`ToolResultBlock`(`packages/llm/llm/src/types.ts`;image block 上还有第三个同类字段,已随 image block 一起移除——见[移除 image block 的 RFC](2026-07-04-drop-image-content-block.md))。没有任何地方构造过带 `cache:` 的块——src、测试和文档粘贴全部搜索为空——两个适配器也都不读 `.cache`:DeepSeek 的 prompt 缓存是自动的,适配器只从响应中映射出 `prompt_cache_hit_tokens`,从不向请求中发送提示。这是 Anthropic 风格的 `cache_control` 接口面,却没有能兑现它的提供方。
|
||||
- **`TextBlock`/`ToolResultBlock` 上的 `CacheHint` 及其 `cache?: CacheHint` 块字段**(`packages/llm/llm/src/types.ts`;图像块曾有第三个此类字段,已随图像块一同移除——参见[删除图像 Agent Note(agent 决策记录)](2026-07-04-drop-image-content-block.md))。任何地方都没有构造带 `cache:` 的块——src、测试和文档粘贴均为空——两个 adapter 也都不读取 `.cache`:DeepSeek prompt caching 是自动的,因此 adapter 会从响应中映射出 `prompt_cache_hit_tokens`,却从不向请求中发送 hint。这是没有任何 provider 能够遵守的 Anthropic 风格 `cache_control` 表面。
|
||||
- **`MessageSourceMap.agent`**(`{ kind: 'agent'; agentId: string }`,同一文件)。零个构造点,包括测试在内。它预期的生产者在实现时并未使用它:subagent 后端将父级的 prompt 发送给子级时不带 `source`,因此记录为 `{ kind: 'user' }`,通用信封渲染器在插值 `source.kind` 时也从未对其做路由。
|
||||
- **`TurnTriggerMap.continuation`**(`packages/core/session/src/types.ts`)。agent loop(智能体循环)在结构上不可能发出它——continuation 发生在一个轮次*内部*作为后续步骤,而非作为新轮次——循环只构造 `message` 和 `injection` 触发器。唯一的写入者是一个手工构建的测试 fixture(测试前置数据),它只需要一个任意的非 message 触发器(`packages/support/llm-replay/tests/llm-replay.spec.ts`),`injection` 触发器同样满足需求;唯一的生产环境触发器读取方 ACP 桥接层只过滤 `kind === 'message'`。
|
||||
- **`TurnTriggerMap.continuation`**(`packages/core/session/src/types.ts`)。agent loop(智能体循环)在结构上不可能发出它——continuation 发生在一个轮次*内部*作为后续步骤,而非作为新轮次——循环只构造 `message` 和 `injection` 触发器。唯一的写入者是一个手工构建的测试 fixture(测试前置数据),它只需要一个任意的非 message 触发器(`packages/support/llm-replay/tests/llm-replay.spec.ts`),`injection` 触发器同样满足需求;唯一的生产环境触发器读取方 ACP(Agent Client Protocol)桥接层只过滤 `kind === 'message'`。
|
||||
|
||||
## 决策
|
||||
|
||||
删除 `CacheHint`、其 `cache?` 块字段、`agent` 消息来源变体与 `continuation` 轮次触发器变体:发布的词汇表不再包含它们。llm-replay fixture 改用 `injection` 触发器(任何非 `message` 触发器均满足其用途)。[core.md](../../../core-data-structures/core.md) 和 [session.md](../../../core-data-structures/session.md) 中的 type-equiv 粘贴与裁剪后的映射表一致——两个符号保留在 `scripts/type-equiv.manifest.json` 中,因为每个映射表本身仍然存在,只是少了一个成员——[内容块词汇 RFC](../architecture/2026-06-11-content-block-vocabulary.md) 的后果部分将缓存提示记录为「受生产者门控」而非「已有归属」,依照 [implemented/AGENTS.md](../AGENTS.md)。
|
||||
`CacheHint`、其 `cache?` 块字段、`agent` 消息来源变体和 `continuation` turn 触发器变体均已删除:已发布词汇不再携带它们。llm-replay fixture 使用 `injection` 触发器(任何非 `message` 触发器都能满足其用途)。[core.md](../../../../docs/core-data-structures/core.md) 和 [session.md](../../../../docs/core-data-structures/session.md) 中的 type-equiv 粘贴与裁剪后的 map 匹配——两个符号仍保留在 `scripts/type-equiv.manifest.json` 中的行,因为每个 map 都只是少了一个成员而继续存在——并且[内容块词汇 Agent Note](../architecture/2026-06-11-content-block-vocabulary.md)的后果按照 [implemented/AGENTS.md](../AGENTS.md),将 cache hint 记录为由生产者门控,而不是已有归属。
|
||||
|
||||
每个变体在获得真正的生产者之日回归,这正是映射表设计的增长方式:缓存功能连同传输它的适配器一起重新添加 `cache`;subagent 归属连同打标的后端和路由它的消费方一起重新添加 `agent`;真正启动新轮次的自动续行功能连同发出它的插件一起重新添加 `continuation`。
|
||||
|
||||
@@ -22,12 +22,12 @@ Status: implemented
|
||||
|
||||
### 为什么不保留它们?
|
||||
|
||||
[内容块词汇 RFC](../architecture/2026-06-11-content-block-vocabulary.md) 将「缓存提示……已有归属」列为设计后果,预留槽位确实能表达意图。但一个空槽位是每个实现和消费方都必须考虑的契约面(我的适配器需要兑现 `cache` 吗?我的渲染器需要路由 `agent` 来源吗?),而同族映射表自身的 JSDoc 已经拒绝了「无发出者的预留」——`refusal` 和 `max_turn_requests` 被明确标注为*当某物首次发出它们时*再添加的变体,而非提前声明。对已声明但无生命的变体施加同样的标准,使词汇表具有实际意义:如果它在映射表中,就一定有东西在生产它。
|
||||
[内容块词汇 Agent Note](../architecture/2026-06-11-content-block-vocabulary.md)曾把“cache hint……有了归属”列为设计后果,预留槽位也确实能表明意图。但空槽位是每个实现和消费者都必须考虑的契约表面(我的 adapter 是否必须遵守 `cache`?我的 renderer 是否必须路由 `agent` 来源?),而相邻 map 自身的 JSDoc 已经拒绝“无 emitter 先预留”——`refusal` 和 `max_turn_requests` 被点名为*首次有内容发出它们时*再添加的变体,而不是提前声明。让已经声明但无用的变体遵守同一标准,才能使词汇真正有意义:只要它位于 map 中,就必须有内容生产它。
|
||||
|
||||
## 验证
|
||||
|
||||
对 `CacheHint`、`agent` 消息来源拼写和 `continuation` 触发器拼写执行 `rg` 搜索,结果仅返回 RFC 记录(本文,以及[移除 image block 的 RFC](2026-07-04-drop-image-content-block.md) 中关于 image block 自身 `cache` 字段的说明);llm-replay fixture 使用 `injection` 触发器断言了相同的回放行为;core-data-structures 粘贴与 type-equiv manifest 保持同步。
|
||||
对 `CacheHint`、`agent` 消息来源拼写和 `continuation` 触发器拼写运行 `rg`,只会返回 Agent Note 记录(本文,以及[删除图像 Agent Note](2026-07-04-drop-image-content-block.md)对图像块自身 `cache` 字段的说明);llm-replay fixture 使用 `injection` 触发器断言相同的重放行为;核心数据结构粘贴和 type-equiv 清单保持同步。
|
||||
|
||||
## 后果
|
||||
|
||||
没有任何运行时行为改变——本来就没有东西能构造这些值。镜像事件的移除([boundary-mirror RFC](2026-06-20-remove-agent-boundary-mirror-events.md)、[stream-chunk RFC](2026-07-02-remove-stream-chunk-mirror.md))只涉及瞬态的 `agent/*` 事件,从不涉及持久词汇,因此不存在冲突。其他地方准入策略已经成立:`rejected`、`prompt/blocked` 和 `hook/invoked`/`hook/result` 各自都有活跃的生产者——本 RFC 将同一标准延伸到缺少生产者的三个变体。image block 自身的 `cache?` 字段属于[移除 image block 的 RFC](2026-07-04-drop-image-content-block.md),已随该块一起移除;本 RFC 覆盖的是留存块类型上的两个字段。
|
||||
操作行为没有变化——原本就没有内容能够构造这些值。镜像事件移除([边界镜像 Agent Note](2026-06-20-remove-agent-boundary-mirror-events.md)、[stream chunk Agent Note](2026-07-02-remove-stream-chunk-mirror.md))只触及瞬态 `agent/*` 事件,从不触及持久词汇,因此不存在冲突。其他位置已经遵守准入策略:`rejected`、`prompt/blocked` 和 `hook/invoked`/`hook/result` 都有实时生产者——本 Agent Note 将同一门槛扩展到缺少生产者的三个变体。图像块自身的 `cache?` 字段归属[删除图像 Agent Note](2026-07-04-drop-image-content-block.md),后者将其与该块一同移除;本 Agent Note 覆盖剩余块类型上的两个字段。
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-prune-write-only-fs-surface.md: f41619ecde1bbf2a1d6d8f8d769409f624fc22c7
|
||||
2026-07-04-prune-write-only-fs-surface.zh.md: afcf1aad28db056997930162539a57bb08bbe815
|
||||
2026-07-04-prune-write-only-fs-surface.md: 6cfd5d9ab8a2fc6322814d384fba735c06681976
|
||||
2026-07-04-prune-write-only-fs-surface.zh.md: cb7494b5e958c8ed86ffa3ba8ffbe82748d1db03
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# RFC: 从 fs seam 中移除只写字段与一个无效的路由旋钮
|
||||
# Agent Note: 从 fs seam 中移除只写字段与一个无效的路由旋钮
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -15,7 +15,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
删除 fs-local 的常量及其重导出,以及 `streamMinSize` 旋钮(`FsIoInternals` 中剩余的旋钮确实被原子写入测试使用);从 `FsTarget` 中移除 `inputPath`;将 `FsEditOutcome` 精简为 `{ version, before, after }`,并将 `replaceAll` 从解析后的参数传入 `formatEditOutput`;从 `FileReadOutcome` 中移除 `limit`/`version`。[filesystem.md](../../../core-data-structures/filesystem.md) 中的粘贴内容、`packages/fs/fs/README.md`,以及那些不得不为已移除字段编造值的测试 mock,都随类型一起缩减。
|
||||
删除 fs-local 常量、其再导出和 `streamMinSize` 配置项(其余 `FsIoInternals` 配置项确实由原子写入测试使用);从 `FsTarget` 删除 `inputPath`;将 `FsEditOutcome` 收窄为 `{ version, before, after }`,并把解析参数中的 `replaceAll` 传给 `formatEditOutput`;从 `FileReadOutcome` 删除 `limit`/`version`。[filesystem.md](../../../../docs/core-data-structures/filesystem.md) 中的粘贴、`packages/fs/fs/README.md`,以及不得不虚构已删除字段的测试 fake 都随类型一同收窄。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -25,7 +25,7 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
被移除的接口已消失——`dsh-fs-local` 中的 `STREAM_MIN_SIZE`/`streamMinSize`、`FsTarget.inputPath`、`FsEditOutcome.replacements`/`.replaceAll`,以及 `FileReadOutcome.limit`/`.version`——而请求侧的 `replaceAll`(`FsEditRequest`)和其他 outcome 类型上的 version 字段未受影响;测试 mock 随类型一起缩减。`formatEditOutput` 在 `replace_all` 两个分支下输出的文本不变,因此没有快照黄金文件被搅动。
|
||||
已删除表面不复存在——`dsh-fs-local` 中的 `STREAM_MIN_SIZE`/`streamMinSize`、`FsTarget.inputPath`、`FsEditOutcome.replacements`/`.replaceAll`,以及 `FileReadOutcome.limit`/`.version`——而请求侧 `replaceAll`(`FsEditRequest`)和其他 outcome 类型上的版本字段保持不变;测试 fake 随类型一同收窄。`formatEditOutput` 在两个 `replace_all` 分支中生成的文本都没有变化,因此没有快照预期输出发生改动。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-remove-agent-steering-mirror.md: fbd13b3d43b0052bcdeffd7f94caa341e1f636c5
|
||||
2026-07-04-remove-agent-steering-mirror.zh.md: 185cc5601a7406e0d801afd877e9d97eaaa12a0c
|
||||
2026-07-04-remove-agent-steering-mirror.md: 9f7cd5abe968ff216cbd7012163ea1c04dc00599
|
||||
2026-07-04-remove-agent-steering-mirror.zh.md: b498f473941b69eb5eab66e2e3034976da75e62c
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除 `agent/steering` 镜像 emit
|
||||
# Agent Note: 移除 `agent/steering` 镜像 emit
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -10,23 +10,23 @@ Status: implemented
|
||||
|
||||
`agent/steering` 以相同的 payload 重复了紧接其前的持久事件 `steering/message`。`agent/queued` 仍保留为纯瞬态信号,因为它在持久化之前触发,覆盖了可能在进入日志前被取消的工作。
|
||||
|
||||
steering 承载着真实的生产流量:hook bridge 的轮次续行决策通过 `inbox.steer()` 注入理由,落地为持久的 `steering/message` 事件,hook-matrix 的 golden 文件对此进行固定——所有这些消费方观察的都是持久事件。没有任何消费方观察镜像事件。
|
||||
Steering 承载真实生产流量——hook bridge 的 turn 延续决策通过 `inbox.steer()` 注入其理由,最终成为由 hook 矩阵预期输出固定的持久 `steering/message` 事件——而这些消费者无一例外都观察持久事件。没有任何内容观察镜像。
|
||||
|
||||
## 决策
|
||||
|
||||
`agent/steering` 从 agent 事件分类体系中移除:`packages/core/agent/src/types.ts` 中的声明(及其在 live-events JSDoc 列表中的提及)、`drainSteering` 中的 emit(随之移除的还有当时已无用的 `ctx` 参数)、`packages/core/agent/README.md` 中的对应行,以及 loop 伪代码块中的 emit 行(`packages/core/agent-loop/src/loop.ts` 模块文档与 [architecture.md](../../../architecture.md));Cordis catalog 重新生成后不再包含它。唯一的回归测试改为在持久事件 `steering/message` 上固定 source 保持性——它所固定的事实存在于日志中。
|
||||
`agent/steering` 已从 agent 事件分类中移除:包括 `packages/core/agent/src/types.ts` 中的声明(以及其中实时事件 JSDoc 列表对它的提及)、`drainSteering` 中的 emit(当时已无用的 `ctx` 参数也随之移除)、`packages/core/agent/README.md` 中的表格行,以及循环伪代码块(`packages/core/agent-loop/src/loop.ts` 模块文档和 [architecture.md](../../../../docs/architecture.md))中的 emit 行;Cordis 目录重新生成后不再包含它。唯一的回归测试改为在持久 `steering/message` 事件上固定来源保留行为——所固定的事实存在于日志上。
|
||||
|
||||
三份已实施的 RFC 曾声明保留该事件,每份均按 [implemented/AGENTS.md](../AGENTS.md) 的要求修订,指向本 RFC 作为移除记录:[boundary RFC](2026-06-20-remove-agent-boundary-mirror-events.md) 的保留列表条目、[stream-chunk RFC](2026-07-02-remove-stream-chunk-mirror.md) 的范围条款,以及 [event-domain-semantics RFC](../architecture/2026-06-30-event-domain-semantics.md) 的瞬态 emit 枚举。
|
||||
三份已实现 Agent Note(agent 决策记录)曾说明保留该事件;按照 [implemented/AGENTS.md](../AGENTS.md),每份记录都已修改并指向本文作为移除记录:包括[边界 Agent Note](2026-06-20-remove-agent-boundary-mirror-events.md) 的保留列表条目、[stream chunk Agent Note](2026-07-02-remove-stream-chunk-mirror.md) 的范围条款,以及[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.md) 的瞬态 emit 枚举。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
### 为什么不保留?
|
||||
|
||||
"它是控制信号,不是边界事件"——但分类体系的操作性区分是「镜像 vs. 纯瞬态」,而非「控制 vs. 边界」,而这个事件属于镜像。需要入队时通知的消费方有 `agent/queued`(带 steering flag);需要 drain 时通知的消费方,本质上是在请求 `steering/message` 被追加的那一刻,而 `session/event` 以相同 payload 加上持久性提供了这一通知。被否决的 [retire-mid-turn-steering RFC](../../rejected/simplification/2026-06-20-retire-mid-turn-steering.md) 捍卫的是 steering *能力*——`steer()`、持久事件、续行强制——本次移除对这些全部保持不变。
|
||||
“它是控制信号,不是边界”——但该分类的实际区分是镜像/仅实时,而非控制/边界,并且该事件确实是镜像。希望在入队时收到通知的消费者可以使用 `agent/queued`(及其 steering 标记);希望在排空时收到通知的消费者,本质上是在要求获知 `steering/message` 被追加的时刻,而 `session/event` 会交付相同 payload 并附带持久性。遭拒绝的[退役 turn 中途 steering Agent Note](../../rejected/simplification/2026-06-20-retire-mid-turn-steering.md)所捍卫的是 steering *功能*——`steer()`、持久事件、强制延续——本次移除不会触及其中任何一项。
|
||||
|
||||
## 验证
|
||||
|
||||
`agent/steering` 这一拼写仅存于 RFC 行文中(本 RFC、上述三份修订的 RFC,以及冻结的[被否决的 steering 能力 RFC](../../rejected/simplification/2026-06-20-retire-mid-turn-steering.md),其文本记录了它所拒绝的提案);catalog 已重新生成;重定向后的测试在 `steering/message` 上固定 source 保持性。
|
||||
`agent/steering` 拼写只存在于 Agent Note 正文中(本 Agent Note、上方三份已修改 Agent Note,以及已冻结的[遭拒绝 steering 功能 Agent Note](../../rejected/simplification/2026-06-20-retire-mid-turn-steering.md),其正文记录了它所否决的提案);目录已重新生成;重新定向的测试在 `steering/message` 上固定来源保留行为。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-share-app-bin-boot-glue.md: 31666e74bb0bb0086de85e6a3afafbc2f73a6e52
|
||||
2026-07-04-share-app-bin-boot-glue.zh.md: d18f83be0f76774e39a1e54f1ea2db3c1c1b7688
|
||||
2026-07-04-share-app-bin-boot-glue.md: 7a763eba8a229ec5017387edb54657a5c367105b
|
||||
2026-07-04-share-app-bin-boot-glue.zh.md: d65a6613f7b05cdea0f99529808c992aff4256e9
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RFC: 共享应用 bin 的启动胶水代码,而非维护两份副本
|
||||
# Agent Note: 共享应用 bin 的启动胶水代码,而非维护两份副本
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,19 +6,19 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
stdio 和 ACP 两个 bin 各自重复了环境加载、fail-loud 处理、入口校验与启动逻辑,包括微妙的 Loader 失败行为。两份副本已经发生漂移,且位于自执行文件中、被排除在单元测试覆盖率之外,导致其导出的辅助函数无法被复用。
|
||||
stdio 和 ACP(Agent Client Protocol)两个 bin 各自重复了环境加载、fail-loud 处理、入口校验与启动逻辑,包括微妙的 Loader 失败行为。两份副本已经发生漂移,且位于自执行文件中、被排除在单元测试覆盖率之外,导致其导出的辅助函数无法被复用。
|
||||
|
||||
## 决策
|
||||
|
||||
辅助函数只存在一处:[`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot)(`packages/ui/app-boot`,归入 `ui` 分组,因为 bin 是已发布产物,其运行时依赖本身也必须是已发布的包,而非 `support/`)。包含:`resolveConfigPath`(快照感知,两个 bin 共用的唯一路径解析器)、`loadEnv`、`installFailLoud`、`assertEntriesLoaded` 与 `boot`,每个函数都通过 bin 的诊断前缀参数化,并在其副作用 seam(warn sink、process slice)处支持注入,使单元测试套件能覆盖每个分支——包括 `boot()` 在进程内驱动真实 Loader、使用相对路径 specifier 配置的场景,既覆盖已稳定树的正常路径,也覆盖无 fiber 入口的拒绝路径。该包启用逐文件 100% 覆盖率门禁;Loader 失败的相关知识只有一个归属地。
|
||||
|
||||
每个 `bin.ts` 是一个精简的自执行组合,基于共享辅助函数加上各自特有的应用生命周期(ACP bin:replay 模式下跳过 env 加载与 stdin-EOF dispose;stdio bin:无额外逻辑)。bin 文件仍被排除在覆盖率之外且不导出任何内容;已发布产物的守卫不变——built-bin 冒烟测试仍在 node_modules 形状的临时目录中以原生 node 运行每个 bin(现在也符号链接了 `ui/app-boot`),并仍断言缺少配置时的非零退出码,遵循「真实入口路径即已发布产物」的防御模式。[extract-example-app-packages RFC](../architecture/2026-06-20-extract-example-app-packages.md) 中关于 bin 归属的事实已相应修订。
|
||||
每个 `bin.ts` 都是在共享辅助函数之上加应用特有生命周期的精简自执行组合(ACP bin:重放模式环境变量跳过和 stdin EOF 释放;stdio bin:没有额外逻辑)。这些 bin 仍排除在覆盖率之外且不导出任何内容;已发布产物守卫保持不变——按照“真实入口路径即已发布产物”的防御模式,已构建 bin 冒烟仍在具有 node_modules 形状的临时目录中用纯 node 运行每个 bin(现在也会符号链接 `ui/app-boot`),并继续断言缺失配置时以非零状态退出。[提取示例应用包 Agent Note(agent 决策记录)](../architecture/2026-06-20-extract-example-app-packages.md)中的 bin 归属事实已据此修改。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
### 为何不保留重复?
|
||||
|
||||
bin 被定位为独立拥有的已发布产物,而新增一个包(package)带来的固定开销(manifest(元数据清单)、README、tsconfig reference、publint 表面积)与去重的代码行数相当。但创建 bin 的那份 RFC 从未权衡过应用间共享的可能——它将三份示例 `start.ts` 副本合并进 bin 后便止步了;漂移是已观察到的事实;而覆盖率缺口的论据独立于去重论据:这是仓库中唯一免于逐文件 100% 门禁的非平凡运行时逻辑。记录在案的备选方案(仅将纯逻辑提取为各应用自己的模块)虽能终结豁免,但会保留两个知识归属地。
|
||||
这些 bin 当时被定位为归属相互独立的已发布产物,而新包会带来固定开销(清单、README、tsconfig 引用、publint 表面),与去重的行数相当。但创建 bin 的 Agent Note 从未权衡应用间共享——它把三份示例 `start.ts` 副本合并进 bin 后便止步于此;漂移是已经观察到的事实;覆盖率缺口的理由也独立于去重理由:这是仓库中唯一免受逐文件 100% 门禁约束的非平凡运行时逻辑。记录的后备方案(只将纯逻辑提取到各应用模块)会结束豁免,但会继续让相关知识拥有两个归属。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-tighten-hook-protocol-contract.md: 92ed629edaa0364d88955458f39f6280322e79c7
|
||||
2026-07-04-tighten-hook-protocol-contract.zh.md: 9b5b19d7ad522fdf74eb330c259d8dee03bee604
|
||||
2026-07-04-tighten-hook-protocol-contract.md: a1972ee8ef486982268ba8886b2413f3557061b4
|
||||
2026-07-04-tighten-hook-protocol-contract.zh.md: 51d11b4c2d79bd79b608ea5aa49b677653c3ac41
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
# RFC: 收紧 hook-protocol 契约——dialect、废弃字段、双重默认值与 lib 拥有的 `hook/result` 语义
|
||||
# Agent Note: 收紧 hook-protocol 契约——dialect、废弃字段、双重默认值与 lib 拥有的 `hook/result` 语义
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,9 +6,9 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
`dsh-hook-protocol`/bridge 契约中有四处遗漏了 [subagent-observe-enrich RFC](../feature/2026-06-30-subagent-observe-enrich.md) 所记录的纪律——该 RFC 因缺乏消费方而移除了 `agentType` 生命周期字段,以下四处未通过同样的检验:
|
||||
`dsh-hook-protocol`/bridge 契约中有四部分没有遵守 [subagent observe/enrich Agent Note(agent 决策记录)](../feature/2026-06-30-subagent-observe-enrich.md)记下的准则——后者因缺少消费者而删除 `agentType` 生命周期字段,以下各项没有通过同一检验:
|
||||
|
||||
1. **`HookDialect` 的 `'native'` 变体**(`packages/hooks/hook-protocol/src/types.ts`)没有任何生产者——bridge 只会标记 `'claude'` 和 `'codex'`;唯一构造 `'native'` 的地方是 lib 自身的单元测试。该字段的 JSDoc 将 `dialect` 定义为「运行它的 bridge」,而 native 并非 bridge:[interception-seams RFC](../feature/2026-06-30-interception-seams.md) 记录了 native hook 不是一个 package,且「native 插件已经可以直接使用类型化的 Decisions」而无需持久化 hook 日志;旗舰 native-plugin 示例也正是如此断言的(完全没有 `hook/*` 事件)。
|
||||
1. **`HookDialect` 的 `'native'` 变体**(`packages/hooks/hook-protocol/src/types.ts`)没有生产者——bridge 会标记 `'claude'` 和 `'codex'`;所有位置中唯一构造 `'native'` 的是该库自己的单元测试。字段自身的 JSDoc 将 `dialect` 定义为“运行它的 bridge”,而 native 不是 bridge:[拦截接缝 Agent Note](../feature/2026-06-30-interception-seams.md) 记载 native hook 不是一个包,并且“native 插件无需持久 hook 日志即可使用类型化 Decision”;旗舰 native 插件实践示例恰好断言了这一点(完全没有 `hook/*` 事件)。
|
||||
2. **`HookOutput.suppressOutput`**(同一文件)被 codec 解析后在所有路径上均被丢弃:没有 bridge 分支处理它、没有 merge fold、没有 warn、没有 deferred-list 行——在所有「被解析但未兑现」的同类字段中它是唯一没有明确延期声明的(`updatedInput` → 一条 warn 日志加 [pre-tool-input-rewrite 提案](../../proposed/feature/2026-06-30-pre-tool-input-rewrite.md);`systemMessage` → 一条 warn 日志加 README deferred 行;`continue`/`stopReason` → 一个 `TODO(hook-continue-false)` 锚点加 `'stop'` decision 记录)。从结构上看根本无物可抑制:hook stdout 从不进入任何 transcript(文本记录)(上下文仅通过 `additionalContext` 流入;日志只记录 `decision`/`stderrSummary`),因此 hook 作者设置 `suppressOutput: true` 得到的是无声的空操作,且无任何警告。
|
||||
3. **`defaultTimeoutMs` 在两个 bridge 配置中以浮动字面量双重默认**——schema 的 `.default(600_000)` 加上一个 `?? 600_000` 回退(`packages/hooks/hooks-claude/src/index.ts`、`packages/hooks/hooks-codex/src/index.ts`),一个协议级常量在每个 bridge 中有两个归属地,两个 bridge 可能在共享默认值上悄然分歧。*提案最初的补救措施是彻底删除该旋钮,但被 no-hardcoded-tunables 审计所取代:审计保留了该旋钮作为 bridge 拥有的显式配置(并在旁边新增了 `stderrSummaryMaxChars`);剩下要修的是字面量的归属地。*
|
||||
4. **`hook/result` 的语义存在于两个 bridge 中(各一份),而非拥有该事件的 lib。** `summarize()`——stderr 截断规则——在 `packages/hooks/hooks-claude/src/index.ts` 与 `packages/hooks/hooks-codex/src/index.ts` 中逐字节相同;decision 字符串规则 `output.decision ?? (output.continue === false ? 'stop' : 'pass')` 同样如此。然而 `dsh-hook-protocol` 声明了 `hook/result`、在文档中将 `stderrSummary` 描述为「已截断」却不拥有截断逻辑,记录了 decision 值却不拥有映射逻辑。如果某个 bridge 漂移(不同的上限、不同的回退),共享持久化事件的语义就会悄然分叉。
|
||||
@@ -29,4 +29,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
`dialect`、`suppressOutput`、可调参数与语义的变更在协议格式(wire format)和 golden 文件中均不可见。代价是 `dsh-hook-protocol` 与两个 bridge 的代码变动——在预发布阶段这很廉价,且比让持久化事件语义的两份副本各自老化要廉价得多。
|
||||
`dialect`、`suppressOutput`、可调参数和语义变更在线协议和预期输出中均不可见。代价是 `dsh-hook-protocol` 和两个 bridge 中的改动——在预发布立场下成本很低,也比让一项持久事件语义的两个副本各自老化更便宜。
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-04-trim-acp-bridge-unreachable-surface.md: 05a62c92ec1553e6eb0b14adc86f8aa1b89827e5
|
||||
2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: 851198eee0559013429ef4eb5491cd7f97217c46
|
||||
2026-07-04-trim-acp-bridge-unreachable-surface.md: ce0c623930192d02c8347c3956c497ee13048904
|
||||
2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: ea94f28b4802c18394cc3f6e6d4bdfa8702dae2b
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# RFC: 裁剪不可达的 ACP 桥接层表面——品牌配置项与 kind 嗅探回退
|
||||
# Agent Note: 裁剪不可达的 ACP 桥接层表面——品牌配置项与 kind 嗅探回退
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -8,7 +8,7 @@ Status: implemented
|
||||
|
||||
`dsh-acp` 有两处对外表面在任何已交付的配置中都不可达:
|
||||
|
||||
1. **`AcpConfig.agentName` / `agentVersion`**(`packages/ui/acp/src/index.ts`)。已交付的 app 包(`packages/examples/acp-demo/src/index.ts`)只向桥接层传递 `{ model }`,因此没有任何叶子 `cordis.yml`(唯一的生产配置表面)能设置这两个配置项;它们只有通过直接挂载桥接层才能设置,而只有单元测试这样做。所有快照 golden(包括 hook-matrix 场景)都固定了 schema 默认值(`deepseek-harness-acp` / `0.0.1`)。这对字段还带着一个活跃的 `TODO(double-default)`:字面量存在两份(schema 的 `.default(...)` 加 `??` 回退),TODO 要求选定一个归属。
|
||||
1. **`AcpConfig.agentName` / `agentVersion`**(`packages/ui/acp/src/index.ts`)。已发布应用包只向 bridge 传递 `{ model }`(`packages/examples/acp-demo/src/index.ts`),因此没有任何叶子 `cordis.yml`——唯一的生产配置表面——能够设置这些配置项;只有直接挂载 bridge 才能设置它们,而这种做法只存在于一个单元测试中。每份快照预期输出——包括 hook 矩阵场景——都固定 schema 默认值(`deepseek-harness-acp` / `0.0.1`)。这对配置项还带有一个尚未解决的 `TODO(double-default)`:字面量存在两次(schema `.default(...)` 加 `??` 后备值),TODO 要求为它们选择一个归属。
|
||||
2. **`toolKindFor` 名称启发式**(同一文件)在通用回退路径中对 `bash*`/`read*`/`write`/`edit*` 工具名做了特殊处理。自 [render-intent union](../architecture/2026-07-02-tool-render-intent-union.md) 以来,这些分支匹配到的每个第一方工具都自带 `presentCall` 并携带其 kind,而没有 presenter 的生产工具(`subagent`、`subagent_fork`)本来就落入 `other`。这些分支只有在工具拒绝自行呈现调用时才在生产中可达:`presentCall` 抛出异常(容错回退),或模型参数未通过工具 schema 导致 `defineTool` 的 `presentCall` 包装层返回 `undefined`(例如 `bash` 调用缺少必需的 `description`)。而桥接层自身的模块文档明确声明了该启发式所违反的设计规则:"桥接层绝不对工具名做特殊处理"。
|
||||
|
||||
## 决策
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-12-drop-unconsumed-skill-provider-events.md: 90157c03e5df05c98b992ce1dbefea26f4865ce7
|
||||
2026-07-12-drop-unconsumed-skill-provider-events.zh.md: 19fec4b827b89b4127b749a9c77715baf39dd00a
|
||||
2026-07-12-drop-unconsumed-skill-provider-events.md: b0ed7585882328b6abdcf57974200053d9c26048
|
||||
2026-07-12-drop-unconsumed-skill-provider-events.zh.md: 557e7ee2155969530a109280d9324b2525e3144a
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# RFC: 移除无消费方的 skill 提供方事件
|
||||
# Agent Note: 移除无消费方的 skill 提供方事件
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -16,7 +16,7 @@ skill 发现按需读取当前的提供方映射表,提供方注册时同步
|
||||
|
||||
skill 注册表不再声明和 emit 提供方成员变更事件。提供方的注册与 dispose(资源释放)仍为 effect 所有的直接状态变更,同步使已完成的 catalog 失效;查找与发现按需读取当前提供方映射表。测试通过提供方查找和收集到的输出来观察清理行为,而非依赖生命周期通知。
|
||||
|
||||
生成的事件 catalog、API catalog 与生产者/消费方矩阵不再包含已删除的通知。skill 系统 RFC 与包文档通过 effect 所有的直接状态及缓存失效契约来描述注册行为。
|
||||
生成式事件目录、API 目录和生产者/消费者矩阵均不再包含已删除通知。skill system Agent Note(agent 决策记录)和包文档改为通过其由 effect 直接拥有的状态与 cache 失效契约描述注册。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-12-prune-unused-web-seam-fields.md: 9fd05da282c22d78dc98e232ef2c23cd6e9c4ea3
|
||||
2026-07-12-prune-unused-web-seam-fields.zh.md: 650b6b74c808c719bcea9783c60064936427ffee
|
||||
2026-07-12-prune-unused-web-seam-fields.md: c50bf44161579a44b09113fc501f3d67fb5d6855
|
||||
2026-07-12-prune-unused-web-seam-fields.zh.md: 401bdd0c812175cffc572e722141d2829a3fc2d5
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# RFC: 裁剪 web seam 中未使用的字段
|
||||
# Agent Note: 裁剪 web seam 中未使用的字段
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
web 能力携带的 request/result/status 值,虽然每个已交付的实现都会填充,但没有任何生产环境的消费方读取它们。`WebSearchResult.providerId`、`query`与 `WebFetchResult.providerId` 是结果回显;`tool-web` 只格式化 content/sources/truncation 或最终 URL/status/body/truncation,没有其他运行时读取这些字段。搜索提供方返回 `WebProviderStatus.reason`,但可用性检查只看 `available`,并有意输出一条通用的不可用诊断信息。
|
||||
web 能力携带的 request/result/status 值,虽然每个已交付的实现都会填充,但没有任何生产环境的消费方读取它们。`WebSearchResult.providerId`、`query` 与 `WebFetchResult.providerId` 是结果回显;`tool-web` 只格式化 content/sources/truncation 或最终 URL/status/body/truncation,没有其他运行时读取这些字段。搜索提供方返回 `WebProviderStatus.reason`,但可用性检查只看 `available`,并有意输出一条通用的不可用诊断信息。
|
||||
|
||||
`WebFetchRequest.timeoutMs` 同样从未被生产调用方设置。`tool-web` 只提供 URL,使用工具定义的 timeout 加 `exec.signal` 作为调用方截止时间,并依赖本地提供方的配置默认值作为兜底。这个未使用的逐请求覆盖迫使 `web-fetch-local` 暴露 `maxTimeoutMs`、对两个 timeout 来源做 clamp,并为没有任何产品路径能选中的优先级规则编写文档和测试。`WebExecContext` 则是另一个单字段包装层:每个调用方分配 `{ signal }`,每个提供方立即解包 `exec?.signal`;不存在第二个执行控制字段。
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
2026-07-12-simplify-session-log-representation.md: dd8e7f319098bcdca9a844f5665583a3aa25ae80
|
||||
2026-07-12-simplify-session-log-representation.zh.md: c759b87bbb13903744a8f6bbb139ab71e1c0f39b
|
||||
2026-07-12-simplify-session-log-representation.md: a40f4013a97a9c940012dbb37d59beb2faf8fb22
|
||||
2026-07-12-simplify-session-log-representation.zh.md: a880a74cf41ca7d06f2278792e1abb4ea46ea969
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-12-simplify-session-log-representation.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The session log maintains two representations that cost more machinery than their consumers require: a pseudo-linked surface and custom request-header deltas.
|
||||
|
||||
+12
-15
@@ -1,6 +1,6 @@
|
||||
# RFC: 简化会话日志表示
|
||||
# Agent Note: 简化会话日志表示
|
||||
|
||||
Status: proposed
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-12-simplify-session-log-representation.md) | 中文
|
||||
|
||||
@@ -8,31 +8,28 @@ Status: proposed
|
||||
|
||||
会话日志维护着两种表示,其机制复杂度超出了消费方的实际需求:一个伪链表 surface 和自定义的请求头增量。
|
||||
|
||||
`SurfaceManager` 用一个数组、一个 seq 映射和可变的 `prev`/`next` 链接存储相同的顺序。生产代码从不读取 `prev`;压缩(compaction)唯一的 `next` 读取是取数组位置的后继。替换操作已经使用 `indexOf`,因此链接并未让其主要操作达到常数时间。一个 seq 数组加线性替换查找具有相同的渐近替换开销,且只有一种表示需要验证。
|
||||
`SurfaceManager` 同时在数组、seq map 和可变 `prev`/`next` 链接中存储相同顺序。生产代码从不读取任一链接:compact 的工具配对 balance 根据按 surface 顺序缓存的每个切点 balance 作答。替换已经使用 `indexOf`,因此链接并未使其主导操作成为常数时间。使用线性替换查找的 seq 数组具有相同的渐近替换成本,却只有一种表示需要验证。
|
||||
|
||||
请求头子系统实现了一套自定义的 system/tool 增量编解码器和传输决策层,尽管其契约声明增量只是编码优化,而非可重建性要求。在每个 agent loop(智能体循环)实例边界保留初始/恢复的完整快照,然后在该实例的组装头发生变化时写入一条规范的完整 `request/header`,即可保留回放能力,同时删除 `SystemDelta`、`ToolsDelta`、往返回退逻辑以及持久化的 `request/header-delta` 变体。编解码器专属的词汇随编解码器一起消失,并非因为其各分支本身无效。
|
||||
|
||||
本提案有意保留追加和替换的 `sourceEventSeqs`、崩溃恢复来源信息以及所有 `SessionStartSource` 变体:已实施的 RFC 赋予这些字段审计/拦截角色,零当前读者这一事实不足以推翻它们。
|
||||
实现保留追加与替换 `sourceEventSeqs`、崩溃修复 provenance,以及所有 `SessionStartSource` 变体,因为这些字段承担审计/拦截职责,当前没有读取方并不能推翻这一点。
|
||||
|
||||
## 提案
|
||||
## 决策
|
||||
|
||||
将 `SurfaceManager.nodes` 改为事件序列号的 `readonly number[]`,移除公开的 `SurfaceNode` 形状。保留内部的替换代信号;更新 tool 配对平衡和压缩调用方,使其通过数组值/索引获取前驱、后继和替换范围,移除节点链接和 seq-to-node 映射。用规范的完整变更头快照替代锚点后的头增量,移除增量编解码器/事件/测试;初始和恢复锚点即使折叠后的头未变也仍为完整快照。
|
||||
`SurfaceManager.nodes` 是由事件序号组成的 `readonly number[]`;公共 `SurfaceNode` 形状、node 链接和 seq-to-node map 均已移除。内部替换 generation 信号保留。session-query 使用的完整 `foldSurface()` 读取会返回相同的数字数组表示和替换元数据,而无需让增量 manager 保留历史。工具配对 balance 和压缩使用事件序号与 surface 位置;由 compact 拥有的每切点 balance cache 不依赖 node 链接。
|
||||
|
||||
修订 session-surface 和 reconstructable-request RFC 中描述已移除编码的部分。更新事件类型/不变式、请求日志/回放、持久化 fixture(测试前置数据)、生成的 catalog、包文档和快照。将编解码器专属的 `fallback` 原因替换为锚点后完整快照的显式 `change` 原因,使其与保留的 `initial` 和 `resume` 锚点区分开来。
|
||||
请求头只使用规范的完整快照。初始与恢复锚点即使没有变化也仍是完整快照;实例内变化会追加另一个完整 `request/header`,reason 为 `change`。delta 事件、codec 类型、diff/apply 辅助函数,以及仅供 codec 使用的 `fallback` reason 均已移除。请求重建选择最新快照。
|
||||
|
||||
`SESSION_FORMAT_VERSION` 有意保持在 `0`,因此一份包含 `request/header-delta` 的旧 v0 日志在增量折叠被删除后,本会通过版本检查并静默丢失头变更。seed/load 校验必须在格式边界处拒绝该遗留事件并显式报错;不添加兼容性折叠或迁移。
|
||||
`SESSION_FORMAT_VERSION` 仍固定为 `0`,因此 seed、追加和持久化加载验证会显式拒绝旧 v0 `request/header-delta` 事件,以及携带已删除 `fallback` reason 的完整快照。不存在兼容性 fold 或迁移。JSONL 与 SQLite 测试固定了这一响亮失败边界;ACP(Agent Client Protocol)快照 harness 则把合法的 session 中途变更表示为完整固定请求头和完整可读 prompt。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**保留链表节点和紧凑增量以备未来扩展。** 链接可能有助于未来的游标 API,增量在大型工具 schema 仅有少量变化时可以缩减日志。但没有已发布的游标使用这些链接,而完整快照以磁盘空间换取了显著更简单的正确性。如果头部体积确实成为问题,可以基于真实 trace 设计压缩方案或经过度量的规范增量方案。
|
||||
|
||||
## 验收标准
|
||||
## 验证
|
||||
|
||||
- `SurfaceManager.nodes` 是一个有序 seq 数组,没有 `SurfaceNode`、链接字段或 seq-to-node 映射;增量追加处理和内部替换代信号保留。
|
||||
- 回放完整变更头快照能重建出完全相同的请求;不再存在任何 header-delta 事件/类型/编解码器。
|
||||
- 包含遗留 `request/header-delta` 的 v0 seed 或持久化日志在回放前被拒绝,JSONL 和 SQLite 加载路径均有覆盖率。
|
||||
- 新形状的 v0 JSONL/SQLite 回放、来源信息、崩溃恢复、压缩、快照、不变式、类型检查、覆盖率、doc-sync 和 hygiene 全部通过。
|
||||
单元覆盖率固定有序 surface 的追加/替换行为、工具配对、压缩、完整请求头 fold/记录、请求重建和开发不变量。Seed 验证以及 JSONL、SQLite 加载测试会在重放前拒绝旧事件。无密钥 ACP 套件以新形状覆盖记录、刷新、重放、变化请求头固定,以及 sandbox 模式切换 fixture(测试前置数据)。
|
||||
|
||||
## 风险
|
||||
## 后果
|
||||
|
||||
完整头会增加日志体积,线性替换查找在非常大的 surface 上可能更慢。替换操作已经是线性的,因为实现调用了 `indexOf`;只有当真实 trace 表明更简单的数组成为瓶颈时才应添加基准测试。由于格式版本保持为 `0`,如果遗漏了对遗留事件的显式拒绝,后果将是静默数据损坏而非类型错误;因此显式报错的加载测试是本提案的组成部分,而非可选的清理工作。
|
||||
完整请求头会增加日志体积,线性替换查找在极大 surface 上也可能较慢。由于先前实现调用 `indexOf`,替换原本就是线性的;benchmark 推迟到真实 trace 表明更简单的数组成为瓶颈时再进行。格式版本仍为 `0`,因此显式拒绝旧事件是预发布格式边界的永久组成部分。作为交换,surface 顺序和请求头状态现在各自只有一种表示,删除了链接维护、map、codec 分支、往返 fallback 和感知 delta 的快照规范化。
|
||||
|
||||
Reference in New Issue
Block a user