Browse Source

Merge latest master into xtr/durable-inbox-web-recovery

_Kerman 3 tuần trước cách đây
mục cha
commit
cb999713d1
100 tập tin đã thay đổi với 738 bổ sung và 30 xóa
  1. 2 2
      .agents/notes/README.i18n.yaml
  2. 1 1
      .agents/notes/README.md
  3. 1 1
      .agents/notes/README.zh.md
  4. 6 0
      .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.i18n.yaml
  5. 1 0
      .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.md
  6. 1 0
      .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.zh.md
  7. 6 0
      .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.i18n.yaml
  8. 1 0
      .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.md
  9. 1 0
      .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.zh.md
  10. 6 0
      .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml
  11. 1 0
      .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.md
  12. 1 0
      .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.zh.md
  13. 6 0
      .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.i18n.yaml
  14. 1 0
      .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md
  15. 1 0
      .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md
  16. 6 0
      .agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml
  17. 53 0
      .agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.md
  18. 53 0
      .agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md
  19. 6 0
      .agents/notes/archived/architecture/2026-06-20-branded-ids.i18n.yaml
  20. 66 0
      .agents/notes/archived/architecture/2026-06-20-branded-ids.md
  21. 66 0
      .agents/notes/archived/architecture/2026-06-20-branded-ids.zh.md
  22. 6 0
      .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.i18n.yaml
  23. 1 0
      .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md
  24. 1 0
      .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md
  25. 6 0
      .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.i18n.yaml
  26. 1 0
      .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.md
  27. 1 0
      .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.zh.md
  28. 6 0
      .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.i18n.yaml
  29. 1 0
      .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.md
  30. 1 0
      .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md
  31. 6 0
      .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.i18n.yaml
  32. 1 0
      .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.md
  33. 1 0
      .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.zh.md
  34. 6 0
      .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.i18n.yaml
  35. 1 0
      .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.md
  36. 1 0
      .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.zh.md
  37. 6 0
      .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml
  38. 1 0
      .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md
  39. 1 0
      .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md
  40. 6 0
      .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.i18n.yaml
  41. 1 0
      .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.md
  42. 1 0
      .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.zh.md
  43. 6 0
      .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.i18n.yaml
  44. 1 0
      .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.md
  45. 1 0
      .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.zh.md
  46. 6 0
      .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.i18n.yaml
  47. 1 0
      .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.md
  48. 1 0
      .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.zh.md
  49. 6 0
      .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml
  50. 1 0
      .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.md
  51. 1 0
      .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md
  52. 6 0
      .agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml
  53. 106 0
      .agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.md
  54. 106 0
      .agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.zh.md
  55. 6 0
      .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.i18n.yaml
  56. 1 0
      .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.md
  57. 1 0
      .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.zh.md
  58. 6 0
      .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
  59. 1 0
      .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
  60. 1 0
      .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
  61. 6 0
      .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.i18n.yaml
  62. 1 0
      .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.md
  63. 1 0
      .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.zh.md
  64. 6 0
      .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.i18n.yaml
  65. 1 0
      .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.md
  66. 1 0
      .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.zh.md
  67. 6 0
      .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.i18n.yaml
  68. 2 1
      .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.md
  69. 2 1
      .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md
  70. 6 0
      .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml
  71. 6 4
      .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
  72. 6 4
      .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md
  73. 6 0
      .agents/notes/archived/architecture/2026-07-26-job-registry-seam.i18n.yaml
  74. 2 1
      .agents/notes/archived/architecture/2026-07-26-job-registry-seam.md
  75. 2 1
      .agents/notes/archived/architecture/2026-07-26-job-registry-seam.zh.md
  76. 6 0
      .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.i18n.yaml
  77. 2 1
      .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.md
  78. 2 1
      .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md
  79. 6 0
      .agents/notes/archived/architecture/2026-07-26-subprocess-seam.i18n.yaml
  80. 1 0
      .agents/notes/archived/architecture/2026-07-26-subprocess-seam.md
  81. 1 0
      .agents/notes/archived/architecture/2026-07-26-subprocess-seam.zh.md
  82. 6 0
      .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.i18n.yaml
  83. 1 0
      .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.md
  84. 1 0
      .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.zh.md
  85. 6 0
      .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.i18n.yaml
  86. 5 4
      .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.md
  87. 5 4
      .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.zh.md
  88. 6 0
      .agents/notes/archived/architecture/2026-07-28-user-settings-seam.i18n.yaml
  89. 1 0
      .agents/notes/archived/architecture/2026-07-28-user-settings-seam.md
  90. 1 0
      .agents/notes/archived/architecture/2026-07-28-user-settings-seam.zh.md
  91. 6 0
      .agents/notes/archived/architecture/2026-07-29-package-regrouping.i18n.yaml
  92. 3 2
      .agents/notes/archived/architecture/2026-07-29-package-regrouping.md
  93. 3 2
      .agents/notes/archived/architecture/2026-07-29-package-regrouping.zh.md
  94. 6 0
      .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.i18n.yaml
  95. 1 0
      .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.md
  96. 1 0
      .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.zh.md
  97. 6 0
      .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.i18n.yaml
  98. 1 0
      .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.md
  99. 1 0
      .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.zh.md
  100. 6 0
      .agents/notes/archived/architecture/2026-07-30-client-locale-full-rollout.i18n.yaml

+ 2 - 2
.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 .agents/notes/README.md
-README.md: ae8e4724d610c97d74910d7dec5c95af69e93281
-README.zh.md: 8cecc7068d9efc5e2ebb78d2301d03633f70f137
+README.md: 8bf1adf875b54eb4580b4d189dfeed07c9f671c0
+README.zh.md: ff9bdb358d584d4f4ad0aea295c31d8d8fff9376

+ 1 - 1
.agents/notes/README.md

@@ -20,7 +20,7 @@ The active lifecycle tree is the working inventory: browse its lifecycle/class f
 
 ## Classification
 
-Each Agent Note belongs to one path-encoded class from the closed set in `scripts/agent-note-tree.ts`; the classification gate rejects other folders. Adding a class requires updating the canonical set and this section. See the [classification Agent Note](implemented/process/2026-06-20-agent-note-classification.md).
+Each Agent Note belongs to one path-encoded class from the closed set in `scripts/agent-note-tree.ts`; the classification gate rejects other folders. Adding a class requires updating the canonical set and this section.
 
 | Class | What it covers |
 |---|---|

+ 1 - 1
.agents/notes/README.zh.md

@@ -22,7 +22,7 @@
 
 ## 分类
 
-每份 Agent Note 属于 `scripts/agent-note-tree.ts` 中封闭集合里的一个路径编码类别;分类门禁拒绝其他文件夹。新增类别需要同时更新规范集合与本节。见[分类 Agent Note](implemented/process/2026-06-20-agent-note-classification.zh.md)。
+每份 Agent Note 属于 `scripts/agent-note-tree.ts` 中封闭集合里的一个路径编码类别;分类门禁拒绝其他文件夹。新增类别需要同时更新规范集合与本节。
 
 | 类别 | 覆盖范围 |
 |---|---|

+ 6 - 0
.agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.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 .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.md
+2026-06-11-runtime-arg-validation.md: 22283c76719a26f733a8125bf263810d53a92c3c
+2026-06-11-runtime-arg-validation.zh.md: f748b7e01b233edb8b3303898cc339dfacd16967

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.md → .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.md

@@ -1,6 +1,7 @@
 # Agent Note: Runtime arg validation at the model boundary
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-06-11-runtime-arg-validation.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.zh.md → .agents/notes/archived/architecture/2026-06-11-runtime-arg-validation.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 模型边界处的运行时参数校验
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-06-11-runtime-arg-validation.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.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 .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.md
+2026-06-11-structured-error-taxonomy.md: a5c1c6e6120eefd3bc96090cfa4536fb83afa796
+2026-06-11-structured-error-taxonomy.zh.md: 54a937c5595aa5645e4cc0f66a6d1a2efc232588

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.md → .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.md

@@ -1,6 +1,7 @@
 # Agent Note: Structured error taxonomy
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-06-11-structured-error-taxonomy.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.zh.md → .agents/notes/archived/architecture/2026-06-11-structured-error-taxonomy.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 结构化错误分类体系
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-06-11-structured-error-taxonomy.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.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 .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.md
+2026-06-17-filesystem-capability-seam.md: a0c898c1d127a7828037b3a59d04ff73c82e31ba
+2026-06-17-filesystem-capability-seam.zh.md: 0442333a0a7cbc4fcae0f4b02db1700ce6aafc9e

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md → .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: Filesystem capability seam — ctx.fs, local backend, and model-facing filesystem tools
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-06-17-filesystem-capability-seam.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md → .agents/notes/archived/architecture/2026-06-17-filesystem-capability-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 文件系统能力 seam——ctx.fs、本地后端与面向模型的文件系统工具
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-06-17-filesystem-capability-seam.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.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 .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md
+2026-06-18-agent-lifecycle-and-ownership-contracts.md: 012346d9e5b8bf4fd661423b3c398e93e513659d
+2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md: 4f8aaac32dfa3d84a449f12441c23371f1a3a27d

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md → .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md

@@ -1,6 +1,7 @@
 # Agent Note: Agent lifecycle and ownership contracts
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md → .agents/notes/archived/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: Agent 生命周期与所有权约定
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-06-18-agent-lifecycle-and-ownership-contracts.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.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 .agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md
+2026-06-18-shared-persistence-write-coordinator.md: 5c324f2c0c2b951f664bcf92725a36c4975fd3a1
+2026-06-18-shared-persistence-write-coordinator.zh.md: 51ef76189cb9276214bf05bbd1dbd8e3755daa35

+ 53 - 0
.agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.md

