diff --git a/.agents/notes/AGENTS.md b/.agents/notes/AGENTS.md
index 958aff54fc..ea0fa8f42c 100644
--- a/.agents/notes/AGENTS.md
+++ b/.agents/notes/AGENTS.md
@@ -1,3 +1,5 @@
# AGENTS.md — Agent Notes
Agent Notes are effectively RFCs written by agents: durable proposals and decision records that preserve rationale, alternatives, consequences, and verification contracts. Follow the [documentation standard](../../docs/AGENTS.md) and the [Agent Note contract](README.md).
+
+Files under [`archived/`](archived/AGENTS.md) are frozen historical snapshots: never edit them or treat them as current authority.
diff --git a/.agents/notes/README.i18n.yaml b/.agents/notes/README.i18n.yaml
index 649c2f1d87..6c7c2c635b 100644
--- a/.agents/notes/README.i18n.yaml
+++ b/.agents/notes/README.i18n.yaml
@@ -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: d2f6d216b151673d818337c67a78dbe908786c8b
-README.zh.md: 4c7f785ba7478f35cade742409d87746ddcdf8ec
+README.md: 3cfbb5154713046846a3bfcb2ccea62c0e4cb6c0
+README.zh.md: ddecac79519219c4a76cf9ba19edea312eea9d0d
diff --git a/.agents/notes/README.md b/.agents/notes/README.md
index d2f6d216b1..3cfbb51547 100644
--- a/.agents/notes/README.md
+++ b/.agents/notes/README.md
@@ -11,12 +11,12 @@ Every Agent Note has two axes, both encoded in its **path** — `{lifecycle}/{cl
- **Lifecycle** (the top-level folder) is the Agent Note's status, and an Agent Note moves between folders as that status changes:
- **`proposed/`** — proposals reviewed before implementation; not yet built (or only partly).
- **`implemented/`** — the decision shipped. The file records what was decided and what was rejected, and is **kept current with what actually shipped**: when the code later moves a file, renames a package, or changes a key/default, the Agent Note is updated in the same change to match (facts only — paths, names, structure — not the decision itself). See [implemented/AGENTS.md](implemented/AGENTS.md).
- - **`rejected/`** — the proposal was considered and declined. Kept for the record so the rejection isn't re-litigated.
+ - **`rejected/`** — the proposal was considered and declined. Keep it only while its rationale prevents a tempting, meaningful mistake; otherwise delete the complete triplet.
- **Class** (the nested folder) is the *kind* of decision — see [Classification](#classification) below.
The date in the filename is when the topic was **first proposed** (per git history). Cross-references between Agent Notes use relative markdown links (`[topic](../../implemented/architecture/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.
-The tree is the inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale.
+The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Low-future-value implemented records move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
## Classification
@@ -33,6 +33,14 @@ Each Agent Note belongs to one path-encoded class from the closed set in `script
The `architecture` / `process` line: **architecture** is about the source we ship; **process** is the surrounding tooling and workflow. (`refactor` is deliberately absent — it overlaps `simplification`, whose discriminator, "does observable behavior change?", already covers it.)
+## Archiving and deletion
+
+Archive an implemented Agent Note when the shipped decision is complete and its rationale is unlikely to guide future work. Keep it active when its alternatives, ownership boundary, negative guarantee, durable or wire semantics, security rule, or reintroduction condition remains useful. Never archive a proposed note: reject an obsolete proposal. Keep a rejected note only while it prevents a plausible mistake; otherwise delete its English, Chinese, and sidecar files together. Use the calibrated [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) workflow rather than word count, age, or a target quota.
+
+The archive is path-encoded as `archived/{class}/yyyy-mm-dd-topic-title.md`; `implemented` is deliberately absent because only implemented notes can enter it. An archival change moves the complete English/Chinese/sidecar triplet, retains `Status: implemented`, inserts the same `Archived: YYYY-MM-DD` line immediately below that status in both language files, re-records the sidecar, and repairs or deletes inbound links. These are the only permitted content changes during archival.
+
+Once sealed, every archived triplet is permanently frozen. Do not edit, translate, reformat, update, move, or delete it, and do not treat it as authority for current behavior. Documentation gates skip archived sources, including their outbound links; active prose may still link into an archived note when it intentionally cites history. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) enforces the closed class tree, complete triplets, archive metadata, sidecar hashes, and the append-only frozen-content manifest. The [archive-policy Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.md) owns the rationale.
+
## When to write one
Every non-trivial change MUST add or update at least one Agent Note in the same PR. A change is non-trivial when it alters behavior, architecture, a cross-file or cross-package contract, process or tooling, testing strategy, an on-disk, wire, or configuration format, or another decision a maintainer may reasonably revisit. A proposal for substantial future work starts in `proposed/`; a decision already made starts in `implemented/`. Pick the class folder that matches the decision (see [Classification](#classification)).
@@ -45,7 +53,7 @@ A feature-addition note may be consolidated into the later removal note only whe
## The file format
-Every Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [the uniform-format Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md).
+Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [the uniform-format Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above.
### The header block
diff --git a/.agents/notes/README.zh.md b/.agents/notes/README.zh.md
index 4c7f785ba7..ddecac7951 100644
--- a/.agents/notes/README.zh.md
+++ b/.agents/notes/README.zh.md
@@ -11,12 +11,12 @@
- **生命周期**(顶层文件夹)是 Agent Note 的状态,Agent Note 随状态变化在文件夹之间移动:
- **`proposed/`**:实施前评审的提案;尚未构建(或仅部分构建)。
- **`implemented/`**:决策已交付。文件记录做了什么决定、否决了什么,并**与实际交付的内容保持同步**:当代码后续移动文件、重命名包(package)或更改键名/默认值时,Agent Note 在同一个变更中同步更新(仅限事实——路径、名称、结构——而非决策本身)。见 [implemented/AGENTS.md](implemented/AGENTS.md)。
- - **`rejected/`**:提案经过讨论后被否决。保留以备查阅,避免同一问题被反复争论。
+ - **`rejected/`**:提案经过讨论后被否决。仅当其决策依据仍能避免一种诱人且影响重大的错误时保留;否则删除完整的三个配对文件。
- **类别**(嵌套文件夹)是决策的*种类*——见下方[分类](#classification)。
文件名中的日期是该主题**首次提出**的时间(以 git 历史为准)。Agent Note 之间的交叉引用使用相对 Markdown 链接(`[topic](../../implemented/architecture/2026-…-….md)`),从不使用纯文字或编号,这样既可机械检查,也能在文件夹间移动时保持有效。
-目录树就是清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`;设计理由见[不设索引的 Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md)。
+活跃生命周期目录树就是工作清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`;设计理由见[不设索引的 Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md)。未来指导价值较低的已实施记录会移至下文所述、单独冻结的 [`archived/`](archived/AGENTS.md) 目录树。
@@ -35,6 +35,14 @@
`architecture` 与 `process` 的界线:**architecture** 关乎我们交付的源码;**process** 关乎围绕源码的工具与工作流。(`refactor` 被有意排除:它与 `simplification` 重叠,而后者的判别标准「可观察行为是否改变」已经覆盖了它。)
+## 归档与删除
+
+当一份 implemented Agent Note 记录的交付决策已经完整落地,且其决策依据不太可能再指导未来工作时,将其归档。如果其中的备选方案、归属边界、否定性保证、持久化语义或协议语义、安全规则,或者重新引入条件仍有价值,则继续作为活跃记录保留。绝不归档 proposed Agent Note:过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种可能发生的错误时保留;否则一并删除其英文、中文和伴随记录文件。请使用经过校准的 [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) 工作流,不要根据字数、存续时间或目标配额来判断。
+
+归档路径编码为 `archived/{class}/yyyy-mm-dd-topic-title.md`;其中有意省略 `implemented`,因为只有 implemented Agent Note 可以进入归档。归档变更会移动完整的英文、中文和伴随记录三个文件,保留 `Status: implemented`,在两种语言的文件中紧接该状态行插入相同的 `Archived: YYYY-MM-DD` 行,重新记录伴随文件,并修复或删除入站链接。归档时只允许对内容做这些更改。
+
+封存后,每组归档文件都永久冻结。禁止编辑、翻译、重新格式化、更新、移动或删除,也不得将其视为当前行为的权威依据。文档门禁会跳过归档源文件,包括其中的出站链接;当活跃文档有意引用历史时,仍可链接到归档 Agent Note。[`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) 强制执行封闭的类别目录树、完整的三文件配对、归档元数据、伴随记录 hash,以及仅追加的冻结内容 manifest。[归档政策 Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.md) 记录了设计依据。
+
## 何时需要写一份
每个非平凡变更都必须在同一 PR(Pull Request)中新增或更新至少一份 Agent Note。如果变更修改了行为、架构、跨文件或跨包契约、流程或工具、测试策略、磁盘、协议或配置格式,或者其他维护者可能合理重新审视的决策,就属于非平凡变更。对未来重大工作的提案从 `proposed/` 开始;已经做出的决策从 `implemented/` 开始。选择与决策匹配的类别文件夹(见[分类](#classification))。
@@ -49,7 +57,7 @@
## 文件格式
-每份 Agent Note 遵循统一的文件内格式,由 `pnpm run verify-agent-note-format`([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts),`doc-sync`(文档同步门禁)的一环)强制执行;该格式的设计动机及其否决的替代方案见[统一格式 Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md)。
+每份活跃 Agent Note 遵循统一的文件内格式,由 `pnpm run verify-agent-note-format`([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts),`doc-sync`(文档同步门禁)的一环)强制执行;该格式的设计动机及其否决的替代方案见[统一格式 Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md)。归档记录保留封存时的格式,并增加上述归档日期行。
### 头部块
diff --git a/.agents/notes/archived/AGENTS.md b/.agents/notes/archived/AGENTS.md
new file mode 100644
index 0000000000..2ac4518a6a
--- /dev/null
+++ b/.agents/notes/archived/AGENTS.md
@@ -0,0 +1,7 @@
+# AGENTS.md — Archived Agent Notes
+
+Archived Agent Note triplets under the kind directories are frozen historical snapshots, not current authority. Never edit, reformat, translate, repair, delete, or move a sealed artifact; use an active Agent Note or current documentation for new decisions and facts.
+
+The archival change may only relocate a complete English/Chinese/sidecar triplet, insert the identical `Archived: YYYY-MM-DD` line below both `Status: implemented` lines, re-record the sidecar, and repair or delete inbound links. Do not inspect, verify, or repair links out of archived notes.
+
+Run the [`dsh-archive-agent-notes`](../../skills/dsh-archive-agent-notes/SKILL.md) workflow and append new artifact hashes with `pnpm run verify-archived-agent-notes --write`. The normal verifier rejects changed or missing sealed artifacts, incomplete triplets, unknown kind folders, and invalid archive metadata.
diff --git a/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.i18n.yaml b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.i18n.yaml
new file mode 100644
index 0000000000..ae1e7c0afe
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.i18n.yaml
@@ -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
+2026-06-11-custom-schema-dsl.md: e09fea6c4bb80e287b1b64471eee4c87f24fba4a
+2026-06-11-custom-schema-dsl.zh.md: 2bacbde02838ef61b05f38796bbeeea262fc2d23
diff --git a/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.md b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.md
new file mode 100644
index 0000000000..e09fea6c4b
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.md
@@ -0,0 +1,24 @@
+# Agent Note: Custom typed tool-schema DSL instead of schemastery
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-06-11-custom-schema-dsl.zh.md)
+
+## Problem
+
+Tool parameters must reach the model as standard JSON Schema while giving tool authors typed `execute(args)` without casts. Schemastery already serves plugin config, but the tool-author API needs per-property `required: true` booleans rather than JSON Schema's separate `required` array.
+
+## Decision
+
+This decision is superseded by the [unified JSON-value schema DSL](2026-07-20-unified-json-value-schema-dsl.md), which retains the small authoring surface while making parameters and typed values share one vocabulary. `ParameterSchemaSpec` keeps per-property `required: true`; `InferArgs` maps required keys to non-optional properties; `parameterSchemaSpecToJsonSchema()` compiles the implicit open object root; and `defineTool()` ties inference, compilation, and validation together. Raw JSON-Schema `ToolDefinition`s remain accepted by `ToolRegistry.register()` for MCP and other external tools.
+
+## Alternatives considered
+
+**Schemastery** (already vendored, used for plugin Config) was evaluated and rejected for this use: it targets validation / transformation against StandardSchema, not JSON Schema *generation*, so it would add indirection without producing the wire format cleanly.
+
+## Consequences
+
+- First-party tool authors get zero-cast typed args; the type gymnastics cost stays inside the core package (sanctioned by the AGENTS.md type-safety policy).
+- The owning unified note defines the current nodes, literal constraints, unions, JSON-value boundary, and object-openness rules.
+- The `InferArgs` mapping is regression-tested at the type level after an early optionality bug.
diff --git a/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.zh.md b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.zh.md
new file mode 100644
index 0000000000..2bacbde028
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-06-11-custom-schema-dsl.zh.md
@@ -0,0 +1,24 @@
+# Agent Note: 使用自定义类型化工具 schema DSL 替代 schemastery
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-06-11-custom-schema-dsl.md) | 中文
+
+## 问题
+
+工具参数必须以标准 JSON Schema 形式到达模型,同时让工具作者在 `execute(args)` 中获得类型化的参数而无需类型断言。Schemastery 已用于插件配置,但工具作者 API 需要逐属性的 `required: true` 布尔值,而非 JSON Schema 的独立 `required` 数组。
+
+## 决策
+
+该决策已由[统一 JSON 值 schema DSL](2026-07-20-unified-json-value-schema-dsl.md)取代;新设计保留小型编写接口,同时让参数与类型化值共享一套词汇。`ParameterSchemaSpec` 保留逐属性的 `required: true`;`InferArgs` 将必需键映射为非可选属性;`parameterSchemaSpecToJsonSchema()` 编译隐式开放的对象根;`defineTool()` 则将类型推导、编译与校验串联起来。原始 JSON Schema 的 `ToolDefinition` 仍是 `ToolRegistry.register()` 接受的输入,供 MCP 和其他外部工具使用。
+
+## 曾考虑的替代方案
+
+**Schemastery**(已作为 vendor 引入,用于插件 Config)经评估后被否决:它面向的是基于 StandardSchema 的校验/转换,而非 JSON Schema *生成*,因此会增加间接层却无法干净地产出协议格式(wire format)。
+
+## 后果
+
+- 第一方工具作者获得零类型断言的类型化参数;类型体操的成本留在核心包内部(符合 AGENTS.md 的类型安全策略)。
+- 当前节点、字面量约束、联合类型、JSON 值边界与对象开放性规则均由上述统一说明定义。
+- `InferArgs` 映射在类型层面有回归测试,源于早期一个可选性 bug。
diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml
rename to .agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.i18n.yaml
index a27551cd40..92053c2212 100644
--- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml
+++ b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.i18n.yaml
@@ -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-extract-example-app-packages.md: f2853db3f454d71572be003cfbf4f6dfd8377cdd
-2026-06-20-extract-example-app-packages.zh.md: 58d3d95996b1dacbc12178b46524374429df71ed
+2026-06-20-extract-example-app-packages.md: 06466aa575a535afe0ba614fb2c2c5b3e857aeab
+2026-06-20-extract-example-app-packages.zh.md: ccd8eae1524210d75770248566810423280df3b0
diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md
rename to .agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.md
index f2853db3f4..06466aa575 100644
--- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md
+++ b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.md
@@ -1,6 +1,7 @@
# Agent Note: Extract example apps into packages
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-20-extract-example-app-packages.zh.md)
diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.zh.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md
rename to .agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.zh.md
index 58d3d95996..ccd8eae152 100644
--- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md
+++ b/.agents/notes/archived/architecture/2026-06-20-extract-example-app-packages.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 将示例应用提取为独立包
Status: implemented
+Archived: 2026-07-26
[English](2026-06-20-extract-example-app-packages.md) | 中文
diff --git a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml
rename to .agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml
index 0a8c0d62fa..860757f042 100644
--- a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml
+++ b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml
@@ -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-03-filesystem-directory-listing-seam.md: c7db576ff3c7a56622f90a4400bd9297c9591bef
-2026-07-03-filesystem-directory-listing-seam.zh.md: 75ee6851127ca6d8c3fc60a66115d521d4627cdc
+2026-07-03-filesystem-directory-listing-seam.md: eb2650daf567d4bd98ed8553a4b743f5a01d945c
+2026-07-03-filesystem-directory-listing-seam.zh.md: 4bcda9f093f22c06348d4c69c4de3bd62f216f8e
diff --git a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md
rename to .agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.md
index c7db576ff3..eb2650daf5 100644
--- a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md
+++ b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.md
@@ -1,6 +1,7 @@
# Agent Note: Add direct directory listing to the filesystem seam
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-03-filesystem-directory-listing-seam.zh.md)
diff --git a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md
rename to .agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md
index 75ee685112..4bcda9f093 100644
--- a/.agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md
+++ b/.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 为文件系统 seam 添加直接目录列举能力
Status: implemented
+Archived: 2026-07-26
[English](2026-07-03-filesystem-directory-listing-seam.md) | 中文
diff --git a/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.i18n.yaml b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.i18n.yaml
new file mode 100644
index 0000000000..a05e6cec2d
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.i18n.yaml
@@ -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
+2026-07-05-windows-fs-permissions.md: da3aabd872156d04e27b8b5521486e1190ac1173
+2026-07-05-windows-fs-permissions.zh.md: 8cb3e90922894f1755e8861411a36463d1ec7367
diff --git a/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.md b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.md
new file mode 100644
index 0000000000..da3aabd872
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.md
@@ -0,0 +1,34 @@
+# Agent Note: Windows write-permission semantics — inherited DACLs, not mode bits
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-05-windows-fs-permissions.zh.md)
+
+The replacement-file decision in this record is superseded by [Windows DACL preservation](../bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md).
+
+## Problem
+
+`writeFileAtomic` in `@deepseek-ai/dsh-fs-local` protects write-in-progress content with POSIX mode bits: the staging directory is created `0o700`, the temp file is opened `0o600`, and new files default to `0o600`. On POSIX this keeps temporary content owner-only regardless of the parent directory's permissions.
+
+Windows has no working equivalent behind the same API. Node's `chmod` there drives only the read-only attribute (every mode this package passes carries owner-write, so the calls are benign no-ops), and `stat().mode` reports synthetic `0o666`/`0o444` bits. The real security state is the file's DACL: a newly created file or directory inherits from its parent, while replacement needs the explicit handling owned by the superseding Agent Note.
+
+## Decision
+
+New Windows files use directory inheritance rather than synthetic mode bits: the staging directory is created inside the target's parent directory (`dirname(absolutePath)`), so it and the temp file inherit the destination directory's DACL. Replacement files follow the stricter [DACL preservation contract](../bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md).
+
+Tests assert mode bits on POSIX only. Native Windows coverage pins the package-owned replacement behavior; new-file inheritance remains an operating-system contract rather than a machine-specific ACL allowlist.
+
+## Alternatives considered
+
+**Explicit owner-only DACLs for new files.** Rejected because they would break inheritance and surprise users whose project directories are deliberately shared. Replacement writes copy the target's existing DACL rather than inventing an owner-only policy.
+
+**Test-side ACL verification.** A `Get-Acl` SID allowlist or `icacls` would verify Windows inheritance and the machine's `%TEMP%` ACL rather than package behavior; `icacls` also localizes well-known account names, making parsing locale-fragile.
+
+**Skip `chmod` on Windows.** Platform-guarding benign no-op calls adds branches without changing behavior.
+
+## Consequences
+
+POSIX keeps owner-only temp content regardless of the parent directory. A new Windows target inside a broadly accessible directory inherits that accessibility by design; a replacement retains the target's narrower DACL when one exists.
+
+Mode preservation across a replace degenerates to a no-op on Windows: a writable file probes as `0o666`, and replaying that through `chmod` leaves the read-only attribute clear. A read-only target cannot be replaced there because publication fails before the synthetic mode would matter.
diff --git a/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.zh.md b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.zh.md
new file mode 100644
index 0000000000..8cb3e90922
--- /dev/null
+++ b/.agents/notes/archived/architecture/2026-07-05-windows-fs-permissions.zh.md
@@ -0,0 +1,34 @@
+# Agent Note: Windows 写入权限语义:继承 DACL,而非权限模式位
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-05-windows-fs-permissions.md) | 中文
+
+本记录中关于替换文件的决策已由 [Windows DACL 保留机制](../bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md)取代。
+
+## 问题
+
+`writeFileAtomic` 在 `@deepseek-ai/dsh-fs-local` 中使用 POSIX 权限模式位保护正在写入的内容:以 `0o700` 创建暂存目录,以 `0o600` 打开临时文件,新文件也默认使用 `0o600`。在 POSIX 上,无论父目录的权限如何,这些设置都能保证临时内容仅对所有者可见。
+
+Windows 在同一 API 背后没有可用的对等机制。Node 的 `chmod` 在 Windows 上只会驱动只读属性(此包传入的每种模式都包含所有者写权限,因此这些调用是无害的空操作),`stat().mode` 则报告合成的 `0o666`/`0o444` 权限位。真正的安全状态由文件的 DACL 决定:新建文件或目录会从父目录继承,替换操作则需要由取代本文的 Agent Note 所定义的显式处理。
+
+## 决策
+
+Windows 新建文件使用目录继承,而不使用合成的权限模式位:暂存目录在目标的父目录(`dirname(absolutePath)`)内创建,因此它和临时文件都会继承目标目录的 DACL。替换文件遵循更严格的 [DACL 保留契约](../bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md)。
+
+测试仅在 POSIX 上断言权限模式位。Windows 原生覆盖率锁定由本包(package)负责的替换行为;新文件继承仍属于操作系统契约,而不是针对特定机器的 ACL 允许清单。
+
+## 备选方案
+
+**为新文件显式设置仅所有者可用的 DACL。** 不予采纳,因为这会破坏继承,也会使特意共享项目目录的用户感到意外。替换写入会复制目标现有的 DACL,而不会自行设计仅所有者可用的策略。
+
+**在测试中验证 ACL。** `Get-Acl` SID 允许清单或 `icacls` 验证的是 Windows 继承机制以及当前机器的 `%TEMP%` ACL,而非包的行为;`icacls` 还会对知名账户名进行本地化,导致解析容易受语言区域影响。
+
+**在 Windows 上跳过 `chmod`。** 为无害的空操作调用增加平台守卫分支,不会改变任何行为。
+
+## 后果
+
+无论父目录的权限如何,POSIX 都会继续将临时内容限制为仅所有者可用。Windows 中的新目标如果位于广泛可访问的目录内,将按设计继承这种可访问性;如果替换目标存在更严格的 DACL,则会保留该 DACL。
+
+在 Windows 上,替换时的模式保留会退化为空操作:可写文件的探测结果为 `0o666`,通过 `chmod` 重放该模式会使只读属性继续保持清除状态。由于发布操作会在合成模式发挥作用前失败,Windows 上无法替换只读目标。
diff --git a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.i18n.yaml b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.i18n.yaml
rename to .agents/notes/archived/architecture/2026-07-23-unified-session-query-service.i18n.yaml
index 7b83a5dc52..0ac9bdaedf 100644
--- a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.i18n.yaml
+++ b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.i18n.yaml
@@ -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-23-unified-session-query-service.md: 676a42017ca42f9e649f6529f84787e7162faac0
-2026-07-23-unified-session-query-service.zh.md: d4449a415840d61cbb10f88def1062a13e556749
+2026-07-23-unified-session-query-service.md: f69836f60dfd73f9d8490687294b8407e53e9b32
+2026-07-23-unified-session-query-service.zh.md: bf25f4337dc8696ae54ffb16a7dd74437d45858b
diff --git a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.md b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.md
rename to .agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md
index 676a42017c..f69836f60d 100644
--- a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.md
+++ b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md
@@ -1,6 +1,7 @@
# Agent Note: Unified session query service
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-23-unified-session-query-service.zh.md)
diff --git a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.zh.md b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.zh.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.zh.md
rename to .agents/notes/archived/architecture/2026-07-23-unified-session-query-service.zh.md
index d4449a4158..bf25f4337d 100644
--- a/.agents/notes/implemented/architecture/2026-07-23-unified-session-query-service.zh.md
+++ b/.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 统一会话查询服务
Status: implemented
+Archived: 2026-07-26
[English](2026-07-23-unified-session-query-service.md) | 中文
diff --git a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml
rename to .agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml
index 7bdc4d2825..57b242ea29 100644
--- a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml
+++ b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml
@@ -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-24-dsh-commander-argument-adapter.md: c1124f67a2c5d9fbba1e04c896a1021c370befc9
-2026-07-24-dsh-commander-argument-adapter.zh.md: be96c354a7dea53446f3c2e35f0e4967265596f5
+2026-07-24-dsh-commander-argument-adapter.md: a5f6e580b91c0de1cdb433e1973bdff960384f06
+2026-07-24-dsh-commander-argument-adapter.zh.md: 4321ab154996c9b23ce58175b112234c92013cb8
diff --git a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.md b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.md
rename to .agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.md
index c1124f67a2..a5f6e580b9 100644
--- a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.md
+++ b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.md
@@ -1,6 +1,7 @@
# Agent Note: Parse `dsh` argv through one Commander adapter
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-24-dsh-commander-argument-adapter.zh.md)
diff --git a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md
similarity index 99%
rename from .agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md
rename to .agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md
index be96c354a7..4321ab1549 100644
--- a/.agents/notes/implemented/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md
+++ b/.agents/notes/archived/architecture/2026-07-24-dsh-commander-argument-adapter.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 通过单个 Commander 适配器解析 `dsh` 的 argv
Status: implemented
+Archived: 2026-07-26
[English](2026-07-24-dsh-commander-argument-adapter.md) | 中文
diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml
rename to .agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml
index cdbc45a2fd..dc1bbe851e 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml
+++ b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml
@@ -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-20-code-mode-result-card-completeness.md: 05ff0ed41c94bef7eb41204d8dd81bbda3c06016
-2026-07-20-code-mode-result-card-completeness.zh.md: a93be4cc42fca87ce4ef11b6ad3a6cbe64bef66f
+2026-07-20-code-mode-result-card-completeness.md: aff755e40b238e7ee448013fe0063bb26450fffe
+2026-07-20-code-mode-result-card-completeness.zh.md: 275e870c4be73e1adaa485f4e9fb979054fbf7c2
diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.md b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.md
rename to .agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md
index 05ff0ed41c..aff755e40b 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.md
+++ b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md
@@ -1,6 +1,7 @@
# Agent Note: Keep the Code Mode result card complete
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-20-code-mode-result-card-completeness.zh.md)
diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md
rename to .agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md
index a93be4cc42..275e870c4b 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md
+++ b/.agents/notes/archived/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 保证 Code Mode 结果卡片内容完整
Status: implemented
+Archived: 2026-07-26
[English](2026-07-20-code-mode-result-card-completeness.md) | 中文
diff --git a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml
rename to .agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml
index 19e9446f50..5e586b2802 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml
+++ b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml
@@ -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-22-collapsed-sidebar-control-rail.md: 940fcabf126941cc0e411b01c337e45831e442aa
-2026-07-22-collapsed-sidebar-control-rail.zh.md: 70ace36fafcb28aa714000262e31c8555d394854
+2026-07-22-collapsed-sidebar-control-rail.md: 6b61f5c64f3f1db19a2e242e9d9f054f30cb470c
+2026-07-22-collapsed-sidebar-control-rail.zh.md: b487950fc2c261060c44ad5b0ddc5820ca326006
diff --git a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md
rename to .agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md
index 940fcabf12..6b61f5c64f 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md
+++ b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.md
@@ -1,6 +1,7 @@
# Agent Note: A collapsed sidebar retains its control rail
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-22-collapsed-sidebar-control-rail.zh.md)
diff --git a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md
rename to .agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md
index 70ace36faf..b487950fc2 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md
+++ b/.agents/notes/archived/bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 侧边栏折叠后保留控制栏
Status: implemented
+Archived: 2026-07-26
[English](2026-07-22-collapsed-sidebar-control-rail.md) | 中文
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml
rename to .agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml
index 49482469f1..06fdf67ec7 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml
+++ b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml
@@ -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-23-demo-web-builds-client-bundles.md: a7d21987d4544246fd3c53864cedfc86279e9440
-2026-07-23-demo-web-builds-client-bundles.zh.md: f10184642b0c7869378802d3040ebf4dbe67d4e0
+2026-07-23-demo-web-builds-client-bundles.md: abd031c4ee6aeb7ed8c0baa61dfac16e5d64cc33
+2026-07-23-demo-web-builds-client-bundles.zh.md: 70604b344b5607b01815382da316805a9beaf27e
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.md b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.md
rename to .agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.md
index a7d21987d4..abd031c4ee 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.md
+++ b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.md
@@ -1,6 +1,7 @@
# Agent Note: demo:web builds the client plugin bundles
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-23-demo-web-builds-client-bundles.zh.md)
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md
rename to .agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md
index f10184642b..70604b344b 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md
+++ b/.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md
@@ -1,6 +1,7 @@
# Agent Note: demo:web 构建客户端插件的打包产物
Status: implemented
+Archived: 2026-07-26
[English](2026-07-23-demo-web-builds-client-bundles.md) | 中文
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml
rename to .agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml
index a2fcf167a7..9daab92e7e 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml
+++ b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml
@@ -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-23-thinking-row-disclosure-target.md: f698c3cb0b73bf5c65b5d4b5b3f29de3080e0af6
-2026-07-23-thinking-row-disclosure-target.zh.md: 0fba5c1d8f7beec7300dcd51e118a08d57d0e74f
+2026-07-23-thinking-row-disclosure-target.md: 9f748b6d76b9ddfe657e56da9f9e7f576b05599e
+2026-07-23-thinking-row-disclosure-target.zh.md: e33c951ca33e032e2eb2d306d90979d1e8471a17
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.md b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.md
similarity index 98%
rename from .agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.md
rename to .agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.md
index f698c3cb0b..9f748b6d76 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.md
+++ b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.md
@@ -1,6 +1,7 @@
# Agent Note: Thinking rows use one disclosure target
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-23-thinking-row-disclosure-target.zh.md)
diff --git a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md
similarity index 98%
rename from .agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md
rename to .agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md
index 0fba5c1d8f..e33c951ca3 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md
+++ b/.agents/notes/archived/bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md
@@ -1,6 +1,7 @@
# Agent Note: thinking 行使用单一展开目标
Status: implemented
+Archived: 2026-07-26
[English](2026-07-23-thinking-row-disclosure-target.md) | 中文
diff --git a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml
rename to .agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml
index 390f118a55..022b9a67f6 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml
+++ b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml
@@ -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-26-intent-draft-same-tick-echo.md: 1a4fdb48c0434bd37d7771dddb640720e1b610e6
-2026-07-26-intent-draft-same-tick-echo.zh.md: 9ecdf7154f5014de242021f99d2e51959c2a3169
+2026-07-26-intent-draft-same-tick-echo.md: 3ef91b123f9abe0817bf5f7e1ad48e2e6f1e2cb3
+2026-07-26-intent-draft-same-tick-echo.zh.md: d890a68f4b9c83713b7a52c44ecb6bcd16265669
diff --git a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.md b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.md
rename to .agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.md
index 1a4fdb48c0..3ef91b123f 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.md
+++ b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.md
@@ -1,6 +1,7 @@
# Agent Note: Intent draft echoes in the same tick
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-26-intent-draft-same-tick-echo.zh.md)
diff --git a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md
similarity index 99%
rename from .agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md
rename to .agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md
index 9ecdf7154f..d890a68f4b 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md
+++ b/.agents/notes/archived/bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Intent draft echoes in the same tick
Status: implemented
+Archived: 2026-07-26
[English](2026-07-26-intent-draft-same-tick-echo.md) | 中文
diff --git a/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.i18n.yaml b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.i18n.yaml
new file mode 100644
index 0000000000..56a3c4f3d4
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.i18n.yaml
@@ -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
+2026-06-14-acp-agent-client-protocol.md: ee616a58cbd0201f14c7000672aec7c2485af0b1
+2026-06-14-acp-agent-client-protocol.zh.md: 8af061a30fed72c60c9e7a1747970a23415d530f
diff --git a/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.md b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.md
new file mode 100644
index 0000000000..ee616a58cb
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.md
@@ -0,0 +1,62 @@
+# Agent Note: Agent Client Protocol (ACP) support — drive the coding agent from external editors
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-06-14-acp-agent-client-protocol.zh.md)
+
+> Superseded by [ACP as an automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md). This note records the retired editor-facing bridge design.
+
+## Problem
+
+The harness originally exposed agents only through a readline loop. That surface could carry text, but it gave an editor no structured way to create or resume sessions, correlate prompt completion, stream reasoning and tool activity, render tool-specific UI, ask for permission, or cancel one conversation without disturbing another. ACP defines those interactions as JSON-RPC over stdio, and Zed is the target client used to make concrete compatibility decisions.
+
+The bridge must preserve the harness's existing ownership boundaries. It cannot depend on the concrete agent loop, bypass the tool registry, execute shell commands in the editor, or invent a second source of session truth. stdout is also the protocol transport, so any accidental log output corrupts the connection.
+
+## Decision
+
+`@deepseek-ai/dsh-acp` was a UI/client-driver plugin in the `ui` package group (it now lives in `acp`). It used `@agentclientprotocol/sdk`'s `AgentSideConnection` over stdin/stdout and programmed only interface services: the agent create/resume factory, session persistence, tool registry, user interaction, and optional approval/bash capabilities. It did not change the agent loop and was not a capability-seam implementation.
+
+The bridge implements the following stable session path:
+
+- `initialize` negotiates the protocol version, advertises text plus `resource_link` prompts, and advertises `loadSession`.
+- `session/new` validates an absolute `cwd`, stores it in `SessionHeader`, creates an agent through `ctx.agents`, and returns any composition-backed config options.
+- `session/load` validates the requested cwd against persisted metadata before constructing an agent, reserves the id across the asynchronous resume, replays user/assistant/tool events as ACP updates, and reports the resumed config-option fold.
+- `session/prompt` accepts text and resource links, rejects unsupported or empty content, allows one in-flight prompt per session, and settles against that prompt's owning `turn/end`. An error turn rejects the RPC; other closed turn reasons map through a total ACP stop-reason codec.
+- `session/cancel` calls the queue-aware agent cancel path and settles only the addressed session's prompt.
+
+Tool-call presentation remains tool-owned. A tool's `presentCall` and `presentResult` return the `generic`, `terminal`, or `diff` render-intent variants; the bridge switches on that union and maps it to ACP. Presenter-less tools receive a generic fallback. Bash terminal cards use Zed's capability-gated `_meta.terminal_info`, `_meta.terminal_output`, and `_meta.terminal_exit` convention; the harness still executes the command through `ctx.bash`, preserving sandbox, environment scrub, ownership, and cwd. Clients without that extension receive ordinary text content. Filesystem tools provide diff cards and file locations without hard-coded tool-name branches in the bridge.
+
+Permission handling is an answerer on the [user-approval seam](2026-07-06-approval-seam.md), not an ask-every-tool policy in ACP. An `approval/request` for a bridge-owned agent with a call id becomes `session/request_permission` on that agent's editor session, with one-shot allow/reject choices. Foreign or call-less requests delegate; a missing or failed answerer remains fail-closed. The plugin that asks—such as a pre-execute policy or bash escalation—owns the decision to ask.
+
+When `ctx.permission` is composed, the bridge exposes one `permission` select from the deployment's preset table. The shipped `workspace-write` and `danger-full-access` presets each bundle a sandbox mode with an approval policy; unmatched effective knobs produce the switch-away-only `custom` state. `session/set_config_option` validates through `PermissionService.set()` and writes both owning knob events. A switch during an open turn appends immediately; an idle switch is overlaid in responses and anchored at the next `agent/prompt-submit`, before request assembly. Until then it is memory-only, so a crash restores the durable fold. ACP session modes are not modeled because config options are the forward protocol surface; `AcpConfig.model` remains connection-wide.
+
+The bridge also provides the ACP-backed `UserInteractionProvider`: `ask_user_question` requests become form elicitations on the owning session. Select, multi-select, option descriptions, and custom-answer override semantics are preserved.
+
+Lifecycle ownership is explicit. The bridge holds an `AgentHandle` per live session. Disconnect and Cordis disposal cancel pending prompts, dispose every handle in parallel, await loop quiescence and persistence flush, and then remove the records. Stream notification failures are contained so a vanished client cannot corrupt an agent turn. The ACP app composition loads no stdout logger; a test guards stdout as framed JSON-RPC only.
+
+The current protocol contract lives in the [`dsh-acp` package README](../../../../packages/acp/acp/README.md).
+
+## Alternatives considered
+
+**A prepended `tools/execute` listener that asks on every ACP-owned call** — rejected. It would hard-code permission policy into the UI bridge, ask even when no policy requires it, and could not serve approval requests that arise after execution begins. The shared user-approval seam keeps mechanism, asking policy, and UI answerer separate.
+
+**Inject the concrete `agentLoop`** — rejected. Agent creation, resume, idle observation, and disposal are interface-level ownership operations on `dsh-agent`; a UI plugin does not need a dependency-rule exception.
+
+**Execute bash through ACP `terminal/*`** — rejected. That would move execution outside the harness and bypass its sandbox, credential scrub, task ownership, cwd resolution, and session log. Terminal metadata is presentation only.
+
+**Represent permission presets as ACP session modes** — rejected. The deployment-defined preset is already one config-option select, while session modes are the legacy surface slated for removal in ACP v2.
+
+**Hijack stdout defensively** — rejected. Process-wide monkey-patching is outside Cordis effect ownership and races the protocol transport. The app composition owns stdout purity.
+
+## Consequences
+
+Editors can create, load, prompt, cancel, render, ask, and reconfigure multiple harness sessions over one ACP connection without a loop-specific dependency. The session event log remains the durable source for replay, prompt settlement, cwd, and per-session configuration. Tool presentation and human-answer channels remain extensible plugin contracts instead of ACP-specific behavior.
+
+The bridge deliberately does not implement session list/delete/resume/close capabilities, MCP passthrough, additional directories, image/audio/embedded-resource prompts, plans, slash commands, usage updates, editor filesystem delegation, or the ACP terminal execution sub-protocol. Runtime model selection was added later through standard session config options by the [LLM catalog and ACP selection Agent Note](../architecture/2026-07-15-llm-model-catalog-and-acp-selection.md).
+
+An idle config selection is truthful in the live response but not durable until the next `agent/prompt-submit` anchors it inside the open turn. Crashing before that boundary loses the pending selection; this is the cost of keeping session events turn-enclosed and replay-safe.
+
+## Verification
+
+The ACP suites cover the in-memory protocol codec, create/load replay, exact prompt settlement, cancellation races, unsupported content, tool presentation, terminal capability fallback, permission outcome mapping, config-option validation and persistence, multi-session isolation, disconnect/disposal quiescence, and HMR cleanup. Snapshot and built-bin tests exercise the app composition, while the real-API e2e self-skips without a key.
diff --git a/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.zh.md b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.zh.md
new file mode 100644
index 0000000000..8af061a30f
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-14-acp-agent-client-protocol.zh.md
@@ -0,0 +1,62 @@
+# Agent Note: Agent Client Protocol(ACP)支持——从外部编辑器驱动编码 agent
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-06-14-acp-agent-client-protocol.md) | 中文
+
+> 已被 [ACP 作为仅面向自动化的协议](../simplification/2026-07-23-acp-automation-only-protocol.md)取代。本 Agent Note 记录已退役的面向编辑器的桥接层设计。
+
+## 问题
+
+harness 最初仅通过 readline 循环暴露 agent。该接口能传输文本,但编辑器无法以结构化方式创建或恢复会话、关联提示词完成、流式输出推理(reasoning)与工具活动、渲染工具专属 UI、请求权限,或在不干扰其他对话的前提下取消某个对话。ACP(Agent Client Protocol)将这些交互定义为基于 stdio 的 JSON-RPC,Zed 是用于做出具体兼容性决策的目标客户端。
+
+桥接层必须保持 harness 既有的所有权边界。它不能依赖具体的 agent loop(智能体循环),不能绕过工具注册表,不能在编辑器中执行 shell 命令,也不能发明第二个会话真源。stdout 同时也是协议传输通道,因此任何意外的日志输出都会破坏连接。
+
+## 决策
+
+`@deepseek-ai/dsh-acp` 曾是 `ui` 包组中的 UI/客户端驱动插件(现位于 `acp`)。它使用 `@agentclientprotocol/sdk` 的 `AgentSideConnection`(基于 stdin/stdout),仅编排接口服务:agent 创建/恢复工厂、会话持久化、工具注册表、用户交互,以及可选的审批/bash 能力。它不修改 agent loop,也不是能力 seam 的实现。
+
+桥接层实现以下稳定的会话路径:
+
+- `initialize` 协商协议版本,声明支持 text 与 `resource_link` 类型的提示词,并声明 `loadSession` 能力。
+- `session/new` 校验绝对路径 `cwd`,将其存入 `SessionHeader`,通过 `ctx.agents` 创建 agent,并返回由组合层支持的配置选项。
+- `session/load` 在构造 agent 之前校验请求的 cwd 与持久化元数据是否一致,在异步恢复期间保留 id,将用户/助手/工具事件作为 ACP update 回放,并报告恢复后的 config-option 折叠结果。
+- `session/prompt` 接受文本和 resource link,拒绝不支持的或空的内容,每个会话同时只允许一个 in-flight 提示词,并在该提示词所属的 `turn/end` 时结算。错误轮次拒绝 RPC;其他关闭轮次的原因通过一个全覆盖的 ACP stop-reason 编解码器映射。
+- `session/cancel` 调用队列感知的 agent 取消路径,仅结算被寻址会话的提示词。
+
+工具调用的展示仍由工具自身负责。工具的 `presentCall` 和 `presentResult` 返回 `generic`、`terminal` 或 `diff` 渲染意图变体;桥接层对该联合类型做 switch 并映射到 ACP。没有 presenter 的工具获得通用回退。Bash 终端卡片使用 Zed 的能力门控约定 `_meta.terminal_info`、`_meta.terminal_output` 和 `_meta.terminal_exit`;harness 仍通过 `ctx.bash` 执行命令,保留沙箱、环境清洗、所有权和 cwd。不支持该扩展的客户端收到普通文本内容。文件系统工具提供 diff 卡片和文件位置,桥接层中无需硬编码工具名分支。
+
+权限处理是[用户审批 seam](2026-07-06-approval-seam.md)上的一个 answerer,而非 ACP 中的「每次工具调用都询问」策略。对桥接层所属 agent 且带有 call id 的 `approval/request`,会变为该 agent 编辑器会话上的 `session/request_permission`,提供一次性允许/拒绝选项。外部请求或无 call id 的请求委托给下游;缺失或失败的 answerer 会在故障时保持拒绝。发起询问的插件(如预执行策略或 bash 升级)拥有「是否询问」的决策权。
+
+当 `ctx.permission` 被组合时,桥接层从部署的预设表中暴露一个 `permission` select。已发布的 `workspace-write` 和 `danger-full-access` 预设各自捆绑一个沙箱模式与一条审批策略;无法匹配的有效旋钮组合产生只能切走的 `custom` 状态。`session/set_config_option` 通过 `PermissionService.set()` 校验并写入两个所属旋钮事件。在开放轮次中的切换立即追加;空闲时的切换叠加在响应中,并在下一次 `agent/prompt-submit` 时锚定到开放轮次之前的请求组装阶段。在此之前它仅存于内存,因此崩溃后恢复的是持久化的折叠结果。ACP session mode 不被建模,因为 config option 是面向未来的协议表面;`AcpConfig.model` 保持连接级别。
+
+桥接层还提供基于 ACP 的 `UserInteractionProvider`:`ask_user_question` 请求变为所属会话上的表单引导。select、multi-select、选项描述与自定义回答覆盖语义均被保留。
+
+生命周期所有权是显式的。桥接层为每个活跃会话持有一个 `AgentHandle`。断连和 Cordis dispose(资源释放)会取消待处理的提示词,并行 dispose 所有 handle,等待循环完全停稳与持久化刷写,然后移除记录。流通知失败被隔离,因此消失的客户端不会破坏 agent 轮次。ACP 应用组合不加载 stdout logger;一个测试守卫 stdout 仅包含帧化的 JSON-RPC。
+
+当前的协议契约见 [`dsh-acp` 包 README](../../../../packages/acp/acp/README.md)。
+
+## 曾考虑的替代方案
+
+**在 `tools/execute` 监听器前置一层,对每个 ACP 所属调用都询问权限**:否决。这会将权限策略硬编码到 UI 桥接层,即使没有策略要求也会询问,且无法服务于执行开始后才产生的审批请求。共享的 user-approval seam 将机制、询问策略和 UI answerer 分离。
+
+**注入具体的 `agentLoop`**:否决。agent 的创建、恢复、空闲观察与释放是 `dsh-agent` 上的接口级所有权操作;UI 插件不需要依赖规则例外。
+
+**通过 ACP `terminal/*` 执行 bash**:否决。这会将执行移到 harness 之外,绕过其沙箱、凭证清洗、任务所有权、cwd 解析与会话日志。终端元数据仅用于展示。
+
+**将权限预设表示为 ACP session mode**:否决。部署定义的预设已经是一个 config-option select,而 session mode 是 ACP v2 计划移除的遗留接口。
+
+**防御性劫持 stdout**:否决。进程级 monkey-patching 超出 Cordis 副作用所有权范围,且与协议传输存在竞争。应用组合拥有 stdout 纯净性。
+
+## 后果
+
+编辑器可以通过一条 ACP 连接创建、加载、提交提示词、取消、渲染、询问和重新配置多个 harness 会话,无需依赖特定的循环实现。会话事件日志仍是回放、提示词结算、cwd 与每会话配置的持久真源。工具展示与人工回答通道仍是可扩展的插件契约,而非 ACP 专属行为。
+
+桥接层有意不实现会话列表/删除/恢复/关闭能力、MCP 透传、附加目录、图片/音频/嵌入资源提示词、plan、斜杠命令、用量更新、编辑器文件系统委托或 ACP 终端执行子协议。后续已通过标准会话配置选项加入运行时模型选择,见 [LLM 目录与 ACP 选择 Agent Note](../architecture/2026-07-15-llm-model-catalog-and-acp-selection.md)。
+
+空闲时的配置选择在实时响应中是真实的,但在下一次 `agent/prompt-submit` 将其锚定到开放轮次之前不具持久性。在该边界之前崩溃会丢失待定选择;这是保持会话事件封闭于轮次内且回放安全的代价。
+
+## 验证
+
+ACP 测试套件覆盖内存协议编解码器、创建/加载回放、精确的提示词结算、取消竞争、不支持的内容、工具展示、终端能力回退、权限结果映射、config-option 校验与持久化、多会话隔离、断连/释放后的完全停稳,以及 HMR(热模块替换)清理。快照测试与 built-bin 测试验证应用组合,真实 API 的 e2e 测试在无 key 时自动跳过。
diff --git a/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml
new file mode 100644
index 0000000000..fee3d69d57
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml
@@ -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
+2026-06-18-acp-terminal-and-tool-rendering.md: 3ffe9b698d453a5d53ce4cc28bc85d8dd75f37a0
+2026-06-18-acp-terminal-and-tool-rendering.zh.md: 3dd8a110260519d0b6342f8984be98a2d1c53f01
diff --git a/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.md b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.md
new file mode 100644
index 0000000000..3ffe9b698d
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.md
@@ -0,0 +1,51 @@
+# Agent Note: Rich ACP bash rendering — the terminal card via the `_meta` convention
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-06-18-acp-terminal-and-tool-rendering.zh.md)
+
+> Superseded for ACP by [ACP as an automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md). Tool render intents remain available to UI transports, but ACP no longer projects them into terminal cards.
+
+## Problem
+
+The ACP bridge lets each tool own its call rendering via `presentCall`/`presentResult` (see [tool-call UI presentation](2026-06-14-acp-agent-client-protocol.md) and `packages/core/tools`). For `bash` we surface the exact command as the `tool_call` title, the model's `description` as a content text block, `kind: 'execute'`, and the completed output wrapped in a fenced ` ```console ` text block.
+
+Reference editors render terminal metadata as a dedicated card with cwd, command, live-style output, and exit status; plain text loses that structure. The command is the title because execute cards hide raw input, while the human-readable description remains a separate block above the card.
+
+## Key finding: agent-executed terminals use a `_meta` convention, NOT `terminal/create`
+
+The ACP spec has a *client-side* terminal sub-protocol — the agent calls the client's `terminal/create` with `{ command, args, cwd, env }` and the **editor** executes the process, then the agent reads `terminal/output` / `wait_for_exit`. That model is wrong for us: our harness executes bash itself through `dsh-bash` (sandboxed env-scrub, background-task ownership, per-session cwd). Routing execution to the editor would bypass all of that and fork execution into two backends.
+
+Studying the two reference agents (2026-06-18) shows neither uses `terminal/create` for their own shell tool — **both keep agent-side execution and emit a `_meta` convention** that Zed special-cases:
+
+- **`claude-agent-acp`** (`tools.ts`, `acp-agent.ts`): gated on `clientCapabilities._meta.terminal_output`. The `tool_call` carries `content: [{ type: 'terminal', terminalId }]` and `_meta.terminal_info.{ terminal_id, cwd }`; output/exit arrive on the `tool_call_update`'s `_meta.terminal_output.{ terminal_id, data }` and `_meta.terminal_exit.{ terminal_id, exit_code, signal }`.
+- **`codex-acp`** (`CodexToolCallMapper.ts`, `TerminalOutputMode.ts`): same `terminal_info` on the call; output via `_meta.terminal_output` (full) or `_meta.terminal_output_delta` (incremental), selected from the same `_meta.terminal_output` capability.
+
+Zed's side (`crates/agent_servers/src/acp.rs`, verified): on a `ToolCall` whose `_meta.terminal_info.terminal_id` is set, it registers a **display-only** terminal (header = `terminal_info.cwd`, label = `tool_call.title`); on a `ToolCallUpdate`, `_meta.terminal_output.data` writes to that terminal and `_meta.terminal_exit.{exit_code,signal}` sets the status. It advertises the capability as `clientCapabilities._meta.terminal_output = true`. `_meta` itself is a spec-blessed ACP extensibility point (typed `{[k]: unknown} | null` on `ToolCall`/`ToolCallUpdate`); the *specific keys* here (`terminal_info`/`terminal_output`/`terminal_exit`) are a Zed convention, not part of the ACP spec — but they are the de-facto contract for the Zed integration and the only way to get the terminal card while keeping execution agent-side.
+
+## Decision
+
+Keep `dsh-bash` agent-side execution; render the terminal card via the `_meta` convention, capability-gated, with the ` ```console ` text block as the guaranteed fallback.
+
+1. **Capability.** `initialize` reads `clientCapabilities._meta.terminal_output` and the bridge remembers it per connection.
+2. **Neutral presentation vocabulary.** `dsh-tools` gains a terminal-shaped presentation a tool can return — provider-neutral (`cwd`, the output `data`, an `exitCode`/`signal`), NO ACP types. `dsh-tool-bash` returns it for `bash` (cwd from the resolved workdir; output + exit parsed from the run result).
+3. **Bridge mapping.** When the client advertised the capability, the bridge maps that presentation to: on `tool_call`, `content:[…, {type:'terminal', terminalId}]` (any tool `content`, e.g. the description, rendered BEFORE the terminal block) + `_meta.terminal_info.{terminal_id,cwd}`; on `tool_call_update`, `_meta.terminal_output.{terminal_id,data}` (the captured output) + `_meta.terminal_exit.{terminal_id, exit_code|signal}` (the parsed exit), with the update's text `content` OMITTED (an ACP `tool_call_update.content` REPLACES the call's content collection, so re-sending the fenced block would clobber the terminal content block). `terminalId` is derived from the harness `callId` (stable, unique per call). When the capability is absent, the bridge sends the description content block on the call and the existing ` ```console ` text content on the update — unchanged.
+4. **The exit pill is parsed from the rendered output; no new execution path, no live streaming.** Output is attached at completion (from the agent's own `tool/result`), not streamed token-by-token. The exit-status pill (`_meta.terminal_exit.{exit_code,signal}`) IS emitted: the pure `presentResult(args, result)` seam sees only content blocks, so `dsh-tool-bash` recovers the structured exit by parsing the status markers (`[exit code: N]` / `[killed by signal: …]`) that `renderResult` appended — the parse is the exact inverse of the marker emission, the two co-evolve in one file, and a round-trip test guards the pair. Disposal is unaffected: nothing new to tear down, since the bridge never creates a client-side terminal.
+
+## Alternatives considered
+
+- **The ACP client-side terminal sub-protocol (`terminal/create`)** — explicitly rejected: the editor would execute the process, bypassing `dsh-bash`'s env scrub, background-task ownership, and per-session cwd, and forking execution into two backends. Both reference agents reject it the same way (the key finding above); agent-side execution plus the `_meta` convention is the only shape that yields the terminal card while keeping the harness's execution policy.
+- **Threading a structured exit through the event schema** — rejected in favor of the marker round-trip: the pure `presentResult(args, result)` seam sees only content blocks, and the parse is the exact inverse of the marker emission, co-evolving in one file under a round-trip test.
+
+## Consequences
+
+- **Zed-convention `_meta` keys.** The terminal card rides on Zed-specific keys (`terminal_info`/`terminal_output`/`terminal_exit`) inside ACP's spec-blessed `_meta` extensibility point, NOT on the ACP terminal sub-protocol. A client that doesn't recognize the keys still gets the text fallback (the capability gate ensures we only emit them when the client opted in via `_meta.terminal_output`), so a non-Zed client is never worse off. If ACP later standardizes agent-executed terminals, migrate to that and drop the convention keys.
+- **Capability honesty.** Emit terminal metadata ONLY when the client advertised `_meta.terminal_output`; the text fallback is the contract for everyone else and must never regress. Covered by a no-capability test asserting the ` ```console ` path.
+- **terminalId collisions.** Deriving it from the per-call `callId` keeps it unique within a session and stable across the call/result pair; never reuse one across calls.
+- **Exit parsed from rendered text.** The exit pill recovers `exit_code`/`signal` by parsing `renderResult`'s status markers rather than threading a structured exit through the event schema (which the pure `presentResult` seam never sees). The parse is the exact inverse of the marker emission and lives in the same file; a round-trip test pins the pair so a marker-format change that breaks the parse fails the suite. If the markers ever need to diverge from what the pill wants, surface a structured exit on the result event instead.
+- **Provider-neutral vocabulary creep.** The terminal presentation widens the `dsh-tools` surface; keep it neutral (no ACP types leak into `dsh-tools`) and only as rich as a second UI consumer would also want.
+
+## Out of scope / non-goals
+
+The text-block baseline stays the no-capability default. Two follow-ups are deliberately NOT built here and would each warrant their own Agent Note when someone takes them on: **live incremental streaming** (`_meta.terminal_output_delta` as chunks arrive, which needs an incremental-output seam on `dsh-bash`), and **command classification** (parsing a `cat`/`sed` as a `read` card with a file location, a `grep` as a `search`, etc., falling back to the terminal card — display-only, must never change what executes).
diff --git a/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md
new file mode 100644
index 0000000000..3dd8a11026
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md
@@ -0,0 +1,51 @@
+# Agent Note: 富 ACP bash 渲染——通过 `_meta` 约定实现终端卡片
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-06-18-acp-terminal-and-tool-rendering.md) | 中文
+
+> 就 ACP 而言已被 [ACP 作为仅面向自动化的协议](../simplification/2026-07-23-acp-automation-only-protocol.md)取代。工具渲染意图对 UI 传输层仍然可用,但 ACP 不再将其投影为终端卡片。
+
+## 问题
+
+ACP(Agent Client Protocol)桥接层允许每个工具通过 `presentCall`/`presentResult` 自行控制调用渲染(见[工具调用 UI 呈现](2026-06-14-acp-agent-client-protocol.md)与 `packages/core/tools`)。对于 `bash`,我们将确切命令作为 `tool_call` 标题呈现,模型的 `description` 作为一个内容文本块,`kind: 'execute'`,完成后的输出包裹在 ` ```console ` 围栏文本块中。
+
+参考编辑器将终端元数据渲染为一张专用卡片,包含 cwd、命令、实时风格的输出和退出状态;纯文本则丢失了这些结构。命令之所以作为标题,是因为执行卡片隐藏原始输入,而人类可读的描述保留为卡片上方的独立块。
+
+## 关键发现:agent 执行的终端使用 `_meta` 约定,而非 `terminal/create`
+
+ACP 规范有一个*客户端侧*终端子协议:agent(智能体)调用客户端的 `terminal/create`(传入 `{ command, args, cwd, env }`),由**编辑器**执行进程,然后 agent 读取 `terminal/output` / `wait_for_exit`。这个模型不适合我们:我们的 harness 通过 `dsh-bash` 自行执行 bash(沙箱化的环境清理、后台任务所有权、按会话的 cwd)。将执行路由到编辑器会绕过所有这些机制,并将执行分叉到两个后端。
+
+研究两个参考 agent(2026-06-18)发现,二者都没有为自己的 shell 工具使用 `terminal/create`——**两者都保持 agent 侧执行,并发出一套 `_meta` 约定**,由 Zed 特殊处理:
+
+- **`claude-agent-acp`**(`tools.ts`、`acp-agent.ts`):以 `clientCapabilities._meta.terminal_output` 为门控。`tool_call` 携带 `content: [{ type: 'terminal', terminalId }]` 与 `_meta.terminal_info.{ terminal_id, cwd }`;输出和退出通过 `tool_call_update` 的 `_meta.terminal_output.{ terminal_id, data }` 与 `_meta.terminal_exit.{ terminal_id, exit_code, signal }` 到达。
+- **`codex-acp`**(`CodexToolCallMapper.ts`、`TerminalOutputMode.ts`):调用上同样携带 `terminal_info`;输出通过 `_meta.terminal_output`(完整)或 `_meta.terminal_output_delta`(增量),由同一个 `_meta.terminal_output` 能力选择。
+
+Zed 侧(`crates/agent_servers/src/acp.rs`,已验证):收到 `ToolCall` 且其 `_meta.terminal_info.terminal_id` 已设置时,注册一个**仅展示**的终端(header = `terminal_info.cwd`,label = `tool_call.title`);收到 `ToolCallUpdate` 时,`_meta.terminal_output.data` 写入该终端,`_meta.terminal_exit.{exit_code,signal}` 设置状态。客户端通过 `clientCapabilities._meta.terminal_output = true` 声明此能力。`_meta` 本身是 ACP 规范认可的扩展点(在 `ToolCall`/`ToolCallUpdate` 上类型为 `{[k]: unknown} | null`);这里的*具体键*(`terminal_info`/`terminal_output`/`terminal_exit`)是 Zed 约定,不属于 ACP 规范,但它们是 Zed 集成的事实契约,也是在保持 agent 侧执行的前提下获得终端卡片的唯一方式。
+
+## 决策
+
+保持 `dsh-bash` 的 agent 侧执行;通过 `_meta` 约定渲染终端卡片,以能力声明为门控,以 ` ```console ` 文本块作为保底回退。
+
+1. **能力声明。** `initialize` 读取 `clientCapabilities._meta.terminal_output`,桥接层按连接记住它。
+2. **提供方无关的展示词汇。** `dsh-tools` 新增一种终端形态的展示结构,工具可返回它——提供方无关(`cwd`、输出 `data`、`exitCode`/`signal`),不含 ACP 类型。`dsh-tool-bash` 为 `bash` 返回该结构(cwd 来自解析后的工作目录;输出与退出从运行结果解析)。
+3. **桥接映射。** 当客户端声明了该能力时,桥接层将展示结构映射为:在 `tool_call` 上,`content:[…, {type:'terminal', terminalId}]`(工具的任何 `content`,如描述,渲染在终端块之前)+ `_meta.terminal_info.{terminal_id,cwd}`;在 `tool_call_update` 上,`_meta.terminal_output.{terminal_id,data}`(捕获的输出)+ `_meta.terminal_exit.{terminal_id, exit_code|signal}`(解析后的退出),且 update 的文本 `content` 被省略(ACP 的 `tool_call_update.content` 会替换调用的 content 集合,因此重新发送围栏块会覆盖终端内容块)。`terminalId` 由 harness 的 `callId` 派生(稳定、每次调用唯一)。当能力未声明时,桥接层在调用上发送描述内容块,在 update 上发送既有的 ` ```console ` 文本内容——行为不变。
+4. **退出信息从渲染输出中解析;无新执行路径,无实时流式传输。** 输出在完成时附加(来自 agent 自身的 `tool/result`),不逐 token 流式传输。退出状态(`_meta.terminal_exit.{exit_code,signal}`)确实会发出:纯 `presentResult(args, result)` seam 只能看到内容块,因此 `dsh-tool-bash` 通过解析 `renderResult` 追加的状态标记(`[exit code: N]` / `[killed by signal: …]`)来恢复结构化退出信息——解析是标记发出的精确逆操作,二者在同一文件中共同演进,一个往返测试守护这对关系。资源释放不受影响:无需新增拆除逻辑,因为桥接层从未创建客户端侧终端。
+
+## 曾考虑的替代方案
+
+- **ACP 客户端侧终端子协议(`terminal/create`)**:明确否决。编辑器将执行进程,绕过 `dsh-bash` 的环境清理、后台任务所有权和按会话的 cwd,并将执行分叉到两个后端。两个参考 agent 以同样的方式否决了它(见上述关键发现);agent 侧执行加 `_meta` 约定是在保持 harness 执行策略的同时获得终端卡片的唯一形态。
+- **通过事件 schema 传递结构化退出信息**:否决,改用标记往返方案。纯 `presentResult(args, result)` seam 只能看到内容块,而解析是标记发出的精确逆操作,二者在同一文件中共同演进,由往返测试守护。
+
+## 后果
+
+- **Zed 约定的 `_meta` 键。** 终端卡片依赖 Zed 特有的键(`terminal_info`/`terminal_output`/`terminal_exit`),位于 ACP 规范认可的 `_meta` 扩展点内,而非 ACP 终端子协议。不识别这些键的客户端仍然获得文本回退(能力门控确保我们仅在客户端通过 `_meta.terminal_output` 声明支持时才发出这些键),因此非 Zed 客户端不会变差。如果 ACP 日后标准化了 agent 执行的终端,则迁移到该标准并移除约定键。
+- **能力诚实。** 仅在客户端声明了 `_meta.terminal_output` 时才发出终端元数据;文本回退是对其他所有客户端的契约,绝不可退化。由一个无能力测试覆盖,断言 ` ```console ` 路径。
+- **terminalId 冲突。** 从每次调用的 `callId` 派生,保证在会话内唯一且在 call/result 对之间稳定;绝不跨调用复用。
+- **退出信息从渲染文本解析。** 退出信息通过解析 `renderResult` 的状态标记恢复 `exit_code`/`signal`,而非通过事件 schema 传递结构化退出(纯 `presentResult` seam 看不到后者)。解析是标记发出的精确逆操作,且位于同一文件中;往返测试固定了这对关系,标记格式变更若破坏解析则测试套件失败。如果标记格式日后需要与退出信息分道扬镳,则改为在 result 事件上暴露结构化退出。
+- **提供方无关词汇的蔓延。** 终端展示结构扩大了 `dsh-tools` 的接口面;保持其中立性(不让 ACP 类型泄漏到 `dsh-tools`),且只提供第二个 UI 消费方同样需要的丰富度。
+
+## 超出范围 / 非目标
+
+文本块基线仍为无能力声明时的默认行为。以下两项后续工作有意不在此处构建,各自需要单独的 Agent Note:**实时增量流式传输**(在分片到达时发出 `_meta.terminal_output_delta`,需要在 `dsh-bash` 上新增增量输出 seam);**命令分类**(将 `cat`/`sed` 解析为带文件位置的 `read` 卡片,将 `grep` 解析为 `search`,回退到终端卡片——仅展示,绝不改变实际执行内容)。
diff --git a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.i18n.yaml b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.i18n.yaml
rename to .agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.i18n.yaml
index c7281e3189..e3de092b15 100644
--- a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.i18n.yaml
@@ -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-30-subagent-observe-enrich.md: a07cef95630689d1ca8cacd3eb7c50e691cb304a
-2026-06-30-subagent-observe-enrich.zh.md: 578aae0a7273defcc1f88fb2a50c83ef454e3c16
+2026-06-30-subagent-observe-enrich.md: 7140616ac4a9725652ed779cba3d4232b6b5127b
+2026-06-30-subagent-observe-enrich.zh.md: 5cf81e9ace48650b834f2cf5e8ec7cc81b8b0e4d
diff --git a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.md b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.md
rename to .agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.md
index a07cef9563..7140616ac4 100644
--- a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.md
+++ b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.md
@@ -1,6 +1,7 @@
# Agent Note: Subagent lifecycle enrichment — lastAssistantMessage (observe-only)
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-30-subagent-observe-enrich.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.zh.md b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.zh.md
rename to .agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.zh.md
index 578aae0a72..5cf81e9ace 100644
--- a/.agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.zh.md
+++ b/.agents/notes/archived/feature/2026-06-30-subagent-observe-enrich.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Subagent 生命周期丰富化——lastAssistantMessage(仅观察)
Status: implemented
+Archived: 2026-07-26
[English](2026-06-30-subagent-observe-enrich.md) | 中文
diff --git a/.agents/notes/archived/feature/2026-07-07-plan-mode.i18n.yaml b/.agents/notes/archived/feature/2026-07-07-plan-mode.i18n.yaml
new file mode 100644
index 0000000000..aaae551f3e
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-07-plan-mode.i18n.yaml
@@ -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
+2026-07-07-plan-mode.md: dfc81c04baeb924ae04fbb51a27d282c5050f217
+2026-07-07-plan-mode.zh.md: 20662e208a2211a3c6add266845b746b31af20d9
diff --git a/.agents/notes/archived/feature/2026-07-07-plan-mode.md b/.agents/notes/archived/feature/2026-07-07-plan-mode.md
new file mode 100644
index 0000000000..dfc81c04ba
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-07-plan-mode.md
@@ -0,0 +1,197 @@
+# Agent Note: Plan mode — a logged per-agent session mode
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-07-plan-mode.zh.md)
+
+> **Superseded vocabulary (2026-07-22):** [Collapse named session modes into plan mode](../simplification/2026-07-22-plan-specific-collaboration-state.md) replaces this note's generic `dsh-mode`, `mode/set`, definition map, and `ctx.modes` design with the current plan-specific `dsh-plan-mode`, `plan/mode`, `{ section }`, and `ctx.planMode` contract. The review, boundary, reconstructability, and sandbox-orthogonality decisions below remain in force; generic API examples are retained as the historical design this simplification removed.
+
+> **Superseded ACP mapping:** [ACP as an automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md) removes the picker, config-option, and elicitation mappings described below. Plan mode remains available to human-facing interfaces.
+
+## Problem
+
+Before this change, the harness had no durable way to put one agent into a distinct working stance. Plan mode needs the agent to explore and design under planning guidance, produce a reviewable artifact, cross an explicit approval boundary, and restore that state across resume and fork without making the model-visible request diverge from the session log.
+
+The extension seams already supplied the surrounding pieces: [`system-prompt/assemble`](../../../../packages/core/system-prompt/README.md) shapes guidance per step and the shipped request is logged in `request/header*` events ([reconstructability](../../implemented/architecture/2026-07-05-reconstructable-requests.md)); [`ctx.userInteraction`](../../../../packages/ui/user-interaction/README.md) carries the approval question and corrective feedback ([ask-user precedent](../../implemented/feature/2026-06-25-ask-user-question.md)); `SessionEventMap` carries durable per-agent facts ([the `todo/write` precedent](../../implemented/feature/2026-06-29-todo-write-tool.md)). The missing piece was the named session state that joins those seams while leaving execution enforcement on the independent sandbox and approval axes.
+
+## Decision
+
+The deliverable is **plan mode**. It ships as the first **session mode** — a named, logged, per-agent COLLABORATION state: a mode definition is deployment-configured guidance the model sees, while the mode IN FORCE for an agent is session state folded from its log. Modes are one axis and the enforcement knobs — the sandbox mode, the approval policy — are others: they never read or write each other, matching how Codex keeps its Plan/Default collaboration presets separate from its sandbox and approval settings. One new product package, `@deepseek-ai/dsh-mode` at `packages/mode/mode/`, owns the event vocabulary, a thin `ctx.modes` service, and every listener; the loop does not change. `plan` is the only required definition — the mode-shaped vocabulary exists so a second mode never renames durable event types, not because more modes ship now.
+
+The state is one `SessionEventMap` member: **`mode/set`**, a log-only, non-surface event carrying `{ mode: string }` with whole-value-replace semantics, plus a pure `foldMode(events)` that returns the mode in force — the last `mode/set`, or the default mode when none exists. Because [the log is the fact channel](../../implemented/architecture/2026-06-30-event-domain-semantics.md), resume, fork, and compaction restore the mode with no extra machinery, and UIs read flips off `session/event`. The default mode is the absence of mode guidance — no section, filtering, or gate. Loading `dsh-mode` still contributes one stable `exit_plan_mode` schema in every mode; that fixed cost avoids tool-catalog churn at mode boundaries.
+
+A mode's whole surface is soft: a `mode:policy` prompt section renders the active definition's guidance, while `exit_plan_mode` remains in the registered tool catalog across every mode and rejects at execution unless the folded mode is `plan`. A transition therefore changes only the system-prompt portion of the attributable `request/header` on the next step, keeping [reconstructability](../../implemented/architecture/2026-07-05-reconstructable-requests.md) green without changing native schemas or Code Mode's SDK. A mode deliberately enforces NOTHING: no execution gate, no tool filtering, no reach into the sandbox or approval knobs — a user who wants a hard read-only floor while planning switches the sandbox-mode option beside the mode picker, in either order, and neither axis disturbs the other. There is likewise NO per-mode tool allow/deny list — which tools a mode admits is an effects question, parked until tool definitions declare their effects ([Deferred](#deferred)); a mode's restraint is its section's guidance plus the exit review.
+
+The model leaves plan mode through the **`exit_plan_mode`** tool: its single argument is the plan text, which makes the plan reconstructable from the log, and the tool conducts the review itself through the user-interaction seam — a question whose supporting detail carries the exact plan, with options and a free-text channel, not a bare permission — so an approval flips the logged mode back to the default, and a rejection becomes the corrective error carrying the user's feedback verbatim, which keeps the model planning with direction. A user flips the mode from any surface through `ctx.modes.set()`; the flip is applied at the next turn boundary (session events are turn-enclosed) and narrated to the model once, only when the model-visible state actually changed.
+
+## High-level API
+
+### A plan-mode session end to end
+
+The user switches the session to plan mode through the ACP mode picker or `/plan [message]` in a terminal front door, and from the next step every request ships the configured plan guidance section. When the optional message is present, that same command submits it into the affected step. The `exit_plan_mode` schema was already present in default and remains byte-identical.
+
+The model explores and designs; the section's guidance is what defers changes into the plan. The sandbox and approval knobs keep whatever the user set them to — a deployment (or user) that wants kernel-enforced read-only during planning pairs plan mode with the independent sandbox-mode option.
+
+When ready, the model calls `exit_plan_mode` with the plan markdown as its argument; the review question carries that exact markdown as supporting detail — approve, or keep planning, with free-text feedback welcome. A native call also renders the plan card; a Code Mode nested dispatch has no native card, so the review detail is the common presentation surface.
+
+On approve, the tool flips the logged mode back to the default: the next step drops the plan section while retaining the same tool catalog (the changed header is in the log), and execution tracking from there is already `todo_write`'s job. On keep-planning, the model receives a corrective error carrying the user's feedback text, revises, and re-presents.
+
+### Deployment configuration
+
+Mode definitions are validated plugin Config — per repo convention, changeable from `cordis.yml` with no code edit. The deployment must provide the complete `plan` section; the package embeds no model instructions. Additional modes use the same config map:
+
+```yaml
+- id: mode
+ name: '@deepseek-ai/dsh-mode'
+ config:
+ modes:
+ plan:
+ section: |
+ You are in plan mode: explore and design, then present the
+ plan for approval through exit_plan_mode.
+```
+
+A definition is exactly `{ section }` — there is deliberately no per-mode tool list and no enforcement field ([FAQ](#faq)). Definition names use the lowercase slash-command subset `/^[a-z][a-z0-9_-]*$/u`; `default` is reserved (the absence of policy) and rejected as a key. An invalid name or unknown definition key — a `tools` list or an `access` cap included — fails validation at load; an unknown mode name fails loudly at `set()` time.
+
+### In the terminal
+
+Terminal front doors get one entry command per configured definition through the plugin-owned command registry (`@deepseek-ai/dsh-commands`): `dsh-mode` registers `/plan [message]` for the required definition and, for example, `/review [message]` when `review` is configured. Each command records its named switch; a non-empty optional message is trimmed and passed to `agent.steer()`, which places it in a running agent's next step or delegates to `send()` for a new idle turn. The command name and result stay out of model history, while that explicit message is logged as an ordinary user message under the selected mode. The synthetic `default` entry contributes no command. The exit review prompts right in the terminal with no new machinery: it is an ordinary user-interaction question, so it rides the composed user-interaction provider's prompt queue that `ask_user_question` already uses.
+
+### Over ACP
+
+The mode PICKER is this package's surface: `session/new`/`session/load` advertise `availableModes`/`currentModeId` from `ctx.modes` (consumed opportunistically via `ctx.get`, the `tool-bash` pattern), `session/set_mode` calls `set()` and notifies `current_mode_update` optimistically (the pending mode IS the user's selection; the logged `mode/set` follows at the boundary), and a `session/event` listener re-notifies on each logged flip that differs from the last sent. The exit tool reuses the user-interaction ACP provider's elicitation flow; its ACP mapping carries the review `detail` because Code Mode nested dispatches have no native plan card, while native calls may additionally stream the plan card. Individual environment knobs — sandbox mode, approval policy, the model — are NOT modes and belong to `session/set_config_option` ([FAQ](#faq)).
+
+### For agent creators
+
+`ctx.modes` is the whole programmatic surface: `list()` returns the configured definitions plus the synthetic `default` entry (for pickers), `get(agent)` returns the folded mode plus any pending intent, and `set(agent, mode)` validates the name against `list()`'s vocabulary and records the boundary-applied intent — `default` is always a valid target, so exiting a mode is the same call as entering one. There is no creation-time mode option — a caller selects through `set()` before the first turn, which flushes identically. There is no live `agent/*` mirror to subscribe: UIs read `mode/set` off `session/event`, per [event-domain semantics](../../implemented/architecture/2026-06-30-event-domain-semantics.md).
+
+## Detailed design
+
+### Vocabulary
+
+```text
+'mode/set': { mode: string } // SessionEventMap merge in dsh-mode: log-only, non-surface,
+ // whole-value replace — the last one in the log wins
+DEFAULT_MODE = 'default' // the fold of a log with no mode/set; reserved, not definable
+```
+
+The payload carries no reason/provenance field: a tool-driven flip sits next to its `tool/call` in the log and a user flip sits at its turn boundary, so the cause is log-adjacent — the same "narrative fields are derivable" call the [reconstructability Agent Note](../architecture/2026-07-05-reconstructable-requests.md) made for request-header facts (the in-flight `env/state` event carries a `source` precisely because its drift variant has NO log-adjacent cause — a contrast, not a conflict). Mode names are config-declared vocabulary, not opaque cross-boundary ids, so they stay bare strings (no `Branded`).
+
+### Config and the resolve step
+
+```text
+interface ModeDefinition { section: string } // prompt text — a mode's whole vocabulary
+interface ModeConfig { modes: Record } // plan is required and owns its complete prompt
+resolveConfig(config): ResolvedModes // explicit resolve (the dsh-bash template), fail-loud:
+ // missing plan, 'default', blank sections, and unknown keys rejected
+```
+
+The one-field shape is deliberate minimalism, not the final vocabulary: a per-tool policy dimension returns as effects metadata on tool definitions ([Deferred](#deferred)), read here rather than re-declared per mode — the config shape must not need a migration when it arrives.
+
+### The fold, the service, and the flush
+
+`foldMode(events)` is pure (exported for reconstructors and tests) and folds the append-only session log directly; `mode/set` is not a surface node, so compaction cannot shadow it. `set(agent, mode)` validates the name against `list()`'s vocabulary — the configured definitions plus the reserved `default`, which is rejected as a config KEY but always accepted as a `set()` TARGET — drops a no-op (target equals pending, else current), and otherwise records `{ mode, narrate }` in a `WeakMap` pending-intent slot. It cannot append immediately because [every session event is turn-enclosed](../../implemented/architecture/2026-06-15-turn-enclosure-invariant.md) and an idle agent has no open turn.
+
+Contained listeners on the loop's interception seams ([defensive patterns](../../../../docs/defensive-patterns.md): a policy plugin must not block a prompt or a turn) flush the pending intent as a `mode/set` append — `agent/prompt-submit` fires inside the just-opened turn before its first assembly, and `agent/turn-continuation` fires after an ordinary step closes before its successor. Automatic request recovery bypasses continuation, so a prepended `agent/request-error` wrapper delegates through the composed policy and asynchronous backoff, then flushes only a `retry` decision before the waterfall returns to the loop; an effect-scoped lifetime guard suppresses a captured wrapper that resumes after plugin disposal. All three paths sit outside tool execution and log publication (post-commit `session/event` observers are observe-only), so every step runs under the mode its assembly folded. When the flushed mode differs from the fold at the last `request/header`, the flush appends one coalesced `context/message` notice in the same frame ("The user switched this session to plan mode."); the user-visible narration cases are enumerated in the [FAQ](#faq).
+
+### The soft layer: a computed section and a stable exit schema
+
+The registered prompt section reads the calling agent's mode from `AssembleContext.agent` and resolves to the active definition's guidance or `''`. The loop renders per step and logs a complete `request/header` whenever the rendered header changes, so entering or leaving a mode is attributable. The section is static per mode and the plan itself stays in the conversation as messages and tool arguments; re-injecting separate plan state on every request ([Prior art](#prior-art)'s compaction-survival hack) is unnecessary prompt churn.
+
+The guidance contribution is `{ name: 'mode:policy', order: 50, text: context => … }`: after persona (0), before tool guidance (100–199), and empty for default or agent-less assembly. `exit_plan_mode` is registered once through `ctx.tools` and never filtered, so native schemas and Code Mode's generated SDK remain byte-identical across mode switches; a deployment without `dsh-mode` lacks that one binding. There is NO `tools/pre-execute` listener: a mode gates nothing, while the exit tool's own folded-mode check rejects out-of-plan calls. The exit review is a question with options and feedback, not a permission, so it lives inside the tool's execution over the user-interaction seam.
+
+### `exit_plan_mode`
+
+`defineTool` has one required `plan: string` argument. Native execution records it in the ordinary `tool/call`; Code Mode records the outer `run_code` source before execution and appends the normalized nested arguments in `tool/code-dispatch` after the dispatch settles. `execute` rejects an agent-less call (the [`todo_write` precedent](../../implemented/feature/2026-06-29-todo-write-tool.md)), rejects any folded mode other than `plan`, rejects an empty or heading-less plan before asking the reviewer, then conducts one single-select `ctx.userInteraction.ask()` review whose `detail` is the exact plan — approve or keep planning — with free-text feedback open. Only exactly one `Approve` selection consents; every other shape fails closed. Approval records a SILENT boundary-applied intent to switch to `default` and returns a short confirmation. The deployment guidance tells the model to make this the only and final tool call in its response; if a model violates that rule, the runtime still holds plan guidance for the rest of the batch, and the next step logs a changed header with the guidance removed and tool schemas unchanged. Every non-approval outcome returns a corrective `isError` and leaves the mode in `plan`.
+
+Its [render intent](../../implemented/architecture/2026-07-02-tool-render-intent-union.md), decided up front: `presentCall` is a `generic` card titled by the plan's first heading with the plan markdown as content, plus a `generic` result card. Native front doors show that card before the question; Code Mode nested dispatches do not produce native call-card events, so the user-interaction `detail` independently carries the same plan on every provider. The seam is consumed opportunistically (`ctx.get('userInteraction')`), so `dsh-mode` composes without it and degrades to the manual exit pinned in the [FAQ](#faq).
+
+### Dependencies and surfaces
+
+`dsh-mode` is one product package, not a capability-seam trio ([Alternatives considered](#alternatives-considered)): it peers on `cordis`, `dsh-session`, `dsh-agent`, `dsh-tools`, and `dsh-system-prompt`, injects `['tools', 'systemPrompt']`, and reads `ctx.userInteraction` opportunistically at execute time (a type-only peer edge on `dsh-user-interaction`); its only UI-facing edges are optional type-only peers (`dsh-commands` for the per-definition entry commands). Beyond the `ctx.modes` call surface everything participates through listeners, so dropping the package gracefully removes modes rather than breaking a consumer. Terminal front doors need no mode-specific code: `dsh-mode` itself registers each definition's command on the command registry when one is composed (an optional type-only peer edge on `dsh-commands`), and the exit review rides the composed user-interaction provider's prompt queue. The ACP wire mapping is pinned in [High-level API](#over-acp); package-wise the bridge takes a type-only peer edge on `dsh-mode` and reads the service opportunistically, so a bridge without the plugin behaves exactly as today.
+
+### The recorded scenario and the harness op
+
+`input.json` gains one step op, `{ "op": "setMode", "modeId": "plan" }`, driven through the real `session/set_mode` RPC, and a scripted `elicitationAnswers` queue. The `plan-mode` scenario enters plan before turn 1, runs a real `cat` under the independently configured sandbox, presents a plan through `exit_plan_mode`, receives scripted approval, then edits on the next step. The first `request/header` contains the full stable toolset plus the configured mode section; the post-approval changed header retains byte-identical tool schemas and removes only that section. `plan-mode-reject` pins corrective free-text feedback and the unchanged plan state. Both recordings replay host commands under Seatbelt or bwrap; backend-specific sandbox denial stays at the bash-tool unit tier.
+
+### The mechanical tail
+
+No new cordis event is declared (`mode/set` rides `session/event`; the listeners attach to existing waterfalls), so the events catalog is untouched. Regenerated in the same change: the persistence log catalog (`mode/set`), the services catalog (`ctx.modes`, JSDoc-complete), the config catalog (`ModeConfig`), the tool catalog (`exit_plan_mode`), the producer/consumer map and doc graphs, and the module graph. Repo plumbing: a root tsconfig `paths` entry, the new group's README plus a [packages map](../../../../packages/README.md) row (a new top-level group is the deliberate act that table names), an `architecture.md` capability-services row for `ctx.modes` (budget-checked), and the cookbook row upgrade.
+
+## Deferred
+
+Each behind its own decision: subagent mode inheritance via a forwarded creation-time mode option (removed as unconsumed; it returns with its first consumer), preset modes beyond `plan` (read-only, accept-edits), the idle-record primitive if pending-intent loss proves real, and — the big one — **effects self-declaration on tool definitions**: a per-tool read-only/mutating classification (the MCP `ToolAnnotations` vocabulary — `readOnlyHint`/`destructiveHint` — is the natural template, with its untrusted-hint caveat implying trust tiers). That item is what a general per-mode tool policy waits on: this Agent Note first shipped an interim per-mode name allowlist and removed it before release — a hand-maintained list mislabels the effects question, must track every tool a deployment composes, and rots silently as tools arrive — so mode-scoped tool availability (and per-tool `ask` policies) returns as a CONSUMER of declared effects, which is its restart trigger.
+
+The ACP automation composition does not mount plan mode or the question tool. Human-facing compositions own plan selection and review; focused plan-mode tests and interactive-interface snapshots pin its logged state, guidance, review, and stable tool schemas.
+
+## FAQ
+
+Behavioral clarifications of the chosen design; rejected designs live in [Alternatives considered](#alternatives-considered), accepted costs in [Consequences](#consequences).
+
+**When does a user's mode flip take effect?** At the next pre-assembly boundary: `agent/prompt-submit` covers the first step, `agent/turn-continuation` covers a normal successor, and the post-composed `agent/request-error` retry decision covers automatic recovery. A mode selected while a request or retry backoff is in flight therefore shapes the following model request. This is the "applies to subsequent requests" semantics every product in [Prior art](#prior-art) ships.
+
+**When is a mode change narrated to the model?** Only when the model-visible state actually changed: the flush compares the flushed mode against the fold at the last `request/header` and narrates once, coalesced. A net-zero flip sequence (plan then back, all before the boundary) narrates nothing; a tool-driven exit narrates through its own tool result instead; a mode set before the first turn narrates nothing — the section is the state statement. The principle is the in-flight env-state proposal's boundary narration: a silently flipped prompt surface leaves the transcript arguing from a state the header no longer has.
+
+**What happens on resume when the config no longer defines the folded mode?** A folded mode name the current config no longer defines behaves as the default mode without a notice, so the session neither gains a substitute restriction nor becomes unusable. `set()`'s loud validation covers only the write path; a resumed log answers to the config it finds.
+
+**What if a deployment composes no user-interaction provider?** Plan mode stays safe but manual: `ctx.userInteraction.ask()` throws `NO_PROVIDER` (and an absent seam never resolves at all), the tool returns the corrective `isError`, and the exit degrades to the user toggling modes — never to an unreviewed exit. The mode section tells the model to present its plan through `exit_plan_mode` — and to ask the user in prose if that fails — so it keeps presenting instead of stalling.
+
+**Why is there no per-mode tool allowlist?** Because "which tools are safe in a planning mode" is a property of each TOOL (its effects), not of the mode — a per-mode name list re-declares that fact in the wrong home, must enumerate every tool the deployment composes (MCP servers included), and rots silently as tools arrive. Until tool definitions declare their effects ([Deferred](#deferred), where the removed interim allowlist is archived with its restart trigger), a mode restrains by its section and the exit review; the exposure is an accepted cost ([Consequences](#consequences)).
+
+**Do subagents inherit the parent's mode?** A fork child inherits for free — the parent's `mode/set` is inside the seeded prefix. A spawn child starts in the default mode; a creation-time mode option and automatic forwarding by subagent providers are deferred together ([Deferred](#deferred)).
+
+**How does plan mode relate to the sandbox's read-only mode?** They are separate axes that never touch: the mode is the collaboration stance (a `mode/set` fold), the sandbox mode is an enforcement knob (a `bash/sandbox-mode` fold, [the sandbox Agent Note](2026-07-06-sandbox.md)) — plan mode neither reads nor caps it, exactly as Codex keeps its Plan/Default presets separate from its sandbox and approval settings. A user who wants kernel-enforced read-only while planning sets both: flip the mode picker AND the sandbox-mode option, in either order; each switch changes only its own fold, so there is no interference and no restore step to crash out of. The log attributes each axis to its own event — the stance to `mode/set`, the confinement to `bash/sandbox-mode`.
+
+**Why aren't sandbox mode, approval policy, or the model themselves modes?** They are individual environment knobs independent of collaboration state. The retired ACP mapping is recorded by the [automation-only protocol decision](../simplification/2026-07-23-acp-automation-only-protocol.md). A mode definition may later bundle env facts (applied through `ctx.envState` where mounted) so a Codex-style preset stays a single mode; fusing approval policy into the mode CONCEPT itself is rejected in [Alternatives considered](#alternatives-considered).
+
+## Prior art
+
+A survey of shipped plan modes (Claude Code, Cursor, Copilot, OpenCode, Gemini CLI, Cline, Windsurf, Codex) shows the same five parts everywhere — the low-authority tool policy, plan artifact, approval moment, execution-state switch, and durable state that [Problem](#problem) builds on.
+
+The mode surface is a LIST everywhere it is advertised, never a boolean: Claude Code's picker offers `plan` beside `acceptEdits` (plus an auto-mode entry into plan), and Codex exposes `Plan` beside `Default` as collaboration-mode presets while keeping approval and sandbox settings separate. The ACP transport does not advertise this human-facing control.
+
+The deployment-owned example prompt borrows the instrumental behavior, not product-specific mechanics. From Codex: remain in plan mode despite imperative implementation language, explore before asking, distinguish repository facts from user-owned choices, and make the plan decision-complete across APIs, data flow, failures, tests, and assumptions. From Claude Code: prohibit mutations and commits, prefer existing patterns, use questions only for requirements or approach choices, and finish through the exit tool rather than a prose approval request. It deliberately omits Codex protocol tags and Claude's plan-file or phased-subagent machinery because those belong to their runtimes, not this plugin contract.
+
+The ecosystems that leave modes to convention show the failure shapes to avoid. Pi-style mode extensions fight over a last-wins global active-tool list, enforce "read-only" by prompt text alone (a hallucinated call to a still-registered tool executes), and re-inject plan state into every request to survive compaction. The contested global list and the re-injection hack close structurally here — per-agent folded state, and a log-only non-surface event compaction cannot shadow. The prompt-only shape, by contrast, is deliberately KEPT — it is what Codex ships for Plan, and it is why the mode axis composes freely with the enforcement axes: a deployment that wants a hard floor pairs the mode with the independent sandbox knob instead of the mode carrying its own enforcement ([FAQ](#faq)).
+
+## Alternatives considered
+
+**Permission modes as the concept (the Claude Code shape).** One `permissionMode` fusing approval policy and tool policy. Here those are two axes with two owners: the approval seam owns "who answers this question", modes own "what surface does the model get". ACP models them as related but distinct (a mode may select an approval policy later — a mode definition gains a field, not a merger).
+
+**A capability-seam trio.** Interface/implementation/consumer fits a swappable backend; a mode's variable parts are config values, not implementations. Splitting would manufacture an empty implementation package — the same "don't split preemptively" call the approval seam and [`todo/`](../../implemented/feature/2026-06-29-todo-write-tool.md) made.
+
+**Loop-owned mode state.** Rejected on the standing rule (plugins, not loop changes): every hook the feature needs — assemble, pre-execute, turn boundaries, session events — is already a documented seam, so a loop edit would buy nothing but coupling.
+
+**A per-mode tool allowlist with a deny-by-default gate (the first shipped shape).** Removed before release. A hand-maintained name list re-declares a per-TOOL fact (its effects) per MODE: it must enumerate every tool the deployment composes — MCP servers and future registrations included — and it rots silently as tools arrive (a new read-only tool is blocked until someone edits every mode; the author burden lands on whoever knows the mode, not whoever knows the tool). It also over-promises: the list looks like a security boundary while the real boundary for anything non-shell does not exist. The general dimension is parked on effects self-declaration ([Deferred](#deferred)); the consequence — plan mode is guidance-only, the very Pi hole the gate once closed — is accepted deliberately, priced in [Consequences](#consequences).
+
+**An `access` sandbox cap on the mode (the second shipped shape).** Also removed before release. `ModeDefinition.access` clamped the bash seam's per-call sandbox resolution to a mode-declared ceiling (a `bash/resolve-mode` waterfall + ladder-min listener, with guards withholding bash under an unconfinable executor and denying escalation mid-mode). The state stayed orthogonal — the clamp never wrote the sandbox knob — but the AXES did not: entering plan changed what the sandbox enforced, fusing the collaboration stance with an enforcement level and contradicting the Codex-shaped separation the review converged on (Plan/Default presets never touch sandbox or approval settings). One user-visible symptom of the fusion: flipping the sandbox option to `workspace-write` while planning silently did nothing. The cap, the waterfall, and the mode→bash dependency edge were removed together; a deployment gets kernel-enforced read-only planning by pairing the mode with the independent sandbox-mode option, and a mode-triggered PRESET (a mode definition bundling suggested knob values, applied as ordinary knob switches) can return later without re-fusing the axes.
+
+**Runtime-only mode (UI- or bridge-local, unlogged).** Resume and fork would silently drop the mode, and the header deltas a mode causes would have no attributable cause in the log. Logged state is what makes the mode auditable and restorable for free.
+
+**Mode flips as `context/message` via `agent.inject()`.** Reuses an existing turn-enclosure path, but puts policy state into the model transcript — the model does not need to be told twice (the section already tells it), and a log-only fact should not occupy surface.
+
+**A plan-file store (`.plans/` directory).** A second durable home for what the log already carries replayably; a deployment wanting files can add a tool that writes them. One home per fact.
+
+**A boolean `planMode` instead of named modes.** Too narrow for the surface the repo already tracks: ACP advertises a mode LIST and the shipped pickers fill it with more than plan ([Prior art](#prior-art)); generalizing later would rename durable event vocabulary. The string-shaped mechanism costs nothing extra now; only `plan` ships as a definition.
+
+**A tool-policy-stack service (the Pi-critique remedy).** A dedicated composition service for tool policies is premature: this implementation performs no mode-scoped tool filtering, and future effect policies can compose through the existing guarded execution seams. Formalize only when declared tool effects create a concrete composition requirement.
+
+**Exit approval through the approval seam (a `{ kind: 'ask' }` gate decision).** The original sketch, natural while the approval seam was the only asking machinery in flight — but it seats a review in a permission chair: the seam's outcome vocabulary is deliberately closed and one-shot (`allowed-once`/`rejected`), so a rejection carries no feedback and an approval can never grow options (approve-and-accept-edits). The exit moment is a question, not a permission — the user-interaction seam gives it options plus the free-text channel, and the rejection feedback reaches the model verbatim. The approval seam remains the right seat for genuine permission gates (the sandbox escalation), and the registry's `ask` vocabulary stays available to deployments that want one there.
+
+**Exit by prose or steering instead of a tool.** No artifact and no approval moment — the tool's argument IS the reviewable plan, and its review question is what gives the human a structured yes/no attached to the exact transition.
+
+## Consequences
+
+What holds now, pinned by the unit, protocol, snapshot, and real-API tiers:
+
+- The mode in force is a pure function of the session log: resume and fork restore it with no extra machinery, and a `mode/set` is followed by a matching complete `request/header` on the next changed step.
+- A user-driven flip narrates exactly once at the next boundary and a net-zero flip sequence narrates nothing; a tool-driven exit narrates only through its tool result.
+- In default mode the plugin contributes no mode section but does contribute the stable `exit_plan_mode` schema; a deployment without `dsh-mode` lacks that binding.
+- Native tool schemas and Code Mode's SDK stay byte-identical across default, plan, and custom-mode transitions; only the configured guidance section changes.
+- Plan mode changes nothing on the enforcement axes: the toolset, the sandbox mode, escalation, and the approval policy behave identically in plan and default — pairing the mode with the independent sandbox/approval knobs is how a deployment hardens planning.
+- Mode definitions are changeable from `cordis.yml` with no code edit; the complete plan instructions are required there, while missing plan config, malformed definitions, and unknown keys fail at load and unknown mode names fail at `set()`.
+- `exit_plan_mode` is always advertised, rejects outside plan, drops only plan guidance after approval, and carries keep-planning feedback in a corrective `isError`; each human-facing surface's user-interaction provider carries the review.
+- The docs tail shipped with the landing: READMEs, regenerated catalogs (persistence log, config, cordis services, tools), the packages map and architecture rows, and the cookbook row.
+
+The accepted costs: a pending user flip set while idle is lost if the process dies before the next turn (the UI re-applies; the idle-record primitive is the escape hatch if this bites in practice). A mode transition changes the system prompt at order 50, so the cache path from that point onward changes, but the tool schemas and Code Mode SDK no longer churn. **A mode restrains by guidance alone**: a model that ignores the section CAN mutate during plan — the review moment, the session log, and independent sandbox, approval, and filesystem policies are the containment surface. Hardening planning means setting those knobs, not widening the mode; the removed enforcement shapes and their effects-declaration restart trigger remain in [Alternatives considered](#alternatives-considered) and [Deferred](#deferred). Human-facing interfaces own the plan picker and review interaction; the ACP automation transport carries neither.
diff --git a/.agents/notes/archived/feature/2026-07-07-plan-mode.zh.md b/.agents/notes/archived/feature/2026-07-07-plan-mode.zh.md
new file mode 100644
index 0000000000..20662e208a
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-07-plan-mode.zh.md
@@ -0,0 +1,197 @@
+# Agent Note: plan mode——记录到日志的逐 agent 会话模式
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-07-plan-mode.md) | 中文
+
+> **已取代的词汇(2026-07-22):**[将具名会话模式收敛为 plan mode](../simplification/2026-07-22-plan-specific-collaboration-state.md) 已将本笔记中通用的 `dsh-mode`、`mode/set`、定义 map 与 `ctx.modes` 设计,替换为当前 plan 专用的 `dsh-plan-mode`、`plan/mode`、`{ section }` 和 `ctx.planMode` 契约。下文的评审、边界、可重建性与沙箱正交性决策仍然有效;通用 API 示例则作为此次简化所移除的历史设计保留下来。
+
+> **已取代的 ACP(Agent Client Protocol)映射:**[ACP 作为仅面向自动化的协议](../simplification/2026-07-23-acp-automation-only-protocol.md)移除了下文所述的选择器、配置选项和 elicitation 映射。面向人类的接口仍可使用 plan mode。
+
+## 问题
+
+此次变更之前,harness 无法持久地让某个 agent(智能体)采用独特的工作姿态。Plan mode 要求 agent 在规划指引下探索和设计,产出可供评审的产物,跨过明确的审批边界,并在恢复与 fork 后还原该状态,同时不能让模型可见请求偏离会话日志。
+
+既有扩展 seam 已经提供了周边机制:[`system-prompt/assemble`](../../../../packages/core/system-prompt/README.md) 为每个步骤塑造指引,已发送的请求则记录在 `request/header*` 事件中(参见[可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md));[`ctx.userInteraction`](../../../../packages/ui/user-interaction/README.md) 承载审批问题与纠正反馈(参见 [ask-user 先例](../../implemented/feature/2026-06-25-ask-user-question.md));`SessionEventMap` 承载逐 agent 的持久事实(参见 [`todo/write` 先例](../../implemented/feature/2026-06-29-todo-write-tool.md))。缺少的是将这些 seam 连在一起的具名会话状态,同时仍让独立的沙箱轴与审批轴负责执行约束。
+
+## 决策
+
+交付项是 **plan mode**。它作为首个**会话模式**发布,即一个具名、记录到日志且逐 agent 生效的协作状态:模式定义是由部署配置、供模型查看的指引;对某个 agent 生效的模式则是从其日志折叠出的会话状态。模式构成一条轴,强制约束旋钮——沙箱模式与审批策略——构成其他轴;它们从不互相读写,这与 Codex 将 Plan/Default 协作预设同沙箱及审批设置分开的做法一致。新的产品包(package)`@deepseek-ai/dsh-mode` 位于 `packages/mode/mode/`,拥有事件词汇、精简的 `ctx.modes` 服务和全部监听器;循环无需改动。`plan` 是唯一的必需定义;采用模式形状的词汇,是为了以后增加第二种模式时无需重命名持久事件类型,而不是因为当前还会发布其他模式。
+
+该状态是 `SessionEventMap` 的一个成员:**`mode/set`** 是只记录日志、不进入 surface 的事件,携带具有整值替换语义的 `{ mode: string }`;另有纯函数 `foldMode(events)` 返回生效模式,即最后一个 `mode/set`,没有该事件时则返回默认模式。由于[日志是事实通道](../../implemented/architecture/2026-06-30-event-domain-semantics.md),恢复、fork 和压缩无需额外机制即可还原模式,UI 则从 `session/event` 读取模式切换。默认模式表示不存在模式指引,即没有段落、过滤或门禁。加载 `dsh-mode` 后,每种模式仍会贡献同一个稳定的 `exit_plan_mode` schema;这项固定成本避免了模式边界处的工具目录抖动。
+
+模式的所有外显行为都是软约束:`mode:policy` 提示词段落渲染当前定义的指引,而 `exit_plan_mode` 在每种模式下都留在已注册的工具目录中,仅当折叠模式不是 `plan` 时才在执行阶段拒绝。因此,转换只会在下一步骤改变可归因 `request/header` 中的系统提示词部分,从而在不改变 Native schema 或 Code Mode SDK 的情况下继续满足[可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md)。模式有意不强制执行任何约束:没有执行门禁,不过滤工具,也不触及沙箱或审批旋钮。若用户希望规划期间存在硬性的只读下限,可以在模式选择器旁切换沙箱模式选项;二者先后顺序任意,任何一条轴都不会扰动另一条轴。同样也不存在逐模式的工具允许/拒绝清单;模式允许哪些工具属于副作用问题,在工具定义能够声明自身副作用前暂缓处理(见[延期工作](#deferred))。模式只依靠其段落指引与退出评审来约束行为。
+
+模型通过 **`exit_plan_mode`** 工具离开 plan mode。其唯一参数是 plan 文本,因此可以从日志重建 plan;该工具自行通过用户交互 seam 完成评审:问题的辅助详情携带确切 plan,并提供选项与自由文本通道,而不是只有一项裸权限。审批通过后,记录到日志的模式切回默认模式;拒绝则成为携带用户逐字反馈的纠正错误,让模型能沿明确方向继续规划。用户可从任意接口通过 `ctx.modes.set()` 切换模式;切换会在下一个轮次边界应用(会话事件都封闭在轮次内),且只有模型可见状态确实变化时才向模型讲述一次。
+
+## 高层 API
+
+### 一次端到端的 plan-mode 会话
+
+用户通过 ACP 模式选择器或终端入口中的 `/plan [message]` 将会话切换到 plan mode;从下一步骤开始,每个请求都携带已配置的 plan 指引段落。如果给出可选消息,同一命令还会把它提交到受影响的步骤中。`exit_plan_mode` schema 在默认模式下已经存在,并会保持逐字节不变。
+
+模型进行探索与设计;段落中的指引会让它把变更推迟到 plan 中。沙箱与审批旋钮保持用户设置的值不变;希望规划期间由内核强制只读的部署方(或用户),可以把 plan mode 与独立的沙箱模式选项配合使用。
+
+准备就绪后,模型调用 `exit_plan_mode`,并把 plan markdown 作为参数;评审问题将这段确切 markdown 作为辅助详情,用户可以批准,也可以要求继续规划并自由填写反馈。Native 调用还会渲染 plan 卡片;Code Mode 嵌套分发没有 Native 卡片,因此评审详情是共用的呈现接口。
+
+批准后,工具把记录到日志的模式切回默认模式:下一步骤会移除 plan 段落,但保留同一个工具目录(变化后的 header 已记录到日志),此后的执行跟踪本就由 `todo_write` 负责。要求继续规划时,模型会收到携带用户反馈文本的纠正错误,随后修改并再次呈现。
+
+### 部署配置
+
+模式定义是经过校验的插件 Config;依照仓库约定,它可以通过 `cordis.yml` 修改,无需编辑代码。部署必须提供完整的 `plan` 段落;该包不内置任何模型指令。其他模式使用同一份配置 map:
+
+```yaml
+- id: mode
+ name: '@deepseek-ai/dsh-mode'
+ config:
+ modes:
+ plan:
+ section: |
+ You are in plan mode: explore and design, then present the
+ plan for approval through exit_plan_mode.
+```
+
+定义的精确形状是 `{ section }`;其中有意不提供逐模式工具清单或强制约束字段(见[常见问题](#faq))。定义名称使用小写斜杠命令子集 `/^[a-z][a-z0-9_-]*$/u`;`default` 是保留项(表示没有策略),不能用作键。名称无效或存在未知定义键——包括 `tools` 清单或 `access` 上限——会在加载时校验失败;未知模式名称则会在调用 `set()` 时大声失败。
+
+### 在终端中
+
+终端入口通过插件自有的命令注册表(`@deepseek-ai/dsh-commands`),为每个已配置定义获得一条进入命令:`dsh-mode` 为必需定义注册 `/plan [message]`,例如还会注册 `/review [message]`(当配置 `review` 时)。每条命令都记录其具名切换;非空的可选消息会去除首尾空白并传给 `agent.steer()`,后者会把消息放入运行中 agent 的下一步骤,或委托给 `send()` 以开启新的空闲轮次。命令名称与结果不会进入模型历史;这条显式消息则会作为所选模式下的普通用户消息记录到日志。合成的 `default` 条目不贡献命令。退出评审无需新机制即可直接在终端中提示:它是普通的用户交互问题,因此会进入组合后的用户交互提供方提示队列,与 `ask_user_question` 使用的队列相同。
+
+### 通过 ACP
+
+模式选择器是该包的对外接口:`session/new`/`session/load` 会通告 `availableModes`/`currentModeId`,其值来自 `ctx.modes`(通过 `ctx.get` 机会式消费,沿用 `tool-bash` 模式);`session/set_mode` 调用 `set()` 并乐观通知 `current_mode_update`(待生效模式就是用户的选择,记录到日志的 `mode/set` 会在边界处跟进);`session/event` 监听器则会在每次已记录切换不同于最近一次已发送值时再次通知。退出工具复用用户交互 ACP 提供方的 elicitation 流程;其 ACP 映射会携带评审 `detail`,因为 Code Mode 嵌套分发没有 Native plan 卡片,而 Native 调用还可以额外流式传输该卡片。沙箱模式、审批策略和模型等单项环境旋钮不是模式,应归入 `session/set_config_option`(见[常见问题](#faq))。
+
+### 面向 agent 创建方
+
+`ctx.modes` 是完整的程序化接口:`list()` 返回已配置定义和供选择器使用的合成 `default` 条目;`get(agent)` 返回折叠模式与可能存在的待生效意图;`set(agent, mode)` 则根据 `list()` 的词汇校验名称,并记录将在边界应用的意图。`default` 始终是有效目标,因此退出模式与进入模式使用同一次调用。创建时没有模式选项;调用方在首个轮次前通过 `set()` 选择模式,随后以相同方式刷写。系统也不提供可订阅的实时 `agent/*` 镜像:UI 依照[事件领域语义](../../implemented/architecture/2026-06-30-event-domain-semantics.md)读取 `mode/set`,该事件来自 `session/event`。
+
+## 详细设计
+
+### 词汇
+
+```text
+'mode/set': { mode: string } // SessionEventMap merge in dsh-mode: log-only, non-surface,
+ // whole-value replace — the last one in the log wins
+DEFAULT_MODE = 'default' // the fold of a log with no mode/set; reserved, not definable
+```
+
+载荷不携带原因/溯源字段:工具驱动的切换在日志中紧邻其 `tool/call`,用户切换则位于轮次边界,因此原因就在日志相邻位置。这与[可重建性 Agent Note](../architecture/2026-07-05-reconstructable-requests.md)针对请求头事实所作的「叙述字段可以派生」决策相同(进行中的 `env/state` 事件之所以携带 `source`,正是因为其漂移变体在日志相邻位置没有原因;二者形成对照,并不冲突)。模式名称是配置声明的词汇,不是不透明的跨边界 id,因此仍使用裸字符串(不使用 `Branded`)。
+
+### 配置与解析步骤
+
+```text
+interface ModeDefinition { section: string } // prompt text — a mode's whole vocabulary
+interface ModeConfig { modes: Record } // plan is required and owns its complete prompt
+resolveConfig(config): ResolvedModes // explicit resolve (the dsh-bash template), fail-loud:
+ // missing plan, 'default', blank sections, and unknown keys rejected
+```
+
+单字段形状是有意采用的最简设计,并非最终词汇:逐工具策略维度会以工具定义中的副作用元数据形式回归(见[延期工作](#deferred)),在此处读取,而不是由每种模式重新声明;该维度到来时,配置形状不应需要迁移。
+
+### 折叠、服务与刷写
+
+`foldMode(events)` 是纯函数(导出供重建方与测试使用),直接折叠仅追加的会话日志;`mode/set` 不是 surface 节点,因此压缩无法遮蔽它。`set(agent, mode)` 根据 `list()` 的词汇校验名称,即已配置定义加上保留的 `default`;后者不能用作配置键,却始终可以作为 `set()` 目标。目标与待生效模式相同(没有待生效模式时则与当前模式相同)时,该方法丢弃无操作;其余情况会把 `{ mode, narrate }` 记录到 `WeakMap` 的待生效意图槽中。它不能立即追加,因为[每个会话事件都封闭在轮次内](../../implemented/architecture/2026-06-15-turn-enclosure-invariant.md),而空闲 agent 没有打开的轮次。
+
+循环拦截 seam 上经过故障隔离的监听器(参见[防御模式](../../../../docs/defensive-patterns.md):策略插件不得阻塞提示词或轮次)会把待生效意图刷写为一条 `mode/set` 追加:`agent/prompt-submit` 在刚打开的轮次中、首次组装前触发;`agent/turn-continuation` 则在普通步骤关闭后、后续步骤开始前触发。自动请求恢复会绕过 continuation,因此,前置的 `agent/request-error` 包装器会先委托给组合后的策略和异步退避,只在 waterfall 返回循环前刷写 `retry` 决策;effect 作用域的生命周期守卫会抑制在插件资源释放后才恢复的已捕获包装器。三条路径都位于工具执行与日志发布之外(提交后的 `session/event` 观察器只负责观察),因此每个步骤都在其组装所折叠出的模式下运行。刷写模式与最后一个 `request/header` 处的折叠结果不同时,刷写会在同一帧中追加一条合并后的 `context/message` 通知(「用户已将此会话切换到 plan mode。」);面向用户的叙述情形列在[常见问题](#faq)中。
+
+### 软层:计算得出的段落与稳定的退出 schema
+
+已注册的提示词段落从 `AssembleContext.agent` 读取调用 agent 的模式,并解析为当前定义的指引或 `''`。循环逐步骤渲染,并在渲染后的 header 发生变化时记录完整的 `request/header`,因此进入或离开模式均可归因。该段落在每种模式内保持静态,plan 本身则以消息和工具参数留在对话中;无需为了跨压缩保留状态,而在每个请求中重新注入独立的 plan 状态([既有方案](#prior-art)采用的办法),徒增提示词抖动。
+
+指引贡献为 `{ name: 'mode:policy', order: 50, text: context => … }`:排在人设(0)之后、工具指引(100–199)之前,并在默认模式或没有 agent 的组装中为空。`exit_plan_mode` 只通过 `ctx.tools` 注册一次且从不过滤,因此模式切换期间 Native schema 与 Code Mode 生成的 SDK 保持逐字节相同;未部署 `dsh-mode` 的环境则没有这项绑定。系统不注册 `tools/pre-execute` 监听器:模式不设置任何门禁,退出工具自身的折叠模式检查会拒绝 plan 之外的调用。退出评审是一个带选项和反馈的问题,不是权限,因此位于工具通过用户交互 seam 执行的过程内。
+
+### `exit_plan_mode`
+
+`defineTool` 有一个必填的 `plan: string` 参数。Native 执行会把它记录在普通 `tool/call` 中;Code Mode 在执行前记录外层 `run_code` 源码,并在分发结算后把规范化的嵌套参数追加到 `tool/code-dispatch`。`execute` 会拒绝没有 agent 的调用(沿用 [`todo_write` 先例](../../implemented/feature/2026-06-29-todo-write-tool.md))和折叠模式不是 `plan` 的调用,并在询问评审人前拒绝空 plan 或不含标题的 plan;随后,它通过 `ctx.userInteraction.ask()` 发起一次单选评审,其 `detail` 是确切 plan,并开放自由文本反馈,供用户批准或要求继续规划。只有恰好选择一个 `Approve` 才表示同意,其他任何形状都按失败关闭处理。批准会记录一项将在边界生效且不叙述的意图,用于切换到 `default`,并返回简短确认。部署指引要求模型把这次调用作为回复中唯一且最后一次工具调用;如果模型违反该规则,运行时仍会让该批次剩余部分保留 plan 指引,下一步骤才记录变化后的 header,其中移除指引而工具 schema 保持不变。所有未获批准的结果都会返回纠正性的 `isError`,并让模式留在 `plan`。
+
+其[渲染意图](../../implemented/architecture/2026-07-02-tool-render-intent-union.md)在设计之初就已确定:`presentCall` 是 `generic` 卡片,以 plan 的首个标题命名、以 plan markdown 作为内容,另配一张 `generic` 结果卡片。Native 入口会在问题之前显示该卡片;Code Mode 嵌套分发不会产生 Native 调用卡片事件,因此用户交互 `detail` 会在每个提供方上独立携带同一份 plan。系统机会式消费该 seam(`ctx.get('userInteraction')`),所以 `dsh-mode` 在没有它时仍可组合,并降级为[常见问题](#faq)中确定的手动退出方式。
+
+### 依赖与接口
+
+`dsh-mode` 是一个产品包,而不是由三个包组成的能力 seam(见[考虑过的替代方案](#alternatives-considered)):它对等依赖 `cordis`、`dsh-session`、`dsh-agent`、`dsh-tools` 与 `dsh-system-prompt`,注入 `['tools', 'systemPrompt']`,并在执行时机会式读取 `ctx.userInteraction`(指向 `dsh-user-interaction` 的仅类型对等依赖边);其仅有的 UI 侧边也是可选的仅类型对等依赖(逐定义进入命令使用 `dsh-commands`)。除 `ctx.modes` 调用接口外,所有内容都通过监听器参与,因此移除该包会平稳移除模式,而不会破坏消费方。终端入口无需模式专用代码:组合命令注册表后,`dsh-mode` 会自行注册每个定义的命令(指向 `dsh-commands` 的可选仅类型对等依赖边),退出评审则使用组合后的用户交互提供方提示队列。[高层 API](#over-acp) 已确定 ACP 协议映射;在包关系上,桥接层对 `dsh-mode` 采用仅类型对等依赖边并机会式读取服务,所以不含该插件的桥接层行为与当前完全相同。
+
+### 已记录场景与 harness 操作
+
+`input.json` 新增一种步骤操作 `{ "op": "setMode", "modeId": "plan" }`,通过真实的 `session/set_mode` RPC 驱动,并配有脚本化的 `elicitationAnswers` 队列。`plan-mode` 场景在第 1 个轮次前进入 plan,在独立配置的沙箱下运行真实的 `cat`,通过 `exit_plan_mode` 呈现 plan,接收脚本化审批,然后在下一步骤编辑。首个 `request/header` 包含完整、稳定的工具集和已配置模式段落;批准后变化的 header 会保留逐字节相同的工具 schema,只移除该段落。`plan-mode-reject` 固定纠正性的自由文本反馈和未变化的 plan 状态。两份记录都在 Seatbelt 或 bwrap 下回放宿主命令;后端特有的沙箱拒绝仍留在 bash 工具单元层。
+
+### 机械收尾
+
+系统不声明新的 Cordis 事件(`mode/set` 通过 `session/event` 传递,监听器附着到现有 waterfall),因此事件目录不变。同一变更重新生成以下内容:持久化日志目录(`mode/set`)、服务目录(`ctx.modes`,JSDoc 完整)、配置目录(`ModeConfig`)、工具目录(`exit_plan_mode`)、生产方/消费方 map 与文档图,以及模块图。仓库接线包括:根 tsconfig 的 `paths` 条目、新包组 README 和[包索引](../../../../packages/README.md)中的一行(新增顶层包组正是该表所命名的有意操作)、`architecture.md` 中经过预算检查的 `ctx.modes` 能力服务行,以及实操手册对应行的升级。
+
+## 延期工作
+
+以下各项都需要独立决策:通过转发的创建时模式选项实现 subagent 模式继承(由于没有消费方而移除,将随首个消费方回归);`plan` 之外的预设模式(只读、接受编辑);若待生效意图丢失被证明是真实问题,则引入空闲记录原语;以及最重要的**在工具定义上自行声明副作用**,即逐工具的只读/变更分类(MCP `ToolAnnotations` 词汇——`readOnlyHint`/`destructiveHint`——是自然模板,其中对不可信提示的警告意味着还需区分信任层级)。通用的逐模式工具策略正在等待这一项:本 Agent Note 最初发布过临时的逐模式名称允许清单,并在发布前移除;手工维护的清单错误地表达了副作用问题,必须跟踪部署所组合的每个工具,且会随工具增加而无声腐化。因此,按模式限制的工具可用性(以及逐工具 `ask` 策略)会作为已声明副作用的消费方回归,而首项消费需求就是其重启触发条件。
+
+ACP 自动化组合不挂载 plan mode 或问题工具。面向人类的组合拥有 plan 选择与评审;聚焦的 plan-mode 测试和交互接口快照会固定其已记录状态、指引、评审和稳定工具 schema。
+
+## 常见问题
+
+以下内容澄清选定设计的行为;遭否决的设计见[考虑过的替代方案](#alternatives-considered),已接受的代价见[后果](#consequences)。
+
+**用户切换模式后何时生效?** 在下一个组装前边界生效:`agent/prompt-submit` 覆盖首个步骤,`agent/turn-continuation` 覆盖普通后续步骤,组合策略之后的 `agent/request-error` 重试决策覆盖自动恢复。因此,在请求或重试退避进行期间选择的模式会塑造下一次模型请求。这就是[既有方案](#prior-art)中每项产品都采用的「应用于后续请求」语义。
+
+**何时向模型讲述模式变化?** 仅当模型可见状态确实变化时:刷写会把刚刷写的模式与最后一个 `request/header` 处的折叠结果进行比较,并合并讲述一次。净变化为零的切换序列(先进入 plan,再在边界前切回)不会产生叙述;工具驱动的退出只通过自身工具结果叙述;首个轮次前设置的模式也不叙述,因为该段落本身就是状态说明。该原则来自进行中 env-state 提案的边界叙述:如果提示词表层悄然切换,transcript(文本记录)仍会依据 header 已不再具备的状态进行论述。
+
+**恢复时,配置已不再定义折叠出的模式会怎样?** 当前配置不再定义的折叠模式名称会在不通知的情况下表现为默认模式,因此会话既不会获得替代约束,也不会变得不可用。`set()` 的大声校验只覆盖写入路径;恢复后的日志以当时找到的配置为准。
+
+**如果部署没有组合用户交互提供方,会怎样?** Plan mode 仍然安全,但只能手动退出:`ctx.userInteraction.ask()` 会抛出 `NO_PROVIDER`(seam 不存在时甚至无法解析到该服务),工具返回纠正性的 `isError`,退出方式降级为由用户切换模式,绝不会在未经评审时退出。模式段落会要求模型通过 `exit_plan_mode` 呈现 plan,并在失败时改用普通文本询问用户,因此模型会继续呈现,而不会停滞。
+
+**为何没有逐模式工具允许清单?** 因为「哪些工具在规划模式下安全」是每个工具自身的属性(即副作用),不是模式的属性。逐模式名称清单会在错误的归属位置重新声明该事实,必须枚举部署所组合的每个工具(包括 MCP 服务器),且会随工具到来而无声腐化。在工具定义声明其副作用前(见[延期工作](#deferred),其中归档了被移除的临时允许清单及其重启触发条件),模式只通过自身段落和退出评审约束行为;由此产生的暴露面属于已接受代价(见[后果](#consequences))。
+
+**subagent 是否继承父级模式?** fork 子级可以直接继承,因为父级的 `mode/set` 位于种子前缀中。spawn 子级从默认模式开始;创建时模式选项与 subagent 提供方的自动转发一并延期(见[延期工作](#deferred))。
+
+**plan mode 与沙箱只读模式有何关系?** 二者是互不接触的独立轴:模式是协作姿态(`mode/set` 折叠),沙箱模式是强制约束旋钮(`bash/sandbox-mode` 折叠,参见[沙箱 Agent Note](2026-07-06-sandbox.md))。Plan mode 既不读取也不限制沙箱模式,与 Codex 将 Plan/Default 预设同沙箱及审批设置分开的做法完全一致。希望规划期间由内核强制只读的用户需要同时设置两者:以任意顺序切换模式选择器与沙箱模式选项;每次切换只改变自身折叠结果,因此二者互不干扰,也不存在可能崩溃的还原步骤。日志会把每条轴归因到各自事件:协作姿态对应 `mode/set`,隔离约束对应 `bash/sandbox-mode`。
+
+**为何沙箱模式、审批策略或模型本身不属于模式?** 它们是独立于协作状态的单项环境旋钮。已退役的 ACP 映射记录在[仅面向自动化的协议决策](../simplification/2026-07-23-acp-automation-only-protocol.md)中。未来模式定义可以捆绑环境事实(在挂载处通过 `ctx.envState` 应用),让 Codex 风格的预设仍是一种模式;但把审批策略融合进模式概念本身的方案已在[考虑过的替代方案](#alternatives-considered)中遭否决。
+
+## 既有方案
+
+对已发布 plan mode(Claude Code、Cursor、Copilot、OpenCode、Gemini CLI、Cline、Windsurf、Codex)的调研表明,各产品都包含同样五个部分:低权限工具策略、plan 产物、审批时刻、执行状态切换,以及[问题](#problem)所依赖的持久状态。
+
+只要产品公开模式接口,该接口就一定是清单,绝不是布尔值:Claude Code 的选择器提供 `plan`,旁边是 `acceptEdits`(另有自动进入 plan 的模式);Codex 则把 `Plan` 与 `Default` 并列公开为协作模式预设,同时让审批和沙箱设置保持独立。ACP 传输层不公开这项面向人类的控制。
+
+由部署拥有的示例提示词借鉴工具性行为,而非产品特有机制。它借鉴 Codex 的以下做法:即使收到祈使式实现语言也留在 plan mode;提问前先探索;区分仓库事实与由用户决定的选择;让 plan 完整覆盖 API、数据流、失败、测试和假设,从而足以作出决策。它还借鉴 Claude Code 的以下做法:禁止变更与提交;优先沿用现有模式;只针对需求或方案选择提问;通过退出工具完成规划,而不在普通文本中请求审批。它有意省略 Codex 协议标签,以及 Claude 的 plan 文件或分阶段 subagent 机制,因为这些属于各自运行时,而非本插件契约。
+
+把模式留给约定的生态展示了应避免的失败形态。Pi 风格的模式扩展会争抢一个后写覆盖的全局活跃工具清单,只靠提示词文本强制「只读」(模型幻觉调用一个仍已注册的工具时,该调用会实际执行),并在每个请求中重新注入 plan 状态以跨过压缩。这里通过逐 agent 的折叠状态,以及压缩无法遮蔽的只记录日志、非 surface 事件,从结构上消除了有争议的全局清单和重复注入补丁。相较之下,仅靠提示词的形态被有意保留:Codex 的 Plan 正是如此实现,这也是模式轴可以与强制约束轴自由组合的原因。需要硬性下限的部署会把模式与独立的沙箱旋钮配对,而不是让模式携带自身强制约束(见[常见问题](#faq))。
+
+## 考虑过的替代方案
+
+**以权限模式作为核心概念(Claude Code 的形态)。** 用一个 `permissionMode` 融合审批策略和工具策略。本设计中,它们是归不同所有者负责的两条轴:审批 seam 拥有「谁回答这个问题」,模式拥有「模型获得什么接口」。ACP 将二者建模为相关但有区别的概念(模式以后可以选择审批策略;届时是模式定义增加字段,而不是合并两者)。
+
+**由三个包组成的能力 seam。** 接口/实现/消费方适合可替换后端;模式的可变部分是配置值,而不是实现。拆分会制造一个空实现包,与审批 seam 及 [`todo/`](../../implemented/feature/2026-06-29-todo-write-tool.md) 作出的「不要过早拆分」决策相同。
+
+**由循环拥有模式状态。** 依据既有规则(用插件,而不修改循环)予以否决:该功能所需的每个钩子——组装、执行前处理、轮次边界、会话事件——都已经是有文档记录的 seam,修改循环只会增加耦合,别无收益。
+
+**带默认拒绝门禁的逐模式工具允许清单(首个发布形态)。** 已在发布前移除。手工维护的名称清单会逐模式重新声明一项逐工具事实(即其副作用):它必须枚举部署所组合的每个工具,包括 MCP 服务器和未来注册项,并会随工具增加而无声腐化(新增只读工具时,在有人编辑每种模式前都会被阻止;编写负担落在了解模式的人身上,而不是了解工具的人身上)。它还过度承诺:该清单看似安全边界,但 shell 之外的任何能力其实都不存在这种真实边界。通用维度已停放到副作用自声明(见[延期工作](#deferred));由此产生的后果——plan mode 只提供指引,也就是该门禁一度弥补的 Pi 缺口——是有意接受的,并计入[后果](#consequences)。
+
+**模式上的 `access` 沙箱上限(第二个发布形态)。** 同样已在发布前移除。`ModeDefinition.access` 会把 bash seam 的逐调用沙箱解析限制在模式声明的上限内(一个 `bash/resolve-mode` waterfall 加上取阶梯最小值的监听器;守卫会在执行器无法施加约束时隐藏 bash,并在模式中途拒绝提升权限)。状态仍保持正交,因为上限从不写入沙箱旋钮;但两条轴并不正交:进入 plan 会改变沙箱实际强制执行的内容,把协作姿态与强制约束级别融合起来,违背评审最终达成的 Codex 形态分离方式(Plan/Default 预设从不触及沙箱或审批设置)。这种融合有一项用户可见症状:规划期间把沙箱选项切换为 `workspace-write` 不会产生任何效果。上限、waterfall 和 mode→bash 依赖边随后一并移除;部署可以把模式与独立沙箱模式选项配合使用,从而在规划期间由内核强制只读。以后也可以重新引入模式触发的预设(模式定义捆绑建议的旋钮值,并作为普通旋钮切换加以应用),而无需再次融合两条轴。
+
+**仅存在于运行时的模式(只在 UI 或桥接层本地存在,不记录日志)。** 恢复与 fork 会悄然丢失模式,模式引起的 header 增量在日志中也没有可归因原因。记录到日志的状态让模式无需额外机制即可审计和还原。
+
+**把模式切换作为 `context/message` 并通过 `agent.inject()` 写入。** 这样可以复用现有的轮次封闭路径,却会把策略状态放入模型 transcript;模型无需被告知两次(段落已经告诉它),而只记录日志的事实不应占用 surface。
+
+**plan 文件存储(`.plans/` 目录)。** 这会为日志已经以可回放方式承载的内容创建第二个持久归属位置;需要文件的部署可以添加一个写入文件的工具。同一事实只应有一个归属位置。
+
+**用布尔值 `planMode` 取代具名模式。** 对仓库已经跟踪的接口而言过于狭窄:ACP 通告的是模式清单,已发布的选择器也不只填入 plan(见[既有方案](#prior-art));以后再泛化会重命名持久事件词汇。字符串形状的机制现在不产生额外成本;只有 `plan` 作为定义发布。
+
+**工具策略栈服务(对 Pi 批评的补救方案)。** 现在就为工具策略建立专用组合服务为时过早:本实现不执行按模式限制的工具过滤,未来的副作用策略可以通过现有带守卫的执行 seam 组合。只有已声明的工具副作用产生具体组合需求后,才应正式建立该服务。
+
+**通过审批 seam 执行退出审批(一个 `{ kind: 'ask' }` 门禁决策)。** 最初草案提出该方案;当时审批 seam 是唯一正在落地的询问机制,所以显得自然,但它把评审放进了权限的位置。该 seam 的结果词汇有意封闭且仅供单次使用(`allowed-once`/`rejected`),因此拒绝无法携带反馈,批准也永远无法增加选项(例如「批准并接受编辑」)。退出时刻是一个问题,而非权限;用户交互 seam 为其提供选项与自由文本通道,拒绝反馈也会逐字传给模型。审批 seam 仍适合真正的权限门禁(沙箱提升权限),注册表的 `ask` 词汇也继续供希望在该处设置询问的部署使用。
+
+**使用普通文本或 steering(中途引导)退出,而不使用工具。** 这样既没有产物,也没有审批时刻。工具参数本身就是可供评审的 plan,而评审问题会把结构化的是/否选择附着到确切转换上并交给人类。
+
+## 后果
+
+以下保证现在已经由单元、协议、快照和真实 API 测试层固定:
+
+- 生效模式是会话日志的纯函数:恢复与 fork 无需额外机制即可还原它;下一变化步骤中,一条 `mode/set` 后会出现匹配的完整 `request/header`。
+- 用户驱动的切换会在下一边界恰好叙述一次,净变化为零的切换序列不会产生叙述;工具驱动的退出只通过自身工具结果叙述。
+- 默认模式下,插件不贡献模式段落,但会贡献稳定的 `exit_plan_mode` schema;未部署 `dsh-mode` 的环境没有这项绑定。
+- 默认、plan 与自定义模式之间转换时,Native 工具 schema 与 Code Mode SDK 保持逐字节相同;只有已配置的指引段落发生变化。
+- Plan mode 不改变强制约束轴上的任何内容:工具集、沙箱模式、权限提升和审批策略在 plan 与默认模式下表现完全相同;部署通过把模式与独立的沙箱/审批旋钮配合使用来强化规划。
+- 模式定义可通过 `cordis.yml` 修改,无需编辑代码;其中必须提供完整 plan 指令。缺失 plan 配置、定义畸形和未知键会在加载时失败,未知模式名称会在 `set()` 时失败。
+- `exit_plan_mode` 始终通告,在 plan 之外会拒绝;批准后只移除 plan 指引,并通过纠正性的 `isError` 携带继续规划反馈;每个面向人类的接口都由其用户交互提供方承载评审。
+- 随功能落地一并交付的文档收尾包括:README、重新生成的目录(持久化日志、配置、Cordis 服务、工具)、包索引与架构行,以及实操手册中的对应行。
+
+已接受的代价如下:在 agent 空闲时设置的待生效用户切换,如果进程在下一轮次前退出,就会丢失(UI 会重新应用;若实践中出现问题,空闲记录原语就是逃生口)。模式转换会改变顺序 50 处的系统提示词,因此从该处开始的缓存路径也会变化,但工具 schema 与 Code Mode SDK 不再抖动。**模式只依靠指引约束行为**:忽略该段落的模型可以在 plan 期间执行变更;评审时刻、会话日志以及独立的沙箱、审批和文件系统策略共同构成约束边界。强化规划意味着设置这些旋钮,而不是扩大模式职责;被移除的强制约束形态及其副作用声明重启触发条件仍记录在[考虑过的替代方案](#alternatives-considered)和[延期工作](#deferred)中。面向人类的接口拥有 plan 选择器与评审交互;ACP 自动化传输层两者都不承载。
diff --git a/.agents/notes/archived/feature/2026-07-14-time-context-plugin.i18n.yaml b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.i18n.yaml
new file mode 100644
index 0000000000..62ca11e69e
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.i18n.yaml
@@ -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
+2026-07-14-time-context-plugin.md: 89d6fca7c473a932e8f014ff0576cecfd6f2e4e0
+2026-07-14-time-context-plugin.zh.md: 11e642f0361209cd29e86441e9ee82d845ea63dd
diff --git a/.agents/notes/archived/feature/2026-07-14-time-context-plugin.md b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.md
new file mode 100644
index 0000000000..89d6fca7c4
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.md
@@ -0,0 +1,60 @@
+# Agent Note: Optional time-context plugin
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-14-time-context-plugin.zh.md)
+
+## Problem
+
+The dynamic system-prompt storage and refresh decision in this record is superseded by [Durable per-step time context](2026-07-16-durable-per-step-time-context.md). The opt-in package, zoned formatting, and validation remain; the follow-up owns the current model-visible and durability contract.
+
+An agent request has no live clock unless a deployment puts one in prompt text or gives the model a query tool. Static text becomes stale, while a tool call adds overhead to ordinary reasoning about dates, deadlines, or idle time. Without elapsed time, the model cannot distinguish an immediate follow-up from one sent hours after the preceding message.
+
+Prompt assembly can derive both facts per step from durable session timestamps, and request-header logging can record the exact rendered value. Accumulating stale readings in conversation history or waking idle agents would violate the existing request lifecycle.
+
+## Decision
+
+`@deepseek-ai/dsh-time-context` is an opt-in function plugin at `packages/context/time-context/`. The `context/` product group holds bounded request-context enrichments that define neither a tool nor a service. `dsh-agent-spine-demo` and shipped examples do not load the package; deployments mount it explicitly when its token and disclosure costs are acceptable.
+
+The plugin registers the global `context:time` system-prompt section at order 10, after the deployment persona and before tool guidance. For an active turn it emits an ISO-shaped timestamp with numeric UTC offset and IANA zone, plus a compact whole-second duration since the last model-visible message before the turn opened. Bare and idle assemblies receive an empty section.
+
+### Previous-message baseline
+
+At a turn's first assembly, the provider scans before `turn/start` for the latest `user/message`, `assistant/message`, `tool/result`, `context/message`, or `steering/message`. It excludes the current prompt so the duration expresses the inter-turn gap instead of approximately zero. Every refresh in that turn keeps the same baseline, and the first turn reports `unavailable (no earlier message in this session)`.
+
+The baseline is the session event's append time, not an unlogged client timestamp. Resume and fork behavior are therefore deterministic from the durable log, and the model-visible value remains reconstructable without a new event. A backward wall-clock adjustment clamps the duration to zero.
+
+### Refresh policy
+
+`refreshIntervalMs` defaults to 60,000 and must be a non-negative safe integer. Every turn's first request refreshes. Later assemblies in that turn reuse the block until its age reaches the interval; `0` refreshes every step. No timer creates work during model calls, tools, or idle time because refresh is request-bound.
+
+When `timeZone` is omitted, `Intl.DateTimeFormat` resolves the Node process's system zone once at plugin load. Node honors `TZ`; without that override, the host or container supplies the zone. An explicit value must be an IANA identifier and is validated at load. The captured zone remains stable until plugin reload, and the ISO-shaped local timestamp includes its current numeric offset so daylight-saving changes stay explicit. This is the deployment process's zone, not a remote user's zone.
+
+### Logging and token shape
+
+The loop records the temporal block in full `request/header` snapshots before transmission, satisfying the [reconstructable-requests contract](../architecture/2026-07-05-reconstructable-requests.md). Each request carries one current block; earlier readings do not remain in conversation history. The plugin owns the fact and contributes it through the prompt registry, following the [prompt-variables Agent Note](../architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md) without a loop special case.
+
+## Testing
+
+Unit tests pin formatting, baselines, refresh policy, validation, per-agent state, disposal, and load-time system-zone capture. A real agent-loop test pins the transmitted prompt and full `request/header` snapshots. A keyless subprocess e2e boots a test-only `cordis.yml` through the real Loader and stdio app, omits `timeZone` under a controlled `TZ`, drives two turns, and verifies the persisted request headers externally. Default snapshot compositions omit the plugin, so their transcript fixtures contain no temporal block.
+
+## Alternatives considered
+
+- **Append a `context/message` on every turn or refresh** — rejected because readings and token cost would accumulate in history. Replacing a prior surface node would preserve its old position, while replacing the tail would hide intervening conversation.
+- **Use `agent/session-prefix`** — rejected because the session-stable prefix cannot represent a per-turn or per-step clock.
+- **Mutate requests in `agent/request`** — rejected because that seam shapes call config after the message boundary; inserted model content would bypass prompt-pressure accounting and request-header logging.
+- **Register separate `{{current_time}}` and `{{elapsed}}` variables** — rejected because independent providers can sample different instants and require shared caching. One section records the pair atomically without a deployment-authored template.
+- **Refresh from a background timer** — rejected because a new value has no consumer outside request assembly. Timer-driven `agent.inject()` would create turns and wake idle sessions merely to report time passing.
+- **Keep UTC as the omitted default** — rejected because an explicitly enabled clock should follow its deployment environment unless the operator chooses UTC. `timeZone: UTC` remains available when a deployment requires it.
+- **Add a time-zone detection library** — rejected because Node's `Intl` runtime already exposes the process's IANA zone. Another dependency cannot infer a remote user's zone either.
+- **Mount the plugin in `dsh-agent-spine-demo`** — rejected because time zone, disclosure, token budget, and freshness are deployment policy. Opt-in keeps default context stable.
+- **Place the package in `core/`** — rejected because `core/` owns the product API spine, while this plugin is an optional leaf with no service key.
+
+## Consequences
+
+- Opted-in models receive a zoned clock and inter-turn duration without a tool call. The system-prompt cost is fixed per request instead of growing with the session.
+- An omitted `timeZone` follows the process's `TZ`, host, or container zone as observed at plugin load. Operators must configure an explicit zone when the deployment environment does not represent the intended user.
+- A refresh changes the request header and can add a full `request/header` snapshot with reason `change`. `refreshIntervalMs` trades freshness against the number and size of durable full snapshots; `0` records a new value on every step whose whole-second rendering changes.
+- No request exists solely to refresh time. A long-running tool leaves the prior reading until the next step assembles.
+- Duration reflects harness processing time at durable append boundaries, not client-network latency before logging. Preserving a client-origin timestamp requires a separate durable input contract.
diff --git a/.agents/notes/archived/feature/2026-07-14-time-context-plugin.zh.md b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.zh.md
new file mode 100644
index 0000000000..11e642f036
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-14-time-context-plugin.zh.md
@@ -0,0 +1,60 @@
+# Agent Note: 可选时间上下文插件
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-14-time-context-plugin.md) | 中文
+
+## 问题
+
+本记录中的动态系统提示词存储和刷新决策已由[持久的逐步骤时间上下文](2026-07-16-durable-per-step-time-context.md)取代。需要显式启用的包(package)、分区时间格式和校验仍然保留;后续 Agent Note 负责当前的模型可见与持久性契约。
+
+如果部署方既未在提示词中提供时钟,也未给模型提供查询工具,agent(智能体)请求就无法获得实时准确的时间。静态文本会变得陈旧,而对于日期、截止时间或闲置时长等常规推理,调用工具会增加开销。缺少已经过去的时长时,模型无法区分紧接着发送的消息与上一条消息几小时后才发送的消息。
+
+提示词组装流程可以在每个步骤中根据持久会话时间戳派生这两项信息,请求头日志则可以记录实际渲染的确切值。在会话历史中累积陈旧读数或唤醒空闲 agent 都会违反现有请求生命周期。
+
+## 决策
+
+`@deepseek-ai/dsh-time-context` 是位于 `packages/context/time-context/`、需要显式启用的函数插件。`context/` 产品分组用于容纳既不定义工具、也不定义服务的有界请求上下文增强。`dsh-agent-spine-demo` 和仓库提供的示例都不会加载该包;只有当 token 与信息披露成本可接受时,部署方才显式挂载它。
+
+该插件注册顺序值为 10 的全局系统提示词区段 `context:time`,位置在部署方角色设定之后、工具指导之前。对于活跃轮次,它会输出带数字 UTC 偏移和 IANA 时区、形似 ISO 的时间戳,以及从轮次开始前最后一条模型可见消息起算的紧凑整秒时长。未绑定 agent 或 agent 处于空闲状态时,该区段为空。
+
+### 上一条消息基线
+
+在轮次首次组装时,提供方会在 `turn/start` 之前查找最近的 `user/message`、`assistant/message`、`tool/result`、`context/message` 或 `steering/message`。它会排除当前提示词,使时长表达轮次间隔,而不是接近零。同一轮次中的每次刷新都保留这条基线;首个轮次报告 `unavailable (no earlier message in this session)`。
+
+基线采用会话事件的追加时间,而不是日志中不存在的客户端时间戳。因此,恢复和 fork 行为可以从持久日志中确定性重现,模型可见值也无需新增事件即可重建。系统挂钟向后调整时,插件会将时长钳制为零。
+
+### 刷新策略
+
+`refreshIntervalMs` 默认值为 60,000,并且必须是非负安全整数。每个轮次的首次请求都会刷新。同一轮次中的后续组装会复用该区块,直至其存在时间达到该间隔;设为 `0` 时每个步骤都刷新。刷新仅由请求驱动,因此在模型调用、工具运行或空闲期间,计时器不会创建任务。
+
+省略 `timeZone` 时,`Intl.DateTimeFormat` 会在插件加载时解析一次 Node 进程的系统时区。Node 会遵循 `TZ`;没有该覆盖值时,时区由主机或容器提供。显式值必须是 IANA 标识符,并在加载时接受校验。捕获的时区在插件重新加载前保持稳定,形似 ISO 的本地时间戳包含其当前数字偏移,使夏令时变化保持显式可见。该默认值代表部署进程的时区,而不是远程用户的时区。
+
+### 日志与 token 形态
+
+agent loop(智能体循环)会在发送前通过完整的 `request/header` 快照记录时间区块,从而满足[可重建请求契约](../architecture/2026-07-05-reconstructable-requests.md)。每个请求只携带一个当前区块;先前的读数不会保留在会话历史中。该插件拥有时间信息,并按照[提示词变量 Agent Note](../architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)通过提示词注册表贡献该信息,无需为循环添加特殊分支。
+
+## 测试
+
+单元测试固定格式化、基线、刷新策略、校验、逐 agent 状态、资源释放行为,以及系统时区在加载时的捕获行为。使用真实 agent loop 的测试固定实际发送的提示词和完整的 `request/header` 快照。无密钥子进程端到端测试通过真实 Loader 和 stdio 应用启动测试专用 `cordis.yml`,在受控 `TZ` 下省略 `timeZone`,驱动两个轮次,并从外部校验持久请求头。默认快照组合不包含该插件,因此其中的 transcript(文本记录)fixture(测试前置数据)不包含时间区块。
+
+## 考虑过的替代方案
+
+- **每个轮次或每次刷新都追加一条 `context/message`**——不予采纳,因为读数和 token 成本会在历史中累积。替换先前的表层节点会保留其旧位置,而替换尾部节点会隐藏中间的会话内容。
+- **使用 `agent/session-prefix`**——不予采纳,因为会话期间保持稳定的前缀无法表示逐轮次或逐步骤变化的时钟。
+- **在 `agent/request` 中修改请求**——不予采纳,因为该边界在消息边界之后塑造调用配置;插入模型可见内容会绕过提示词压力核算和请求头日志。
+- **注册独立的 `{{current_time}}` 和 `{{elapsed}}` 变量**——不予采纳,因为独立提供方可能在不同时间点采样,并且需要共享缓存。单个区段会以原子方式记录两项信息,也不需要部署方编写时间模板。
+- **通过后台计时器刷新**——不予采纳,因为请求组装之外没有消费新值的对象。由计时器驱动 `agent.inject()` 会创建轮次,并且只为报告时间流逝就唤醒空闲会话。
+- **省略配置时仍默认使用 UTC**——不予采纳,因为显式启用的时钟应跟随部署环境,除非运维方选择 UTC。需要 UTC 的部署仍可配置 `timeZone: UTC`。
+- **引入时区探测库**——不予采纳,因为 Node 的 `Intl` 运行时已经能够提供进程的 IANA 时区,而且额外依赖同样无法推断远程用户的时区。
+- **在 `dsh-agent-spine-demo` 中挂载插件**——不予采纳,因为时区、信息披露、token 预算和新鲜度都属于部署策略。选择加入能保持默认上下文稳定。
+- **将包放入 `core/`**——不予采纳,因为 `core/` 负责产品 API 主干,而该插件是没有服务键的可选叶节点。
+
+## 后果
+
+- 选择加入的模型无需调用工具,即可获得分区时钟和轮次间隔时长。每个请求的系统提示词成本固定,不会随会话增长。
+- 省略 `timeZone` 时,插件采用加载时观察到的进程 `TZ`、主机或容器时区。当部署环境不能代表目标用户时,运维方必须显式配置时区。
+- 刷新会改变请求头,并可能新增一份 reason 为 `change` 的完整 `request/header` 快照。`refreshIntervalMs` 用新鲜度换取完整持久快照的数量与大小;设为 `0` 时,每个整秒渲染结果发生变化的步骤都会记录新值。
+- 系统不会仅为刷新时间而创建请求。长时间运行的工具会保留先前读数,直至下一步骤开始组装。
+- 时长反映持久追加边界处的 harness 处理时间,不包含消息进入日志之前的客户端网络延迟。若要保留客户端来源时间戳,需要单独的持久输入契约。
diff --git a/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.i18n.yaml b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.i18n.yaml
new file mode 100644
index 0000000000..efebe5b58e
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.i18n.yaml
@@ -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
+2026-07-20-tui-startup-slogans.md: aa847f96ebe13c4b1833531074577561faa2afb9
+2026-07-20-tui-startup-slogans.zh.md: bd667dfe9f54bbe923c48d0adf2889735b1255c5
diff --git a/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.md b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.md
new file mode 100644
index 0000000000..aa847f96eb
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.md
@@ -0,0 +1,40 @@
+# Agent Note: Startup slogans replace the configured TUI welcome line
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-20-tui-startup-slogans.zh.md)
+
+> **Superseded** for the slogan/animation half by the [banner sweep Agent Note](2026-07-21-tui-banner-sweep.md): the slogan bank and typewriter reveal shipped, read as weird in use, and were replaced by a subtitle-free banner with a whole-banner sweep. The removal of the configured demo welcome and the animation-lifecycle groundwork (start after `ui.start()`, clear through `detachListeners`) stand.
+
+## Problem
+
+The TUI header subtitle came from a `welcome` config the demo leaf set to "TUI agent ready. Give it a coding task." — instructional filler that told a returning user nothing, restated what the product is on every boot, and had a hardcoded twin (`'ready.'`) as the schema default in two packages. The product wanted a startup moment with some character instead of a static banner caption.
+
+## Decision
+
+- `examples/tui-agent/cordis.yml` no longer configures `welcome`; the config key stays for deployments and fixtures that need a fixed, deterministic subtitle (the Code Mode overlay and every snapshot/scripted fixture keep theirs).
+- When `welcome` is unset, `dsh-tui` picks one member of an exported `STARTUP_SLOGANS` bank per boot (`pickStartupSlogan`, injectable random source) and reveals it with a typewriter animation: one character per 40 ms frame, a `▌` block cursor trailing until complete. The reveal starts only after `ui.start()` succeeds and its interval is cleared on dispose alongside the other listeners.
+- The slogan bank is presentation copy, deliberately not config: deployments that want controlled wording already have `welcome`. Slogans are ASCII-only by contract because the reveal slices per character.
+- `dsh-tui-demo` forwards `welcome` only when configured instead of defaulting it, so the app no longer decides the TUI's idle subtitle.
+- The keyless PTY boot scenario now waits for the reveal cursor (`▌` — the only source of that glyph in an empty transcript) instead of the removed welcome text.
+
+The same change restores `packages/ui/tui/src/index.ts` to 100 % per-file coverage, which the color-scheme merge had broken on the integration branch: the editor border-color reassignment inside `applyColorScheme` was dead (the `setStatus` call right after re-derives it) and is removed, and the color-scheme query's `.then`/`.catch` arrows became named, tested handlers (`applyReportedScheme`, `ignoreSchemeQueryFailure` — the latter pinned by a test whose terminal throws on the DSR query write).
+
+## Alternatives considered
+
+**A fixed cooler slogan.** Rejected: one string re-read on every boot decays into wallpaper exactly like the line it replaces; a small rotating bank keeps the moment alive at no complexity cost.
+
+**Making the bank and reveal speed configurable.** Rejected: that is two new knobs for presentation copy; `welcome` is already the escape hatch for deployments with an opinion, and the no-hardcoded-tunables rule targets deployment-varying behavior, not brand copy.
+
+**Animating in `HeaderComponent` itself.** Rejected: the component would need a TUI handle and its own lifecycle; the chat already owns a render loop, timers, and a disposal path, so the reveal lives beside the other `createTuiChat` effects and `detachListeners` clears it.
+
+## Consequences
+
+- Boot output is no longer byte-deterministic when `welcome` is unset (random slogan, timed frames). Every recorded or snapshot surface pins `welcome` explicitly, so no snapshot changed; the PTY smoke anchors on the reveal cursor and the session-id line instead.
+- The `welcome` schema default disappeared from both `dsh-tui` and `dsh-tui-demo`; a direct caller passing no welcome now gets a slogan, not `'ready.'`.
+- Adding a slogan is a one-line bank edit; tests assert membership, not specific text.
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` pins deterministic bank selection with an injected random source, the reveal (a bank member fully rendered, cursor frames observed), the configured-welcome path rendering verbatim with no cursor, and dispose stopping a mid-reveal animation. `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` boots the real tree in a PTY and waits on the reveal cursor. Verified live in tmux (mid-reveal frame `no map below▌` then the full slogan).
diff --git a/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.zh.md b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.zh.md
new file mode 100644
index 0000000000..bd667dfe9f
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-20-tui-startup-slogans.zh.md
@@ -0,0 +1,40 @@
+# Agent Note: 启动 slogan 取代配置化的 TUI 欢迎语
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-20-tui-startup-slogans.md) | 中文
+
+> **已被取代**:slogan/动画的那一半由[横幅扫入 Agent Note](2026-07-21-tui-banner-sweep.md)取代:slogan 库和打字机动画上线后实际使用中显得怪异,已替换为无副标题的横幅加整体扫入。移除示例配置中欢迎语的决定与动画生命周期基础设施(`ui.start()` 后启动、经 `detachListeners` 清除)保持不变。
+
+## Problem
+
+TUI 头部副标题来自一个 `welcome` 配置,示例叶子配置把它设为 "TUI agent ready. Give it a coding task."——一句说明书式的填充语,对老用户毫无信息量,每次启动都在复述产品是什么,而且它还有一个硬编码的孪生兄弟(`'ready.'`)作为两个包里的 schema 默认值。产品需要的是一个有性格的启动时刻,而不是一条静态横幅说明。
+
+## Decision
+
+- `examples/tui-agent/cordis.yml` 不再配置 `welcome`;该配置键保留给需要固定、确定性副标题的部署与 fixture(Code Mode overlay 和所有快照/脚本化 fixture 都保留各自的欢迎语)。
+- `welcome` 未设置时,`dsh-tui` 每次启动从导出的 `STARTUP_SLOGANS` 库里挑选一条(`pickStartupSlogan`,随机源可注入),并以打字机动画逐字显示:每帧 40 ms 一个字符,完成前尾随一个 `▌` 块状光标。动画只在 `ui.start()` 成功后启动,其定时器与其他监听器一起在 dispose 时清除。
+- slogan 库是展示文案,刻意不做成配置:想控制措辞的部署已经有 `welcome` 这个出口。按契约 slogan 只含 ASCII,因为逐字显示按字符切片。
+- `dsh-tui-demo` 只在配置了 `welcome` 时才转发它,不再填默认值,应用不再替 TUI 决定空闲副标题。
+- 无 key 的 PTY 启动场景改为等待逐字显示的光标(`▌`——空 transcript 里该字形的唯一来源),不再等待已删除的欢迎文本。
+
+同一变更把 `packages/ui/tui/src/index.ts` 恢复到 100% 的单文件覆盖率(颜色方案合并曾在集成分支上破坏它):`applyColorScheme` 里对编辑器边框颜色的重新赋值是死代码(紧随其后的 `setStatus` 调用会重新推导它),已删除;颜色方案查询的 `.then`/`.catch` 箭头函数改为具名、有测试的处理器(`applyReportedScheme`、`ignoreSchemeQueryFailure`——后者由一个让终端在 DSR 查询写入时抛错的测试固定)。
+
+## Alternatives considered
+
+**换一条更酷的固定 slogan。** 否决:一条每次启动都重读的字符串会和它取代的那行一样退化成墙纸;一个小的轮换库以零复杂度代价让这个时刻保持新鲜。
+
+**把 slogan 库和显示速度做成配置。** 否决:那是为展示文案新增两个旋钮;对措辞有主张的部署已经有 `welcome` 这个出口,而「插件里不许硬编码可调参数」规则针对的是随部署变化的行为,不是品牌文案。
+
+**在 `HeaderComponent` 内部做动画。** 否决:组件将需要持有 TUI 句柄和自己的生命周期;聊天层已经拥有渲染循环、定时器和释放路径,所以逐字显示与 `createTuiChat` 的其他资源放在一起,由 `detachListeners` 清除。
+
+## Consequences
+
+- `welcome` 未设置时启动输出不再字节级确定(随机 slogan、定时帧)。所有录制或快照表面都显式固定 `welcome`,因此没有快照变化;PTY 冒烟测试改为锚定逐字显示光标和会话 id 行。
+- `welcome` 的 schema 默认值从 `dsh-tui` 和 `dsh-tui-demo` 中消失;不传 welcome 的直接调用方现在得到的是 slogan,而不是 `'ready.'`。
+- 新增一条 slogan 只需在库里加一行;测试断言成员归属,不断言具体文本。
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` 固定以下行为:注入随机源后的确定性选取、逐字显示(库中某条完整渲染、观察到光标帧)、配置了 welcome 时逐字动画不启动且原文渲染、以及 dispose 停止进行中的动画。`examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` 在 PTY 里启动真实配置树并等待显示光标。已在 tmux 中实机验证(中途帧 `no map below▌`,随后是完整 slogan)。
diff --git a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml
index 2c0b4d3404..f8a7157bdd 100644
--- a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml
@@ -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-21-dsh-system-prompt-source-path.md: 4cb89e8124840bba6633235d195e95957245137c
-2026-07-21-dsh-system-prompt-source-path.zh.md: 90c23bed4a3f95155e323c63a68fe2da09543ea6
+2026-07-21-dsh-system-prompt-source-path.md: 9581966d10693e1ccbdce1a860314a34387e225e
+2026-07-21-dsh-system-prompt-source-path.zh.md: 392fcd44d988d483306effc34d4aaf4211803b4e
diff --git a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.md b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.md
rename to .agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.md
index 4cb89e8124..9581966d10 100644
--- a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.md
+++ b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.md
@@ -1,6 +1,7 @@
# Agent Note: dsh tells the agent where its own source lives
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-dsh-system-prompt-source-path.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.zh.md b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.zh.md
rename to .agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.zh.md
index 90c23bed4a..392fcd44d9 100644
--- a/.agents/notes/implemented/feature/2026-07-21-dsh-system-prompt-source-path.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-dsh-system-prompt-source-path.zh.md
@@ -1,6 +1,7 @@
# Agent Note: dsh 告知 agent 其自身源码所在位置
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-dsh-system-prompt-source-path.md) | 中文
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.i18n.yaml
new file mode 100644
index 0000000000..1060fa28b5
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.i18n.yaml
@@ -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
+2026-07-21-tui-auto-pane-title.md: 5235ffb12807b7e24ef952df594052ed7aa65cdf
+2026-07-21-tui-auto-pane-title.zh.md: 0b93d5a0a9d31e9f4321ca7ae975c1db74d2229e
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.md b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.md
new file mode 100644
index 0000000000..5235ffb128
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.md
@@ -0,0 +1,42 @@
+# Agent Note: Auto-titled terminal from the first message
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-21-tui-auto-pane-title.zh.md)
+
+> **Superseded** by the [session-title consolidation Agent Note](../simplification/2026-07-22-tui-titles-from-session-title-service.md): the TUI-local `autoTitle` generation is removed; titles come from the log-backed session-title service, and the terminal rename consumes `session/title` events.
+
+> **Superseded** for the default and the resume behavior by the [auto-title default-on Agent Note](2026-07-21-tui-auto-title-default-on.md): `autoTitle` now defaults on, and a resumed session re-derives its title from the stored first message instead of keeping the static one. The OSC 0 path, the one-shot latch, the model-summary shape, the fire-and-forget call, and every failure fallback below stand.
+
+## Problem
+
+The TUI's terminal title is a single static string (`title`, default `DeepSeek Harness`) shared by every session. A user who runs one agent per tmux pane or terminal tab sees the same label on all of them, so panes are indistinguishable at a glance and the tab bar carries no signal about what each session is doing.
+
+## Decision
+
+- `TuiConfig` gains an `autoTitle` boolean (default `false`). When it is on, the TUI issues one background model call after the first user message of a fresh session and replaces the terminal title with a short, model-generated label; the static `title` is the pre-title and the fallback.
+- The label is a model summary, not a truncation of the prompt. The request carries a fixed task instruction (summarize the request as a short title of two to five lowercase words, no punctuation) plus the user's first message and no tools; the TUI takes the first non-empty line of the reply and caps it at 40 characters (39 plus an ellipsis).
+- The title is set through `runtime.terminal.setTitle`, the same OSC 0 path the static `title` already uses. No new terminal-control surface is introduced, and pi-tui keeps ownership of terminal writes.
+- The call is fire-and-forget and one-shot per session. A `titleSettled` latch guards it: with `autoTitle` off it is pre-settled and never runs; on a resumed session whose first `user/message` is already logged it is pre-settled so the static title stands; a whitespace-only first message is skipped without consuming the slot. Any failure, an empty reply, a missing `llm` service, or a missing agent provider/model leaves the static title untouched. A dedicated `AbortController` cancels an in-flight request on shutdown.
+- The title call reaches `ctx.llm.stream` directly rather than through `agent.send`, so it never appends to the session or transcript and cannot perturb the agent loop.
+- The feature defaults off and is enabled only in the interactive product config (`examples/tui-agent/cordis.yml`) and the scripted PTY fixture. Enabling it in the shared `dsh-tui-demo` schema default would fire an extra model call in keyless replay and boot scenarios that send no user message.
+
+## Alternatives considered
+
+**Truncate the first user message instead of a model title.** Rejected: the user chose a short model-made label; a truncated raw prompt is noisy, often begins with boilerplate, and rarely reads as a title.
+
+**Rename the window (OSC 2) or the tmux window.** Rejected: OSC 0 sets only `pane_title`, so it labels the pane without renaming or leaking into the user's window title; the user confirmed OSC is the right lever.
+
+**Default the feature on.** Rejected: enabling it in the shared demo schema perturbs keyless replay and boot snapshots and spends a model call on every fresh session; opt-in per deployment keeps the default surface inert.
+
+**Fold this into the log-backed session-title work (PR #451).** Rejected: that change is session metadata persisted to the log; this is a terminal label with no persistence. Keeping them independent leaves each self-contained and avoids a shared dependency.
+
+**Block the first turn until the title resolves.** Rejected: awaiting the title before sending the user's message adds latency to the actual request; fire-and-forget makes the rename invisible to the turn.
+
+## Consequences
+
+- When enabled, a fresh session spends one extra, tool-less model call with a single short user message and a few output tokens; off by default, it costs nothing.
+- Because the title call stamps `sessionId`, it shares the session's `llm-replay` cursor: enabling `autoTitle` in a replay-backed snapshot scenario would consume a recorded script entry. This is why the default is off and the scripted PTY fixture answers the call with a tool-branching adapter rather than replay.
+- `packages/ui/tui/tests/tui.spec.ts` pins the behavior with a mock `llm` adapter: a generated title replaces the static one, over-long output is truncated with an ellipsis, a whitespace-only first message keeps the one-shot slot, empty or failing replies leave the title, a resumed session never fires, and the feature-off / no-service / missing-provider / missing-model paths keep the static title. A shutdown test asserts the in-flight request is aborted.
+- `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` proves the real Loader-booted path: the scripted adapter answers the tool-less title call with a fixed string, and the conversation scenario asserts the OSC 0 sequence reaches the PTY. Boot scenarios send no user message, so they never fire the call.
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.zh.md
new file mode 100644
index 0000000000..0b93d5a0a9
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-pane-title.zh.md
@@ -0,0 +1,42 @@
+# Agent Note: 从首条消息自动命名终端
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-21-tui-auto-pane-title.md) | 中文
+
+> **已被取代**:见[标题归一 Agent Note](../simplification/2026-07-22-tui-titles-from-session-title-service.md)。TUI 本地的 `autoTitle` 生成已移除;标题来自日志承载的 session-title 服务,终端重命名消费 `session/title` 事件。
+
+> **已被取代**(就默认值与恢复行为而言),见[自动标题默认开启 Agent Note](2026-07-21-tui-auto-title-default-on.md):`autoTitle` 现默认开启,恢复会话会从已存储的首条消息重新推导标题,而非保留静态标题。下文的 OSC 0 路径、一次性门闩、模型概括形态、发出后不等待其返回的调用,以及每一条失败兜底,均仍然成立。
+
+## Problem
+
+TUI 的终端标题是一个所有会话共用的静态字符串(`title`,默认 `DeepSeek Harness`)。在 tmux 每个窗格或每个终端标签页各跑一个 agent(智能体)的用户看来,它们的标签全都一样,因此窗格一眼看去无从区分,标签栏也不携带任何关于各会话正在做什么的信号。
+
+## Decision
+
+- `TuiConfig` 新增布尔字段 `autoTitle`(默认 `false`)。开启后,TUI 会在全新会话的首条用户消息之后发起一次后台模型调用,并用一个简短的、模型生成的标签替换终端标题;静态 `title` 是替换前的初值,也是兜底。
+- 该标签是模型概括,而非对提示词的截断。请求携带一段固定的任务指令(将该请求概括为两到五个小写单词、不含标点的简短标题)加上用户的首条消息,且不带工具;TUI 取回复的首个非空行并截断到 40 个字符(39 个字符加一个省略号)。
+- 标题通过 `runtime.terminal.setTitle` 设置——静态 `title` 已经在用的同一条 OSC 0 路径。不引入任何新的终端控制面,终端写入仍归 pi-tui 所有。
+- 该调用发出后不等待其返回,且每会话仅一次。一个 `titleSettled` 门闩守护它:`autoTitle` 关闭时它预先置为已结算、从不运行;在首条 `user/message` 已入日志的恢复会话中它预先结算,因此静态标题得以保留;仅含空白的首条消息被跳过且不消耗名额。任何失败、空回复、缺少 `llm` 服务、或缺少 agent 的 `provider` 或 `model`,都会让静态标题保持不动。一个专用的 `AbortController` 在关闭时取消尚在进行的请求。
+- 标题调用直接抵达 `ctx.llm.stream`,而非经由 `agent.send`,因此它从不追加进会话或 transcript(文本记录),也无法扰动 agent loop(智能体循环)。
+- 该功能默认关闭,仅在交互式产品配置(`examples/tui-agent/cordis.yml`)与脚本化 PTY fixture(测试前置数据)中开启。若在共享的 `dsh-tui-demo` schema 默认值里开启,会在不发送任何用户消息的无密钥回放与启动场景中多发一次模型调用。
+
+## Alternatives considered
+
+**截断首条用户消息,而非用模型生成标题。** 否决:用户选择的是简短的、模型制作的标签;截断后的原始提示词嘈杂、常以样板文字开头,且很少读起来像标题。
+
+**重命名窗口(OSC 2)或 tmux 窗口。** 否决:OSC 0 只设置 `pane_title`,因此它标记窗格而不重命名、也不泄漏进用户的窗口标题;用户确认 OSC 是正确的手段。
+
+**让该功能默认开启。** 否决:在共享的 demo schema 里开启会扰动无密钥回放与启动快照,并在每个全新会话上花掉一次模型调用;按部署选择性开启可让默认面保持惰性。
+
+**并入日志支撑的会话标题工作(PR #451)。** 否决:那项改动是持久化到日志的会话元数据;本项是不做持久化的终端标签。让二者相互独立可使各自自成一体,并避免共享依赖。
+
+**阻塞首轮直到标题就绪。** 否决:在发送用户消息前先等待标题,会给实际请求增加延迟;发出后不等待其返回可让重命名对该轮次不可见。
+
+## Consequences
+
+- 开启时,全新会话会多花一次无工具的模型调用,只带单条简短的用户消息和少量输出 token;默认关闭时它不产生任何开销。
+- 由于标题调用会打上 `sessionId`,它与会话的 `llm-replay` 游标共享:在以回放支撑的快照场景中开启 `autoTitle` 会消耗一条录制脚本条目。这正是它默认关闭、且脚本化 PTY fixture 用按工具分支的适配器而非回放来回答该调用的原因。
+- `packages/ui/tui/tests/tui.spec.ts` 用一个 mock `llm` 适配器固定该行为:生成的标题替换静态标题、过长输出以省略号截断、仅含空白的首条消息保留一次性名额、空回复或失败回复保留标题、恢复的会话从不触发,以及功能关闭 / 无服务 / 缺提供方 / 缺模型各路径都保留静态标题。一项关闭测试断言尚在进行的请求被中止。
+- `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` 证明真实的经 Loader 启动的路径:脚本化适配器以固定字符串回答无工具的标题调用,对话场景断言 OSC 0 序列抵达 PTY。启动场景不发送用户消息,因此它们从不触发该调用。
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.i18n.yaml
new file mode 100644
index 0000000000..6d0a7c59d1
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.i18n.yaml
@@ -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
+2026-07-21-tui-auto-title-default-on.md: 498c6095fcd05c40ce2ad48364a9ac02beb9aa05
+2026-07-21-tui-auto-title-default-on.zh.md: 8bd426c8705d84f674f811347c04ef42de7cae15
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.md b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.md
new file mode 100644
index 0000000000..498c6095fc
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.md
@@ -0,0 +1,33 @@
+# Agent Note: Auto-title on by default, re-derived on resume
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-21-tui-auto-title-default-on.zh.md)
+
+> **Superseded** by the [session-title consolidation Agent Note](../simplification/2026-07-22-tui-titles-from-session-title-service.md): the TUI-local `autoTitle` generation is removed; titles come from the log-backed session-title service, and the terminal rename consumes `session/title` events.
+
+## Problem
+
+The [auto-title Agent Note](2026-07-21-tui-auto-pane-title.md) shipped `autoTitle` off by default and, on a resumed session, kept the static title because the first `user/message` was already logged. In use both choices defeated the feature's purpose. A per-session descriptive pane title is what makes one tmux pane or terminal tab distinguishable from the next; leaving it off by default means the product ships an inert feature that almost no user turns on, and skipping re-derivation on resume means a resumed session — exactly the long-lived session most worth labelling — falls back to the shared static string. The user asked for a descriptive per-session name to be the normal experience.
+
+## Decision
+
+- `autoTitle` defaults **on** (`z.boolean().default(true)`, mirrored by `resolveTuiConfig`'s `?? true`). A deployment with an `llm` service and an agent provider/model gets a model-made pane title on every session without opting in; one without them keeps the static title, so default-on is inert where the call cannot run.
+- A **resumed** session re-derives the title on mount from its already-logged first `user/message`: `createTuiChat` scans `agent.session.events` for the first such event and feeds its text to the same one-shot `generateTitle`. The title is never persisted (the session header carries no title field), so it is always derived, never restored.
+- The one-shot latch is now simply `titleSettled = !resolved.autoTitle`. The prior pre-settle-on-resume clause is gone: on resume `generateTitle` runs once from the stored first message and then latches, so a message that arrives *after* the resume does not re-title. A fresh session has no stored `user/message` at mount, so the resume scan is a no-op and the live `session/event` listener titles the first message instead.
+- Everything else from the [auto-title Agent Note](2026-07-21-tui-auto-pane-title.md) stands unchanged: the OSC 0 `runtime.terminal.setTitle` path, the model-summary shape (two-to-five lowercase words, first non-empty line, 40-char cap), the fire-and-forget `ctx.llm.stream` call that never touches the session or transcript, the shutdown `AbortController`, and every failure fallback (empty reply, missing `llm`, missing provider/model, whitespace-only prompt).
+
+## Alternatives considered
+
+**Keep the feature off by default.** Rejected: this is a direct reversal of the [auto-title Agent Note](2026-07-21-tui-auto-pane-title.md)'s "default off" decision at the user's request. Off-by-default ships an inert feature; the descriptive name is only useful if it is the normal experience. The keyless-replay concern that motivated off-by-default is addressed by pinning `autoTitle: false` in the replay-backed snapshot scenarios rather than by suppressing it for every deployment.
+
+**Persist the derived title in the session header.** Rejected: the header has no title field and adding one would make a terminal label into session metadata — the boundary the [auto-title Agent Note](2026-07-21-tui-auto-pane-title.md) already drew against the log-backed session-title work. Re-deriving from the stored first message costs one tool-less call on resume and keeps the label a pure function of the conversation.
+
+**Re-derive on resume from the latest message instead of the first.** Rejected: the title summarises what the session is *about*, which its opening request captures; a mid-conversation message would make the pane label drift as the work moves on.
+
+## Consequences
+
+- A fresh session with a working `llm` now spends one extra tool-less model call by default (previously only when opted in); a resumed session spends one on mount. Deployments without an `llm` or provider/model are unaffected.
+- The replay-backed `examples/tui-agent/tests/tui.snapshot.ts` must opt **out**: it pins `autoTitle: false`, because a default-on title request is not among the recorded turns and `installLlmReplay` fails loud on an unrecorded request. The unit `packages/ui/tui/tests/tui.snapshot.ts` needs no opt-out — it mounts no `llm` service, so `generateTitle` short-circuits and the default flip is inert there. The interactive `examples/tui-agent/cordis.yml` and the scripted PTY fixture already set `autoTitle: true`, so the keyless smoke's OSC 0 assertion is unchanged.
+- `packages/ui/tui/tests/tui.spec.ts` pins the new defaults: the config-default test expects `autoTitle: true`; the disabled-path test now sets `autoTitle: false` explicitly; and the former "resumed session never fires" test is rewritten to assert re-derivation from the stored first message and that a later live message does not re-title. `docs/config-catalog.md` regenerates to "On by default".
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.zh.md
new file mode 100644
index 0000000000..8bd426c870
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-auto-title-default-on.zh.md
@@ -0,0 +1,33 @@
+# Agent Note: 自动标题默认开启,恢复时重新推导
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-21-tui-auto-title-default-on.md) | 中文
+
+> **已被取代**:见[标题归一 Agent Note](../simplification/2026-07-22-tui-titles-from-session-title-service.md)。TUI 本地的 `autoTitle` 生成已移除;标题来自日志承载的 session-title 服务,终端重命名消费 `session/title` 事件。
+
+## Problem
+
+[自动标题 Agent Note](2026-07-21-tui-auto-pane-title.md) 交付时 `autoTitle` 默认关闭,并且在恢复会话中因首条 `user/message` 已入日志而保留静态标题。实际使用中这两个选择都违背了该功能的初衷。让一个 tmux 窗格或终端标签页区别于下一个的,正是每会话各异的描述性窗格标题;默认关闭意味着产品交付了一个几乎无人开启的惰性功能,而恢复时不重新推导,则意味着恢复会话——恰恰是最值得标记的长命会话——退回到共用的静态字符串。用户要求把每会话的描述性名称做成常态体验。
+
+## Decision
+
+- `autoTitle` 默认**开启**(`z.boolean().default(true)`,`resolveTuiConfig` 以 `?? true` 与之对齐)。带有 `llm` 服务与 agent 提供方/模型的部署无需选择性开启即可在每个会话获得模型制作的窗格标题;不具备它们的部署保留静态标题,因此在调用无法运行处,默认开启是惰性的。
+- **恢复**会话在挂载时从其已入日志的首条 `user/message` 重新推导标题:`createTuiChat` 在 `agent.session.events` 中扫描首个此类事件,并把其文本喂给同一个一次性的 `generateTitle`。标题从不持久化(会话头不携带标题字段),因此它始终是推导得来,而非恢复而来。
+- 一次性门闩现在只是 `titleSettled = !resolved.autoTitle`。此前"恢复即预先结算"的分句已删除:恢复时 `generateTitle` 从已存储的首条消息运行一次随后上闩,因此恢复*之后*到达的消息不会再改标题。全新会话在挂载时没有已存储的 `user/message`,因此恢复扫描是空操作,改由实时的 `session/event` 监听器为首条消息命名。
+- [自动标题 Agent Note](2026-07-21-tui-auto-pane-title.md) 的其余一切保持不变:OSC 0 的 `runtime.terminal.setTitle` 路径、模型概括形态(两到五个小写单词、首个非空行、40 字符上限)、从不触碰会话或 transcript(文本记录)的发出后不等待其返回的 `ctx.llm.stream` 调用、关闭时的 `AbortController`,以及每一条失败兜底(空回复、缺 `llm`、缺提供方/模型、仅含空白的提示词)。
+
+## Alternatives considered
+
+**让该功能保持默认关闭。** 否决:这是应用户要求,对[自动标题 Agent Note](2026-07-21-tui-auto-pane-title.md)"默认关闭"决策的直接反转。默认关闭交付的是惰性功能;只有当描述性名称成为常态体验时它才有用。当初促成默认关闭的无密钥回放顾虑,改由在以回放支撑的快照场景中固定 `autoTitle: false` 来处理,而非为每个部署都压制该功能。
+
+**把推导出的标题持久化进会话头。** 否决:会话头没有标题字段,加一个会把终端标签变成会话元数据——正是[自动标题 Agent Note](2026-07-21-tui-auto-pane-title.md)已经对日志支撑的会话标题工作划出的边界。从已存储的首条消息重新推导,代价是恢复时一次无工具调用,并让标签保持为对话的纯函数。
+
+**恢复时从最新消息而非首条消息重新推导。** 否决:标题概括的是会话*关于什么*,而这由其开场请求捕获;一条对话中途的消息会让窗格标签随工作推进而漂移。
+
+## Consequences
+
+- 带可用 `llm` 的全新会话现在默认多花一次无工具的模型调用(此前只在选择性开启时才有);恢复会话在挂载时花掉一次。不具备 `llm` 或提供方/模型的部署不受影响。
+- 以回放支撑的 `examples/tui-agent/tests/tui.snapshot.ts` 必须选择**关闭**:它固定 `autoTitle: false`,因为默认开启的标题请求不在录制轮次之列,而 `installLlmReplay` 对未录制的请求会显式报错。单元 `packages/ui/tui/tests/tui.snapshot.ts` 无需选择关闭——它不挂载 `llm` 服务,因此 `generateTitle` 提前短路,默认值的翻转在那里是惰性的。交互式的 `examples/tui-agent/cordis.yml` 与脚本化 PTY fixture(测试前置数据)已设 `autoTitle: true`,因此无密钥冒烟测试的 OSC 0 断言保持不变。
+- `packages/ui/tui/tests/tui.spec.ts` 固定新的默认值:config 默认测试期望 `autoTitle: true`;关闭路径测试现在显式设 `autoTitle: false`;此前的"恢复会话从不触发"测试改写为断言从已存储首条消息重新推导,并断言之后的实时消息不会再改标题。`docs/config-catalog.md` 重新生成为"On by default"。
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml
index 684f23438c..20ee31b643 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml
@@ -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-21-tui-banner-brand-gradient.md: 41edf5d0bcf856bc7695af6bf651ff04c11adc01
-2026-07-21-tui-banner-brand-gradient.zh.md: 9253c001e8df2a4d0f79f69f32d65c11afd13e22
+2026-07-21-tui-banner-brand-gradient.md: 3516b0dcf9b6949721ec3e0d062f2d135da21083
+2026-07-21-tui-banner-brand-gradient.zh.md: 6fd0f140d474d26860eef77d64d5df550709d940
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.md b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.md
rename to .agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.md
index 41edf5d0bc..3516b0dcf9 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.md
@@ -1,6 +1,7 @@
# Agent Note: TUI banner brand gradient
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-banner-brand-gradient.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.zh.md
index 9253c001e8..6fd0f140d4 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-banner-brand-gradient.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-brand-gradient.zh.md
@@ -1,6 +1,7 @@
# Agent Note: TUI 启动横幅品牌渐变
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-banner-brand-gradient.md) | 中文
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.i18n.yaml
new file mode 100644
index 0000000000..a23f12cb22
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.i18n.yaml
@@ -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
+2026-07-21-tui-banner-sweep.md: 3351ff40e50fb3ef4de569cab0a33f311ea24a46
+2026-07-21-tui-banner-sweep.zh.md: e783ec20158c5a3be07b4afdf539463763d40948
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.md b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.md
new file mode 100644
index 0000000000..3351ff40e5
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.md
@@ -0,0 +1,36 @@
+# Agent Note: The banner sweeps in; the subtitle line is gone
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-21-tui-banner-sweep.zh.md)
+
+> **Superseded** by the [no-banner Agent Note](2026-07-21-tui-no-banner.md): the banner itself was removed, taking the sweep with it.
+
+## Problem
+
+The [startup-slogans Agent Note](2026-07-20-tui-startup-slogans.md) replaced the instructional welcome line with a random slogan bank revealed by a per-character typewriter. In use the quotes read as weird — random flavor text in a tool's header — and the animation was slow (40 ms/char over a full sentence) while animating only one line of a four-line banner. This note supersedes that decision's slogan half; the removal of the configured demo welcome and the animation-lifecycle groundwork stand.
+
+## Decision
+
+- The slogan bank, `pickStartupSlogan`, and the typewriter reveal are deleted. When `welcome` is unset the banner simply has **no subtitle line** — title and model/session detail only. The `welcome` config remains for deployments and fixtures that want a fixed subtitle, rendered frame-deterministically with no animation.
+- The startup animation is now the **whole banner**: `HeaderComponent` gains a `revealWidth` clip, and the header box wipes in left-to-right over ~24 frames at 15 ms (~360 ms total, ~60 fps), started after `ui.start()` succeeds and cleared through the same `detachListeners` path the typewriter used. `stopBannerReveal` also resets the clip so a disposed-mid-sweep header re-renders whole.
+- The PTY smoke's boot marker changes from the typewriter cursor (`▌`) to the banner's top-right corner (`╮`), which only renders once the sweep completes.
+
+## Alternatives considered
+
+**Keep the animation as-is and only change the copy.** Rejected: any fixed or rotating phrase re-read on every boot decays into wallpaper; the user's judgment was that the quotes themselves, not just their content, were wrong for the surface.
+
+**Animate per banner line (top-down) instead of a left-right sweep.** Rejected: with only four lines the animation would have four visible steps — closer to a flicker than a reveal; the horizontal sweep uses the full terminal width for a smooth motion at the same total duration.
+
+**Character-level clipping via `revealWidth` on styled text.** Adopted with `truncateToWidth` from pi-tui, the same ANSI-aware clipper the header already uses for width overflow, so the sweep cannot tear escape sequences.
+
+## Consequences
+
+- Boot output with `welcome` unset is again animation-dependent but no longer random: every boot sweeps the same banner. Configured welcomes (all snapshot/scripted fixtures, the Code Mode overlay) stay frame-deterministic and unchanged.
+- The `STARTUP_SLOGANS`/`pickStartupSlogan` exports are gone; no consumer outside the deleted tests referenced them.
+- The default banner is one line shorter (no subtitle), so PTY assertions anchored on banner geometry use the corner glyph rather than any subtitle text.
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` pins: the sweep completes to a full banner (both corners + title) and produced at least one clipped mid-sweep frame; a configured welcome renders verbatim with no clipped frames; the unset-welcome banner has no subtitle; and dispose clears the sweep's own interval handle. The PTY smoke boots on the `╮` completion marker across the tui-demo bin, the dsh CLI, and the personal-overlay scenarios. Verified live in tmux.
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.zh.md
new file mode 100644
index 0000000000..e783ec2015
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-banner-sweep.zh.md
@@ -0,0 +1,36 @@
+# Agent Note: 横幅整体扫入;副标题行移除
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-21-tui-banner-sweep.md) | 中文
+
+> **已被取代**:由[移除启动横幅 Agent Note](2026-07-21-tui-no-banner.md)取代:横幅本身已移除,扫入动画随之移除。
+
+## Problem
+
+[启动 slogan Agent Note](2026-07-20-tui-startup-slogans.md) 用随机 slogan 库加逐字打字机动画取代了说明书式的欢迎行。实际使用中这些引语显得怪异——工具头部出现随机的风味文案——而且动画很慢(每字符 40 ms,扫完一整句),却只动画四行横幅中的一行。本 note 取代该决定中 slogan 的那一半;移除示例配置中欢迎语的决定与动画生命周期的基础设施保持不变。
+
+## Decision
+
+- 删除 slogan 库、`pickStartupSlogan` 和打字机动画。`welcome` 未设置时横幅直接**没有副标题行**——只有标题和模型/会话详情。`welcome` 配置保留给想要固定副标题的部署与 fixture,无动画、逐帧确定地渲染。
+- 启动动画现在作用于**整个横幅**:`HeaderComponent` 增加 `revealWidth` 裁剪,头部盒子以约 24 帧、每帧 15 ms(总计约 360 ms、约 60 fps)从左到右扫入,在 `ui.start()` 成功后启动,经打字机动画用过的同一条 `detachListeners` 路径清除。`stopBannerReveal` 同时重置裁剪,因此扫入中途被 dispose 的头部会重新完整渲染。
+- PTY 冒烟测试的启动标记从打字机光标(`▌`)改为横幅右上角(`╮`),它只在扫入完成后才渲染。
+
+## Alternatives considered
+
+**保留动画原样、只改文案。** 否决:任何每次启动都被重读的固定或轮换语句都会退化成墙纸;用户的判断是引语本身——而不只是内容——对这个表面来说就是错的。
+
+**按横幅行逐行(自上而下)动画而非左右扫入。** 否决:只有四行时动画只有四个可见步骤——更像闪烁而不是展开;水平扫入用满终端宽度,在相同总时长内动作更平滑。
+
+**用 `revealWidth` 对带样式文本做字符级裁剪。** 采用 pi-tui 的 `truncateToWidth`——头部处理宽度溢出时已在使用的同一个 ANSI 感知裁剪器——因此扫入不可能撕裂转义序列。
+
+## Consequences
+
+- `welcome` 未设置时启动输出再次依赖动画但不再随机:每次启动扫入同一幅横幅。配置了欢迎语的场景(全部快照/脚本化 fixture、Code Mode overlay)保持逐帧确定且不变。
+- `STARTUP_SLOGANS`/`pickStartupSlogan` 导出移除;除被删除的测试外没有消费者引用它们。
+- 默认横幅少一行(无副标题),因此锚定横幅几何的 PTY 断言使用角落字形而非任何副标题文本。
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` 固定:扫入完成为完整横幅(两个角 + 标题)且产生了至少一个裁剪的中途帧;配置的欢迎语原文渲染且无裁剪帧;未设置欢迎语的横幅没有副标题;dispose 清除扫入自己的定时器句柄。PTY 冒烟测试在 tui-demo bin、dsh CLI 和个人 overlay 场景中以 `╮` 完成标记启动。已在 tmux 中实机验证。
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-borderless-banner.i18n.yaml
index 5d3ddbd972..972ead6485 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.i18n.yaml
@@ -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-21-tui-borderless-banner.md: 2fcb414c11f91df0914b17aa973e45746bbdfc67
-2026-07-21-tui-borderless-banner.zh.md: 8f80b21e6425bb38fff52529f1df8d262c34338f
+2026-07-21-tui-borderless-banner.md: 09fe713544134865687162c4624090b8e9aa3ebf
+2026-07-21-tui-borderless-banner.zh.md: b11c6d3c8cd327d7779617b4a7939ce55d8b765c
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.md b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.md
rename to .agents/notes/archived/feature/2026-07-21-tui-borderless-banner.md
index 2fcb414c11..09fe713544 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.md
@@ -1,6 +1,7 @@
# Agent Note: The banner returns, borderless
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-borderless-banner.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-borderless-banner.zh.md
index 8f80b21e64..b11c6d3c8c 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-borderless-banner.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-borderless-banner.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 横幅回归,无边框
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-borderless-banner.md) | 中文
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml
index d7cdbb3c3c..cbe0bd6811 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml
@@ -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-21-tui-footer-cache-hit-rate.md: aaee8ed31ff8f20370f490d3ce27c8705cda3e16
-2026-07-21-tui-footer-cache-hit-rate.zh.md: 67a7aa474d98878a5bc0bc0a76a8c2ccad004e9b
+2026-07-21-tui-footer-cache-hit-rate.md: 9e6ec734030088f063c049312ea345dc07303554
+2026-07-21-tui-footer-cache-hit-rate.zh.md: ec761df2cb9cad1dd922082f4ff1a8bb25cbdd69
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.md b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.md
rename to .agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.md
index aaee8ed31f..9e6ec73403 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.md
@@ -1,6 +1,7 @@
# Agent Note: TUI footer shows the session cache hit rate
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-footer-cache-hit-rate.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md
index 67a7aa474d..ec761df2cb 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-footer-cache-hit-rate.zh.md
@@ -1,6 +1,7 @@
# Agent Note: TUI 页脚展示会话缓存命中率
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-footer-cache-hit-rate.md) | 中文
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-no-banner.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.i18n.yaml
new file mode 100644
index 0000000000..2a2b8007b1
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.i18n.yaml
@@ -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
+2026-07-21-tui-no-banner.md: bfe78b2ba958193cc7bfe483b17faf5ae27eb4bb
+2026-07-21-tui-no-banner.zh.md: c92ad46d4176ec0444e5db766350c4e74c137645
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-no-banner.md b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.md
new file mode 100644
index 0000000000..bfe78b2ba9
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.md
@@ -0,0 +1,40 @@
+# Agent Note: No startup banner
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-21-tui-no-banner.zh.md)
+
+> **Superseded** by the [borderless-banner Agent Note](2026-07-21-tui-borderless-banner.md): the banner and its sweep return without the box. The model's footer home this note added stays.
+
+## Problem
+
+The TUI opened with a boxed product banner ("DEEPSEEK HARNESS" + model/session detail), most recently with a sweep-in animation ([banner sweep Agent Note](2026-07-21-tui-banner-sweep.md)). The user's verdict: remove it. A product title re-read on every boot is chrome, the box spends four rows before any content, and the identifying facts it carried (model, session) have better homes.
+
+## Decision
+
+- `HeaderComponent`, the sweep animation, and its lifecycle wiring are deleted. The TUI mounts straight into the transcript; startup renders nothing above the separator.
+- The model name moves into the footer status line's left segment (` ↑tokens ↓tokens`), so the session's driving model stays visible at all times, not just at boot. The session id is no longer displayed — it lives in the session log and `./.sessions` filenames, where `dsh --resume ` and the `/resume` selector retrieve it.
+- `welcome`, when configured, renders as the transcript's first line (a muted notice) inside `rebuildTranscript`, so palette swaps preserve it. Unset renders nothing. Fixtures keep their configured welcomes; the PTY smoke's boot marker becomes the footer's model name, the only mounted-TUI text guaranteed to render regardless of cwd length.
+
+This supersedes the [banner sweep Agent Note](2026-07-21-tui-banner-sweep.md) entirely: both the sweep and the banner it animated are gone.
+
+## Alternatives considered
+
+**Keep a one-line header (no box).** Rejected: the only load-bearing fact was the model name, and the footer already aggregates session status; a dedicated header row for one fact is the same chrome, smaller.
+
+**Show the session id in the footer too.** Rejected: a 36-char UUID dominates the 100-column footer and clips the status segment; it identifies the session for resume, which is a log/filesystem concern, not a glanceable one.
+
+**Print the welcome outside the transcript (above the separator).** Rejected: any fixed region above the transcript is a banner again; as a transcript line it scrolls away naturally and survives rebuilds through the same path as every other transcript element.
+
+## Consequences
+
+- Startup output is fully deterministic again — no animation frames at all; the interval-lifecycle machinery from the two animation iterations is gone.
+- All 26 pi-tui terminal snapshots re-recorded (`test:snapshot:refresh`): banner rows gone, footer rows gain the model prefix.
+- Anything that anchored on banner text (`DEEPSEEK`, box corners) re-anchors on the footer model name; `main-session-` no longer appears in boot output.
+- `/clear` now wipes the welcome line too: it is an ordinary transcript line, and `/clear` empties the transcript (the old banner survived `/clear` only by sitting outside it).
+- The footer's left segment is wider; on narrow terminals the right status segment clips earlier.
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` pins: no box corners/product title and an empty transcript when `welcome` is unset, with the model in the footer; a configured welcome as the first transcript line without a banner; and the welcome surviving a palette-swap transcript rebuild. The PTY smoke boots on the footer model name and asserts `DEEPSEEK HARNESS` is absent. Snapshots verify the full frames.
diff --git a/.agents/notes/archived/feature/2026-07-21-tui-no-banner.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.zh.md
new file mode 100644
index 0000000000..c92ad46d41
--- /dev/null
+++ b/.agents/notes/archived/feature/2026-07-21-tui-no-banner.zh.md
@@ -0,0 +1,40 @@
+# Agent Note: 移除启动横幅
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-21-tui-no-banner.md) | 中文
+
+> **已被取代**,见[无边框横幅 Agent Note](2026-07-21-tui-borderless-banner.md):横幅及其扫入动画回归,只是去掉了盒子。本 note 为模型设立的页脚归宿得以保留。
+
+## Problem
+
+TUI 启动时展示一个带框的产品横幅("DEEPSEEK HARNESS" + 模型/会话详情),最近一版还带扫入动画([横幅扫入 Agent Note](2026-07-21-tui-banner-sweep.md))。用户的裁决:删掉它。每次启动都被重读的产品标题是装饰,盒子在任何内容之前先占掉四行,而它承载的识别信息(模型、会话)有更好的去处。
+
+## Decision
+
+- 删除 `HeaderComponent`、扫入动画及其生命周期接线。TUI 直接挂载进 transcript;启动时分隔线之上不渲染任何东西。
+- 模型名移入页脚状态行的左段(` ↑tokens ↓tokens`),会话使用的模型因此始终可见,而不只是启动时。会话 id 不再显示——它存在于会话日志和 `./.sessions` 文件名中,`dsh --resume ` 和 `/resume` 选择器会从中获取该 id。
+- 配置了 `welcome` 时,它作为 transcript 的第一行(一条弱化的通知)在 `rebuildTranscript` 内渲染,因此调色板切换会保留它。未设置则什么也不渲染。fixture 保留各自配置的欢迎语;PTY 冒烟测试的启动标记改为页脚的模型名——无论 cwd 多长都保证渲染的唯一挂载后文本。
+
+本 note 完全取代[横幅扫入 Agent Note](2026-07-21-tui-banner-sweep.md):扫入动画和它所动画的横幅都已移除。
+
+## Alternatives considered
+
+**保留单行头部(去掉盒子)。** 否决:唯一有承载价值的信息是模型名,而页脚已经聚合会话状态;为一条信息保留专用头部行仍是同一种装饰,只是小一点。
+
+**把会话 id 也放进页脚。** 否决:36 字符的 UUID 会占满 100 列页脚并裁掉状态段;它的用途是恢复会话的标识,属于日志/文件系统关注点,不是需要一瞥可见的信息。
+
+**把欢迎语渲染在 transcript 之外(分隔线上方)。** 否决:transcript 上方任何固定区域都会再次变成横幅;作为 transcript 行它自然滚走,并通过与其他 transcript 元素相同的路径在重建后保留。
+
+## Consequences
+
+- 启动输出再次完全确定——没有任何动画帧;两轮动画迭代留下的定时器生命周期机制全部移除。
+- 全部 26 个 pi-tui 终端快照重新录制(`test:snapshot:refresh`):横幅行消失,页脚行增加模型前缀。
+- 锚定横幅文本(`DEEPSEEK`、盒子角)的内容改为锚定页脚模型名;启动输出中不再出现 `main-session-`。
+- `/clear` 现在也会清掉欢迎行:它是普通的 transcript 行,而 `/clear` 清空 transcript(旧横幅能在 `/clear` 后存活只因为它在 transcript 之外)。
+- 页脚左段变宽;窄终端上右侧状态段更早被裁剪。
+
+## Testing
+
+`packages/ui/tui/tests/tui.spec.ts` 固定:`welcome` 未设置时无盒子角/产品标题、transcript 为空、模型在页脚;配置的欢迎语作为 transcript 第一行且无横幅;欢迎语在调色板切换的 transcript 重建后保留。PTY 冒烟测试以页脚模型名为启动标记并断言 `DEEPSEEK HARNESS` 不出现。快照验证完整帧。
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.i18n.yaml
similarity index 65%
rename from .agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-reload-command.i18n.yaml
index 05f9ae37b6..f1e467433d 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.i18n.yaml
@@ -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-21-tui-reload-command.md: 89bf2f7bb482d7f3889136c1a6ac9918ba0c4919
-2026-07-21-tui-reload-command.zh.md: cfea10690af49f2cf484938a3f9f12d954766a71
+2026-07-21-tui-reload-command.md: e491e8f4510128b7fda03d41bc1d13e4dfdc9f5e
+2026-07-21-tui-reload-command.zh.md: ed96c689fe4ba700f5b6836d7b7727e0252737a2
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-reload-command.md
rename to .agents/notes/archived/feature/2026-07-21-tui-reload-command.md
index 89bf2f7bb4..e491e8f451 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.md
@@ -1,6 +1,7 @@
# Agent Note: The /reload command re-reads loader configs on demand
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-reload-command.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-reload-command.zh.md
index cfea10690a..ed96c689fe 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-reload-command.zh.md
@@ -1,6 +1,7 @@
# Agent Note: /reload 命令按需重读 loader 配置
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-reload-command.md) | 中文
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml
index 4b47c4a3b2..5231dfd7e7 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.i18n.yaml
@@ -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-21-tui-steering-queue-badge.md: b1d111187bb42dfaaa6f3618c1d31f25b7e4b455
-2026-07-21-tui-steering-queue-badge.zh.md: 6ddb5351e9b8db5bd4e73f12768d37667483d874
+2026-07-21-tui-steering-queue-badge.md: 37e8a11c0dd30a0674107ff33d51a31d92385ada
+2026-07-21-tui-steering-queue-badge.zh.md: 6ef412b26a32c3aa359215404ca81635fca776c1
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.md b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.md
rename to .agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.md
index b1d111187b..6419f67a1e 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.md
@@ -1,6 +1,7 @@
# Agent Note: TUI status line badges queued steering messages
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-steering-queue-badge.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.zh.md
index 6ddb5351e9..5bb965175b 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-steering-queue-badge.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-steering-queue-badge.zh.md
@@ -1,6 +1,7 @@
# Agent Note: TUI 状态行标示排队中的 steering 消息
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-steering-queue-badge.md) | 中文
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.i18n.yaml
index 8cac245f00..a6fc0d00d6 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.i18n.yaml
@@ -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-21-tui-verbose-status-line.md: 71584ee91a911cc8652512ec26b00dae8c818f36
-2026-07-21-tui-verbose-status-line.zh.md: bda3c5e8394f7707916c6fc76045b1a6f38fa95b
+2026-07-21-tui-verbose-status-line.md: 9ed396b0dbf4325d6fdd2a4f20f8d81b5b408171
+2026-07-21-tui-verbose-status-line.zh.md: 14c9b36646e226715156ba8db8f62cd6090ce9a0
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.md b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.md
rename to .agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.md
index 71584ee91a..9ed396b0db 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.md
@@ -1,6 +1,7 @@
# Agent Note: The running status line shows the turn phase and elapsed time
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-verbose-status-line.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.zh.md
rename to .agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.zh.md
index bda3c5e839..14c9b36646 100644
--- a/.agents/notes/implemented/feature/2026-07-21-tui-verbose-status-line.zh.md
+++ b/.agents/notes/archived/feature/2026-07-21-tui-verbose-status-line.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 运行状态行展示轮次阶段与已用时长
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-verbose-status-line.md) | 中文
diff --git a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.i18n.yaml b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.i18n.yaml
similarity index 65%
rename from .agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-23-trajectory-step-cell.i18n.yaml
index 1702c90c43..cc1d5c06fc 100644
--- a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.i18n.yaml
@@ -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-23-trajectory-step-cell.md: 414c3aac856fb5e60f0e4cf42f8e7b410cdf3413
-2026-07-23-trajectory-step-cell.zh.md: aa76b422f165ebf6918b3781fdfe38797a34ba51
+2026-07-23-trajectory-step-cell.md: 871f02d72b74bb6dbeb782fde3b639b237cf71e1
+2026-07-23-trajectory-step-cell.zh.md: 3ebb4becd569242bfdea222df6d042a4c00ad096
diff --git a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.md b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.md
rename to .agents/notes/archived/feature/2026-07-23-trajectory-step-cell.md
index 414c3aac85..871f02d72b 100644
--- a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.md
+++ b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.md
@@ -1,6 +1,7 @@
# Agent Note: Trajectory step cell and turn list chrome
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-23-trajectory-step-cell.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.zh.md b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.zh.md
rename to .agents/notes/archived/feature/2026-07-23-trajectory-step-cell.zh.md
index aa76b422f1..3ebb4becd5 100644
--- a/.agents/notes/implemented/feature/2026-07-23-trajectory-step-cell.zh.md
+++ b/.agents/notes/archived/feature/2026-07-23-trajectory-step-cell.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Trajectory 步骤单元格与轮次列表 chrome
Status: implemented
+Archived: 2026-07-26
[English](2026-07-23-trajectory-step-cell.md) | 中文
diff --git a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml
rename to .agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml
index 4b7354a322..c545c8d7ee 100644
--- a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml
+++ b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml
@@ -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-24-new-session-clears-to-empty-state.md: 1605f44a05d0f59b61fe95cb5b03a0f9f5c3d4ab
-2026-07-24-new-session-clears-to-empty-state.zh.md: 1f78d99babc33d30ee1300bfa6bf048a78e7132e
+2026-07-24-new-session-clears-to-empty-state.md: c9b57fec3aa093062847aacba4d01b877edf4bd5
+2026-07-24-new-session-clears-to-empty-state.zh.md: 82a4f8b1e933d6aa3531556e8be4839b204ea562
diff --git a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.md b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.md
rename to .agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.md
index 1605f44a05..c9b57fec3a 100644
--- a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.md
+++ b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.md
@@ -1,6 +1,7 @@
# Agent Note: New Session clears onto the empty-state launch
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-24-new-session-clears-to-empty-state.zh.md)
diff --git a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.zh.md b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.zh.md
similarity index 99%
rename from .agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.zh.md
rename to .agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.zh.md
index 1f78d99bab..82a4f8b1e9 100644
--- a/.agents/notes/implemented/feature/2026-07-24-new-session-clears-to-empty-state.zh.md
+++ b/.agents/notes/archived/feature/2026-07-24-new-session-clears-to-empty-state.zh.md
@@ -1,6 +1,7 @@
# Agent Note: New Session clears onto the empty-state launch
Status: implemented
+Archived: 2026-07-26
[English](2026-07-24-new-session-clears-to-empty-state.md) | 中文
diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json
new file mode 100644
index 0000000000..0ca8c4d9bd
--- /dev/null
+++ b/.agents/notes/archived/manifest.json
@@ -0,0 +1,182 @@
+{
+ "version": 1,
+ "files": {
+ "architecture/2026-06-11-custom-schema-dsl.i18n.yaml": "sha256:f05d94c11762e506183044ddb1494a2b200ca16999ef3cef51c7b3a324eec945",
+ "architecture/2026-06-11-custom-schema-dsl.md": "sha256:71286f2676f8b47d0bd56c6cc43cf8102946e6d195942860a5810b9c534d2b2b",
+ "architecture/2026-06-11-custom-schema-dsl.zh.md": "sha256:999ff59565a4459184a644c4de6ef98c1bb1a174712e529f5c8417342abdd437",
+ "architecture/2026-06-20-extract-example-app-packages.i18n.yaml": "sha256:d99b612cc1051c86d883d74737c72e921735e7a28e0b5e6351d3870c664bdcc4",
+ "architecture/2026-06-20-extract-example-app-packages.md": "sha256:9c7aca3a1e9a1ccc3729961663bc649b90076e671cae23e3db8203305983ccce",
+ "architecture/2026-06-20-extract-example-app-packages.zh.md": "sha256:19bd50232d9f25d35aa3f9dc72d9af0df457dd0eaca8b982d5aa625e5b95bcff",
+ "architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml": "sha256:636a822f3240e0401cdddad6a21f3454af1c1593fff14d4c9ce6613495f7dac1",
+ "architecture/2026-07-03-filesystem-directory-listing-seam.md": "sha256:809a3c79f4d602607e8fa93aafd1ebccf4fae50c31f1fb1b1e386bb7ad089153",
+ "architecture/2026-07-03-filesystem-directory-listing-seam.zh.md": "sha256:13735cd4c9fe990e6df3b028d6da01da89e94fde454dc0e968e517151cbd4281",
+ "architecture/2026-07-05-windows-fs-permissions.i18n.yaml": "sha256:7e61ee9bbd9de4bf3285a6f250d9625bd062e5fb90279dbffd64c820f1f7fe6b",
+ "architecture/2026-07-05-windows-fs-permissions.md": "sha256:03734da511eae3b0736f7cad73d9da76ae2f69f9d5ed09089b0121ccb135a861",
+ "architecture/2026-07-05-windows-fs-permissions.zh.md": "sha256:454848057ea905fe76c88d17264e71e71fb685f08f82088de6976878372865c3",
+ "architecture/2026-07-23-unified-session-query-service.i18n.yaml": "sha256:e8733b6543d9602ec206a087d9e89815f041f60fb57e93bee80e1309b9f03067",
+ "architecture/2026-07-23-unified-session-query-service.md": "sha256:28d003686f29ec5e072e51e73da353575bcdcba5af20fefdfad88340e1ddd32c",
+ "architecture/2026-07-23-unified-session-query-service.zh.md": "sha256:cfbe6525bc3b072fbc6db6bdca7a4d8cb4fc5507b1655bebc6af0589ed29ed31",
+ "architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml": "sha256:cf99eda0e58b49630d5f95792459d7095666fafbef61f614165d5cdd031b7118",
+ "architecture/2026-07-24-dsh-commander-argument-adapter.md": "sha256:705654c8a43bcd199f72c21a77d24ca8bfa02447aff1c7f3e4e820be61dcd562",
+ "architecture/2026-07-24-dsh-commander-argument-adapter.zh.md": "sha256:3844f02d7659d18caf5d39e1131ed775c789cbf92dc44b4a446c7d6468aa5d00",
+ "bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml": "sha256:1035dae11d049d32ab09fd7d4f950eceae44bf46ba498b3cfaf3c75102b9fb64",
+ "bug-fix/2026-07-20-code-mode-result-card-completeness.md": "sha256:6ca2c9d4df98be18813ef38b7462db880900b5bcd6944fbcd1b8f2258006b93e",
+ "bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md": "sha256:ed85fa7f935e5f525d566bc37a92014614983e649c75de9a9f244939097a7991",
+ "bug-fix/2026-07-22-collapsed-sidebar-control-rail.i18n.yaml": "sha256:98de4a1ae016608b88010d413a204c2d33695f4b1d7217e5d1b705be09c1b669",
+ "bug-fix/2026-07-22-collapsed-sidebar-control-rail.md": "sha256:b58620a3cf203507a5d651b90554bb7897e9d271613dc7c32d0f3bec992475bb",
+ "bug-fix/2026-07-22-collapsed-sidebar-control-rail.zh.md": "sha256:f36ef24f26ead60b01169c8e4a2a01b396c3f4284f14979e4d52b47c9589075c",
+ "bug-fix/2026-07-23-demo-web-builds-client-bundles.i18n.yaml": "sha256:f657e2166a05d6164c1ca65560bdd168ed969f7354217d8ec7a00274fd6c4307",
+ "bug-fix/2026-07-23-demo-web-builds-client-bundles.md": "sha256:a9d8dfcd153b1d10479e9d42848f07adf89398cb685e1a09afafcecff14e36d9",
+ "bug-fix/2026-07-23-demo-web-builds-client-bundles.zh.md": "sha256:dd06828964980798f343b8aafdeded2580b5b2e4c794305221fe73dab0a7eba2",
+ "bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml": "sha256:fd926967311f30ea4a222e88b845f95d74af75d1e94b24ebef59186593b9ca78",
+ "bug-fix/2026-07-23-thinking-row-disclosure-target.md": "sha256:92815c170972b1b91c3d75dd0c846c070805ec1e99ce368b6aae37b048e19869",
+ "bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md": "sha256:0e09f5f5e14d74214e5157ceb5859c866bab6de701c47e2ce5c450866d75aecf",
+ "bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml": "sha256:c623947c4fa00e6d4b51792c7972ba09582bbcb7605beb373725c0dd666f2c81",
+ "bug-fix/2026-07-26-intent-draft-same-tick-echo.md": "sha256:fa8b1417b2cdd3deecbf8e55bdddd73dd3a8c6e3486fd399b0b8bdf317e56373",
+ "bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md": "sha256:00ce72552dbaa11562fbc541343a5d33f9449edabbe6dd354eb879a7d4d530f8",
+ "feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918",
+ "feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b",
+ "feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69",
+ "feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml": "sha256:79592f96bb25713d01865f37972a6919b2bfb3b66368df0f275bbd59d09ebcf6",
+ "feature/2026-06-18-acp-terminal-and-tool-rendering.md": "sha256:946d0c580705ef2e7c7ac1897ada074e72f2ec4209c1e7531b6eccf116e9aecc",
+ "feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md": "sha256:fd815817925a038f79b52b6fab43abdb2d655db3ec07974ce1320ea3674f2afc",
+ "feature/2026-06-30-subagent-observe-enrich.i18n.yaml": "sha256:08c2478ba394429f46c1e87a9f055e88704a9000e5d250d5600c0c85124cb17f",
+ "feature/2026-06-30-subagent-observe-enrich.md": "sha256:0630975c3e325975a932f58a65a178b79c624dc56ebd29e288e96f5a189cfbfa",
+ "feature/2026-06-30-subagent-observe-enrich.zh.md": "sha256:b9fbb44a7d81f4063faf3baaf97c382a2f5106be533feb4de792ee57b766c1a4",
+ "feature/2026-07-07-plan-mode.i18n.yaml": "sha256:c59b6a6c218d741cdef8edf625f1d015e409a39411fa64e65200fdebb1c49394",
+ "feature/2026-07-07-plan-mode.md": "sha256:7bf1bb8e826edf68f0ec919dfd4f66955b935b46b4400d7de85fac3e4663edbc",
+ "feature/2026-07-07-plan-mode.zh.md": "sha256:5b08cbcd8023f26744e481386177dd0e82423e8b0032829d0dbc22a92cced0cd",
+ "feature/2026-07-14-time-context-plugin.i18n.yaml": "sha256:670c093817c77e093562e02f43984d42ed44ebcced7c91d09366839e412d05e1",
+ "feature/2026-07-14-time-context-plugin.md": "sha256:618b121da38a8b610bcadaecf121ca823b2c8c13598c012b350c214b82fd238f",
+ "feature/2026-07-14-time-context-plugin.zh.md": "sha256:1e9eee8ba427a6f2ee08c79e2fcb33c0948e67a80758fdf9f8c9f7dff9aea361",
+ "feature/2026-07-20-tui-startup-slogans.i18n.yaml": "sha256:265d1fd79dae6c785201c81ffe2de3baa9fe9e3b6c0f84aac79c90f4040ced15",
+ "feature/2026-07-20-tui-startup-slogans.md": "sha256:aaaab4b419d35ce24317b7730f15af0029878bf3d17c6f184b05138c2cd44930",
+ "feature/2026-07-20-tui-startup-slogans.zh.md": "sha256:01fba568cd92e9c54857f6dba1a3a5a6a4d0e906f36915d7e64682e67d456708",
+ "feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml": "sha256:22efaf3237425fecbac1b40a444454e0fc244a3c85c2f6a14535de22ea777719",
+ "feature/2026-07-21-dsh-system-prompt-source-path.md": "sha256:5fa554932c62a8bbd5a619581710d7f8b6b65d79ec1e340129cda96d279c5ae3",
+ "feature/2026-07-21-dsh-system-prompt-source-path.zh.md": "sha256:995cd593074881c72510a6af3ba80108bbf986d49508cce9f698c2fcb493fd23",
+ "feature/2026-07-21-tui-auto-pane-title.i18n.yaml": "sha256:0e9ad2adf0810811b2981435e761fd57b1e2cd89e5aa084522150c41e3cf3876",
+ "feature/2026-07-21-tui-auto-pane-title.md": "sha256:0dd4572eacefc5fba508df8d1ff3f28b55e10b4b178e1f9773db3434a337c527",
+ "feature/2026-07-21-tui-auto-pane-title.zh.md": "sha256:3ac195bf3fc63d40c2d36e6a38d6a41c73d8b21daa5e668412fd28b8a2630aa1",
+ "feature/2026-07-21-tui-auto-title-default-on.i18n.yaml": "sha256:46815dbc1cbacfb11cb9f18f8df0f54dc1c4e5f9c051591dd3af97ad338b6c47",
+ "feature/2026-07-21-tui-auto-title-default-on.md": "sha256:0caad9db58e031f9f667e93a3f53ebcf3c1f0700efc6decd51acafa3657372ac",
+ "feature/2026-07-21-tui-auto-title-default-on.zh.md": "sha256:4e85a028e47caaa3f1cfcb01e143616c2a5a4916662c5f49f2b070386745d2e2",
+ "feature/2026-07-21-tui-banner-brand-gradient.i18n.yaml": "sha256:adc228a5e6797096002619ba5bd8c47d49f2d5e98e40dd168ae5e07bc57bc460",
+ "feature/2026-07-21-tui-banner-brand-gradient.md": "sha256:9b14ab1ae88eab598cd0f8d2d1cfbe53cec89a5374e3e3c765b487c91579e1eb",
+ "feature/2026-07-21-tui-banner-brand-gradient.zh.md": "sha256:111dfde012857af10b2f7b9b8a9b9f783522e4ad14dad3ff5e25706b5bbffcbe",
+ "feature/2026-07-21-tui-banner-sweep.i18n.yaml": "sha256:4cf71f8a8436bd9151be10aa7ae71ed1656206b49f056ae173e2b3e88bf9efea",
+ "feature/2026-07-21-tui-banner-sweep.md": "sha256:87654f4b1960ab4f7455e993de298d2ee75f63ceb66fccea6136238ff0134b7a",
+ "feature/2026-07-21-tui-banner-sweep.zh.md": "sha256:91b98a38c1111a561072d749a985c023ecaef0d749d6c1aaa320bfe0034660bd",
+ "feature/2026-07-21-tui-borderless-banner.i18n.yaml": "sha256:9e80de590085e6e02f0830fedb149289387bb83eaa073c9f99a4eb7af1afba80",
+ "feature/2026-07-21-tui-borderless-banner.md": "sha256:e3237b4de432cd97262a4baf1f64fee6bea48c3180a2e773575f603ed008d44c",
+ "feature/2026-07-21-tui-borderless-banner.zh.md": "sha256:6c65cd654a1aed704d80b5882aba8ae0a2c1090709d672189847f5d0a6f58122",
+ "feature/2026-07-21-tui-footer-cache-hit-rate.i18n.yaml": "sha256:56898ebb26741c83bb1c5de4c6e64bd3ca06b5e3b90ab79107823f19353596eb",
+ "feature/2026-07-21-tui-footer-cache-hit-rate.md": "sha256:c66a1485d21fe6a4b975ffeed56c021c0d9556488bfadc4fb32648b3948c1fea",
+ "feature/2026-07-21-tui-footer-cache-hit-rate.zh.md": "sha256:6fc2efe5817e83a9deb057a2de9b31b4c786700ebf40d38369abb5cefae231d0",
+ "feature/2026-07-21-tui-no-banner.i18n.yaml": "sha256:26d98ba4a5c04504d649ada26d666dec0115026b9724af160483d0fcfa535903",
+ "feature/2026-07-21-tui-no-banner.md": "sha256:a75c8535ac348199c9de0c2a6e266b3b4c21fe188f86ad1388a5241ad03c5a73",
+ "feature/2026-07-21-tui-no-banner.zh.md": "sha256:05659d5e54a10fbace886f4407ec7a457cbab85139af03e3ae59612fa7611988",
+ "feature/2026-07-21-tui-reload-command.i18n.yaml": "sha256:9be416ccd681aed0781fdfd2c44c4821c1e45f2a0deccb1f2b47d46163bde488",
+ "feature/2026-07-21-tui-reload-command.md": "sha256:b8616457822ae87c90062308bc8c0d2badd5f368092ec65847d0d9520b1ac372",
+ "feature/2026-07-21-tui-reload-command.zh.md": "sha256:c24bfcb0df13977a9c11c4d0fe433169e535b5f764995b668430dbb14a8e6b33",
+ "feature/2026-07-21-tui-steering-queue-badge.i18n.yaml": "sha256:a029da558a6e14e1f13269960b98273ca9af0141579967acfbc19b656775f4a5",
+ "feature/2026-07-21-tui-steering-queue-badge.md": "sha256:9aabd68c8910fdc7e7b05674492ddb8dc9285dd691fe554adcb84026fb846cc8",
+ "feature/2026-07-21-tui-steering-queue-badge.zh.md": "sha256:919fd737866c3700f945751628071dab89eabdbf8f809deab93b9e6fbe2c8c59",
+ "feature/2026-07-21-tui-verbose-status-line.i18n.yaml": "sha256:4371b9a46d713d4180aa5d0b1ecde1ff3cae948380a8f56474c895e6113d7824",
+ "feature/2026-07-21-tui-verbose-status-line.md": "sha256:9dcba19ee725b1593e9413a1da5398c205a258aff2e384acd406bb618e86c7f0",
+ "feature/2026-07-21-tui-verbose-status-line.zh.md": "sha256:203c2abac99cedf7afa2540c925367ba66f00b61b926d1cc86472a603ad2bb07",
+ "feature/2026-07-23-trajectory-step-cell.i18n.yaml": "sha256:fe2e935a0affdef877902a40d9861ef5f55b30f40650469f6a52a4d45a92793f",
+ "feature/2026-07-23-trajectory-step-cell.md": "sha256:185e3b87174cb6d2f2d2271fd2a74b1517d03e8570be602570d027bf6002d106",
+ "feature/2026-07-23-trajectory-step-cell.zh.md": "sha256:51f46be43d2f5c4f78a05ed9aeec92d1f33ac988f45cf24d35528e9c43828ef3",
+ "feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml": "sha256:978638cbf18bc6dce9fea0817654f41cc307f99004a637b85a63ae2208fe9095",
+ "feature/2026-07-24-new-session-clears-to-empty-state.md": "sha256:b6b71d3883a167056070713e3dffb5046de953bdd218074d17c88e7690e03d83",
+ "feature/2026-07-24-new-session-clears-to-empty-state.zh.md": "sha256:82a80b48337487029acd05a0137d268f0850f46801fa44a0e62733cacd00d5e9",
+ "process/2026-06-11-doc-sync-enforcement.i18n.yaml": "sha256:33b6d5874427bd7a2bd82e7e2f4f482b12448b2464aef15a9c57975edb48554d",
+ "process/2026-06-11-doc-sync-enforcement.md": "sha256:aa2fe83d519fc30d48dff19e596e83c8922aacc9e063e14fe2cc35b769b9100e",
+ "process/2026-06-11-doc-sync-enforcement.zh.md": "sha256:698017bd35f030fdea3eac51df9e43138c48140f504739d687b7251d13fced2b",
+ "process/2026-07-03-documentation-graph-atlas.i18n.yaml": "sha256:b1e1ed4b7865d87f939dbf8c94c0ea1069fdf7af6fa68f695e6c9d6eccbeb123",
+ "process/2026-07-03-documentation-graph-atlas.md": "sha256:b62e92bb12123bfa4c4dac806f584aabb6b60af4c5a6a4ab88f84bb9153e766d",
+ "process/2026-07-03-documentation-graph-atlas.zh.md": "sha256:3485ede4a5e695643bcf9e744a62f8914cff788ae35717dac5eb6bf77e0d65cf",
+ "process/2026-07-06-parallel-github-ci-gates.i18n.yaml": "sha256:0f6ece268d9a51bc20cb8eb929f26d8838761603a64eb08dc24521198f10da36",
+ "process/2026-07-06-parallel-github-ci-gates.md": "sha256:6249bd7396ae7f2d0dc671879ce21cefab33a47ace6ef17a25a70e8650b815af",
+ "process/2026-07-06-parallel-github-ci-gates.zh.md": "sha256:cf7edb9bcf97ab1d4e452330c0df0b127a664509ec3f11597ace3eabeb663a5b",
+ "process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml": "sha256:1dbe70d21dd510bec4f2f56ae39d0fdc7290d5648280ca0b67224cd23b3a02a8",
+ "process/2026-07-21-doc-sync-through-gate-scheduler.md": "sha256:b3eb3f2395ad8f1b77f44aa3fdac79856e5d0b6b4873560d0cc87b63de2ea2e0",
+ "process/2026-07-21-doc-sync-through-gate-scheduler.zh.md": "sha256:e262e02c3d08057b83b0d29281eadb92723f0fe5b3f54424528f47be137bc760",
+ "process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml": "sha256:677aa91c3ccd9eda8a658b10410699ac608d3891d2fa32529898a3432fb56660",
+ "process/2026-07-22-installer-in-repo-skip-clone.md": "sha256:4e30c0dd5429db33638a91a30afdd3386ac1a4705bd259a5eef325b5f86cced8",
+ "process/2026-07-22-installer-in-repo-skip-clone.zh.md": "sha256:1d93c99f5a8d56077e766242c33245621626be55cf481d01c83bb5cbbe9a74d7",
+ "process/2026-07-23-browser-demo-gif-recording.i18n.yaml": "sha256:808ccdda39e540645b440e40a2124baed737b98636265d8f6d8cf036a70f0d50",
+ "process/2026-07-23-browser-demo-gif-recording.md": "sha256:4d3a3dc829c75b66f4f57a6a763b4b9562ce10efb90a19308142f598c5ea8524",
+ "process/2026-07-23-browser-demo-gif-recording.zh.md": "sha256:409e5d31ea87f35c5227fcd1bd105167f580a8a24d29a223ce390b29eb31639a",
+ "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml": "sha256:ad3d1263cb0051b885173bf064de62065e2c646ccaae2d7250723da3b4eab90c",
+ "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md": "sha256:8fb061d51c8c23b47d2367814bab3623c6d5b972f38d207a273caa9030b579bd",
+ "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md": "sha256:2ffeaca91f82844a5616d6dcce6b4af514bb8a7c46f78e47f668b204ac6edc04",
+ "simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml": "sha256:f01960a5e8fab5e4f284f35ced6b84400aab257b243805db797a9c4a00ff525e",
+ "simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md": "sha256:0020f6b80e8bea5a8441b5bf7385a9bcfacbe14485f0e77de5d8b4fe3d2f69d0",
+ "simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md": "sha256:21647760eb06e57f8a38b35196233c634b8284a178b99b14f504a791758e9088",
+ "simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml": "sha256:0594648368c942f429599ac0ff5977d62c89c70a31d4bdbac61b0a30fe15ef3b",
+ "simplification/2026-06-20-prune-dead-seam-methods.md": "sha256:fd3b0eaf600e178eeeef0c6cedc71f2382878733c557f1915d3b47f74a1d0d6d",
+ "simplification/2026-06-20-prune-dead-seam-methods.zh.md": "sha256:4f5feef9331e3a1346bc362ffb39cfa373db2c609041bfeee6d88a10392464b1",
+ "simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml": "sha256:e4c992a27ae0e37e5ef663c2cddf55eefe20387fd6103bebf655834d8e75e9db",
+ "simplification/2026-07-04-drop-inert-request-knobs.md": "sha256:8735c2b868a85b13235e0491a0fa7b9570dd090eef5170324fc5e93782687b67",
+ "simplification/2026-07-04-drop-inert-request-knobs.zh.md": "sha256:78b243f5d580f2a6fbbdb7d26574295d6ed74feb8d9bba34bbcdf4aa87624b5c",
+ "simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml": "sha256:30cbf5f573ad9df5140a2bc57181c6465dc3cb0717d192a8bbbb5b1c68a56f29",
+ "simplification/2026-07-04-drop-unconsumed-web-observation-surface.md": "sha256:2d4d4ad2d0b72c602a20af6082392c22c889e4cf455614177fdc9e892069948e",
+ "simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md": "sha256:012b4fb2a346e01d5d88a53913a790df713650907ad7987b744bba456be36bbf",
+ "simplification/2026-07-04-fold-stdio-ui-helper.i18n.yaml": "sha256:e0e476ec897d29a8688b201746a1db47033486b560c07244b37d91058d39e07a",
+ "simplification/2026-07-04-fold-stdio-ui-helper.md": "sha256:d6cb5b0cbada51a19e4c2b1aab8dc738a30760ec8e7348e090ad44c8b80e3955",
+ "simplification/2026-07-04-fold-stdio-ui-helper.zh.md": "sha256:57618fe935bf3310f1d91ab7d1940d8dff7b21919dbe82f1fa0137ae5f7c2189",
+ "simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml": "sha256:338c2290ae2cdcbeb758e996970e7f9dc8c36261f076302e358d70508604bac6",
+ "simplification/2026-07-04-prune-producerless-vocabulary-variants.md": "sha256:87a269ba0c849084bf16b546fe8fff3e6bba188d3565b10099721109551ada5a",
+ "simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md": "sha256:1485426f46ae46bf5c25ab95962cb7edc4dd3b43f3bd2211c0e41f02c505e1fc",
+ "simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml": "sha256:6c8ed11b067c34f1af060d6c36de3685f3a15874786d811620b57e42c2b8d5c6",
+ "simplification/2026-07-04-prune-write-only-fs-surface.md": "sha256:5602e09004f9f2b81f447abed4de10b18a96df5f44b13fd1cc0c06ffd3ce5b4a",
+ "simplification/2026-07-04-prune-write-only-fs-surface.zh.md": "sha256:086f2cce3dc120f0c31c7dbc1855390f72e3940f2ffa92108de20fa175fff86a",
+ "simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml": "sha256:24fb3c525cae7334841b7daca4c65783f013aa53910a81d20e092b4dd7081cda",
+ "simplification/2026-07-04-remove-agent-steering-mirror.md": "sha256:3351fef50ba8635e5a3829a39cad24333bf3285799602b0891acdec312aa858f",
+ "simplification/2026-07-04-remove-agent-steering-mirror.zh.md": "sha256:75ad399226bc42950128e410be9666cb3d0b76ca673418f31cd2a57a8f56e513",
+ "simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml": "sha256:bd64279826444b41f6f1dc5d92fecd974edc3663885470f2eae226978926a59b",
+ "simplification/2026-07-04-share-app-bin-boot-glue.md": "sha256:de0f4dca1e89c0c19d649aa37989df1991376d2cac1a5ec72c1a3ca0dce27e49",
+ "simplification/2026-07-04-share-app-bin-boot-glue.zh.md": "sha256:e014ac4c2b609b70c467540ff0985c59a74df0eaf3062c43ffcaf1ac35d18ce2",
+ "simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml": "sha256:9080af48de70cc519f935896ae90134bcecdf4cee56bb5abb3e909672f2dded3",
+ "simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md": "sha256:c11fbdea4bdd14eba517dc6377f8e59eb73fc05779424f38833cc662baf04fd5",
+ "simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md": "sha256:f1bceae26fdea3fc71d8a0e32530eadb00342590a611b9fbc7ff09d0fc8aa3b8",
+ "simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml": "sha256:cb9f223b74ea3ba0279f17d2bfd59033b67d1ea7b525b0ebee01fb9ee74da4be",
+ "simplification/2026-07-12-drop-unconsumed-skill-provider-events.md": "sha256:cc78d0f80438e52e7d928b786101a902a15e4317fb0db2a47833c44520937c60",
+ "simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md": "sha256:ccb7146536c8a0f956d4799ddafc7b7cfea264fbc642107bd8aa24f06d88932d",
+ "simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml": "sha256:896dea8f5430603c445169fa79bfba997421a76d48fa4336349ec693572e6167",
+ "simplification/2026-07-12-prune-unused-web-seam-fields.md": "sha256:e732eb5eed007e95f40f32eddd8d94cd34f0ce579f1a70b16ca072a48a3989b4",
+ "simplification/2026-07-12-prune-unused-web-seam-fields.zh.md": "sha256:ac427d5cf6525c155b12dc7605954ebe3ce1f3edb6da0d0305e8d79cf475460b",
+ "simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml": "sha256:a7e5e21bf8a3a06bbf1272c677a7fff980e7548459405835416b88f1537bfe92",
+ "simplification/2026-07-19-retire-subagent-mock-package.md": "sha256:3df91519b77efcc413a54927adb2f829e944ce7f827211ac6ae66d4b5e0398a7",
+ "simplification/2026-07-19-retire-subagent-mock-package.zh.md": "sha256:c86d96800abc5aebf2d63694cb2cdcb21867091b2867de44493212f302498889",
+ "simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml": "sha256:602ab8fda1facb04a8f04d088267cbbd0426d607a8cc8c3fc056887f4a2696d9",
+ "simplification/2026-07-19-use-one-session-surface-manager.md": "sha256:267882c357527a12d8581c9d78249819a987c766a74a2d47f351dc5b14bf7d0a",
+ "simplification/2026-07-19-use-one-session-surface-manager.zh.md": "sha256:21c68a432c22209a3c19c8424da8e03fe91415d9ce3753cf17d727663077e4c9",
+ "simplification/2026-07-20-retire-readline-front-door.i18n.yaml": "sha256:48b8573d325d280b65e7debde660140e7afdf1db793d4eb32c839c635121a965",
+ "simplification/2026-07-20-retire-readline-front-door.md": "sha256:f632cd22fd81cc470992ff4e5f695a118a2aaed53748a860da7e9a78ca99ebbd",
+ "simplification/2026-07-20-retire-readline-front-door.zh.md": "sha256:3612b25120a87f0e1c9d075c9e3c4b6a9449f38c8b219dd1aef506c6e6f92d98",
+ "simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml": "sha256:17ee6e9a3db867b85d8399879c40552a6771b5d7585f7b58e33601428a1309e3",
+ "simplification/2026-07-21-tui-remove-cancel-command.md": "sha256:e90ad809b5ea241a653641f7331893347a1a0be7c677c99cbfc6bba8c907ab19",
+ "simplification/2026-07-21-tui-remove-cancel-command.zh.md": "sha256:94d388753157eb498b9a8dbd9050dc07e5ee893e9b5a07f4c265b2e8e66f6338",
+ "simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml": "sha256:633975e45444f179e5fcd258d3c4bce924975583505fa97f18cff21861a88ca2",
+ "simplification/2026-07-21-tui-todo-write-opt-in.md": "sha256:7c4c0818f5cb5b1a506dabb71a56b7d79b811e4b912d492865f1404f4d1ece99",
+ "simplification/2026-07-21-tui-todo-write-opt-in.zh.md": "sha256:2c121b8ea03182f7854e7d834b07967fdb6790af6a2a38c1d24bb0ca968496ba",
+ "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml": "sha256:4177012c0821a8c22499852ecdf096af56d7263cb91c5d9d1bcd552cc26a3e00",
+ "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md": "sha256:45234e7cc04b6010c6141f8d5924c04547300098f96262d423c50108e7c7011a",
+ "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md": "sha256:15e5a4ad3dee0bb711480cabe45cd97ec37bbdba19c2c2b47d1e9c203b07a48b",
+ "testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml": "sha256:d9fb0a30bbf58bbd6fcb45c84bee204f3f97a8a4cf7b867eec7320ba8663abf7",
+ "testing/2026-06-22-fork-snapshot-scenarios.md": "sha256:2bd6458490789f68110ec6a7fb6ea55af544f09df854c163fcb83f04a440da98",
+ "testing/2026-06-22-fork-snapshot-scenarios.zh.md": "sha256:f39e26c527dcb92d364b00bd3294f79bb30960ff35a4309bde00563aa594ec08",
+ "testing/2026-07-04-hook-snapshot-matrix.i18n.yaml": "sha256:f8fe2a2893e929d3a0476151f4f44ce9b4c26790aae84f6ddbf365239ce6a0bb",
+ "testing/2026-07-04-hook-snapshot-matrix.md": "sha256:287b9e0d97ea2e79a3ec6175c02ab01ef1314652528d5911a3a971e008094b6e",
+ "testing/2026-07-04-hook-snapshot-matrix.zh.md": "sha256:25b33993da3b8eb94113b90050ac03b72d40bf085bff72b3119d38105a2d7ee2",
+ "testing/2026-07-04-single-source-acp-replay-config.i18n.yaml": "sha256:cdf1ede909bc51792b1dcd74d5928111f75d4aba5020e8b30b3f395887348329",
+ "testing/2026-07-04-single-source-acp-replay-config.md": "sha256:a94352fe79201949e28028abe4c7d932fd0d2e81d869e7d3b58d22f54a649417",
+ "testing/2026-07-04-single-source-acp-replay-config.zh.md": "sha256:bed4dcd236a07192dd3de6c76e4a5c47dd5ec35963ce830bd5521bbb41d3f3a3",
+ "testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml": "sha256:4f3ebae0faea8a38ffe0d5291a33b3bcf99ed723f8e0cc5cccecbedbf4fb9ce9",
+ "testing/2026-07-06-pin-request-header-content-in-one-scenario.md": "sha256:050bf8044ce22a27a0f57b5cef84ccff0dc45b1a3f6b70aa41950d41038d0702",
+ "testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md": "sha256:cac75d4475666239bbe0030b90c0fa7cc66024af5b9f8ef217e53018be64890e"
+ }
+}
diff --git a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.i18n.yaml b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.i18n.yaml
similarity index 65%
rename from .agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.i18n.yaml
rename to .agents/notes/archived/process/2026-06-11-doc-sync-enforcement.i18n.yaml
index 19b6055f0a..25c34c889b 100644
--- a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.i18n.yaml
+++ b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.i18n.yaml
@@ -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-11-doc-sync-enforcement.md: 375059312c312dff7b5ddcb95ea5b82ac8cd4d06
-2026-06-11-doc-sync-enforcement.zh.md: 5c17263bdc2b4908a82237d1fc3b08f1f22a62d9
+2026-06-11-doc-sync-enforcement.md: 00fc6e904f1908b5cc4ddbe02b6eecf46b06408b
+2026-06-11-doc-sync-enforcement.zh.md: 9daaf88626d18093f8af7ba9ee5b6ce9e596d011
diff --git a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md
rename to .agents/notes/archived/process/2026-06-11-doc-sync-enforcement.md
index 375059312c..00fc6e904f 100644
--- a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md
+++ b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.md
@@ -1,6 +1,7 @@
# Agent Note: Doc-sync enforcement
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-11-doc-sync-enforcement.zh.md)
diff --git a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.zh.md b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.zh.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.zh.md
rename to .agents/notes/archived/process/2026-06-11-doc-sync-enforcement.zh.md
index 5c17263bdc..9daaf88626 100644
--- a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.zh.md
+++ b/.agents/notes/archived/process/2026-06-11-doc-sync-enforcement.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Doc-sync 强制
Status: implemented
+Archived: 2026-07-26
[English](2026-06-11-doc-sync-enforcement.md) | 中文
diff --git a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.i18n.yaml b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.i18n.yaml
rename to .agents/notes/archived/process/2026-07-03-documentation-graph-atlas.i18n.yaml
index d3cd78a06b..1c1ed11b5a 100644
--- a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.i18n.yaml
+++ b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.i18n.yaml
@@ -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-03-documentation-graph-atlas.md: 8b532fdd6f600eba05411588f8277b7cc43b3613
-2026-07-03-documentation-graph-atlas.zh.md: d426735407c0395fd96aed65a49aea0f0fdf6a90
+2026-07-03-documentation-graph-atlas.md: 6f2949e3673c43018f961cc954c9925945875f55
+2026-07-03-documentation-graph-atlas.zh.md: 731ae8a97a8437216c706adf86574d1982d7c484
diff --git a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md
rename to .agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md
index 8b532fdd6f..6f2949e367 100644
--- a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md
+++ b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md
@@ -1,6 +1,7 @@
# Agent Note: Documentation graph index for maintainers and SDK users
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-03-documentation-graph-atlas.zh.md)
diff --git a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.zh.md b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.zh.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.zh.md
rename to .agents/notes/archived/process/2026-07-03-documentation-graph-atlas.zh.md
index d426735407..731ae8a97a 100644
--- a/.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.zh.md
+++ b/.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 面向维护者与 SDK 用户的文档关系图索引
Status: implemented
+Archived: 2026-07-26
[English](2026-07-03-documentation-graph-atlas.md) | 中文
diff --git a/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.i18n.yaml b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.i18n.yaml
new file mode 100644
index 0000000000..32db8ee132
--- /dev/null
+++ b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.i18n.yaml
@@ -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
+2026-07-06-parallel-github-ci-gates.md: 0a621e0cf0b37d3ba6f612aaf1d9d7d052496929
+2026-07-06-parallel-github-ci-gates.zh.md: 340e5301b941fcb4774e2625e6706ab092a78c26
diff --git a/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.md b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.md
new file mode 100644
index 0000000000..0a621e0cf0
--- /dev/null
+++ b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.md
@@ -0,0 +1,51 @@
+# Agent Note: Parallel GitHub CI gates
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-06-parallel-github-ci-gates.zh.md)
+
+## Problem
+
+The keyless GitHub CI gates are mostly orthogonal: typecheck, lint, documentation freshness, coverage, snapshot replay, build, package-publication hygiene, demo smoke, and built-bin smoke fail for different reasons and do not need each other's runtime state. Running them as one ordered command chain makes the workflow wall clock equal the sum of those gates, while splitting every short leaf into its own GitHub job repeats checkout, Node setup, pnpm restore, and install work until orchestration overhead becomes the bottleneck.
+
+The original broad-lane split stopped meeting that balance as the workspace grew. On the merge of PR #404, Linux static, coverage, snapshot, and artifact jobs took 148, 195, 94, and 230 seconds; Windows static and artifacts took 251 and 482 seconds. Package-manager packing once per package dominated both artifact validators, coverage needlessly rebuilt output before a source-only suite, and CPU-heavy gates contended inside the static and coverage lanes.
+
+The artifact boundary remains load-bearing. `publint`, `verify-node-next-types`, compiled invariant loading, and built-bin smoke tests need emitted `lib/` output. Sharding cannot race those consumers ahead of build or replace their published-artifact signal with source execution.
+
+## Decision
+
+The production topology below is historical and is superseded by [Evidence-based larger hosted runners](2026-07-22-evidence-based-larger-hosted-runners.md). The larger-runner decision removes its shard selectors and workflow jobs; this note preserves why that earlier topology was implemented.
+
+[CI](../../../../.github/workflows/ci.yml) treats one minute for non-Windows jobs and three minutes for Windows jobs as observed performance targets, not cancellation deadlines. Hosted-runner variance should leave complete timing evidence and useful failure logs instead of cancelling an otherwise-correct gate. The [serial cross-platform CI reference](2026-07-21-serial-cross-platform-ci-reference.md) independently runs the complete unsharded primary Node aggregate on Linux, macOS, and Windows so the optimized lane inventory is not its own completeness oracle.
+
+In that topology, [scripts/run-gates.ts](../../../../scripts/run-gates.ts) was the common bounded scheduler and GitHub supplied explicit shard names for the expensive gate families. `scripts/static-shards.ts` partitioned static gates into foundation, documentation-type, API-contract, catalog, prose, documentation-projection, and documentation-build ownership and rejected a missing or duplicate gate assignment. Linux lint used disjoint A-C, D-M, N-S, and T-Z package-source and package-test lanes, while Windows used complete package-source and package-test lanes; both included a repository complement starting from `.` so new top-level targets could not disappear between shards and owned the single cross-file duplication run. `scripts/coverage-shards.ts` assigned every workspace package to exactly one source-coverage lane. Directory filters retained a trailing separator because Vitest positional filters match substrings and would otherwise admit prefix-named siblings. Each coverage lane included only its owned source files, repeated the exhaustive companion topology test, and ran without a preceding build because the complete coverage suite passes from a tree with every generated `lib/` removed.
+
+Snapshot replay used two explicit multi-file lanes and eight scenario partitions of the large ACP file. `scripts/snapshot-shards.ts` owned that inventory, and its test discovered every file admitted by the snapshot config. Each snapshot job installed dependencies while its Linux runner prepared Bubblewrap, built the shipped runtime, and ran only its assigned replay surface. The suite retained bounded concurrency of five subprocesses because replay spent most of its time waiting on child protocol I/O. Fixture guards still inspected the complete ACP scenario table in every partition.
+
+Cold standalone documentation typechecking rebuilds the complete project-reference graph, so a dedicated documentation-type lane builds once and checks Markdown blocks against those declarations. The Linux documentation lane uses VitePress's MPA build to retain page rendering and dead-link validation within the observed non-Windows target; separate blocking Windows build and production-site lanes preserve the emitted-package and shipped-site checks without putting both critical paths in one job.
+
+Artifacts use two lanes: one metadata lane for `publint`, NodeNext declarations, and compiled invariant loading, plus one built-bin smoke lane. Each lane produces its own build before its consumers. Repeating the short build costs runner minutes but avoids an upload/download dependency and keeps each job's critical path bounded.
+
+[scripts/publint-all.ts](../../../../scripts/publint-all.ts) calls publint's supported API in-process against an in-memory publication view made from each manifest's declared files and npm's mandatory metadata files. This preserves the distinction between workspace files and published files without spawning a package-manager pack command 103 times. [scripts/verify-built-package-invariants.mjs](../../../../scripts/verify-built-package-invariants.mjs) stages those structurally validated manifest-declared `lib/` files below the real package, then imports the compiled self-reference through plain Node and Cordis Loader normalization. A companion that reaches an undeclared runtime chunk still fails.
+
+Compatibility lanes run the source worker and Zstandard runtime smokes on every advertised Node line. TypeScript checks the source graph once in a dedicated primary Node 24 lane; repeating the same compiler analysis in runtime compatibility jobs added time without runtime-specific signal.
+
+The workflow caches the pnpm store, keys each immutable ESLint cache to its owning lint shard, preserves native PowerShell for Windows measurements, and retains one aggregate `all checks passed` status for branch protection. Windows reuses the three exhaustive lint partitions and groups foundation/catalog/prose plus documentation-type/API-contract gates behind shared runner setups; only scheduling differs from the Linux partitions. Windows build and production-site validation remain blocking, while the wider Windows static, lint, and artifact matrix remains observational.
+
+## Alternatives considered
+
+- **Keep the broad lanes** - minimizes workflow YAML, but it preserves the measured multi-minute feedback loop.
+- **Run every leaf gate as a separate GitHub job** - maximizes fan-out, but short generators and prose checks would spend more time preparing a runner than checking the repository.
+- **Upload one build to artifact consumers** - avoids repeated compilation, but upload/download and dependency scheduling lengthen wall time; the clean build is short enough to repeat inside bounded lanes.
+- **Keep package-manager packing in both publication gates** - delegates inventory selection to pnpm, but repeats more than 200 package-manager processes. The manifest structural gate plus publication-view fixtures make the optimized inventory contract explicit and fail on an on-disk but unpublished dependency.
+- **Keep build before coverage** - provides emitted output the source suite no longer consumes; a clean-tree coverage proof showed it was pure latency.
+- **Typecheck on every Node version** - repeats compiler work while the compatibility smokes already exercise actual Node-specific loading and compression behavior.
+
+## Consequences
+
+The shard inventories and matrix jobs described above are not part of the current repository contract. The superseding larger-runner decision keeps the complete primary inventory in one process and uses the serial suite as its independent completeness oracle.
+
+The optimized publication validators rely on the manifest `files` contract enforced by `verify-package-invariants`. If publication rules grow beyond that contract, the structural gate and both staged views must change together.
+
+Compatibility jobs no longer claim that TypeScript itself was exercised under every Node runtime. They prove runtime-sensitive source loading on Node 22, 24, and 26, while the primary runtime owns the single source-graph typecheck.
diff --git a/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.zh.md b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.zh.md
new file mode 100644
index 0000000000..340e5301b9
--- /dev/null
+++ b/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.zh.md
@@ -0,0 +1,51 @@
+# Agent Note: 并行 GitHub CI 门禁
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-06-parallel-github-ci-gates.md) | 中文
+
+## 问题
+
+无密钥 GitHub CI 门禁大多相互正交:类型检查、lint、文档新鲜度、覆盖率、快照重放、构建、包(package)的发布卫生检查、demo 冒烟和已构建二进制冒烟会因不同原因失败,也不需要彼此的运行时状态。将它们作为一条有序命令链运行,会使工作流墙钟时间等于所有门禁耗时之和;而把每个短小叶子拆成独立 GitHub job,又会反复执行 checkout、Node 设置、pnpm 恢复和安装,直到编排开销成为瓶颈。
+
+随着 workspace 增长,原有的宽车道拆分不再满足这一平衡。PR(Pull Request)#404 合并时,Linux 的静态、覆盖率、快照和产物 job 分别耗时 148、195、94 和 230 秒;Windows 的静态和产物 job 分别耗时 251 和 482 秒。每个包都调用一次包管理器打包,主导了两个产物验证器的耗时;覆盖率在仅运行源码的套件前无谓地重建输出;CPU 密集型门禁则在静态与覆盖率车道内争用资源。
+
+产物边界仍然承载关键约束。`publint`、`verify-node-next-types`、已编译不变量加载和已构建二进制冒烟测试都需要生成的 `lib/` 输出。分片不能让这些消费方抢在构建前运行,也不能用源码执行取代它们对已发布产物的信号。
+
+## 决策
+
+下述生产拓扑已经成为历史,并由[基于证据采用更大的托管 runner](2026-07-22-evidence-based-larger-hosted-runners.md) 取代。更大 runner 的决策移除了其分片选择器和工作流 job;本文保留早期拓扑为何被实现的记录。
+
+[CI](../../../../.github/workflows/ci.yml) 将非 Windows job 的一分钟和 Windows job 的三分钟视为观测所得的性能目标,而非取消截止时间。托管 runner 的波动应留下完整计时证据和有用的失败日志,而不是取消本来正确的门禁。[串行跨平台 CI 参考](2026-07-21-serial-cross-platform-ci-reference.md)会在 Linux、macOS 和 Windows 上独立运行完整、未分片的主 Node 聚合,使优化后的车道清单不会成为自身完整性的唯一判据。
+
+在该拓扑中,[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 是通用的有界调度器,GitHub 则为昂贵的门禁族提供显式分片名称。`scripts/static-shards.ts` 将静态门禁划分为基础、文档类型、API 契约、目录、正文、文档投影和文档构建等归属,并拒绝缺失或重复的门禁分配。Linux lint 使用互不重叠的 A-C、D-M、N-S、T-Z 包源码和包测试车道,Windows 则使用完整的包源码与包测试车道;两者都包含从 `.` 开始的仓库补集,使新增顶层目标无法消失在分片之间,并负责唯一一次跨文件重复检查。`scripts/coverage-shards.ts` 把每个 workspace 包恰好分配给一个源码覆盖率车道。目录过滤器保留尾部分隔符,因为 Vitest 位置过滤器按子字符串匹配,否则会纳入具有同名前缀的相邻项。每个覆盖率车道只包含其拥有的源码文件,重复运行穷尽式伴随拓扑测试,并且不先执行构建,因为从删除了所有生成式 `lib/` 的树开始,完整覆盖率套件仍可通过。
+
+快照重放使用两个显式多文件车道,以及大型 ACP(Agent Client Protocol)文件的八个场景分区。`scripts/snapshot-shards.ts` 拥有该清单,其测试会发现快照配置允许的每个文件。每个快照 job 在其 Linux runner 准备 Bubblewrap 的同时安装依赖,随后构建已发布运行时,并且只运行分配给它的重放表面。该套件保留五个子进程的有界并发,因为重放的大部分时间都在等待子进程协议 I/O。fixture(测试前置数据)守卫仍会在每个分区中检查完整 ACP 场景表。
+
+冷启动的独立文档类型检查会重建完整的项目引用图,因此专用文档类型车道只构建一次,再用这些声明检查 Markdown 块。Linux 文档车道使用 VitePress 的 MPA 构建,在观测所得的非 Windows 目标内保留页面渲染与死链接验证;单独的阻塞式 Windows 构建和生产站点车道保留已生成包与已发布站点检查,同时避免把两条关键路径放进同一个 job。
+
+产物使用两个车道:一个元数据车道负责 `publint`、NodeNext 声明和已编译不变量加载,另一个负责已构建二进制冒烟。每个车道都会在其消费方之前自行构建。重复短时构建会消耗 runner 分钟数,但避免了上传/下载依赖,并使每个 job 的关键路径保持有界。
+
+[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 在进程内针对内存发布视图调用 publint 支持的 API;该视图由每份清单声明的文件和 npm 强制元数据文件构成。这样无需生成 103 次包管理器打包命令,也能保留 workspace 文件与已发布文件之间的区别。[scripts/verify-built-package-invariants.mjs](../../../../scripts/verify-built-package-invariants.mjs) 在真实包下暂存这些经过结构验证、由清单声明的 `lib/` 文件,再通过纯 Node 和 Cordis Loader 规范化导入已编译的自引用。若伴随项触及未声明的运行时分片,仍会失败。
+
+兼容性车道会在每条声明支持的 Node 版本线上运行源码 worker 和 Zstandard 运行时冒烟。TypeScript 在专用的主 Node 24 车道中只检查一次源码图;在运行时兼容性 job 中重复同一编译器分析只会增加耗时,不会提供运行时特有信号。
+
+工作流缓存 pnpm store,将每个不可变 ESLint 缓存的键绑定到其所属 lint 分片,为 Windows 测量保留原生 PowerShell,并保留一个聚合的 `all checks passed` 状态用于分支保护。Windows 复用三个穷尽式 lint 分区,并在共享 runner 设置后组合基础/目录/正文门禁与文档类型/API 契约门禁;只有调度方式与 Linux 分区不同。Windows 构建和生产站点验证继续阻塞,而更广泛的 Windows 静态、lint 和产物矩阵仍为观察性检查。
+
+## 曾考虑的替代方案
+
+- **保留宽车道**:最大限度减少工作流 YAML,但会保留观测到的数分钟反馈周期。
+- **让每个叶子门禁分别成为 GitHub job**:最大化扇出,但短小的生成器和正文检查准备 runner 的时间会超过检查仓库的时间。
+- **向产物消费方上传一次构建**:避免重复编译,但上传/下载和依赖调度会延长墙钟时间;干净构建足够短,可以在有界车道内重复。
+- **在两个发布门禁中保留包管理器打包**:把清单选择委托给 pnpm,但会重复启动 200 多个包管理器进程。清单结构门禁加发布视图 fixture 使优化后的清单契约显式化,并会在存在磁盘上有但未发布的依赖时失败。
+- **在覆盖率前保留构建**:提供源码套件已不再消费的生成输出;干净树覆盖率证明表明这只是纯粹的延迟。
+- **在每个 Node 版本上执行类型检查**:重复编译器工作,而兼容性冒烟已经验证实际的 Node 特有加载与压缩行为。
+
+## 后果
+
+上述分片清单和矩阵 job 不属于当前仓库契约。取而代之的更大 runner 决策在单个进程中保留完整主清单,并以串行套件作为独立完整性判据。
+
+优化后的发布验证器依赖由 `verify-package-invariants` 强制执行的清单 `files` 契约。如果发布规则超出该契约,结构门禁和两个暂存视图必须一起变化。
+
+兼容性 job 不再声称 TypeScript 本身已在每个 Node 运行时下执行。它们证明 Node 22、24 和 26 上对运行时敏感的源码加载,而主运行时负责唯一一次源码图类型检查。
diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml
rename to .agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml
index 46073fc52b..8ca2ff1db1 100644
--- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml
+++ b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml
@@ -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-21-doc-sync-through-gate-scheduler.md: d66d9dc75ee4e8268d55e344a53c51c0bcf5f4d4
-2026-07-21-doc-sync-through-gate-scheduler.zh.md: 8c4c4595c2bc6e9439ed24cda1e70ae5a5ccd146
+2026-07-21-doc-sync-through-gate-scheduler.md: ba7eeb75e65e5fc342ad7d3b9055d60a4e6f5770
+2026-07-21-doc-sync-through-gate-scheduler.zh.md: 7723a409e2a5d1ee046869403d9f4aadee10757d
diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md
rename to .agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.md
index d66d9dc75e..ba7eeb75e6 100644
--- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md
+++ b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.md
@@ -1,6 +1,7 @@
# Agent Note: doc-sync through the gate scheduler
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-doc-sync-through-gate-scheduler.zh.md)
diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md
rename to .agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md
index 8c4c4595c2..7723a409e2 100644
--- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md
+++ b/.agents/notes/archived/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md
@@ -1,6 +1,7 @@
# Agent Note: doc-sync 走门禁调度器
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-doc-sync-through-gate-scheduler.md) | 中文
diff --git a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml
rename to .agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml
index a6becde554..20649748cb 100644
--- a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml
+++ b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml
@@ -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-22-installer-in-repo-skip-clone.md: f63c438205f7bd6aeb8dd78941bbe0880a8e31a1
-2026-07-22-installer-in-repo-skip-clone.zh.md: f9fe4865ad1090211c094fc8fba843b623512cc9
+2026-07-22-installer-in-repo-skip-clone.md: 607ce3baf842d437b64050a5ef17f3c7e4cffba5
+2026-07-22-installer-in-repo-skip-clone.zh.md: 17260427d514b5fd1c87c1d7faae5b1c8c1d9b0b
diff --git a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.md b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.md
rename to .agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.md
index f63c438205..607ce3baf8 100644
--- a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.md
+++ b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.md
@@ -1,6 +1,7 @@
# Agent Note: installer skips the clone when run from inside a checkout
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-22-installer-in-repo-skip-clone.zh.md)
diff --git a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.zh.md b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.zh.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.zh.md
rename to .agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.zh.md
index f9fe4865ad..17260427d5 100644
--- a/.agents/notes/implemented/process/2026-07-22-installer-in-repo-skip-clone.zh.md
+++ b/.agents/notes/archived/process/2026-07-22-installer-in-repo-skip-clone.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 在检出目录内运行时安装脚本跳过克隆
Status: implemented
+Archived: 2026-07-26
[English](2026-07-22-installer-in-repo-skip-clone.md) | 中文
diff --git a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.i18n.yaml b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.i18n.yaml
rename to .agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.i18n.yaml
index a8cc857059..9c3f69671d 100644
--- a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.i18n.yaml
+++ b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.i18n.yaml
@@ -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-23-browser-demo-gif-recording.md: 2213b8cd1be0a05638ce659840150e21d3a927bc
-2026-07-23-browser-demo-gif-recording.zh.md: 391af22ea92cb7de61fa4254153209fbbcc3ed68
+2026-07-23-browser-demo-gif-recording.md: 844e976c5be5acfca6b1f933256fb5a8a82f980a
+2026-07-23-browser-demo-gif-recording.zh.md: 12702daaca35dc26f8e9ad7aa1b803f9e4192a5c
diff --git a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.md b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.md
rename to .agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.md
index 2213b8cd1b..844e976c5b 100644
--- a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.md
+++ b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.md
@@ -1,6 +1,7 @@
# Agent Note: Browser demo GIF recording
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-23-browser-demo-gif-recording.zh.md)
diff --git a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.zh.md b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.zh.md
similarity index 99%
rename from .agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.zh.md
rename to .agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.zh.md
index 391af22ea9..12702daaca 100644
--- a/.agents/notes/implemented/process/2026-07-23-browser-demo-gif-recording.zh.md
+++ b/.agents/notes/archived/process/2026-07-23-browser-demo-gif-recording.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 浏览器演示 GIF 录制
Status: implemented
+Archived: 2026-07-26
[English](2026-07-23-browser-demo-gif-recording.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml
similarity index 59%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml
index 9529343206..fe51aa6304 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml
@@ -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: a3c7c089d7dfa1a4cd6a891c416bf270dc7eff3d
-2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md: d9129386dc95bae0716253fdf50236b25ebfdf75
+2026-06-20-drop-unconsumed-llm-adapter-change-event.md: ad05be999158a225230b4a1d760983b71075aada
+2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md: 0efc0193b3d63b7df6e1671afed0d1faebeaa2af
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
index a3c7c089d7..ad05be9991 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
@@ -1,6 +1,7 @@
# Agent Note: Drop the unconsumed `llm/adapter-change` event
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md
index d9129386dc..0efc0193b3 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除未被消费的 `llm/adapter-change` 事件
Status: implemented
+Archived: 2026-07-26
[English](2026-06-20-drop-unconsumed-llm-adapter-change-event.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml
similarity index 60%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml
index cf51518edd..46c237e63f 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.i18n.yaml
@@ -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: 40fa71a7bcf00d8933165ee8d4594112fdb8c0a8
-2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md: 00b3b907eed9cbbb37eac028f32ca5cf3f796ac5
+2026-06-20-drop-unconsumed-llm-assembled-surfaces.md: fd3d48e0918f8395b6c94e407b889ec3a6de7fbe
+2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md: 83d631329ccddb0f8880f223884ae4dc75a14d09
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
index 40fa71a7bc..cf887aa389 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
@@ -1,6 +1,7 @@
# Agent Note: Drop unconsumed assembled LLM convenience surfaces
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md
rename to .agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md
index 00b3b907ee..794b346ffd 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md
+++ b/.agents/notes/archived/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除未被消费的 LLM 组装便捷接口
Status: implemented
+Archived: 2026-07-26
[English](2026-06-20-drop-unconsumed-llm-assembled-surfaces.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml
rename to .agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml
index 872b3fc588..20e305dae0 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml
@@ -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: 91a18b4c327d2f3cb636d6d9e0c26e4246a6ffd2
-2026-06-20-prune-dead-seam-methods.zh.md: b962bc45052b723eefa533b98fe85f4508d6b6a7
+2026-06-20-prune-dead-seam-methods.md: 4f292803ff34e504502d8b2b427ebbc769088968
+2026-06-20-prune-dead-seam-methods.zh.md: 325d41536d21686b95618d5d92ef8a946f7b8ddb
diff --git a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.md b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.md
rename to .agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.md
index 91a18b4c32..4f292803ff 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.md
+++ b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.md
@@ -1,6 +1,7 @@
# Agent Note: Prune dead methods from the persistence seam
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-20-prune-dead-seam-methods.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.zh.md b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.zh.md
rename to .agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.zh.md
index b962bc4505..325d41536d 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.zh.md
+++ b/.agents/notes/archived/simplification/2026-06-20-prune-dead-seam-methods.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 从持久化 seam 中移除无用方法
Status: implemented
+Archived: 2026-07-26
[English](2026-06-20-prune-dead-seam-methods.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml
index 4d018139c5..c5e8631a70 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml
@@ -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: 06fa6c1c539f9ff0cfabf76bc41c53800bd46c8c
-2026-07-04-drop-inert-request-knobs.zh.md: 42aadde2b279a453fac9444060b8ac34bf9e3c8b
+2026-07-04-drop-inert-request-knobs.md: d4ef5f00caf3b2f580877374adaf6ddcd84e7326
+2026-07-04-drop-inert-request-knobs.zh.md: dfb57080e91039d48b9c173bf958c583005c3c32
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md
rename to .agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md
index 06fa6c1c53..d4ef5f00ca 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md
@@ -1,6 +1,7 @@
# Agent Note: Drop `GenerateOptions.prefill` and `ToolSchema.strict` — request knobs with no working end-to-end path
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-drop-inert-request-knobs.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.zh.md b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.zh.md
index 42aadde2b2..dfb57080e9 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除 `GenerateOptions.prefill` 与 `ToolSchema.strict`——无端到端可用路径的请求旋钮
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-drop-inert-request-knobs.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml
similarity index 59%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml
index 2845b37a05..3079ec531b 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.i18n.yaml
@@ -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: 5b1cb1307c63ef7c298200ee1655119026b9ebf5
-2026-07-04-drop-unconsumed-web-observation-surface.zh.md: c4351c7e66d00f47e1bf9ed117c5f7b043045808
+2026-07-04-drop-unconsumed-web-observation-surface.md: e63df08e486862b5bbb37db1e2c15b6d7e9d67e2
+2026-07-04-drop-unconsumed-web-observation-surface.zh.md: f9d1c09f90ec5065f25d4941dc319cff817caf97
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
rename to .agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
index 5b1cb1307c..e63df08e48 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
@@ -1,6 +1,7 @@
# Agent Note: Drop the unconsumed web observation surface — the `providers-change` event and the status methods
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-drop-unconsumed-web-observation-surface.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md
index c4351c7e66..f9d1c09f90 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除未被消费的 web 观测接口——`providers-change` 事件与 status 方法
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-drop-unconsumed-web-observation-surface.md) | 中文
diff --git a/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.i18n.yaml
new file mode 100644
index 0000000000..ae31e636f8
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.i18n.yaml
@@ -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
+2026-07-04-fold-stdio-ui-helper.md: 72211108820bd59235d6171c6008d2d63ad79c40
+2026-07-04-fold-stdio-ui-helper.zh.md: e9329ac3a038a37ee2bd51c65f976cbf7bb143c0
diff --git a/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.md b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.md
new file mode 100644
index 0000000000..7221110882
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.md
@@ -0,0 +1,31 @@
+# Agent Note: Fold the stdio UI helper into the stdio app
+
+Status: implemented
+Archived: 2026-07-26
+
+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
+
+The readline UI was a whole package (`@deepseek-ai/dsh-ui-stdio` under `packages/support/`) whose only runtime importer was the app package `@deepseek-ai/dsh-stdio-demo`. The examples reach the readline UI by loading the app, never by composing the helper themselves; every other repo reference was mechanical or descriptive surface that existed BECAUSE the package boundary existed — manifest and tsconfig entries, generated module-graph rows, dependency-graph and README rows, and doc comments naming the package. The ui group README recorded the support placement rationale ("exists chiefly for the examples and the coverage gate — `ui/` is reserved for surfaces shipped as product"), which left a standing tension: a shipped product app depending on a support package documented as NOT product surface.
+
+The boundary bought package metadata, workspace and tsconfig references, module-graph rows, README entries, and publint surface for a helper that is not independently swappable: the stdio app's front-door cluster always includes the readline UI, and nothing else can meaningfully consume it.
+
+## Decision
+
+At the time, the helper moved into `@deepseek-ai/dsh-stdio` as the terminal-channel plugin. `createStdioChat`, its `StdioRuntime` test seam, and its unit tests moved with it, keeping EOF handling, rendering, disposal, and piped-vs-TTY behavior under the per-file coverage gate without hijacking process globals. The module kept the named `name`/`inject`/`Config`/`apply` export shape consumed by the app mount, while the then-current Echo and REPL Loader smokes proved the composed tree and the plugin-shape suite pinned explicit `unwrapExports` behavior. The superseding removal note above owns the current package and example state.
+
+The earlier support helper package was removed: its manifest, tsconfig references, module-graph rows, and README rows disappeared, while the remaining documentation described the in-package module.
+
+## Alternatives considered
+
+### Why not promote it to `ui/` instead?
+
+Promotion would have resolved the support-vs-product mismatch while keeping the boundary — the right call only if the readline UI were an independently swappable integration or had a second composer, and the consumer census said neither. The structured ACP bridge stays its own package because it is an automation protocol surface with its own contract and snapshot tiers; the readline helper is scaffolding for one app's front door. Re-extraction stays cheap pre-release: if a second product app wants the readline UI, split it back out then, with that consumer shaping the package contract.
+
+## Consequences
+
+- The stdio app owns its whole front door; a leaf `cordis.yml` still loads one app package and nothing changed shape for the demos.
+- A future standalone terminal UI that wants the helper as a package reintroduces it with that second consumer, rather than the repo keeping a boundary for hypothetical reuse.
diff --git a/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.zh.md b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.zh.md
new file mode 100644
index 0000000000..e9329ac3a0
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-04-fold-stdio-ui-helper.zh.md
@@ -0,0 +1,31 @@
+# Agent Note: 将 stdio UI 辅助模块折入 stdio 应用
+
+Status: implemented
+Archived: 2026-07-26
+
+[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 包。
+
+这条边界换来的是:包元数据、workspace 与 tsconfig 引用、module-graph 行、README 条目,以及 publint 表面——服务于一个并不可独立替换的辅助模块:stdio 应用的前门集群始终包含 readline UI,且没有其他消费方能有意义地使用它。
+
+## 决策
+
+当时,该辅助函数移入 `@deepseek-ai/dsh-stdio`,成为终端通道插件。`createStdioChat`、其 `StdioRuntime` 测试 seam 和单元测试随之一同迁移,使 EOF 处理、渲染、释放以及管道/TTY 行为继续受逐文件覆盖率门禁约束,而不会劫持进程全局量。该模块保留应用挂载所消费的具名 `name`/`inject`/`Config`/`apply` 导出形状;当时的 Echo 和 REPL Loader 冒烟证明组合树,插件形状套件则固定显式 `unwrapExports` 行为。上方取代本文的移除记录负责当前包和示例状态。
+
+早期的支持辅助包已移除:其清单、tsconfig 引用、模块图行和 README 行均已消失,其余文档改为描述包内模块。
+
+## 曾考虑的替代方案
+
+### 为什么不将其提升到 `ui/` 而是折入?
+
+提升可以解决 support 与 product 之间的错位,同时保留边界——只有在 readline UI 是一个可独立替换的集成或有第二个组合方时才是正确选择,而消费方普查表明两者皆非。结构化的 ACP(Agent Client Protocol)桥接保留为独立包,因为它是具有自身契约和快照层级的自动化协议表面;readline 辅助模块只是一个应用前门的脚手架。在发布前重新拆分成本很低:如果将来有第二个产品应用需要 readline UI,届时再拆出来,由那个消费方来塑造包契约。
+
+## 后果
+
+- stdio 应用完整拥有自己的前门;叶子 `cordis.yml` 仍然只加载一个应用包,演示的形态没有变化。
+- 未来如果有独立的终端 UI 需要将该辅助模块作为包使用,届时由那个第二消费方驱动重新引入,而非仓库为假设性的复用保留一条边界。
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml
similarity index 60%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml
index 115c11bb23..f35edf4a1b 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.i18n.yaml
@@ -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: aa8c0500e79b91443e8b224c586a07644068799b
-2026-07-04-prune-producerless-vocabulary-variants.zh.md: 0ef2b68d7f6812c5f9ae380e6086a442d336c6cb
+2026-07-04-prune-producerless-vocabulary-variants.md: c1544fa9d72c994518f690d482a62d506ff3f83e
+2026-07-04-prune-producerless-vocabulary-variants.zh.md: e48f55c49d35d4a675f0d9540dab034bcb864cd3
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md
rename to .agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md
index aa8c0500e7..0472528af2 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md
@@ -1,6 +1,7 @@
# Agent Note: Prune producer-less vocabulary variants (block cache hints, the `agent` message source, the `continuation` turn trigger)
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-prune-producerless-vocabulary-variants.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md
index 0ef2b68d7f..85542e4688 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 裁剪无生产者的词汇变体(块缓存提示、`agent` 消息来源、`continuation` 轮次触发器)
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-prune-producerless-vocabulary-variants.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml
index e7aabd9dc2..21b28645b6 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.i18n.yaml
@@ -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: 6cfd5d9ab8a2fc6322814d384fba735c06681976
-2026-07-04-prune-write-only-fs-surface.zh.md: cb7494b5e958c8ed86ffa3ba8ffbe82748d1db03
+2026-07-04-prune-write-only-fs-surface.md: e1b6bb6bd9721120015aaced8ffae84048396b60
+2026-07-04-prune-write-only-fs-surface.zh.md: 2f8135b2b1fa9892270a110ea3c3112e6ea20700
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.md b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.md
rename to .agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.md
index 6cfd5d9ab8..e1b6bb6bd9 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.md
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.md
@@ -1,6 +1,7 @@
# Agent Note: Prune write-only fields and a dead routing knob from the fs seam
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-prune-write-only-fs-surface.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.zh.md b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.zh.md
index cb7494b5e9..2f8135b2b1 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-prune-write-only-fs-surface.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 从 fs seam 中移除只写字段与一个无效的路由旋钮
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-prune-write-only-fs-surface.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml
index 21e7cf9217..e6790ee816 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.i18n.yaml
@@ -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: 6d78217a4939c4a87325cd120638042acbd68dc1
-2026-07-04-remove-agent-steering-mirror.zh.md: 6aabb71903eaeb7204df2d5880a52acabc6d612a
+2026-07-04-remove-agent-steering-mirror.md: 0d7c7f8ac9592033423156d38f3bbe6d037afd07
+2026-07-04-remove-agent-steering-mirror.zh.md: be30e0add3a2d7392cb7a59ed4bc425ec0ad5588
diff --git a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.md b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.md
rename to .agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.md
index 6d78217a49..21f44ff601 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.md
+++ b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.md
@@ -1,6 +1,7 @@
# Agent Note: Remove the `agent/steering` mirror emit
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-remove-agent-steering-mirror.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.zh.md b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md
index 6aabb71903..03095fc38c 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除 `agent/steering` 镜像 emit
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-remove-agent-steering-mirror.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml
index 32517c2c50..0bed1b52c6 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.i18n.yaml
@@ -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: 7a763eba8a229ec5017387edb54657a5c367105b
-2026-07-04-share-app-bin-boot-glue.zh.md: d65a6613f7b05cdea0f99529808c992aff4256e9
+2026-07-04-share-app-bin-boot-glue.md: 8b2aeef73ecfc07f3102f852d4b0a44b336b7d92
+2026-07-04-share-app-bin-boot-glue.zh.md: ed5146030ca8d23a653497d6c097ad6c3b4445b1
diff --git a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.md b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.md
rename to .agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.md
index 7a763eba8a..8b2aeef73e 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.md
+++ b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.md
@@ -1,6 +1,7 @@
# Agent Note: Share the app bins' boot glue instead of maintaining twin copies
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-share-app-bin-boot-glue.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.zh.md b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.zh.md
index d65a6613f7..ed5146030c 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-share-app-bin-boot-glue.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 共享应用 bin 的启动胶水代码,而非维护两份副本
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-share-app-bin-boot-glue.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml
similarity index 60%
rename from .agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml
index 08ee0f1c29..b3fa52eab4 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.i18n.yaml
@@ -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: 959cad37f279888fda79b1632c3553ea122803c5
-2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: 9127b7f167c3e5c74cdb99266828c20e79f3cb90
+2026-07-04-trim-acp-bridge-unreachable-surface.md: 2b0e6bba085f30959cd3c33362cd369ca7aec422
+2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: ab924775588e61841f835bfe73ccae6d9886fd2a
diff --git a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
rename to .agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
index 959cad37f2..2b0e6bba08 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
+++ b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
@@ -1,6 +1,7 @@
# Agent Note: Trim unreachable ACP bridge surface — the branding knobs and the kind-sniffing fallback
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-trim-acp-bridge-unreachable-surface.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md
rename to .agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md
index 9127b7f167..ab92477558 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 裁剪不可达的 ACP 桥接层表面——品牌配置项与 kind 嗅探回退
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-trim-acp-bridge-unreachable-surface.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml
similarity index 60%
rename from .agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml
index 6c3e5859e7..374974a45d 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.i18n.yaml
@@ -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: b0ed7585882328b6abdcf57974200053d9c26048
-2026-07-12-drop-unconsumed-skill-provider-events.zh.md: fd380b2c0421abfc3032b88b550b4a3e8b88bf38
+2026-07-12-drop-unconsumed-skill-provider-events.md: 88ce8d01dfbae8347e8671b52597f45735d6890e
+2026-07-12-drop-unconsumed-skill-provider-events.zh.md: ac4e3ff0277867e3b68d9b5c8f999b8819166ae4
diff --git a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md
rename to .agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md
index b0ed758588..88ce8d01df 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md
+++ b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md
@@ -1,6 +1,7 @@
# Agent Note: Drop unconsumed skill provider events
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-12-drop-unconsumed-skill-provider-events.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md
rename to .agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md
index fd380b2c04..ac4e3ff027 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-12-drop-unconsumed-skill-provider-events.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 移除无消费方的 skill 提供方事件
Status: implemented
+Archived: 2026-07-26
[English](2026-07-12-drop-unconsumed-skill-provider-events.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml
index 1f8a055362..856d5e4d46 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.i18n.yaml
@@ -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: c50bf44161579a44b09113fc501f3d67fb5d6855
-2026-07-12-prune-unused-web-seam-fields.zh.md: 401bdd0c812175cffc572e722141d2829a3fc2d5
+2026-07-12-prune-unused-web-seam-fields.md: 4ad6dbb2efe314977d5e82f49e849dda9408146d
+2026-07-12-prune-unused-web-seam-fields.zh.md: 58706f1248132606d3d2d27603f7283eae310f66
diff --git a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md
rename to .agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.md
index c50bf44161..4ad6dbb2ef 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md
+++ b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.md
@@ -1,6 +1,7 @@
# Agent Note: Prune unused web seam fields
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-12-prune-unused-web-seam-fields.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md
rename to .agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md
index 401bdd0c81..58706f1248 100644
--- a/.agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-12-prune-unused-web-seam-fields.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 裁剪 web seam 中未使用的字段
Status: implemented
+Archived: 2026-07-26
[English](2026-07-12-prune-unused-web-seam-fields.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml
similarity index 62%
rename from .agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml
index 2b0b5c067d..0259af5b2a 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.i18n.yaml
@@ -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-19-retire-subagent-mock-package.md: 4a7fa32fdb0d8e656d61c39491a49bbd85e0adf3
-2026-07-19-retire-subagent-mock-package.zh.md: 7de72abb18050fb737000a2013e514dde3dae521
+2026-07-19-retire-subagent-mock-package.md: 9fce0d2f8ea20b4f31e2042acba4df34152e3201
+2026-07-19-retire-subagent-mock-package.zh.md: d82650c9e74f8fff1386185f0930762d21a7243f
diff --git a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.md b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.md
rename to .agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.md
index 4a7fa32fdb..9fce0d2f8e 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.md
+++ b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.md
@@ -1,6 +1,7 @@
# Agent Note: Retire the standalone subagent mock package
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-19-retire-subagent-mock-package.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.zh.md b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.zh.md
rename to .agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.zh.md
index 7de72abb18..d82650c9e7 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-retire-subagent-mock-package.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-19-retire-subagent-mock-package.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 撤销独立的 subagent mock 包
Status: implemented
+Archived: 2026-07-26
[English](2026-07-19-retire-subagent-mock-package.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml
index cd03e02285..a499c4bef7 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.i18n.yaml
@@ -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-19-use-one-session-surface-manager.md: dee1a2a1cb6642730c87035de071d77ad38bd238
-2026-07-19-use-one-session-surface-manager.zh.md: ce538f1569c91e317af347d2ac20db624215eac8
+2026-07-19-use-one-session-surface-manager.md: 741e949ee04150cdee3328a2ff04d79688bd484d
+2026-07-19-use-one-session-surface-manager.zh.md: 87502c600c6c2d546ad728819ebbf08f7e55bda3
diff --git a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.md b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.md
rename to .agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.md
index dee1a2a1cb..741e949ee0 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.md
+++ b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.md
@@ -1,6 +1,7 @@
# Agent Note: Use one surface manager per session
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-19-use-one-session-surface-manager.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.zh.md b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.zh.md
rename to .agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.zh.md
index ce538f1569..87502c600c 100644
--- a/.agents/notes/implemented/simplification/2026-07-19-use-one-session-surface-manager.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-19-use-one-session-surface-manager.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 每个会话只使用一个表层管理器
Status: implemented
+Archived: 2026-07-26
[English](2026-07-19-use-one-session-surface-manager.md) | 中文
diff --git a/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.i18n.yaml b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.i18n.yaml
new file mode 100644
index 0000000000..815d23408b
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.i18n.yaml
@@ -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
+2026-07-20-retire-readline-front-door.md: ecc9967b0ff97a998f9a1ac5c23e16ed82f1d797
+2026-07-20-retire-readline-front-door.zh.md: ea97ee79a2f901e350ef62f1afe6bfed3d7cb249
diff --git a/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.md b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.md
new file mode 100644
index 0000000000..ecc9967b0f
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.md
@@ -0,0 +1,47 @@
+# Agent Note: Retire the readline front door and the repl-agent example
+
+Status: implemented
+Archived: 2026-07-26
+
+English | [中文](2026-07-20-retire-readline-front-door.zh.md)
+
+## Problem
+
+The repo shipped two interactive terminal front doors: the line-oriented readline channel (`@deepseek-ai/dsh-stdio`) and the full-screen [`@deepseek-ai/dsh-tui`](../feature/2026-07-17-dedicated-full-screen-tui-front-door.md). After the TUI landed, readline's interactive role was redundant — `demo:tui` superseded `demo:repl` as the coding-agent experience — while its remaining real role, pipes and automation, was already served better by the one-shot `@deepseek-ai/dsh-cli-demo` app (task in, DSH-native `text`/`json`/`stream-json` out, durable persistence, signal handling).
+
+The duplication was structural, not just cosmetic: `dsh-stdio-demo` carried a `TerminalMode` (`auto`/`readline`/`tui`) selection seam, ~1,000 lines of readline unit tests, a readline transcript grammar (`[tool call] …` lines) that the CI demo smoke and two built-bin e2es grepped, and an inverted example composition where the flagship `tui-agent` leaf was defined as an include-patch over the `repl-agent` leaf it superseded.
+
+## Decision
+
+Delete the readline front door and the repl-agent example; keep exactly three front-door archetypes: **interactive TUI** (TTY-only, fails loud on pipes), **one-shot CLI** (`-p`/positional task, pipes and automation), and **servers** (ACP / JSON-RPC).
+
+- `packages/ui/stdio` and `examples/repl-agent` are gone. `packages/examples/stdio-demo` is renamed `@deepseek-ai/dsh-tui-demo` (`packages/examples/tui-demo`) and always mounts `dsh-tui`; the `TerminalMode`/`resolveTerminalMode`/`ui.mode` seam is deleted. The bin refuses non-TTY streams **before booting the Loader** (a compose-time throw inside a Loader tree is logged per-entry, not rethrown, so a piped launch would otherwise settle into an idle UI-less process instead of exiting nonzero).
+- `examples/tui-agent/cordis.yml` now owns the coding composition inline (the include-patch inversion is gone); its Code Mode overlay includes its own base. `examples/cordis-agent` moved to the TUI app.
+- `examples/echo-agent` moved to the one-shot `dsh-cli-demo` app; `dsh-cli-demo` gained `-p/--prompt` as the flag form of the single task (mutually exclusive with the positional).
+- The UI-independent with-key coding e2es (`full-loop`, `coding-task`, `resume`, `compaction`, `todo-write`, `code-mode` and their shared harness) moved verbatim from `examples/repl-agent/tests/` to `examples/tui-agent/tests/` — they assemble the stack programmatically and never touched a UI.
+- The SDK wizard's `stdio` run interface became `tui` (`RunInterface = 'acp' | 'tui' | 'embed'`), contributing a `dsh-tui` entry instead of `dsh-stdio`; the generated `index.ts` guards TTY before `startSDK` for the same pre-boot fail-loud reason as the tui-demo bin.
+
+### Testing policy: PTY only for the TUI
+
+Pipes remain the default test medium. PTY-driven subprocess tests are sanctioned **only** where the subject is the TUI itself: `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` (which gained the Code Mode overlay boot scenario, replacing repl-agent's pipe smoke as the overlay's keyless composition proof) and the minimal PTY boot smoke in `examples/cordis-agent` (whose front door IS the TUI). Everything else moved to pipes over the one-shot bin:
+
+- `examples/echo-agent/tests/echo.e2e.ts` proves the Loader boot + mock-model tool round-trip through `stream-json` records instead of readline transcript lines.
+- The CI demo-smoke gate (`scripts/run-gates.ts`, AGENTS.md) runs `demo:echo --output-format stream-json -p "echo ci smoke"` and parses the records structurally.
+- The TUI's piped-launch refusal (nonzero exit + pointer at the one-shot CLI) is covered by `apps/cli/tests/built-bin.e2e.ts` (the `dsh` TTY guard under plain Node); the echo-round-trip-under-plain-Node and missing-config fail-loud proofs live in `cli-demo`'s built-bin suite.
+- `packages/context/time-context/tests/time-context.e2e.ts` runs one one-shot turn; multi-turn elapsed rendering stays unit-covered in its spec.
+
+## Accepted losses
+
+- **Piped multi-turn in one process** — the readline channel could script several turns over stdin; the one-shot bin runs one task per process. Multi-turn continuity is covered by `RESUME_SESSION_ID`/resume e2es and the TUI's scripted PTY conversation.
+- **Non-TTY `ask_user_question`** — the readline provider was the only non-TTY terminal implementation of `ctx.userInteraction`. A headless or ACP automation run whose model calls `ask_user_question` fails that tool call unless its composition supplies a provider; Web owns the shipped non-terminal provider.
+
+## Alternatives considered
+
+- **Keep `dsh-stdio` as a pipe/automation channel without the repl demo** — rejected: its automation role duplicated `dsh-cli-demo` with a weaker contract (unstructured transcript, EOF-exit heuristics vs. one durable turn ending and format-pure output).
+- **Rewrite the piped smokes as PTY drivers** — rejected: PTY is the flakier, more complex medium and is reserved for the one surface pipes cannot prove (real TTY takeover/restore).
+
+## Consequences
+
+- One interactive front door (TUI), one automation front door (one-shot CLI), two servers; no mode-selection seam in the terminal app.
+- ~1,000 lines of readline unit tests deleted with their behavior; the readline transcript grammar is gone from all gates.
+- This supersedes the packaging half of [fold the stdio UI helper](2026-07-04-fold-stdio-ui-helper.md) (the folded package is now deleted) and amends the composition described in [the TUI front-door note](../feature/2026-07-17-dedicated-full-screen-tui-front-door.md) (no `auto` selection; `tui-agent` owns the coding composition).
diff --git a/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.zh.md b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.zh.md
new file mode 100644
index 0000000000..ea97ee79a2
--- /dev/null
+++ b/.agents/notes/archived/simplification/2026-07-20-retire-readline-front-door.zh.md
@@ -0,0 +1,47 @@
+# Agent Note: 退役 readline 前端与 repl-agent 示例
+
+Status: implemented
+Archived: 2026-07-26
+
+[English](2026-07-20-retire-readline-front-door.md) | 中文
+
+## 问题
+
+仓库同时提供两个交互式终端前端:面向行的 readline 通道(`@deepseek-ai/dsh-stdio`)和全屏的 [`@deepseek-ai/dsh-tui`](../feature/2026-07-17-dedicated-full-screen-tui-front-door.md)。TUI 落地之后,readline 的交互角色已经冗余——`demo:tui` 作为编码 agent 体验取代了 `demo:repl`——而它剩下的真实角色(管道与自动化)已由单次任务的 `@deepseek-ai/dsh-cli-demo` 应用以更好的方式承担(任务输入、DSH 原生 `text`/`json`/`stream-json` 输出、持久化、信号处理)。
+
+这种重复是结构性的,不只是表面问题:`dsh-stdio-demo` 携带一个 `TerminalMode`(`auto`/`readline`/`tui`)选择接缝、约 1,000 行 readline 单元测试、一套被 CI 演示冒烟测试和两个 built-bin e2e 用 grep 匹配的 readline 文本记录语法(`[tool call] …` 行),以及一个倒置的示例组合:旗舰 `tui-agent` 叶节点被定义为对它所取代的 `repl-agent` 叶节点的 include patch。
+
+## 决定
+
+删除 readline 前端和 repl-agent 示例;只保留三类前端原型:**交互式 TUI**(仅 TTY,管道下快速失败)、**单次任务 CLI**(`-p`/位置参数任务,服务管道与自动化)以及**服务器**(ACP / JSON-RPC)。
+
+- `packages/ui/stdio` 与 `examples/repl-agent` 已删除。`packages/examples/stdio-demo` 更名为 `@deepseek-ai/dsh-tui-demo`(`packages/examples/tui-demo`)并始终挂载 `dsh-tui`;`TerminalMode`/`resolveTerminalMode`/`ui.mode` 接缝随之删除。bin 在**启动 loader 之前**就拒绝非 TTY 流(Loader 树内组合期抛出的异常按条目记录日志而不会重新抛出,管道启动否则会沉降为一个空闲的无 UI 进程而不是以非零码退出)。
+- `examples/tui-agent/cordis.yml` 现在内联拥有编码组合(include patch 倒置消失);其 Code Mode 覆盖层 include 自己的基础配置。`examples/cordis-agent` 迁移到 TUI 应用。
+- `examples/echo-agent` 迁移到单次任务的 `dsh-cli-demo` 应用;`dsh-cli-demo` 新增 `-p/--prompt` 作为单个任务的旗标形式(与位置参数互斥)。
+- 与 UI 无关的带密钥编码 e2e(`full-loop`、`coding-task`、`resume`、`compaction`、`todo-write`、`code-mode` 及其共享 harness)原样从 `examples/repl-agent/tests/` 移入 `examples/tui-agent/tests/`——它们以编程方式组装整个栈,从不接触任何 UI。
+- SDK 向导的 `stdio` 运行接口改为 `tui`(`RunInterface = 'acp' | 'tui' | 'embed'`),贡献 `dsh-tui` 配置项而不是 `dsh-stdio`;生成的 `index.ts` 在 `startSDK` 之前检查 TTY,理由与 tui-demo bin 的启动前快速失败相同。
+
+### 测试策略:PTY 仅用于 TUI
+
+管道仍是默认测试介质。PTY 驱动的子进程测试**仅**在被测对象就是 TUI 本身时获准使用:`examples/tui-agent/tests/tui-keyless-smoke.e2e.ts`(新增 Code Mode 覆盖层启动场景,取代 repl-agent 的管道冒烟测试成为该覆盖层的无密钥组合证明)和 `examples/cordis-agent` 中最小的 PTY 启动冒烟测试(其前端就是 TUI)。其余全部改为通过单次任务 bin 走管道:
+
+- `examples/echo-agent/tests/echo.e2e.ts` 通过 `stream-json` 记录证明 Loader 启动 + mock 模型的工具往返,而不是匹配 readline 文本记录行。
+- CI 演示冒烟门禁(`scripts/run-gates.ts`、AGENTS.md)运行 `demo:echo --output-format stream-json -p "echo ci smoke"` 并结构化解析记录。
+- TUI 对管道启动的拒绝(非零退出 + 指向单次任务 CLI 的提示)由 `apps/cli/tests/built-bin.e2e.ts`(纯 Node 下的 `dsh` TTY 守卫)覆盖;纯 Node 下的 echo 往返证明与缺失配置的快速失败证明位于 `cli-demo` 的 built-bin 套件。
+- `packages/context/time-context/tests/time-context.e2e.ts` 运行一个单次任务轮次;多轮 elapsed 渲染仍由其单元测试覆盖。
+
+## 接受的损失
+
+- **单进程内的管道多轮对话**——readline 通道可以通过 stdin 脚本化多个轮次;单次任务 bin 每个进程只运行一个任务。多轮连续性由 `RESUME_SESSION_ID`/resume e2e 和 TUI 的脚本化 PTY 对话覆盖。
+- **非 TTY 的 `ask_user_question`**——readline 提供方是 `ctx.userInteraction` 唯一的非 TTY 终端实现。模型调用 `ask_user_question` 的 headless 或 ACP 自动化运行会让该工具调用失败,除非其组合提供相应的 provider;Web 拥有已交付的非终端 provider。
+
+## 曾考虑的替代方案
+
+- **保留 `dsh-stdio` 作为纯管道/自动化通道而只删 repl 演示**——不予采纳:它的自动化角色以更弱的契约重复了 `dsh-cli-demo`(非结构化文本记录、EOF 退出的启发式判断,对比后者的一次持久轮次结束和格式纯净输出)。
+- **把管道冒烟测试改写为 PTY 驱动**——不予采纳:PTY 是更易波动、更复杂的介质,仅保留给管道无法证明的那一个表面(真实 TTY 的接管/恢复)。
+
+## 后果
+
+- 一个交互式前端(TUI)、一个自动化前端(单次任务 CLI)、两个服务器;终端应用不再有模式选择接缝。
+- 约 1,000 行 readline 单元测试随其行为一起删除;readline 文本记录语法从所有门禁中消失。
+- 本决定取代 [fold the stdio UI helper](2026-07-04-fold-stdio-ui-helper.md) 的打包部分(被折叠的包现已删除),并修订 [TUI 前端 Agent Note](../feature/2026-07-17-dedicated-full-screen-tui-front-door.md) 描述的组合(不再有 `auto` 选择;`tui-agent` 拥有编码组合)。
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml
similarity index 63%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml
index 62bf9574c0..ca9895d9ed 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml
@@ -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-21-tui-remove-cancel-command.md: f9bad74e7b8f04a162a32e8045d2f874991b9d5d
-2026-07-21-tui-remove-cancel-command.zh.md: 6a4c0af1d2ac345afd775566db21f7a7c0b262da
+2026-07-21-tui-remove-cancel-command.md: eba4ada458cd7926cc06130d081b669da514fe79
+2026-07-21-tui-remove-cancel-command.zh.md: 9954af56682591287abc955d9f2485f02e1ce6b8
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.md b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.md
rename to .agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.md
index f9bad74e7b..eba4ada458 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.md
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.md
@@ -1,6 +1,7 @@
# Agent Note: Drop the TUI `/cancel` slash command
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-remove-cancel-command.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.zh.md b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.zh.md
rename to .agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.zh.md
index 6a4c0af1d2..9954af5668 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-remove-cancel-command.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-remove-cancel-command.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Drop the TUI `/cancel` slash command
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-remove-cancel-command.md) | 中文
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml
rename to .agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml
index 4e0393bede..5f3c3b7d0d 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml
@@ -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-21-tui-todo-write-opt-in.md: f89f76a462f4d30960254833ab71973f6a4f7655
-2026-07-21-tui-todo-write-opt-in.zh.md: f80d2639612819975f03aea9771019cd5237a2ee
+2026-07-21-tui-todo-write-opt-in.md: fd2ad9bb89bd20ffddf4d509e427a34c5ec0cf2b
+2026-07-21-tui-todo-write-opt-in.zh.md: 5b1a7f19a48c87686cfc2052d4ad19ed45ab1fb9
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.md b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.md
rename to .agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.md
index f89f76a462..fd2ad9bb89 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.md
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.md
@@ -1,6 +1,7 @@
# Agent Note: Ship the TUI without `todo_write`; keep it a one-line opt-in
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-21-tui-todo-write-opt-in.zh.md)
diff --git a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.zh.md b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.zh.md
similarity index 99%
rename from .agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.zh.md
rename to .agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.zh.md
index f80d263961..5b1a7f19a4 100644
--- a/.agents/notes/implemented/simplification/2026-07-21-tui-todo-write-opt-in.zh.md
+++ b/.agents/notes/archived/simplification/2026-07-21-tui-todo-write-opt-in.zh.md
@@ -1,6 +1,7 @@
# Agent Note: Ship the TUI without `todo_write`; keep it a one-line opt-in
Status: implemented
+Archived: 2026-07-26
[English](2026-07-21-tui-todo-write-opt-in.md) | 中文
diff --git a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml
similarity index 71%
rename from .agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml
rename to .agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml
index 9ea114c43e..ed26cc462d 100644
--- a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml
+++ b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml
@@ -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-redundant-snapshot-log-expected-output.md: c2452f971d3cb76dceb766072dbc0a5c81465e78
-2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md: e6175181589eabae064c34044f353efae537961b
+2026-06-20-remove-redundant-snapshot-log-expected-output.md: 306e1c67cc8370b629bd83c0335baf84037938f5
+2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md: 552fbb36fcea5dca5e9699348caa3a4fef1bb293
diff --git a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md
rename to .agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md
index c2452f971d..306e1c67cc 100644
--- a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md
+++ b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md
@@ -1,6 +1,7 @@
# Agent Note: Use `session.jsonl` as the only snapshot session-log artifact
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md)
diff --git a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md
rename to .agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md
index e617518158..552fbb36fc 100644
--- a/.agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md
+++ b/.agents/notes/archived/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 使用 `session.jsonl` 作为唯一的快照会话日志产物
Status: implemented
+Archived: 2026-07-26
[English](2026-06-20-remove-redundant-snapshot-log-expected-output.md) | 中文
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml
similarity index 64%
rename from .agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml
rename to .agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml
index 2e87edf31e..ca654248d0 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml
+++ b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.i18n.yaml
@@ -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-22-fork-snapshot-scenarios.md: 46c688a4095a1d8af32b3b99887929f71a1526ce
-2026-06-22-fork-snapshot-scenarios.zh.md: 8823611ec02f269e5a21a8065c9ed3cc3911f749
+2026-06-22-fork-snapshot-scenarios.md: 806ce2c1f3681810f609141936741c199e48fb67
+2026-06-22-fork-snapshot-scenarios.zh.md: 72f242784efef56f38590c393935e2d344cb89e2
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.md b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.md
rename to .agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.md
index 46c688a409..806ce2c1f3 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.md
+++ b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.md
@@ -1,6 +1,7 @@
# Agent Note: Record fork and mixed spawn+fork snapshot scenarios
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-06-22-fork-snapshot-scenarios.zh.md)
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.zh.md b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.zh.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.zh.md
rename to .agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.zh.md
index 8823611ec0..72f242784e 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.zh.md
+++ b/.agents/notes/archived/testing/2026-06-22-fork-snapshot-scenarios.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 记录 fork 与混合 spawn+fork 快照场景
Status: implemented
+Archived: 2026-07-26
[English](2026-06-22-fork-snapshot-scenarios.md) | 中文
diff --git a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml
similarity index 65%
rename from .agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml
rename to .agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml
index 83bd9197d8..106e643572 100644
--- a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml
+++ b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.i18n.yaml
@@ -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-hook-snapshot-matrix.md: 98f9db27fd8afaaa99c9985dff4d148ef4eb926b
-2026-07-04-hook-snapshot-matrix.zh.md: 40b9ee84ad7232ac806096375e8da42bd02bfddf
+2026-07-04-hook-snapshot-matrix.md: 6c7229ee3b13867e01f657528265aabc2e4430df
+2026-07-04-hook-snapshot-matrix.zh.md: 7e46626db0234e269796338e8e0a4406547e1b71
diff --git a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.md b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.md
rename to .agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.md
index 98f9db27fd..6c7229ee3b 100644
--- a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.md
+++ b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.md
@@ -1,6 +1,7 @@
# Agent Note: Hook snapshot matrix — end-to-end expected outputs for both bridges
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-hook-snapshot-matrix.zh.md)
diff --git a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.zh.md b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.zh.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.zh.md
rename to .agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.zh.md
index 40b9ee84ad..7e46626db0 100644
--- a/.agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.zh.md
+++ b/.agents/notes/archived/testing/2026-07-04-hook-snapshot-matrix.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 钩子快照矩阵——覆盖两种 bridge 的端到端预期输出测试
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-hook-snapshot-matrix.md) | 中文
diff --git a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml
similarity index 61%
rename from .agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml
rename to .agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml
index 719884e56c..e596c0ae47 100644
--- a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml
+++ b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.i18n.yaml
@@ -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-single-source-acp-replay-config.md: f270d70feca184217503c472c1cb7c536187a249
-2026-07-04-single-source-acp-replay-config.zh.md: 86eb2d3e15c4939d1de3a78150cd0536d46e10f6
+2026-07-04-single-source-acp-replay-config.md: 7d6217fe781209d449d87bb2e0411159e5f7eaa0
+2026-07-04-single-source-acp-replay-config.zh.md: a040c14e6e08f9c60e9ad53449ad66c1203831c2
diff --git a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.md b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.md
rename to .agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md
index f270d70fec..7d6217fe78 100644
--- a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.md
+++ b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md
@@ -1,6 +1,7 @@
# Agent Note: Single-source the acp-agent replay config
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-04-single-source-acp-replay-config.zh.md)
diff --git a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.zh.md b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.zh.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.zh.md
rename to .agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.zh.md
index 86eb2d3e15..a040c14e6e 100644
--- a/.agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.zh.md
+++ b/.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 将 acp-agent 回放配置改为单一来源
Status: implemented
+Archived: 2026-07-26
[English](2026-07-04-single-source-acp-replay-config.md) | 中文
diff --git a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml
similarity index 59%
rename from .agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml
rename to .agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml
index 2b7c1cc937..fdc781de9e 100644
--- a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml
+++ b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml
@@ -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-06-pin-request-header-content-in-one-scenario.md: bca6d9eb943e758d68efaf3a76ec367179cd15fd
-2026-07-06-pin-request-header-content-in-one-scenario.zh.md: e01c84c63583e2fe14b0b4fe0381a18b209d2346
+2026-07-06-pin-request-header-content-in-one-scenario.md: cadb4f74a1e8556eb32a72285b78e1339f457514
+2026-07-06-pin-request-header-content-in-one-scenario.zh.md: 5fdc38b9aa4cdce54026f23ad375b81d96b9c8c0
diff --git a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md
rename to .agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md
index bca6d9eb94..cadb4f74a1 100644
--- a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md
+++ b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md
@@ -1,6 +1,7 @@
# Agent Note: Pin request-header content in one snapshot scenario
Status: implemented
+Archived: 2026-07-26
English | [中文](2026-07-06-pin-request-header-content-in-one-scenario.zh.md)
diff --git a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md
similarity index 99%
rename from .agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md
rename to .agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md
index e01c84c635..5fdc38b9aa 100644
--- a/.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md
+++ b/.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md
@@ -1,6 +1,7 @@
# Agent Note: 在单个快照场景中固定请求头内容
Status: implemented
+Archived: 2026-07-26
[English](2026-07-06-pin-request-header-content-in-one-scenario.md) | 中文
diff --git a/.agents/notes/implemented/AGENTS.md b/.agents/notes/implemented/AGENTS.md
index c34e1a49b8..b8da5dc8ef 100644
--- a/.agents/notes/implemented/AGENTS.md
+++ b/.agents/notes/implemented/AGENTS.md
@@ -6,6 +6,8 @@ These Agent Notes describe shipped decisions. Follow the [root instructions](../
Keep paths, symbols, defaults, and mechanisms current in the same change that alters them. Rewrite stale facts in place; do not append change history.
+When a shipped note is unlikely to guide future work, archive its complete triplet through [`dsh-archive-agent-notes`](../../skills/dsh-archive-agent-notes/SKILL.md) instead of continuing to maintain it.
+
### This is not a license to rewrite the *decision*
Update factual realization in place. A reversal of the decision or its rationale requires a new Agent Note and cross-link; a fully superseded old note may be deleted only through the consolidation rule in the [Agent Note contract](../README.md).
diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml
index 01823c55d0..b8d807098d 100644
--- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml
@@ -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-11-content-block-vocabulary.md: 9aad01cee6083b1f380be66869af3137a07d9f1f
-2026-06-11-content-block-vocabulary.zh.md: 5720f0742a0729a3f98e4b05ab37acf97ae78db5
+2026-06-11-content-block-vocabulary.md: d926c28e7e197aff28c7b1c09d085febf866832b
+2026-06-11-content-block-vocabulary.zh.md: 6361f00abe109bffdb5bd3ff5652df67d6b3c8a1
diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md
index 9aad01cee6..d926c28e7e 100644
--- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md
+++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md
@@ -23,6 +23,6 @@ In-session context injection (`context/message`) and mid-turn steering (`steerin
- Reasoning has a core home without provider-specific shapes.
- Multimodal blocks return only with coordinated adapter, UI, and compaction support; see [the drop-image Agent Note](../simplification/2026-07-04-drop-image-content-block.md).
-- Cache hints and assistant prefill remain absent until a shipping adapter can honor them; see the [producer-less variants](../simplification/2026-07-04-prune-producerless-vocabulary-variants.md) and [inert request knobs](../simplification/2026-07-04-drop-inert-request-knobs.md) Agent Notes.
+- Cache hints and assistant prefill remain absent until a shipping adapter can honor them; see the [producer-less variants](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md) and [inert request knobs](../../archived/simplification/2026-07-04-drop-inert-request-knobs.md) Agent Notes.
- Every adapter pays a translation cost; the first real adapters have since validated the streaming protocol, and new adapters should continue proving their provider-specific mapping in adapter-local tests.
- IDs that cross package boundaries are branded (`CallId`, the shared agent/session `SessionId`) — nominal typing at zero runtime cost.
diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md
index 5720f0742a..6361f00abe 100644
--- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md
+++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md
@@ -23,6 +23,6 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循
- 推理(reasoning)在核心层有了归属,无需依赖提供方特有的结构。
- 多模态块只有在适配器、UI 和上下文压缩(context compaction)三方协同支持后才会回归;见 [drop-image Agent Note](../simplification/2026-07-04-drop-image-content-block.md)。
-- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见[无生产者的词汇变体](../simplification/2026-07-04-prune-producerless-vocabulary-variants.md)与[无端到端可用路径的请求旋钮](../simplification/2026-07-04-drop-inert-request-knobs.md) Agent Note。
+- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见[无生产者的词汇变体](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)与[无端到端可用路径的请求旋钮](../../archived/simplification/2026-07-04-drop-inert-request-knobs.md) Agent Note。
- 每个适配器都需承担翻译成本;首批真实适配器已验证了流式输出协议,新适配器应继续在适配器本地测试中验证其提供方特有的映射。
- 跨包(package)边界的 ID 使用品牌类型(`CallId`、agent 与会话共享的 `SessionId`)——零运行时开销的名义类型。
diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml
index 33a0bce890..a29fa6e073 100644
--- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml
@@ -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-14-session-persistence.md: 52434930bb662b0c97e61f7c2f69b67c309b6317
-2026-06-14-session-persistence.zh.md: 143b58d32191108d7ba24b489bd4f898b1547aab
+2026-06-14-session-persistence.md: 75e13b860f621ed407849b3b4c62ff7287ab4812
+2026-06-14-session-persistence.zh.md: a6bd400a053779c742940236737447d1687622de
diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md
index 52434930bb..75e13b860f 100644
--- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md
+++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md
@@ -15,11 +15,11 @@ The [event-sourced model](2026-06-11-event-sourced-sessions.md) makes the append
Persistence is an abstract **capability seam** ([capability seams](2026-06-13-capability-seams.md), the `dsh-bash` template), not loop or core logic:
1. **Interface** (`dsh-session-persistence`, `ctx.sessionPersistence`) — an abstract `SessionPersistence` service: `create`/`append`/`load`/`list`. Its persisted unit IS the existing `SessionEvent` (`{ type, seq, time, data }`), reused verbatim — no conversion type.
-2. **Implementation** (`dsh-session-persistence-jsonl`) — an append-only logical JSONL log per session (a `SessionHeader` line then one `SessionEvent` per line, verbatim **including `assistant/chunk`**), encoded as [checksummed Zstandard frames by default](2026-07-19-zstandard-jsonl-session-logs.md) or raw lines by configuration.
+2. **Implementation** (`dsh-session-persistence-jsonl`) — an append-only logical JSONL log per session: a `SessionHeader` line followed by storage records that losslessly represent the contiguous `SessionEvent` stream. Eligible `assistant/chunk` delta runs use packed rows by default; [checksummed Zstandard frames](2026-07-19-zstandard-jsonl-session-logs.md) are the default physical encoding, with raw lines configurable.
Key choices recorded here because they are durable, contested, and surprising:
-- **The canonical durable log persists every `SessionEvent` verbatim, including `assistant/chunk`.** `deriveMessages()` skips chunks, and a chunk-filtered rollout (Codex's `policy.rs`) is tempting — but `seq = log.length` and the load-validation `events[i].seq === i` require a *contiguous* log; filtering chunks out would leave holes and break both the contract and resume. A chunk-filtered projection is possible later as a derived view with its own renumbering, but it is NOT the canonical log.
+- **The canonical durable log persists every `SessionEvent` losslessly, including `assistant/chunk`.** JSONL storage may encode a consecutive delta run as one packed row, but `load` reconstructs the exact event boundaries, sequence numbers, and timestamps. `deriveMessages()` skips chunks, and a chunk-filtered rollout (Codex's `policy.rs`) is tempting — but `seq = log.length` and the load-validation `events[i].seq === i` require a *contiguous* logical log; filtering chunks out would leave holes and break both the contract and resume. A chunk-filtered projection is possible later as a derived view with its own renumbering, but it is NOT the canonical log.
- **Append-only; a crashed turn is closed, never truncated.** Flushed events are never rewritten. The [semantic checkpoint policy](../bug-fix/2026-07-21-semantic-session-checkpoints.md) drains the request before model dispatch, a recorded top-level call before tool dispatch, and the complete response/result batch after a step; the loop drains the final turn boundary. Because one interrupted turn may contain substantial valid work, `load` preserves its contiguous, parseable events and appends risk-classified error results for unanswered assistant calls, a missing `step/end`, and `turn/end` with `{ kind: 'interrupted' }`. The synthetic results keep resumed provider transcripts valid. Only an incomplete final record is discarded; a parse error or sequence gap at or before the last real `turn/end` is corruption and makes the session unloadable.
- **File backend canonical, DB backend a proven drop-in.** `SessionEvent` maps 1:1 onto a row `(session_id, seq, type, time, data)` — `append` is INSERT (in a transaction asserting the contiguous-seq contract), `load` is SELECT … ORDER BY seq. `dsh-session-persistence-sqlite` is exactly this: a `SessionPersistence` subclass with no interface change (opencode runs this exact shape on SQLite/WAL), and it passes the same `runPersistenceContract` suite as the JSONL backend — so the contract holds both backends to identical semantics (lazy materialization, interrupted-turn close on load, contiguous-seq), expressed once over file bytes and once over rows. Its database carries a dedicated application id and monotonic schema version. A pristine file creates all tables and stamps both header values in one transaction; an unversioned file with any user-defined schema object or application identity, a foreign current-version identity, and every non-current version reject before journal-mode mutation.
- **Metadata is out-of-log.** Format version, cwd, and lineage are storage concerns, not replayable conversation state, so they live in a `SessionHeader` owned by `dsh-session` and attached to a `Session` via a new readonly `session.header` — never in `SessionEventMap`, never reaching `deriveMessages()`. `createdAt` is non-negative safe-integer Unix epoch milliseconds: live creation and persistence registration reject fractional values, JSONL validates the decoded header, and SQLite stores it in a strict `INTEGER` column. The alternative (a merge-extensible `session/meta` event as log line 0) was rejected: an in-log event would ride along with a seeded/forked session for free, but metadata is not replayable state, so the explicit out-of-log header seam is the cleaner cost. (The header was originally split into an immutable `SessionHeader` plus a mutable `SessionSummary` whose union was `SessionMeta`; the mutable summary was later removed as dead state — see [Drop the mutable session summary](../simplification/2026-06-19-drop-mutable-session-summary.md).)
@@ -33,4 +33,4 @@ Format versioning: the header carries a `version`; `load` rejects any non-curren
## Consequences
-Two new packages and the metadata seam in `dsh-session` (`session.header`, the `create(id?, options?)` signature). Bought: durable resume/fork, a read/replay path, crash tolerance, and host-side session access over the existing event-sourced log, with the backend swappable behind one interface. The reusable `runPersistenceContract` suite holds every backend to the same append-only, contiguous-seq, lazy-materialization, integer-metadata, and serializability semantics. Persisting the full log also settles event fidelity: `assistant/chunk` remains verbatim. SQLite initialization either commits its complete owned schema and header identity or leaves no partial schema to strand on the next open.
+Two new packages and the metadata seam in `dsh-session` (`session.header`, the `create(id?, options?)` signature). Bought: durable resume/fork, a read/replay path, crash tolerance, and host-side session access over the existing event-sourced log, with the backend swappable behind one interface. The reusable `runPersistenceContract` suite holds every backend to the same append-only, contiguous-seq, lazy-materialization, integer-metadata, and serializability semantics. Persisting the full logical log also settles event fidelity: every `assistant/chunk` survives exactly even when JSONL packs several into one storage row. SQLite initialization either commits its complete owned schema and header identity or leaves no partial schema to strand on the next open.
diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md
index 143b58d321..a6bd400a05 100644
--- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md
+++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md
@@ -15,11 +15,11 @@ Status: implemented
持久化是一个抽象的**能力 seam**([能力 seam](2026-06-13-capability-seams.md),`dsh-bash` 模板),而非循环或核心逻辑:
1. **接口**(`dsh-session-persistence`,`ctx.sessionPersistence`):一个抽象的 `SessionPersistence` 服务,提供 `create`/`append`/`load`/`list`。其持久化单元就是现有的 `SessionEvent`(`{ type, seq, time, data }`),原样复用,无转换类型。
-2. **实现**(`dsh-session-persistence-jsonl`):每个会话一个仅追加的逻辑 JSONL 日志(一行 `SessionHeader`,之后每行一个 `SessionEvent`,逐字节保留,**包括 `assistant/chunk`**),默认编码为[带校验和的 Zstandard 帧](2026-07-19-zstandard-jsonl-session-logs.md),也可通过配置使用原始行。
+2. **实现**(`dsh-session-persistence-jsonl`):每个会话一个仅追加的逻辑 JSONL 日志:先是一行 `SessionHeader`,随后是无损表示连续 `SessionEvent` 流的存储记录。符合条件的 `assistant/chunk` 增量连续段默认使用打包行;[带校验和的 Zstandard 帧](2026-07-19-zstandard-jsonl-session-logs.md)是默认物理编码,也可通过配置使用原始行。
以下关键选择记录于此,因为它们是持久性的、有争议的、且出人意料的:
-- **规范的持久日志逐字节保留每个 `SessionEvent`,包括 `assistant/chunk`。** `deriveMessages()` 跳过分片,而过滤分片的方案(Codex 的 `policy.rs`)很有吸引力,但 `seq = log.length` 以及加载验证 `events[i].seq === i` 要求日志是*连续*的;过滤掉分片会留下空洞,同时破坏契约和恢复功能。基于分片过滤的投影可以作为派生视图在后续实现(带有自己的重新编号),但它不是规范日志。
+- **规范的持久日志无损保留每个 `SessionEvent`,包括 `assistant/chunk`。** JSONL 存储可以将一段连续的增量事件编码为一条打包行,但 `load` 会重建精确的事件边界、序号与时间戳。`deriveMessages()` 跳过分片,而过滤分片的方案(Codex 的 `policy.rs`)很有吸引力,但 `seq = log.length` 以及加载验证 `events[i].seq === i` 要求*连续*的逻辑日志;过滤掉分片会留下空洞,同时破坏契约和恢复功能。基于分片过滤的投影可以作为派生视图在后续实现(带有自己的重新编号),但它不是规范日志。
- **仅追加;崩溃的轮次被关闭,而非截断。** 已刷写的事件永不被重写。[语义检查点策略](../bug-fix/2026-07-21-semantic-session-checkpoints.md)会在模型分发前排空请求、在工具分发前排空已记录的顶层调用,并在步骤结束后排空完整的响应/结果批次;循环则排空最终轮次边界。由于一个被中断的轮次可能包含大量有效工作,`load` 保留其连续、可解析的事件,并为未应答的 assistant 调用追加按风险分类的错误结果、补一个缺失的 `step/end`,以及带 `{ kind: 'interrupted' }` 的 `turn/end`。合成的结果保证恢复后的提供方 transcript(文本记录)仍然有效。只有不完整的最后一条记录会被丢弃;在最后一个真实 `turn/end` 处或之前出现解析错误或序号间隙,属于数据损坏,会使该会话不可加载。
- **文件后端为规范实现,数据库后端为经过验证的直接替换。** `SessionEvent` 1:1 映射到一行 `(session_id, seq, type, time, data)`:`append` 是 INSERT(在一个断言连续 seq 契约的事务中),`load` 是 SELECT … ORDER BY seq。`dsh-session-persistence-sqlite` 正是如此:一个 `SessionPersistence` 子类,接口无变化(opencode 在 SQLite/WAL 上运行的正是这个形状),且通过与 JSONL 后端相同的 `runPersistenceContract` 测试套件。该契约以相同的语义约束两个后端(惰性物化、加载时关闭中断轮次、连续 seq),一次表达在文件字节上,一次表达在数据库行上。其数据库拥有专用的 application id 与单调递增的 schema 版本。系统会在一个事务中为全新文件创建所有表并写入这两个 header 值;未版本化文件若带有任何用户定义的 schema 对象或应用标识、当前版本文件若带有外部应用标识,以及任何非当前版本文件,都会在修改日志模式之前被拒绝。
- **元数据在日志之外。** 格式版本、cwd 和谱系是存储关注点,不是可回放的对话状态,因此它们存放在 `dsh-session` 拥有的 `SessionHeader` 中,并通过新的只读属性 `session.header` 附加到 `Session` 上——永远不进入 `SessionEventMap`,永远不到达 `deriveMessages()`。`createdAt` 是以 Unix epoch 毫秒表示的非负安全整数:运行时创建和持久化注册会拒绝小数值,JSONL 会验证解码后的 header,SQLite 则将其存入严格的 `INTEGER` 列。替代方案(一个可合并扩展的 `session/meta` 事件作为日志第 0 行)被否决:日志内事件会随 seed/fork 的会话免费携带,但元数据不是可回放状态,因此显式的日志外 header seam 是更干净的代价。(header 最初被拆分为不可变的 `SessionHeader` 加可变的 `SessionSummary`,二者的联合类型为 `SessionMeta`;可变 summary 后来因属于死状态而被移除——见 [移除可变会话摘要](../simplification/2026-06-19-drop-mutable-session-summary.md)。)
@@ -33,4 +33,4 @@ Status: implemented
## 后果
-新增两个包(package),以及 `dsh-session` 中的元数据 seam(`session.header`,`create(id?, options?)` 签名)。收益:持久恢复/fork、读取/回放路径、崩溃容忍,以及基于现有事件溯源日志的宿主侧会话访问,后端在一个接口之后可替换。可复用的 `runPersistenceContract` 测试套件以相同的仅追加、连续 seq、惰性物化、整数元数据与可序列化语义约束每个后端。持久化完整日志还确定了事件保真度:`assistant/chunk` 保持逐字节不变。SQLite 初始化要么提交完整的自有 schema 与 header 标识,要么不留下任何会使下次打开受阻的部分 schema。
+新增两个包(package),以及 `dsh-session` 中的元数据 seam(`session.header`,`create(id?, options?)` 签名)。收益:持久恢复/fork、读取/回放路径、崩溃容忍,以及基于现有事件溯源日志的宿主侧会话访问,后端在一个接口之后可替换。可复用的 `runPersistenceContract` 测试套件以相同的仅追加、连续 seq、惰性物化、整数元数据与可序列化语义约束每个后端。持久化完整的逻辑日志还确定了事件保真度:即使 JSONL 将多个 `assistant/chunk` 打包到一条存储行中,每个事件也会精确保留。SQLite 初始化要么提交完整的自有 schema 与 header 标识,要么不留下任何会使下次打开受阻的部分 schema。
diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml
index 6f85d08fe8..530e207654 100644
--- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml
@@ -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-17-filesystem-capability-seam.md: 08e8d52b314eb10e2c7ec444dd61a96d8621e032
-2026-06-17-filesystem-capability-seam.zh.md: 6f4889234516ee134c9873781a874b5f1a3644ac
+2026-06-17-filesystem-capability-seam.md: fee0161e5e8397ac1d1c0e2850efad840c65d971
+2026-06-17-filesystem-capability-seam.zh.md: ee50b36d25315c3d8daed4502bc248977f9e6011
diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md
index 08e8d52b31..fee0161e5e 100644
--- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md
+++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md
@@ -32,7 +32,7 @@ The read-before-write/edit and observed-state policy is a fourth package, `@deep
The first backend is deliberately local-only: `dsh-fs-local` implements `ctx.fs` against the host filesystem. Future sibling backends can provide sandboxed, remote, virtual, or project-scoped filesystems behind the same interface.
-The first consumer is deliberately text-file-only: `dsh-tool-fs` exposes model-facing `read`, `write`, and `edit` tools for UTF-8 text files. Future consumers can add directory listing, search/glob, binary-safe operations, file watching, or higher-level project operations without changing the local backend package, as long as the needed capability exists on `ctx.fs`. Direct directory listing was later added by [Add direct directory listing to the filesystem seam](2026-07-03-filesystem-directory-listing-seam.md).
+The first consumer is deliberately text-file-only: `dsh-tool-fs` exposes model-facing `read`, `write`, and `edit` tools for UTF-8 text files. Future consumers can add directory listing, search/glob, binary-safe operations, file watching, or higher-level project operations without changing the local backend package, as long as the needed capability exists on `ctx.fs`. Direct directory listing was later added by [Add direct directory listing to the filesystem seam](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md).
Filesystem permissions and sandboxing are not implied by this split. The local backend resolves relative paths from its configured base directory, but containment policy is a separate decision: either a stricter `ctx.fs` implementation enforces it, or a permission/sandbox plugin wraps `tools/execute` and vetoes calls before they reach the consumer.
@@ -95,7 +95,7 @@ Literal edit is a provider primitive (`editText`), not composed in `tool-fs` fro
The policy plugin, not `ctx.fs`, gates on prior observation: an `edit` requires a prior observation by the owner (else `FS_NOT_OBSERVED`), and the recorded version is passed to `editText` as the CAS basis. With the policy plugin absent, `ctx.fs` alone is a complete unconstrained seam (unconditional write/edit); the tool is never method-coupled to the policy.
-Filesystem contract failures are thrown as `FsError extends HarnessError`, and the tool registry converts them into `isError` tool results with structured `{ name, code }` metadata. `dsh-fs` owns this vocabulary rather than each tool inventing messages. The codes are `FS_NOT_FOUND`, `FS_NOT_TEXT`, `FS_STALE_VERSION`, `FS_NOT_OBSERVED`, `FS_NOT_REGULAR_FILE`, `FS_AMBIGUOUS_EDIT`, `FS_EDIT_NOT_FOUND`, and `FS_ABORTED`. (An earlier draft included `FS_PARTIAL_OBSERVATION`; freshness-based authorization has no partial/full distinction, so it was dropped. Directory-listing-specific codes were added later by [Add direct directory listing to the filesystem seam](2026-07-03-filesystem-directory-listing-seam.md).)
+Filesystem contract failures are thrown as `FsError extends HarnessError`, and the tool registry converts them into `isError` tool results with structured `{ name, code }` metadata. `dsh-fs` owns this vocabulary rather than each tool inventing messages. The codes are `FS_NOT_FOUND`, `FS_NOT_TEXT`, `FS_STALE_VERSION`, `FS_NOT_OBSERVED`, `FS_NOT_REGULAR_FILE`, `FS_AMBIGUOUS_EDIT`, `FS_EDIT_NOT_FOUND`, and `FS_ABORTED`. (An earlier draft included `FS_PARTIAL_OBSERVATION`; freshness-based authorization has no partial/full distinction, so it was dropped. Directory-listing-specific codes were added later by [Add direct directory listing to the filesystem seam](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md).)
## Tool consumer behavior
diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md
index 6f48892345..ee50b36d25 100644
--- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md
+++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md
@@ -32,7 +32,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local`
第一个后端有意仅限本地:`dsh-fs-local` 基于宿主文件系统实现 `ctx.fs`。未来的兄弟后端可在同一接口之后提供沙箱、远程、虚拟或项目作用域的文件系统。
-第一个消费方有意仅限文本文件:`dsh-tool-fs` 暴露面向模型的 `read`、`write` 和 `edit` 工具,处理 UTF-8 文本文件。未来的消费方可以添加目录列表、搜索/glob、二进制安全操作、文件监视或更高层的项目操作,只要 `ctx.fs` 上存在所需能力,就无需改动本地后端包。直接目录列表后来由[为文件系统 seam 添加直接目录列举能力](2026-07-03-filesystem-directory-listing-seam.md)添加。
+第一个消费方有意仅限文本文件:`dsh-tool-fs` 暴露面向模型的 `read`、`write` 和 `edit` 工具,处理 UTF-8 文本文件。未来的消费方可以添加目录列表、搜索/glob、二进制安全操作、文件监视或更高层的项目操作,只要 `ctx.fs` 上存在所需能力,就无需改动本地后端包。直接目录列表后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)添加。
文件系统权限和沙箱并非此拆分所隐含。本地后端从其配置的基目录解析相对路径,但隔离策略是独立的决策:要么由更严格的 `ctx.fs` 实现强制执行,要么由权限/沙箱插件包装 `tools/execute` 并在调用到达消费方之前否决。
@@ -95,7 +95,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local`
策略插件(而非 `ctx.fs`)对先前观测进行门控:`edit` 要求 owner 有先前观测(否则报 `FS_NOT_OBSERVED`),记录的版本作为 CAS 基础传给 `editText`。在策略插件缺席时,`ctx.fs` 本身是一个完整的无约束 seam(无条件写入/编辑);工具从不与策略方法耦合。
-文件系统契约失败以 `FsError extends HarnessError` 抛出,工具注册表将其转换为带结构化 `{ name, code }` 元数据的 `isError` 工具结果。`dsh-fs` 拥有此词汇,而非由每个工具各自发明消息。错误码包括 `FS_NOT_FOUND`、`FS_NOT_TEXT`、`FS_STALE_VERSION`、`FS_NOT_OBSERVED`、`FS_NOT_REGULAR_FILE`、`FS_AMBIGUOUS_EDIT`、`FS_EDIT_NOT_FOUND` 和 `FS_ABORTED`。(早期草案包含 `FS_PARTIAL_OBSERVATION`;基于新鲜度的授权没有 partial/full 区分,因此已删除。目录列表相关的错误码后来由[为文件系统 seam 添加直接目录列举能力](2026-07-03-filesystem-directory-listing-seam.md)添加。)
+文件系统契约失败以 `FsError extends HarnessError` 抛出,工具注册表将其转换为带结构化 `{ name, code }` 元数据的 `isError` 工具结果。`dsh-fs` 拥有此词汇,而非由每个工具各自发明消息。错误码包括 `FS_NOT_FOUND`、`FS_NOT_TEXT`、`FS_STALE_VERSION`、`FS_NOT_OBSERVED`、`FS_NOT_REGULAR_FILE`、`FS_AMBIGUOUS_EDIT`、`FS_EDIT_NOT_FOUND` 和 `FS_ABORTED`。(早期草案包含 `FS_PARTIAL_OBSERVATION`;基于新鲜度的授权没有 partial/full 区分,因此已删除。目录列表相关的错误码后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)添加。)
## 工具消费方行为
diff --git a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml
index 70ec051143..2175305799 100644
--- a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml
@@ -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-24-web-capability-seam.md: 4f4e821fec9494fe9ea96894267707d9dd202e4d
-2026-06-24-web-capability-seam.zh.md: d7c07a8ae0365c321120102a0af401d85d7e2eae
+2026-06-24-web-capability-seam.md: b705236690859961ed69b307dbb59ebefcbd65ac
+2026-06-24-web-capability-seam.zh.md: 9b6899c922524350d2eee62140480fd76a450baa
diff --git a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md
index 4f4e821fec..b705236690 100644
--- a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md
+++ b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md
@@ -34,7 +34,7 @@ Search and fetch are separate tools but one web-access seam. `ctx.web` owns prov
This keeps the model schema stable without making plugin load order, credential state, or HMR timing part of the model-facing contract. If web search is enabled but no usable search provider exists, `web_search` remains visible and execution fails with a structured `WebError` such as `WEB_PROVIDER_UNAVAILABLE` or `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. If a provider appears after `dsh-tool-web`, the next execution can use it without changing the schema. If a provider disappears mid-call, execution fails with a structured `WebError` instead of silently choosing another provider or falling through to `UNKNOWN_TOOL`.
-The seam deliberately exposes no observation surface — no registry-change event and no aggregated capability-status query. Unavailability is a fact a caller observes by executing: `search()`/`fetch()` resolve the provider at call time and throw the structured `WebError` that names what failed. [The observation-surface Agent Note](../simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) records that judgment: derived-on-call selection and enablement-based registration leave no consumer that needs a change signal or an availability probe distinct from executing and routing the error, and a future provider-status panel reintroduces the smallest signal or query it actually consumes.
+The seam deliberately exposes no observation surface — no registry-change event and no aggregated capability-status query. Unavailability is a fact a caller observes by executing: `search()`/`fetch()` resolve the provider at call time and throw the structured `WebError` that names what failed. [The observation-surface Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) records that judgment: derived-on-call selection and enablement-based registration leave no consumer that needs a change signal or an availability probe distinct from executing and routing the error, and a future provider-status panel reintroduces the smallest signal or query it actually consumes.
## Package topology
diff --git a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md
index d7c07a8ae0..9b6899c922 100644
--- a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md
+++ b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md
@@ -34,7 +34,7 @@ Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13
这使模型 schema 保持稳定,而不将插件加载顺序、凭证状态或 HMR(热模块替换)时序纳入面向模型的契约。如果 web 搜索已启用但不存在可用的搜索提供方,`web_search` 仍然可见,执行时以结构化的 `WebError`(如 `WEB_PROVIDER_UNAVAILABLE` 或 `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)失败。如果某个提供方在 `dsh-tool-web` 之后出现,下一次执行即可使用它而无需更改 schema。如果某个提供方在调用过程中消失,执行以结构化的 `WebError` 失败,而不是静默选择另一个提供方或回退到 `UNKNOWN_TOOL`。
-该 seam 刻意不暴露任何观察面——没有注册表变更事件,也没有聚合的能力状态查询。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。
+该 seam 刻意不暴露任何观察面——没有注册表变更事件,也没有聚合的能力状态查询。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。
## 包拓扑
diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml
index c446db0d3f..c6d8176b90 100644
--- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml
@@ -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-30-event-domain-semantics.md: 1f3452cce0235718c35d71577d7013f3e647648c
-2026-06-30-event-domain-semantics.zh.md: ec2da7786e80fb6a0df9ff338d77a50e7b3ef569
+2026-06-30-event-domain-semantics.md: 75c1cac11d1bfc9aa7fba9c523eab8c0475027e8
+2026-06-30-event-domain-semantics.zh.md: a412b8735b72274252473f3218e0d57d4f814bde
diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md
index 1f3452cce0..75c1cac11d 100644
--- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md
+++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md
@@ -33,7 +33,7 @@ This vocabulary is the foundation for interception decisions, the durable `hook/
- The loop no longer emits any boundary mirror; `closeStep` appends `step/end` only and `closeTurn` appends `turn/end` only. `Session.append` owns post-commit observer containment, so a throwing boundary observer cannot change the turn outcome or starve later consumers; an acceptance or internal validation failure still escapes before the boundary enters the log.
- Tests that observed boundaries via the removed emits now observe the durable `turn/start`/`turn/end`/`step/start`/`step/end` session events — the behavior they pin (boundary ordering, step counting) is unchanged; only the feed they read moved to the canonical one. The tests that exercised a *throwing turn-boundary emit listener* were deleted, because that code path no longer exists (there is no emit to throw from). Per [AGENTS.md "tests document behavior, not golden truth"](../../../../AGENTS.md), the behavior and its test moved (or died) together.
- The loop marks the step open (`stepOpen = true`) only after `append('step/start')` returns. Internal dispatch validation runs before the log push and may reject without opening a step; post-commit `session/event` observer failures are contained inside `Session.append`. The marker therefore represents exactly the committed boundary that owes a later `step/end`.
-- The full realization of this is [the simplification Agent Note "Stop mirroring durable boundaries as agent events"](../simplification/2026-06-20-remove-agent-boundary-mirror-events.md): all four boundary mirrors are removed and every consumer reads boundaries off `session/event`. `agent/steering` (not a boundary mirror) stayed outside that Agent Note's scope and was removed by its own follow-up, [Remove the `agent/steering` mirror emit](../simplification/2026-07-04-remove-agent-steering-mirror.md) — it mirrored the durable `steering/message`.
+- The full realization of this is [the simplification Agent Note "Stop mirroring durable boundaries as agent events"](../simplification/2026-06-20-remove-agent-boundary-mirror-events.md): all four boundary mirrors are removed and every consumer reads boundaries off `session/event`. `agent/steering` (not a boundary mirror) stayed outside that Agent Note's scope and was removed by its own follow-up, [Remove the `agent/steering` mirror emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md) — it mirrored the durable `steering/message`.
- The cordis events catalog (`docs/cordis-catalog/events.md`) is regenerated to drop the mirror events.
diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md
index ec2da7786e..a412b8735b 100644
--- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md
+++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md
@@ -33,7 +33,7 @@ harness 通过 Cordis 事件分类体系扩展 agent loop(智能体循环)
- 循环不再 emit 任何边界镜像;`closeStep` 仅追加 `step/end`,`closeTurn` 仅追加 `turn/end`。`Session.append` 负责 post-commit observer 隔离,因此抛出异常的边界 observer 无法改变轮次结果或饿死后续消费方;接受或内部校验失败仍会在边界进入日志之前逃逸。
- 之前通过已移除 emit 观察边界的测试,现在观察持久的 `turn/start`/`turn/end`/`step/start`/`step/end` 会话事件——它们固定的行为(边界顺序、步骤计数)不变;只是读取的源移到了规范源。那些测试*抛出异常的轮次边界 emit 监听器*的用例被删除,因为该代码路径不再存在(没有 emit 可供抛出)。按照 [AGENTS.md「测试记录行为,而非黄金真相」](../../../../AGENTS.md),行为与其测试一同迁移(或一同消亡)。
- 循环仅在 `append('step/start')` 返回后才标记步骤已打开(`stepOpen = true`)。内部分发校验在日志推入之前运行,可能在不打开步骤的情况下拒绝;post-commit `session/event` observer 的失败被隔离在 `Session.append` 内部。因此该标记精确表示已提交的、欠一个后续 `step/end` 的边界。
-- 完整实现见[简化 Agent Note「停止将持久边界镜像为 agent 事件」](../simplification/2026-06-20-remove-agent-boundary-mirror-events.md):全部四个边界镜像被移除,所有消费方从 `session/event` 读取边界。`agent/steering`(不是边界镜像)不在该 Agent Note 范围内,由其后续 Agent Note [移除 `agent/steering` 镜像 emit](../simplification/2026-07-04-remove-agent-steering-mirror.md) 单独移除——它镜像的是持久的 `steering/message`。
+- 完整实现见[简化 Agent Note「停止将持久边界镜像为 agent 事件」](../simplification/2026-06-20-remove-agent-boundary-mirror-events.md):全部四个边界镜像被移除,所有消费方从 `session/event` 读取边界。`agent/steering`(不是边界镜像)不在该 Agent Note 范围内,由其后续 Agent Note [移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md) 单独移除——它镜像的是持久的 `steering/message`。
- Cordis 事件目录(`docs/cordis-catalog/events.md`)重新生成以移除镜像事件。
diff --git a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
index 7b9f3b3418..0c5577e3bb 100644
--- a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
@@ -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-22-unified-send-and-coalesced-user-messages.md: 1716728432a179db0b0a6419ef20e0c2278d53f2
-2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 3569777a280c9f2a378825ace0e76cff4a2c17bb
+2026-07-22-unified-send-and-coalesced-user-messages.md: 12128d9e57601d0b85d20d1cb4240bb08eadc3cb
+2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 177d90f7116f7451b8e3c4ccf7d1577ff12ae701
diff --git a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
index 1716728432..47822d4849 100644
--- a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
+++ b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
@@ -43,5 +43,5 @@ The delivery surface is now one primitive plus three self-documenting presets, a
## Related
- [one-send-one-turn](../simplification/2026-07-17-one-send-one-turn.md) — the one-claimed-message-per-turn rule this builds on.
-- [remove-agent-steering-mirror](../simplification/2026-07-04-remove-agent-steering-mirror.md) — the precedent for collapsing a mirrored live event.
+- [remove-agent-steering-mirror](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md) — the precedent for collapsing a mirrored live event.
- [explicit-turn-cancellation](2026-07-16-explicit-turn-cancellation.md) — the cancel-cause signal `keepInbox` extends.
diff --git a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
index 3569777a28..323ed974d4 100644
--- a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
+++ b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
@@ -43,5 +43,5 @@ agent 的对外驱动接口逐渐长出三个近乎平行的动词——`send`
## 相关
- [one-send-one-turn](../simplification/2026-07-17-one-send-one-turn.md)——本决策所依托的“每轮次只认领一条消息”规则。
-- [remove-agent-steering-mirror](../simplification/2026-07-04-remove-agent-steering-mirror.md)——折叠镜像实时事件的先例。
+- [remove-agent-steering-mirror](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)——折叠镜像实时事件的先例。
- [explicit-turn-cancellation](2026-07-16-explicit-turn-cancellation.md)——`keepInbox` 所扩展的取消原因信号。
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml
new file mode 100644
index 0000000000..529e319ee0
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml
@@ -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
+2026-07-25-web-client-session-scope-and-provide-channel.md: 063494b56461593015d6de4c2b55a2d1d6a3c676
+2026-07-25-web-client-session-scope-and-provide-channel.zh.md: cd5d29dfbcd9356a9ea15852d5d27a3660084abf
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md
new file mode 100644
index 0000000000..063494b564
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md
@@ -0,0 +1,137 @@
+# Agent Note: Web client Agent-scope parity model and the provisioning channel (agents/scope / blank reuse / provide)
+
+Status: implemented
+
+English | [中文](2026-07-25-web-client-session-scope-and-provide-channel.zh.md)
+
+> Scope: the client Agent scope (actx) and targeted events, the client/host materialization parity model, the blank-session bit and reuse (`connectWorkspace`), the per-session provisioning channel (`sessions.provide`), the read-only queue mirror (`session/queued`), and the host wire smalls that carry these capabilities (the summary `blank` column, the `host/session-added` frame field, and the `host/commands-changed` frame). The input state machine and the slash pipeline live in the [input machine note](2026-07-25-web-input-machine-and-slash-pipeline.md); the command business surfaces live in the [command surfaces note](2026-07-25-web-command-surfaces-and-assembly.md).
+
+## Problem
+
+The web client had a single global session surface: slots all rendered from the root context, so plugins had no notion of "which agent/session is current"; the draft's true copy was buried inside the Session object, leaving any plugin that wanted to participate in input with nowhere to hook in. To support a command/input system, the platform layer first had to answer:
+
+- Who owns session interaction state (menus, popups, drafts, in-flight requests), and how two sessions are structurally isolated;
+- What a "new session" is before the host entity exists — whether the client must forge an independent life for it;
+- How session-scope components fetch their own session data, instead of props passed down layer by layer;
+- What a user-abandoned new session leaves behind on the host side, and who collects it.
+
+Hard constraints: the host is the single source of truth; every registration goes through a `ctx.effect` disposer; the scope mechanism matches the host's Agent scope architecture; model-visible ⟺ already in the session log.
+
+## Decision
+
+### The parity model: client and host share one root state axis
+
+Host-side `session.create(workspaceId)` produces Session + Agent + cwd in one piece (an atomic bundle, never split); the client side is the mirror of that birth — the instant a session row enters the list mirror, the client mints its Agent scope (actx + provide + the full input surface mounted):
+
+- Session identity is the host's true form from birth: the sessionId arrives via the `session.create` response / the `host/session-added` frame, and every client-side address (the scope tag, slot store keys, RPC addressing) uses that same id.
+- The materialization moment = the instant the user picks a Workspace (cwd settled): the client calls `session.create({workspaceId})` on the spot and receives the complete entity.
+- "New Session with no workspace picked" is a **pure view state** (a navigation position) corresponding to no session/scope entity; until the pick, the composer is locked whole (no slash, no plain text).
+- A "blank session" is just an ordinary materialized session whose log is still empty; to every Agent-scope plugin on the host (goal/plan/skill/…) it is indistinguishable from any session, so slash/plan are all naturally live.
+
+### Agent scope: the actx is the sole session carrier in the client-side cordis world
+
+The runtime's `agents/scope.ts` matches the host's `dsh-scope` at the mechanism layer (fiber + tag + filter; no value import: the host package carries the scoped-events `Events` merge, which would collide with the Context merge inside the client program):
+
+- `createScope(ctx, key)`: a no-op plugin fiber plus `extend({[kScope]: key, [Context.filter]: …})` — the filter lives directly on the actx: untagged listeners receive globally, tagged ones receive only their own scope.
+- Dispatch is the cordis primitives with thisArg = the actx itself: `actx.bail(actx, event, req)` / `actx.emit(actx, event, payload)`.
+- `Session.bindScope(actx)`: paired exactly once when resolve mints the scope (rebinding throws; dropScope unbinds), mirroring the host's `Agent.loopCtx` — the Session uses it to dispatch its own scoped events. The reverse actx→Session direction is one hop through `sessions.sessionOf(actx)` (mirroring host plugins' `agent.session` usage).
+
+Three deliberate divergences from the host dsh-scope:
+
+- The filter lives on the actx itself rather than a separate carrier: the host wrapper layer guards the business Agent subject against drifting from the scope key (host events inject the Agent itself as the first argument), while client event payloads carry only an id — there is no subject to protect.
+- Keys compare by branded `SessionId` value rather than object identity: on the host, agent.id === session id (1:1 on the same axis), agent identity directly reuses the `SessionId` brand, and a client scope's identity is its wire id.
+- The client scope is an **Agent identity** scope, not a live-object scope: during a cold session the host Agent object is already disposed while the client actx stays alive (in view) — the identity axis is in strict parity while object hot/cold is deliberately unsynchronized.
+
+id→ctx handoff is allowed in only three kinds of places (business providers never hand off):
+
+- Slot inject factories: the ctx never enters the render layer; the identity the slot framework hands a component is the sessionId, exchanged back into objects/controllers through service maps.
+- Root coordination services self-addressing: from a projection's sessionId back to the actx via `sessions.scope(id)`.
+- Root untagged listeners: looking up their own store by the payload's sessionId.
+
+### Scope lifecycle: anchored to the list mirror — birth is entering view, death is prune
+
+Session instances share the scope's lifecycle; liveness eligibility = host-listed (one criterion, shared by mint and prune):
+
+- Birth = a session row entering client view (the list baseline pull / the local `create()` echo / the `host/session-added` frame); a lazy first resolve mints the scope (resolution is a pure function, render-safe).
+- One prune tears down three things together: the Session instance, the scope fiber (cascading through every consumer hung on the actx), and the session-keyed slot store. The staged session (= `list.current`) is the exception: removed while still on stage, it keeps a frozen read-only view, torn down only once the stage moves away.
+- Reopening = lazily rebuilding the instance + `open()` pulling history (the host session log is the durable truth).
+- Remaining TODO: approval/question frames never enter history and cannot be recovered across a prune (the manager-level pendingBuffers cover only the never-instantiated window).
+
+### The blank bit: the empty session's visible projection, conversion, and reuse
+
+A session "materialized but with no first prompt" is governed by the summary-derived bit `blank` (a derived column, not a header field; SessionHeader stays immutable):
+
+- The host criterion: `session.events.length === 0` (zero log events = no user message yet). A live session reads `summarize()` straight from memory; a cold session is always `false` — the lazy-create contract guarantees a never-appended session never enters `persistence.list()` at all (both the JSONL and SQLite backends are verified truly lazy), so blank never touches disk.
+- The wire carries it in two places: the required `SessionSummary.blank` column, and the required `blank` field on the `host/session-added` frame (always true at creation, letting other tabs enter the same blank-session state into their mirrors).
+- The client mirror only lowers, never raises (monotonic), flipped from three sources, all reusing existing wire signals:
+ - The sender's own tab: the **successful response** to the first `prompt()` flips false (acceptance proves the user/message is already in the host log — this flip is confirmation, not optimism; `onEngaged` synchronously updates the list mirror, converting the current `New Session` row in place to an ordinary title, adding no list row). A rejected first prompt keeps the session blank: aligned with host authority, still shown as `New Session`, keeping its connectWorkspace reuse eligibility.
+ - Other tabs: the `host/session-status (running:true)` frame flips it — a blank session never runs, so the first running necessarily means no longer blank;
+ - Reconnect alignment: `session.list`'s summary.blank is authoritative, so a tab that missed frames aligns naturally on its next pull; a stale blank:true can never mark a converted session back to blank.
+- List discipline: the store retains every row; the Workspace browser's grouping, flat view, search, and counts share one visible projection — every non-blank session shows, while blank sessions show only the one with `session.id === sessions.current`, its title forced to `New Session`. After a Workspace switch, the old blank entity stays in the mirror but is hidden from the list while the target Workspace's current blank shows; the user-visible surface therefore holds at most one blank row globally.
+- The residue ledger takes zero GC: after a refresh, blank sessions come back with the bit intact and are reused on the next same-workspace connect, so the ordinary single-tab path keeps at most one per workspace; after a host restart, blanks leave no disk trace and simply evaporate; the extra empty shells from multi-tab races only become non-current hidden rows, digested by later reuse, with no coordination.
+
+### connectWorkspace: the sole entry point of New Session
+
+`workspaces.connectWorkspace(workspaceId): Promise` (owned by WorkspacesService — it holds both the workspace canonical path and the sessions reference):
+
+- The reuse arm: the list mirror is searched for `blank && cwd == workspace.path` (direct equality on the host realpath canonical form); a hit returns that id directly, creating nothing.
+- The create arm: on a miss, `session.create({workspaceId})` returns the new id.
+- An unknown workspaceId fails loud (never silently creating somewhere else).
+- The resolution guarantee (one contract for both arms): when the promise resolves, the returned id is already in the list store and `sessions.binding(id)` resolves synchronously — `SessionsService.create` projects the list synchronously after RPC success before resolving, so a draft mover can write text into the new scope's machine before open, without waiting for a notifier flush.
+- The caller takes the id and does its own `sessions.open`; sending the first prompt is an ordinary `session.prompt` — the session already exists, a failure is an ordinary prompt failure, the draft text is still in the machine, and a retry is simply sending again.
+- The global New Session button defaults to `recentWorkspaceId`: first comparing each Workspace's newest Session `updatedAt`, falling back to the Workspace `createdAt` when it has no Sessions, and keeping host order on ties; only with no Workspace at all does it `sessions.clear()` into the no-session view. Create actions inside a Workspace group still hit that Workspace explicitly.
+- At startup the runtime subscribes to the first complete baseline: a successfully restored current session is kept in place; otherwise it automatically calls `connectWorkspace(recentWorkspaceId)` and opens the returned blank session. The policy settles only once; a later user-initiated clear is never overridden by auto-selection again, and a connect failure waits for the next baseline projection to retry.
+- Re-picking the Workspace in the blank Hero also goes through `connectWorkspace`; when the target id differs from the current one, the current input machine's non-empty draft moves to the target scope first, then `sessions.open(nextId)`. The old blank entity is not deleted — it merely leaves the list by no longer being current.
+
+### Per-session provisioning: the `sessions.provide` standard-kit channel
+
+The sole provisioning path by which session slot components fetch their own session data. Plugins declare a fixed key map through the static descriptor `sessions.provide({hooks, props, resolve})` (a duplicate key throws at registration); `resolve(binding)` materializes values for a specific session and tears them down with the scope. Web-react's `standardKit` single loop binds the hooks compartment into `use` selector hooks (`observableHook`→uSES, anti-tearing) and passes the props compartment through as-is.
+
+Slot scope is the closed set `root | session-maybe | session`:
+
+- `root` receives only the global standard kit, with no session identity or provisioning.
+- `session-maybe` follows the current session, but the component instance does not change key when the id appears, disappears, or changes; with no session, `sessionId`, the results of `useSession`/`useInput`, and `inputActions` may all be absent. The unkeyed root `SessionMaybeProvider` drives these updates, while `SessionMaybeProvideInfo` uses the static key map to retain the complete hook/prop shape even with no session.
+- `session` guarantees that `sessionId`, every hook source, and every prop exist; each strict entry's error boundary is keyed by `sessionId`, so switching sessions recreates that entry and its session store.
+
+`conversation` is the resident `session-maybe` shell: `ConversationRoot`, HeroShell, the Workspace picker, the composer stack, and the overlay chain's fallback frame retain their React instances across the no-session → blank-session switch; `conversation.session` carries only the strict-session header/view, while the composer and every input slot also stay strict `session`. With no session, the composer stack places the presentation-only `DisabledInputBar` directly; once a session appears, the input body is swapped for the strictly bound InputBar; the textarea may be rebuilt, while the Hero and the layout skeleton are not. The blank → engaging/active transition stays inside the same strict-session subtree, and the InputBar is never rebuilt on a phase flip.
+
+- The runtime's first built-in entry: the `'session'` hook — `useSession` itself rides the same mechanism, no special-casing.
+- Concurrent discipline: the render plane reads only from the hooks compartment (uSES consistency guarantee); props-compartment callbacks are used only in event-handler space; descriptor resolution is render-safe (idempotent caching, with prune reaping residue from abandoned renders).
+- Third-party components take zero value dependencies; types are a one-line type-only import (declaration merging into `SessionStandardProps` / `SessionMaybeStandardProps`).
+
+### The read-only queue mirror
+
+- The MuxFrame `session/queued`: the Session holds a read-only inbox mirror (previews truncated; steering retired by source match); queue frames never enter history — pure stream state, cleared on reconnect and refilled from the new baseline; the never-instantiated window is buffered and replayed through the manager pendingBuffers.
+- Queue semantics: running does not lock input; ordinary messages queue through `session.prompt {mode:'queue'}`, and commands never queue.
+
+### Host wire smalls
+
+- The summary `blank` column and the `host/session-added` frame's `blank` field (see the blank bit above).
+- The SSE frame `host/commands-changed` (a pure invalidation signal); the client routes it into the typed events `commands/changed` and `connection/reset` (broadcast after each connection generation is established; wire-derived caches uniformly treat prior state as stale).
+- `command.list/execute` and `skill.list` are uniformly single-addressed by `sessionId` (a session always has an Agent; `agentFor`'s resume semantics come ready-made); the command-surface narrative lives in the [command surfaces note](2026-07-25-web-command-surfaces-and-assembly.md).
+- The `session.create` request shape: workspaceId/cwd as either-or, plus an optional caller-preallocated sessionId (a same-id same-cwd retry is idempotent; a different cwd reports `session-conflict`).
+
+## Alternatives considered
+
+| Rejected | One-line reason |
+|---|---|
+| A client-local Intent + materialize (published CAS / the pendingPrompt attach transaction / the before-create chain) | The client is forced to simulate the first half-life the host lacks, breeding a pile of state machinery — published CAS, the attach transaction, partial publication |
+| Host-reserved IDs (a draft Map) | The host merely acknowledges a number; the state machine stays on the client untouched |
+| A host draft Session (a Session without an Agent) | Every host surface that looks up the Agent must fork for drafts; core would need an attachAgent seam plus late-written header cwd |
+| Binding an Agent before cwd (ungrouped) | Overturns the readonly header.cwd "created in" invariant, plus the launch-dir side-effect product trap |
+| Passing session context down through React Context | Plugins should hold one mental model across host and client; the scope mechanism is isomorphic to the host dsh-scope |
+| A `scopeTarget` carrier + fused dispatcher (mirroring the host `agentEvents`) | The host wrapper layer guards the business Agent subject against drifting from the scope key; client events have no subject to guard — the filter on the actx plus cordis primitives covers every need |
+| Sessions not holding a ctx (a cordis-free object layer) | A red line born only so the filtering unit tests avoid importing cordis, at the cost of two-hop contribute callbacks plus mutable public fields; the host Agent already holds loopCtx |
+| Resident Session instances (resident-instance) | The host session log is the durable truth; residency is mere identity convenience, and its misalignment with the scope lifecycle is a source of complexity |
+| Components receiving wiring-callback bundles (two-layer inject→props pass-down) | The standard-kit channel lets components fetch their own; the public surface converges to hooks + stable props |
+| Swapping the no-session Hero view for the entire session Conversation | Even with the outer layout unchanged, the Hero, picker, and composer subtrees would remount together, making the whole UI region jump |
+| Making InputBar itself `session-maybe` | The input state machine, keyboard command surface, and actions would all have to accept absent values; replacing only the disabled input body keeps optionality at the shell boundary |
+| A dedicated conversion frame | `session-status(running:true)` semantically implies conversion (a blank session never runs); adding a frame buys zero information for one more wire type |
+
+## Consequences
+
+- Plugins gain session context isomorphic to the host's: per-session state hangs on the actx and mounts/tears down in one piece with the scope fiber, making leaks structurally impossible; two-session isolation is structurally guaranteed by the scope filter.
+- The client object layer converges to a wire mirror: session identity, lifecycle, and capability adjudication all defer to the host entity — the input system (the next layer) always faces a session with a real Agent, and providers like slash/skill uniformly address by sessionId directly.
+- Blank-session governance takes zero dedicated mechanisms: state rides one derived bit, visibility rides the unified list projection (only the current blank shows, as `New Session`), reclamation rides lazy persistence's existing contract (evaporation on restart), and the ordinary ceiling rides same-Workspace reuse.
+- The cost: the id→ctx handoff discipline and provide's Concurrent discipline are conventions rather than type-enforced, pinned by review and tests; fully disabled input while no workspace is picked is an experience cost the product surface accepts (the price of the single state axis).
+- Known gaps: approval/question recovery across prune (TODO); model selection returns in live-mutation shape (the host `selectModel` trio is ready-made, awaiting its own branch).
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md
new file mode 100644
index 0000000000..cd5d29dfbc
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md
@@ -0,0 +1,137 @@
+# Agent Note: Web client Agent-scope 对等模型与供数通道(agents/scope / blank 复用 / provide)
+
+Status: implemented
+
+[English](2026-07-25-web-client-session-scope-and-provide-channel.md) | 中文
+
+> 范围:client Agent scope(actx)与定向事件、client/host 实体化对等模型、空会话 blank 位与复用(`connectWorkspace`)、per-session 供数通道(`sessions.provide`)、队列只读镜像(`session/queued`),以及承载这些能力的 host wire 小件(summary `blank` 列、`host/session-added` 帧字段、`host/commands-changed` 帧)。输入状态机与 slash 管线见[输入状态机 note](2026-07-25-web-input-machine-and-slash-pipeline.md);命令业务面见[命令业务面 note](2026-07-25-web-command-surfaces-and-assembly.md)。
+
+## 问题
+
+web client 只有一张全局会话面:slot 全部从根 context 渲染,插件拿不到「当前是哪个 agent/session」的语境;draft 真身埋在 Session 对象里,任何要参与输入的插件都无处下手。要支撑命令/输入体系,平台层必须先回答:
+
+- 会话交互态(菜单、popup、草稿、在途请求)归谁持有,双会话如何结构性隔离;
+- 「新会话」在 host 实体存在之前是什么——client 要不要为它造一段独立生命;
+- session-scope 组件如何「自己拿会话数据」,而不是层层下传 props;
+- 用户放弃的新会话在 host 侧留下什么,谁来收。
+
+硬约束:host 是唯一真源;一切注册走 `ctx.effect` disposer;scope 机制与 host 的 Agent scope 架构一致;模型可见 ⟺ 已入 session log。
+
+## 决策
+
+### 对等模型:client 与 host 同一根状态轴
+
+host 侧 `session.create(workspaceId)` 一体产出 Session + Agent + cwd(原子大礼包,不拆);client 侧就是这次出生的镜像——会话行进入 list mirror 的瞬间,client 为它铸 Agent scope(actx + provide + 输入面全套挂上):
+
+- 会话身份自出生即为 host 真身:sessionId 由 `session.create` 响应 / `host/session-added` 帧带来,client 侧一切寻址(scope tag、slot store 键、RPC 地址)用的都是同一个 id。
+- 实体化时点 = 用户选定 Workspace(cwd 确定)的瞬间:client 当场调 `session.create({workspaceId})`,拿到完整实体。
+- 「New Session 且未选 workspace」是**纯视图态**(一个导航位置),不对应任何 session/scope 实体;选定之前 composer 整体锁死(无 slash、无纯文本)。
+- 「空会话」就是一个日志还空着的普通实体化会话;对 host 上所有 Agent-scope 插件(goal/plan/skill/…)它与任何会话无异,slash/plan 天然全活。
+
+### Agent scope:actx 是 client 侧 cordis 世界的唯一会话载体
+
+runtime `agents/scope.ts` 与 host `dsh-scope` 机制层一致(fiber + tag + filter 过滤;不 value-import:host 包携带 scoped-events 的 `Events` merge,进 client program 撞 Context merge):
+
+- `createScope(ctx, key)`:no-op plugin fiber + `extend({[kScope]: key, [Context.filter]: …})`——filter 直接住 actx:untagged listener 全局可收,tagged 只收本 scope。
+- 派发就是 cordis 原语,thisArg = actx 本身:`actx.bail(actx, event, req)` / `actx.emit(actx, event, payload)`。
+- `Session.bindScope(actx)`:resolve 铸 scope 时单次配对(重复绑 throw;dropScope unbind),镜像 host `Agent.loopCtx`——Session 用它自行派发 scoped 事件。actx→Session 反向走 `sessions.sessionOf(actx)` 一跳(镜像 host 插件 `agent.session` 用法)。
+
+与 host dsh-scope 的有意分歧三条:
+
+- filter 住 actx 自身而非独立 carrier:host 包装层护的是「业务 Agent subject 与 scope key 不漂移」(host 事件首参注入 Agent 本体),client 事件 payload 只带 id、无 subject 可护。
+- key 用品牌 `SessionId` 值比较而非对象身份:host 里 agent.id === session id(1:1 同轴),agent 身份直接复用 `SessionId` 品牌,client scope 的身份即 wire id。
+- client 是 **Agent 身份** scope 而非活对象 scope:cold 会话期 host Agent 对象已 dispose 而 client actx 存活(视野内)——身份轴严格对等、对象冷热有意不同步。
+
+id→ctx 换乘只许三类位置(业务 provider 永不换乘):
+
+- slot inject 工厂:ctx 不进渲染层,slot 框架交给组件的身份就是 sessionId,经服务 map 换回对象/controller。
+- root 协调服务自寻址:从投影的 sessionId 经 `sessions.scope(id)` 找回 actx。
+- root untagged listener:按 payload 的 sessionId 查自有 store。
+
+### scope 生命周期:挂靠 list mirror,出生即视野、死亡即 prune
+
+Session 实例与 scope 同生命周期,存活资格 = host listed(一个判据,mint 与 prune 共用):
+
+- 出生 = 会话行进入 client 视野(list 基线拉取 / `create()` 本地回声 / `host/session-added` 帧),lazy 首次 resolve 铸 scope(resolution 纯函数、渲染安全)。
+- prune 一次同拆三样:Session 实例、scope fiber(级联挂在 actx 上的一切消费者)、session-keyed slot store。staged session(= `list.current`)例外:被移除仍在台上时保留冻结只读视图,stage 移走才拆。
+- 重开 = lazy 重建实例 + `open()` 拉 history(host session log 是持久真相)。
+- 遗留 TODO:approval/question 帧不进 history,跨 prune 不可恢复(manager 级 pendingBuffers 只覆盖「从未实例化」窗口)。
+
+### blank 位:空会话的可见投影、转正与复用
+
+「实体化但无首讯」的会话经 summary 派生位 `blank` 治理(派生列而非 header 字段,SessionHeader 保持不可变):
+
+- host 判据:`session.events.length === 0`(零日志事件 = 尚无用户消息)。live 会话 `summarize()` 内存直读;cold 会话恒 `false`——lazy-create 契约保证 never-appended 会话根本不进 `persistence.list()`(JSONL/SQLite 两后端均已实证真 lazy),blank 从不落盘。
+- wire 承载两处:`SessionSummary.blank` 必填列;`host/session-added` 帧必填 `blank` 字段(创建时恒 true,供别的 tab 按同一空会话状态入镜像)。
+- client 镜像只降不升(单调),三来源翻转,全部复用既有 wire 信号:
+ - 发送方本地:首次 `prompt()` 的**成功响应**翻 false(受理即证明 user/message 已入 host 日志——此点翻转是确证而非乐观;`onEngaged` 同步更新列表镜像,当前 `New Session` 行原地转为普通标题,不新增列表行)。首讯被拒则会话保持 blank:与 host 权威对齐、继续显示为 `New Session`、保持 connectWorkspace 复用资格。
+ - 其他端:`host/session-status (running:true)` 帧翻转——blank 会话从不 running,首次 running 必然已非 blank;
+ - 重连对齐:`session.list` 的 summary.blank 是权威,错过帧的端下次拉取自然对齐;陈旧的 blank:true 不能把已转正的会话重新标回 blank。
+- 列表纪律:store 保留全部行;Workspace browser 的分组、平铺、搜索和计数共用同一可见投影——所有非 blank 会话都显示,blank 会话只显示 `session.id === sessions.current` 的一条,并强制标题为 `New Session`。切换 Workspace 后,旧 blank 实体仍在镜像中但从列表隐藏,目标 Workspace 的 current blank 显示;因此用户可见面全局至多一条 blank 行。
+- 残留账零 GC:刷新后 blank 会话带位回来,下次同 workspace 复用,普通单端路径使每个 workspace 至多保留一个;host 重启后 blank 无盘痕自然蒸发;多 tab 竞态多出的空壳只会成为非 current 隐藏行,后续复用消化,不做协调。
+
+### connectWorkspace:New Session 的唯一入口
+
+`workspaces.connectWorkspace(workspaceId): Promise`(归属 WorkspacesService——它同时持有 workspace 规范 path 与 sessions 引用):
+
+- 复用臂:list mirror 中找 `blank && cwd == workspace.path`(host realpath 规范 canon 直等比较),命中直接返回该 id,不新建。
+- 新建臂:未命中则 `session.create({workspaceId})`,返回新 id。
+- 未知 workspaceId fail loud(不静默创建到别处)。
+- 解析保证(两臂同契约):promise resolve 时返回的 id 已在 list store 且 `sessions.binding(id)` 同步可解析——`SessionsService.create` 在 RPC 成功后同步投影列表再 resolve,使 draft 搬运方可以在 open 之前往新 scope 的 machine 写文本,不等 notifier flush。
+- 调用方拿 id 自行 `sessions.open`;首讯发送就是普通 `session.prompt`——会话本来就在,失败即普通 prompt 失败,draft 文本还在 machine 里,重试即再次发送。
+- 全局 New Session 按钮默认取 `recentWorkspaceId`:先比较各 Workspace 内 Session 的最新 `updatedAt`,无 Session 时回退 Workspace `createdAt`,同值保持 Host 顺序;只有完全没有 Workspace 时才 `sessions.clear()` 进入无 session 视图。Workspace 分组内的创建动作仍显式命中该 Workspace。
+- runtime 启动时订阅首次完整基线:若已有恢复成功的 current session 则保持不动,否则自动 `connectWorkspace(recentWorkspaceId)` 并 open 返回的 blank session。该策略只结算一次;之后用户主动 clear 不会再次被自动选择覆盖,连接失败则等下一次基线投影重试。
+- blank Hero 中改选 Workspace 也走 `connectWorkspace`;若目标 id 与当前 id 不同,先把当前 input machine 的非空 draft 搬到目标 scope,再 `sessions.open(nextId)`。旧 blank 实体不删除,只因不再 current 而从列表隐藏。
+
+### per-session 供数:`sessions.provide` 标准件通道
+
+session slot 组件「自己拿 session 数据」的唯一供数路径。插件以静态描述符 `sessions.provide({hooks, props, resolve})` 声明固定键表(重名 key 注册时 throw),`resolve(binding)` 在确定 session 下物化值并随 scope 拆;web-react `standardKit` 统一循环把 hooks 格绑成 `use` 选择器 hook(`observableHook`→uSES,防 tearing)、props 格原样透传。
+
+slot scope 是闭集 `root | session-maybe | session`:
+
+- `root` 只拿全局标准件,不接收 session 身份或供数。
+- `session-maybe` 跟随 current session,但组件实例不因 id 有无或切换而换 key;无 session 时 `sessionId`、`useSession`/`useInput` 的选择结果及 `inputActions` 均可缺省。根部无 key 的 `SessionMaybeProvider` 驱动这条更新,`SessionMaybeProvideInfo` 靠静态键表在无 session 时仍保留完整 hook/prop 形状。
+- `session` 保证 `sessionId`、所有 hook source 与 props 均存在;每个严格 entry 的错误边界以 `sessionId` 为 key,切换 session 会重建该 entry 及其 session store。
+
+`conversation` 是 `session-maybe` 的常驻外壳:`ConversationRoot`、HeroShell、Workspace picker、composer stack 与 overlay chain 的 fallback 外框在无 session → blank session 的切换中保持 React 实例;`conversation.session` 只承载严格 session 的 header/view,composer 与各输入 slot 也保持严格 `session`。无 session 时 composer stack 直接放纯展示的 `DisabledInputBar`,session 出现后把输入体换成严格绑定的 InputBar;textarea 允许重建,Hero 与布局骨架不重建。blank → engaging/active 仍在同一严格 session subtree 内,InputBar 不因 phase 翻转而重建。
+
+- runtime 内建第一条:`'session'` hook——`useSession` 本身走同一机制,无特判。
+- Concurrent 纪律:渲染平面只从 hooks 格读(uSES 一致性保证);props 格回调只在事件 handler 空间用;描述符解析 render-safe(幂等缓存、废弃渲染残留由 prune 收尸)。
+- 第三方组件值零依赖,类型一行 type-only import(declaration merging 进 `SessionStandardProps` / `SessionMaybeStandardProps`)。
+
+### 队列只读镜像
+
+- MuxFrame `session/queued`:Session 持只读 inbox 镜像(预览截断、steering 按 source 匹配退休);queue 帧不进 history,纯 stream 态——重连清空、新基线重灌;未实例化窗口经 manager pendingBuffers 缓冲重放。
+- 队列语义:running 不锁输入;普通消息经 `session.prompt {mode:'queue'}` 排队,命令永不排队。
+
+### host wire 小件
+
+- summary `blank` 列与 `host/session-added` 帧 `blank` 字段(见上文 blank 位)。
+- SSE 帧 `host/commands-changed`(纯失效信号);client 路由为类型事件 `commands/changed` 与 `connection/reset`(连接代建立后广播,wire 派生缓存一律视旧态为 stale)。
+- `command.list/execute`、`skill.list` 一律 `sessionId` 单址(会话恒有 Agent,`agentFor` 的 resume 语义现成);命令面叙述见[命令业务面 note](2026-07-25-web-command-surfaces-and-assembly.md)。
+- `session.create` 请求形状:workspaceId/cwd 二选一 + 可选调用方预分配 sessionId(同 id 同 cwd 重试幂等,异 cwd 报 `session-conflict`)。
+
+## Alternatives considered
+
+| 弃案 | 一行理由 |
+|---|---|
+| client-local Intent + materialize(published CAS / pendingPrompt attach 事务 / before-create 链) | client 被迫模拟 host 缺失的前半段生命,养出 published CAS、attach 事务、部分发布一坨状态机 |
+| host 预留 ID(draft Map) | host 只认了个号,状态机原封留在 client |
+| host draft Session(有 Session 无 Agent) | 每个查 Agent 的 host 面都要为 draft 分叉;core 要开 attachAgent 缝 + header cwd 后写 |
+| 无 cwd 先绑 Agent(ungrouped) | header.cwd readonly "created in" 不变性被推翻 + launch-dir 副作用产品坑 |
+| React Context 层层传会话语境 | 插件在 host/client 两侧应是一个心智模型;scope 机制与 host dsh-scope 同构 |
+| `scopeTarget` carrier + 融合派发器(镜像 host `agentEvents`) | host 包装层护的是「业务 Agent subject 与 scope key 不漂移」,client 事件无 subject 可护;filter 住 actx + cordis 原语覆盖全部需求 |
+| Session 不持 ctx(对象层 cordis-free) | 只为筛选单测不引 cordis 而生的红线,代价是 contribute 两跳回调 + 可变公有字段;host Agent 本就持 loopCtx |
+| Session 实例常驻(resident-instance) | host session log 即持久真相;常驻仅为身份便利,与 scope 生命周期错位是复杂度之源 |
+| 组件收 wiring 回调包(inject→props 两层下传) | 标准件通道让组件自取;公共面收敛为 hooks + 稳定 props |
+| Hero 无 session 视图与 session Conversation 整支互换 | 即使外层 layout 不变,Hero、picker 与 composer 子树仍会一起重建,界面产生整块抖动 |
+| 让 InputBar 自身变成 `session-maybe` | 输入状态机、键盘命令面与动作都被迫接受缺省值;只替换 disabled 输入体能把可选性留在外壳边界 |
+| 专用「转正」帧 | `session-status(running:true)` 语义蕴含转正(blank 会话从不 running),加帧是 wire 多一型换零信息 |
+
+## 后果
+
+- 插件获得与 host 同构的会话语境:per-session 状态挂 actx、随 scope fiber 一次拆装,泄漏结构性不可能;双会话隔离由 scope filter 结构性保证。
+- client 对象层收敛为 wire 镜像:会话身份、生命周期、能力判别全部以 host 实体为准——输入体系(下一层)面对的永远是「有真 Agent 的会话」,slash/skill 等 provider 一律以 sessionId 直接寻址。
+- 空会话治理零专用机制:状态靠一个派生位,可见性靠统一列表投影(仅 current blank 以 `New Session` 展示),回收靠 lazy persistence 的既有契约(重启蒸发),常规上限靠同 Workspace 复用。
+- 代价:id→ctx 换乘纪律、provide 的 Concurrent 纪律都是约定而非类型强制,靠 review 与测试钉住;「未选 workspace」期间输入全禁是产品面接受的体验代价(单一状态轴换来的)。
+- 已知欠账:approval/question 跨 prune 恢复(TODO);模型选择以 live-mutation 形状回归(host `selectModel` 三件套现成,等独立分支)。
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.i18n.yaml
new file mode 100644
index 0000000000..d636aab9ff
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.i18n.yaml
@@ -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
+2026-07-25-web-command-surfaces-and-assembly.md: 5188e8c17b31157b1c03203a8d7ba2d8e6a1496b
+2026-07-25-web-command-surfaces-and-assembly.zh.md: 0134cc10cf4f49b7719d6a0dacb239389776d6ed
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md
new file mode 100644
index 0000000000..5188e8c17b
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md
@@ -0,0 +1,62 @@
+# Agent Note: Web command business surfaces and assembly (ui-command / ui-skill / ui-subagent)
+
+Status: implemented
+
+English | [中文](2026-07-25-web-command-surfaces-and-assembly.zh.md)
+
+> Scope: the command directory cache and three-kind dispatch (ui-command), the popup selection flow, the two skill / subagent reference sources, and fixture command routing plus assembly acceptance (the slash-flow snapshot). The carrying wire lives in the [session scope note](2026-07-25-web-client-session-scope-and-provide-channel.md); triggers, the menu, and the input machine live in the [input machine note](2026-07-25-web-input-machine-and-slash-pipeline.md).
+
+## Problem
+
+The pipeline was ready but command knowledge had no landing spot: host-side `ctx.commands` and `ctx.skills` were complete while the web channel had no command capability. The business layer had to answer:
+
+- Command UI takes more than one shape (execute on the spot, pop a select box, backfill and keep typing arguments) — how do business packages ship with zero skeleton changes;
+- When is the directory fetched: pulling on every menu open is too slow, while a resident cache needs invalidation and reconnect stories;
+- Sessions are always agent-backed (Session + Agent born in the same instant) — by what address does the client command surface honor the host's per-agent effective directory;
+- Assembly-level acceptance: with the layers split apart, how the user-visible main chain is pinned once they come together.
+
+## Decision
+
+### ui-command: a `CommandService` + a session-keyed `CommandDirectory` + a per-session `PopupSelectController`
+
+- The `ClientSessionContext { sessionId }` projection is self-held in the ui-slash contract (types.ts): sessions are always agent-backed, so session identity is the entire projection of command capability; the wire addresses by `{sessionId}` (both `command.list` and `command.execute`; the host resolves the Agent from the session header).
+- The directory is compartmented by `SessionId`, with per-key single-flight + an epoch guard (an old pull never overwrites newer state); `commands/changed` soft-invalidates every key (the old snapshot keeps serving while the repull runs in the background), `connection/reset` hard-invalidates every key and rewarms, Enter strong-waits on the current key, and a failure keeps the draft with no downgrade. Prewarming hangs on the source's `warm` hook — once over the full roster at scope birth, which covers the entire session lifecycle (session capability is constant from birth).
+- `register(contribution)` registers client commands (a descriptor + `available(projection)` + a popupSelect spec); candidate synthesis = the host directory + contribution availability filtering, then the query/position pass, and a host/contribution name clash fails loud.
+- The three command kinds derive from the registration surfaces; developers never declare positions: a host descriptor with `input` = **leadingInput** (backfill `/name ␣` + claim, keep typing arguments, leading position only); a client-registered popupSelect spec = **popupSelect** (the official select-box shell, business ships zero components); neither = **execute** (run on selection, zero UI).
+- The dispatch decision table: the menu can trigger all three kinds; Space recognizes only leadingInput (the misfire defense: irreversible side effects keep explicit entry points only); Enter runs execute / opens the shell only on a bare token, while leadingInput tolerates trailing arguments.
+- The popup from `popupFor(actx)`: search filters locally, select is single-flight, the projection is captured at open, onSelect consumes the token through the consume-token event only on success, a failure is retained for retry, and a session switch merely hides it. The popup shell is a transient layer (never in the state machine): the box holds focus, Enter/↑↓/Escape belong to it, and clicking outside the box dismisses (clicking the textarea also returns focus).
+
+### Reference sources (seeing only projections plus their own apply closures, on the root ctx)
+
+- **ui-skill**: `skill.list({sessionId})` addresses by session (the host resolves the project root from the session header); the directory cache is single-flight keyed by sessionId, prewarmed at birth by the `warm` hook and fully cleared by `connection/reset`. A pick produces a text outcome (the literal `/name ` text, Decision 21); `lexicon` supplies the roster from CatalogFetch's settled snapshot (`undefined` while not warm). No match hook (references never enter command adjudication). Skill references ride ordinary prompts as literal text (outside the command plane; tool-skill unchanged, with the session-prefix directory providing the cooperative association).
+- **ui-subagent**: candidates are zero-RPC (the sessions.list snapshot filtered by parentId/running); a pick produces a text outcome (the literal `@name ` text); `lexicon` derives from the same snapshot (the model-side representation awaits its business workstream).
+
+### Fixture command routing and assembly
+
+- The connection fixture adds command routing (fixture + fake-api): the keyless rig can run the complete command flow (directory, execution, popup selection).
+- The apps/cli assembly mounts all the new packages; the tsconfig path map / reference sets are filled in; catalogs/docs are regenerated with the wire and events.
+
+### Assembly-level acceptance: the slash-flow snapshot
+
+`apps/web/tests/slash-flow.snapshot.ts` pins the user-visible main chain (assembled keyless; package mocks are no substitute for the assembled transcript): the composer disabled with no session → creating a Workspace and entering an already-materialized blank session → picking the `/echo` leadingInput from the `/` menu → the command executes but the blank bit does not flip and the list still shows `New Session` → the first ordinary prompt's successful acceptance converts that same row; the same session-bound textarea holds across blank → active. `workspace-flow.snapshot.ts` separately pins blank-row creation/reuse, first-prompt rejection backfill, and — on a Workspace switch before the first prompt — the draft moving across input machines with the old blank row hidden.
+
+## Alternatives considered
+
+| Rejected | One-line reason |
+|---|---|
+| Inline prompt dispatch (command text riding the message into the host for parsing) | Conflates the command and message planes; command execution being independent of the message queue is existing host semantics |
+| A bridge materializing skills as commands | Skills have their own directory; N registrations would be a detour; the tag form naturally avoids the command plane |
+| A `skill.invoke` RPC | The host has no such operation; skill references are plain text riding prompts |
+| A new ContentBlock reference type | Full-chain cost (adapters/UI/compaction); text-as-truth plus structured occurrence records suffices |
+| Client packages self-reporting command directories | The host is the single source of truth; the client only reads descriptors, with `commands-changed` pushing invalidation |
+| The `requires: 'none' \| 'agent'` discriminant axis (an agentless directory + dual-addressed queries) | With sessions always agent-backed, the amphibious command has no owner; the whole axis reverts to master's shape, to be reopened on real demand |
+| Dedicated commandresult / commandpanel slots | Results go through notices; the popup shell is a skeleton-internal overlay; rich result cards sit in the ledger |
+| An agent-type directory as the `@` source | No type registry exists; the live-session snapshot already covers it |
+| A PickAction/EnterCommand class family (class-inheritance pick products) | Cross-package runtime values break client bundle purity; pure data interfaces plus closure methods are equivalent |
+
+## Consequences
+
+- Shipping a business command = a host registration plus one client `command.register` (popupSelect) or zero registration (execute/leadingInput derive automatically), with zero skeleton changes; the cost is that the three-kind semantics concentrate in ui-command, and a hypothetical fourth kind means changing it.
+- The resident directory cache plus push invalidation buys zero-latency menus and reliable enter adjudication; the cost is three invalidation paths (the change frame, reconnect, the epoch guard) that all need tests pinning them.
+- sessionId addressing puts the host's per-agent effective directory (global + scoped shadows) straight on the wire, with the client presenting it as-is.
+- Known gaps: the popupSelect shell has no shipped business consumer yet (model selection and its kin return with #600's host `selectModel` in live-mutation shape, serving as the onboarding template then); the queue's second cut (per-item Inbox operations), rich result cards, and roster configurability sit in the ledger awaiting their triggers.
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md
new file mode 100644
index 0000000000..0134cc10cf
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md
@@ -0,0 +1,62 @@
+# Agent Note: Web 命令业务面与装配(ui-command / ui-skill / ui-subagent)
+
+Status: implemented
+
+[English](2026-07-25-web-command-surfaces-and-assembly.md) | 中文
+
+> 范围:命令目录缓存与三型判定(ui-command)、popup 选择流、skill / subagent 两个引用源、fixture 命令路由与装配验收(slash-flow 快照)。承载 wire 见[会话作用域 note](2026-07-25-web-client-session-scope-and-provide-channel.md);触发/菜单/输入机器见[输入状态机 note](2026-07-25-web-input-machine-and-slash-pipeline.md)。
+
+## 问题
+
+管线就绪但没有命令知识的落点:host 侧 `ctx.commands` 与 `ctx.skills` 完整而 web 通道无命令能力。业务层要回答:
+
+- 命令 UI 不止一种形态(当场执行、弹选择框、回填后继续打参数)——业务包如何零骨架改动上架;
+- 目录何时拉取:每次开菜单现拉太慢,常驻缓存就要有失效与重连故事;
+- 会话恒 agent-backed(Session+Agent 同瞬出生),client 命令面以什么地址兑现 host 的 per-agent 有效目录;
+- 装配级验收:拆开的各层合起来,用户可见主链如何钉住。
+
+## 决策
+
+### ui-command:`CommandService` + session 键控 `CommandDirectory` + per-session `PopupSelectController`
+
+- 投影 `ClientSessionContext { sessionId }` 自持于 ui-slash 契约(types.ts):会话恒 agent-backed,会话身份即命令能力的全部投影;wire 以 `{sessionId}` 寻址(`command.list` / `command.execute` 均是;host 从会话 header 解析 Agent)。
+- 目录按 `SessionId` 分格,per-key single-flight + epoch guard(旧拉取永不覆盖新态),`commands/changed` 全 key 软失效(旧快照继续服务、后台重拉)、`connection/reset` 全 key 硬失效并预热,Enter 强等当前 key、失败留草稿不降级。预热挂 source 的 `warm` 钩子——scope 出生时对全 roster 一次,即覆盖整个会话生命周期(会话能力自出生恒定)。
+- `register(contribution)` 注册 client 命令(descriptor + `available(projection)` + popupSelect spec);候选合成 = host 目录 + contribution 可用性过滤,再过 query/position,host/contribution 重名 fail loud。
+- 命令三型按注册面派生,开发者不声明位置:host descriptor 带 `input` = **leadingInput**(回填 `/name ␣` + claim,继续打参数,仅限行首);client 注册 popupSelect spec = **popupSelect**(官方选择框壳,业务零组件);两者皆无 = **execute**(选中即执行,零 UI)。
+- 判定决策表:菜单可触发三型;Space 只认 leadingInput(误触发防线:不可逆副作用只留显式入口);Enter 裸 token 才 execute/开壳、leadingInput 容忍尾随参数。
+- `popupFor(actx)` 的 popup:search 本地过滤、select single-flight、open 时捕获投影、onSelect 成功才经 consume-token 事件消 token、失败保留可重试、session 切换只隐藏。popup 壳是瞬态层(不进状态机):框持焦点、Enter/↑↓/Escape 归它、点框外即 dismiss(点 textarea 同时归还焦点)。
+
+### 引用源(只见投影 + 自家 apply 闭包的 root ctx)
+
+- **ui-skill**:`skill.list({sessionId})` 按会话寻址(host 从会话 header 解析项目根);目录缓存按 sessionId 键控 single-flight,`warm` 钩子出生预热、`connection/reset` 全清。pick 产出 text outcome(`/name ` 原文,决策 21);`lexicon` 从 CatalogFetch 的 settled 快照给名录(未热 `undefined`)。无 match 钩子(引用不进命令裁决)。skill 引用以原文随普通 prompt 走(命令平面之外;tool-skill 不变,session-prefix 目录提供协作关联)。
+- **ui-subagent**:候选零 RPC(sessions.list 快照按 parentId/running 过滤);pick 产出 text outcome(`@name ` 原文);`lexicon` 同快照派生(模型侧表示待业务立项)。
+
+### fixture 命令路由与装配
+
+- connection fixture 补命令路由(fixture + fake-api):keyless 台架可跑完整命令流(目录、执行、popup 选择)。
+- apps/cli 装配挂全部新包;tsconfig path map / reference 集补齐;catalog/docs 随 wire 与事件再生成。
+
+### 装配级验收:slash-flow 快照
+
+`apps/web/tests/slash-flow.snapshot.ts` 钉住用户可见主链(assembled keyless,包 mock 不替代装配转录):无 session 时 composer 禁用 → 创建 Workspace 并进入已实体化的 blank session → `/` 菜单选 `/echo` leadingInput → 命令执行但 blank 位不翻转、列表仍显示 `New Session` → 首条普通 prompt 成功受理后同一行转正;同一 session-bound textarea 跨 blank → active 保持。`workspace-flow.snapshot.ts` 另钉住 blank 行创建/复用、首讯拒绝回填,以及首讯前切换 Workspace 时 draft 跨 input machine 搬运且旧 blank 行隐藏。
+
+## Alternatives considered
+
+| 弃案 | 一行理由 |
+|---|---|
+| prompt 内联派发(命令文本随消息进 host 解析) | 混淆命令/消息平面;命令执行独立于消息队列是既有 host 语义 |
+| skill 物化为 command 的桥 | skill 自有目录;N 笔注册是绕路;标签形式天然避开命令平面 |
+| `skill.invoke` RPC | host 无此操作;skill 引用是随 prompt 的普通文本 |
+| 新 ContentBlock 引用类型 | 全链路成本(adapter/UI/compaction);文本即真身 + 结构化 occurrence 记录已足够 |
+| client 各包自报命令目录 | host 是唯一真源;client 只读 descriptor,`commands-changed` 推失效 |
+| `requires: 'none' \| 'agent'` 判别轴(agentless 目录 + 双址查询) | 会话恒 agent-backed 后两栖命令无 owner;整轴回退 master 形状,待真需求重开 |
+| 专用 commandresult / commandpanel 坑位 | 结果走 notice;popup 壳是骨架内浮层;富结果卡入台账 |
+| agent-type 目录做 `@` 源 | 无类型注册表;live-session 快照已覆盖 |
+| PickAction/EnterCommand 类族(类继承 pick 产物) | 跨包运行时值破坏 client bundle 纯度;纯数据接口 + 闭包方法等价 |
+
+## 后果
+
+- 业务命令上架 = host 注册 + client 一笔 `command.register`(popupSelect)或零注册(execute/leadingInput 自动派生),零骨架改动;代价是三型语义集中在 ui-command,假想的第四型意味着改它。
+- 常驻目录缓存 + 推失效换来菜单零延迟与回车裁决可靠;代价是三条失效路径(change 帧、重连、epoch guard)都需测试钉住。
+- sessionId 寻址让 host 的 per-agent 有效目录(全局 + scoped shadows)直接上 wire,client 原样呈现。
+- 已知欠账:popupSelect 壳暂无已上架业务消费者(模型选择等 #600 的 host `selectModel` 以 live-mutation 形态回归,届时作接入样板);队列第二刀(逐项 Inbox 操作)、富结果卡、roster 可配置性入台账待触发。
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml
new file mode 100644
index 0000000000..0249baff80
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml
@@ -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
+2026-07-25-web-input-machine-and-slash-pipeline.md: acbd132a5fdb97a4098064aae689dfca604ad4b7
+2026-07-25-web-input-machine-and-slash-pipeline.zh.md: 158650a41b47f98037a1b3e610d9294694c55a8c
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
new file mode 100644
index 0000000000..acbd132a5f
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
@@ -0,0 +1,132 @@
+# Agent Note: Web input state machine, composer slots, and the slash pipeline (ui-conversation input / ui-slash)
+
+Status: implemented
+
+English | [中文](2026-07-25-web-input-machine-and-slash-pipeline.zh.md)
+
+> Scope: the input state machine (the occurrence table + claim watch + the submit transaction), the hub/facade and send orchestration, the three scoped bail events for cross-plugin input rewrites, `/` and `@` trigger detection and the menu pipeline (ui-slash), and the slot system around the composer. It depends on the [session scope note](2026-07-25-web-client-session-scope-and-provide-channel.md)'s sctx / provide / session-maybe and blank entity model; command knowledge (the three kinds, the directory, popups) is untouched here — that is the [command surfaces note](2026-07-25-web-command-surfaces-and-assembly.md)'s territory.
+
+## Problem
+
+Two composers, each a law unto itself: hero (EmptyState, the controlled chain writing straight into the Session) and the in-conversation InputBar (a plain controlled textarea) — behavior, draft ownership, and send path all inconsistent. To bring the three trigger families — `/` commands, skill references, `@` references — onto the input surface, these had to be answered:
+
+- How the three trigger families layer, and who holds knowledge of "commands" versus who stays zero-knowledge;
+- How the input box expresses "command mode" — derived from the draft text or explicit state? What do backspace, enter, space, and pasting a whole line each mean;
+- Submission is an asynchronous transaction (an RPC round trip) — how are stale-result backwash, session switching, and React concurrent replay defended;
+- How reference chips are represented on a plain textarea, and who owns undo / clipboard / paste matching / model serialization;
+- How cross-plugin input rewrites (menu backfill, reference insertion, token consumption) achieve dependency inversion;
+- Which React shells must be reused across no session → blank session, and which strict-session input bodies may be replaced.
+
+Hard constraints: components mount through slots only; presentation artifacts never enter the session log; the keyboard path is IME-safe throughout.
+
+## Decision
+
+### The input state machine (`InputMachine`)
+
+A pure state machine, events in / effects out, clock injected. Four phases (plain / adjudicating / claimed / submitting). Command mode is **never derived from the draft**; the pick paths establish it explicitly at discrete moments; the claim is watched by `draft.startsWith(token)`, with a backspace break releasing automatically; the claim shape is `{token, hint?}` (hint feeds ghost text).
+
+The event surface (`dispatch(ev)` is the single write entry; one transaction per event):
+
+- `draft-changed {draft, editRange?}` — the textarea's full draft; editRange narrows the occurrence-shift computation, defaulting to a shared prefix/suffix scan.
+- `newline {selection}` — the Ctrl+Enter line break (not via the browser's execCommand: under self-managed undo a browser write forks two histories).
+- `begin-command {claim, span}` / `insert-ref {reference, span}` / `consume-token {guard}` — the machine side of the three bail events; span CAS = draftRev equality.
+- `set-invalid {invalidIds}` — the style bit for owner-resolution results (not a transaction).
+- `undo` / `redo` — the self-managed transaction log (a ring of 100; single-character typing merges within injected-clock windows; a successful submit clears the log).
+- `paste-begin {text, selection, components?, generation?}` — the paste plus hot-snapshot synchronously matched components in one transaction (one Undo returns to before the paste); opens a PasteMatchAttempt.
+- `paste-upgrade {attemptId, span, reference}` — an asynchronous match upgrade as its own transaction (Undo in two steps); the attempt stays current, and insertedRange shrinks with each upgrade.
+- `invalidate-paste` — attempt-ending gestures observed at the DOM layer (caret/selection operations and the like).
+- `enter {mode}` / `adjudicated` / `adjudication-failed` / `submit-settled` / `release` — the submit-transaction plane: a SubmitAttempt (seq + AbortSignal) blocks backwash; success commits and clears the draft; failure rolls back under the drift guard (the enter-time snapshot is backfilled only while the live draft still equals it; if the user has typed again, only a notice fires).
+
+The effect surface (executed by the shell): `adjudicate` (calls SlashController.adjudicate), `begin-submit` (the claim.submit transaction), `default-sink` (ordinary messages, hub-orchestrated), `notice`.
+
+The occurrence table and the chip's three projections:
+
+- Each reference occupies one `U+FFFC` in the draft; a table entry is `{occurrenceId, source, ref, offset, label, clipboardText, invalid?}`; same-named chips stay independent through occurrenceId.
+- Every edit updates the draft and the table in one transaction: ranges shift; a deletion/replacement intersecting a placeholder acts on the whole chip.
+- The single-character placeholder makes keyboard atomicity mostly hold natively (the caret has no interior position; Backspace / arrow keys / Shift extension natively take the whole chip); a mouse click on a chip goes backdrop hit → whole-chip setSelectionRange.
+- The visual projection = label: the backdrop renders the chip at the placeholder offset (the textarea glyph is invisible), with invalid taking the invalid style.
+- The clipboard/persistence projection = clipboardText: copy/cut expands placeholders inside the selection; the draft-persistence mirror writes the same projection (the chat store always holds plain text; the refresh seed semantics = select-all copy → reopen → paste, with chips degrading to text across a refresh).
+- The model projection = generated per chip at submit through the source's `codec.serialize` (owned by the submit attempt's signal and stale guard; a missing owner / failure / cancel means no send, never a downgrade to `/name`).
+
+### Cross-plugin input rewrites: three scoped bail events
+
+The contract is declared in ui-slash (the bottom of the dependency chain); producers dispatch via `sctx.bail(sctx, ...)`, and the only consuming side is the three listeners the hub hangs on the sctx when building the shell; returning `true` ⟺ the machine passed the phase and CAS guards and actually rewrote (emitting the event ≠ a successful modification; whether Space gets `preventDefault` follows the return value):
+
+- `slash/input-begin-command` `{claim, span}` — backfill of the command claim adjudicated from a menu pick / Space (dispatched by the SlashController).
+- `slash/input-insert-reference` `{reference, span}` — reference chip insertion (dispatched by the SlashController).
+- `slash/input-consume-token` `{guard: span | bare-token}` — consuming the command token after business success (dispatched by the downstream command surfaces).
+
+Calls that stay un-evented (registry registration → explicit call → await): Input's own draft/submit, asynchronous Enter adjudication, the reference serializer, the asynchronous paste matcher. `@mode bail` has entered the JSDoc parser and the cordis catalog gate (scripts/jsdoc.ts).
+
+### The slash pipeline (ui-slash: a root `SlashService` + a per-session `SlashController`)
+
+A trigger/menu/pick pipeline with zero knowledge of "commands":
+
+- The service holds only the source registry (`SlashSource{trigger: '/'|'@', name, candidates, onPick, matchSpace?, matchEnter?}`; (trigger,name) unique, registration order = group order = polling order) and `sessionOf(sctx)`. Implementing a match hook IS the declaration of participation in space/enter adjudication; the pipeline polls in registration order, the first non-undefined answer wins, and no claimant means the default sink. matchSpace is synchronous (space fires mid-keystroke; hot cache only); matchEnter is asynchronous (it may await the source's own warmup, and a warmup failure rejects).
+- The controller holds the single authoritative hit (span included; retained for Space after the menu closes), the per-session menu store, the candidate-fetch generation, keyboard arbitration (combobox mode: focus stays in the textarea, ↑↓/Enter/Escape are intercepted and all pass the IME composition guard, with the single exception Shift+Enter unconditionally going first), and pick orchestration (outcome → self-dispatched bail events); at each session scope's birth it runs `warm(projection)` once over the source roster — within that scope the projection holds only the stable sessionId, with no published/capability transitions; the scope disposer tears down the controller.
+- Trigger-detection word boundaries (`user@host` and URL `/` never trigger) and the guard tiers (plain: `/` everywhere + `@` inline / claimed: `/` suppressed, `@` live / frozen: none) are the frozen pure core.
+
+### hub / facade: the resident shell and the strict-session input body
+
+- The hub (trigger/decoration registries + send orchestration) takes the slash/command services as optional `ctx.get()` dependencies: without ui-slash or the command surfaces, input still sends and receives normally — graceful degradation.
+- Each materialized Session has exactly one `SessionInputShell` (the facade), created and torn down with the session scope; with no session, no input machine is built. `ConversationRoot` is itself the `session-maybe` resident shell, holding HeroShell, the Workspace picker, the composer stack, and the chain-fallback frame.
+- With no session the shell renders the presentation-only `DisabledInputBar`; once `connectWorkspace` returns a blank session, only the input body is swapped for the strict-session InputBar. The textarea may be rebuilt here, while `ConversationRoot`, the Hero, and the layout skeleton hold; blank → engaging/active stays the same session-bound InputBar, with the textarea never rebuilt on a phase flip.
+- ConversationRoot's Hero criterion is `sessionId === undefined || (composerPhase === 'blank' && (openState === 'open' || openState === 'loading'))`. The first submit enters engaging synchronously, and a failure keeps the composer and the error context rather than falling back to the blank Hero; the sidebar's blank bit flips false only after a prompt is successfully accepted.
+- Sending unifies in the hub defaultSink: after an optimistic draft clear it goes only through `session.prompt {mode:'queue'|'steer'}`; backfill happens only when it fails and the live draft is still empty — a user who has kept typing is never overwritten. No Draft materialize or attach transaction exists.
+- When the blank Hero re-picks the Workspace, the shell calls `connectWorkspace`; if the target session differs, the non-empty draft moves from the current shell to the target shell before the new id is opened, and the old blank session survives but is no longer current.
+- The Notifier's two-bit contract: `dirty` (snapshot freshness, clearable by an `ensureFresh` pull) and `notifyPending` (notification debt, cleared only by a flush) are mutually independent — a pull must not swallow a push, and object-layer push subscribers (watchTransaction) depend on this guarantee.
+
+### Plain-text references (Decision 21): text outcomes and lexicon decoration
+
+skill/@subagent references skip the placeholder + occurrence identity chain — a pick inserts the literal `/name ` `@name ` text straight into the draft, with the chip visual purely derived:
+
+- PickOutcome gains a `{text}` arm; the new scoped bail event `slash/input-insert-text` `{text, span}` (the same contract as the other three: draftRev CAS, returning true ⟺ an actual rewrite); facade.insertText goes through setDraft concatenation — zero machine changes.
+- Sources get an optional `lexicon?(session)` hook: a synchronous hot-snapshot name roster, with `undefined` = data not warm — zero decoration, never triggering a fetch (the render path stays synchronous and side-effect-free); the controller aggregates it into the `lexicon()` public surface.
+- `decorations.scanTextRefs`: a word-boundary scan of the draft (`/name`, `@name` at line start / after whitespace; `x/name` never hits) against the roster; a hit gets the `.textRef` mark (a pure range highlight on the backdrop, same as hlToken); an edit breaking the match shape simply disappears on the next scan.
+- Sending is the literal text (no more `` serialization); on the bubble side MessageItem decorates both shapes (the legacy `` tag + plain-text tokens).
+- The old occurrence/paste/serialize chain stays on disk in full, undeleted (additive; deletion is a separate future cut). Known limitation kept as-is: with the lexicon not warm at paste / cold start there is no decoration — it lights up only after typing `/` opens the menu once.
+
+### Per-session provide contributions and the private keyboard surface
+
+- ui-conversation (the hub doubling as a contributor) supplies through `sessions.provide` the `'input'` hook (machine state + the queue overlay) plus the `inputActions` prop (`setDraft`/`submit`, stable void callbacks).
+- The public/private boundary: the public provide carries only React-vocabulary members; the keyboard/DOM command surface (track/arbitrate/space/undo/redo/paste/dismissPopup/bindMirror — synchronous return values, disposer semantics) is InputBar-exclusive, passed privately in-package through the InputBar entry's own inject, never leaving the plugin boundary.
+
+### The slot system
+
+`conversation` is itself session-maybe; its session content and the composer input slots are strict session, while the Hero Workspace picker stays root. The child slots are all declared by ui-conversation's conversation registration:
+
+- `conversation.session` (single) — the strict-session header, view ring, and chat store; rebuilt when the session id switches.
+- `conversation.composer.bar` (single) — the slot for the InputBar itself: the InputBar is a true slot entry (self-registered into its own slot) and the content of the composer chain's fallback; it is not a chain entry — the chain's single election would unmount it on a takeover, breaking textarea DOM survival.
+- `conversation.input.overlay` — the floating-overlay anchor inside the input card; registrants' inject resolves each one's own per-session controller by the slot sessionId.
+- `conversation.input.dock` — the stacked strip above the input (QueueDock's read-only queue list lands here), ordered by `order`.
+- `conversation.composer.dock` — the stats band on the composer's top edge.
+- `conversation.input.left` / `conversation.input.right` — the tool-row left and right regions.
+- `conversation.input.plan` / `conversation.input.model` (single) — the tool row's two named control seats; the bar passes only `locked` (owner props), each stays empty until its owning plugin registers, no placeholder fallback.
+- `conversation.hero.workspace` (root scope) — the Workspace picker shared by the no-session and blank Hero; a pick reuses or creates the target blank session through `connectWorkspace`, moving the draft where necessary before switching current.
+
+### Testing discipline
+
+The state machine's entire behavior is covered by pure-JS unit tests (event sequences in, asserting state and effects, zero browser DOM); the interaction matrix is projection-tested row by row. This requirement is precisely what forced the pure-core + service-shell layering.
+
+## Alternatives considered
+
+| Rejected | One-line reason |
+|---|---|
+| An ActiveCommand intermediate state / a registerMode mode registry / deriving command mode from the draft | Claims are established explicitly by the pick paths — no table, no derivation |
+| Direct bindTarget/bindDraft object wiring | Reverse coupling plus root-singleton cross-session mispairing; scoped bail events preserve dependency inversion with structurally correct routing |
+| A unified slash/input-apply, or eventing everything | Three independent payloads cover the cross-plugin rewrites; asynchronous paths stay registry-based explicit calls |
+| contenteditable / a rich-text tree | Poor compatibility; textarea + U+FFFC + the occurrence table covers the full interaction contract |
+| Dual draft persistence {text, occurrences} | The mirror writing the clipboard projection adds zero new concepts; chip degradation across refresh is acceptable |
+| The native textarea undo stack | Unreliable under controlled + programmatic writes; the paste two-step undo semantics can only be self-managed |
+| The InputBar receiving a 16-member wiring-callback bundle | The consumption matrix proved 11 members InputBar-exclusive and 1 a dead member; the standard-kit channel lets components fetch their own, with the keyboard surface passed privately in-package |
+| Space adjudication also claiming execute-kind commands | The misfire defense: after a space the whole line is an ordinary prompt; irreversible side effects keep explicit entry points only |
+| A generic tokenPattern decoration mechanism | Structured occurrence records replace pattern scanning |
+| A placeholder select resident in the tool row | Named seats stay empty until registration; a placeholder clashing with the real implementation is two sources of truth |
+| All references through U+FFFC chips (the pre-Decision-21 line) | Plain text + derived decoration carries zero identity state; the literal text IS the model projection, sparing undo/clipboard any special cases; the chip chain is kept for scenarios needing indivisible atomicity |
+
+## Consequences
+
+- One resident conversation shell carries no-session/blank/active: no session → blank guarantees only the outer frame's React identity, allowing the disabled textarea to be replaced by the strict InputBar; the same blank session → engaging/active keeps the InputBar and the textarea. EmptyState and the controlled intent chain (`sessions.updateIntent`/`updatePendingPrompt`/`workspaces.sendSession`) are deleted along with their last consumer.
+- The input surface's zero knowledge of commands plus optional dependencies: pure input works without the command packages; `@` references and skill references get free reuse of the same menu/pick pipeline. The cost is that space/enter adjudication is a per-source polling protocol whose answer semantics (sync/async, the meaning of undefined) are a frozen contract.
+- Transactionalized submission (attempt seq + the drift guard) makes the three defect classes — stale-result backwash, session switching, concurrent replay — structurally impossible, pinned by the matrix tests.
+- Known gaps: chip fidelity across refresh (paste matching is reusable for it) has no workstream yet; the subagent reference's model representation awaits its business workstream.
diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md
new file mode 100644
index 0000000000..158650a41b
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md
@@ -0,0 +1,132 @@
+# Agent Note: Web 输入状态机、composer 坑位与 slash 管线(ui-conversation input / ui-slash)
+
+Status: implemented
+
+[English](2026-07-25-web-input-machine-and-slash-pipeline.md) | 中文
+
+> 范围:输入状态机(occurrence 表 + claim 看护 + 提交事务)、hub/facade 与发送编排、跨插件输入改写的三个 scoped bail 事件、`/` 与 `@` 触发检测与菜单管线(ui-slash)、composer 周边坑位体系。依赖[会话作用域 note](2026-07-25-web-client-session-scope-and-provide-channel.md)的 sctx / provide / session-maybe 与 blank 实体模型;命令知识(三型、目录、popup)零涉——那是[命令业务面 note](2026-07-25-web-command-surfaces-and-assembly.md)的领地。
+
+## 问题
+
+两个各自为政的 composer:hero(EmptyState,受控链直写 Session)与会话内 InputBar(普通受控 textarea),行为、draft 所有权、发送路径全不一致。要让 `/` 命令、skill 引用、`@` 引用三类触发进入输入面,必须回答:
+
+- 三类触发如何分层,谁对"命令"有知识、谁零知识;
+- 输入框如何表达"命令态"——从 draft 文本推导还是显式状态?退格、回车、空格、整行粘贴各是什么语义;
+- 提交是异步事务(RPC 往返)——晚到结果回灌、会话切换、React concurrent 重放如何防御;
+- 引用 chip 在纯 textarea 上如何表示,undo/剪贴板/粘贴匹配/模型序列化各归谁;
+- 跨插件的输入改写(菜单回填、引用插入、token 消费)如何做到依赖倒置;
+- 无 session → blank session 时哪些 React 外壳必须复用,哪些严格 session 输入体允许替换。
+
+硬约束:组件一律经 slots 挂载;呈现物不进 session log;键盘路径全程 IME 安全。
+
+## 决策
+
+### 输入状态机(`InputMachine`)
+
+纯状态机,事件进/效果出,注入时钟。四相 phase(plain / adjudicating / claimed / submitting)。命令态**永不从 draft 推导**,由 pick 路径在离散时刻显式建立;claim 由 `draft.startsWith(token)` 看护、退格破坏自动 release;claim 形状 `{token, hint?}`(hint 供 ghost text)。
+
+事件面(`dispatch(ev)` 单写入口,每个事件一个 transaction):
+
+- `draft-changed {draft, editRange?}`——textarea 全量草稿;editRange 缩小 occurrence 平移计算,缺省前后缀共扫。
+- `newline {selection}`——Ctrl+Enter 换行(不经浏览器 execCommand:自管 undo 下浏览器写入会分叉双历史)。
+- `begin-command {claim, span}` / `insert-ref {reference, span}` / `consume-token {guard}`——三个 bail 事件的机器侧;span CAS = draftRev 相等。
+- `set-invalid {invalidIds}`——owner resolution 结果的样式位(非 transaction)。
+- `undo` / `redo`——自管 transaction log(环形 100;单字符打字按注入时钟窗合并;提交成功清 log)。
+- `paste-begin {text, selection, components?, generation?}`——粘贴 + 热快照同步匹配组件同 transaction(Undo 一次回粘贴前);打开 PasteMatchAttempt。
+- `paste-upgrade {attemptId, span, reference}`——异步匹配升级为独立 transaction(Undo 两段);attempt 保持 current,insertedRange 随升级收缩。
+- `invalidate-paste`——DOM 层观察到的 attempt 终结手势(caret/selection 操作等)。
+- `enter {mode}` / `adjudicated` / `adjudication-failed` / `submit-settled` / `release`——提交事务平面:SubmitAttempt(seq + AbortSignal)防回灌,成功 commit 清稿,失败带漂移守卫 rollback(回车时快照仅当 live draft 仍等于它才回填;用户已再输入则只发 notice)。
+
+效果面(shell 执行):`adjudicate`(调 SlashController.adjudicate)、`begin-submit`(claim.submit 事务)、`default-sink`(普通消息,hub 编排)、`notice`。
+
+occurrence 表与 chip 三投影:
+
+- 每颗引用在 draft 中占一个 `U+FFFC`;表项 `{occurrenceId, source, ref, offset, label, clipboardText, invalid?}`;同名 chip 因 occurrenceId 独立。
+- 一切编辑同 transaction 更新 draft 与表:区间平移;与占位符相交的删除/替换作用于整颗。
+- 单字符占位使键盘原子性大半原生成立(caret 无内部位;Backspace/方向键/Shift 扩选原生即整颗);鼠标点 chip 由 backdrop 命中 → 整颗 setSelectionRange。
+- 视觉投影 = label:backdrop 在占位符 offset 渲染 chip(textarea 字形不可见),invalid 走失效样式。
+- 剪贴板/持久化投影 = clipboardText:copy/cut 把选区内占位符展开;draft 持久化 mirror 写同一投影(chat store 里永远是普通文本,刷新 seed 语义 = 全选复制→重开→粘贴,chip 跨刷新降级为文本)。
+- 模型投影 = submit 时经 source `codec.serialize` 逐颗生成(归 submit attempt 的 signal 与 stale guard;owner 缺失/失败/取消则不发送,不降级为 `/name`)。
+
+### 跨插件输入改写:三个 scoped bail 事件
+
+契约声明在 ui-slash(依赖最底层),生产者经 `sctx.bail(sctx, ...)` 派发,唯一消费侧是 hub 建 shell 时挂在 sctx 上的三个 listener;返回 `true` ⟺ 机器过 phase + CAS 守卫并实际改写(发出事件 ≠ 修改成功,Space 是否 `preventDefault` 以返回值为准):
+
+- `slash/input-begin-command` `{claim, span}`——菜单 pick / Space 裁决出的命令 claim 回填(SlashController 派发)。
+- `slash/input-insert-reference` `{reference, span}`——引用 chip 插入(SlashController 派发)。
+- `slash/input-consume-token` `{guard: span | bare-token}`——业务成功后消费命令 token(下游命令面派发)。
+
+不事件化的调用(registry 注册 → 显式调用 → await):Input 自身的 draft/submit、Enter 异步裁决、reference serializer、异步 paste matcher。`@mode bail` 已入 JSDoc parser 与 cordis catalog 门禁(scripts/jsdoc.ts)。
+
+### slash 管线(ui-slash:root `SlashService` + per-session `SlashController`)
+
+对"命令"零知识的触发/菜单/pick 管线:
+
+- service 只有 source 注册表(`SlashSource{trigger: '/'|'@', name, candidates, onPick, matchSpace?, matchEnter?}`;(trigger,name) 唯一、注册序 = 组序 = 轮询序)与 `sessionOf(sctx)`。实现 match 钩子即参与空格/回车裁决的声明;管线按注册序轮询,首个非 undefined 应答胜出,无人认领落 default sink。matchSpace 同步(空格在击键中触发,只许热缓存);matchEnter 异步(可 await 源自身预热,预热失败即 reject)。
+- controller 持有唯一权威 hit(含 span;菜单关闭后为 Space 保留)、per-session menu store、候选 fetch generation、键盘仲裁(combobox 模式:焦点始终在 textarea,↑↓/Enter/Escape 拦截且全程过 IME composition 守卫,唯一例外 Shift+Enter 无条件先行)、pick 编排(outcome → 自派 bail 事件);每个 session scope 出生时对 source roster 做一次 `warm(projection)`,projection 在该 scope 内只有稳定的 sessionId,无 published/能力跃迁;scope disposer 拆除 controller。
+- 触发检测词边界(`user@host`、URL `/` 永不触发)、守卫分档(plain:`/` 到处 + `@` 行内 / claimed:`/` 抑制、`@` 活 / frozen:全无)为冻结纯核。
+
+### hub / facade:常驻外壳与严格 session 输入体
+
+- hub(trigger/decoration 注册表 + 发送编排)对 slash/command 服务是可选 `ctx.get()` 依赖:无 ui-slash/命令面时输入正常收发,优雅降级。
+- 每个实体 Session 只有一个 `SessionInputShell`(facade),随 session scope 创建和拆除;无 session 时不造 input machine。`ConversationRoot` 自身是 `session-maybe` 常驻外壳,持有 HeroShell、Workspace picker、composer stack 与 chain fallback 外框。
+- 无 session 时外壳渲染纯展示的 `DisabledInputBar`;`connectWorkspace` 返回 blank session 后,仅输入体换成严格 session 的 InputBar。这里允许 textarea 重建,`ConversationRoot`、Hero 与布局骨架保持;blank → engaging/active 仍是同一 session-bound InputBar,textarea 不因 phase 翻转而重建。
+- ConversationRoot 的 Hero 判据是 `sessionId === undefined || (composerPhase === 'blank' && (openState === 'open' || openState === 'loading'))`。首次 submit 同步进入 engaging,失败也保留 composer 与错误上下文,不退回 blank Hero;sidebar 的 blank 位只在 prompt 成功受理后翻 false。
+- 发送统一在 hub defaultSink:乐观清稿后只走 `session.prompt {mode:'queue'|'steer'}`;失败且 live draft 仍为空才回填,用户已经继续输入则不覆盖。不存在 Draft materialize 或 attach 事务。
+- blank Hero 改选 Workspace 时,外壳调用 `connectWorkspace`;目标 session 不同时把非空 draft 从当前 shell 搬到目标 shell,再 open 新 id,旧 blank session 留存但不再 current。
+- Notifier 双位契约:`dirty`(快照新鲜度,`ensureFresh` 拉取可清)与 `notifyPending`(通知欠账,只有 flush 清)各自独立——拉取不得吞推送,对象层推订阅者(watchTransaction)依赖这一保证。
+
+### 纯文本引用(决策 21):text outcome 与 lexicon 装饰
+
+skill/@subagent 引用不走占位符 + occurrence 身份链——pick 直接把 `/name ` `@name ` 原文插进 draft,chip 视觉纯派生:
+
+- PickOutcome 增 `{text}` arm;新 scoped bail 事件 `slash/input-insert-text` `{text, span}`(与另三个同契约:draftRev CAS、返回 true ⟺ 实际改写);facade.insertText 走 setDraft 拼接,机器零改动。
+- source 可选 `lexicon?(session)` 钩子:同步热快照名录,`undefined` = 数据未热——零装饰、永不触发 fetch(渲染路径保持同步无副作用);controller 聚合为 `lexicon()` 公面。
+- `decorations.scanTextRefs`:词边界扫描 draft(行首/空白后的 `/name`、`@name`,`x/name` 永不命中)对照名录,命中即 `.textRef` mark(backdrop 纯 range 高亮,同 hlToken);编辑破坏匹配形状下次扫描自然消失。
+- 发送即原文(不再 `` 序列化);气泡侧 MessageItem 双形状装饰(legacy `` 标签 + 纯文本 token)。
+- 旧 occurrence/paste/serialize 链全部保留在盘未删(additive;删除另成将来一刀)。已知局限维持现状:粘贴/冷启动时 lexicon 未热不装饰,输 `/` 开一次菜单后才亮。
+
+### per-session 供数贡献与键盘私面
+
+- ui-conversation(hub 兼贡献者)经 `sessions.provide` 供 `'input'` hook(机器状态 + queue overlay)+ `inputActions` prop(`setDraft`/`submit`,稳定 void 回调)。
+- 公私分界:公共 provide 只放 React 语汇成员;键盘/DOM 命令面(track/arbitrate/space/undo/redo/paste/dismissPopup/bindMirror——同步返回值、disposer 语义)是 InputBar 独占,走 InputBar entry 自己的 inject 包内私递,不出插件边界。
+
+### 坑位体系
+
+`conversation` 本身是 session-maybe;其会话内容与 composer 输入坑位严格 session,Hero Workspace picker 保持 root。子坑均由 ui-conversation 的 conversation 注册声明:
+
+- `conversation.session`(single)——严格 session 的 header、view ring 与 chat store;session id 切换时重建。
+- `conversation.composer.bar`(single)——InputBar 本体的坑位:InputBar 是真 slot entry(自家坑自注册),composer chain fallback 的内容;不做 chain entry——chain 单选举会在 takeover 时卸载它,破坏 textarea DOM 存活。
+- `conversation.input.overlay`——输入卡内浮层锚点;注册者 inject 按 slot sessionId 解析各自 per-session controller。
+- `conversation.input.dock`——输入上方堆叠条(QueueDock 的队列只读列表落此),order 定序。
+- `conversation.composer.dock`——composer 上沿统计带。
+- `conversation.input.left` / `conversation.input.right`——工具行左右区。
+- `conversation.input.plan` / `conversation.input.model`(single)——工具行两具名控制位;bar 只传 `locked`(owner props),空到 owning 插件注册为止,无占位 fallback。
+- `conversation.hero.workspace`(root scope)——无 session / blank Hero 共用的 Workspace picker;pick 经 `connectWorkspace` 复用或创建目标 blank session,必要时搬运 draft 后切 current。
+
+### 测试纪律
+
+状态机全部行为由纯 JS 单测覆盖(事件序列进、断言状态与效果,零浏览器 DOM);交互矩阵逐行投影测试。这一要求正是纯核 + 服务壳分层的成因。
+
+## Alternatives considered
+
+| 弃案 | 一行理由 |
+|---|---|
+| ActiveCommand 中间态 / registerMode 模式注册表 / 从 draft 推导命令态 | claim 由 pick 路径显式建立——无表、无推导 |
+| bindTarget/bindDraft 对象直连 | 反向耦合 + root 单例跨会话误配;scoped bail 事件保依赖倒置且路由结构性正确 |
+| 统一 slash/input-apply 或全事件化 | 三个独立 payload 覆盖跨插件改写;异步链路保持 registry 显式调用 |
+| contenteditable / 富文本树 | 兼容性差;textarea + U+FFFC + occurrence 表覆盖全部交互契约 |
+| draft 双持久化 {text, occurrences} | mirror 写剪贴板投影零新概念;chip 跨刷新降级可接受 |
+| 原生 textarea undo 栈 | 受控 + 程序化写入下不可靠;粘贴两段 undo 语义只能自管 |
+| InputBar 收 16 员 wiring 回调包 | 消费矩阵实证 11 员 InputBar 独占、1 员死成员;标准件通道让组件自取,键盘面包内私递 |
+| 空格裁决也认领即执行型命令 | 误触发防线:空格后整行是普通 prompt;不可逆副作用只留显式入口 |
+| 通用 tokenPattern 装饰机制 | 结构化 occurrence 记录取代模式扫描 |
+| 占位 select 常驻工具行 | 具名坑位空到注册为止;占位件与真实现冲突时是双真相源 |
+| 引用一律走 U+FFFC chip(决策 21 前旧线) | 纯文本 + 派生装饰零身份状态;原文即模型投影,undo/剪贴板免特判;chip 链保留给需要不可分原子性的场景 |
+
+## 后果
+
+- 一个常驻 conversation 外壳承接 no-session/blank/active:无 session → blank 只保证大框架 React identity,允许 disabled textarea 替换为严格 InputBar;同一 blank session → engaging/active 保持 InputBar 与 textarea。EmptyState 与受控 intent 链(`sessions.updateIntent`/`updatePendingPrompt`/`workspaces.sendSession`)随最后消费者一并删除。
+- 输入面对命令零知识 + 可选依赖:无命令包时纯输入可用;`@` 引用与 skill 引用免费复用同一菜单/pick 管线。代价是空格/回车裁决是逐 source 轮询协议,其应答语义(同步/异步、undefined 含义)为冻结契约。
+- 提交事务化(attempt seq + 漂移守卫)使晚到结果回灌、会话切换、concurrent 重放三类缺陷结构性不可能,由矩阵测试钉住。
+- 已知欠账:chip 跨刷新保真(可复用粘贴匹配)未立项;subagent 引用的模型表示待业务立项。
diff --git a/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.i18n.yaml
new file mode 100644
index 0000000000..66ff10e180
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.i18n.yaml
@@ -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
+2026-07-26-packed-chunk-rows-by-default.md: d6a044676604e4a4512a7a6674edb80e120b2f3c
+2026-07-26-packed-chunk-rows-by-default.zh.md: 184d462d70dcc666a0b38497ead307ce6861382d
diff --git a/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.md b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.md
new file mode 100644
index 0000000000..d6a0446766
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.md
@@ -0,0 +1,59 @@
+# Agent Note: Make packed chunk rows the default JSONL layout
+
+Status: implemented
+
+English | [中文](2026-07-26-packed-chunk-rows-by-default.zh.md)
+
+## Problem
+
+Provider streams produce many token-sized `assistant/chunk` delta events whose repeated JSON envelopes can outweigh their payloads. The session log must retain each chunk as a distinct logical event: live `session/event` delivery, sequence numbers, `sourceEventSeqs`, replay, cancellation evidence, and UI streaming all depend on those boundaries.
+
+The JSONL storage seam can reduce that envelope cost without changing the logical log. A run of at least three consecutive same-block delta events fits in one `text-chunks`, `reasoning-chunks`, or `tool-call-chunks` storage row, and decoding reconstructs every original event, timestamp, and sequence number. A credible default must cover runtime writers, app-level config, snapshot producers, and committed fixtures together; otherwise tests avoid the layout that deployments write.
+
+## Decision
+
+`dsh-session-persistence-jsonl` resolves an omitted `packChunks` to `true`. The ACP demo wrapper exposes the same default, and every composition that omits the field inherits packed writes. `packChunks: false` remains an explicit write-side diagnostic mode that stores one event per line.
+
+Reading is unconditional and layout-blind. Packed, unpacked, and mixed files load into the same contiguous `SessionEvent[]`, so the default does not require a session-format version change or an on-disk runtime migration. The option controls newly appended batches only; it never selects a reader mode.
+
+### Logical events and physical rows
+
+Packing stays at the `dsh-session` storage seam through `packChunkRuns()` and `decodeStorageRecord()`. The encoder recognizes exact delta-event shapes, preserves unrecognized events verbatim, and packs only runs of at least three. A packed row is storage vocabulary, not a `SessionEventMap` member: it never enters `Session.events` or fires `session/event`.
+
+The JSONL backend packs each durable append batch. Raw `compression: 'none'` and default Zstandard framing carry the same logical storage records; selecting raw mode for reviewable fixtures does not disable packing. Repository replay readers and normalizers decode the shared row format instead of maintaining snapshot-specific codecs.
+
+### Canonical snapshot fixtures
+
+Every committed session-format JSONL fixture uses the canonical packed representation. `scripts/session-fixture-layout.snapshot.ts` discovers tracked `*.jsonl` files and unignored untracked additions repository-wide, selects those whose first record is a `session` header, decodes all body records, and rejects content that differs from `packChunkRuns()` output. The inventory therefore includes ACP, headless, TUI, `apps/web`, parent sessions, child sessions, and future fixture names without a maintained path list.
+
+ACP and headless snapshot runs harvest the default JSONL backend output. TUI and web record-mode writers apply `packChunkRuns()` to their in-memory events before writing fixtures. The authored `packed-chunks` ACP scenario runs under the ordinary config and retains all three packed row kinds; its contract decodes both its independent source fixture and target fixture before asserting event-for-event equality.
+
+Focused package tests keep unpacked and mixed-layout inputs for reader compatibility. They do not opt the default snapshot corpus out of the canonical layout.
+
+### In-flight branch convergence
+
+The temporary [`scripts/migrate-packed-session-fixtures.ts`](../../../../scripts/migrate-packed-session-fixtures.ts) command lets in-flight branches converge after merging current `master`: `pnpm run migrate:packed-session-fixtures` discovers the same repository-wide fixture set as the permanent gate, preserves each header line, decodes existing mixed records, writes the canonical packed body, proves decoded equality, and proves idempotence. It never calls a model or regenerates transcript and presentation outputs.
+
+The command remains linked from the testing policy and ACP snapshot README while older branches may carry fixture edits. The [removal proposal](../../proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md) deletes the CLI, package command, this transitional section, and the documentation links, then replaces the permanent gate's command-specific remediation text once a live open-PR inventory shows that every affected branch is merged, closed, or canonical. The shared canonicalizer and snapshot gate remain permanent.
+
+### Verification contract
+
+JSONL persistence tests prove that omission writes a packed row, explicit `false` writes one event per line, and both forms load identical events. Canonicalizer unit tests cover header preservation, unpacked conversion, non-session JSONL, already-packed idempotence, and malformed input. The keyless snapshot gate covers every committed fixture and assembled replay path; documentation gates keep config defaults and bilingual contracts aligned.
+
+## Alternatives considered
+
+**Flip only the backend schema default.** This leaves wrapper defaults, direct TUI/web serializers, existing fixtures, and future fixture policy inconsistent. A default is meaningful only when shipping compositions and the tests representing them share it.
+
+**Keep snapshots unpacked for readability.** Packed rows retain every fragment and timestamp explicitly, while the shared decoder and normalizer provide logical inspection. Keeping the largest committed consumer on a different layout would make snapshot coverage avoid the shipping write path.
+
+**Remove `packChunks` and always pack.** One writer is simpler, but one-event-per-line output remains useful for diagnostics and for focused mixed-layout compatibility tests. The explicit opt-out preserves those current consumers without weakening the default.
+
+**Batch chunks as logical session events.** This reduces event count, but it delays or reshapes live delivery, renumbers provenance, and requires every UI and replay consumer to understand another streaming unit. Physical packing obtains the storage benefit behind the existing persistence interface.
+
+**Keep the branch migrator permanently.** The read-only canonicalizer and snapshot gate own continuing enforcement. A mutation command has value only while in-flight branches still carry the former fixture layout, so its lifetime is explicitly bounded by the removal proposal.
+
+## Consequences
+
+Ordinary JSONL writes and committed fixtures use fewer physical rows while preserving the exact logical event stream. Runtime readers accept every existing layout, and operators retain a deliberate unpacked diagnostic mode. Raw files are less convenient for per-token line processing, and external tools that incorrectly treat every post-header row as a `SessionEvent` encounter storage tags more often; supported readers call `decodeStorageRecord()`.
+
+The repository carries a large mechanical fixture diff, reviewed through decoded equality and the canonical-layout gate rather than token-by-token line inspection. It also temporarily carries one branch migration command and its links; the separate removal proposal prevents that transition aid from becoming permanent process surface.
diff --git a/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md
new file mode 100644
index 0000000000..184d462d70
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md
@@ -0,0 +1,59 @@
+# Agent Note: 将打包分片行设为默认 JSONL 布局
+
+Status: implemented
+
+[English](2026-07-26-packed-chunk-rows-by-default.md) | 中文
+
+## 问题
+
+提供方流会产生大量 token 大小的 `assistant/chunk` 增量事件,其重复 JSON 封装可能比载荷本身更大。会话日志必须将每个分片保留为独立的逻辑事件:实时 `session/event` 传递、序号、`sourceEventSeqs`、回放、取消证据和 UI 流式输出都依赖这些边界。
+
+JSONL 存储 seam 可以在不改变逻辑日志的情况下减少这部分封装开销。一段至少包含 3 个连续、同属一个块的增量事件可以编码为一条 `text-chunks`、`reasoning-chunks` 或 `tool-call-chunks` 存储行,解码则会重建每个原始事件、时间戳和序号。一个可信的默认值必须同时覆盖运行时写入器、应用级配置、快照生成器和签入仓库的 fixture(测试前置数据);否则测试会绕开部署实际写入的布局。
+
+## 决策
+
+`dsh-session-persistence-jsonl` 会将省略的 `packChunks` 解析为 `true`。ACP(Agent Client Protocol)演示包装层公开相同的默认值,所有省略该字段的组合都会继承打包写入。`packChunks: false` 仍是写入侧显式诊断模式,以每事件一行的形式存储。
+
+读取始终不受选项控制且与布局无关。打包、非打包和混合文件都会加载为相同且连续的 `SessionEvent[]`,因此更改默认值不需要变更会话格式版本,也不需要对磁盘数据执行运行时迁移。该选项只控制新追加的批次,绝不会选择读取器模式。
+
+### 逻辑事件与物理行
+
+打包保留在 `dsh-session` 的存储 seam,并通过 `packChunkRuns()` 和 `decodeStorageRecord()` 实现。编码器识别精确的增量事件形态,原样保留无法识别的事件,并且只打包至少包含 3 个事件的连续段。打包行属于存储词汇,不是 `SessionEventMap` 成员:它绝不会进入 `Session.events`,也不会触发 `session/event`。
+
+JSONL 后端会打包每个持久追加批次。原始模式 `compression: 'none'` 与默认 Zstandard 帧承载相同的逻辑存储记录;为使 fixture 便于评审而选择原始模式,不会禁用打包。仓库中的回放读取器和规范化器会解码共享行格式,而不维护快照专用编解码器。
+
+### 规范快照 fixture
+
+每个签入仓库的会话格式 JSONL fixture 都使用规范打包表示。`scripts/session-fixture-layout.snapshot.ts` 会在整个仓库中发现已跟踪的 `*.jsonl` 文件,以及未被忽略的新增未跟踪 JSONL 文件,选择首条记录为 `session` header 的文件,解码所有正文记录,并拒绝与 `packChunkRuns()` 输出不同的内容。因此,该清单无需维护路径列表即可覆盖 ACP、headless、TUI、`apps/web`、父会话、子会话以及未来的 fixture 名称。
+
+ACP 和 headless 快照运行会采集默认 JSONL 后端的输出。TUI 和 web 的记录模式写入器会在写入 fixture 前,对内存事件应用 `packChunkRuns()`。人工编写的 `packed-chunks` ACP 场景在普通配置下运行,并保留全部 3 种打包行类型;其契约先解码独立的源 fixture 和目标 fixture,再断言二者逐事件相等。
+
+聚焦的包(package)测试保留非打包和混合布局输入,以验证读取器兼容性。这些测试不会让默认快照语料库豁免规范布局要求。
+
+### 在途分支收敛
+
+临时命令 [`scripts/migrate-packed-session-fixtures.ts`](../../../../scripts/migrate-packed-session-fixtures.ts) 让在途分支合并当前 `master` 后可以完成收敛:`pnpm run migrate:packed-session-fixtures` 会发现与永久门禁相同的仓库级 fixture 集合,保留各文件的 header 行,解码现有混合记录,写入规范打包正文,并证明解码结果相等且操作具有幂等性。该命令绝不会调用模型,也不会重新生成 transcript(文本记录)与呈现输出。
+
+只要较旧分支仍可能携带 fixture 改动,测试政策和 ACP 快照 README 就会继续链接该命令。最新的开放 PR(Pull Request)清单确认每个受影响分支均已合并、关闭或符合规范后,[移除提案](../../proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md)会删除该 CLI、包命令、本过渡章节和文档链接,并替换永久门禁中仅适用于该命令的修复指引。共享规范布局转换器与快照门禁保持永久存在。
+
+### 验证契约
+
+JSONL 持久化测试证明:省略选项时会写入打包行,显式传入 `false` 时会按每事件一行的形式写入,两种形式都会加载为完全相同的事件。规范布局转换器单元测试覆盖 header 保留、非打包转换、非会话 JSONL、已打包输入的幂等性和畸形输入。无密钥快照门禁覆盖每个签入仓库的 fixture 和组装后的回放路径;文档门禁则确保配置默认值与双语契约保持一致。
+
+## 曾考虑的替代方案
+
+**仅翻转后端 schema 默认值。** 这会让包装层默认值、TUI/web 直接序列化器、现有 fixture 与未来 fixture 政策仍然彼此不一致。只有已交付组合及代表这些组合的测试采用相同默认值时,该默认值才有意义。
+
+**快照继续使用非打包格式以便阅读。** 打包行仍会显式保留每个片段和时间戳,共享解码器与规范化器则提供逻辑检查。如果让规模最大的签入仓库消费方采用不同布局,快照覆盖就会绕开已交付的写入路径。
+
+**删除 `packChunks` 并始终打包。** 只保留一个写入器更简单,但每事件一行的输出仍适用于诊断和聚焦的混合布局兼容性测试。显式停用选项在不削弱默认值的同时,保留了这些现有消费方。
+
+**把分片批量合并为逻辑会话事件。** 这会减少事件数量,但也会延迟或重塑实时传递,改变溯源信息所引用的序号,并要求每个 UI 和回放消费方理解另一种流式单位。物理打包通过现有持久化接口获得存储收益。
+
+**永久保留分支迁移器。** 只读的规范布局转换器与快照门禁负责持续强制执行。只有在途分支仍携带旧 fixture 布局时,会修改仓库内容的命令才有价值,因此移除提案明确限定了其生命周期。
+
+## 后果
+
+常规 JSONL 写入与签入仓库的 fixture 使用更少的物理行,同时精确保留逻辑事件流。运行时读取器接受所有现有布局,操作方也保留有意提供的非打包诊断模式。按 token 逐行处理原始文件较为不便;错误地将 header 后每一行都视为 `SessionEvent` 的外部工具会更频繁地遇到存储 tag,受支持的读取器则会调用 `decodeStorageRecord()`。
+
+仓库会产生大规模机械 fixture diff;评审应依据解码结果相等这一事实和规范布局门禁,而不是逐行、逐 token 检查。仓库还会暂时保留一个分支迁移命令及其链接;单独的移除提案会防止这项过渡辅助机制成为永久的流程接口。
diff --git a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml
index 5556ed5fa1..89c1e8e8c1 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml
+++ b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml
@@ -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-21-semantic-session-checkpoints.md: 4bca02fe3893ac39621ed79a000ca8f86db4ff67
-2026-07-21-semantic-session-checkpoints.zh.md: 1f187eb6448a3c9ca6784ec2bddd7295be2706d7
+2026-07-21-semantic-session-checkpoints.md: 0034cde40e5b07bda1573ca39fb7d51816006140
+2026-07-21-semantic-session-checkpoints.zh.md: 3351221d7eeaf1353b4adb0fa4c4dc324ec33da5
diff --git a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md
index 4bca02fe38..0034cde40e 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md
+++ b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md
@@ -26,4 +26,4 @@ Flushing every event or streaming chunk minimizes loss but turns local append an
## Consequences
-Hard-crash recovery retains the complete model request, durable tool intent, and complete settled step at the nearest semantic boundary while allowing partial streaming chunks since the previous boundary to remain lossy. Default CLI, TUI, ACP, Python SDK runtime, headless persistence tests, and JSON-RPC compositions mount the policy with their persistence backend. Unit tests cover ordering, cancellation during a checkpoint, fail-closed behavior, nested dispatch, disposal, and Loader shape; a real child process killed with `SIGKILL` proves request and tool-intent recovery through JSONL, and the shared persistence contract proves both recovery classifications across backends. Keyless ACP snapshots prove both that retry-risk guidance reaches resumed history and the next model turn and that graceful cancellation persists the loop's real closing boundaries.
+Hard-crash recovery retains the complete model request, durable tool intent, and complete settled step at the nearest semantic boundary while allowing partial streaming chunks since the previous boundary to remain lossy. Default CLI, TUI, ACP, Python SDK runtime, headless persistence tests, and JSON-RPC compositions mount the policy with their persistence backend. Unit tests cover ordering, cancellation during a checkpoint, fail-closed behavior, nested dispatch, disposal, and Loader shape; a real child process killed with `SIGKILL` proves request and tool-intent recovery through JSONL, and the shared persistence contract proves both recovery classifications across backends. The crash harness waits for the expected marker contents rather than path existence, so open-before-write visibility cannot trigger the kill early. Keyless ACP snapshots prove both that retry-risk guidance reaches resumed history and the next model turn and that graceful cancellation persists the loop's real closing boundaries.
diff --git a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md
index 1f187eb644..3351221d7e 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md
+++ b/.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md
@@ -26,4 +26,4 @@ ACP(Agent Client Protocol)应用在一个有序 Cordis effect 中统一持
## 后果
-发生硬崩溃时,崩溃恢复会在最近的语义边界保留完整的模型请求、持久化的工具意图与完整且已结束的步骤,但允许上一个边界之后的部分流式分片仍可能丢失。默认的 CLI(命令行界面)、TUI、ACP、Python SDK 运行时、headless 持久化测试与 JSON-RPC 组合都会在持久化后端旁加载该策略。单元测试覆盖顺序、检查点期间的取消、失败关闭行为、嵌套分发、dispose(资源释放)与 Loader 形状;一个被 `SIGKILL` 终止的真实子进程通过 JSONL 证明系统可以恢复请求与工具意图,共享持久化契约则证明各后端都支持这两种恢复分类。无密钥 ACP 快照既证明重试风险指引会进入恢复后的历史记录与下一个模型轮次,也证明取消流程正常收尾时,系统会持久化由循环实际生成的闭合边界。
+发生硬崩溃时,崩溃恢复会在最近的语义边界保留完整的模型请求、持久化的工具意图与完整且已结束的步骤,但允许上一个边界之后的部分流式分片仍可能丢失。默认的 CLI(命令行界面)、TUI、ACP、Python SDK 运行时、headless 持久化测试与 JSON-RPC 组合都会在持久化后端旁加载该策略。单元测试覆盖顺序、检查点期间的取消、失败关闭行为、嵌套分发、dispose(资源释放)与 Loader 形状;一个被 `SIGKILL` 终止的真实子进程通过 JSONL 证明系统可以恢复请求与工具意图,共享持久化契约则证明各后端都支持这两种恢复分类。崩溃 harness 会等待预期的标记内容,而不是仅等待路径存在,因此文件在写入前因打开而可见时,不会导致该 harness 提前终止子进程。无密钥 ACP 快照既证明重试风险指引会进入恢复后的历史记录与下一个模型轮次,也证明取消流程正常收尾时,系统会持久化由循环实际生成的闭合边界。
diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml
index b2d0ae7c13..242e981a84 100644
--- a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml
@@ -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-15-code-mode.md: 1170fa4f9fa778fa9176097317477ea336588d32
-2026-06-15-code-mode.zh.md: 4362efa332d0ed24a6383a4cf75b83c0dba22e7e
+2026-06-15-code-mode.md: 38d1ebdda089f1cfa1c5f3192fa2399b3a00102b
+2026-06-15-code-mode.zh.md: 2ddccb005b806fee0b1f4d6f79d6473f492a117d
diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.md
index 1170fa4f9f..38d1ebdda0 100644
--- a/.agents/notes/implemented/feature/2026-06-15-code-mode.md
+++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.md
@@ -50,7 +50,7 @@ Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentat
**Concurrency is serialized.** Each run owns a dispatch queue, so even `Promise.all` executes tool calls in submission order. Settlement abandons queued calls that have not started. Parallelism requires per-tool concurrency-safety metadata.
-**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../bug-fix/2026-07-20-code-mode-result-card-completeness.md).
+**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md).
### Observability: `tool/code-dispatch`
diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md
index 4362efa332..2ddccb005b 100644
--- a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md
+++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md
@@ -50,7 +50,7 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一
**并发被序列化。** 每次 run 拥有一个分发队列,因此即使 `Promise.all` 也按提交顺序执行工具调用。结算时放弃尚未开始的排队调用。并行化需要每个工具的并发安全元数据。
-**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy 输出落盘预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../bug-fix/2026-07-20-code-mode-result-card-completeness.md)。
+**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy 输出落盘预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。
### 可观测性:`tool/code-dispatch`
diff --git a/.agents/notes/implemented/feature/2026-07-07-session-prefix.i18n.yaml b/.agents/notes/implemented/feature/2026-07-07-session-prefix.i18n.yaml
index 7c81df63b3..e189661f67 100644
--- a/.agents/notes/implemented/feature/2026-07-07-session-prefix.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-07-07-session-prefix.i18n.yaml
@@ -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-07-session-prefix.md: 6671a7e84c885ff066adbd55b0a2e0d5ddffe49a
-2026-07-07-session-prefix.zh.md: 4efbf200dfa71222b276b9d933748c8f0eb01607
+2026-07-07-session-prefix.md: 75113952fc5f3df8da1580d42ed2a385b6135fe8
+2026-07-07-session-prefix.zh.md: e38bf09298296203b275d6d66a62ef17be7c045d
diff --git a/.agents/notes/implemented/feature/2026-07-07-session-prefix.md b/.agents/notes/implemented/feature/2026-07-07-session-prefix.md
index 6671a7e84c..df00701216 100644
--- a/.agents/notes/implemented/feature/2026-07-07-session-prefix.md
+++ b/.agents/notes/implemented/feature/2026-07-07-session-prefix.md
@@ -26,7 +26,7 @@ Because composition runs before the boundary snapshot, a composing listener's se
## Testing
-[Interception tests](../../../../packages/core/agent-loop/tests/interception.spec.ts) pin compose-once reuse without changed headers, prepend order, empty-prefix omission, immutability, composition before pre-step, and the prefix on the routed header; [cancellation tests](../../../../packages/core/agent-loop/tests/cancel.spec.ts) pin discard and recomposition. Session, invariant, token-meter, and compaction tests cover header round trips, request reconstruction, and durable prefix-aware pressure accounting. Snapshot normalization preserves prefix counts, while the [pinned-header scenario](../testing/2026-07-06-pin-request-header-content-in-one-scenario.md) owns content and the default example remains prefix-free. The provider-independent seam needs no dedicated e2e; the with-key [request-cache e2e](../../../../packages/core/agent-loop/tests/request-cache.e2e.ts) covers its cache economics.
+[Interception tests](../../../../packages/core/agent-loop/tests/interception.spec.ts) pin compose-once reuse without changed headers, prepend order, empty-prefix omission, immutability, composition before pre-step, and the prefix on the routed header; [cancellation tests](../../../../packages/core/agent-loop/tests/cancel.spec.ts) pin discard and recomposition. Session, invariant, token-meter, and compaction tests cover header round trips, request reconstruction, and durable prefix-aware pressure accounting. Snapshot normalization preserves prefix counts, while the [pinned-header scenario](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md) owns content and the default example remains prefix-free. The provider-independent seam needs no dedicated e2e; the with-key [request-cache e2e](../../../../packages/core/agent-loop/tests/request-cache.e2e.ts) covers its cache economics.
## Alternatives considered
diff --git a/.agents/notes/implemented/feature/2026-07-07-session-prefix.zh.md b/.agents/notes/implemented/feature/2026-07-07-session-prefix.zh.md
index 4efbf200df..d0c5747063 100644
--- a/.agents/notes/implemented/feature/2026-07-07-session-prefix.zh.md
+++ b/.agents/notes/implemented/feature/2026-07-07-session-prefix.zh.md
@@ -26,7 +26,7 @@ Status: implemented
## 测试
-[拦截测试](../../../../packages/core/agent-loop/tests/interception.spec.ts)固定了以下行为:没有变更 header 时的组合一次复用、前置插入顺序、空前缀省略、不可变性、组合在步骤前检查点之前完成,以及已路由 header 上的前缀;[取消测试](../../../../packages/core/agent-loop/tests/cancel.spec.ts)固定了丢弃与重新组合。Session、不变式、token-meter 和压缩测试覆盖 header 往返、请求重建与持久前缀感知的压力核算。快照归一化保留前缀计数,[固定 header 场景](../testing/2026-07-06-pin-request-header-content-in-one-scenario.md)拥有内容,默认示例保持无前缀。与提供方无关的 seam 无需专门 e2e;带密钥的 [request-cache e2e](../../../../packages/core/agent-loop/tests/request-cache.e2e.ts) 覆盖了其缓存经济性。
+[拦截测试](../../../../packages/core/agent-loop/tests/interception.spec.ts)固定了以下行为:没有变更 header 时的组合一次复用、前置插入顺序、空前缀省略、不可变性、组合在步骤前检查点之前完成,以及已路由 header 上的前缀;[取消测试](../../../../packages/core/agent-loop/tests/cancel.spec.ts)固定了丢弃与重新组合。Session、不变式、token-meter 和压缩测试覆盖 header 往返、请求重建与持久前缀感知的压力核算。快照归一化保留前缀计数,[固定 header 场景](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)拥有内容,默认示例保持无前缀。与提供方无关的 seam 无需专门 e2e;带密钥的 [request-cache e2e](../../../../packages/core/agent-loop/tests/request-cache.e2e.ts) 覆盖了其缓存经济性。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/feature/2026-07-10-session-query-service.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-session-query-service.i18n.yaml
index f19f20eb9e..b93367f1c4 100644
--- a/.agents/notes/implemented/feature/2026-07-10-session-query-service.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-07-10-session-query-service.i18n.yaml
@@ -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-10-session-query-service.md: 722f6bf6163278719c3bbb598ed2a2a9d042fb8e
-2026-07-10-session-query-service.zh.md: ae6fcb78e18afe83784560c493ea93b7ee0bd68c
+2026-07-10-session-query-service.md: 42d12fe2c5e34e71a6166816857b9ced52a61a95
+2026-07-10-session-query-service.zh.md: 2c8d322ca6099db1c8ddbb8a02efbc8729e83dbf
diff --git a/.agents/notes/implemented/feature/2026-07-10-session-query-service.md b/.agents/notes/implemented/feature/2026-07-10-session-query-service.md
index 722f6bf616..42d12fe2c5 100644
--- a/.agents/notes/implemented/feature/2026-07-10-session-query-service.md
+++ b/.agents/notes/implemented/feature/2026-07-10-session-query-service.md
@@ -12,7 +12,7 @@ Full-text search is related but materially larger. Putting provider coordination
## Decision
-`@deepseek-ai/dsh-session-query` owns the single abstract `ctx.sessionQuery` service over one logical corpus. It concretely implements `listSessions()`, provider-independent `filterSessions(filters)`, `listEvents(sessionId)`, `filterEvents(sessionId, filters)`, bounded `readEvent(request)`, `traceSession(sessionId)`, and `traceEvent(request)`, while concrete backends implement its two full-text methods. The [unified service decision](../architecture/2026-07-23-unified-session-query-service.md) owns that topology, the [SQLite search decision](2026-07-10-sqlite-session-query-provider.md) owns search behavior, and the [tracing decision](2026-07-13-session-query-tracing.md) owns lineage and event-relationship semantics.
+`@deepseek-ai/dsh-session-query` owns the single abstract `ctx.sessionQuery` service over one logical corpus. It concretely implements `listSessions()`, provider-independent `filterSessions(filters)`, `listEvents(sessionId)`, `filterEvents(sessionId, filters)`, bounded `readEvent(request)`, `traceSession(sessionId)`, and `traceEvent(request)`, while concrete backends implement its two full-text methods. The [unified service decision](../../archived/architecture/2026-07-23-unified-session-query-service.md) owns that topology, the [SQLite search decision](2026-07-10-sqlite-session-query-provider.md) owns search behavior, and the [tracing decision](2026-07-13-session-query-tracing.md) owns lineage and event-relationship semantics.
The service observes the optional `ctx.sessionPersistence` binding dynamically but retains no persisted cache or invalidation listener. Each cross-corpus list asks the active backend for authoritative metadata, then overlays a fresh live-store list. Matching ids become one `SessionRecord`: the live header wins and `live`/`persisted` independently report source availability. Immutable header disagreement is `SESSION_QUERY_SOURCE_CONFLICT`.
diff --git a/.agents/notes/implemented/feature/2026-07-10-session-query-service.zh.md b/.agents/notes/implemented/feature/2026-07-10-session-query-service.zh.md
index ae6fcb78e1..2c8d322ca6 100644
--- a/.agents/notes/implemented/feature/2026-07-10-session-query-service.zh.md
+++ b/.agents/notes/implemented/feature/2026-07-10-session-query-service.zh.md
@@ -12,7 +12,7 @@ Status: implemented
## 决策
-`@deepseek-ai/dsh-session-query` 拥有面向单一逻辑语料库的唯一抽象 `ctx.sessionQuery` 服务。它具体实现 `listSessions()`、提供方无关的 `filterSessions(filters)`、`listEvents(sessionId)`、`filterEvents(sessionId, filters)`、有界的 `readEvent(request)`、`traceSession(sessionId)` 和 `traceEvent(request)`,而具体后端实现其两个全文搜索方法。[统一服务决策](../architecture/2026-07-23-unified-session-query-service.md)拥有这一拓扑,[SQLite 搜索决策](2026-07-10-sqlite-session-query-provider.md)拥有搜索行为,[追踪决策](2026-07-13-session-query-tracing.md)拥有血缘与事件关系语义。
+`@deepseek-ai/dsh-session-query` 拥有面向单一逻辑语料库的唯一抽象 `ctx.sessionQuery` 服务。它具体实现 `listSessions()`、提供方无关的 `filterSessions(filters)`、`listEvents(sessionId)`、`filterEvents(sessionId, filters)`、有界的 `readEvent(request)`、`traceSession(sessionId)` 和 `traceEvent(request)`,而具体后端实现其两个全文搜索方法。[统一服务决策](../../archived/architecture/2026-07-23-unified-session-query-service.md)拥有这一拓扑,[SQLite 搜索决策](2026-07-10-sqlite-session-query-provider.md)拥有搜索行为,[追踪决策](2026-07-13-session-query-tracing.md)拥有血缘与事件关系语义。
该服务动态观察可选的 `ctx.sessionPersistence` 绑定,但不保留持久化缓存或失效监听器。每次跨语料库列表操作向活跃后端请求权威元数据,然后叠加一份新鲜的活跃 store 列表。id 匹配的条目合并为一条 `SessionRecord`:活跃 header 优先,`live`/`persisted` 各自独立报告来源可用性。不可变 header 不一致时产生 `SESSION_QUERY_SOURCE_CONFLICT`。
diff --git a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml
index d4b48d4f41..6d9595a8e9 100644
--- a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml
@@ -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-10-sqlite-session-query-provider.md: 98618a7eb572ce59c5fa5984675c9dc57b3f4289
-2026-07-10-sqlite-session-query-provider.zh.md: bb3650da907cf86a853f748fa0ee40d5c2168709
+2026-07-10-sqlite-session-query-provider.md: 372c21241f9ae5d7300165f016db9b36e6b52855
+2026-07-10-sqlite-session-query-provider.zh.md: dc10a6e6a609809aa6f2b194f262e2642d9545bc
diff --git a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md
index 98618a7eb5..372c21241f 100644
--- a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md
+++ b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md
@@ -12,7 +12,7 @@ Splitting those concerns across a provider coordinator and a database implementa
## Decision
-`@deepseek-ai/dsh-session-query` declares one abstract `ctx.sessionQuery` service whose exact reads, filters, and traces are concrete and whose two full-text methods are abstract. `searchSessions(request, exec?)` returns cursor-paginated `SessionSearchHit`s grouped by each session's strongest matching event; `searchEvents(request, exec?)` returns `SessionEventSearchHit`s within one logical session. Both requests require `query`, accept `limit` and an owned branded `SessionSearchCursor`, and support an optional abort signal. Session search accepts `sessionFilters` plus event metadata filters; event search accepts event metadata filters. Results expose bounded plain-text snippets but no provider identifier or numeric relevance score. The [unified service decision](../architecture/2026-07-23-unified-session-query-service.md) owns the single-key topology.
+`@deepseek-ai/dsh-session-query` declares one abstract `ctx.sessionQuery` service whose exact reads, filters, and traces are concrete and whose two full-text methods are abstract. `searchSessions(request, exec?)` returns cursor-paginated `SessionSearchHit`s grouped by each session's strongest matching event; `searchEvents(request, exec?)` returns `SessionEventSearchHit`s within one logical session. Both requests require `query`, accept `limit` and an owned branded `SessionSearchCursor`, and support an optional abort signal. Session search accepts `sessionFilters` plus event metadata filters; event search accepts event metadata filters. Results expose bounded plain-text snippets but no provider identifier or numeric relevance score. The [unified service decision](../../archived/architecture/2026-07-23-unified-session-query-service.md) owns the single-key topology.
`@deepseek-ai/dsh-session-query-sqlite` extends the interface service and is the sole concrete owner of `ctx.sessionQuery`. It depends on live `ctx.sessions`, observes optional `ctx.sessionPersistence` dynamically, and owns a dedicated derived SQLite database. There is no search-provider registry, coordinator, persistence event, or agent-loop integration.
diff --git a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md
index bb3650da90..dc10a6e6a6 100644
--- a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md
+++ b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md
@@ -12,7 +12,7 @@ Status: implemented
## 决策
-`@deepseek-ai/dsh-session-query` 声明一个抽象的 `ctx.sessionQuery` 服务,其精确读取、过滤与追踪均有具体实现,仅有两项全文方法为抽象方法。`searchSessions(request, exec?)` 返回按游标分页的 `SessionSearchHit`,并按每个会话中匹配度最强的事件分组;`searchEvents(request, exec?)` 返回一个逻辑会话内的 `SessionEventSearchHit`。两种请求都必须提供 `query`,可以接受 `limit` 和由服务拥有的品牌化 `SessionSearchCursor`,并支持可选的中止信号。会话搜索接受 `sessionFilters` 与事件元数据过滤器,事件搜索接受事件元数据过滤器。结果会公开有界的纯文本摘要片段,但不公开提供方标识符或数值相关性分数。单一键拓扑由[统一服务决策](../architecture/2026-07-23-unified-session-query-service.md)定义。
+`@deepseek-ai/dsh-session-query` 声明一个抽象的 `ctx.sessionQuery` 服务,其精确读取、过滤与追踪均有具体实现,仅有两项全文方法为抽象方法。`searchSessions(request, exec?)` 返回按游标分页的 `SessionSearchHit`,并按每个会话中匹配度最强的事件分组;`searchEvents(request, exec?)` 返回一个逻辑会话内的 `SessionEventSearchHit`。两种请求都必须提供 `query`,可以接受 `limit` 和由服务拥有的品牌化 `SessionSearchCursor`,并支持可选的中止信号。会话搜索接受 `sessionFilters` 与事件元数据过滤器,事件搜索接受事件元数据过滤器。结果会公开有界的纯文本摘要片段,但不公开提供方标识符或数值相关性分数。单一键拓扑由[统一服务决策](../../archived/architecture/2026-07-23-unified-session-query-service.md)定义。
`@deepseek-ai/dsh-session-query-sqlite` 扩展接口服务,并且是 `ctx.sessionQuery` 唯一的具体所有者。它依赖实时的 `ctx.sessions`,动态观察可选的 `ctx.sessionPersistence`,并拥有一个专用的派生 SQLite 数据库。系统没有搜索提供方注册表、协调器、持久化事件或 agent loop(智能体循环)集成。
diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml
index 601a077189..0e6f7fde7e 100644
--- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml
@@ -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-18-markdown-cross-link-lint.md: 2e3b0f1fcd03f244756b0030f2da758c516a2bbb
-2026-06-18-markdown-cross-link-lint.zh.md: cfe973ae939eceb50d6381ecf35c80df508907c3
+2026-06-18-markdown-cross-link-lint.md: b8b1337e9d758da6a4cc0bb46a6b37906357f877
+2026-06-18-markdown-cross-link-lint.zh.md: 823af80950127a0bf0b76da7769611d0d3a6c09b
diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md
index 2e3b0f1fcd..b8b1337e9d 100644
--- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md
+++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md
@@ -6,7 +6,7 @@ English | [中文](2026-06-18-markdown-cross-link-lint.zh.md)
## Problem
-Docs in this repo link to each other by relative path — `[topic](../implemented/2026-…-….md)`, `[the cookbook](adding-a-tool.md)`, `[architecture.md](../../architecture.md)`. Nothing verified those targets exist. A rename or a move silently breaks every inbound link, and the break is invisible until a reader clicks it. [Doc-sync enforcement](2026-06-11-doc-sync-enforcement.md) already mechanized two classes of doc drift (uncompilable code blocks, a stale event-taxonomy table) and [verify-md-wrap](2026-06-11-doc-sync-enforcement.md) a third (hard-wrapped prose) — but a dead cross-link is a fourth, equally mechanical class that was still verified by eyeball.
+Docs in this repo link to each other by relative path — `[topic](../implemented/2026-…-….md)`, `[the cookbook](adding-a-tool.md)`, `[architecture.md](../../architecture.md)`. Nothing verified those targets exist. A rename or a move silently breaks every inbound link, and the break is invisible until a reader clicks it. [Doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md) already mechanized two classes of doc drift (uncompilable code blocks, a stale event-taxonomy table) and [verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.md) a third (hard-wrapped prose) — but a dead cross-link is a fourth, equally mechanical class that was still verified by eyeball.
The motivating case is the Agent Note tree reorganization that introduced this gate: unifying `docs/adr/` + `.agents/notes/` into one `.agents/notes/` with `proposed/`/`implemented/`/`rejected/` subfolders renamed roughly forty inter-doc links by hand. A single fat-fingered path would have shipped a broken link with nothing to catch it.
diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md
index cfe973ae93..823af80950 100644
--- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md
+++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md
@@ -6,7 +6,7 @@ Status: implemented
## 问题
-本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[Doc-sync 强制](2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移机械化(无法编译的代码块、陈旧的事件分类表),[verify-md-wrap](2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。
+本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[Doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移机械化(无法编译的代码块、陈旧的事件分类表),[verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。
引入这道门禁的直接动因是 Agent Note(agent 决策记录)目录树重组:将 `docs/adr/` 与 `.agents/notes/` 统一到同一个 `.agents/notes/` 下,并设置 `proposed/`、`implemented/`、`rejected/` 子目录,需要手工重命名约 40 条文档间链接。只要有一处路径输入错误,就会在没有任何检查拦截的情况下交付断链。
diff --git a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.i18n.yaml
index cc0a877084..bbf8f64dbc 100644
--- a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.i18n.yaml
@@ -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-agent-note-classification.md: 094233ed108e35e390cdc66b419179249c2d9173
-2026-06-20-agent-note-classification.zh.md: b424b07e051507a894c8f5961feb5fe24a9da3c6
+2026-06-20-agent-note-classification.md: edb65a772c81b818bf3811c9f3f64ed1a6497647
+2026-06-20-agent-note-classification.zh.md: eff333b52309fbfc7706fbf15a26b07c761f5b0e
diff --git a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md
index 094233ed10..edb65a772c 100644
--- a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md
+++ b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md
@@ -38,7 +38,7 @@ Both are `doc-sync` members, in the `verify-md-wrap` style (tsx ESM, verify-don'
- **A `Classification:` prose line** in each file (next to `Status:`), parsed by the gate. Workable, but it duplicates into the file a fact the path can already carry, and a line can disagree with its folder. Path-encoding makes the label and its storage the same thing — there is nothing to keep in sync.
- **A `refactor` class.** It overlaps `simplification` almost entirely; the only discriminator anyone reached for was "does observable behavior change?", which `simplification` already encodes (it does not). One class, not two.
-- **A generated or hand-maintained corpus index.** Rejected because the lifecycle/class tree is authoritative, while a centralized inventory creates a merge hotspot without providing discovery that tree navigation or repository search cannot provide. The separate [index proposal](../../rejected/process/2026-07-04-generate-agent-note-index-tables.md) records the discarded generated shape.
+- **A generated or hand-maintained corpus index.** Rejected because the lifecycle/class tree is authoritative, while a centralized inventory creates a merge hotspot without providing discovery that tree navigation or repository search cannot provide.
## Consequences
diff --git a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.zh.md b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.zh.md
index b424b07e05..eff333b523 100644
--- a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.zh.md
+++ b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.zh.md
@@ -38,7 +38,7 @@ Status: implemented
- **在每个文件中添加 `Classification:` 行文行**(紧邻 `Status:`),由门禁解析。可行,但它将路径已能承载的事实重复到文件中,且行内容可能与所在文件夹不一致。路径编码使标签与其存储合二为一,没有需要保持同步的东西。
- **设立 `refactor` 类别。** 与 `simplification` 几乎完全重叠;唯一有人试图用来区分的标准是「可观察行为是否改变?」,而 `simplification` 已经编码了这一点(它不改变)。一个类别即可,无需两个。
-- **生成或手工维护的语料索引。** 不予采纳:生命周期/类别目录树才是权威结构;集中式清单会制造合并热点,却没有提供目录树导航或仓库搜索无法实现的发现能力。单独的[索引提案](../../rejected/process/2026-07-04-generate-agent-note-index-tables.md)记录了被放弃的生成形状。
+- **生成或手工维护的语料索引。** 不予采纳:生命周期/类别目录树才是权威结构;集中式清单会制造合并热点,却没有提供目录树导航或仓库搜索无法实现的发现能力。
## 后果
diff --git a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.i18n.yaml
index 16fb9e2d1e..5faf01450d 100644
--- a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.i18n.yaml
@@ -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-generated-cordis-catalog.md: b5957cf06a9316447aae70183de462024bb24be3
-2026-06-20-generated-cordis-catalog.zh.md: 35ed06c4a8e13245a37adaa4d5da7a842e97c0c7
+2026-06-20-generated-cordis-catalog.md: 5005e50a2e23c8286a8057dc57f365554bde5056
+2026-06-20-generated-cordis-catalog.zh.md: 0f8f20673b01ef1218a7d2dfa47c189862803775
diff --git a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md
index b5957cf06a..5005e50a2e 100644
--- a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md
+++ b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md
@@ -25,7 +25,7 @@ Specific choices:
- **Cross-links to the data-structure catalog.** Every repository-owned type name in a signature (`GenerateOptions`, `StreamChunk`, `ToolDefinition`, …) links to its primary core-data-structures page through a curated map. The AST walk is fail-closed: each parameter, generic constraint/default, and return-type reference must be mapped, be the signature's own type parameter, be a named TypeScript/Cordis foundation type, or carry a named exception with its non-catalog documentation owner. Violations aggregate with source pointers and name the appropriate owning lists. The map does NOT reuse `type-equiv.manifest.json`, which documents `…Map` symbols while signatures reference derived union names and lists some symbols on multiple pages.
- **A dedicated fence.** Signature blocks use a ` ```ts cordis-catalog ` info string and place the original event or public-method JSDoc immediately before its declaration. `doc-typecheck` recognizes and skips the bare fragments, excluding them from the opt-out ratio — the same treatment `type-equiv` blocks get.
-This **supersedes the event-taxonomy half** of [doc-sync enforcement](2026-06-11-doc-sync-enforcement.md): `verify-event-taxonomy` and its `docs/architecture.md` table are retired (the architecture.md heading stays, its body now points at the catalog; the Service-map role table stays as curated prose). doc-typecheck, verify-md-wrap, verify-md-links, and verify-type-equiv are unchanged.
+This **supersedes the event-taxonomy half** of [doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md): `verify-event-taxonomy` and its `docs/architecture.md` table are retired (the architecture.md heading stays, its body now points at the catalog; the Service-map role table stays as curated prose). doc-typecheck, verify-md-wrap, verify-md-links, and verify-type-equiv are unchanged.
## Alternatives considered
diff --git a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.zh.md
index 35ed06c4a8..0f8f20673b 100644
--- a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.zh.md
+++ b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.zh.md
@@ -25,7 +25,7 @@ Status: implemented
- **指向数据结构目录的交叉链接。** 签名中由仓库拥有的每个类型名(`GenerateOptions`、`StreamChunk`、`ToolDefinition`……)都会通过人工维护的映射链接到其主要核心数据结构页面。AST 遍历采用失败关闭策略:每个参数、泛型约束/默认值和返回类型引用都必须已映射、是签名自身的类型参数、是点名的 TypeScript/Cordis 基础类型,或带有点名的例外及其非目录文档归属。违规会连同源码位置汇总报告,并点明相应的归属列表。该映射不会复用 `type-equiv.manifest.json`,因为后者记录 `…Map` 符号,而签名引用派生的联合类型名,并且会在多个页面列出某些符号。
- **专用围栏。** 签名块使用 ` ```ts cordis-catalog ` 信息字符串,并把原始事件或公共方法 JSDoc 直接放在其声明之前。`doc-typecheck` 会识别并跳过这些裸片段,将其排除在 opt-out 比例之外——与 `type-equiv` 块的处理相同。
-本决策**取代** [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)中事件分类的那一半:`verify-event-taxonomy` 及其 `docs/architecture.md` 表格退役(architecture.md 的标题保留,正文改为指向目录;服务映射的角色表格作为人工行文保留)。doc-typecheck、verify-md-wrap、verify-md-links 和 verify-type-equiv 不受影响。
+本决策**取代** [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)中事件分类的那一半:`verify-event-taxonomy` 及其 `docs/architecture.md` 表格退役(architecture.md 的标题保留,正文改为指向目录;服务映射的角色表格作为人工行文保留)。doc-typecheck、verify-md-wrap、verify-md-links 和 verify-type-equiv 不受影响。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml
index 1458f7ff50..3019c21fbb 100644
--- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml
@@ -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-bilingual-docs-and-pairing-gate.md: 3732e6812a3f1f40242aa5a83a0bf1d1bc4d6139
-2026-07-02-bilingual-docs-and-pairing-gate.zh.md: a870e063230a34b807eed2f4ffc1c6067cb3aedc
+2026-07-02-bilingual-docs-and-pairing-gate.md: bebff600ca27763c04bdecea78ceb916542f7eca
+2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 66c9b75b1bab47558bb63b7e97cf6d7c7610b0d5
diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md
index 3732e6812a..bebff600ca 100644
--- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md
+++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md
@@ -6,7 +6,7 @@ English | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md)
## Problem
-This repo's documentation corpus is read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.
+This repo's documentation corpus is read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.
## Decision
diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md
index a870e06323..66c9b75b1b 100644
--- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md
+++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md
@@ -6,7 +6,7 @@ Status: implemented
## 问题
-本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。
+本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。
## 决策
diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml
index 0360eacf60..cfa2cafeb6 100644
--- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml
@@ -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-06-parallel-pre-push-gates.md: f2e8f0054e595be20a320ec7095f0fe674eb93c6
-2026-07-06-parallel-pre-push-gates.zh.md: 03b8773e475a9d1c82cea830cae6806a1c016f01
+2026-07-06-parallel-pre-push-gates.md: 0c3311b259a2fcf00deb4eed491c301a0c330186
+2026-07-06-parallel-pre-push-gates.zh.md: 6949237d2e025034162f66950033d3ad6ecf11ea
diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md
index f2e8f0054e..0c3311b259 100644
--- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md
+++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md
@@ -16,7 +16,7 @@ Aggregate jobs such as documentation synchronization hide long sequential chains
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) discovers packages from `packages//` and runs `publint` with a worker pool sized from `availableParallelism()`. `DSH_PUBLINT_CONCURRENCY` can cap or raise the worker count for local machines and CI runners with different resource profiles. Results are buffered per package and printed in deterministic package order, so parallel execution does not scramble each package's log block.
-The per-gate package scripts remain the vocabulary for ad hoc local runs. `hygiene` stays an aggregate `&&` chain, while `doc-sync` owns its member list in the scheduler ([doc-sync through the gate scheduler](2026-07-21-doc-sync-through-gate-scheduler.md)).
+The per-gate package scripts remain the vocabulary for ad hoc local runs. `hygiene` stays an aggregate `&&` chain, while `doc-sync` owns its member list in the scheduler ([doc-sync through the gate scheduler](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md)).
## Alternatives considered
diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md
index 03b8773e47..6949237d2e 100644
--- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md
+++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md
@@ -16,7 +16,7 @@ Status: implemented
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages//` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
-各门禁的包脚本仍是临时本地运行所用的词汇。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 在调度器中拥有其成员列表([通过门禁调度器运行 doc-sync](2026-07-21-doc-sync-through-gate-scheduler.md))。
+各门禁的包脚本仍是临时本地运行所用的词汇。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 在调度器中拥有其成员列表([通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md))。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml
index 2b1dc53f6b..13b35d6f30 100644
--- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml
@@ -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-19-remove-generated-agent-note-index.md: 27c1591b29a1ca64370de6ffadfb9c524a804ced
-2026-07-19-remove-generated-agent-note-index.zh.md: 868955bc10900f784bd88066042abe24454e27b5
+2026-07-19-remove-generated-agent-note-index.md: ee85ec0757d5924f5784c43a50003eb96e0a9531
+2026-07-19-remove-generated-agent-note-index.zh.md: 23e6d3b0b9aaaa02f53e72789f409c0050112193
diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md
index 27c1591b29..ee85ec0757 100644
--- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md
+++ b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md
@@ -16,8 +16,6 @@ The lifecycle/class filesystem tree is the Agent Note inventory. [README.md](../
`scripts/agent-note-tree.ts` owns the closed lifecycle/class sets and structural walker. `verify-agent-note-classification` validates that tree and rejects the legacy homes and a root `INDEX.md`; it does not render or freshness-check a centralized list.
-This decision supersedes the rejected [generated-index proposal](../../rejected/process/2026-07-04-generate-agent-note-index-tables.md).
-
## Alternatives considered
**Keep the committed generated index and resolve conflicts by regenerating it.** Regeneration makes conflict resolution mechanical but does not prevent unrelated branches from modifying the same artifact or reduce the review noise it creates.
diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md
index 868955bc10..23e6d3b0b9 100644
--- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md
+++ b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md
@@ -16,8 +16,6 @@ Status: implemented
`scripts/agent-note-tree.ts` 持有封闭的生命周期/类别集合与结构遍历器。`verify-agent-note-classification` 校验该目录树,并拒绝旧目录和根目录中的 `INDEX.md`,但不会渲染集中式清单或检查其新鲜度。
-本决策取代已拒绝的[生成索引提案](../../rejected/process/2026-07-04-generate-agent-note-index-tables.md)。
-
## 备选方案
**保留提交到仓库的生成索引,并通过重新生成解决冲突。** 重新生成能让冲突解决过程机械化,但无法阻止无关分支修改同一产物,也不会减少由此产生的评审噪音。
diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml
index f8b54b0ec5..05147cd54a 100644
--- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml
@@ -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-23-portable-required-pull-request-ci.md: 9cf8d97016300c5258c075879176aa6abd64e59e
-2026-07-23-portable-required-pull-request-ci.zh.md: c6839a133d0c3fe7a699362f6168e17d827a5b61
+2026-07-23-portable-required-pull-request-ci.md: d1002c7d9db7cd8bbed3bdfda8a773a4b124bf16
+2026-07-23-portable-required-pull-request-ci.zh.md: fedfc6b9c982ace5ece430c52db23c22ec5119d4
diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md
index 9cf8d97016..d1002c7d9d 100644
--- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md
+++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md
@@ -12,15 +12,15 @@ Billing health, a runner definition's `Ready` state, and a large autoscaling cei
## Decision
-[CI](../../../../.github/workflows/ci.yml) runs the required primary Node 24 and Windows jobs on repo-restricted enterprise 32-core pools. Standard `ubuntu-latest` jobs retain Node 22.19, Node 26, and Python SDK compatibility, and `master` runs complete serial Linux, macOS, and Windows references. Those standard-hosted jobs keep the portable execution boundary observable without duplicating the primary inventory on every pull request.
+[CI](../../../../.github/workflows/ci.yml) runs the required primary Node 24 jobs, plus the stable `all checks passed` aggregate, on repo-restricted enterprise 32-core pools. The aggregate performs no checkout or repository gate, but sharing the enterprise pool prevents the required verdict from introducing a separate standard-hosted billing dependency after its substantive jobs have already succeeded. The required Windows job runs on standard `windows-2025` with single-worker bounds, keeping the complete Windows contract independent of enterprise Windows allocation. Standard `ubuntu-latest` jobs retain Node 22.19, Node 26, and Python SDK compatibility, and `master` runs complete serial Linux, macOS, and Windows references. Those standard-hosted jobs keep the portable execution boundary observable without duplicating the primary inventory on every pull request.
-The two Linux primary jobs, Node compatibility, Python SDK, and `windows node 24 / complete` remain dependencies of `all checks passed`; branch protection continues to require `e2e` and `all checks passed`. There is no automatic fallback when an enterprise label cannot allocate: the standard jobs continue to report their own contracts, but they cannot manufacture the missing required result.
+The two Linux primary jobs, Node compatibility, Python SDK, and `windows node 24 / complete` remain dependencies of `all checks passed`; branch protection continues to require `e2e` and `all checks passed`. There is no automatic fallback when a remaining enterprise Linux label cannot allocate: the standard jobs continue to report their own contracts, but they cannot manufacture the missing required result.
The [larger-runner decision](2026-07-22-evidence-based-larger-hosted-runners.md) owns the current primary topology and its measurements. The [serial cross-platform reference](2026-07-21-serial-cross-platform-ci-reference.md) remains the independent standard-hosted completeness check, and the manual larger-runner suites retain size comparisons without expanding the ordinary required matrix.
## Alternatives considered
-**Keep every required job on standard capacity.** This removes the enterprise allocation dependency, but complete standard-runner jobs give materially slower feedback and still experience shared-capacity queues. The current split retains portable compatibility and serial evidence while spending enterprise capacity on the primary critical path.
+**Keep the Linux primary jobs and aggregate on standard capacity.** This removes the remaining enterprise allocation dependency, but complete standard-runner jobs give materially slower feedback and still experience shared-capacity queues. The current split retains portable compatibility and serial evidence while spending enterprise capacity on the Linux primary critical path.
**Select enterprise size from advertised core count.** Benchmarks show non-monotonic scaling and setup variance, so exact complete-job measurements choose the required pools instead.
@@ -30,6 +30,6 @@ The [larger-runner decision](2026-07-22-evidence-based-larger-hosted-runners.md)
## Consequences
-Ordinary pull requests receive lower active runtime at the cost of depending on enterprise configuration and paid rounded minutes. A live exact-head run proves the same commands that branch protection consumes; queue delay is reported separately from each job's `startedAt` to `completedAt` execution interval.
+Ordinary pull requests spend enterprise capacity on the Linux critical path while standard Windows trades longer runtime for independent allocation. A live exact-head run proves the same commands that branch protection consumes; queue delay is reported separately from each job's `startedAt` to `completedAt` execution interval.
-Standard compatibility and serial jobs remain useful when enterprise allocation is degraded, but they do not make a blocked required aggregate green. Recovering availability may require temporarily restoring the complete standard-hosted topology; changing a pool definition's status alone is insufficient evidence that it can receive work.
+Standard compatibility and required Windows jobs remain useful when enterprise allocation is degraded, but they do not make a blocked required Linux job or aggregate green. Recovering Linux availability may require restoring the complete standard-hosted topology; changing a pool definition's status alone is insufficient evidence that it can receive work.
diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md
index c6839a133d..fedfc6b9c9 100644
--- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md
+++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md
@@ -12,15 +12,15 @@ Status: implemented
## 决策
-[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业和 Windows 作业。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26 和 Python SDK 兼容性,`master` 则运行完整的 Linux、macOS 和 Windows 串行参考流程。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。
+[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业,以及稳定的 `all checks passed` 聚合流程。该聚合流程不执行代码检出或仓库门禁;但让它与所依赖的实质性作业共用企业级运行器池,可以避免这些作业已经成功后,必需判定结果又引入一项单独的标准托管计费依赖。必需的 Windows 作业在标准 `windows-2025` 上运行,并采用单工作线程上限,使完整的 Windows 契约不依赖企业级 Windows 运行器分配。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26 和 Python SDK 兼容性,`master` 则运行完整的 Linux、macOS 和 Windows 串行参考流程。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。
-两项 Linux 主作业、Node 兼容性、Python SDK 和 `windows node 24 / complete` 继续作为 `all checks passed` 的依赖项;分支保护继续要求 `e2e` 和 `all checks passed`。企业级运行器标签无法分配运行器时没有自动后备机制:标准作业会继续报告各自的契约,但无法产出缺失的必需结果。
+两项 Linux 主作业、Node 兼容性、Python SDK 和 `windows node 24 / complete` 继续作为 `all checks passed` 的依赖项;分支保护继续要求 `e2e` 和 `all checks passed`。剩余的企业级 Linux 运行器标签无法分配运行器时没有自动后备机制:标准作业会继续报告各自的契约,但无法产出缺失的必需结果。
当前主拓扑及其测量结果由[大型运行器决策](2026-07-22-evidence-based-larger-hosted-runners.md)记录。[跨平台串行参考流程](2026-07-21-serial-cross-platform-ci-reference.md)继续作为独立的标准托管完整性检查,手动大型运行器套件则保留规格比较,同时不扩大普通必需矩阵。
## 曾考虑的替代方案
-**将所有必需作业保留在标准容量上。** 此方案消除了企业级运行器分配依赖,但标准运行器上的完整作业反馈明显更慢,仍会遇到共享容量排队。当前拆分既保留可移植兼容性和串行证据,又将企业级运行器容量用于主关键路径。
+**将 Linux 主作业和聚合流程保留在标准容量上。** 此方案消除了剩余的企业级运行器分配依赖,但标准运行器上的完整作业反馈明显更慢,仍会遇到共享容量排队。当前拆分既保留可移植兼容性和串行证据,又将企业级运行器容量用于 Linux 主关键路径。
**根据标称核心数选择企业规格。** 基准测试表明扩展效果不呈单调变化,设置耗时也存在波动,因此必需运行器池改由完整作业的精确测量结果选定。
@@ -30,6 +30,6 @@ Status: implemented
## 后果
-普通拉取请求获得更短的活动耗时,代价是依赖企业级运行器配置,并消耗按整分钟取整的付费分钟数。一次实际的分支头精确运行能够证明分支保护使用的同一组命令;排队延迟与每个作业从 `startedAt` 到 `completedAt` 的执行区间分开报告。
+普通拉取请求会将企业级运行器容量用于 Linux 关键路径,而标准托管 Windows 作业则以更长的运行时间换取不依赖企业池的运行器分配。一次实际的分支头精确运行能够证明分支保护使用的同一组命令;排队延迟与每个作业从 `startedAt` 到 `completedAt` 的执行区间分开报告。
-企业级运行器分配能力下降时,标准兼容性作业和串行作业仍能提供有用证据,但无法让受阻的必需聚合流程变绿。恢复可用性时,可能需要暂时恢复完整的标准托管拓扑;仅改变运行器池定义的状态,不足以证明它可以接收作业。
+企业级运行器分配能力下降时,标准兼容性作业和必需的 Windows 作业仍能提供有用证据,但无法让受阻的必需 Linux 作业或聚合流程变绿。恢复 Linux 可用性时,可能需要恢复完整的标准托管拓扑;仅改变运行器池定义的状态,不足以证明它可以接收作业。
diff --git a/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.i18n.yaml
new file mode 100644
index 0000000000..4533e6dfe5
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.i18n.yaml
@@ -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
+2026-07-26-dependencies-over-hand-rolling.md: 22720c483c1c9e8145497b3e83cbc9f17570b161
+2026-07-26-dependencies-over-hand-rolling.zh.md: ac988eb4b3af9ba18ee2150bab93f01f0e36003e
diff --git a/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md
new file mode 100644
index 0000000000..22720c483c
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md
@@ -0,0 +1,36 @@
+# Agent Note: Prefer maintained dependencies over hand-rolling
+
+Status: implemented
+
+English | [中文](2026-07-26-dependencies-over-hand-rolling.zh.md)
+
+## Problem
+
+The harness hand-rolls a lot of infrastructure that mature external packages already provide. Some of that is deliberate — vendored Cordis ([vendoring decision](2026-06-11-vendor-cordis-as-source.md)), the [twin LLM adapters](../architecture/2026-06-13-twin-llm-adapters.md), schemastery as the config-schema standard — but much of it accreted from an unstated "avoid new dependencies" reflex: the repo-wide external dependency list stayed tiny while packages grew their own SSE parsers, protocol framers, retry loops, and glob matchers. Nothing in `AGENTS.md` actually stated a dependency policy, so agents inferred one from the existing pattern, and the inferred rule ("don't add deps") is stricter than anyone decided. That is the "Not Invented Here" fallacy operating by default: every hand-rolled clone of a well-maintained library is code we test, document, review, and debug ourselves, with none of the ecosystem's accumulated edge-case fixes.
+
+## Decision
+
+Introducing an external dependency is a legitimate simplification, not a policy exception. When a well-maintained package (or a Node builtin at our engine floor) covers a hand-rolled surface, replacing the hand-rolled code is the preferred direction, subject to the same evidence standard as any other simplification: the swap must genuinely shrink what we own — code, tests, and contract surface — rather than merely relocate complexity behind a wrapper.
+
+The bar for a new dependency:
+
+- **Net deletion.** The dependency replaces real owned code (implementation + dedicated tests + docs), not hypothetical future code. A dep that only adds capability is a feature decision, not a simplification.
+- **Health.** Actively maintained, widely used, sensible transitive footprint. A tiny unmaintained package trades our code for someone's abandoned code.
+- **Fit at the boundary.** The package's semantics cover our actual contract; residual semantics we still hand-roll around it count against the swap.
+- **Not a settled seam.** schemastery (config schemas), vendored Cordis, the `@earendil-works` twins, and other decisions recorded in implemented Agent Notes are not reopened by this policy; a swap that collapses a recorded design needs to beat the recorded rationale, not just cite this note.
+
+`packages/util/`'s "zero-dependency" charter describes that group's *export* discipline — util packages stay free of harness dependencies so any group can depend on them — and does not ban external packages where they simplify; a util package whose entire job a maintained external package does better should be replaced by the dependency, not preserved for the charter.
+
+Dependency-swap proposals are recorded as `proposed/simplification` Agent Notes like any other removal, with the candidate package, the deletable surface, residual semantics, and supply-chain considerations stated. The [supply-chain proposal](../../proposed/process/2026-06-11-supply-chain-and-vendor-drift.md) owns advisory scanning and update cadence for the dependency list this policy grows.
+
+## Alternatives considered
+
+- **Keep the implicit no-new-deps culture.** Rejected: it was never a recorded decision, and its cost is concrete — hand-rolled protocol and parsing code duplicates battle-tested libraries, inflates the per-file coverage burden, and slows every reviewer who must re-derive edge cases the ecosystem already fixed.
+- **A hard allowlist of approved packages.** Rejected: the repo is pre-release and the dependency set is small; a per-PR evidence bar (net deletion, health, fit) plus review keeps judgment where the context is, without a standing committee artifact that would itself need maintenance.
+- **Vendor every new dependency like Cordis.** Rejected: vendoring is for packages we must patch or pin against upstream churn ([vendoring decision](2026-06-11-vendor-cordis-as-source.md)); applying it broadly recreates the maintenance burden the dependency was meant to shed. Ordinary npm dependencies with lockfile pinning are the default.
+
+## Consequences
+
+- Agents and contributors surveying for simplifications now treat "replace hand-rolled X with package Y" as in-scope output; [dsh-find-simplifications](../../../skills/dsh-find-simplifications/SKILL.md) carries the corresponding guidance.
+- The dependency list will grow, and with it the supply-chain surface; the mitigations live in the [supply-chain proposal](../../proposed/process/2026-06-11-supply-chain-and-vendor-drift.md), which this policy makes more urgent.
+- Root `AGENTS.md` carries the one-line rule; this note owns the rationale and the bar.
diff --git a/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.zh.md b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.zh.md
new file mode 100644
index 0000000000..ac988eb4b3
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.zh.md
@@ -0,0 +1,36 @@
+# Agent Note: 优先选用持续维护的依赖,而非手写实现
+
+Status: implemented
+
+[English](2026-07-26-dependencies-over-hand-rolling.md) | 中文
+
+## 问题
+
+harness 手写了大量基础设施,而成熟的外部包(package)早已提供同等能力。其中一部分是有意为之——以源码形式收录的 Cordis([引入 vendor 的决策](2026-06-11-vendor-cordis-as-source.md))、[孪生 LLM(大语言模型)适配器](../architecture/2026-06-13-twin-llm-adapters.md)、作为配置 schema 标准的 schemastery——但相当大一部分源自一条未经言明的「避免新依赖」反射,逐渐累积而成:仓库级的外部依赖清单始终很小,各包却各自长出了自己的 SSE(Server-Sent Events)解析器、协议分帧器、重试循环和 glob 匹配器。`AGENTS.md` 其实从未写下任何依赖政策,agent(智能体)只能从既有模式中自行推断出一条,而这条推断出的规则(「不要加依赖」)比任何人实际决定过的都更严格。这正是 Not Invented Here(非我发明)谬误在默认状态下运作:每一个对维护良好的库的手写克隆,都是要由我们自己测试、撰写文档、评审和调试的代码,却享受不到生态累积下来的边界情况修复。
+
+## 决策
+
+引入外部依赖是一种正当的简化,而不是政策特例。当一个维护良好的包(或我们引擎下限即已提供的 Node 内置能力)覆盖了某块手写接口面时,替换手写代码就是优先方向,并遵循与其他任何简化相同的证据标准:这次替换必须切实缩减我们持有的东西(代码、测试和契约面),而不是仅仅把复杂度挪到一个包装层后面。
+
+新依赖的准入门槛:
+
+- **净删除。** 该依赖替换的是真实持有的代码(实现 + 专属测试 + 文档),而不是假想中的未来代码。只增加能力的依赖属于功能决策,不属于简化。
+- **健康度。** 持续维护、广泛使用、传递依赖足迹合理。一个无人维护的小包,只是拿我们的代码换来别人废弃的代码。
+- **边界契合。** 该包的语义要覆盖我们的实际契约;仍需围绕它手写补齐的残留语义,要计入这次替换的减分项。
+- **不触碰已定案的 seam。** schemastery(配置 schema)、源码收录的 Cordis、`@earendil-works` 孪生适配器,以及其他记录在已实现 Agent Note(agent 决策记录)中的决策,不因本政策而重开;一次会瓦解已记录设计的替换,必须胜过所记录的论证理由,而不能只援引本 Agent Note。
+
+`packages/util/` 的「零依赖」章程描述的是该分组的*导出*纪律(util 包不携带 harness 依赖,从而任何分组都能依赖它们),并不禁止在能带来简化时使用外部包;如果一个 util 包的全部职责有维护良好的外部包做得更好,就应当用该依赖替换它,而不是为了章程而保留它。
+
+依赖替换提案与其他任何移除类提案一样,记录为 `proposed/simplification` Agent Note,写明候选包、可删除的接口面、残留语义和供应链考量。本政策会使依赖清单增长,这份清单的安全公告扫描与更新节奏由[供应链提案](../../proposed/process/2026-06-11-supply-chain-and-vendor-drift.md)负责。
+
+## 曾考虑的替代方案
+
+- **维持隐性的「不加新依赖」文化。** 不予采纳:它从来不是一项有记录的决策,而其成本是具体的——手写的协议与解析代码重复实现了久经实战检验的库,推高了按文件计的覆盖率负担,还拖慢每一位评审人:他们必须重新推导生态早已修复的边界情况。
+- **一份获批包的硬性白名单。** 不予采纳:仓库处于预发布阶段,依赖集合很小;按 PR(Pull Request)设置证据门槛(净删除、健康度、契合度)再加评审,就能把判断留在上下文所在之处,无需一份本身也需要维护的常设委员会式产物。
+- **像 Cordis 一样把每个新依赖都以源码形式收录。** 不予采纳:源码收录(vendor)只适用于我们必须打补丁、或必须锁定以抵御上游变动的包([引入 vendor 的决策](2026-06-11-vendor-cordis-as-source.md));将其推广到所有依赖,会重新制造出引入依赖本要卸下的维护负担。默认做法是普通 NPM 依赖加 lockfile 锁定。
+
+## 后果
+
+- 巡查简化机会的 agent 与贡献者,现在把「用包 Y 替换手写的 X」视为范围内的产出;[dsh-find-simplifications](../../../skills/dsh-find-simplifications/SKILL.md) 承载相应指引。
+- 依赖清单会增长,供应链接触面随之扩大;缓解措施记录在[供应链提案](../../proposed/process/2026-06-11-supply-chain-and-vendor-drift.md)中,本政策使该提案更加紧迫。
+- 根 `AGENTS.md` 承载一行规则;论证理由与准入门槛由本 Agent Note 持有。
diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml
new file mode 100644
index 0000000000..998417a651
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml
@@ -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
+2026-07-26-frozen-agent-note-archive.md: e829d30853c7b80dee76da0fdc22db7e9b04e828
+2026-07-26-frozen-agent-note-archive.zh.md: f90a81561eb4c11c8d48400d2adfbcfff7e6a9ed
diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md
new file mode 100644
index 0000000000..e829d30853
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md
@@ -0,0 +1,37 @@
+# Agent Note: Freeze low-future-value Agent Notes outside the active corpus
+
+Status: implemented
+
+English | [中文](2026-07-26-frozen-agent-note-archive.zh.md)
+
+## Problem
+
+Implemented Agent Notes are maintained as current decision records, so every path, symbol, default, translation, code fence, package reference, and outbound link in the active corpus remains an obligation. That cost is justified when the rationale can guide future work, but not for closed UI details, minor fixes, superseded implementation mechanics, or process history whose current authority lives elsewhere. Deleting every low-value implemented record would erase useful historical evidence, while retaining every rejected proposal preserves ideas that are neither plausible nor instructive. The corpus needs a retention boundary that distinguishes active guidance from frozen history without turning archival into another maintenance tier.
+
+## Decision
+
+Only implemented Agent Notes can be archived. An implemented note moves when its shipped decision is complete and its rationale, alternatives, consequences, negative guarantees, and reintroduction conditions are unlikely to guide future work. Foundational boundaries, durable and wire semantics, security rules, recurring design temptations, and unresolved reintroduction conditions remain active regardless of age or word count. Proposed notes never enter the archive; an obsolete proposal becomes rejected. A rejected note remains only while it prevents a tempting, meaningful mistake and is otherwise deleted as a complete triplet.
+
+The archive uses `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`; the redundant `implemented` segment is absent. The archival change moves the complete English, Chinese, and consistency-sidecar triplet, leaves `Status: implemented` intact, and inserts `Archived: YYYY-MM-DD` immediately below it in both language files. Relocation, that metadata line, the corresponding sidecar re-record, and mechanical inbound-link repair are the only permitted archival edits.
+
+After archival, the triplet is permanently frozen and is historical context rather than current authority. It is not updated for renamed packages, changed behavior, translation standards, formatting rules, broken outbound links, or later documentation contracts. Active prose may intentionally link into an archived note, redirect that link to current authority, or delete it. Repository gates therefore validate links into archived files but never treat archived files as link sources.
+
+[`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) owns the frozen boundary. It accepts only the closed set of Agent Note kinds, requires a complete triplet with implemented status and matching valid archive dates, verifies the sidecar against both current Git blob hashes, and seals every artifact by path and SHA-256 content hash in an append-only manifest. Its `--write` mode first proves every existing seal unchanged and then appends only newly archived artifacts. Pull-request CI supplies the trusted base SHA and checks out complete history before running the verifier, so a reused runner's shallow checkout cannot omit the baseline manifest. The ordinary Agent Note format, translation-pairing, wrapping, Markdown-link, package-path, Mermaid, documentation-TypeScript, and type-equivalence gates exclude archive sources; their evolving standards cannot create pressure to edit history.
+
+The [`dsh-archive-agent-notes`](../../../skills/dsh-archive-agent-notes/SKILL.md) workflow owns classification. It requires a semantic note-by-note audit, uses code and current documentation to identify present authority, treats word count only as triage, carries calibrated keep/archive/delete examples, and reports genuinely borderline outcomes for review.
+
+## Alternatives considered
+
+**Delete every note that leaves the active corpus.** Rejected because an implemented record can have low forward guidance while still providing useful historical evidence about a closed decision. A content-sealed archive preserves that evidence without pretending it remains current.
+
+**Keep every implemented and rejected note active.** Rejected because maintenance effort and search noise grow with records that no longer help a future decision. Rejected notes in particular earn retention only by preventing a plausible fallacy.
+
+**Archive rejected or proposed notes too.** Rejected because archive status means “implemented historical decision.” An obsolete proposal needs an explicit rejection, while a rejection with no guardrail value needs deletion rather than a second low-value holding area.
+
+**Continue applying all documentation gates to archived notes.** Rejected because a later formatting, translation, code, package, or link rule would require rewriting the historical snapshot. The dedicated verifier owns completeness and immutability instead.
+
+**Permit factual refreshes while freezing only rationale.** Rejected because that recreates the judgment and translation burden of the active corpus and makes it unclear which clauses are historical. Current facts belong in active documentation or a new active Agent Note.
+
+## Consequences
+
+The active corpus becomes a set of decisions expected to influence future work, while low-value implemented history remains searchable and linkable without consuming maintenance attention. Rejected clutter can disappear when it no longer protects a meaningful choice, and proposed work cannot quietly evade a verdict through archival. The archive adds a manifest, a dedicated verifier, and an explicit one-time metadata step. Archived facts and outbound links can become stale by design, so readers and agents must treat active code and documentation as authority and cite an archived note only as history.
diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md
new file mode 100644
index 0000000000..f90a81561e
--- /dev/null
+++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md
@@ -0,0 +1,37 @@
+# Agent Note: 将未来指导价值较低的 Agent Note 冻结在活跃记录集合之外
+
+Status: implemented
+
+[English](2026-07-26-frozen-agent-note-archive.md) | 中文
+
+## 问题
+
+implemented Agent Note(agent 决策记录)作为当前决策记录持续维护,因此活跃记录集合中的每个路径、符号、默认值、译文、代码围栏、包(package)引用和出站链接都会形成维护义务。当决策依据可以指导未来工作时,这项成本合理;但对于已经收尾的 UI 细节、小型修复、已被取代的实现机制,或当前权威依据已转移到别处的流程历史,这项成本并不值得。删除所有低价值的已实施记录会抹去有用的历史证据,而保留每一项被否决的提案,又会留下既无采纳可能也无启发意义的想法。这套记录集合需要一道留存边界,在区分活跃指导与冻结历史的同时,避免让归档成为另一个维护层级。
+
+## 决策
+
+只有 implemented Agent Note 可以归档。当一份已实施记录的交付决策已经完整落地,且其决策依据、备选方案、后果、否定性保证和重新引入条件不太可能再指导未来工作时,将其移入归档。基础性边界、持久化语义与协议语义、安全规则、反复出现且看似诱人的设计选择和尚未解决的重新引入条件,无论记录的存续时间或字数如何,都继续作为活跃记录保留。proposed Agent Note 绝不进入归档;过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种诱人且影响重大的错误时保留,否则将其三个配对文件完整删除。
+
+归档路径为 `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`,其中省略了冗余的 `implemented` 层级。归档变更会移动完整的英文、中文和一致性伴随记录三个文件,保留 `Status: implemented`,并在两种语言的文件中紧接该状态行插入 `Archived: YYYY-MM-DD`。归档时只允许做文件迁移、添加该元数据行、相应地重新记录伴随文件,以及机械修复入站链接。
+
+归档后,这三个文件永久冻结,只作为历史背景,不再是当前权威依据。不得因为包重命名、行为变化、翻译标准、格式规则、出站链接失效或后续文档契约而更新归档文件。活跃文档可以有意链接到归档 Agent Note,也可以把该链接重定向到当前权威依据,或直接删除。仓库门禁因此会校验指向归档文件的链接,但绝不把归档文件作为链接源来校验。
+
+[`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) 负责维护冻结边界。它只接受封闭集合中的 Agent Note 类别,要求三个配对文件完整、状态为 implemented,且归档日期有效并互相匹配;它还会用双方当前的 Git blob hash 校验伴随记录,并在仅追加的 manifest 中按路径和 SHA-256 内容 hash 封存每项产物。其 `--write` 模式会先证明每条现有封存记录对应的内容都未改变,再仅追加新归档的产物。拉取请求 CI 会提供可信的基准 SHA,并在运行校验器前检出完整历史,因此复用运行器上的浅克隆检出无法漏掉基线 manifest。普通的 Agent Note 格式、翻译配对、换行、Markdown 链接、包路径、Mermaid、文档 TypeScript 和类型等价门禁都排除归档源文件,因此这些门禁持续演进的标准不会产生修改历史记录的压力。
+
+[`dsh-archive-agent-notes`](../../../skills/dsh-archive-agent-notes/SKILL.md) 工作流负责分类判断。它要求逐份 Agent Note 做语义审计,使用代码和当前文档识别现行权威依据,仅把字数作为初步筛选手段,收录经过校准的保留、归档和删除示例,并报告真正处于边界的结果,以供评审。
+
+## 曾考虑的替代方案
+
+**删除每一份移出活跃记录集合的记录。** 不予采纳,因为已实施记录可能对未来的指导价值较低,却仍能为已经收尾的决策提供有用的历史证据。按内容 hash 封存的归档既能保留这些证据,又不会假装它们仍然反映当前状态。
+
+**继续将每一份 implemented 和 rejected Agent Note 作为活跃记录保留。** 不予采纳,因为不再帮助未来决策的记录会不断增加维护成本和搜索噪声。尤其是 rejected Agent Note,只有能避免一种可能发生的谬误时,才值得保留。
+
+**同时归档 rejected 或 proposed Agent Note。** 不予采纳,因为归档状态表达的是「已经实施的历史决策」。过时的提案需要明确转为 rejected;无法提供防错价值的 rejected Agent Note 则应删除,而不是再放入第二个低价值存放区。
+
+**继续对归档 Agent Note 应用所有文档门禁。** 不予采纳,因为后续新增的格式、翻译、代码、包或链接规则会迫使维护者重写历史快照。改由专用校验器负责完整性与不可变性。
+
+**允许更新事实,只冻结决策依据。** 不予采纳,因为这会重新引入活跃记录集合的判断和翻译负担,也会让读者无法分辨哪些条款属于历史。当前事实应写在活跃文档或新的活跃 Agent Note 中。
+
+## 后果
+
+活跃记录集合由预计仍会影响未来工作的决策组成;未来指导价值较低的实施历史仍可搜索和链接,却不再消耗维护精力。当被否决的记录不再保护有意义的选择时,可以清除这类杂项;提案也无法通过归档悄悄逃避明确结论。归档机制增加一份 manifest、一个专用校验器和一个显式的一次性元数据步骤。归档中的事实和出站链接可以按设计逐渐陈旧,因此读者和 agent 必须以活跃代码与文档为权威依据,并且仅将归档 Agent Note 作为历史引用。
diff --git a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml
index cd359dbe3e..cbc79b0a1c 100644
--- a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml
@@ -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-26-gui-pr-gif-evidence-and-assets-branch.md: c75b88cd9b4580217857c1fd730b8b335200680d
-2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md: 6f2fdd1d8211661e780197b23a28688dab68ef50
+2026-07-26-gui-pr-gif-evidence-and-assets-branch.md: 221df5dc82dfb437261f6a836dcc6c2972f30939
+2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md: 861cedb5d15c8e057c2a5f9001453fdc3a3c6757
diff --git a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md
index c75b88cd9b..221df5dc82 100644
--- a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md
+++ b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md
@@ -6,7 +6,7 @@ English | [中文](2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md)
## Problem
-A pull request that changes what a product user sees in the GUI is otherwise reviewed through prose and test names, neither of which shows the rendered result. The [browser-demo GIF recording](2026-07-23-browser-demo-gif-recording.md) skill produces truthful local GIFs but deliberately stopped at the local artifact, so each pull request that wanted to show one re-derived publication on its own — and committing the GIF to the pull request branch is never acceptable, because binary media in history bloats every future clone permanently.
+A pull request that changes what a product user sees in the GUI is otherwise reviewed through prose and test names, neither of which shows the rendered result. The [browser-demo GIF recording](../../../skills/record-browser-gif/SKILL.md) skill produces truthful local GIFs but deliberately stopped at the local artifact, so each pull request that wanted to show one re-derived publication on its own — and committing the GIF to the pull request branch is never acceptable, because binary media in history bloats every future clone permanently.
The recording procedure itself also kept being re-learned failure by failure: screenshots written outside the browser tool's allowed roots or into missing directories fail at capture time, transient UI states polled across separate tool calls are lost because the turn settles between calls, substring completion predicates match the echo of the user's own prompt, and an inline environment-variable assignment on the encoder command expands too late to take effect.
@@ -16,7 +16,7 @@ Every pull request that changes product-user-visible GUI behavior includes a dem
The GIF is published to a dedicated orphan assets branch — no parent commit, media only — never to the pull request branch; one assets branch serves a whole pull request series (existing branches: `code-mode-ui-assets`, `pr-613-assets`). Publication works in a shallow single-branch scratch clone, commits as `assets: gif (#)`, and the pull request body embeds the blob URL with the required `?raw=true` suffix. Assets branches are append-only: merged pull request bodies reference their URLs forever, so an assets branch is never rewritten or deleted.
-Recording itself stays side-effect-free; publication is a bounded final step the skill performs only when the task includes attaching the GIF to a pull request. This amends the recording/upload boundary recorded in the [browser-demo GIF recording note](2026-07-23-browser-demo-gif-recording.md), which stays current for the recording half.
+Recording itself stays side-effect-free; publication is a bounded final step the skill performs only when the task includes attaching the GIF to a pull request. The [record-browser-gif skill](../../../skills/record-browser-gif/SKILL.md) remains the current contract for the recording half.
The skill folds in the operational lessons recording earned: frames go under `.playwright-mcp/`, ignored by the repository `.gitignore` and created before capture, because the browser tool writes only under its allowed roots and resolves relative names against the repository root; each pull request stages its own built tree with a fresh scratch workspace and a new session per scenario, and servers are stopped by PID rather than a broad process-name pattern; transient states are captured by driving a slow foreground operation and polling a concrete DOM marker inside one browser-script call; completion predicates match an exact-text element rather than a substring; and the encoder runs with `GIF_SKILL_DIR` exported on its own line, per-frame durations holding the settled state longest, and both a JSON-summary check and a visual read of the encoded GIF.
diff --git a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md
index 6f2fdd1d82..861cedb5d1 100644
--- a/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md
+++ b/.agents/notes/implemented/process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md
@@ -6,7 +6,7 @@ Status: implemented
## 问题
-改变产品用户在 GUI 中所见行为的 PR(Pull Request),此前只能通过文字描述和测试名称接受评审,两者都无法展示渲染结果。[浏览器演示 GIF 录制](2026-07-23-browser-demo-gif-recording.md)对应的 skill(技能)能生成真实可信的本地 GIF,但刻意止步于本地产物,于是每个想展示 GIF 的 PR 都得各自重新摸索发布方式;而把 GIF 提交到 PR 分支从来不可接受:进入历史的二进制媒体会永久增大之后每一次克隆的体积。
+改变产品用户在 GUI 中所见行为的 PR(Pull Request),此前只能通过文字描述和测试名称接受评审,两者都无法展示渲染结果。[浏览器演示 GIF 录制](../../../skills/record-browser-gif/SKILL.md)对应的 skill(技能)能生成真实可信的本地 GIF,但刻意止步于本地产物,于是每个想展示 GIF 的 PR 都得各自重新摸索发布方式;而把 GIF 提交到 PR 分支从来不可接受:进入历史的二进制媒体会永久增大之后每一次克隆的体积。
录制流程本身也在靠一次次失败反复重新学习:截图写到浏览器工具允许的根目录之外或写入不存在的目录,会在截取时直接失败;跨多次工具调用轮询的瞬态 UI 状态会丢失,因为调用之间轮次已经结算;用子串匹配做完成判定会命中用户自己提示词的回显;在编码器命令上内联赋值环境变量则因参数先于赋值展开而不生效。
@@ -16,7 +16,7 @@ Status: implemented
GIF 发布到专用的孤儿(orphan)assets 分支上:该分支没有父提交、只含媒体,GIF 绝不进入 PR 自己的分支;一个 assets 分支服务整个 PR 系列(现有分支:`code-mode-ui-assets`、`pr-613-assets`)。发布在浅层单分支的临时克隆中进行,提交信息形如 `assets: gif (#)`,PR 正文用带必需 `?raw=true` 后缀的 blob URL 嵌入。assets 分支只允许追加:已合并的 PR 正文会永远引用其 URL,因此 assets 分支绝不重写或删除。
-录制本身保持无副作用;发布是一个有边界的收尾步骤,仅当任务包含把 GIF 附到 PR 时才由该 skill 执行。这修订了[浏览器演示 GIF 录制记录](2026-07-23-browser-demo-gif-recording.md)中记录的录制/上传边界;录制部分仍以该记录为准。
+录制本身保持无副作用;发布是一个有边界的收尾步骤,仅当任务包含把 GIF 附到 PR 时才由该 skill 执行。[record-browser-gif skill](../../../skills/record-browser-gif/SKILL.md) 仍是录制部分的现行契约。
该 skill 还吸收了录制实践换来的操作经验:帧文件放在仓库 `.gitignore` 忽略的 `.playwright-mcp/` 目录下并在截取前先创建,因为浏览器工具只能写入其允许的根目录,相对文件名也相对仓库根目录解析;每个 PR 从自己构建的分支树启动服务,配以全新的临时工作区目录,每个录制场景新开会话,停止服务器时按 PID 精确匹配而不是用宽泛的进程名模式;瞬态状态靠驱动一个缓慢的前台操作、并在同一次浏览器脚本调用内轮询具体的 DOM 标记来截取;完成判定匹配精确文本元素而非子串;编码器在单独一行 export `GIF_SKILL_DIR` 之后运行,逐帧时长让最终稳定状态停留最久,并同时核对 JSON 摘要与目视检查编码后的 GIF。
diff --git a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml
index cfed1ac086..cd3947fa17 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml
+++ b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml
@@ -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: 31acbb122cf56adfdcfcbce602d09a35f7f13e17
-2026-06-20-remove-agent-boundary-mirror-events.zh.md: f386e8d1ccfc2df094645a3cf9ae9524091d2a4e
+2026-06-20-remove-agent-boundary-mirror-events.md: cde4d00fd2b677cf935b286b063f2c6952a5a98c
+2026-06-20-remove-agent-boundary-mirror-events.zh.md: 188ea0ca95539bebe864685ed8c4073e4d2014d4
diff --git a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md
index 31acbb122c..cde4d00fd2 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md
+++ b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md
@@ -11,7 +11,7 @@ English | [中文](2026-06-20-remove-agent-boundary-mirror-events.zh.md)
out kept this Agent Note's scope to boundaries. Each retained event was later
removed by its own decision — see
[Stop mirroring the token stream as an agent event](2026-07-02-remove-stream-chunk-mirror.md)
- and [Remove the `agent/steering` mirror emit](2026-07-04-remove-agent-steering-mirror.md). -->
+ and [Remove the `agent/steering` mirror emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md). -->
## Problem
@@ -33,13 +33,13 @@ Removed (durable-boundary mirrors — the session log is authoritative for each)
RETAINED — NOT durable-boundary mirrors, so out of scope for this decision:
-- `agent/steering` — not a boundary, so out of scope for THIS decision (the original proposal bundled it into the removal; that would have been scope creep here). It mirrors the durable `steering/message` control record rather than a boundary, and was removed by its own follow-up: [Remove the `agent/steering` mirror emit](2026-07-04-remove-agent-steering-mirror.md).
+- `agent/steering` — not a boundary, so out of scope for THIS decision (the original proposal bundled it into the removal; that would have been scope creep here). It mirrors the durable `steering/message` control record rather than a boundary, and was removed by its own follow-up: [Remove the `agent/steering` mirror emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md).
- `agent/stream-chunk` — the live token stream. Out of scope for THIS decision (a mirror of the durable `assistant/chunk`, not a boundary), it was removed by its own follow-up: [Stop mirroring the token stream as an agent event](2026-07-02-remove-stream-chunk-mirror.md).
- `agent/created`, `agent/disposed`, `agent/status`, `agent/error`, `agent/queued` — lifecycle/control events that are not transcript data. `agent/queued` in particular is an inbox acknowledgement that fires before any durable event exists (cancelled queued work may never enter the log), so it is deliberately live-only.
## Alternatives considered
-- **Bundling `agent/steering` into the removal** — the original proposal's shape; narrowed out as scope creep: it mirrors the durable `steering/message` control record, not a boundary, and was removed by [its own later decision](2026-07-04-remove-agent-steering-mirror.md) (as was `agent/stream-chunk`, by [the stream-chunk-mirror Agent Note](2026-07-02-remove-stream-chunk-mirror.md)).
+- **Bundling `agent/steering` into the removal** — the original proposal's shape; narrowed out as scope creep: it mirrors the durable `steering/message` control record, not a boundary, and was removed by [its own later decision](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md) (as was `agent/stream-chunk`, by [the stream-chunk-mirror Agent Note](2026-07-02-remove-stream-chunk-mirror.md)).
- **Keeping the turn mirrors for the stdio UI** — [the event-domain-semantics Agent Note](../architecture/2026-06-30-event-domain-semantics.md)'s original stance; rejected here because `dsh-ui-stdio` is a disposable test REPL, not a load-bearing consumer, and it renders boundaries from `session/event` plus its live target object instead.
## Consequences
diff --git a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md
index f386e8d1cc..188ea0ca95 100644
--- a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md
+++ b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md
@@ -11,7 +11,7 @@ Status: implemented
移除;把它排除在外,使本 Agent Note 的范围保持在边界上。后来每个保留事件
都由各自的决策移除——参见
[停止将 token 流镜像为 agent 事件](2026-07-02-remove-stream-chunk-mirror.md)
- 和[移除 `agent/steering` 镜像 emit](2026-07-04-remove-agent-steering-mirror.md)。 -->
+ 和[移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)。 -->
## 问题
@@ -33,13 +33,13 @@ Status: implemented
保留——不是持久边界镜像,因此不在本决策范围内:
-- `agent/steering`——不是边界,因此不在本决策范围内(原始提案将其一并移除;在此会造成范围蔓延)。它镜像持久的 `steering/message` 控制记录,而非边界,后来由自己的后续决策移除:[移除 `agent/steering` 镜像 emit](2026-07-04-remove-agent-steering-mirror.md)。
+- `agent/steering`——不是边界,因此不在本决策范围内(原始提案将其一并移除;在此会造成范围蔓延)。它镜像持久的 `steering/message` 控制记录,而非边界,后来由自己的后续决策移除:[移除 `agent/steering` 镜像 emit](../../archived/simplification/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` 一并移除**——原始提案的形状;作为范围蔓延被排除:它镜像持久的 `steering/message` 控制记录,而非边界,后来由[自己的决策](2026-07-04-remove-agent-steering-mirror.md)移除(`agent/stream-chunk` 也由[流分片镜像 Agent Note](2026-07-02-remove-stream-chunk-mirror.md)移除)。
+- **将 `agent/steering` 一并移除**——原始提案的形状;作为范围蔓延被排除:它镜像持久的 `steering/message` 控制记录,而非边界,后来由[自己的决策](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)移除(`agent/stream-chunk` 也由[流分片镜像 Agent Note](2026-07-02-remove-stream-chunk-mirror.md)移除)。
- **为 stdio UI 保留轮次镜像**——[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.md) 的原始立场;在此否决,因为 `dsh-ui-stdio` 是可随时丢弃的测试 REPL,而非承载关键约束的消费方,并且它改为根据 `session/event` 加自己的实时目标对象渲染边界。
## 后果
diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml
index 6e428fa348..23aedfe574 100644
--- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml
+++ b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml
@@ -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: d496f273e2635624e0ab8e70e06c8729563c5466
-2026-06-26-fsspec-style-fs-seam.zh.md: 18e4be5177f593253dc100a864b6c741904a90b2
+2026-06-26-fsspec-style-fs-seam.md: b5c201fb192782f130d3609978d16f0fc6d4d55e
+2026-06-26-fsspec-style-fs-seam.zh.md: 3e4e6c439c85cc8e105766ee7f43c95640e26a43
diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md
index d496f273e2..b5c201fb19 100644
--- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md
+++ b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md
@@ -113,7 +113,7 @@ It keeps the interface/implementation/consumer discipline, consumer-never-import
## Later extension
-The seam was later extended with direct directory listing by [Add direct directory listing to the filesystem seam](../architecture/2026-07-03-filesystem-directory-listing-seam.md). That follow-up is tracked separately so this Agent Note's acceptance criteria continue to describe the fsspec-style refit that originally shipped.
+The seam was later extended with direct directory listing by [Add direct directory listing to the filesystem seam](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md). That follow-up is tracked separately so this Agent Note's acceptance criteria continue to describe the fsspec-style refit that originally shipped.
## Alternatives considered
diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md
index 18e4be5177..3e4e6c439c 100644
--- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md
+++ b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md
@@ -113,7 +113,7 @@ type FsWriteIntent =
## 后续扩展
-后来,[为文件系统 seam 添加直接目录列表](../architecture/2026-07-03-filesystem-directory-listing-seam.md)进一步扩展了该 seam。该后续工作单独跟踪,使本 Agent Note 的验收标准继续描述最初落地的 fsspec 风格改造。
+后来,[为文件系统 seam 添加直接目录列表](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)进一步扩展了该 seam。该后续工作单独跟踪,使本 Agent Note 的验收标准继续描述最初落地的 fsspec 风格改造。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml
index 5bc1c28d52..83ddffc483 100644
--- a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml
+++ b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml
@@ -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: 5b8e4a33819af69b68a9c5f93be12f4069baebde
-2026-07-02-remove-stream-chunk-mirror.zh.md: 9357748884b0f3857e75d8fcc3b28fc5c5db1838
+2026-07-02-remove-stream-chunk-mirror.md: 1d9ff86800521eb5ef226575e33a35dcaffd6f6e
+2026-07-02-remove-stream-chunk-mirror.zh.md: 26dcc36038efd2857a90c15a675d843d57282ec1
diff --git a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md
index 5b8e4a3381..cc75f71407 100644
--- a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md
+++ b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md
@@ -35,7 +35,7 @@ Removed: `agent/stream-chunk`.
Not touched:
- `assistant/chunk` (the durable session event) — the authoritative token stream, kept exactly as-is. This Agent Note removes the LIVE MIRROR, not the persistence (the persistence-removal proposal was separately rejected — see above).
-- `agent/steering` — not touched by THIS decision (a control signal, not the token stream). Its durable twin is `steering/message`, and the mirror emit was removed by its own follow-up: [Remove the `agent/steering` mirror emit](2026-07-04-remove-agent-steering-mirror.md).
+- `agent/steering` — not touched by THIS decision (a control signal, not the token stream). Its durable twin is `steering/message`, and the mirror emit was removed by its own follow-up: [Remove the `agent/steering` mirror emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md).
- `agent/status`, `agent/error`, `agent/created`/`agent/disposed`, `agent/queued`, `agent/session-start` — lifecycle/control events that are not transcript data and have no durable duplicate.
## Alternatives considered
diff --git a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md
index 9357748884..47e4c42dae 100644
--- a/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md
+++ b/.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md
@@ -35,7 +35,7 @@ ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
未触及:
- `assistant/chunk`(持久会话事件)——权威 token 流,原样保留。本 Agent Note 移除的是实时镜像,而非持久化(移除持久化的提案已单独遭到拒绝——见上文)。
-- `agent/steering`——本决策未触及(它是控制信号,不是 token 流)。其持久孪生事件是 `steering/message`,镜像发射由其自身的后续 Agent Note 移除:[移除 `agent/steering` 镜像发射](2026-07-04-remove-agent-steering-mirror.md)。
+- `agent/steering`——本决策未触及(它是控制信号,不是 token 流)。其持久孪生事件是 `steering/message`,镜像发射由其自身的后续 Agent Note 移除:[移除 `agent/steering` 镜像发射](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)。
- `agent/status`、`agent/error`、`agent/created`/`agent/disposed`、`agent/queued`、`agent/session-start`——生命周期/控制事件,不是 transcript 数据,也没有持久副本。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml
index b2c35d276c..002473026f 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml
+++ b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml
@@ -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: a1972ee8ef486982268ba8886b2413f3557061b4
-2026-07-04-tighten-hook-protocol-contract.zh.md: 4917a8f551672f51b0ebc27b1267c7e8e8eb2178
+2026-07-04-tighten-hook-protocol-contract.md: a67d0e8447e36516006e57051581c03877c1ba12
+2026-07-04-tighten-hook-protocol-contract.zh.md: c0f97cf39adb0fd17caa3ad2c518d26736bfc7a6
diff --git a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md
index a1972ee8ef..a67d0e8447 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md
+++ b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md
@@ -6,7 +6,7 @@ English | [中文](2026-07-04-tighten-hook-protocol-contract.zh.md)
## Problem
-Four pieces of the `dsh-hook-protocol`/bridge contract missed the discipline the [subagent-observe-enrich Agent Note](../feature/2026-06-30-subagent-observe-enrich.md) records — it dropped an `agentType` lifecycle field for lacking a consumer, and these failed the same test:
+Four pieces of the `dsh-hook-protocol`/bridge contract missed the discipline the [subagent-observe-enrich Agent Note](../../archived/feature/2026-06-30-subagent-observe-enrich.md) records — it dropped an `agentType` lifecycle field for lacking a consumer, and these failed the same test:
1. **`HookDialect`'s `'native'` variant** (`packages/hooks/hook-protocol/src/types.ts`) had zero producers — the bridges stamp `'claude'` and `'codex'`; the only `'native'` constructor anywhere was the lib's own unit test. The field's own JSDoc defines `dialect` as "the bridge that ran it", and native is not a bridge: the [interception-seams Agent Note](../feature/2026-06-30-interception-seams.md) records that native hooks are not a package and that "a native plugin can already use the typed Decisions" without the durable hook log, and the flagship native-plugin worked example asserts exactly that (no `hook/*` events at all).
2. **`HookOutput.suppressOutput`** (same file) was parsed by the codec and discarded on every path: no bridge branch, no merge fold, no warn, no deferred-list row — uniquely among its parsed-but-unhonored siblings, each of which carries a stated deferral (`updatedInput` → a logged warn plus the [pre-tool-input-rewrite proposal](../../proposed/feature/2026-06-30-pre-tool-input-rewrite.md); `systemMessage` → a logged warn plus a README deferred row; `continue`/`stopReason` → a `TODO(hook-continue-false)` anchor plus the `'stop'` decision record). Structurally there is nothing to suppress: hook stdout never enters any transcript (context flows only via `additionalContext`; the log records only `decision`/`stderrSummary`), so a hook author setting `suppressOutput: true` got silent nothing with no warn.
diff --git a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md
index 4917a8f551..c0f97cf39a 100644
--- a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md
+++ b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md
@@ -6,7 +6,7 @@ Status: implemented
## 问题
-`dsh-hook-protocol`/bridge 契约中有四部分没有遵守 [subagent observe/enrich Agent Note(agent 决策记录)](../feature/2026-06-30-subagent-observe-enrich.md)记下的准则——后者因缺少消费方而删除 `agentType` 生命周期字段,以下各项没有通过同一检验:
+`dsh-hook-protocol`/bridge 契约中有四部分没有遵守 [subagent observe/enrich Agent Note(agent 决策记录)](../../archived/feature/2026-06-30-subagent-observe-enrich.md)记下的准则——后者因缺少消费方而删除 `agentType` 生命周期字段,以下各项没有通过同一检验:
1. **`HookDialect` 的 `'native'` 变体**(`packages/hooks/hook-protocol/src/types.ts`)没有生产者——bridge 会标记 `'claude'` 和 `'codex'`;所有位置中唯一构造 `'native'` 的是该库自己的单元测试。字段自身的 JSDoc 将 `dialect` 定义为“运行它的 bridge”,而 native 不是 bridge:[拦截 seam Agent Note](../feature/2026-06-30-interception-seams.md) 记载 native 钩子不是一个包,并且“native 插件无需持久钩子日志即可使用类型化 Decision”;旗舰 native 插件实践示例恰好断言了这一点(完全没有 `hook/*` 事件)。
2. **`HookOutput.suppressOutput`**(同一文件)被 codec 解析后在所有路径上均被丢弃:没有 bridge 分支处理它、没有合并 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 记录)。从结构上看根本无物可抑制:钩子 stdout 从不进入任何 transcript(文本记录)(上下文仅通过 `additionalContext` 流入;日志只记录 `decision`/`stderrSummary`),因此钩子作者设置 `suppressOutput: true` 得到的是无声的空操作,且无任何警告。
diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml
index feeadfed91..13d4b4e495 100644
--- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml
+++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml
@@ -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-acp-snapshot-tests.md: b4cda8f32fe7a84a977bcbdbe5db0671cb9a7083
-2026-06-19-acp-snapshot-tests.zh.md: 5337c3852b524af4e8c556e93ec80084b30a6d0b
+2026-06-19-acp-snapshot-tests.md: 430441e633af1e487f19272900360a8ed2f595c9
+2026-06-19-acp-snapshot-tests.zh.md: 243e431567b45d69e1fe16dda5d07ec058403b7c
diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md
index b4cda8f32f..430441e633 100644
--- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md
+++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md
@@ -20,7 +20,7 @@ A snapshot test boots the real ACP example, drives its stdio protocol from a det
Each scenario's `session.jsonl` is harvested from a real run. `assistant/chunk` events reproduce the model streams; tool, message, and boundary events capture the harness behavior. One ordinary session artifact therefore serves as both replay source and behavioral expected output.
-When a scenario pins an alternative physical storage layout, its fixture is mechanically derived from a real unpacked counterpart. The scenario test requires every intended storage-row kind and exact event-for-event equality after decoding before the ordinary replay and log comparison proves that the assembled process consumes and reproduces that layout.
+Every committed session-format fixture uses the canonical packed physical layout. The all-row-kinds scenario is mechanically derived from an independent real recording; its test requires every packed storage-row kind and exact event-for-event equality after both fixtures decode, then ordinary replay and log comparison prove that the assembled process consumes and reproduces the layout.
### Replay derives the model script from the log
@@ -44,16 +44,16 @@ Replay is positional and therefore permits only one in-flight model stream per s
### Recording harvests the log; keyless replay needs a providerless config
-Recording runs the scenario with the real `llm-deepseek` adapter and the JSONL persistence backend configured with `persistenceCompression: 'none'`, then copies the produced `.jsonl` into the scenario dir. The explicit raw mode keeps committed replay fixtures line-readable while ordinary deployments use the backend's compressed default. Per-event appends are durable, but the harness shuts the subprocess down gracefully (close stdin → `await ctx.dispose()`) before harvesting so the final events are flushed. `llm-replay` itself does no recording — it is replay-only.
+Recording runs the scenario with the real `llm-deepseek` adapter and the JSONL persistence backend configured with `persistenceCompression: 'none'`, then copies the produced `.jsonl` into the scenario dir. The explicit raw mode keeps committed replay fixtures line-readable while ordinary deployments use the backend's compressed default; eligible chunk runs still use the default packed storage rows. Per-event appends are durable, but the harness shuts the subprocess down gracefully (close stdin → `await ctx.dispose()`) before harvesting so the final events are flushed. `llm-replay` itself does no recording — it is replay-only.
-Replay uses a `cordis.snapshot.yml` overlay that replaces the real adapter with `llm-replay` while retaining the live composition. Recording uses the ordinary config and a harness-supplied persistence root. Replay mode skips `.env` loading, so a stray API key cannot trigger a live call. See the [single-source config Agent Note](2026-07-04-single-source-acp-replay-config.md).
+Replay uses a `cordis.snapshot.yml` overlay that replaces the real adapter with `llm-replay` while retaining the live composition. Recording uses the ordinary config and a harness-supplied persistence root. Replay mode skips `.env` loading, so a stray API key cannot trigger a live call. See the [single-source config Agent Note](../../archived/testing/2026-07-04-single-source-acp-replay-config.md).
### Two surfaces: normalize, then compare
A snapshot run asserts **two** normalized surfaces, because the harness's external surfaces are distinct:
1. The **stdout transcript** — the framed ACP JSON-RPC responses and committed-message updates an automation client receives. It catches regressions in the transport contract and is compared against a committed `stdout.expected.jsonl`.
-2. The **re-persisted session JSONL**, normalized and compared with `session.jsonl`. The same fixture is both replay source and expected log. Prompt text is scrubbed; one scenario per header class pins readable prompt and tool content as described in the [header-pinning Agent Note](2026-07-06-pin-request-header-content-in-one-scenario.md). Override scenarios derive model behavior solely from their sidecar.
+2. The **re-persisted session JSONL**, normalized and compared with `session.jsonl`. The same fixture is both replay source and expected log. Prompt text is scrubbed; one scenario per header class pins readable prompt and tool content as described in the [header-pinning Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md). Override scenarios derive model behavior solely from their sidecar.
The surfaces are complementary: stdout covers the minimal automation wire, while JSONL covers loop, tool, and boundary structure that the wire intentionally omits.
@@ -69,7 +69,7 @@ Tool determinism comes from a generated cwd, scrubbed environment, fresh non-log
### Two subcommands, replay in the default gate
-`pnpm run test:snapshot` replays committed fixtures keylessly; `test:snapshot:record` uses the real API and rewrites the harvested session log and stdout expected output. Missing fixtures fail loud. Every scenario carries `input.json`, `stdout.expected.jsonl`, and `session.jsonl`; no-model cases use a header-only log. `replay.override.json` is required only for scenarios marked `overridden`, because its presence replaces derived replay. Fixture guards reject missing, mismatched, and orphaned files. Both commands accept scenario filters.
+`pnpm run test:snapshot` replays committed fixtures keylessly; `test:snapshot:record` uses the real API and rewrites the harvested session log and stdout expected output. The same keyless gate discovers repository JSONL by its `session` header and rejects any fixture that differs from the shared codec's canonical packed representation. Missing fixtures fail loud. Every scenario carries `input.json`, `stdout.expected.jsonl`, and `session.jsonl`; no-model cases use a header-only log. `replay.override.json` is required only for scenarios marked `overridden`, because its presence replaces derived replay. Fixture guards reject missing, mismatched, and orphaned files. Both commands accept scenario filters.
## Alternatives considered
diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md
index 5337c3852b..243e431567 100644
--- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md
+++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md
@@ -20,7 +20,7 @@ Status: implemented
每个场景的 `session.jsonl` 都从真实运行中采集。`assistant/chunk` 事件复现模型流;工具、消息和边界事件捕获 harness 行为。因此,一份普通会话产物同时充当重放来源和行为预期输出。
-当场景固定另一种物理存储布局时,其 fixture 会从真实的未打包对应项机械派生。场景测试要求包含每一种预期存储行类型,并在解码后逐事件精确相等;随后,普通重放与日志比较才会证明组合后的进程能够消费并复现该布局。
+每个签入仓库的会话格式 fixture 都使用规范的打包物理布局。覆盖所有行类型的场景从一份独立的真实录制机械派生;测试要求它包含每一种打包存储行类型,并在两份 fixture 解码后逐事件精确相等;随后,普通重放与日志比较会证明组装后的进程能够消费并复现该布局。
### 回放从日志推导模型脚本
@@ -44,16 +44,16 @@ Status: implemented
### 录制采集日志;无密钥回放需要无提供方的配置
-记录模式使用真实 `llm-deepseek` 适配器和配置为 `persistenceCompression: 'none'` 的 JSONL 持久化后端运行场景,再把生成的 `.jsonl` 复制到场景目录。显式 raw 模式让已提交重放 fixture 保持逐行可读,而普通部署使用后端的压缩默认值。逐事件追加具有持久性,但 harness 会在采集前优雅关闭子进程(关闭 stdin → `await ctx.dispose()`),以确保最终事件已刷出。`llm-replay` 本身不执行记录——它只负责重放。
+记录模式使用真实 `llm-deepseek` 适配器和配置为 `persistenceCompression: 'none'` 的 JSONL 持久化后端运行场景,再把生成的 `.jsonl` 复制到场景目录。显式 raw 模式让已提交重放 fixture 保持逐行可读,而普通部署使用后端的压缩默认值;符合条件的分片连续段仍使用默认的打包存储行。逐事件追加具有持久性,但 harness 会在采集前优雅关闭子进程(关闭 stdin → `await ctx.dispose()`),以确保最终事件已刷出。`llm-replay` 本身不执行记录——它只负责重放。
-重放使用 `cordis.snapshot.yml` overlay,以 `llm-replay` 替换真实适配器,同时保留实时组合。记录使用普通配置和由 harness 提供的持久化根目录。重放模式跳过 `.env` 加载,因此意外存在的 API 密钥不会触发实时调用。参见[单一来源配置 Agent Note](2026-07-04-single-source-acp-replay-config.md)。
+重放使用 `cordis.snapshot.yml` overlay,以 `llm-replay` 替换真实适配器,同时保留实时组合。记录使用普通配置和由 harness 提供的持久化根目录。重放模式跳过 `.env` 加载,因此意外存在的 API 密钥不会触发实时调用。参见[单一来源配置 Agent Note](../../archived/testing/2026-07-04-single-source-acp-replay-config.md)。
### 两个表面:归一化后比对
快照运行断言**两个**归一化后的表面,因为 harness 的外部表面是不同的:
1. **stdout transcript**——自动化客户端收到的、经过 framing 的 ACP JSON-RPC 响应与已提交的消息更新。它捕获传输契约的回归,与已提交的 `stdout.expected.jsonl` 比较。
-2. **重新持久化的会话 JSONL**,经过规范化后与 `session.jsonl` 比较。同一 fixture 同时作为重放来源和预期日志。提示词文本会被清理;按照[请求头固定 Agent Note](2026-07-06-pin-request-header-content-in-one-scenario.md)所述,每种请求头类别由一个场景固定可读提示词与工具内容。Override 场景仅从其 sidecar 派生模型行为。
+2. **重新持久化的会话 JSONL**,经过规范化后与 `session.jsonl` 比较。同一 fixture 同时作为重放来源和预期日志。提示词文本会被清理;按照[请求头固定 Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)所述,每种请求头类别由一个场景固定可读提示词与工具内容。Override 场景仅从其 sidecar 派生模型行为。
两个表面互补:stdout 覆盖精简的自动化线协议,JSONL 覆盖线协议有意省略的 loop、工具和 boundary 结构。
@@ -69,7 +69,7 @@ Status: implemented
### 两个子命令,回放在默认门禁中
-`pnpm run test:snapshot` 无需密钥即可重放已提交 fixture;`test:snapshot:record` 使用真实 API,并重写采集的会话日志与 stdout 预期输出。缺少 fixture 时会响亮失败。每个场景都包含 `input.json`、`stdout.expected.jsonl` 和 `session.jsonl`;不调用模型的情况使用仅有请求头的日志。只有标记为 `overridden` 的场景才需要 `replay.override.json`,因为它一旦存在就会取代派生重放。Fixture 守卫会拒绝缺失、不匹配和孤立文件。两个命令都接受场景过滤器。
+`pnpm run test:snapshot` 无需密钥即可重放已提交 fixture;`test:snapshot:record` 使用真实 API,并重写采集的会话日志与 stdout 预期输出。同一无密钥门禁会通过 `session` header 发现仓库中的 JSONL,并拒绝与共享编解码器的规范打包表示不同的任何 fixture。缺少 fixture 时会响亮失败。每个场景都包含 `input.json`、`stdout.expected.jsonl` 和 `session.jsonl`;不调用模型的情况使用仅有请求头的日志。只有标记为 `overridden` 的场景才需要 `replay.override.json`,因为它一旦存在就会取代派生重放。Fixture 守卫会拒绝缺失、不匹配和孤立文件。两个命令都接受场景过滤器。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml
index 822fc921b4..e6c04226f9 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml
+++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml
@@ -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-22-fork-child-replay-seed-boundary.md: d3cbbb1dae1d64a10973bd5895ccc47d877eba28
-2026-06-22-fork-child-replay-seed-boundary.zh.md: a944538b9dcb74eb15142593089c7905efc3f565
+2026-06-22-fork-child-replay-seed-boundary.md: ed3ec095bc14128f5ebc0a9188bc022ef97b1c8b
+2026-06-22-fork-child-replay-seed-boundary.zh.md: 84cd56ccea69aab0246582512908b4a73ce3c36a
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md
index d3cbbb1dae..ed3ec095bc 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md
+++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md
@@ -35,7 +35,7 @@ The SQLite layout containing `seed_length`, `source_event_seqs`, and `surface_op
`dsh-llm-replay`'s `parseSessionHeader` now also reads `seedLength` (absent ⇒ 0), and `loadSessionScripts` derives a child's entries from `parseSessionLog(text).slice(seedLength)` — the events at or after the boundary, i.e. the child's own model calls. For a spawn child `seedLength` is 0 and this is a no-op, so spawn scenarios are byte-for-byte unchanged.
-This closes the routing correctness gap, and two recorded fork scenarios exercise it end to end — see [Record fork and mixed spawn+fork snapshot scenarios](2026-06-22-fork-snapshot-scenarios.md).
+This closes the routing correctness gap, and two recorded fork scenarios exercise it end to end — see [Record fork and mixed spawn+fork snapshot scenarios](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md).
## Alternatives considered
diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md
index a944538b9d..84cd56ccea 100644
--- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md
+++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md
@@ -35,7 +35,7 @@ subagent 脚本由 [`deriveReplayScript`](../../../../packages/support/llm-repla
`dsh-llm-replay` 的 `parseSessionHeader` 现在也读取 `seedLength`(缺失则为 0),`loadSessionScripts` 从 `parseSessionLog(text).slice(seedLength)` 推导子会话条目——即边界及之后的事件,也就是子会话自身的模型调用。对 spawn 子会话而言 `seedLength` 为 0,此操作是空操作,spawn 场景逐字节不变。
-这关闭了路由正确性的缺口,两个已录制的 fork 场景对其进行端到端验证——见[记录 fork 与混合 spawn+fork 快照场景](2026-06-22-fork-snapshot-scenarios.md)。
+这关闭了路由正确性的缺口,两个已录制的 fork 场景对其进行端到端验证——见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md)。
## 曾考虑的替代方案
diff --git a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml
index 65e49bd193..a99819223f 100644
--- a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml
+++ b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml
@@ -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-22-subagent-snapshot-replay.md: 8cd7bc86e07af9ed274c18574b575b9070854e88
-2026-06-22-subagent-snapshot-replay.zh.md: eae78129405fedd03c2c579845c07c6e5694cc30
+2026-06-22-subagent-snapshot-replay.md: b8fefce5ff27b0cd3cfa2920b137e78cda0d696d
+2026-06-22-subagent-snapshot-replay.zh.md: a673d6e5dd124986b827fcc6708db447090173c7
diff --git a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md
index 8cd7bc86e0..b8fefce5ff 100644
--- a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md
+++ b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md
@@ -54,5 +54,5 @@ Both replay keyless in the default gate.
- The `TODO(subagent-snapshots)` deferral is resolved: nested-agent transcripts are now a first-class snapshot shape.
- `GenerateOptions.sessionId` is a small, honest core-seam addition useful beyond replay (telemetry, request routing).
-- The `subagent` tool is bound to a single provider, so both children in `subagent-multi` are spawn (fresh). The keying routes by session, not by backend, so it is already correct for fork. The script *derivation* was not: a fork child's log begins with the seeded parent prefix (the parent's `assistant/chunk` events), so deriving its script from the whole log would replay the parent's responses as the child's. That correctness gap is closed by persisting a seed boundary — see [Persist the seed boundary so fork-child replay routes correctly](2026-06-22-fork-child-replay-seed-boundary.md) — and recorded fork + mixed spawn+fork scenarios now exercise both transports through one transcript (see [Record fork and mixed spawn+fork snapshot scenarios](2026-06-22-fork-snapshot-scenarios.md)).
+- The `subagent` tool is bound to a single provider, so both children in `subagent-multi` are spawn (fresh). The keying routes by session, not by backend, so it is already correct for fork. The script *derivation* was not: a fork child's log begins with the seeded parent prefix (the parent's `assistant/chunk` events), so deriving its script from the whole log would replay the parent's responses as the child's. That correctness gap is closed by persisting a seed boundary — see [Persist the seed boundary so fork-child replay routes correctly](2026-06-22-fork-child-replay-seed-boundary.md) — and recorded fork + mixed spawn+fork scenarios now exercise both transports through one transcript (see [Record fork and mixed spawn+fork snapshot scenarios](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md)).
- Out-of-process (ACP) subagents are a different replay shape entirely (each child is its own PROCESS with its own replay), tracked as `TODO(acp-subagent-replay)` in the PR3 plan.
diff --git a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md
index eae7812940..a673d6e5dd 100644
--- a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md
+++ b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md
@@ -54,5 +54,5 @@ Status: implemented
- `TODO(subagent-snapshots)` 延期项已解决:嵌套 agent 的 transcript 现在是快照层的一等形态。
- `GenerateOptions.sessionId` 是一个小而诚实的 core-seam 新增,在回放之外同样有用(遥测、请求路由)。
-- `subagent` 工具绑定到单一提供方,因此 `subagent-multi` 中的两个子 agent 都是 spawn(全新创建)。键控按会话路由而非按后端路由,因此对 fork 同样正确。但脚本*派生*逻辑此前不正确:fork 子会话的日志以种子化的父前缀(父会话的 `assistant/chunk` 事件)开头,如果从完整日志派生脚本,就会把父 agent 的响应当作子 agent 的来回放。这一正确性缺口通过持久化种子边界来弥合——见[持久化种子边界,使 fork 子项重放能够正确路由](2026-06-22-fork-child-replay-seed-boundary.md)——录制的 fork 与混合 spawn+fork 场景现在通过一份 transcript 同时验证两种传输方式(见[记录 fork 与混合 spawn+fork 快照场景](2026-06-22-fork-snapshot-scenarios.md))。
+- `subagent` 工具绑定到单一提供方,因此 `subagent-multi` 中的两个子 agent 都是 spawn(全新创建)。键控按会话路由而非按后端路由,因此对 fork 同样正确。但脚本*派生*逻辑此前不正确:fork 子会话的日志以种子化的父前缀(父会话的 `assistant/chunk` 事件)开头,如果从完整日志派生脚本,就会把父 agent 的响应当作子 agent 的来回放。这一正确性缺口通过持久化种子边界来弥合——见[持久化种子边界,使 fork 子项重放能够正确路由](2026-06-22-fork-child-replay-seed-boundary.md)——录制的 fork 与混合 spawn+fork 场景现在通过一份 transcript 同时验证两种传输方式(见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md))。
- 进程外(ACP(Agent Client Protocol))subagent 是完全不同的回放形态(每个子 agent 是自己的进程、有自己的回放),作为 `TODO(acp-subagent-replay)` 记录在 PR3 计划中。
diff --git a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml
index 5c5bad0b1a..4aa2ea289d 100644
--- a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml
+++ b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml
@@ -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-08-shared-acp-snapshot-package.md: 3e5a2b12114d535490a17361128862f6d1c09a73
-2026-07-08-shared-acp-snapshot-package.zh.md: 072ef692702cb769c428f8b6aa3863a3b77d3d59
+2026-07-08-shared-acp-snapshot-package.md: dc86bf020b159a1c4af26bbc49725ce2b7de8180
+2026-07-08-shared-acp-snapshot-package.zh.md: 19eb070bbc5aa1a0b72c0cc874225064e92632b8
diff --git a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md
index 3e5a2b1211..dc86bf020b 100644
--- a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md
+++ b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md
@@ -12,7 +12,7 @@ A second ACP example wanting snapshot coverage — the sandbox/approval composit
## Decision
-The machinery lives in [`packages/support/acp-snapshot`](../../../../packages/support/acp-snapshot/README.md) (`@deepseek-ai/dsh-acp-snapshot`); an example's `*.snapshot.ts` is its scenario table, its agent paths, and one factory call, over its own `snapshots/` fixtures and `cordis.snapshot.yml` overlay ([single-source replay config](2026-07-04-single-source-acp-replay-config.md)). Reading `DSH_SNAPSHOT` stays at that edge — the library takes a resolved `mode`.
+The machinery lives in [`packages/support/acp-snapshot`](../../../../packages/support/acp-snapshot/README.md) (`@deepseek-ai/dsh-acp-snapshot`); an example's `*.snapshot.ts` is its scenario table, its agent paths, and one factory call, over its own `snapshots/` fixtures and `cordis.snapshot.yml` overlay ([single-source replay config](../../archived/testing/2026-07-04-single-source-acp-replay-config.md)). Reading `DSH_SNAPSHOT` stays at that edge — the library takes a resolved `mode`.
**`src/launcher.ts`** — `launchAcpTestAgent` owns the common unbuilt-process boundary: absolute tsx loader resolution, `TSX_TSCONFIG_PATH`, isolated harness homes, stdio wiring, a raw-byte stdout tee, stderr and update capture, fail-closed permission fallback, update waiters, and graceful or signalled shutdown. Snapshot scenarios and ordinary e2e suites supply the same `AgentUnderTest` (`binScript`, `configPath`, `tsconfigPath`); a test that plays a user supplies only its permission handler. The ACP and hook e2e suites plus the sandbox/approval e2e suite use this launcher instead of rebuilding the SDK client boundary.
@@ -20,7 +20,7 @@ The machinery lives in [`packages/support/acp-snapshot`](../../../../packages/su
**`src/normalize.ts`** — the pure normalizers, hook-free by policy: when a future event carries a new volatile field (an approval duration, say), the shared normalizer learns it in the same change, keeping one home for what "normalized" means rather than per-suite scrub extensions.
-**`src/suite.ts`** — the `Scenario` type and `defineAcpSnapshotSuite(options)`, registering the per-scenario compares, record/refresh fixture write-back, the header pin with its live uniformity guard, and the fixture guard block (no orphan scenario dirs, required files present, exactly one pin per class, every JSONL a `scrubSystemPrompts` fixed point, non-pinning fixtures also `scrubRequestHeaders` fixed points). Refresh expands packed timing envelopes before aligning existing volatile event times, so switching between packed and unpacked layouts cannot shift later records; fresh chunk-fragment arrays remain authoritative because their boundaries are replay behavior. A scenario directory's `session.jsonl` plus contiguous `session..jsonl` siblings are its ordered primary/child inventory, so the scenario table declares policy without duplicating a child count. The pinned-header contract ([pinned-header Agent Note](2026-07-06-pin-request-header-content-in-one-scenario.md)) is per-suite: each header class flags exactly one `pinsHeader` scenario, whose `system-prompt.expected.md` and JSONL tool list split the composed header into reviewable artifacts; the uniformity guard compares both against every live header in that class. A pinning scenario declares any legitimate changed-header count, and its Markdown artifact records every full changed prompt. The pure helpers (`sessionFixtureNames`, `fixtureContext`, `normalizedHeaders`, `normalizedSystemPrompts`, `formatSystemPromptSnapshot`, `headerChangeCount`) are exported from the module for direct unit coverage.
+**`src/suite.ts`** — the `Scenario` type and `defineAcpSnapshotSuite(options)`, registering the per-scenario compares, record/refresh fixture write-back, the header pin with its live uniformity guard, and the fixture guard block (no orphan scenario dirs, required files present, exactly one pin per class, every JSONL a `scrubSystemPrompts` fixed point, non-pinning fixtures also `scrubRequestHeaders` fixed points). Refresh expands packed timing envelopes before aligning existing volatile event times, so switching between packed and unpacked layouts cannot shift later records; fresh chunk-fragment arrays remain authoritative because their boundaries are replay behavior. A scenario directory's `session.jsonl` plus contiguous `session..jsonl` siblings are its ordered primary/child inventory, so the scenario table declares policy without duplicating a child count. The pinned-header contract ([pinned-header Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)) is per-suite: each header class flags exactly one `pinsHeader` scenario, whose `system-prompt.expected.md` and JSONL tool list split the composed header into reviewable artifacts; the uniformity guard compares both against every live header in that class. A pinning scenario declares any legitimate changed-header count, and its Markdown artifact records every full changed prompt. The pure helpers (`sessionFixtureNames`, `fixtureContext`, `normalizedHeaders`, `normalizedSystemPrompts`, `formatSystemPromptSnapshot`, `headerChangeCount`) are exported from the module for direct unit coverage.
## Alternatives considered
diff --git a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.zh.md b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.zh.md
index 072ef69270..19eb070bbc 100644
--- a/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.zh.md
+++ b/.agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.zh.md
@@ -12,7 +12,7 @@ ACP(Agent Client Protocol)快照层([快照 Agent Note(agent 决策记
## 决策
-这些机制位于 [`packages/support/acp-snapshot`](../../../../packages/support/acp-snapshot/README.md)(`@deepseek-ai/dsh-acp-snapshot`);示例的 `*.snapshot.ts` 只包含场景表、agent 路径和一次工厂调用,依赖自己的 `snapshots/` fixture 与 `cordis.snapshot.yml` overlay([单源回放配置](2026-07-04-single-source-acp-replay-config.md))。读取 `DSH_SNAPSHOT` 留在边缘层——库接收的是已解析的 `mode`。
+这些机制位于 [`packages/support/acp-snapshot`](../../../../packages/support/acp-snapshot/README.md)(`@deepseek-ai/dsh-acp-snapshot`);示例的 `*.snapshot.ts` 只包含场景表、agent 路径和一次工厂调用,依赖自己的 `snapshots/` fixture 与 `cordis.snapshot.yml` overlay([单源回放配置](../../archived/testing/2026-07-04-single-source-acp-replay-config.md))。读取 `DSH_SNAPSHOT` 留在边缘层——库接收的是已解析的 `mode`。
**`src/launcher.ts`**——`launchAcpTestAgent` 拥有通用的未构建进程边界:绝对 tsx loader 解析、`TSX_TSCONFIG_PATH`、隔离的 harness home、stdio 接线、原始字节 stdout tee、stderr 与更新捕获、失败关闭的权限后备、更新 waiter,以及优雅或信号式关闭。快照场景和普通 e2e 套件提供相同的 `AgentUnderTest`(`binScript`、`configPath`、`tsconfigPath`);扮演用户的测试只提供其权限 handler。ACP 与钩子 e2e 套件以及沙箱/approval e2e 套件都使用该 launcher,而不再重新构建 SDK client 边界。
@@ -20,7 +20,7 @@ ACP(Agent Client Protocol)快照层([快照 Agent Note(agent 决策记
**`src/normalize.ts`** 是纯规范化器,按策略不含钩子:当未来某个事件携带新的易变字段(例如审批耗时),共享规范化器在同一个变更中学会它,保持「规范化」的含义只有一个归属,而非各套件各自扩展清洗逻辑。
-**`src/suite.ts`**——包含 `Scenario` 类型和 `defineAcpSnapshotSuite(options)`,注册各场景比较、记录/刷新 fixture 写回、带实时一致性守卫的请求头固定项,以及 fixture 守卫块(没有孤立场景目录、必需文件存在、每种类别恰好一个固定项、每份 JSONL 都是 `scrubSystemPrompts` 固定点、非固定 fixture 同时也是 `scrubRequestHeaders` 固定点)。刷新会先展开打包的计时信封,再对齐现有易变事件时间,因此在打包与未打包布局之间切换不会移动后续记录;全新的分片片段数组仍为权威,因为其边界属于回放行为。场景目录中的 `session.jsonl` 加连续的 `session..jsonl` 同级文件构成有序主项/子项清单,因此场景表可以声明策略而不重复子项数量。固定请求头契约([固定请求头 Agent Note](2026-07-06-pin-request-header-content-in-one-scenario.md))按套件生效:每种请求头类别恰好标记一个 `pinsHeader` 场景,其 `system-prompt.expected.md` 和 JSONL 工具列表把组合请求头拆成可评审产物;一致性守卫会将两者与该类别的每个实时请求头比较。固定场景可以声明任何合法的变更请求头数量,其 Markdown 产物记录每个完整的已变提示词。纯辅助函数(`sessionFixtureNames`、`fixtureContext`、`normalizedHeaders`、`normalizedSystemPrompts`、`formatSystemPromptSnapshot`、`headerChangeCount`)从模块导出,以便直接进行单元覆盖。
+**`src/suite.ts`**——包含 `Scenario` 类型和 `defineAcpSnapshotSuite(options)`,注册各场景比较、记录/刷新 fixture 写回、带实时一致性守卫的请求头固定项,以及 fixture 守卫块(没有孤立场景目录、必需文件存在、每种类别恰好一个固定项、每份 JSONL 都是 `scrubSystemPrompts` 固定点、非固定 fixture 同时也是 `scrubRequestHeaders` 固定点)。刷新会先展开打包的计时信封,再对齐现有易变事件时间,因此在打包与未打包布局之间切换不会移动后续记录;全新的分片片段数组仍为权威,因为其边界属于回放行为。场景目录中的 `session.jsonl` 加连续的 `session..jsonl` 同级文件构成有序主项/子项清单,因此场景表可以声明策略而不重复子项数量。固定请求头契约([固定请求头 Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))按套件生效:每种请求头类别恰好标记一个 `pinsHeader` 场景,其 `system-prompt.expected.md` 和 JSONL 工具列表把组合请求头拆成可评审产物;一致性守卫会将两者与该类别的每个实时请求头比较。固定场景可以声明任何合法的变更请求头数量,其 Markdown 产物记录每个完整的已变提示词。纯辅助函数(`sessionFixtureNames`、`fixtureContext`、`normalizedHeaders`、`normalizedSystemPrompts`、`formatSystemPromptSnapshot`、`headerChangeCount`)从模块导出,以便直接进行单元覆盖。
## 曾考虑的替代方案
diff --git a/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.i18n.yaml b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.i18n.yaml
new file mode 100644
index 0000000000..56e0178e8d
--- /dev/null
+++ b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.i18n.yaml
@@ -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
+2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md: 047449f4915c973e86cdb9f05f6dc51535133534
+2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md: 379d57e1e0006bf8f567d0b750ca0bb641ca6b49
diff --git a/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md
new file mode 100644
index 0000000000..047449f491
--- /dev/null
+++ b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md
@@ -0,0 +1,34 @@
+# Agent Note: Evaluate landstrip before building a Windows sandbox launcher
+
+Status: proposed
+
+English | [中文](2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md)
+
+## Problem
+
+The [sandbox decision](../../implemented/feature/2026-07-06-sandbox.md) leaves `PLATFORM_CHAINS.win32` empty and plans to fill it with "a confinement runner from the AppContainer/restricted-token family, shipped from its own repository on the `node-addon-landlock-run` template" — an estimated ~1,500-line new repo (the landlock-run subtree is ~1,460 lines of C/TS/scripts/tests plus docs and CI) authored and maintained in-house.
+
+Since that note was written, a maintained third-party runner has appeared: `@landstrip/landstrip` (npm, actively developed, Rust core with prebuilt per-platform `optionalDependencies`) covers Landlock + seccomp on Linux, Seatbelt on macOS, and AppContainer/restricted-user on Windows, with JSON/YAML policy input and a trap-fd denial-reporting channel. It is exec-wrapped like bwrap, so it fits the chain's `confine(argv)` shape without touching the Linux/macOS rungs.
+
+## Proposal
+
+When the Windows sandbox phase is picked up, evaluate wrapping landstrip's Windows backend as the `win32` chain runner before authoring an in-house AppContainer launcher repository. The evaluation must answer:
+
+- **Probe synthesis.** landstrip has no `--probe`; the chain's functional-probe contract would have to be synthesized from a trap run.
+- **Dialect mapping.** Denial and runner-failure stderr dialects, and fail-closed exit-code classification, need explicit mapping into the chain's vocabulary.
+- **License.** The binaries are LGPL-2.1-or-later; distribution review is required before it enters the shipped closure.
+- **Provenance.** The in-house launcher's value is byte-pinned native-CI provenance over a ~300-line reviewable C file; landstrip is a single-maintainer Rust binary set. For the *existing Linux rung* that trade is already settled — do not swap it ([sandbox note](../../implemented/feature/2026-07-06-sandbox.md) and the launcher's own migration away from a Rust dependency). For a rung we have not built, weighing third-party maintenance against a second in-house native repo is a genuinely open question.
+
+## Alternatives considered
+
+- **Build the in-house AppContainer launcher as planned.** Still the default if the evaluation fails on license, provenance, or probe fit; the cost is owning a second native security launcher repo indefinitely.
+- **Swap the Linux Landlock rung to landstrip too.** Rejected outright: sandbox correctness is a security invariant, the current launcher's reviewability and provenance chain were chosen deliberately, and it already migrated away from a Rust dependency for exactly this reason.
+
+## Acceptance criteria
+
+- Before any Windows-rung implementation starts, an evaluation records the probe, dialect, license, and provenance answers, and the go/no-go is added to the sandbox note's deferred-phases plan.
+
+## Risks
+
+- Single-maintainer supply chain in a security-critical position — the reason this is an evaluation gate, not an adoption decision.
+- The package is young; its API and packaging may churn before the Windows phase starts, so re-verify against the live registry then.
diff --git a/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md
new file mode 100644
index 0000000000..379d57e1e0
--- /dev/null
+++ b/.agents/notes/proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md
@@ -0,0 +1,34 @@
+# Agent Note: 在构建 Windows 沙箱启动器之前先评估 landstrip
+
+Status: proposed
+
+[English](2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) | 中文
+
+## 问题
+
+[沙箱决策](../../implemented/feature/2026-07-06-sandbox.md)将 `PLATFORM_CHAINS.win32` 留空,并计划用「AppContainer/受限令牌(restricted-token)家族的一个约束运行器,按 `node-addon-landlock-run` 模板从其独立仓库发布」来填充——一个估计约 1,500 行、需要自研编写并维护的新仓库(landlock-run 子树约为 1,460 行 C/TS/脚本/测试,外加文档与 CI)。
+
+自那份决策记录写成以来,出现了一个持续维护的第三方运行器:`@landstrip/landstrip`(npm 包,活跃开发中,Rust 内核,附带按平台预构建的 `optionalDependencies`)覆盖 Linux 上的 Landlock + seccomp、macOS 上的 Seatbelt,以及 Windows 上的 AppContainer/受限用户,支持 JSON/YAML 策略输入和基于 trap-fd 的拒绝上报通道。它与 bwrap 一样采用 exec 包装方式,因此无需触碰 Linux/macOS 梯级即可契合链的 `confine(argv)` 形态。
+
+## 提案
+
+当 Windows 沙箱阶段启动时,在动手编写自研 AppContainer 启动器仓库之前,先评估将 landstrip 的 Windows 后端包装为 `win32` 链运行器。评估必须回答:
+
+- **探测合成。** landstrip 没有 `--probe`;链所要求的功能探测契约必须从一次 trap 运行中合成出来。
+- **方言映射。** 拒绝与运行器失败两类 stderr 方言,以及失败即关闭(fail-closed)的退出码分类,都需要显式映射到链的词汇中。
+- **许可证。** 其二进制文件采用 LGPL-2.1-or-later 许可;在进入随产品发布的依赖闭包之前需要先做分发审查。
+- **溯源。** 自研启动器的价值在于对一个约 300 行、可审阅的 C 文件施以字节级锁定的原生 CI 溯源;而 landstrip 是单一维护者手中的一组 Rust 二进制文件。对*既有的 Linux 梯级*而言,这笔权衡早有定论——不要替换它(见[沙箱 Note](../../implemented/feature/2026-07-06-sandbox.md)以及该启动器自身摆脱 Rust 依赖的迁移)。而对一个我们尚未构建的梯级,在第三方维护与第二个自研原生仓库之间如何取舍,是一个真正悬而未决的问题。
+
+## 曾考虑的替代方案
+
+- **按原计划构建自研 AppContainer 启动器。** 若评估在许可证、溯源或探测契合度上不通过,这仍是默认选项;代价是要无限期持有第二个原生安全启动器仓库。
+- **把 Linux Landlock 梯级也换成 landstrip。** 直接否决:沙箱正确性是安全不变量,当前启动器的可审阅性与溯源链是刻意选择的结果,而且它正是出于这一原因才迁移摆脱了 Rust 依赖。
+
+## 验收标准
+
+- 在任何 Windows 梯级实现开始之前,先有一份评估记录下探测、方言、许可证与溯源问题的答案,并把「做/不做」(go/no-go)的结论加入沙箱 Note 的延后阶段计划。
+
+## 风险
+
+- 处于安全关键位置的单一维护者供应链——这正是本提案定为一道评估门禁、而非采用决定的原因。
+- 该包尚且年轻;在 Windows 阶段启动之前其 API 与打包方式可能反复变动,届时需对照线上注册表重新核验。
diff --git a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml
index 8f00d8b537..db4a8984c7 100644
--- a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml
+++ b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml
@@ -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-11-api-extractor-reports.md: f110bfe3353e65442f218336aca3e9d492ac2341
-2026-06-11-api-extractor-reports.zh.md: 8cb7353e10a8811b20ccd539de15f8e06b76e6ae
+2026-06-11-api-extractor-reports.md: 03f512992fe87ea3d0f8d51a1772ce1ec89a5c0d
+2026-06-11-api-extractor-reports.zh.md: a8180124c5e5402dce3c28c1bd8c54219d5b68fc
diff --git a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.md b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.md
index f110bfe335..03f512992f 100644
--- a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.md
+++ b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.md
@@ -4,7 +4,7 @@ Status: proposed
English | [中文](2026-06-11-api-extractor-reports.zh.md)
-> Split out from the original "Doc-sync and API reports" Agent Note (2026-06-11). Parts 1-2 (doc-block typechecking, event-taxonomy verification) shipped — see [doc-sync enforcement](../../implemented/process/2026-06-11-doc-sync-enforcement.md). This is the deferred part 3, kept as a standalone proposal.
+> Split out from the original "Doc-sync and API reports" Agent Note (2026-06-11). Parts 1-2 (doc-block typechecking, event-taxonomy verification) shipped — see [doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md). This is the deferred part 3, kept as a standalone proposal.
## Problem
diff --git a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md
index 8cb7353e10..a8180124c5 100644
--- a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md
+++ b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md
@@ -4,7 +4,7 @@ Status: proposed
[English](2026-06-11-api-extractor-reports.md) | 中文
-> 从最初的「doc-sync(文档同步门禁)与 API 报告」Agent Note(agent 决策记录)中拆出(首次提出于 2026-06-11)。第 1 至第 2 部分(文档块类型检查、事件分类体系校验)已交付,见 [doc-sync 强制](../../implemented/process/2026-06-11-doc-sync-enforcement.md)。本文是被推迟的第 3 部分,作为独立提案保留。
+> 从最初的「doc-sync(文档同步门禁)与 API 报告」Agent Note(agent 决策记录)中拆出(首次提出于 2026-06-11)。第 1 至第 2 部分(文档块类型检查、事件分类体系校验)已交付,见 [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)。本文是被推迟的第 3 部分,作为独立提案保留。
## 问题
diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml
new file mode 100644
index 0000000000..0a31f7a857
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml
@@ -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
+2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 63e3f45ab2340ee2b732da286117e25be45bed08
+2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 2348e07d58f7f0ed39a1759cc30133c8e15dbc4a
diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md
new file mode 100644
index 0000000000..63e3f45ab2
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md
@@ -0,0 +1,31 @@
+# Agent Note: Use pnpm/action-setup for symmetric CI pnpm caching
+
+Status: proposed
+
+English | [中文](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md)
+
+## Problem
+
+Five workflows repeat a hand-rolled three-step pnpm setup — `corepack enable`, `pnpm store path --silent >> $GITHUB_OUTPUT`, then `actions/cache@v4` keyed on `pnpm-lock.yaml`: `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat, serial-linux, and benchmark jobs of `ci.yml` (~40–60 YAML lines total). The maintained equivalent — `pnpm/action-setup@v4` (reads `packageManager` from package.json) plus `actions/setup-node` with `cache: pnpm` — is already proven in-repo in `landlock-run.yml`, and also insulates against corepack's removal from newer Node distributions.
+
+## Proposal
+
+Convert the symmetric-cache workflows to `pnpm/action-setup@v4` + `setup-node` `cache: pnpm`. Explicitly do NOT convert:
+
+- the three enterprise-runner PR jobs in `ci.yml` — they deliberately use `actions/cache/restore` only, keeping cache compression/upload off the paid latency-critical path, an asymmetry `setup-node`'s cache cannot express;
+- the Windows job, which deliberately skips the store cache.
+
+## Alternatives considered
+
+- **Keep the hand-rolled steps.** They work, but they are five drifting copies of setup boilerplate, and the corepack dependency is a known future break.
+- **Convert everything including the enterprise jobs.** Rejected: the restore-only asymmetry is a documented latency decision in `ci.yml`'s comments; erasing it to unify tooling inverts the priority.
+
+## Acceptance criteria
+
+- The five symmetric workflows set up pnpm via the actions; one cold run per lane repopulates the new cache-key format, after which cache hit rates match the old steps.
+- The enterprise-runner PR jobs and the Windows job are untouched.
+
+## Risks
+
+- Cache-key format changes once (one cold run per lane).
+- A third-party action in more workflows; it is already trusted in-repo (`landlock-run.yml`) and is the pnpm team's official action.
diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md
new file mode 100644
index 0000000000..2348e07d58
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md
@@ -0,0 +1,31 @@
+# Agent Note: 用 pnpm/action-setup 实现对称的 CI pnpm 缓存
+
+Status: proposed
+
+[English](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md) | 中文
+
+## 问题
+
+五个工作流重复着同一套手写(hand-rolled)的三步 pnpm 设置——`corepack enable`、`pnpm store path --silent >> $GITHUB_OUTPUT`、再加以 `pnpm-lock.yaml` 为缓存键的 `actions/cache@v4`:`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`,以及 `ci.yml` 的 node-compat、serial-linux 与 benchmark 作业(合计约 40–60 行 YAML)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——已在仓库内的 `landlock-run.yml` 中得到验证,同时还能隔绝 corepack 被从较新 Node 发行版中移除的影响。
+
+## 提案
+
+将各对称缓存工作流改为 `pnpm/action-setup@v4` + `setup-node` `cache: pnpm`。以下明确不做转换:
+
+- `ci.yml` 中运行在企业 runner 上的三个 PR(Pull Request)作业——它们刻意只用 `actions/cache/restore`,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外,这种不对称是 `setup-node` 的缓存无法表达的;
+- Windows 作业,它刻意跳过 store 缓存。
+
+## 曾考虑的替代方案
+
+- **保留手写步骤。** 它们能用,但那是五份会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。
+- **连企业作业在内全部转换。** 否决:只恢复不上传(restore-only)的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。
+
+## 验收标准
+
+- 五个对称工作流经由上述 action 完成 pnpm 设置;每条泳道各跑一次冷运行以重建新的缓存键格式,此后缓存命中率与旧步骤持平。
+- 企业 runner 上的 PR 作业与 Windows 作业保持原样不动。
+
+## 风险
+
+- 缓存键格式变更一次(每条泳道各一次冷运行)。
+- 更多工作流引入一个第三方 action;它已在仓库内获得信任(`landlock-run.yml`),且是 pnpm 团队的官方 action。
diff --git a/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.i18n.yaml b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.i18n.yaml
new file mode 100644
index 0000000000..c3d518c176
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.i18n.yaml
@@ -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
+2026-07-26-remove-packed-session-fixture-migrator.md: 0a29ef98828ac07d291392d637b0508937c9a9a6
+2026-07-26-remove-packed-session-fixture-migrator.zh.md: 64b994855a7e92d5b0922884b6c66df1b82b6d90
diff --git a/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md
new file mode 100644
index 0000000000..0a29ef9882
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md
@@ -0,0 +1,38 @@
+# Agent Note: Remove the packed-session fixture branch migrator
+
+Status: proposed
+
+English | [中文](2026-07-26-remove-packed-session-fixture-migrator.zh.md)
+
+## Problem
+
+The repository's default writers and snapshot check keep session fixtures in the canonical packed-row layout. `pnpm run migrate:packed-session-fixtures` remains alongside that permanent enforcement only so in-flight branches carrying older fixture edits can merge current `master` and mechanically converge without re-recording model output.
+
+Once every such branch is merged, closed, or already canonical, the write command and its branch-convergence instructions have no continuing owner. Keeping a mutation command after its transition ends adds a second apparent maintenance path beside the permanent read-only snapshot check.
+
+## Proposal
+
+Remove the temporary `scripts/migrate-packed-session-fixtures.ts` CLI and the root `migrate:packed-session-fixtures` package command after a live inventory confirms that no open pull request still needs to convert session-format JSONL. Remove the transitional command links from the testing policy, the ACP snapshot README, and the implemented packed-row Agent Note in the same change; replace the command-specific remediation text in `scripts/session-fixture-layout.snapshot.ts` with command-independent canonical-layout guidance.
+
+Retain `scripts/session-fixture-layout.ts`, its unit tests, and `scripts/session-fixture-layout.snapshot.ts`. They define and enforce the permanent canonical layout; only the branch-facing writer is temporary.
+
+Before removing the command, each affected branch merges the current `master`, runs the migrator once, commits the resulting fixture-only rewrite separately, and verifies that the repository-wide snapshot layout check passes. Closed or superseded branches require no migration.
+
+## Alternatives considered
+
+**Keep the command indefinitely.** This makes old fixture conversion convenient, but it leaves a repository-wide mutation tool after the only known migration window closes. The read-only gate already supplies the durable behavior and diagnostic.
+
+**Remove the canonicalization module with the CLI.** The module is not transition residue: snapshot CI uses it to discover future fixtures, decode mixed physical records, and compare them with the canonical packed representation. Removing it would also remove enforcement.
+
+**Delete the command immediately when packed rows reach `master`.** Older open branches would then need ad hoc scripts or manual snapshot regeneration after retargeting, increasing conflict risk and making decoded-event preservation harder to review.
+
+## Acceptance criteria
+
+- A live open-PR inventory finds no branch with session-format JSONL changes that still depends on the temporary migration command.
+- The temporary CLI, root package command, every branch-convergence link, and the command-specific gate diagnostic are absent; the permanent canonicalizer, unit tests, and snapshot check remain.
+- `pnpm run test:snapshot`, `pnpm run doc-sync`, lint, and whitespace validation pass without the temporary command.
+- Current documentation describes only the packed default and permanent canonical-layout enforcement.
+
+## Risks
+
+An incomplete open-branch inventory could strand a contributor with a large unpacked fixture conflict after the command disappears. The removal therefore depends on live pull-request evidence, not elapsed time. Retaining the command too long has a smaller operational cost but obscures which mechanism is permanent.
diff --git a/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md
new file mode 100644
index 0000000000..64b994855a
--- /dev/null
+++ b/.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md
@@ -0,0 +1,38 @@
+# Agent Note: 移除打包会话 fixture 分支迁移器
+
+Status: proposed
+
+[English](2026-07-26-remove-packed-session-fixture-migrator.md) | 中文
+
+## 问题
+
+仓库的默认写入器和快照检查会使会话 fixture(测试前置数据)保持规范打包行布局。在永久强制机制之外仍保留 `pnpm run migrate:packed-session-fixtures`,唯一原因是让携带旧版 fixture 改动的在途分支可以合并当前 `master`,并在不重新录制模型输出的情况下通过机械转换收敛。
+
+一旦每个此类分支均已合并、关闭或符合规范,写入命令及其分支收敛指引便不再有持续维护者。过渡结束后继续保留会修改仓库内容的命令,会在永久只读快照检查旁增加第二条看似有效的维护路径。
+
+## 提案
+
+最新清单确认不再有任何开放 PR(Pull Request)需要转换会话格式 JSONL 后,移除临时 CLI `scripts/migrate-packed-session-fixtures.ts`,以及根包(package)提供的 `migrate:packed-session-fixtures` 命令。在同一变更中,移除测试政策、ACP 快照 README 和已实现打包行 Agent Note(agent 决策记录)中指向该过渡命令的链接,并将 `scripts/session-fixture-layout.snapshot.ts` 中仅适用于该命令的修复指引替换为与具体命令无关的规范布局指引。
+
+保留 `scripts/session-fixture-layout.ts`、其单元测试和 `scripts/session-fixture-layout.snapshot.ts`。它们定义并强制执行永久规范布局;只有面向分支的写入器是临时机制。
+
+移除命令前,每个受影响分支都要合并当前 `master`,运行一次迁移器,单独提交由此产生的仅 fixture 重写,并验证仓库级快照布局检查通过。已关闭或被取代的分支无需迁移。
+
+## 曾考虑的替代方案
+
+**无限期保留该命令。** 这会让旧 fixture 转换更方便,但也会在唯一已知迁移窗口关闭后,留下一个仓库级写入工具。只读门禁已经提供可长期保留的行为与诊断。
+
+**随 CLI 一同移除规范布局转换模块。** 该模块不是过渡残留:快照 CI 使用它发现未来 fixture、解码混合物理记录,并与规范打包表示进行比较。移除该模块也会移除强制机制。
+
+**打包行进入 `master` 后立即删除命令。** 较旧的开放分支在重新定向后,只能使用临时脚本或手动重新生成快照,这会增加冲突风险,也会让解码事件保真度更难评审。
+
+## 验收标准
+
+- 最新开放 PR 清单未发现任何仍依赖临时迁移命令处理会话格式 JSONL 改动的分支。
+- 临时 CLI、根包命令、所有分支收敛链接与仅适用于该命令的门禁诊断均不存在;永久规范布局转换器、单元测试和快照检查仍然保留。
+- `pnpm run test:snapshot`、`pnpm run doc-sync`、lint 和空白校验在没有临时命令的情况下通过。
+- 当前文档仅描述打包默认值和永久规范布局强制机制。
+
+## 风险
+
+若开放分支清单不完整,命令消失后,贡献者可能会受困于大规模非打包 fixture 冲突。因此,移除操作取决于实时 PR 证据,而不是经过的时间。保留命令过久的运维成本较低,但会模糊哪一种机制才是永久机制。
diff --git a/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml
new file mode 100644
index 0000000000..0219885a7c
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml
@@ -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
+2026-07-26-consolidate-gate-scripts-on-existing-deps.md: 0bf32e01ea407e2718f8ec39ca962587a37df9cc
+2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md: ba9f157a61ffdc415acf9b2a61857026fc2c8bf1
diff --git a/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md
new file mode 100644
index 0000000000..0bf32e01ea
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md
@@ -0,0 +1,38 @@
+# Agent Note: Consolidate gate scripts on already-present deps and builtins
+
+Status: proposed
+
+English | [中文](2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md)
+
+## Problem
+
+The `scripts/` gates mostly use the right tools (`node:fs` `globSync` in 15+ gates, mdast/micromark in the markdown gates), but a handful of stragglers hand-roll what a sibling gate already does with an existing dependency or builtin:
+
+- **Duplicated fence scanners.** `scripts/md-fences.ts` (~55 lines, consumed by `doc-typecheck.ts`) and `extractEquivBlocks` in `scripts/verify-type-equiv.ts` (~39 lines) are two copies of the same regex line-scanner for fenced code blocks, while `scripts/verify-mermaid.ts` already extracts fences by visiting mdast `code` nodes via the shared `scripts/markdown.ts` helpers — and `markdownProseLines` in `markdown.ts` itself parses to mdast but then hand-tracks fence state with a second regex. The regex scanners only recognize backtick fences at column 0, so they silently disagree with the mdast-based gates on tilde and indented fences.
+- **Hand-rolled argv parsing.** `parseOptions` in `scripts/publint-all.ts` and its near-identical copy in `scripts/verify-built-package-invariants.mjs` (~26 lines) step argv indexes manually, while sibling scripts (`verify-runtime-closure.ts`, `build-exe-for-python-sdk.ts`, `packages/sdk/scripts/src/args.ts`) already use the `node:util` `parseArgs` builtin.
+- **Hand-rolled directory walks.** Five sites re-derive nested `readdirSync` walks that `globSync` covers: `verify-runtime-closure.ts` (packages + vendor manifests), `dev-web.ts` `discoverPluginDirs`, `verify-package-paths.ts` `realPackageNames`, `verify-client-domain-graph.ts` `listSources`, and `publint-all.ts` `addPath` (~55–65 lines total). `scripts/package-invariants.ts` shows the one-line `globSync` template.
+
+No new dependency is needed anywhere; every replacement is an existing devDep or a Node builtin.
+
+## Proposal
+
+- Extract a shared ~10–15-line mdast fence helper (visiting `code` nodes for `lang`, `meta`, `value`, `position.start.line`) into `scripts/markdown.ts`; rewrite `doc-typecheck.ts` and `verify-type-equiv.ts` onto it; delete `md-fences.ts` and the duplicated scanner; drop the redundant fence regex in `markdownProseLines`.
+- Replace both `parseOptions` copies with `parseArgs`.
+- Replace the five straggler walks with `globSync`. Keep the walks in `check-workspace-constraints.ts` and `clean.ts`: they need dirent-level detail to diagnose malformed trees, which glob-by-pattern cannot report.
+
+## Alternatives considered
+
+- **A new glob/walking dependency (`tinyglobby`, `fdir`).** Rejected: the builtin already won repo-wide; these are stragglers, not a gap.
+- **`p-map` for `publint-all.ts`'s ~19-line ordered worker pool.** Deliberately left out: one new devDep for one small deletion is at the edge of the [dependency policy](../../implemented/process/2026-07-26-dependencies-over-hand-rolling.md) bar, and the pool's requirements (bounded workers, deterministic order, env override) are documented in the [parallel-gates note](../../implemented/process/2026-07-06-parallel-pre-push-gates.md). Fold it in only if `p-map` earns a second consumer.
+- **Leaving the fence scanners.** Rejected: two drifting copies of a parser beside a third correct implementation is exactly the duplication the shared `markdown.ts` helper exists to prevent, and the column-0-backtick-only limitation is a latent inconsistency between sibling gates.
+
+## Acceptance criteria
+
+- `md-fences.ts` is gone; `doc-typecheck` and `verify-type-equiv` extract fences through `scripts/markdown.ts`; `pnpm run doc-sync` passes with unchanged results on the current tree (any delta traces to a fence shape the regex scanners mishandled).
+- Both CLIs parse via `parseArgs`; unknown options still fail loud.
+- The five walk sites use `globSync`; the gates they feed pass unchanged.
+
+## Risks
+
+- Behavioral deltas on pathological markdown: mdast honors tilde/indented fences the regex scanners ignored, so `doc-typecheck`'s opt-out ratio could shift if any stray fence shape exists in the docs tree; verify by running `doc-sync` before/after.
+- `parseArgs` keeps the last value of a duplicated option instead of erroring — a dev-tool edge case the tests don't pin. (Strict mode still rejects a `--`-prefixed token where a value is expected, matching the current parsers.)
diff --git a/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md
new file mode 100644
index 0000000000..ba9f157a61
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md
@@ -0,0 +1,38 @@
+# Agent Note: 把门禁脚本统一到已有依赖与内置模块上
+
+Status: proposed
+
+[English](2026-07-26-consolidate-gate-scripts-on-existing-deps.md) | 中文
+
+## 问题
+
+`scripts/` 下的门禁大多已经在用正确的工具(15 个以上的门禁使用 `node:fs` 的 `globSync`,markdown 门禁使用 mdast/micromark),但少数几个掉队的脚本仍在手写同类门禁早已用既有依赖或内置模块完成的事情:
+
+- **重复的围栏扫描器。**`scripts/md-fences.ts`(约 55 行,由 `doc-typecheck.ts` 消费)和 `scripts/verify-type-equiv.ts` 中的 `extractEquivBlocks`(约 39 行)是同一个围栏代码块正则行扫描器的两份拷贝,而 `scripts/verify-mermaid.ts` 已经通过共享的 `scripts/markdown.ts` 辅助函数访问 mdast `code` 节点来提取代码围栏;`markdown.ts` 自己的 `markdownProseLines` 也是先解析成 mdast,再用第二个正则手工跟踪围栏状态。这两个正则扫描器只识别第 0 列的反引号围栏,因此在波浪线围栏和缩进围栏上与基于 mdast 的门禁悄悄不一致。
+- **手写的 argv 解析。**`scripts/publint-all.ts` 中的 `parseOptions` 和 `scripts/verify-built-package-invariants.mjs` 中与之几乎相同的拷贝(约 26 行)手工推进 argv 下标,而同类脚本(`verify-runtime-closure.ts`、`build-exe-for-python-sdk.ts`、`packages/sdk/scripts/src/args.ts`)已经在使用 `node:util` 的内置 `parseArgs`。
+- **手写的目录遍历。**五处代码各自重写了 `globSync` 已覆盖的嵌套 `readdirSync` 遍历:`verify-runtime-closure.ts` 对 packages 与 vendor manifest(元数据清单)的扫描、`dev-web.ts` 的 `discoverPluginDirs`、`verify-package-paths.ts` 的 `realPackageNames`、`verify-client-domain-graph.ts` 的 `listSources`,以及 `publint-all.ts` 的 `addPath`(合计约 55–65 行)。`scripts/package-invariants.ts` 展示了一行式的 `globSync` 模板。
+
+所有替换都不需要引入新依赖;每一处替换用的都是既有的 devDependency 或 Node 内置模块。
+
+## 提案
+
+- 在 `scripts/markdown.ts` 中提取一个约 10–15 行的共享 mdast 围栏辅助函数(访问 `code` 节点,读取 `lang`、`meta`、`value`、`position.start.line`);把 `doc-typecheck.ts` 和 `verify-type-equiv.ts` 改写到它上面;删除 `md-fences.ts` 和重复的扫描器;去掉 `markdownProseLines` 中冗余的围栏正则。
+- 用 `parseArgs` 替换两份 `parseOptions` 拷贝。
+- 用 `globSync` 替换那五处掉队的目录遍历。保留 `check-workspace-constraints.ts` 和 `clean.ts` 中的遍历:它们需要 dirent 级别的细节来诊断结构异常的目录树,按模式匹配的 glob 报告不了这些信息。
+
+## 曾考虑的替代方案
+
+- **新的 glob/目录遍历依赖(`tinyglobby`、`fdir`)。**不予采纳:内置模块已在全仓库范围内胜出;这几处只是掉队者,不是能力缺口。
+- **用 `p-map` 替换 `publint-all.ts` 中约 19 行的有序 worker 池。**刻意未纳入:为一次小删除引入一个新 devDependency,正处在[依赖策略](../../implemented/process/2026-07-26-dependencies-over-hand-rolling.md)门槛的边缘,而且该池的需求(worker 数量有界、确定性顺序、环境变量覆盖)已记录在[并行 pre-push 门禁决策记录](../../implemented/process/2026-07-06-parallel-pre-push-gates.md)中。仅当 `p-map` 赢得第二个消费方时再顺带纳入。
+- **保留这两个围栏扫描器。**不予采纳:在第三个正确实现旁边放着两份逐渐漂移的解析器拷贝,正是共享的 `markdown.ts` 辅助函数要防止的那种重复;「只认第 0 列反引号」的限制也是同类门禁之间的潜在不一致。
+
+## 验收标准
+
+- `md-fences.ts` 已删除;`doc-typecheck` 与 `verify-type-equiv` 通过 `scripts/markdown.ts` 提取代码围栏;`pnpm run doc-sync` 在当前代码树上通过且结果不变(如有差异,必须能追溯到正则扫描器处理有误的某种围栏形态)。
+- 两个 CLI 都改用 `parseArgs` 解析;未知选项仍然大声失败。
+- 五处遍历代码改用 `globSync`;它们供给的门禁保持原样通过。
+
+## 风险
+
+- 病态 markdown 上的行为差异:mdast 会承认正则扫描器忽略的波浪线围栏和缩进围栏,因此如果文档树中存在任何零散的此类围栏形态,`doc-typecheck` 的 opt-out 比例可能变化;应在改动前后分别运行 `doc-sync` 加以验证。
+- `parseArgs` 对重复出现的选项保留最后一个值而不报错——一个测试未固定的开发工具边缘用例。(严格模式下,需要取值处遇到以 `--` 开头的 token 仍会拒绝,与现有解析器行为一致。)
diff --git a/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.i18n.yaml
new file mode 100644
index 0000000000..c486815180
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.i18n.yaml
@@ -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
+2026-07-26-eventsource-parser-for-deepseek-sse.md: 8a93b7f6c7aa0d428f25e87c44e1d29e884ecc81
+2026-07-26-eventsource-parser-for-deepseek-sse.zh.md: b16109d9458f487c7e463cf02e6b2d22fbbde015
diff --git a/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md
new file mode 100644
index 0000000000..8a93b7f6c7
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md
@@ -0,0 +1,33 @@
+# Agent Note: Replace the hand-rolled SSE parser in llm-deepseek with eventsource-parser
+
+Status: proposed
+
+English | [中文](2026-07-26-eventsource-parser-for-deepseek-sse.zh.md)
+
+## Problem
+
+`packages/llm/llm-deepseek/src/sse.ts` hand-implements Server-Sent Events parsing: a streaming `TextDecoder`, event-block splitting on `\r?\n\r?\n`, `data:` payload extraction and joining, comment/field skipping, the `[DONE]` sentinel, a `STREAM_CLOSED` error on EOF without it, and a flush of a final unterminated event block. The file is ~67 lines with ~108 lines of dedicated tests (`tests/sse.spec.ts`) re-proving SSE spec behavior — UTF-8 split across chunks, CRLF handling, multi-`data:` joining, no-space-after-colon — that a maintained parser already guarantees. Its only consumer is `adapter.ts` (`yield* translate(parseSse(response.body))`).
+
+This is exactly the surface `eventsource-parser` owns: the de-facto standard SSE parser (it underlies the Vercel AI SDK and the MCP SDK), zero-dependency, actively maintained, and already present in this repo's lockfile transitively via `@modelcontextprotocol/sdk` — so adopting it directly adds no new supply-chain surface in practice.
+
+## Proposal
+
+Replace `sse.ts` with `EventSourceParserStream` from `eventsource-parser/stream`: `response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream())`, keeping only the DeepSeek protocol shim (~10–25 lines): yield each event's `data`, terminate on `[DONE]`, and throw `LlmError('STREAM_CLOSED')` when the stream ends without the sentinel. All required builtins (`TextDecoderStream`, `pipeThrough`, async-iterable `ReadableStream`) exist at the Node ^22.19 engine floor. Delete the spec-conformance tests; keep the `[DONE]`/`STREAM_CLOSED`/EOF contract tests. Add `eventsource-parser` to `llm-deepseek`'s dependencies (its second runtime dep after schemastery). Update the [twin-adapters note](../../implemented/architecture/2026-06-13-twin-llm-adapters.md) and the `dsh-llm` JSDoc that brand this adapter "hand-rolled fetch + SSE parsing" in the same PR.
+
+The library also strips a leading BOM (the hand-rolled parser would fail to match `data:` after one) and offers `maxBufferSize` hardening the current parser lacks.
+
+## Alternatives considered
+
+- **Keep the hand-rolled parser.** Defensible under the [twin-adapters decision](../../implemented/architecture/2026-06-13-twin-llm-adapters.md): the adapter is deliberately the hand-rolled design-verification twin of the pi-ai adapter. But the note's load-bearing distinction is owning the fetch/translate internals versus delegating to a full provider SDK; a ~700-byte SSE micro-parser is transport plumbing, not the design under verification. Whether that reading stands is the twin-note owner's call — this proposal explicitly needs their sign-off.
+- **`createParser({onEvent})` callback API instead of the stream.** Works fed by a manual `TextDecoder` loop, but the `pipeThrough` composition deletes more of the hand-rolled code.
+
+## Acceptance criteria
+
+- `sse.ts`'s parsing internals are gone; the remaining shim only encodes the DeepSeek `[DONE]`/`STREAM_CLOSED` protocol.
+- `llm-deepseek` unit tests and the real-API e2e suite pass; keyless snapshots are unchanged (parsing is transport-internal and payload extraction is equivalent).
+- The twin-adapters note and `dsh-llm` JSDoc no longer claim hand-rolled SSE parsing.
+
+## Risks
+
+- One deliberate robustness deviation is lost: the hand-rolled parser flushes a final event block that lacks its terminating blank line, and `tests/sse.spec.ts` pins that a trailing `data: [DONE]` without `\n\n` still yields DONE. eventsource-parser is spec-strict and only dispatches on the blank line, so that shape becomes `STREAM_CLOSED`. Real providers and `dsh-llm-mock-server` always terminate events properly, so the pinned behavior is a robustness nicety, not an observed provider shape — drop the test, or keep a tiny buffer-tail check if the deviation is judged load-bearing.
+- Dilutes the documented "hand-rolled" identity of the twin adapter; mitigated by updating the note in the same change rather than leaving the claim stale.
diff --git a/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.zh.md b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.zh.md
new file mode 100644
index 0000000000..b16109d945
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.zh.md
@@ -0,0 +1,33 @@
+# Agent Note: 用 eventsource-parser 替换 llm-deepseek 中手写的 SSE 解析器
+
+Status: proposed
+
+[English](2026-07-26-eventsource-parser-for-deepseek-sse.md) | 中文
+
+## 问题
+
+`packages/llm/llm-deepseek/src/sse.ts` 手写实现了 SSE(Server-Sent Events)解析:一个流式 `TextDecoder`、按 `\r?\n\r?\n` 切分事件块、提取并拼接 `data:` 载荷、跳过注释与其他字段、`[DONE]` 哨兵、在未见哨兵即 EOF 时抛出 `STREAM_CLOSED` 错误,以及对最后一个未终结事件块的 flush。该文件约 67 行,另有约 108 行专属测试(`tests/sse.spec.ts`)重复验证 SSE 规范行为——UTF-8 字符被切分到多个分片、CRLF 处理、多条 `data:` 拼接、冒号后无空格——而这些行为,持续维护的解析器早已有保证。它唯一的消费方是 `adapter.ts`(`yield* translate(parseSse(response.body))`)。
+
+这恰好是 `eventsource-parser` 负责的接口面:事实标准的 SSE 解析器(Vercel AI SDK 和 MCP SDK 都构建在它之上),零依赖,持续维护,并且已通过 `@modelcontextprotocol/sdk` 作为传递依赖出现在本仓库的 lockfile 中——因此直接采用它实际上不增加新的供应链接触面。
+
+## 提案
+
+用 `eventsource-parser/stream` 的 `EventSourceParserStream` 替换 `sse.ts`:`response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream())`,只保留 DeepSeek 协议垫层(约 10–25 行):逐个产出事件的 `data`,遇到 `[DONE]` 终止,流在未见哨兵时结束则抛出 `LlmError('STREAM_CLOSED')`。所需的全部内置能力(`TextDecoderStream`、`pipeThrough`、可异步迭代的 `ReadableStream`)在 Node ^22.19 引擎下限即已存在。删除规范符合性测试;保留 `[DONE]`/`STREAM_CLOSED`/EOF 契约测试。将 `eventsource-parser` 加入 `llm-deepseek` 的依赖(这是它继 schemastery 之后的第二个运行时依赖)。在同一个 PR(Pull Request)中更新[孪生适配器 Agent Note(agent 决策记录)](../../implemented/architecture/2026-06-13-twin-llm-adapters.md)以及 `dsh-llm` 中把该适配器标为「手写 fetch + SSE 解析」的 JSDoc。
+
+该库还会剥离开头的 BOM(手写解析器在 BOM 之后会无法匹配 `data:`),并提供当前解析器缺少的 `maxBufferSize` 加固能力。
+
+## 曾考虑的替代方案
+
+- **保留手写解析器。** 依据[孪生适配器决策](../../implemented/architecture/2026-06-13-twin-llm-adapters.md),这一选择有辩护余地:该适配器有意作为 pi-ai 适配器的手写设计验证孪生体。但那份 Agent Note 起支撑作用的区分在于「自行持有 fetch/translate 内部实现」与「委托给完整的提供方 SDK」;一个约 700 字节的 SSE 微型解析器属于传输层管道,不是被验证的设计本身。这一解读是否成立由孪生 Agent Note 的所有者裁定——本提案明确需要其签署确认。
+- **改用 `createParser({onEvent})` 回调 API 而非流。** 配合手动的 `TextDecoder` 循环可以工作,但 `pipeThrough` 组合方式能删除更多手写代码。
+
+## 验收标准
+
+- `sse.ts` 的解析内部实现消失;剩下的垫层只编码 DeepSeek 的 `[DONE]`/`STREAM_CLOSED` 协议。
+- `llm-deepseek` 单元测试与真实 API 的 e2e 套件通过;无密钥快照不变(解析属于传输层内部,载荷提取等价)。
+- 孪生适配器 Agent Note 与 `dsh-llm` 的 JSDoc 不再声称手写 SSE 解析。
+
+## 风险
+
+- 会失去一处有意为之的健壮性偏离:手写解析器会 flush 缺少终结空行的最后一个事件块,`tests/sse.spec.ts` 固定了「末尾的 `data: [DONE]` 即使没有 `\n\n` 也仍产出 DONE」这一行为。eventsource-parser 严格遵循规范,只在空行处分发事件,因此这种形态会变成 `STREAM_CLOSED`。真实提供方和 `dsh-llm-mock-server` 总是正确终结事件,所以被固定的行为只是健壮性上的锦上添花,并非实际观测到的提供方形态:可以删除该测试;若判定该偏离确有支撑作用,也可以保留一个小型的缓冲区尾部检查。
+- 稀释了孪生适配器有文档记录的「手写」身份;缓解方式是在同一次变更中更新那份 Agent Note,而不是让声明陈旧下去。
diff --git a/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.i18n.yaml
new file mode 100644
index 0000000000..ced514a423
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.i18n.yaml
@@ -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
+2026-07-26-turndown-for-tool-web-html-markdown.md: 7f25e51bf6e6fc9313a880abee737bca80a472af
+2026-07-26-turndown-for-tool-web-html-markdown.zh.md: 3a59b08e13fd392e4f34ac543f32f5b4648f3c1c
diff --git a/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.md b/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.md
new file mode 100644
index 0000000000..7f25e51bf6
--- /dev/null
+++ b/.agents/notes/proposed/simplification/2026-07-26-turndown-for-tool-web-html-markdown.md
@@ -0,0 +1,32 @@
+# Agent Note: Replace tool-web's regex HTML-to-markdown converter with turndown
+
+Status: proposed
+
+English | [中文](2026-07-26-turndown-for-tool-web-html-markdown.zh.md)
+
+## Problem
+
+`packages/web/tool-web/src/html.ts` (~86 lines, ~40 lines of dedicated tests) converts fetched HTML to markdown with regexes: strip script/style/noscript/comments, convert ``/``/`