@@ -0,0 +1,53 @@
+# Agent Note: Shared persistence write coordinator
+
+Status: implemented
+Archived: 2026-08-31
+
+English | [中文](2026-06-18-shared-persistence-write-coordinator.zh.md)
+
+## Problem
+
+The JSONL provider needs correctness-heavy write orchestration around its storage primitives: per-Session state, `session/created` adoption, prefix reads, write-behind control, per-id operation serialization, HMR seeding, and dispose drains. Keeping that lifecycle in the Service Definition prevents an out-of-tree provider from copying it. The removed first-party database provider demonstrated the duplication cost; the [JSONL-only persistence decision](../simplification/2026-08-30-jsonl-only-session-persistence.md) owns its removal.
+
+## Decision
+
+`dsh-session-persistence` exports a backend-agnostic `PersistenceCoordinator`. The JSONL provider composes one (`new PersistenceCoordinator(ctx, this)`), implements the small `PersistenceBackend` hook interface, and delegates its stateful public methods (`create`/`append`/`prepare`/`load`/`inspect`/`readFrom`) to it. Backend-owned metadata and revision listing bypass the coordinator.
+
+Composition, not inheritance. The coordinator is a concrete class the backend holds, not a base class the backend extends. The risk that a coordinator makes unusual backends fight an inheritance hierarchy is avoided: a backend exposes only the hooks and cannot reach the coordinator's private orchestration state. A third-party backend MAY still implement the abstract service directly without the coordinator, including immutable logical inspection and the default preparation fallback through `load`.
+
+The coordinator holds one lifecycle entry for each exact live `Session`: initialization plus a package-private write controller that owns pending events, a fixed batching deadline, the active write, failure retention, and the shared flush barrier. Each `session/event` enters that bounded write path, and `session/flush` bypasses the wait to observe quiescence. The [flush-controller simplification](../simplification/2026-07-23-collapse-persistence-flush-state.md) owns controller consolidation; the [bounded batching decision](2026-08-08-bounded-session-persistence-write-batching.md) owns scheduling cadence.
+
+Creation borrows the exact `Session.events` snapshot as its persistence seed. `Session` has already detached, validated, and deeply frozen every event, and the snapshot array remains stable when later appends replace the cached view. The coordinator and its backend hooks only read this typed in-process value, so cloning the complete log again would duplicate the ownership work described by the [agent-scope runtime decision](2026-07-12-agent-scope-runtime-design.md#session-append-materialize-validate-commit-notify). Public persistence `append()` still snapshots caller-owned input at its API boundary.
+
+Prepared-session suffixes and events admitted to the write-behind queue retain their existing copies. Those paths establish asynchronous queue ownership one suffix or event at a time and have no measured whole-log clone cost; removing their copies remains a separate ownership audit rather than part of creation-seed borrowing.
+
+The coordinator retires a session from `session/disposed`: it waits for the controller's initialization and current flush, serializes a final drain, and removes the controller and owned per-id state only after success. A failure leaves the controller discoverable for backend teardown to retry. Settled per-id chain tails remove themselves only when they are still current, so a completion cannot erase a newer operation for the same id. Backend teardown unregisters write-path listeners, flushes every remaining controller, awaits per-id operations, and then closes the backend.
+
+### The hook interface (`PersistenceBackend<TornMarker>`)
+
+Five required members plus optional empty-materialization and lifecycle hooks form the only boundary between the coordinator and storage:
+
+- `name` — backend label for the dispose-failure `AggregateError`.
+- `loadStored(id)` — read one stored prefix by id across every storage scope. Preparation, logical load/inspection, physical suffix reads, live adoption, and the create-collision probe share this lookup. The coordinator asserts the returned id and rejects a stored/live cwd mismatch before repair or state publication.
+- `appendBatch(meta, events, isMaterialized)` — durably append a contiguous batch, lazily materializing the session ATOMICALLY when not yet materialized. Ordinary creation therefore cannot leave an abandoned materialized-but-empty session.
+- `materializeHeader?(meta)` — explicitly persist a header-only session for `SessionPersistence.ensureMaterialized(session)`. This is reserved for a lifecycle frontend that treats an empty session itself as a resumable durable resource; [standard ACP automation controls](../feature/2026-08-22-standard-acp-automation-controls.md) are the first consumer. Backends that support that lifecycle implement the hook; lazy creation remains the default.
+- `commitRepair(meta, tornMarker, closers)` — make a crash repair durable: truncate the torn tail (iff `tornMarker !== undefined`) and append `closers`. **NOT required to be atomic** — JSONL legitimately truncates then appends in two fsync'd steps. Used by `prepare`/`load` (truncate + synthetic closers) and live adoption (truncate only, `closers = []`).
+- `list()` — list all stored metadata.
+- `close?()` — optional lifecycle teardown for a provider with owned resources; JSONL omits it. The dispose effect awaits it after the quiescence drain so a close failure never masks a drain error.
+
+### The opaque torn marker
+
+The single design choice that keeps the seam clean: the crash-repair "where is the torn tail" token is opaque to the coordinator. The coordinator computes the synthetic closers (it owns `interruptedTurnClosers` from `dsh-session`), but it only tests `tornMarker !== undefined` and passes the value straight back to `commitRepair`; it never inspects it. JSONL carries the byte offset to truncate to plus any complete events decoded from an incomplete final frame, while another provider may choose its own marker type. The coordinator therefore knows neither byte lengths nor frame recovery state.
+
+## Testing
+
+The shared `runPersistenceContract` proves that JSONL `inspect` balances an interrupted logical view without changing storage or revisions before `prepare` or `load` commits recovery. `runCoordinatorContract` (`tests/coordinator-contract.ts`) covers adoption, HMR, collision, Session and provider disposal drains, and crash-tail repair through an in-memory reference and JSONL. `persistence.spec.ts`, `preparations.spec.ts`, and `write-behind.spec.ts` cover preparation reuse and reservation, bounded prepared-state eviction, fixed-window follow-up batches, live-controller cleanup, same-id chain-tail races, failed-batch retry, and close ordering. JSONL specs retain storage mechanics and the through-coordinator torn-tail case that exercises the opaque-marker branch.
+
+## Alternatives considered
+
+- **A base class the backends extend** — rejected for composition: a backend exposes only the hooks, cannot reach the coordinator's private orchestration state, and a third-party backend may still implement the abstract service directly without the coordinator at all.
+- **A wider hook API** — each candidate hook folds away: there is no scope-specific live lookup because `loadStored` plus the coordinator's cwd check preserves the collision boundary, no storage-locator generic because validated JSONL metadata reproduces its path, no separate `materialize` hook because the first batch must commit atomically with materialization, no separate create-collision probe because it is `loadStored(id) !== undefined`, and no coordinator pass-through for `list()` because listing needs none of the orchestration.
+
+## Consequences
+
+The coordinator adds one indirection, an opaque torn marker, detached Session-retirement tasks, and bounded prepared Session state, but centralizes correctness-heavy orchestration for the JSONL provider and future implementations. Session disposal remains an observe-only event, so the Session owner does not await persistence retirement; the coordinator contains failures, preserves pending events in the live controller, and makes provider teardown the quiescence boundary. Its hook surface stays narrow: identity, adoption, collision checks, preparation, and immutable inspection reuse `loadStored`; materialization stays atomic inside `appendBatch`; and listing bypasses the coordinator. Read models use `inspect` rather than `load`, so observing a persisted open turn does not commit interruption closers; the [Session preparation decision](2026-08-05-session-preparation.md) owns reuse, reservation, and publication. A new provider implements storage primitives rather than copy the bounded write lifecycle.

+ 53 - 0
.agents/notes/archived/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md

@@ -0,0 +1,53 @@
+# Agent Note: 共享持久化写入协调器
+
+Status: implemented
+Archived: 2026-08-31
+
+[English](2026-06-18-shared-persistence-write-coordinator.md) | 中文
+
+## 问题
+
+JSONL provider 需要在其存储原语周围执行对正确性要求很高的写入编排:逐 Session 状态、`session/created` 接管、前缀读取、write-behind 控制、按 id 串行执行、HMR 种子注入与 dispose 排空。把该生命周期放在 Service Definition 中,可以避免仓库外 provider 重复实现。已删除的 first-party 数据库 provider 证明了这种重复成本;其删除由 [JSONL-only 持久化决策](../simplification/2026-08-30-jsonl-only-session-persistence.zh.md)负责。
+
+## 决策
+
+`dsh-session-persistence` 导出后端无关的 `PersistenceCoordinator`。JSONL provider 组合一个协调器实例(`new PersistenceCoordinator(ctx, this)`)、实现小型 `PersistenceBackend` 钩子接口,并把有状态公开方法(`create`/`append`/`prepare`/`load`/`inspect`/`readFrom`)委托给协调器。由后端拥有的元数据与修订版本列举会绕过协调器。
+
+组合,而非继承。协调器是后端持有的具体类,不是后端继承的基类。协调器让非常规后端与继承层级作斗争的风险由此规避:后端只暴露钩子,无法触及协调器的私有编排状态。第三方后端仍然可以完全不使用协调器、直接实现抽象服务,包括不可变逻辑检查,以及通过 `load` 实现的默认准备回退。
+
+协调器为每个存活的 `Session` 实例持有一个生命周期条目:初始化,加上一个包私有写入控制器,后者负责待处理事件、固定批处理截止时间、活跃写入、失败保留和共享 flush 屏障。每个 `session/event` 都进入这条有界写入路径,`session/flush` 则绕过等待以观察完全停稳。控制器归并由 [flush 控制器简化](../simplification/2026-07-23-collapse-persistence-flush-state.zh.md)定义;调度节奏由[有界批处理决策](2026-08-08-bounded-session-persistence-write-batching.zh.md)定义。
+
+创建流程将 `Session.events` 的原始快照借作持久化种子。`Session` 已经分离、验证并深度冻结每个事件,后续追加会替换缓存视图,因此该快照数组保持稳定。协调器及其后端钩子只读取这个有类型的进程内值;再次克隆完整日志会重复 [agent scope 运行时决策](2026-07-12-agent-scope-runtime-design.zh.md#session-append-materialize-validate-commit-notify)规定的所有权工作。持久化服务的公开 `append()` 仍在 API 边界为调用方拥有的输入创建快照。
+
+已准备 Session 的后缀,以及进入 write-behind 队列的事件,仍保留现有复制。这些路径会逐个后缀或事件建立异步队列所有权,且没有已测得的完整日志克隆成本;移除这些复制属于单独的所有权审计,不属于创建种子的借用决策。
+
+协调器通过 `session/disposed` 退役会话:它等待控制器完成初始化和当前 flush,串行执行最后一次排空,且仅在成功后才移除控制器与其拥有的每 id 状态。失败时保持控制器可被找到,以供后端 teardown(拆除)重试。每个 id 的已结算链尾仅在其仍是当前链尾时才移除自身,因此旧操作完成后不会抹除同一 id 的新操作。后端 teardown 会注销写入路径监听器、flush 每个剩余的控制器、等待所有按 id 串行化的操作,最后关闭后端。
+
+### 钩子接口(`PersistenceBackend<TornMarker>`)
+
+五个必需成员加可选的空会话实体化与生命周期钩子,构成协调器与存储之间唯一的边界:
+
+- `name`——后端标签,用于 dispose 失败时的 `AggregateError`。
+- `loadStored(id)`——按 id 跨所有存储范围读取一个已存储前缀。准备、逻辑加载/检查、物理后缀读取、存活会话接管与创建碰撞探测共用此查找。协调器会断言返回的 id,并在修复或发布状态之前拒绝已存储记录与存活会话的 cwd 不匹配。
+- `appendBatch(meta, events, isMaterialized)`——持久追加一个连续批次,在尚未物化时原子地惰性物化会话。因此,普通创建不会留下被放弃的已物化空会话。
+- `materializeHeader?(meta)`——为 `SessionPersistence.ensureMaterialized(session)` 显式持久化仅含 header 的会话。它只供把空会话本身视为可恢复持久资源的生命周期前端使用;[标准 ACP 自动化控制](../feature/2026-08-22-standard-acp-automation-controls.zh.md)是第一个 consumer。支持该生命周期的后端实现此钩子;惰性创建仍是默认行为。
+- `commitRepair(meta, tornMarker, closers)`——使崩溃修复持久化:截断损坏的尾部(当且仅当 `tornMarker !== undefined`)并追加 `closers`。**不要求原子性**——JSONL 合理地分两步 fsync,先截断再追加。用于 `prepare`/`load`(截断 + 合成收尾事件)和存活会话接管(仅截断,`closers = []`)。
+- `list()`——列出所有已存储的元数据。
+- `close?()`——供拥有资源的 provider 使用的可选生命周期清理;JSONL 省略该钩子。dispose effect 在排空至完全停稳后 await 它,因此 close 失败不会掩盖排空错误。
+
+### 不透明的 torn marker
+
+保持 seam 整洁的唯一设计选择:崩溃修复中「损坏尾部在哪里」的 token 对协调器是不透明的。协调器计算合成收尾事件(它拥有来自 `dsh-session` 的 `interruptedTurnClosers`),但只测试 `tornMarker !== undefined` 并将值原样传回 `commitRepair`,从不检视其内容。JSONL 携带要截断到的字节偏移,以及从不完整最终帧中解码出的任何完整事件;其他 provider 可以选择自己的 marker 类型。协调器因此既不了解字节长度,也不了解帧恢复状态。
+
+## 测试
+
+共享 `runPersistenceContract` 证明 JSONL 的 `inspect` 会配平被中断的逻辑视图但不改变存储或修订版本,随后由 `prepare` 或 `load` 提交恢复。`runCoordinatorContract`(`tests/coordinator-contract.ts`)通过内存参考实现与 JSONL 覆盖接管、HMR、碰撞、Session 与 provider dispose 排空和崩溃尾部修复。`persistence.spec.ts`、`preparations.spec.ts` 与 `write-behind.spec.ts` 覆盖准备复用与预留、有界准备状态淘汰、固定窗口后续批次、存活控制器清理、同 id 链尾竞态、失败批次重试与关闭顺序。JSONL 规格保留存储机制,以及覆盖不透明 marker 分支的经由协调器崩溃尾部用例。
+
+## 曾考虑的替代方案
+
+- **后端继承的基类**——否决,改用组合:后端只暴露钩子,无法触及协调器的私有编排状态,且第三方后端仍可完全不使用协调器、直接实现抽象服务。
+- **更宽的钩子 API**——每个候选钩子都被折叠掉:没有限定存储范围的存活会话查找,因为 `loadStored` 加上协调器的 cwd 检查即可维持碰撞边界;没有存储定位器泛型,因为经验证的 JSONL 元数据可还原其路径;没有单独的 `materialize` 钩子,因为首批事件必须与物化原子提交;没有单独的创建碰撞探测,因为它就是 `loadStored(id) !== undefined`;`list()` 也不经由协调器透传,因为列举不需要任何编排。
+
+## 后果
+
+协调器增加一层间接、一个不透明 torn marker、脱离 Session 生命周期的退役任务,以及有界的已准备 Session 状态,但为 JSONL provider 与未来实现集中管理对正确性要求很高的编排。Session dispose 仍是仅观察事件,因此 Session owner 不等待持久化退役;协调器收容失败、在存活控制器中保留待处理事件,并以 provider teardown 为完全停稳边界。其钩子面保持窄小:标识校验、接管、碰撞检查、准备与不可变检查共用 `loadStored`;物化保持在 `appendBatch` 内原子完成;列举绕过协调器。读模型使用 `inspect` 而非 `load`,因此观察已持久化但仍开放的轮次时不会提交中断收尾事件;复用、预留与发布由 [Session 准备阶段决策](2026-08-05-session-preparation.zh.md)定义。新 provider 只需实现存储原语,而无需复制有界写入生命周期。

+ 6 - 0
.agents/notes/archived/architecture/2026-06-20-branded-ids.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 .agents/notes/archived/architecture/2026-06-20-branded-ids.md
+2026-06-20-branded-ids.md: 3078ebc7035c8c5a86c7a743a0452b0ebddf6c7d
+2026-06-20-branded-ids.zh.md: 3a7d7c319c9fc14724af306b8c601afa759a18e7

+ 66 - 0
.agents/notes/archived/architecture/2026-06-20-branded-ids.md

@@ -0,0 +1,66 @@
+# Agent Note: Branded IDs everywhere they belong
+
+Status: implemented
+Archived: 2026-09-04
+
+English | [中文](2026-06-20-branded-ids.zh.md)
+
+## Problem
+
+The harness brands `ToolCallId` (`packages/llm/llm/src/brand.ts`) and the shared agent/session `SessionId` (`packages/core/session/src/types.ts`) using `Branded<B> = string & { readonly [BRAND]: B }` and the stateless `brandString<T>()` constructor from `@deepseek-ai/dsh-brand` at `packages/util/brand/` — see its [README](../../../../packages/util/brand/README.md). `dsh-brand` also states the governing policy: *"Branding is for ids that cross package boundaries and could plausibly be confused; not every string needs a brand."* That policy is right; the problem is that it is only half-applied. Two gaps let a structurally-identical-but-semantically-wrong string slip through the type checker.
+
+**Gap 1 — unbranded cross-boundary IDs in the bash seam.** The background-job id is a plain `string`: `BashTask.id: string` (`packages/shell/shell/src/types.ts`), carried as `string` through the whole executor seam (`ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)` in `packages/shell/shell/src/index.ts`) and validated/passed as `string` by the model-facing tools (`validateJobId`, `assertTaskAccess`, the `job_id` schema arg in `packages/shell/tool-bash/src/index.ts`). It is generated by a per-executor counter — `` `bash-${this.nextTaskId++}` `` in `packages/shell/bash-local/src/index.ts` — which gives it **exactly the same `name-N` shape as `SessionId`'s default** (`` `session-${++counter}` `` in `packages/core/session/src/index.ts`). A bash job id and a session id are trivially swappable at a call site and the compiler says nothing. It is a model-facing id (the model passes `job_id` back to `bash_output`/`bash_kill`), so a confusion here is reachable from untrusted input.
+
+The bash **owner token** is the related sub-case: `ShellExecRequest.owner?: string` and `ShellExecSpec.owner: string | undefined` (`packages/shell/shell/src/types.ts`) are documented as a deliberately *opaque* isolation key, but in every live caller the value IS the owning agent's shared `Agent.id`/`SessionId` (`callerToken = (exec) => exec.agent?.id` in `packages/shell/tool-bash/src/index.ts`) wearing a different seam-local name. It is compared for access control (`owner !== callerToken(exec)`), so a mismatched-but-well-typed string here is a cross-session isolation bug the type system currently cannot catch. This is the shared id alias covered by the [unified agent/session identity decision](../simplification/2026-06-20-unify-agent-and-session-id.md).
+
+**Gap 2 — brand erosion at the boundaries of the *already-branded* IDs.** Even `ToolCallId` and `SessionId` decay back to bare `string` at exactly the places confusion is most likely: registry/store key types and public method params. Representative sites include the session store, the agent registry (both keyed by the shared `SessionId`), tool-presentation call-id maps, ACP's session records, and the persistence coordinator. A brand that is dropped at a collection key buys nothing on lookups — the value of the existing brands is partly unrealized.
+
+## Decision
+
+Brands remain ordinary strings; `brandString<T>()` returns its input unchanged, so serialization, comparison, and wire formats do not change. The decision has three parts, all honoring the existing "not every string" policy.
+
+- **Brand the bash job id.** Add `BashTaskId = Branded<'BashTaskId'>` in `packages/shell/shell/src/types.ts` (the package that *owns* the id), importing `Branded` and constructing values with `brandString<BashTaskId>()` from `@deepseek-ai/dsh-brand`. The brand utility exists so `dsh-shell` can brand its ids by depending on it alone — it never pulls in `dsh-llm` or `dsh-session` just to reach the primitive. Thread the type through `BashTask.id`, the `ShellExecutor` Service Definition methods (`get`/`ownerOf`/`readOutput`/`kill`), the generation site in `dsh-bash-local`, and the `dsh-tool-bash` validation/access surface.
+
+- **Mint a distinct `OwnerToken` brand.** Add `OwnerToken = Branded<'OwnerToken'>` in `packages/shell/shell/src/types.ts`; type `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` as `OwnerToken | undefined`. The `dsh-tool-bash` consumer applies `brandString<OwnerToken>()` to the agent's shared `id` (`SessionId`) at the one place the two vocabularies meet. The bash Service Definition never imports `dsh-session`. (Rationale in the next section.)
+
+- **Stop the brand erosion.** Propagate the existing brands to the `Map` key types and public method params listed under Gap 2 — `Map<SessionId, Session>`, `Map<SessionId, Agent>`, `get(id: SessionId)`, `Map<ToolCallId, …>`, ACP's `SessionId` surface, and the coordinator's `Map<SessionId, …>`. This is the larger mechanical share of the change and the part that makes the *existing* brands actually load-bearing on lookups, not just on struct fields.
+
+Illustrative shape:
+
+```ts ignore-check
+import { brandString, type Branded } from '@deepseek-ai/dsh-brand'
+
+/** A background bash task handle (generated `bash-N` by the local executor). */
+export type BashTaskId = Branded<'BashTaskId'>
+const taskId = brandString<BashTaskId>('bash-1')
+
+/** A bash task's opaque isolation key — the consumer's owner identity, NOT the bash seam's. */
+export type OwnerToken = Branded<'OwnerToken'>
+const owner = brandString<OwnerToken>('session-1')
+```
+
+## Alternatives considered
+
+### Why not typing `owner` as `SessionId`?
+
+The obvious shortcut is to type `owner` as `SessionId` directly — it always *is* one. We reject that. The bash executor seam is a capability seam (Service Definition `dsh-shell`, Service Provider `dsh-bash-local`, Consumer `dsh-tool-bash`) and its owner token is *documented as deliberately opaque*: the executor "never interprets it (no access policy lives in the seam — that is the consumer's job)" (`packages/shell/shell/src/types.ts`). Typing the Service Definition's field as `SessionId` would import `dsh-session`'s vocabulary into a package that must not know what an owner token *means* — it would couple a generic execution backend to the session model and contradict the opaque-token design. A sandboxed or remote executor that replaces `dsh-bash-local` should not inherit a session dependency. The distinct `OwnerToken` brand keeps the seam decoupled: `dsh-shell` knows only "an owner is some opaque branded token," and the `dsh-tool-bash` consumer — which already decides the access policy — is the single boundary that applies `brandString<OwnerToken>()` to its `SessionId`. The brand still delivers the safety win (you cannot pass a `BashTaskId` or a raw string where an owner is expected) without the coupling.
+
+## Out of scope / possible extensions
+
+Kept deliberately narrow per the "not every string needs a brand" policy. Each of these is a plausible future brand, deferred with a reason, not a commitment:
+
+- **`ModelId`** (`GenerateOptions.model`, the `LlmRuntime` adapter-registry key) — a real cross-package lookup key (config → agent → llm → adapter); a reasonable next brand, left out only to keep this decision's blast radius focused.
+- **`ToolName`** (the `ToolRuntime` key) — author-defined, human-readable, and rarely confused with another id; the weakest candidate, likely not worth a brand.
+- **`ErrorCode`** (`HarnessError.code`) — a closed vocabulary (`ABORTED`, `NO_ADAPTER`, …), not a per-instance id; better served by a string-literal union than a brand, if anything.
+- **Other numeric ordinals** — the [Session sequence and log-offset decision](2026-08-31-session-sequence-and-log-offset-brands.md) brands event identities and log gaps because they cross persistence and reference seams. Turn and step numbers remain plain numbers: they are payload-local ordinals and are not interchangeable with Session event positions.
+- **Validated construction** — `brandString<T>()` performs no runtime check, and every boundary (ACP `sessionId`, provider-issued `call.id`, the empty-string fallback in `dsh-llm-deepseek`) trusts the raw string. A `SessionId.parse()` / `isValid()` companion that throws on malformed input at boundaries is a genuine gap, but it is a runtime-behavior change with its own design (what is "malformed"? what happens on failure?) and belongs in its own decision.
+
+## Verification
+
+The landed invariants: `BashTaskId` and `OwnerToken` are defined in `dsh-shell` and threaded end-to-end (Service Definition, the `dsh-bash-local` generation site, the `dsh-tool-bash` model-facing tool) with no `dsh-shell` dependency on `dsh-session`; no collection keyed by an in-scope branded id (`ToolCallId`/`SessionId`/`BashTaskId`) is keyed by bare `string`; public method params and exported signatures keep the brand; and boundaries where raw strings enter use `brandString<T>()` rather than scattered `as` casts.
+
+## Consequences
+
+- **Mechanical churn across two surfaces.** Propagating brands touches the bash seam (Service Definition + Service Provider + Consumer) and the ACP session-id surface plus the persistence coordinator. The churn is broad but low-severity: a missed site is a compile error, not a silent bug. Construction returns the same runtime string, so there is no snapshot or e2e behavioral diff. It sits next to the [unified agent/session identity decision](../simplification/2026-06-20-unify-agent-and-session-id.md) because both touch the session-id / owner-token boundary; `OwnerToken` stays distinct from the unified id for the decoupling reason above.
+- **Brands do not validate.** A brand is a confusability guard, not a correctness proof: a *wrong* session id that is still a well-formed string passes the type checker exactly as before. This decision does not close that gap (see Out of scope) — it only stops the *category* error of passing the wrong *kind* of id.
+- **The "where to stop" line stays a judgment call.** Branding `BashTaskId` but not `ToolName`, `OwnerToken` but not `ModelId`, is a taste call about which strings "could plausibly be confused." Reasonable reviewers may want more or fewer; the policy in `brand.ts` is the tie-breaker, and this decision errs toward the ids that are model-facing or used for access control.

+ 66 - 0
.agents/notes/archived/architecture/2026-06-20-branded-ids.zh.md

@@ -0,0 +1,66 @@
+# Agent Note: 在所有应有之处使用 branded ID
+
+Status: implemented
+Archived: 2026-09-04
+
+[English](2026-06-20-branded-ids.md) | 中文
+
+## 问题
+
+harness 使用 `Branded<B> = string & { readonly [BRAND]: B }` 以及 `@deepseek-ai/dsh-brand` 中的无状态 `brandString<T>()` 构造函数,为 `ToolCallId`(`packages/llm/llm/src/brand.ts`)和 agent(智能体)/会话共享的 `SessionId`(`packages/core/session/src/types.ts`)做 brand 处理;该包位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.zh.md)。`dsh-brand` 还声明了治理策略:*「Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。」* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 仍能通过类型检查器。
+
+**缺口 1:bash seam 中未 brand 的跨边界 ID。** 后台 job id 是普通 `string`:`BashTask.id: string`(`packages/shell/shell/src/types.ts`),作为 `string` 贯穿整个执行器 seam(`packages/shell/shell/src/index.ts` 中的 `ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)`),再由面向模型的工具以 `string` 校验并传递(`validateJobId`、`assertTaskAccess`、`packages/shell/tool-bash/src/index.ts` 中 `job_id` 的 schema 参数)。它由每执行器计数器生成——`packages/shell/bash-local/src/index.ts` 中的 `` `bash-${this.nextTaskId++}` ``——其形状与 `SessionId` 的默认值**完全相同,都是 `name-N`**(`packages/core/session/src/index.ts` 中的 `` `session-${++counter}` ``)。bash job id 和会话 id 在调用点轻易就能互换,而编译器毫无反应。它是面向模型的 id(模型会把 `job_id` 传回 `bash_output`/`bash_kill`),所以该混淆可由不受信任的输入触达。
+
+bash **owner token** 是相关的子情形:`ShellExecRequest.owner?: string` 和 `ShellExecSpec.owner: string | undefined`(`packages/shell/shell/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent 共享的 `Agent.id`/`SessionId`(`callerToken = (exec) => exec.agent?.id`,位于 `packages/shell/tool-bash/src/index.ts`),只是披着另一个 seam 本地名称。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是跨会话隔离 bug,而当前类型系统无法捕获。这正是[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.zh.md)覆盖的共享 id 别名。
+
+**缺口 2:*已经 brand* 的 ID 在边界处被侵蚀。** 就连 `ToolCallId` 和 `SessionId` 也恰好在最容易混淆的地方退化为裸 `string`:注册表/store 键类型和公开方法参数。代表性位置包括会话存储、agent 注册表(二者都以共享的 `SessionId` 为键)、工具展示层的 call-id map、ACP(Agent Client Protocol)的会话记录,以及持久化协调器。在集合键处丢弃 brand,会让既有 brand 在查找时毫无价值;它们的价值只实现了一部分。
+
+## 决策
+
+Brand 仍是普通字符串;`brandString<T>()` 原样返回输入,因此序列化、比较与协议格式(wire format)均不改变。该决策分三部分,全部遵循既有的「不是每个 string 都需要」策略。
+
+- **为 bash job id 加 brand。** 在 `packages/shell/shell/src/types.ts`(*拥有*该 id 的包)中添加 `BashTaskId = Branded<'BashTaskId'>`,从 `@deepseek-ai/dsh-brand` 导入 `Branded` 并用 `brandString<BashTaskId>()` 构造值。brand 工具包让 `dsh-shell` 只依赖它就能为自己的 id 加 brand,而无需为了原语引入 `dsh-llm` 或 `dsh-session`。将该类型贯穿 `BashTask.id`、`ShellExecutor` Service Definition 方法(`get`/`ownerOf`/`readOutput`/`kill`)、`dsh-bash-local` 中的生成点,以及 `dsh-tool-bash` 的校验/访问面。
+
+- **铸造独立的 `OwnerToken` brand。** 在 `packages/shell/shell/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在两套词汇唯一交汇的位置,对 agent 共享的 `id`(`SessionId`)应用 `brandString<OwnerToken>()`。bash Service Definition 从不导入 `dsh-session`。(理由见下一节。)
+
+- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map<SessionId, Session>`、`Map<SessionId, Agent>`、`get(id: SessionId)`、`Map<ToolCallId, …>`、ACP 的 `SessionId` surface、协调器的 `Map<SessionId, …>`。这是变更中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。
+
+示意形状:
+
+```ts ignore-check
+import { brandString, type Branded } from '@deepseek-ai/dsh-brand'
+
+/** A background bash task handle (generated `bash-N` by the local executor). */
+export type BashTaskId = Branded<'BashTaskId'>
+const taskId = brandString<BashTaskId>('bash-1')
+
+/** A bash task's opaque isolation key — the consumer's owner identity, NOT the bash seam's. */
+export type OwnerToken = Branded<'OwnerToken'>
+const owner = brandString<OwnerToken>('session-1')
+```
+
+## 曾考虑的替代方案
+
+### 为什么不把 `owner` 类型标注为 `SessionId`?
+
+显而易见的捷径是直接把 `owner` 类型标注为 `SessionId`——它确实*总是*一个会话 id。我们否决这个方案。bash 执行器 seam 是能力 seam(Service Definition `dsh-shell`、Service Provider `dsh-bash-local`、Consumer `dsh-tool-bash`),其 owner token 被*明确记录为刻意不透明*:执行器「从不解释它(seam 中没有访问策略——那是消费方的职责)」(`packages/shell/shell/src/types.ts`)。把 Service Definition 的字段类型标注为 `SessionId`,会把 `dsh-session` 的词汇引入一个不应知道 owner token *含义*的包——这会让通用执行后端耦合会话模型,并违背不透明 token 的设计。取代 `dsh-bash-local` 的沙箱化执行器或远程执行器不应继承会话依赖。独立的 `OwnerToken` brand 使 seam 保持解耦:`dsh-shell` 只知道「owner 是某种带 brand 的不透明 token」,而已经决定访问策略的 `dsh-tool-bash` 消费方,是把 `brandString<OwnerToken>()` 应用于其 `SessionId` 的唯一边界。该 brand 仍带来安全收益(不能把 `BashTaskId` 或裸 string 传到 owner 位置),且不引入耦合。
+
+## 不在范围内 / 可能的扩展
+
+遵循「不是每个 string 都需要 brand」的策略,刻意保持窄范围。以下每项都是合理的未来 brand 候选,附带推迟理由而非承诺:
+
+- **`ModelId`**(`GenerateOptions.model`,`LlmRuntime` 适配器注册表的键):一个真正的跨包查找键(config → agent → llm → 适配器);合理的下一个 brand,仅为控制本决策的影响范围而暂不纳入。
+- **`ToolName`**(`ToolRuntime` 的键):由作者定义、人类可读,且很少与其他 id 混淆;最弱的候选,可能不值得加 brand。
+- **`ErrorCode`**(`HarnessError.code`):一个封闭词汇(`ABORTED`、`NO_ADAPTER`……),不是逐实例的 id;如果要做,string 字面量联合类型比 brand 更合适。
+- **其他数值序号**:[Session 序列号与日志偏移决策](2026-08-31-session-sequence-and-log-offset-brands.zh.md)会为事件身份与日志间隙加 brand,因为它们跨越 persistence 与引用 seam。turn 与 step number 保持普通 number:它们是 payload-local ordinal,不会与 Session 事件位置互换。
+- **带校验的构造**:`brandString<T>()` 不执行运行时检查,且每个边界(ACP `sessionId`、提供方签发的 `call.id`、`dsh-llm-deepseek` 中的空字符串回退)都信任裸 string。一个在边界处对格式错误的输入抛异常的 `SessionId.parse()` / `isValid()` 配套工具确实是缺口,但它属于运行时行为变更,有自己的设计问题(什么算「格式错误」?失败时会怎样?),应在独立决策中处理。
+
+## 验证
+
+已落地的不变式如下:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-shell` 中,并端到端贯穿 Service Definition、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的工具,且 `dsh-shell` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`ToolCallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界都使用 `brandString<T>()`,而不是散落的 `as` cast。
+
+## 后果
+
+- **两个接口面的机械性改动。** 传播 brand 涉及 bash seam(Service Definition + Service Provider + Consumer)以及 ACP 会话 id 接口和持久化协调器。改动面广但严重度低:遗漏的位置是编译错误而非静默 bug。构造返回同一个运行时字符串,因此不会产生 snapshot 或 e2e 行为差异。它与[统一 agent/会话标识决策](../simplification/2026-06-20-unify-agent-and-session-id.zh.md)相邻,因为二者都触及会话 id / owner-token 边界;`OwnerToken` 出于上述解耦理由仍与统一后的 id 保持独立。
+- **Brand 不做校验。** Brand 是混淆防护,不是正确性证明:一个*错误的*会话 id 只要仍是格式正确的 string,就和以前一样能通过类型检查器。本决策不关闭这个缺口(见「不在范围内」)——它只阻止这类*类别*错误:传入错误*种类*的 id。
+- **「在哪里停下」仍是判断题。** 为 `BashTaskId` 加 brand 但不为 `ToolName` 加,为 `OwnerToken` 加但不为 `ModelId` 加,是对哪些 string「可能被混淆」的品味判断。合理的评审者可能想要更多或更少;`brand.ts` 中的策略是裁决依据,本决策倾向于面向模型或用于访问控制的 id。

+ 6 - 0
.agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.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 .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md
+2026-06-30-bash-stdin-env-trusted-plugin-api.md: b9883c88d091e6e932da7c65b8b7fe4d107c0fa4
+2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md: 884b83de21243e27fa39f7840adfffebc398a9b0

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md → .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md

@@ -1,6 +1,7 @@
 # Agent Note: stdin + extra env on the bash seam
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md → .agents/notes/archived/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 在 bash seam 上支持 stdin 与额外 env
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-06-30-bash-stdin-env-trusted-plugin-api.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.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 .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.md
+2026-07-02-fs-per-session-cwd.md: 81dafda9ba71bd2746926e8baa0f4eaf76748429
+2026-07-02-fs-per-session-cwd.zh.md: aa05ed41615f29d580595a66d4743d4baf02b1a9

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-02-fs-per-session-cwd.md → .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.md

@@ -1,6 +1,7 @@
 # Agent Note: Resolve filesystem paths against the caller's session cwd
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-02-fs-per-session-cwd.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-02-fs-per-session-cwd.zh.md → .agents/notes/archived/architecture/2026-07-02-fs-per-session-cwd.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 相对文件系统路径按调用方的会话 cwd 解析
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-02-fs-per-session-cwd.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.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 .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.md
+2026-07-05-subagent-provider-lifecycle-events.md: a15c3e6faee993902547174fe9ea235d07d9ec2f
+2026-07-05-subagent-provider-lifecycle-events.zh.md: 49e55a3b148775ed7e676b5f55058ef1427a86c6

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md → .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.md

@@ -1,6 +1,7 @@
 # Agent Note: Subagent provider-lifecycle events — `subagent/provider-added` / `subagent/provider-removed`
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-05-subagent-provider-lifecycle-events.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md → .agents/notes/archived/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: Subagent 提供方生命周期事件——`subagent/provider-added` / `subagent/provider-removed`
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-05-subagent-provider-lifecycle-events.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.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 .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.md
+2026-07-06-tool-result-retention-library.md: 5ac8547036ed0b45ec6e55e6fb213a98b7c3df8f
+2026-07-06-tool-result-retention-library.zh.md: b18fdd60f529f9e6df6b8fc7e3c90f9a39e65fc8

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-06-tool-result-retention-library.md → .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.md

@@ -1,6 +1,7 @@
 # Agent Note: Tool result retention library
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-06-tool-result-retention-library.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-06-tool-result-retention-library.zh.md → .agents/notes/archived/architecture/2026-07-06-tool-result-retention-library.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 工具结果保留库
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-06-tool-result-retention-library.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-12-scoped-layers-store.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 .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.md
+2026-07-12-scoped-layers-store.md: d3060696ef54c8d06cd0724179dec49682c721e1
+2026-07-12-scoped-layers-store.zh.md: f5482a5a76724128fb445fbd7c21a2f61b8634c1

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md → .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.md

@@ -1,6 +1,7 @@
 # Agent Note: Shared scoped-layer storage
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-12-scoped-layers-store.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.zh.md → .agents/notes/archived/architecture/2026-07-12-scoped-layers-store.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 共享作用域分层存储
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-12-scoped-layers-store.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.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 .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md
+2026-07-15-llm-model-catalog-and-acp-selection.md: c5f6644d4746a8a962e7ab843493fda3862a8c0a
+2026-07-15-llm-model-catalog-and-acp-selection.zh.md: 539f2f9a2f9d3d7b48b3ecabca4b401e32c5702c

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md → .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md

@@ -1,6 +1,7 @@
 # Agent Note: Advisory LLM catalogs and per-session ACP model selection
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-15-llm-model-catalog-and-acp-selection.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md → .agents/notes/archived/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 建议性 LLM 目录与 ACP 会话级模型选择
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-15-llm-model-catalog-and-acp-selection.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.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 .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.md
+2026-07-15-lsp-capability-seam.md: 3cce6eb3c7d8afef53aa675440c7eea1e990721d
+2026-07-15-lsp-capability-seam.zh.md: c424a794a6662a655565dde6337c49543c5a8085

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md → .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: LSP capability seam and model-facing query tool
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-15-lsp-capability-seam.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.zh.md → .agents/notes/archived/architecture/2026-07-15-lsp-capability-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: LSP 能力 seam 与面向模型的查询工具
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-15-lsp-capability-seam.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.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 .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.md
+2026-07-15-replay-token-meter-service.md: 1e6832f1bd049a9568b3af3b925211f0665de288
+2026-07-15-replay-token-meter-service.zh.md: 6d00d5e1511aa66818689ccec7d4b023c291f9aa

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.md → .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.md

@@ -1,6 +1,7 @@
 # Agent Note: Replay token meter service
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-15-replay-token-meter-service.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.zh.md → .agents/notes/archived/architecture/2026-07-15-replay-token-meter-service.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 回放式 token 计量服务
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-15-replay-token-meter-service.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.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 .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.md
+2026-07-17-local-spill-startup-cleanup.md: c9567cab8b1193b9ea6dbc0ad3fae2bc18af9656
+2026-07-17-local-spill-startup-cleanup.zh.md: 97f6d641ab7e09da401c5632804f6e27ad78d850

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-17-local-spill-startup-cleanup.md → .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.md

@@ -1,6 +1,7 @@
 # Agent Note: One-shot startup cleanup for local spill files
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-17-local-spill-startup-cleanup.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-17-local-spill-startup-cleanup.zh.md → .agents/notes/archived/architecture/2026-07-17-local-spill-startup-cleanup.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 本地 spill 文件的一次性启动清理
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-17-local-spill-startup-cleanup.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-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 .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.md
+2026-07-19-gui-layering-and-rpc-protocol.md: b27d8d024612d890819bfca9b43c0c81464dfdd3
+2026-07-19-gui-layering-and-rpc-protocol.zh.md: 3cf4ba6421c7332c1f8cebb61656a1546f3ad45f

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md → .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.md

@@ -1,6 +1,7 @@
 # Agent Note: GUI layering and the RPC protocol — host/client layering by capability provider, the four-quadrant message model, and the fetch carrier
 
 Status: implemented
+Archived: 2026-08-27
 
 English | [中文](2026-07-19-gui-layering-and-rpc-protocol.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md → .agents/notes/archived/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: GUI 分层与 RPC 协议——host/client 按能力提供方分层、四象限消息模型与 fetch 载体
 
 Status: implemented
+Archived: 2026-08-27
 
 [English](2026-07-19-gui-layering-and-rpc-protocol.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.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 .agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.md
+2026-07-19-package-owned-invariant-service.md: 6ebdd0edbe04766280fb9aae7067ba861b2ce010
+2026-07-19-package-owned-invariant-service.zh.md: dde72faed7249d64b939aaca1bc5d452eeed7bde

+ 106 - 0
.agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.md

@@ -0,0 +1,106 @@
+# Agent Note: Package-owned invariant service contract
+
+Status: implemented
+Archived: 2026-09-04
+
+English | [中文](2026-07-19-package-owned-invariant-service.zh.md)
+
+## Problem
+
+Runtime invariant checks span session traces, agent state, scoped dispatch, and request reconstruction. Putting all checks in one diagnostics package makes that package import product vocabularies from unrelated domains, centralizes tests away from their owners, and requires the central package to change whenever a product package adds or removes a check.
+
+Deployments that opt into diagnostics need more than presence or absence of one plugin. Such a composition carries the known invariant contributions while permitting a global off switch and package-selective diagnostics. Selection must remain stable when a package loads later or reloads under HMR, and disabled contributions must not allow two plugins to claim the same package name silently.
+
+Published ownership must be mechanically complete. Without a repository rule, a package can expose a partial companion, dependency, or publication map and remain broken until a maintainer notices the gap; packages that publish none must keep their reason reviewable in the README.
+
+## Decision
+
+### One registry service, package-owned contributions
+
+`@deepseek-ai/dsh-invariants` is a product-independent Cordis service plugin that registers `ctx.invariants`. It owns configuration, registration uniqueness, child-fiber lifecycle, and package-attributed failures. It imports no session, agent, scope, or agent-loop package and contains none of their checks.
+
+A workspace package publishes a `./invariant` companion plugin only when it owns an independently observable event or mutable-data relationship. The companion registers its exact full npm name. Packages without such a relationship omit the companion and publication wiring and record the reason in their README; generated placeholders, empty installers, and synthetic API-shape assertions are forbidden by the [runtime-contract Agent Note](2026-07-19-package-invariant-runtime-contracts.md) and [omission decision](../simplification/2026-08-28-omit-unneeded-invariant-companions.md). Package root entrypoints do not import or register diagnostics implicitly, so loading a root package does not change runtime checking or require the invariant service.
+
+### Configuration and selection
+
+```ts
+interface Config {
+  enabled?: boolean
+  package_allowlist?: string[]
+  package_blocklist?: string[]
+}
+```
+
+Defaults are `enabled: true`, `package_allowlist: []`, and `package_blocklist: []`. For a full registration name, selection is:
+
+```ts
+export function selected(enabled: boolean, package_allowlist: RegExp[], package_blocklist: RegExp[], packageName: string): boolean {
+  return enabled
+    && (
+      package_allowlist.length === 0
+      || package_allowlist.some(pattern => pattern.test(packageName))
+    )
+    && !package_blocklist.some(pattern => pattern.test(packageName))
+}
+```
+
+Blocklist matches override allowlist matches. Each list entry is a case-sensitive JavaScript regex source compiled by `new RegExp(pattern)`. Matching is unanchored unless callers supply `^` and `$`; slash-delimited syntax and flags are not interpreted. Startup rejects blank, whitespace-padded, invalid, or duplicate sources within either list. A source that matches no loaded package remains valid because registration order, later loading, and HMR must not change config validity.
+
+### Registration and failure ownership
+
+The public registration boundary is `ctx.invariants.register(packageName, installer)`. It reserves one active registration per full npm package name even when filters disable installation, and returns the effect disposer. Disposing the companion or service releases the reservation and all contribution state.
+
+An enabled installer runs in a dedicated child Cordis fiber owned by the service. `InvariantInstaller.inject` declares the child fiber's service API explicitly; the registry carries no product-specific dependency metadata. The service joins a returned installer promise before registration succeeds, so asynchronous startup checks remain transactional. The installer receives a bound `fail(message)` reporter. Calling it throws an `Error` subclass named `InvariantError` with stable code `INVARIANT` and the registering `packageName`; it does not extend a product-package error base.
+
+Registration setup is transactional. If an installer fails after registering listeners, the child fiber is disposed completely and the name reservation is released before the failure escapes. Filtered registrations create no child but retain their reservation until disposal. Reloading a companion therefore begins with one clean installer state; stateful contributions rebuild baselines from their owning services.
+
+The former functional-plugin entry point and one-argument `InvariantError` constructor are not retained as compatibility APIs. The repository is pre-release and all call sites move to the service and package-attributed error together.
+
+### Initial stateful companions and exhaustive ownership
+
+| Companion entry | Registration name | Owned checks |
+|---|---|---|
+| `@deepseek-ai/dsh-session/invariant` | `@deepseek-ai/dsh-session` | session sequence, turn/step enclosure, and same-step call/result trace |
+| `@deepseek-ai/dsh-agent/invariant` | `@deepseek-ai/dsh-agent` | agent-status transitions |
+| `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | scoped-event carrier presence and subject consistency |
+| `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | model-request reconstruction |
+
+These four owners supplied the initial stateful checks. Later owners add companions for real event or mutable-data relationships, while packages without one omit the companion and document why. Every published companion is a separately bundled `./invariant` export with its own declarations and Loader-safe namespace plugin shape.
+
+`verify-package-invariants` discovers every workspace package, accepts clean omission, and rejects partial companion wiring, generated markers, empty installers, installers that omit or ignore the reporter, foreign or unresolved registration names, missing `./invariant` exports or published files, missing invariant peer/development dependencies and project references, and bundle overrides that omit a published companion entry.
+
+### Scoped-event semantic map
+
+The generated scoped-event subject resolver lives in `dsh-scope`, beside the contract and invariant that consume it. `gen-scoped-events` uses the root TypeScript Program to enumerate `this: Scoped<Base>` declarations, infer routing-key types from real `scopeTarget(base, key)` calls, and require one unambiguous payload subject or an explicit unsupported marker. The committed runtime map imports no event-owner package, so semantic completeness does not expand either the service or scope package's runtime closure.
+
+### Example composition and SDK output
+
+The `dsh-sdk-minimal` patch mounts the service and all four stateful companion subpaths as explicit rows. A subpath entry adds its installable root npm package rather than treating the subpath as a package name. The shipped base-backed config trees omit the service and companions under the [shipped-config decision](../simplification/2026-08-03-omit-invariants-from-shipped-config.md).
+
+Workspace constraints recognize the separate invariant bundle, and package exports, project references, build configuration, dependency declarations, and the lockfile describe the same publication metadata. Generated config catalogs, module graphs, and API documentation derive from those sources.
+
+## Testing
+
+Service tests cover defaults, global disablement, allow/block selection, blocklist precedence, anchoring, unanchored matching, case sensitivity, invalid configuration, zero-match patterns, late registration, duplicate ownership, disposal, rollback, and HMR re-registration. Owners with executable checks keep positive and negative behavior beside the companion source.
+
+Composition tests cover standard-spine forwarding and generated SDK entries. Loader tests preserve each companion namespace, while built plain-Node smokes exercise the compiled subpath exports. The scoped-event freshness gate reruns its semantic Program analysis.
+
+Every Vitest configuration loads a test host that mounts an explicitly enabled service before an ordinary Cordis root's first plugin and adds the current test package's companion when one exists. One exhaustive topology mounts all published companions once; focused service and owner tests construct their own invariant topology so they can exercise disablement, filtering, rollback, and reload without duplicate ownership. Gate tests also execute every published companion's `apply` function and verify that it calls `register` with its manifest name, rather than accepting source text alone.
+
+## Alternatives considered
+
+- **Keep all checks in `dsh-invariants`.** Rejected because the registry would continue importing every checked product domain, owner changes would require central edits, and package tests would remain detached from the contracts they protect.
+- **Let root package entrypoints register checks implicitly when `ctx.invariants` happens to exist.** Rejected because root behavior would depend on composition order and optional service presence, diagnostics could not be selected independently, and package loading would hide a registration effect outside an explicit companion.
+- **Discover every `invariant.ts` file automatically at runtime.** Rejected because filesystem/package discovery is not a runtime ownership contract, makes bundled publication ambiguous, and cannot express explicit Cordis load order or dependency installation. Build-time generation, verification, and the test host may enumerate the source tree because they validate repository completeness rather than composing a shipped deployment.
+- **Validate allow/block entries against the currently loaded package set.** Rejected because a zero-match pattern can intentionally target a later or HMR-loaded contribution; current load order must not determine config validity.
+
+## Consequences
+
+- Product packages own and test their relational assertions while the service stays product-independent.
+- Only owners with a meaningful runtime relationship pay the publication, dependency, listener, or trace-state cost of a companion; other packages record the omission reason in their README.
+- Compositions that mount the diagnostics can disable all checks or select package names without changing their plugin tree.
+- Explicit companion entries make diagnostic cost and ownership visible in Cordis config and package exports.
+- One selected contribution adds one child fiber and its listener/state cost, while filtered registrations retain only name ownership.
+- Regex sources are deployment configuration and remain fixed until the service reloads.
+- Ordinary Vitest roots install the owning test package's selected companion when published; one exhaustive topology pays the full child-fiber cost once for repository-wide registration coverage.
+- Session storage validation, snapshotting, freezing, cited source-event validation, and surface acceptance remain always on and are not affected by invariant selection.

+ 106 - 0
.agents/notes/archived/architecture/2026-07-19-package-owned-invariant-service.zh.md

@@ -0,0 +1,106 @@
+# Agent Note: 包拥有的不变式服务约定
+
+Status: implemented
+Archived: 2026-09-04
+
+[English](2026-07-19-package-owned-invariant-service.md) | 中文
+
+## 问题
+
+运行时不变式检查跨越会话轨迹、agent(智能体)状态、作用域 dispatch 和请求重建。如果所有检查都放在一个诊断包中,该包就必须导入彼此无关的产品领域词汇,测试也会离开真正的所有者;任何产品包新增或移除检查时,都要修改中央包。
+
+选择启用诊断的部署还需要比“是否加载一个插件”更细的控制。这类组合会携带已知的不变式贡献,同时允许全局关闭或按包选择诊断。包稍后加载或在 HMR(热模块替换)下重载时,选择结果必须保持稳定;被过滤的贡献也不能让两个插件静默占用同一个包名。
+
+已发布的包所有权必须机械完整。若没有仓库规则,包可能暴露不完整的 companion、依赖或发布映射,并一直保持损坏,直到维护者发现;不发布 companion 的包则必须在 README 中保留可评审的原因。
+
+## 决策
+
+### 一个注册表服务,贡献归包所有
+
+`@deepseek-ai/dsh-invariants` 是与产品无关的 Cordis 服务插件,注册 `ctx.invariants`。它只负责配置、注册唯一性、子 fiber 生命周期和带包归属的失败;不导入 session、agent、scope 或 agent-loop 包,也不包含这些包的检查。
+
+只有拥有可独立观察的事件或可变数据关系时,工作区包才发布 `./invariant` 伴随插件;该 companion 会注册自己完整且准确的 npm 包名。没有该关系的包会省略 companion 与发布接线,并在 README 中记录原因;[运行时约定 Agent Note](2026-07-19-package-invariant-runtime-contracts.zh.md) 与[省略决策](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md)禁止生成占位符、空 installer 和合成 API 形状断言。包的根入口不会隐式导入或注册诊断,因此加载根包不会改变运行时检查,也不要求不变式服务存在。
+
+### 配置与选择
+
+```ts
+interface Config {
+  enabled?: boolean
+  package_allowlist?: string[]
+  package_blocklist?: string[]
+}
+```
+
+默认值为 `enabled: true`、`package_allowlist: []` 和 `package_blocklist: []`。对完整注册名的选择规则为:
+
+```ts
+export function selected(enabled: boolean, package_allowlist: RegExp[], package_blocklist: RegExp[], packageName: string): boolean {
+  return enabled
+    && (
+      package_allowlist.length === 0
+      || package_allowlist.some(pattern => pattern.test(packageName))
+    )
+    && !package_blocklist.some(pattern => pattern.test(packageName))
+}
+```
+
+blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写的 JavaScript 正则表达式源,通过 `new RegExp(pattern)` 编译。除非调用方提供 `^` 与 `$`,否则匹配不锚定;系统不会解析斜杠包围语法或 flags。服务启动会拒绝空白、首尾带空白、无效或同一列表内重复的源。没有匹配当前已加载包的有效源仍然合法,因为注册顺序、稍后加载和 HMR 不应改变配置有效性。
+
+### 注册与失败归属
+
+公开注册边界是 `ctx.invariants.register(packageName, installer)`。即使过滤器禁止安装,它也会为每个完整 npm 包名保留唯一的活跃注册,并返回 effect disposer。卸载伴随插件或服务都会释放注册名及全部贡献状态。
+
+启用的 installer 在服务拥有的独立 Cordis 子 fiber 中运行。`InvariantInstaller.inject` 显式声明该子 fiber 的服务 API;注册表不携带产品专用依赖元数据。服务会在注册成功前等待 installer 返回的 promise,因此异步启动检查仍具有事务性。installer 接收绑定后的 `fail(message)` 报告器。调用它会抛出名为 `InvariantError` 的 `Error` 子类,保留稳定代码 `INVARIANT` 并记录注册方 `packageName`;该错误不继承产品包中的错误基类。
+
+注册启动是事务性的。如果 installer 在注册监听器后失败,子 fiber 会完整释放,并在失败向外传播前解除包名占用。被过滤的注册不创建子 fiber,但会保留占用直到 dispose(资源释放)。伴随插件重载时总会从干净的 installer 状态开始;有状态贡献从其所属服务重建基线。
+
+原有函数式插件入口与单参数 `InvariantError` 构造函数不作为兼容 API 保留。仓库处于预发布阶段,所有调用方会一起迁移到服务和带包归属的错误。
+
+### 首批有状态伴随插件与完整所有权
+
+| 伴随入口 | 注册名 | 所属检查 |
+|---|---|---|
+| `@deepseek-ai/dsh-session/invariant` | `@deepseek-ai/dsh-session` | 会话序列、轮次/步骤包围关系和同一步骤的调用/结果轨迹 |
+| `@deepseek-ai/dsh-agent/invariant` | `@deepseek-ai/dsh-agent` | agent 状态转换 |
+| `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | 作用域事件载体的存在性与主体一致性 |
+| `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | 模型请求重建 |
+
+这四个所有者提供了首批有状态检查。后续所有者会为真实事件或可变数据关系增加 companion,没有该关系的包则省略 companion 并记录原因。每个已发布伴随入口都是单独打包的 `./invariant` export,具有独立声明和对 Loader 安全的命名空间插件形态。
+
+`verify-package-invariants` 会发现每个工作区包,接受完整省略,并拒绝不完整的 companion 接线、生成标记、空 installer、缺少或不使用失败报告器的 installer、外部或无法解析的注册名、缺失的 `./invariant` export 或发布文件、缺失的不变式对等依赖(peer dependency)、开发依赖及项目引用,以及遗漏已发布伴随入口的自定义构建配置。
+
+### 作用域事件语义映射
+
+生成的作用域事件主体解析表位于 `dsh-scope`,与消费它的约定和不变式相邻。`gen-scoped-events` 使用根 TypeScript Program 枚举 `this: Scoped<Base>` 声明,从真实 `scopeTarget(base, key)` 调用推断路由键类型,并要求唯一、无歧义的 payload 主体或显式 unsupported 标记。提交的运行时映射不导入事件所有者包,因此语义完整性不会扩大服务包或 scope 包的运行时依赖闭包。
+
+### 示例组合与 SDK 输出
+
+`dsh-sdk-minimal` patch 将该服务与四个有状态配套子路径作为显式配置行挂载。子路径配置行会添加可安装的根 npm 包,而不会把子路径误当成包名。根据[交付配置决策](../simplification/2026-08-03-omit-invariants-from-shipped-config.zh.md),交付的、基于 base 的配置树会省略该服务及其配套插件。
+
+Workspace 约束识别独立的不变式 bundle;包 exports、项目引用、构建配置、依赖声明和 lockfile 描述同一份发布元数据。生成的配置目录、模块图和 API 文档都从这些源派生。
+
+## 测试
+
+服务测试覆盖默认值、全局关闭、allow/block 选择、blocklist 优先级、锚定与非锚定匹配、大小写敏感、无效配置、零匹配模式、延迟注册、重复所有权、dispose、回滚和 HMR 重新注册。具备可执行检查的所有者会把正向与负向行为保留在 companion 源码旁边。
+
+组合测试覆盖标准主干转发和生成的 SDK 条目。Loader 测试固定每个伴随命名空间,构建后的纯 Node 冒烟测试覆盖编译子路径 export。作用域事件新鲜度门禁会重新执行语义 Program 分析。
+
+每个 Vitest 配置都会加载测试宿主;在普通 Cordis 根上下文启动第一个插件之前,宿主会挂载显式启用的服务,并在当前测试包存在伴随插件时添加它。一个完整拓扑会一次挂载所有已发布伴随插件;服务与所有者的聚焦测试自行构建不变式拓扑,从而在不发生重复所有权冲突的前提下覆盖关闭、过滤、回滚与重载。门禁测试还会执行每个已发布伴随插件的 `apply` 函数,并验证它调用 `register` 时使用 manifest(元数据清单)中的包名,而不是只检查源码文本。
+
+## 考虑过的替代方案
+
+- **把所有检查保留在 `dsh-invariants`。** 不予采纳,因为注册表仍要导入所有被检查的产品领域,所有者变更仍需中央编辑,测试也继续远离被保护的约定。
+- **当 `ctx.invariants` 恰好存在时,让根包入口隐式注册检查。** 不予采纳,因为根入口行为会依赖组合顺序与可选服务是否存在,诊断无法独立选择,而且包加载会隐藏一个不在显式伴随插件中的注册 effect。
+- **在运行时自动发现所有 `invariant.ts` 文件。** 不予采纳,因为文件系统或包发现不是运行时所有权约定,会让 bundle 发布含义不清,也无法表达显式 Cordis 加载顺序或依赖安装。构建期生成与校验以及测试宿主可以枚举源码树,因为它们验证的是仓库完整性,而不是组合已发布的部署。
+- **根据当前已加载包集合验证 allow/block 条目。** 不予采纳,因为零匹配模式可能有意指向稍后加载或 HMR 加载的贡献;当前加载顺序不能决定配置有效性。
+
+## 后果
+
+- 产品包拥有并测试自己的关系断言,服务保持与产品无关。
+- 只有具备有意义运行时关系的所有者才承担 companion 的发布、依赖、listener 或 trace 状态成本;其他包在 README 中记录省略原因。
+- 挂载诊断的组合无需改变插件树即可关闭全部检查或按包名选择。
+- 显式伴随条目让诊断成本和所有权在 Cordis 配置与包 export 中可见。
+- 每个选中贡献增加一个子 fiber 及其 listener/状态成本,被过滤注册则只保留包名占用。
+- 正则表达式源属于部署配置,在服务重载前保持固定。
+- 当前测试包发布伴随插件时,普通 Vitest 根上下文会安装其中被选中的伴随插件;一个完整拓扑只支付一次全部子 fiber 成本,用于覆盖整个仓库的注册。
+- 会话存储验证、快照、冻结、引用的源事件验证与 surface 接受规则始终启用,不受不变式选择影响。

+ 6 - 0
.agents/notes/archived/architecture/2026-07-20-todo-event-ownership.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 .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.md
+2026-07-20-todo-event-ownership.md: 3dfcd604393d05265eb895fa50647338830bd38f
+2026-07-20-todo-event-ownership.zh.md: 8dabf8f0cde4ad8af325b00eb1be9d7b7011233f

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-20-todo-event-ownership.md → .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.md

@@ -1,6 +1,7 @@
 # Agent Note: todo event types belong to their producer
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-20-todo-event-ownership.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-20-todo-event-ownership.zh.md → .agents/notes/archived/architecture/2026-07-20-todo-event-ownership.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: todo 事件类型归其生产方所有
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-20-todo-event-ownership.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.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 .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
+2026-07-22-unified-send-and-coalesced-user-messages.md: d2e39f4efd122d9e0dbb45a082779afa2ff42941
+2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 37c43fcb7bfa81b177e8ed00876e913e3ddf6ad6

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md → .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md

@@ -1,6 +1,7 @@
 # Agent Note: Unify agent delivery on send(target × wakeup) and coalesce injected context into user/message
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-22-unified-send-and-coalesced-user-messages.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md → .agents/notes/archived/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 将 agent 投递统一到 send(target × wakeup) 并把注入的上下文合并进 user/message
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-22-unified-send-and-coalesced-user-messages.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-23-toolview-dissolution.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 .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.md
+2026-07-23-toolview-dissolution.md: d84255f697886556a46c0511aa89f2c4bfd886d8
+2026-07-23-toolview-dissolution.zh.md: 9ae339361193a9b97f7bba44904af0c01e7f3fa0

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-23-toolview-dissolution.md → .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.md

@@ -1,6 +1,7 @@
 # Agent Note: Toolview dissolution — tool rows are per-view keyed slots
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-23-toolview-dissolution.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-23-toolview-dissolution.zh.md → .agents/notes/archived/architecture/2026-07-23-toolview-dissolution.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: toolview 溶解——工具行即 per-view keyed slot
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-23-toolview-dissolution.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.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 .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.md
+2026-07-24-adapter-owned-reasoning-effort-capabilities.md: f5c240145dc7f405f8bc1f66b6b5f7f68991eb80
+2026-07-24-adapter-owned-reasoning-effort-capabilities.zh.md: 81994d4908bd9531cc7db35634bf768372ace1d7

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.md → .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.md

@@ -1,6 +1,7 @@
 # Agent Note: Adapter-owned reasoning effort capabilities
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-24-adapter-owned-reasoning-effort-capabilities.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.zh.md → .agents/notes/archived/architecture/2026-07-24-adapter-owned-reasoning-effort-capabilities.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 适配器持有的推理强度能力
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-24-adapter-owned-reasoning-effort-capabilities.md) | 中文
 

+ 6 - 0
.agents/notes/archived/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 .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.md
+2026-07-25-web-command-surfaces-and-assembly.md: bfb7d2697a979efc8101581d42f4ab223d1b1e97
+2026-07-25-web-command-surfaces-and-assembly.zh.md: d81f70403d22efb2203bbe221a45035fc3d6503f

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md → .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.md

@@ -1,6 +1,7 @@
 # Agent Note: Web command business surfaces and assembly (ui-commands / ui-skill / ui-subagent)
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-25-web-command-surfaces-and-assembly.zh.md)
 
@@ -28,7 +29,7 @@ The pipeline was ready but command knowledge had no landing spot: host-side `ctx
 
 ### 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, the plain-text-reference decision); `lexicon` supplies the roster from CatalogFetch's settled snapshot (`undefined` while not warm), and `subscribeLexicon` notifies per-session listeners on settle and on invalidation. 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-skill**: `skills/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, the plain-text-reference decision); `lexicon` supplies the roster from CatalogFetch's settled snapshot (`undefined` while not warm), and `subscribeLexicon` notifies per-session listeners on settle and on invalidation. 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 and `subscribeLexicon` forwards the list store's change feed (the model-side representation awaits its business workstream).
 
 ### Fixture command routing and assembly

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md → .agents/notes/archived/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: Web 命令业务面与装配(ui-commands / ui-skill / ui-subagent)
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-25-web-command-surfaces-and-assembly.md) | 中文
 
@@ -28,7 +29,7 @@ Status: implemented
 
 ### 引用源(只见投影 + 自家 apply 闭包的 root ctx)
 
-- **ui-skill**:`skill.list({sessionId})` 按会话寻址(host 从会话 header 解析项目根);目录缓存按 sessionId 键控 single-flight,`warm` 钩子出生预热、`connection/reset` 全清。pick 产出 text outcome(`/name ` 原文,纯文本引用决策);`lexicon` 从 CatalogFetch 的 settled 快照给名录(未热 `undefined`),`subscribeLexicon` 在 settle 与失效时按会话通知监听者。无 match 钩子(引用不进命令裁决)。skill 引用以原文随普通提示词走(命令平面之外;tool-skill 不变,会话前缀目录提供协作关联)。
+- **ui-skill**:`skills/list({sessionId})` 按会话寻址(host 从会话 header 解析项目根);目录缓存按 sessionId 键控 single-flight,`warm` 钩子出生预热、`connection/reset` 全清。pick 产出 text outcome(`/name ` 原文,纯文本引用决策);`lexicon` 从 CatalogFetch 的 settled 快照给名录(未热 `undefined`),`subscribeLexicon` 在 settle 与失效时按会话通知监听者。无 match 钩子(引用不进命令裁决)。skill 引用以原文随普通提示词走(命令平面之外;tool-skill 不变,会话前缀目录提供协作关联)。
 - **ui-subagent**:候选零 RPC(sessions.list 快照按 parentId/running 过滤);pick 产出 text outcome(`@name ` 原文);`lexicon` 同快照派生,`subscribeLexicon` 转发 list store 的变更通道(模型侧表示待业务立项)。
 
 ### fixture 命令路由与装配

+ 6 - 0
.agents/notes/archived/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 .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
+2026-07-25-web-input-machine-and-slash-pipeline.md: b6d485d495717770ebb5148880b3c8c1cb23b948
+2026-07-25-web-input-machine-and-slash-pipeline.zh.md: 69ed9a40f4dd9a316f147e3077c9167e1ee46eeb

+ 6 - 4
.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md → .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md

@@ -1,6 +1,7 @@
 # Agent Note: Web input state machine, composer slots, and the slash pipeline (ui-conversation input / ui-input-trigger)
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-25-web-input-machine-and-slash-pipeline.zh.md)
 
@@ -48,14 +49,14 @@ Calls that stay un-evented (registry registration → explicit call → await):
 A trigger/menu/pick pipeline with zero knowledge of "commands":
 
 - The service holds only the source registry (`InputTriggerSource{trigger: '/'|'@', name, order?, candidates, onPick, matchSpace?, matchEnter?}`; (trigger,name) unique; the optional `order` sorts the roster — lower first, default 0, ties keep registration order — and that sorted roster is both group order and polling order) and `sessionOf(sctx)`. Implementing a match hook IS the declaration of participation in space/enter adjudication; the pipeline polls in roster 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 composer surface, ↑↓/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). `toggleSource(name, syntheticHit)` is the chrome-launch path: it seeds only that registered source over the caller's composer selection and publishes `launcher = name` until close; ordinary typed tracking clears the launcher and restores the full trigger roster. Both paths render the same MenuView and execute the same `onPick` chain. A `dismiss()` verb backs MenuView's injected `onDismiss` (a pointer down outside both the menu and the surrounding composer card closes the menu; MenuView also localizes group titles through the `slash.menu` locale namespace and clamps its height to the viewport space above the composer via ui-primitives' `useAnchoredMaxHeight`); 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.
+- 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 composer surface; ↑↓/Enter/Escape are intercepted; Tab settles a highlighted completion, using the candidate's drill action when available and its ordinary pick otherwise, while no highlight preserves native focus traversal; all arbitration passes the IME composition guard, with the single exception Shift+Enter unconditionally going first), and pick orchestration (outcome → self-dispatched bail events). `toggleSource(name, syntheticHit)` is the chrome-launch path: it seeds only that registered source over the caller's composer selection and publishes `launcher = name` until close; ordinary typed tracking clears the launcher and restores the full trigger roster. Both paths render the same MenuView and execute the same `onPick` chain. A `dismiss()` verb backs MenuView's injected `onDismiss` (a pointer down outside both the menu and the surrounding composer card closes the menu; MenuView also localizes group titles through the `slash.menu` locale namespace and clamps its height to the viewport space above the composer via ui-primitives' `useAnchoredMaxHeight`); 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-input-trigger 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. It always owns the same scrollport and composer seat; separate strict-session header and body outlets fill those fixed regions after a Session appears.
-- The composer bar is one `session-maybe` slot entry rendered unconditionally: with no session the same InputBar renders inert (machine faces absent, `disabled` owner prop), and once `connectWorkspace` returns a blank session the same instance goes live — the composer surface DOM survives the no-session → blank transition and every later phase flip; `ConversationRoot`, the Hero, and the layout skeleton hold throughout.
+- The composer bar is one `session-maybe` slot entry rendered unconditionally: with no session the same InputBar renders inert (machine faces absent, `disabled` owner prop), and once `connectWorkspace` returns a blank session the same instance goes live — the composer surface DOM survives the no-session → blank transition and every later phase flip; `ConversationRoot`, the Hero, and the layout skeleton hold throughout. The memoized InputBar renders its overlay, left, right, and dock child slots after the renderer has bound their standard props; `ConversationRoot` passes only scalar data and callbacks, so an unrelated shell render does not create fresh ReactNode owner props or invalidate the bar.
 - ConversationRoot's Hero criterion is `sessionId === undefined || (composerPhase === 'blank' && (openState === 'open' || summaryBlank === true))`: a summary-proven blank Session remains Hero in every open state, while an unproven Session settles during 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` with `mode:'queue'` (the Web UI has no steer entry; host-wire `mode:'steer'` remains outside this machine); 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.
@@ -67,8 +68,8 @@ skill/@subagent references skip the placeholder + occurrence identity chain —
 
 - 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 paired optional `subscribeLexicon?(session, listener)` hook is the invalidation channel for rolls that change after warm (catalog settles, children spawn/exit). The controller aggregates the rolls into its `lexicon` snapshot store (re-polling on each source notification); sources registered after scope birth are warmed and folded in via the service's live-controller broadcast.
-- `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 becomes a `TextRefNode` entity in the Lexical tree (the claim decoration has precedence on the leading-token seat — [the Lexical composer note](2026-08-20-web-composer-lexical-editor.md)); an edit breaking the match shape reverts the entity to plain text.
-- Sending is the literal text (no more `<skill>` serialization); on the bubble side MessageItem decorates both shapes (the legacy `<skill>` tag + plain-text tokens).
+- `decorations.scanTextRefs`: a word-boundary scan of the draft (`/name`, `@name` at line start / after whitespace; `x/name` never hits; a `/name` token also ends at whitespace or the draft end — the whitespace-bounded shape of the host skill gesture, so `/nfs-hg/xxx` is a path and `/plan。` is prose; the sent-text projection `projectUserText` in ui-primitives applies the same shape) against the roster; a hit becomes a `TextRefNode` entity in the Lexical tree (the claim decoration has precedence on the leading-token seat — [the Lexical composer note](2026-08-20-web-composer-lexical-editor.md)); an edit breaking the match shape reverts the entity to plain text.
+- Sending is the literal text (no more `<skill>` serialization); on the bubble side `projectUserText` decorates a plain-text `/name` token only when the same step logged a `skill-invocation` injection for that name — ui-chat's `SkillNameProjector` attaches the step's injected names to the direct message Node, the way the recall projector attaches session labels — so `/123` or a stray `/word` stays plain; a command-input bubble (ui-goal) names its executed command the same way and renders the token as a `command` chip; `@name` tokens still decorate by shape.
 - Decoration reactivity: the shell subscribes to the controller's lexicon store and re-scans the document on each roll change, so a roll that settles after the scope-birth prewarm lights existing draft tokens up without any menu interaction or unrelated re-render.
 
 ### Per-session provide contributions and the private keyboard surface
@@ -105,6 +106,7 @@ The state machine's entire behavior is covered by pure-JS unit tests (event sequ
 | 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 — both sides retired with the textarea itself; Lexical's history owns undo now |
 | 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 |
+| `ConversationRoot` rendering InputBar's child slots into owner props | Fresh React elements defeat the bar's memo boundary; the bar already receives `renderSlot` and owns the exact positions |
 | 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 |

+ 6 - 4
.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md → .agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: Web 输入状态机、composer slot 与 slash 流水线(ui-conversation input / ui-input-trigger)
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-25-web-input-machine-and-slash-pipeline.md) | 中文
 
@@ -48,14 +49,14 @@ Status: implemented
 对「命令」零知识的触发/菜单/pick 流水线:
 
 - 服务只有 source 注册表(`InputTriggerSource{trigger: '/'|'@', name, order?, candidates, onPick, matchSpace?, matchEnter?}`;(trigger,name) 唯一;可选 `order` 对 roster 排序——越小越靠前、默认 0、同值保持注册序——排序后的 roster 同时是组序与轮询序)与 `sessionOf(sctx)`。实现 match 钩子即参与空格/回车裁决的声明;流水线按 roster 序轮询,首个非 undefined 应答胜出,无人认领落 default sink。matchSpace 同步(空格在击键中触发,只许热缓存);matchEnter 异步(可 await 源自身预热,预热失败即 reject)。
-- controller 持有唯一权威 hit(含 span;菜单关闭后为 Space 保留)、每会话 menu store、候选 fetch generation、键盘仲裁(combobox 模式:焦点始终在编辑器表面,↑↓/Enter/Escape 拦截且全程过 IME composition 守卫,唯一例外 Shift+Enter 无条件先行),以及 pick 编排(outcome → 自派 bail 事件)。`toggleSource(name, syntheticHit)` 是 chrome launcher 路径:它基于调用方的编辑器 selection,只 seed 对应的已注册 source,并发布 `launcher = name` 直至关闭;普通的键入式 tracking 会清除 launcher 并恢复完整的 trigger roster。两条路径渲染同一个 MenuView,并执行同一条 `onPick` 链。`dismiss()` 动词支撑 MenuView 注入的 `onDismiss`(指针落在菜单与所在 composer 卡片之外即关闭菜单;MenuView 还经 `slash.menu` locale 命名空间本地化组标题,并经 ui-primitives 的 `useAnchoredMaxHeight` 把高度收敛到 composer 上方的视口空间);每个会话作用域出生时对 source roster 做一次 `warm(projection)`,projection 在该 scope 内只有稳定的 sessionId,无 published/能力跃迁;scope disposer 拆除 controller。
+- controller 持有唯一权威 hit(含 span;菜单关闭后为 Space 保留)、每会话 menu store、候选 fetch generation、键盘仲裁(combobox 模式:焦点始终在编辑器表面;↑↓/Enter/Escape 会被拦截;Tab 会选定高亮补全项,候选项可下钻时走 drill 动作,否则走普通 pick,无高亮时保留原生焦点遍历;所有仲裁都经过 IME composition 守卫,唯一例外是 Shift+Enter 无条件先行),以及 pick 编排(outcome → 自派 bail 事件)。`toggleSource(name, syntheticHit)` 是 chrome launcher 路径:它基于调用方的编辑器 selection,只 seed 对应的已注册 source,并发布 `launcher = name` 直至关闭;普通的键入式 tracking 会清除 launcher 并恢复完整的 trigger roster。两条路径渲染同一个 MenuView,并执行同一条 `onPick` 链。`dismiss()` 动词支撑 MenuView 注入的 `onDismiss`(指针落在菜单与所在 composer 卡片之外即关闭菜单;MenuView 还经 `slash.menu` locale 命名空间本地化组标题,并经 ui-primitives 的 `useAnchoredMaxHeight` 把高度收敛到 composer 上方的视口空间);每个会话作用域出生时对 source roster 做一次 `warm(projection)`,projection 在该 scope 内只有稳定的 sessionId,无 published/能力跃迁;scope disposer 拆除 controller。
 - 触发检测词边界(`user@host`、URL `/` 永不触发)、守卫分档(plain:`/` 到处 + `@` 行内 / claimed:`/` 抑制、`@` 活 / frozen:全无)为冻结纯核。
 
 ### hub / facade:常驻外壳与严格会话输入体
 
 - hub(trigger/decoration 注册表 + 发送编排)对 slash/command 服务是可选 `ctx.get()` 依赖:无 ui-input-trigger/命令面时输入正常收发,优雅降级。
 - 每个实体会话只有一个 `SessionInputShell`(facade),随会话作用域创建和拆除;无会话时不造 input machine。`ConversationRoot` 自身是 `session-maybe` 常驻外壳,持有 HeroShell、Workspace picker、composer stack 与 chain fallback 外框。它始终拥有同一个 scrollport 与 composer seat;会话出现后,彼此独立的严格会话 header 和 body outlet 只填入这些固定区域。
-- composer bar 是一个无条件渲染的 `session-maybe` slot entry:无会话时同一个 InputBar 以惰性态渲染(machine face 缺席、`disabled` owner prop),`connectWorkspace` 返回 blank 会话后同一实例转为 live——编辑器表面 DOM 在无会话 → blank 切换及其后每次 phase 翻转中都不重建;`ConversationRoot`、Hero 与布局骨架全程保持。
+- composer bar 是一个无条件渲染的 `session-maybe` slot entry:无会话时同一个 InputBar 以惰性态渲染(machine face 缺席、`disabled` owner prop),`connectWorkspace` 返回 blank 会话后同一实例转为 live——编辑器表面 DOM 在无会话 → blank 切换及其后每次 phase 翻转中都不重建;`ConversationRoot`、Hero 与布局骨架全程保持。memoized InputBar 在 renderer 绑定各 child slot 的标准 props 后自行渲染 overlay、left、right 与 dock;`ConversationRoot` 只传标量数据和回调,因此无关 shell render 不会制造新的 ReactNode owner prop 或使 bar 失效。
 - ConversationRoot 的 Hero 判据是 `sessionId === undefined || (composerPhase === 'blank' && (openState === 'open' || summaryBlank === true))`:summary 已证实为空的会话在任何 open state 下都保持 Hero,未经证实的会话则在 loading 期间进入 settling。首次 submit 同步进入 engaging,失败也保留 composer 与错误上下文,不退回 blank Hero;sidebar 的 blank 位只在提示词成功受理后翻 false。
 - 发送统一在 hub defaultSink:乐观清稿后只走 `session.prompt` 且固定 `mode:'queue'`(Web UI 无 steer 入口;host 线缆上的 `mode:'steer'` 不经此 machine);失败且 live draft 仍为空才回填,用户已经继续输入则不覆盖。不存在 Draft materialize 或 attach 事务。
 - blank Hero 改选 Workspace 时,外壳调用 `connectWorkspace`;目标会话不同时把非空 draft 从当前 shell 搬到目标 shell,再 open 新 id,旧 blank 会话留存但不再 current。
@@ -67,8 +68,8 @@ skill/@subagent 引用不走占位符 + occurrence 身份链——纯文本引
 
 - PickOutcome 增 `{text}` arm;新 scoped bail 事件 `slash/input-insert-text` `{text, span}`(与另三个同约定:draftRev CAS、返回 true ⟺ 实际改写);facade.insertText 走 setDraft 拼接,机器零改动。
 - source 可选 `lexicon?(session)` 钩子:同步热快照名录,`undefined` = 数据未热——零装饰、永不触发 fetch(渲染路径保持同步无副作用);配对的可选 `subscribeLexicon?(session, listener)` 钩子是名录在 warm 之后仍会变化(目录 settle、子代生灭)时的失效通道。controller 把各名录聚合进自己的 `lexicon` 快照 store(每次 source 通知重拉);scope 出生后才注册的 source 由服务广播给活 controller,补 warm 并并入名录。
-- `decorations.scanTextRefs`:词边界扫描 draft(行首/空白后的 `/name`、`@name`,`x/name` 永不命中)对照名录,命中即成为 Lexical 树中的 `TextRefNode` 实体(claim 装饰对行首 token 席位有优先权——见 [Lexical composer note](2026-08-20-web-composer-lexical-editor.zh.md));编辑破坏匹配形状时实体还原为普通文本。
-- 发送即原文(不再 `<skill>` 序列化);气泡侧 MessageItem 双形状装饰(legacy `<skill>` 标签 + 纯文本 token)。
+- `decorations.scanTextRefs`:词边界扫描 draft(行首/空白后的 `/name`、`@name`,`x/name` 永不命中;`/name` token 还必须止于空白或 draft 末尾——与宿主 skill gesture 同样以空白为界,因此 `/nfs-hg/xxx` 是路径、`/plan。` 是普通文本;ui-primitives 中已发送文本的投影 `projectUserText` 采用同一形状)对照名录,命中即成为 Lexical 树中的 `TextRefNode` 实体(claim 装饰对行首 token 席位有优先权——见 [Lexical composer note](2026-08-20-web-composer-lexical-editor.zh.md));编辑破坏匹配形状时实体还原为普通文本。
+- 发送即原文(不再 `<skill>` 序列化);气泡侧 `projectUserText` 只在同一步骤记录了该名字的 `skill-invocation` 注入时才装饰纯文本 `/name` token——ui-chat 的 `SkillNameProjector` 把该步骤注入的 skill 名挂到直接消息节点上,与 recall 投影挂会话标签的方式相同——因此 `/123` 或随手敲的 `/词` 保持普通文本;指令输入气泡(ui-goal)以同样方式指明其已执行的指令,把 token 渲染为 `command` chip;`@name` token 仍按形状装饰。
 - 装饰响应性:shell 订阅 controller 的 lexicon store,每次名录变化重扫全文档,scope 出生预热后才 settle 的名录会直接点亮已有 draft token,无需菜单交互或无关重渲染。
 
 ### 每会话供数贡献与键盘私面
@@ -105,6 +106,7 @@ skill/@subagent 引用不走占位符 + occurrence 身份链——纯文本引
 | draft 双持久化 {text, occurrences} | mirror 写剪贴板投影零新概念;chip 跨刷新降级可接受 |
 | 原生 textarea undo 栈 | 受控 + 程序化写入下不可靠;粘贴两段 undo 语义只能自管——两侧都随 textarea 一并退役;undo 现归 Lexical history |
 | InputBar 收 16 员 wiring 回调包 | 消费矩阵实证 11 员 InputBar 独占、1 员死成员;标准件通道让组件自取,键盘面包内私递 |
+| 由 `ConversationRoot` 把 InputBar child slot 渲染为 owner prop | 新 React element 会击穿 bar 的 memo 边界;bar 已收到 `renderSlot`,也拥有这些位置 |
 | 空格裁决也认领即执行型命令 | 误触发防线:空格后整行是普通提示词;不可逆副作用只留显式入口 |
 | 通用 tokenPattern 装饰机制 | 结构化 occurrence 记录取代模式扫描 |
 | 占位 select 常驻工具行 | 具名 slot 在注册前保持为空;占位件与真实现冲突时是两个真源 |

+ 6 - 0
.agents/notes/archived/architecture/2026-07-26-job-registry-seam.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 .agents/notes/archived/architecture/2026-07-26-job-registry-seam.md
+2026-07-26-job-registry-seam.md: d1a8cfb7bc4f1a8e313dfa8d59a5d0e5f8106190
+2026-07-26-job-registry-seam.zh.md: 1624a410407d8e6ac8cc85a6fe1480e0867eef6d

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md → .agents/notes/archived/architecture/2026-07-26-job-registry-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: The job registry is a capability seam (`dsh-jobs` / `dsh-jobs-local`)
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-26-job-registry-seam.zh.md)
 
@@ -16,7 +17,7 @@ The [background-job runtime](2026-06-20-generic-long-running-tool-runtime.md) sh
 - **`@deepseek-ai/dsh-jobs-local` (Service Provider)** — `LocalJobRegistry`, the process-local registry: the in-memory store, per-kind id counters, waiter bookkeeping, `TASK_WAIT_TIMEOUT` deadline code, owner-cleanup effects, force-fail teardown, and the default-10 configurable admission policy. Admission derives `running` plus `stopping` capacity from the same records per exact owner, with one unowned bucket; it adds no public count or second state owner. The `dsh-timeout` dependency and Schemastery-owned provider config live here; the Service Definition package has no provider dependencies.
 - **`@deepseek-ai/dsh-tool-jobs` (Consumer)** — unchanged; it injects `'jobs'` and never imports provider types.
 
-Compositions load `dsh-jobs-local` where they previously loaded `dsh-jobs` (the CLI cordis.yml row, `agent-spine-demo`, test harnesses, the tool-catalog generator boot). Producer misconfiguration diagnostics ("background jobs unavailable: load …") name `dsh-jobs` — the Service Definition package that declares the absent `ctx.jobs` service — and the Service Definition package's own APIs (its README and the direct-mount fence) point at Service Providers, so the producer message stays correct when another backend becomes the recommended default. Producers, `JobKindMap` declaration merges, and the controller keep importing `@deepseek-ai/dsh-jobs` only.
+Compositions load `dsh-jobs-local` where they previously loaded `dsh-jobs` (`dsh-base`, `sdk-minimal`, test harnesses, and the tool-catalog generator boot). Producer misconfiguration diagnostics ("background jobs unavailable: load …") name `dsh-jobs` — the Service Definition package that declares the absent `ctx.jobs` service — and the Service Definition package's own APIs (its README and the direct-mount fence) point at Service Providers, so the producer message stays correct when another backend becomes the recommended default. Producers, `JobKindMap` declaration merges, and the controller keep importing `@deepseek-ai/dsh-jobs` only.
 
 The seam keeps the in-process contract semantics unchanged: `JobStart.run()` still passes callbacks and exact `Agent` objects, so a durable or cross-process backend still has design work to do before it can satisfy this Service Definition (identity, restart, ownership, observation). The split moves that future work out of every Consumer's dependency graph; it does not pre-design the backend.
 

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md → .agents/notes/archived/architecture/2026-07-26-job-registry-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 任务注册表是一个能力 seam(`dsh-jobs` / `dsh-jobs-local`)
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-26-job-registry-seam.md) | 中文
 
@@ -16,7 +17,7 @@ Status: implemented
 - **`@deepseek-ai/dsh-jobs-local`(Service Provider)**——`LocalJobRegistry`,即进程内注册表:内存存储、按 kind 划分的 id 计数器、等待方簿记、`TASK_WAIT_TIMEOUT` deadline 代码、所有者清理 effect、强制失败的拆除,以及默认值为 10 且可配置的准入策略。准入从同一组记录中按确切 owner 派生 `running` 加 `stopping` 容量,并为无 owner 任务使用一个共享桶;它不新增公开计数或第二个状态 owner。`dsh-timeout` 依赖与由 Schemastery 管理的 Service Provider 配置都位于此包;Service Definition 包不含任何提供方依赖。
 - **`@deepseek-ai/dsh-tool-jobs`(Consumer)**——保持不变;它注入 `'jobs'`,从不导入提供方类型。
 
-各组合在原先加载 `dsh-jobs` 的位置改为加载 `dsh-jobs-local`:CLI(命令行界面)的 cordis.yml 配置项、`agent-spine-demo`、各测试 harness,以及工具目录生成器的启动流程。生产方的配置错误诊断信息(「background jobs unavailable: load …」)点名 `dsh-jobs`——即声明缺失的 `ctx.jobs` 服务的 Service Definition 包;Service Definition 包自身的 API(其 README 与直接挂载防线)会指向各 Service Provider,因此当另一个后端日后成为推荐默认时,生产方的消息依旧正确。生产方、`JobKindMap` 声明合并和控制器仍然只导入 `@deepseek-ai/dsh-jobs`。
+各组合在原先加载 `dsh-jobs` 的位置改为加载 `dsh-jobs-local`:`dsh-base`、`sdk-minimal`、各测试 harness,以及工具目录生成器的启动流程。生产方的配置错误诊断信息(「background jobs unavailable: load …」)点名 `dsh-jobs`——即声明缺失的 `ctx.jobs` 服务的 Service Definition 包;Service Definition 包自身的 API(其 README 与直接挂载防线)会指向各 Service Provider,因此当另一个后端日后成为推荐默认时,生产方的消息依旧正确。生产方、`JobKindMap` 声明合并和控制器仍然只导入 `@deepseek-ai/dsh-jobs`。
 
 该 seam 保持进程内约定语义不变:`JobStart.run()` 仍然传入回调和确切的 `Agent` 对象,因此持久化或跨进程后端在能满足此 Service Definition 之前仍有设计工作要做(身份、重启、所有权、观察)。这次拆分把该项未来工作移出了每个 Consumer 的依赖图;它并不预先设计后端。
 

+ 6 - 0
.agents/notes/archived/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 .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.md
+2026-07-26-packed-chunk-rows-by-default.md: c230c1f1faf5e597321654ebd01d60fae725f518
+2026-07-26-packed-chunk-rows-by-default.zh.md: e354efdf6bb68c02f30dc17c8d4ba17a495b61bd

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.md → .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.md

@@ -1,6 +1,7 @@
 # Agent Note: Make packed chunk rows the default JSONL layout
 
 Status: implemented
+Archived: 2026-09-01
 
 English | [中文](2026-07-26-packed-chunk-rows-by-default.zh.md)
 
@@ -18,7 +19,7 @@ Reading is unconditional and layout-blind. Packed, unpacked, and mixed files loa
 
 ### Logical events and physical rows
 
-The JSONL packing path 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 encoding vocabulary, not a `SessionEventMap` member: it never enters `Session.events` or fires `session/event`. The [packed session-history transport decision](2026-08-15-packed-session-history-transport.md) reuses this vocabulary for a bounded lossless wire interval without changing those event semantics.
+The JSONL packing path 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 encoding vocabulary, not a `SessionEventMap` member: it never enters the Session log or fires `session/event`. The [packed session-history transport decision](2026-08-15-packed-session-history-transport.md) reuses this vocabulary for a bounded lossless wire interval without changing those event semantics.
 
 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.
 

+ 2 - 1
.agents/notes/implemented/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md → .agents/notes/archived/architecture/2026-07-26-packed-chunk-rows-by-default.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 将打包分片行设为默认 JSONL 布局
 
 Status: implemented
+Archived: 2026-09-01
 
 [English](2026-07-26-packed-chunk-rows-by-default.md) | 中文
 
@@ -18,7 +19,7 @@ JSONL 存储 seam 可以在不改变逻辑日志的情况下减少这部分封
 
 ### 逻辑事件与物理行
 
-JSONL 打包路径保留在 `dsh-session` 的存储 seam,并通过 `packChunkRuns()` 和 `decodeStorageRecord()` 实现。编码器识别精确的增量事件形态,原样保留无法识别的事件,并且只打包至少包含 3 个事件的连续段。打包行属于编码词汇,不是 `SessionEventMap` 成员:它绝不会进入 `Session.events`,也不会触发 `session/event`。[打包会话历史传输决策](2026-08-15-packed-session-history-transport.zh.md)会为有界的无损协议区间复用该词汇,而不改变这些事件语义。
+JSONL 打包路径保留在 `dsh-session` 的存储 seam,并通过 `packChunkRuns()` 和 `decodeStorageRecord()` 实现。编码器识别精确的增量事件形态,原样保留无法识别的事件,并且只打包至少包含 3 个事件的连续段。打包行属于编码词汇,不是 `SessionEventMap` 成员:它绝不会进入 Session 日志,也不会触发 `session/event`。[打包会话历史传输决策](2026-08-15-packed-session-history-transport.zh.md)会为有界的无损协议区间复用该词汇,而不改变这些事件语义。
 
 JSONL 后端会打包每个持久追加批次。原始模式 `compression: 'none'` 与默认 Zstandard 帧承载相同的逻辑存储记录;为使 fixture 便于评审而选择原始模式,不会禁用打包。仓库中的回放读取器和规范化器会解码共享行格式,而不维护快照专用编解码器。
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-26-subprocess-seam.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 .agents/notes/archived/architecture/2026-07-26-subprocess-seam.md
+2026-07-26-subprocess-seam.md: bd8cba2197a56acef2ab12e9b43e254fbc744ee5
+2026-07-26-subprocess-seam.zh.md: ba23649e9e68f578bdc9ba26a026981d0afe981f

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md → .agents/notes/archived/architecture/2026-07-26-subprocess-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: The subprocess service is its own seam under the bash executors (`dsh-subprocess` / `dsh-subprocess-local`)
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-26-subprocess-seam.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.zh.md → .agents/notes/archived/architecture/2026-07-26-subprocess-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 进程服务是 bash 执行器之下的独立 seam(`dsh-subprocess` / `dsh-subprocess-local`)
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-26-subprocess-seam.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.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 .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.md
+2026-07-27-dispose-ladder-to-consumer.md: 7e1b189c1d3a32c6f812bba5d5f06f7a6a89c29b
+2026-07-27-dispose-ladder-to-consumer.zh.md: 6c61cad5d4a62c5c976894f72d0ff5e9c69184bc

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-27-dispose-ladder-to-consumer.md → .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.md

@@ -1,6 +1,7 @@
 # Agent Note: The dispose ladder belongs to its consumer, not the subprocess seam
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-27-dispose-ladder-to-consumer.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-27-dispose-ladder-to-consumer.zh.md → .agents/notes/archived/architecture/2026-07-27-dispose-ladder-to-consumer.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: dispose 阶梯归其消费方所有,而非 subprocess seam
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-27-dispose-ladder-to-consumer.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.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 .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.md
+2026-07-28-directory-picker-capability-seam.md: e2f3312b7429078eb81cba62ab1727e71fe67b74
+2026-07-28-directory-picker-capability-seam.zh.md: 41a05febe58c353242cc389e20bbf84b3620b7c9

+ 5 - 4
.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.md → .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: A capability-discriminated directory-picker seam for the web-GUI host
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-28-directory-picker-capability-seam.zh.md)
 
@@ -10,9 +11,9 @@ The web GUI's "Open local folder" flow was hardwired to one interaction: `host.p
 
 ## Decision
 
-A three-package capability seam in `packages/host/` — `directory-picker` (Service Definition), `directory-picker-native`, `directory-picker-browse` (backends) — with one contract method: `capability()` returns a **discriminated union**, `{ kind: 'native', pick(signal) }` or `{ kind: 'browse', list(path?), createDirectory(path, name) }`. The gateway (`dsh-host-apiproxy`) injects `directoryPicker`, serves the matching RPCs, and answers `directory-picker-unavailable` for the other kind. The union is discriminated because the backends differ in *interaction shape* — flattening them into one method set would force every backend to fake the other's shape.
+A three-package capability seam in `packages/host/` — `directory-picker` (Service Definition), `directory-picker-native`, `directory-picker-browse` (backends) — has one contract method: `capability()` returns a **discriminated union**, `{ kind: 'native', pick(signal) }` or `{ kind: 'browse', list(path?), createDirectory(path, name) }`. `DirectoryPickerController` in `dsh-api-workspace-controller` injects `directoryPicker`, serves the matching generated Remote methods, and answers `directory-picker-unavailable` for the other kind. The union is discriminated because the backends differ in *interaction shape* — flattening them into one method set would force every backend to fake the other's shape.
 
-**The client side is slot-composed, not advertisement-branched.** ui-workspace's two trigger surfaces each declare a `single` directory-flow hole (`conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`; two keys because a hole has exactly one declaring slot entry — same owner contract, same occupant). Backend packages are **dual-face**: the browser half registers the matching interaction into both holes — `-native` a renderless occupant driving `host.pickDirectory`, `-browse` the in-app Select Workspace Directory dialog. The hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`) carries the whole exchange: ui-workspace keeps the trigger (menu entry rendered only while the hole is occupied) and the adoption (`createWorkspace({path})`, retryable error dialog, Choose again), the occupant owns everything between `open` and the picked path. One `cordis.yml` row therefore swaps the host capability and the client flow together; a mismatch is impossible by construction, and mounting two flow packages fails at client load (`single` hole). The earlier `host.describe.directoryPicker` advertisement and the client's kind branching are deleted — with composition wiring both sides, a wire fact for the client to branch on had no remaining consumer. The hole registry (`ctx.slots.entries`) replaces it as the per-menu-open occupancy read.
+**The client side is slot-composed, not advertisement-branched.** ui-workspace's two trigger surfaces each declare a `single` directory-flow hole (`conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`; two keys because a hole has exactly one declaring slot entry — same owner contract, same occupant). Backend packages are **dual-face**: the browser half registers the matching interaction into both holes — `-native` a renderless occupant driving `directoryPicker/pick`, `-browse` the in-app Select Workspace Directory dialog. The hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`) carries the whole exchange: ui-workspace keeps the trigger (menu entry rendered only while the hole is occupied) and the adoption (`createWorkspace({path})`, retryable error dialog, Choose again), the occupant owns everything between `open` and the picked path. One `cordis.yml` row therefore swaps the host capability and the client flow together; a mismatch is impossible by construction, and mounting two flow packages fails at client load (`single` hole). The earlier `host.describe.directoryPicker` advertisement and the client's kind branching are deleted — with composition wiring both sides, a wire fact for the client to branch on had no remaining consumer. The hole registry (`ctx.slots.entries`) replaces it as the per-menu-open occupancy read.
 
 Placement and policy rulings folded into this decision:
 
@@ -31,7 +32,7 @@ Placement and policy rulings folded into this decision:
 
 - **Extend `ctx.fs` with browse methods.** Rejected: authority-domain coupling above; also a listing-for-display contract (hidden flags, crumbs, home anchor) does not belong on a storage seam.
 - **One uniform Service Definition method set (`pick(): path`).** Rejected: an in-app browser cannot be served behind a single host-side call — the browsing loop lives in the client and needs primitives on the wire; the native chooser cannot implement primitives. The interaction difference is irreducible, hence the discriminant.
-- **Direct stdlib calls inside apiproxy (no seam).** Rejected: keeps the gateway the only swap point (source edits), loses fixture/test backends, and contradicts the plugin doctrine that motivated the work.
+- **Direct stdlib calls inside the API adapter (no seam).** Rejected: keeps the adapter the only swap point (source edits), loses fixture/test backends, and contradicts the plugin doctrine that motivated the work.
 - **Adopting a file-manager/drive-enumeration dependency.** Rejected per the survey above; recorded here as the dependency policy requires.
 - **A flip-label show-hidden toggle ("Hide hidden files").** Rejected: a flipping action label is ambiguous between state and action and doubles the negative; the fixed label with a pressed presentation states both at once.
 - **Pure relatedTarget blur cancellation (no mousedown suppression).** Rejected: Safari does not focus buttons on pointer down, so a click's focusout carries a null `relatedTarget` and would cancel the editor before the click lands; editing-scoped mousedown suppression plus the card-anchored relatedTarget guard covers pointer and keyboard paths together.
@@ -43,6 +44,6 @@ Placement and policy rulings folded into this decision:
 ## Consequences
 
 - `cordis.yml` chooses the interaction; `apps/cli` mounts the [`-auto` chooser](../feature/2026-07-29-directory-picker-adaptive-default.md), which resolves the host's situation at boot and mounts `-native` or `-browse` itself, one row still swapping backend and UI together; composing a backend row directly pins the interaction.
-- The wire gains `host.listDirectory`/`host.createDirectory` and four error codes; the connection fixture serves a deterministic browse tree and a deterministic `pickDirectory` path for keyless assembled tests.
+- The wire exposes generated `directoryPicker/list` and `directoryPicker/createDirectory` methods with four error codes; the Connection fixture serves a deterministic browse tree and `directoryPicker/pick` result for keyless assembled tests.
 - A future interaction (or an Electron provider of the `native` interaction) is one dual-face backend package — no gateway surgery, no ui-workspace edits.
 - `ApiProxyDefaults.pickDirectory` (test-only injection) is gone; tests provide a stub `ctx.directoryPicker` like any other service.

+ 5 - 4
.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.zh.md → .agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: web GUI 宿主的能力可辨识目录选择 seam
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-28-directory-picker-capability-seam.md) | 中文
 
@@ -10,9 +11,9 @@ web GUI 的「打开本地文件夹」流程被焊死在一种交互上:`host.
 
 ## 决策
 
-在 `packages/host/` 落一个三包能力 seam——`directory-picker`(Service Definition)、`directory-picker-native`、`directory-picker-browse`(后端)——唯一约定方法 `capability()` 返回**可辨识联合**:`{ kind: 'native', pick(signal) }` 或 `{ kind: 'browse', list(path?), createDirectory(path, name) }`。网关(`dsh-host-apiproxy`)注入 `directoryPicker`,提供对应的 RPC,另一种 kind 的调用以 `directory-picker-unavailable` 应答。联合之所以可辨识,是因为后端差异在**交互形态**——压平成统一方法集会逼每个后端伪装另一方的形态。
+在 `packages/host/` 落一个三包能力 seam——`directory-picker`(Service Definition)、`directory-picker-native`、`directory-picker-browse`(后端)——唯一约定方法 `capability()` 返回**可辨识联合**:`{ kind: 'native', pick(signal) }` 或 `{ kind: 'browse', list(path?), createDirectory(path, name) }`。`dsh-api-workspace-controller` 中的 `DirectoryPickerController` 注入 `directoryPicker`,提供匹配的生成 Remote 方法,另一种 kind 的调用以 `directory-picker-unavailable` 应答。联合之所以可辨识,是因为后端差异在**交互形态**——压平成统一方法集会逼每个后端伪装另一方的形态。
 
-**client 侧靠 slot 组合,而非按广播分支。** ui-workspace 的两个触发表层各自声明一个 `single` 目录流洞(`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`;之所以是两个 key,是因为一个洞只有一个声明它的 slot entry——owner 约定相同、占用者相同)。后端包是**双面包**:浏览器一侧把匹配的交互注册进两个洞——`-native` 是驱动 `host.pickDirectory` 的无渲染占用者,`-browse` 是应用内的选择工作区目录对话框。洞的 owner 会话(`open`/`busy`/`onPicked`/`onCancel`/`onError`)承载整个交换:ui-workspace 保留触发(菜单入口仅在洞被占用时渲染)与接纳(`createWorkspace({path})`、可重试的错误对话框、重新选择),占用者持有从 `open` 到所选路径之间的一切。因此一行 `cordis.yml` 同时切换宿主能力与 client 流程;错配在构造上不可能,同时挂两个流程包会在 client 加载期失败(`single` 洞)。早先的 `host.describe.directoryPicker` 广播与客户端 kind 分支被删除——组合已经接好两侧后,供客户端分支用的 wire 事实不再有任何消费者。洞注册表(`ctx.slots.entries`)取而代之,成为每次打开菜单的占用读取。
+**client 侧靠 slot 组合,而非按广播分支。** ui-workspace 的两个触发表层各自声明一个 `single` 目录流洞(`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`;之所以是两个 key,是因为一个洞只有一个声明它的 slot entry——owner 约定相同、占用者相同)。后端包是**双面包**:浏览器一侧把匹配的交互注册进两个洞——`-native` 是驱动 `directoryPicker/pick` 的无渲染占用者,`-browse` 是应用内的选择工作区目录对话框。洞的 owner 会话(`open`/`busy`/`onPicked`/`onCancel`/`onError`)承载整个交换:ui-workspace 保留触发(菜单入口仅在洞被占用时渲染)与接纳(`createWorkspace({path})`、可重试的错误对话框、重新选择),占用者持有从 `open` 到所选路径之间的一切。因此一行 `cordis.yml` 同时切换宿主能力与 client 流程;错配在构造上不可能,同时挂两个流程包会在 client 加载期失败(`single` 洞)。早先的 `host.describe.directoryPicker` 广播与客户端 kind 分支被删除——组合已经接好两侧后,供客户端分支用的 wire 事实不再有任何消费者。洞注册表(`ctx.slots.entries`)取而代之,成为每次打开菜单的占用读取。
 
 并入本决策的位置与策略裁决:
 
@@ -31,7 +32,7 @@ web GUI 的「打开本地文件夹」流程被焊死在一种交互上:`host.
 
 - **给 `ctx.fs` 增加浏览方法。** 否决:上述权限域耦合;且面向展示的列举约定(hidden 标志、面包屑、home 锚点)不属于存储 seam。
 - **统一的 Service Definition 方法集(`pick(): path`)。** 否决:应用内浏览器无法藏在一次宿主侧调用后面——浏览循环在客户端,需要协议上的原语;而原生选择器实现不了原语。交互差异不可约,故用判别标签。
-- **apiproxy 里直接调标准库(不建 seam)。** 否决:换装点仍是改网关源码,失去 fixture(测试前置数据)/测试后端,与促成这项工作的插件教义相悖。
+- **API adapter 里直接调标准库(不建 seam)。** 否决:换装点仍是改 adapter 源码,失去 fixture(测试前置数据)/测试后端,与促成这项工作的插件教义相悖。
 - **引入文件管理器/盘符枚举依赖。** 按上文调研否决;依赖政策要求记录于此。
 - **动作标签随状态翻转的「显示隐藏」开关(「隐藏隐藏文件」)。** 否决:会翻转的动作标签在状态与动作之间有歧义,还把否定叠了两层;固定标签加按下态呈现一次说清两者。
 - **纯 relatedTarget 失焦取消(不做 mousedown 抑制)。** 否决:Safari 在指针按下时不给按钮聚焦,点击触发的 focusout 因而携带空 `relatedTarget`,会在点击落地前就取消编辑器;编辑期作用的 mousedown 抑制加上锚定卡片的 relatedTarget 守卫才能同时覆盖指针与键盘路径。
@@ -43,6 +44,6 @@ web GUI 的「打开本地文件夹」流程被焊死在一种交互上:`host.
 ## 后果
 
 - `cordis.yml` 决定交互形态;`apps/cli` 挂 [`-auto` 选择器](../feature/2026-07-29-directory-picker-adaptive-default.zh.md),它在启动时判定宿主处境并自行挂载 `-native` 或 `-browse`,一行仍同时切换后端与 UI;直接组合某个后端行即固定交互。
-- 协议新增 `host.listDirectory`/`host.createDirectory` 与四个错误码;connection fixture 提供确定性浏览树与确定性 `pickDirectory` 路径供无密钥组装测试使用。
+- 协议公开生成的 `directoryPicker/list` 与 `directoryPicker/createDirectory` 方法及四个错误码;Connection fixture 提供确定性浏览树与 `directoryPicker/pick` 结果供无密钥组装测试使用。
 - 未来的新交互(或提供 `native` 交互的 Electron 提供方)只是一个双面后端包——无需网关手术,也不动 ui-workspace。
 - `ApiProxyDefaults.pickDirectory`(仅测试注入)删除;测试像提供其他服务一样提供 stub `ctx.directoryPicker`。

+ 6 - 0
.agents/notes/archived/architecture/2026-07-28-user-settings-seam.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 .agents/notes/archived/architecture/2026-07-28-user-settings-seam.md
+2026-07-28-user-settings-seam.md: 75fef5ed976d80acb4e26133c2b98d8504042121
+2026-07-28-user-settings-seam.zh.md: 9797e5a3288a7709f3541784778c514890015410

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-28-user-settings-seam.md → .agents/notes/archived/architecture/2026-07-28-user-settings-seam.md

@@ -1,6 +1,7 @@
 # Agent Note: user-settings seam (`ctx.settings`) and the file provider
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-28-user-settings-seam.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-28-user-settings-seam.zh.md → .agents/notes/archived/architecture/2026-07-28-user-settings-seam.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 用户设置 seam(`ctx.settings`)与文件提供方
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-28-user-settings-seam.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-29-package-regrouping.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 .agents/notes/archived/architecture/2026-07-29-package-regrouping.md
+2026-07-29-package-regrouping.md: 7073d868fe73f75477cf9a6fa2c5f7c425d014a8
+2026-07-29-package-regrouping.zh.md: b33ecf1600ce9c9796e932c14c5df9782bb3a94b

+ 3 - 2
.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md → .agents/notes/archived/architecture/2026-07-29-package-regrouping.md

@@ -1,6 +1,7 @@
 # Agent Note: Regroup packages/ by measured clustering
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-29-package-regrouping.zh.md)
 
@@ -21,13 +22,13 @@ Five regrouping decisions remain current; every other group keeps its prior boun
 
 | Group | Members (folder names) | From |
 |---|---|---|
-| `session/` | session-persistence, session-persistence-jsonl, session-persistence-sqlite, session-checkpoint-policy, session-projection, session-projection-cache, session-title, session-title-llm, session-title-first-prompt-llm, session-title-all-prompts-llm, session-telemetry, session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` |
+| `session/` | session-persistence, session-persistence-jsonl, session-checkpoint-policy, session-projection, session-projection-cache, session-title, session-title-llm, session-title-first-prompt-llm, session-title-all-prompts-llm, session-telemetry, session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` |
 | `interaction/` | user-questions, user-approval, permission-presets, tool-ask-user, commands, tui | `ui/` |
 | `boot/` | app-boot | `ui/` |
 | `guard/` | repeat-tool-reminder, timeout-policy | `guard/` + `timeout/` |
 | `extensions/` | tool-cordis | `cordis/` |
 
-- **`session/`** is the durable session data plane: the persistence seam with its backends and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals.
+- **`session/`** is the durable session data plane: the persistence seam with its JSONL provider and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals.
 - **`interaction/`** is the human-collaboration plane plus the terminal channel that answers it: the question/approval seams, the permission preset, the model-facing `ask_user_question` tool, the human-command registry (`plan-mode` and `command-goal` already consume `commands` together with the interaction seams), and `tui` — the interactive channel is the plane's richest provider and consumer (peer edges to `commands` and `user-questions`), and a one-package `tui/` group would spend a top-level name on one plugin.
 - **`boot/`** is a role-complete single-package group: the shared boot glue that belongs to no channel and no assembly (consumed by `apps/cli` and test-only Loader drivers).
 - **`guard/`** keeps its documented role, loop-hygiene guards, and gains the tool-call timeout enforcer, dissolving the one-package `timeout/` group whose name collided with `util/timeout`.

+ 3 - 2
.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md → .agents/notes/archived/architecture/2026-07-29-package-regrouping.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 按实测聚类重新划分 packages/ 分组
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-29-package-regrouping.md) | 中文
 
@@ -21,13 +22,13 @@ Status: implemented
 
 | 组 | 成员(目录名) | 来源 |
 |---|---|---|
-| `session/` | session-persistence、session-persistence-jsonl、session-persistence-sqlite、session-checkpoint-policy、session-projection、session-projection-cache、session-title、session-title-llm、session-title-first-prompt-llm、session-title-all-prompts-llm、session-telemetry、session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` |
+| `session/` | session-persistence、session-persistence-jsonl、session-checkpoint-policy、session-projection、session-projection-cache、session-title、session-title-llm、session-title-first-prompt-llm、session-title-all-prompts-llm、session-telemetry、session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` |
 | `interaction/` | user-questions、user-approval、permission-presets、tool-ask-user、commands、tui | `ui/` |
 | `boot/` | app-boot | `ui/` |
 | `guard/` | repeat-tool-reminder、timeout-policy | `guard/` + `timeout/` |
 | `extensions/` | tool-cordis | `cordis/` |
 
-- **`session/`** 是持久会话数据平面:持久化 seam 连同其各后端与检查点策略、从该日志折叠(fold)出全量值并对外提供的投影、基于日志的标题,以及 OTel 上报。标题折叠本身就是读取侧的承重构件(`session-query` 对 `dsh-session-title` 声明对等依赖),所以标题属于数据平面,而非某个「派生服务」附属区。用这个朴素的名字是有意为之(名字要像人起的);旁边的 `core/session` 包仍是常驻内存的实时服务,本组则是围绕它的持久家族。`session-query/` 保持独立成组:这个读取/工具面自带模型工具和 SQLite FTS 后端,其消费不依赖持久化内部实现。
+- **`session/`** 是持久会话数据平面:持久化 seam 连同其 JSONL provider 与检查点策略、从该日志折叠(fold)出全量值并对外提供的投影、基于日志的标题,以及 OTel 上报。标题折叠本身就是读取侧的承重构件(`session-query` 对 `dsh-session-title` 声明对等依赖),所以标题属于数据平面,而非某个「派生服务」附属区。用这个朴素的名字是有意为之(名字要像人起的);旁边的 `core/session` 包仍是常驻内存的实时服务,本组则是围绕它的持久家族。`session-query/` 保持独立成组:这个读取/工具面自带模型工具和 SQLite FTS 后端,其消费不依赖持久化内部实现。
 - **`interaction/`** 是人机协作平面加上应答它的终端通道:提问/批准 seam、权限预设、面向模型的 `ask_user_question` 工具、人类命令注册表(`plan-mode` 与 `command-goal` 已经把 `commands` 和各交互 seam 放在一起消费),以及 `tui`——这个交互通道是该平面功能最丰富的提供方与消费方(对 `commands` 与 `user-questions` 均有对等依赖边),而一个单包 `tui/` 组会把一个顶层名字花在一个插件上。
 - **`boot/`** 是角色完备的单包组:不归属任何通道也不归属任何组装的共享 boot 胶水(被 `apps/cli` 与仅限测试的 Loader driver 消费)。
 - **`guard/`** 保留其文档记载的角色(循环卫生守卫),并新纳入强制执行工具调用超时的包;那个与 `util/timeout` 撞名的单包组 `timeout/` 随之解散。

+ 6 - 0
.agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.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 .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.md
+2026-07-29-request-level-llm-config-credentials.md: dda5cd6710ad432a25bce1214078098f4e797559
+2026-07-29-request-level-llm-config-credentials.zh.md: e521920586c8fe9d3a498e0369d1693096562973

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-29-request-level-llm-config-credentials.md → .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.md

@@ -1,6 +1,7 @@
 # Agent Note: request-level LLM configuration and the credential seam
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-29-request-level-llm-config-credentials.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-29-request-level-llm-config-credentials.zh.md → .agents/notes/archived/architecture/2026-07-29-request-level-llm-config-credentials.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 请求级 LLM 配置与凭据 seam
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-29-request-level-llm-config-credentials.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.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 .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.md
+2026-07-30-adapter-owned-max-token-defaults.md: 30b1840a9ff80092609698e63d061a7d82503e5d
+2026-07-30-adapter-owned-max-token-defaults.zh.md: 770c61ce1514fa8e16d4cbabed4dd97b5b7b40fb

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-30-adapter-owned-max-token-defaults.md → .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.md

@@ -1,6 +1,7 @@
 # Agent Note: Adapter-owned max-token defaults
 
 Status: implemented
+Archived: 2026-09-04
 
 English | [中文](2026-07-30-adapter-owned-max-token-defaults.zh.md)
 

+ 1 - 0
.agents/notes/implemented/architecture/2026-07-30-adapter-owned-max-token-defaults.zh.md → .agents/notes/archived/architecture/2026-07-30-adapter-owned-max-token-defaults.zh.md

@@ -1,6 +1,7 @@
 # Agent Note: 适配器持有的最大 token 默认值
 
 Status: implemented
+Archived: 2026-09-04
 
 [English](2026-07-30-adapter-owned-max-token-defaults.md) | 中文
 

+ 6 - 0
.agents/notes/archived/architecture/2026-07-30-client-locale-full-rollout.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 .agents/notes/archived/architecture/2026-07-30-client-locale-full-rollout.md
+2026-07-30-client-locale-full-rollout.md: 381a0ed642d7452cb4be2c9a755bf33c120cba76
+2026-07-30-client-locale-full-rollout.zh.md: 81ae013e86c6ecb608a15c3664c5203233e27e40

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác