Browse Source

Merge branch 'master' into fix/sidebar-document-preview-polish

Yifffan 1 tháng trước cách đây
mục cha
commit
1f76250952
100 tập tin đã thay đổi với 796 bổ sung và 160 xóa
  1. 2 2
      .agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml
  2. 2 0
      .agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md
  3. 2 0
      .agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml
  5. 1 1
      .agents/notes/implemented/architecture/2026-06-18-session-surface.md
  6. 1 1
      .agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md
  7. 2 2
      .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
  8. 2 2
      .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md
  9. 1 1
      .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md
  10. 1 1
      .agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml
  11. 1 1
      .agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md
  12. 1 1
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
  13. 0 0
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
  14. 2 2
      .agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml
  15. 6 10
      .agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md
  16. 6 10
      .agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md
  17. 1 1
      .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml
  18. 1 1
      .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md
  19. 2 2
      .agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml
  20. 3 3
      .agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md
  21. 1 1
      .agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md
  22. 1 1
      .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml
  23. 1 1
      .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md
  24. 2 2
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml
  25. 4 6
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md
  26. 4 6
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md
  27. 2 2
      .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml
  28. 0 1
      .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md
  29. 0 1
      .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md
  30. 2 2
      .agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml
  31. 4 4
      .agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md
  32. 4 4
      .agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md
  33. 2 2
      .agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml
  34. 3 3
      .agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md
  35. 3 3
      .agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md
  36. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml
  37. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
  38. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml
  39. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md
  40. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml
  41. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md
  42. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml
  43. 1 1
      .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md
  44. 3 3
      .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml
  45. 29 0
      .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md
  46. 29 0
      .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md
  47. 1 1
      .agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml
  48. 2 2
      .agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md
  49. 2 2
      .agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml
  50. 8 0
      .agents/notes/implemented/feature/2026-08-05-agent-teams.md
  51. 8 0
      .agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md
  52. 1 1
      .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml
  53. 1 1
      .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md
  54. 1 1
      .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml
  55. 3 3
      .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md
  56. 1 1
      .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml
  57. 2 2
      .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md
  58. 1 1
      .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml
  59. 2 2
      .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md
  60. 6 0
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml
  61. 39 0
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md
  62. 39 0
      .agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md
  63. 6 0
      .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml
  64. 100 0
      .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md
  65. 100 0
      .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md
  66. 2 2
      .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml
  67. 2 0
      .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md
  68. 2 0
      .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md
  69. 2 2
      .agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml
  70. 1 1
      .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md
  71. 1 1
      .agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md
  72. 6 0
      .agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml
  73. 37 0
      .agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md
  74. 37 0
      .agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md
  75. 1 1
      .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml
  76. 1 1
      .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md
  77. 2 2
      .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml
  78. 1 1
      .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md
  79. 1 1
      .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md
  80. 2 2
      .agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.i18n.yaml
  81. 7 7
      .agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md
  82. 7 7
      .agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md
  83. 2 2
      .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml
  84. 2 2
      .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md
  85. 2 2
      .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md
  86. 6 0
      .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml
  87. 3 3
      .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md
  88. 1 1
      .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md
  89. 2 2
      .agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml
  90. 3 3
      .agents/notes/implemented/simplification/2026-09-07-file-content-scan.md
  91. 3 3
      .agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md
  92. 6 0
      .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml
  93. 39 0
      .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md
  94. 39 0
      .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md
  95. 6 0
      .agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml
  96. 51 0
      .agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md
  97. 51 0
      .agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md
  98. 1 1
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml
  99. 1 1
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md
  100. 2 2
      .agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml

+ 2 - 2
.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.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/implemented/architecture/2026-06-13-twin-llm-adapters.md
-2026-06-13-twin-llm-adapters.md: a4c87325a0b0d1ebe6cf8f95672e5de74ef37d57
-2026-06-13-twin-llm-adapters.zh.md: 36996750a16cc95393d727cffee3bcc53573eaf2
+2026-06-13-twin-llm-adapters.md: fe8b0b55e0e027e29eb0920e64a1760d0dc35aee
+2026-06-13-twin-llm-adapters.zh.md: 4248b5afeb9f3fd4503914e53203c674e3a80dbf

+ 2 - 0
.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md

@@ -25,3 +25,5 @@ The rule they enforce: **anything the StreamChunk vocabulary cannot express for
 ## Consequences
 
 The twin doubles adapter and key-gated e2e maintenance—both cover V4 Flash and Pro across representative reasoning modes—in exchange for continuous seam-neutrality validation and a second implementation example. Both use `apiKey`, `baseURL`, and `models`; the direct-fetch adapter exposes `thinking`/`reasoningEffort`, while pi-ai exposes one `reasoning` level. A future conformance suite could justify retiring one adapter through a superseding Agent Note.
+
+The [Messages adapter](../feature/2026-09-07-deepseek-messages-adapter.md) adds an Anthropic-protocol implementation inside `llm-deepseek`; it preserves the same stream conventions.

+ 2 - 0
.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md

@@ -25,3 +25,5 @@ Status: implemented
 ## 后果
 
 孪生体使适配器和需要密钥的 e2e 维护量翻倍——两者都覆盖 V4 Flash 和 Pro 在各代表性推理(reasoning)模式下的行为——换来的是持续的 seam 中立性验证和第二份实现示例。两个适配器均使用 `apiKey`、`baseURL` 和 `models`;直接 fetch 适配器暴露 `thinking`/`reasoningEffort`,pi-ai 适配器暴露一个 `reasoning` 级别。未来如果有一致性测试套件,可以通过后续 Agent Note 论证退役其中一个适配器。
+
+[Messages 适配器](../feature/2026-09-07-deepseek-messages-adapter.zh.md) 在 `llm-deepseek` 内增加 Anthropic 协议实现,并遵守相同的流约定。

+ 2 - 2
.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-18-session-surface.md
-2026-06-18-session-surface.md: 93ea55883dedd943fe1ffac67a9842c962ca6dac
-2026-06-18-session-surface.zh.md: 54ecb1162bc46007dfcbb7d8cb39075d52171567
+2026-06-18-session-surface.md: 26780d54a1ea12283900572c6fd0e8f740de5524
+2026-06-18-session-surface.zh.md: 328a9ee9f1bed9c52996cfd307549a7745e16d4b

+ 1 - 1
.agents/notes/implemented/architecture/2026-06-18-session-surface.md

@@ -37,7 +37,7 @@ Delta processing is O(1) when no new events and O(new events) when new events ar
 
 ### Persistence
 
-The fields are serialized as top-level JSON properties. JSONL preserves placement and provenance without a separate column mapping. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns exact replacement keys and strict-acceptance rationale; the [V2-to-V3 specification](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes) owns historical conversion. This note retains ordered-projection ownership and replacement rationale.
+The fields are serialized as top-level JSON properties. JSONL preserves placement and source-event references without a separate column mapping. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns exact replacement keys and strict-acceptance rationale; the [V2-to-V3 specification](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes) owns historical conversion. This note retains ordered-projection ownership and replacement rationale.
 
 ### Crash recovery
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md

@@ -37,7 +37,7 @@ surface 元数据仅属于四种 surface 事件类型(`system/message`、`user
 
 ### 持久化
 
-这些字段作为顶层 JSON 属性序列化。JSONL 无需单独列映射即可保留位置与来源。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责精确替换键与严格准入依据;[V2 到 V3 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)负责历史转换。本文继续负责有序投影的所有权与替换依据。
+这些字段作为顶层 JSON 属性序列化。JSONL 无需单独列映射即可保留位置与源事件引用。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责精确替换键与严格准入依据;[V2 到 V3 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)负责历史转换。本文继续负责有序投影的所有权与替换依据。
 
 ### 崩溃恢复
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.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/implemented/architecture/2026-07-05-reconstructable-requests.md
-2026-07-05-reconstructable-requests.md: 2f88675a75a72e7fbf105dfbf4f337a4dd80948a
-2026-07-05-reconstructable-requests.zh.md: 90008502a6651e38c142b7fb88052c05d46dea76
+2026-07-05-reconstructable-requests.md: f47d0938b91edf6c9b544223ac44925f02dd8c1f
+2026-07-05-reconstructable-requests.zh.md: d2499a6264a3e6bebf31944d8eb056265e082c68

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md

@@ -22,9 +22,9 @@ Prefix-cache stability is corollary #1, not the headline: an append-only log pro
 
 **Messages.** `Session.deriveMessages()` is cached: each surface entry is projected exactly once, when first seen, through the public per-event function `deriveEventMessage(event)`; a surface rewrite (a compaction `replace` — `SurfaceManager.replaceGeneration`) rebuilds. Callers get a fresh array per call over shared, deep-frozen messages: mutating logged history through a projection is unrepresentable (it throws), replacing the old clone-per-call isolation. External reconstructors fold the same public function over a log prefix, so no two paths can disagree.
 
-`EpochHeader` records the request's non-history state: call config and tool schemas. Writers omit `tools: []` and `adapterDefaults: {}`; current acceptance rejects those fields and any `header.system`, rather than repairing them. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain intact. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. The rendered system prompt is derived history — the `system/message` event at surface node 0, per the [surface-node Agent Note](2026-09-02-system-prompt-as-surface-node.md) — so a prompt change is a surface replacement rather than a header change. Adapter-supplied effort and token defaults retain their `adapterDefaults` provenance; a Web model selection restored from the log omits an adapter-owned effort so the next resolution cannot reclassify the same effective config as an explicit selection and a false change. `request/header` always writes a full snapshot: the first loop instance uses reason `initial`, later instances use `resume`, an in-instance change uses `change`, and an unchanged envelope beginning an explicitly declared message series or following a surface replacement uses `series`. A `change` snapshot carries `startsSeries: true` when the changed request also starts a series, preserving the two independent facts without a duplicate header. Ordinary append-only later Turns, further same-series Steps, and retries inherit the latest snapshot. `foldRequestHeader` selects the latest snapshot. Legacy `request/header-delta` events and the removed `fallback` reason are rejected when appended or loaded.
+`EpochHeader` records the request's non-history state: call config and tool schemas. Writers omit `tools: []` and `adapterDefaults: {}`; current acceptance rejects those fields and any `header.system`, rather than repairing them. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain intact. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. The rendered system prompt is derived history — the `system/message` event at surface node 0, per the [surface-node Agent Note](2026-09-02-system-prompt-as-surface-node.md) — so a prompt change is a surface replacement rather than a header change. Adapter-supplied effort and token defaults retain their `adapterDefaults` ownership; a Web model selection restored from the log omits an adapter-owned effort so the next resolution cannot reclassify the same effective config as an explicit selection and a false change. `request/header` always writes a full snapshot: the first loop instance uses reason `initial`, later instances use `resume`, an in-instance change uses `change`, and an unchanged envelope beginning an explicitly declared message series or following a surface replacement uses `series`. A `change` snapshot carries `startsSeries: true` when the changed request also starts a series, preserving the two independent facts without a duplicate header. Ordinary append-only later Turns, further same-series Steps, and retries inherit the latest snapshot. `foldRequestHeader` selects the latest snapshot. Legacy `request/header-delta` events and the removed `fallback` reason are rejected when appended or loaded.
 
-Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze provenance decision](../simplification/2026-09-06-agent-request-freeze-provenance.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header.
+Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze evidence decision](../simplification/2026-09-06-agent-request-freeze-evidence.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header.
 
 **The open step is the reconstruction boundary.** Its entered `user/message` batch and any newly written `request/header` precede request dispatch. Injection after the atomic claim joins a later request, while a listener that must affect this request returns messages through `agent/pre-step`. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md

@@ -24,7 +24,7 @@ Status: implemented
 
 `EpochHeader` 记录请求的非历史状态:调用配置和工具 schema。写入方省略 `tools: []` 与 `adapterDefaults: {}`;当前接纳拒绝这些字段以及任何 `header.system`,而不修复它们。仅含空白的系统消息内容、`config.stop: []` 与嵌套扩展保持原样。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责历史转换。渲染后的系统提示词是派生历史——surface 第 0 号节点上的 `system/message` 事件,见[surface 节点 Agent Note](2026-09-02-system-prompt-as-surface-node.zh.md)——因此提示词变更是 surface 替换而不是 header 变更。适配器提供的推理强度与 token 默认值会保留其 `adapterDefaults` 来源信息;Web 从日志恢复模型选择时会省略适配器持有的推理强度,因此下一次解析不会把相同的有效配置重新归类为显式选择并产生虚假变更。`request/header` 始终写入完整快照:首个循环实例使用 reason `initial`,后续实例使用 `resume`,实例内变更使用 `change`,内容未变的封装显式开启消息序列或跟随表层替换时使用 `series`。如果发生变化的请求同时开启序列,`change` 快照会携带 `startsSeries: true`,无需重复 header 即可保留这两个独立事实。普通的仅追加后续 Turn、同一序列内后续的 Step 与重试沿用最新快照。`foldRequestHeader` 选择最新快照。旧的 `request/header-delta` 事件和已移除的 `fallback` reason 在追加或加载时都会被拒绝。
 
-每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结来源证明决策](../simplification/2026-09-06-agent-request-freeze-provenance.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。
+每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结证据决策](../simplification/2026-09-06-agent-request-freeze-evidence.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。
 
 **已打开步骤是重建边界。** 进入步骤的 `user/message` 批次与任何新写入的 `request/header` 都位于请求分派之前。原子领取后发生的注入加入后续请求;必须影响本次请求的监听器则通过 `agent/pre-step` 返回消息。header 重建选择该步骤的 `request/header`,或在无新 header 写入时沿用前一个快照。
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.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/implemented/architecture/2026-07-08-tool-output-spill-files.md
-2026-07-08-tool-output-spill-files.md: a80b6cdcb0e731a687d511d38ea288a68a298173
+2026-07-08-tool-output-spill-files.md: fa6697aad63c97b0ef580fe1ca46e50541e3d682
 2026-07-08-tool-output-spill-files.zh.md: a995eafc7878b342a2164c5116da1f43ada791a5

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md

@@ -22,7 +22,7 @@ A thin spill storage seam plus a default spill policy plugin, in a new `packages
 | `@deepseek-ai/dsh-spill-local` | Local backend: private, session-scoped file storage on the host filesystem. |
 | `@deepseek-ai/dsh-spill-policy` | Tool-result policy plugin: wraps final text results after dispatch and replaces oversized results with a retained preview plus a spill locator. |
 
-The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../bug-fix/2026-09-05-session-reference-spill-reuse.md) adds a direct storage consumer with separate preview, provenance, and failure semantics; it does not change the tool-result policy.
+The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../bug-fix/2026-09-05-session-reference-spill-reuse.md) adds a direct storage consumer with separate preview, source-description, and failure semantics; it does not change the tool-result policy.
 
 ### Spill seam
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.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/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
-2026-07-10-single-file-executable-sdk-runtime-distribution.md: 8c55c142137f0ab24869bd57ffed3835b7b0516a
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: e8c38a7b96cbff9feae2271fcd9a5c41087c62c4
 2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 52ca2e13901d9469e2f9663936acb766a934e8f7

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


+ 2 - 2
.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.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/implemented/architecture/2026-07-28-portable-execution-world-consumers.md
-2026-07-28-portable-execution-world-consumers.md: 787fe341e58cc212c99e0f35f07eea8e83daf000
-2026-07-28-portable-execution-world-consumers.zh.md: a558a5af64437b8743e741ace4ccf27079501721
+2026-07-28-portable-execution-world-consumers.md: 3375a38d41724aee9b3f7591b014d6eea6979857
+2026-07-28-portable-execution-world-consumers.zh.md: 62d7338d237358b05c0c52f97720312474b74717

+ 6 - 10
.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md

@@ -26,19 +26,15 @@ Generic consumers use that execution world:
 - `dsh-lsp-stdio` reads and contains source through `ctx.fs`, resolves and launches language servers through `ctx.subprocess`, and carries provider-owned file URIs through initialization and result rendering. One provider-lifetime signal aborts filesystem and protocol work during disposal, including workspace lookup before queue ownership; its JSON-RPC, pooling, synchronization, and normalization stay unchanged.
 - `dsh-terminal-bash` maps persistent-shell semantics onto `ctx.subprocess.spawnTerminal()`. The local `node-pty` and process-inspection implementation moves into `dsh-subprocess-local`; another subprocess provider supplies the same primitive. `danger-full-access` needs no `ctx.sandbox`; a confined mode requires a same-world sandbox provider and fails before spawn when none is mounted. Prompt and silence evidence collected during asynchronous pre-write inspection is discarded when the provider write begins. Cancellation retains the send reservation while an in-flight write settles and then signals the foreground group, so late bytes or the signal cannot target a successor; an in-flight readiness poll cannot release that reservation, and a rejected write sends no signal. The absolute deadline remains armed throughout cancellation. A signal failure becomes terminal transport failure. Completion of a stale inspection resumes polling for the current send. Startup cancellation begins terminal rollback without waiting for a stalled readiness or signalling call. Close rejects new public signals and delegates provider-managed session quiescence to the handle's awaited termination operation.
 
-## E2B POC boundary
+## Remote provider ownership
 
-The opt-in E2B realization has exactly three provider-specific packages under `packages/e2b/`: `dsh-e2b` creates one sandbox and deletes it on timeout or disposal, `dsh-fs-e2b` implements `ctx.fs`, and `dsh-subprocess-e2b` implements `ctx.subprocess` over E2B Commands, PTYs, and remote Linux process groups. The two adapters obtain the sole SDK handle from the owner and never create private sandboxes.
+The [E2B provider removal](../simplification/2026-09-11-remove-e2b-providers.md) supersedes the E2B realization of this decision. The filesystem/subprocess agreement and asynchronous terminal contracts remain in force for remote implementations.
 
-E2B owns the mutable filesystem, managed command and Bash processes, terminal allocation and terminal-session groups, language-server processes and source reads, and adapter-private files under `.dsh-e2b`. The host owns Cordis and plugin objects, the agent loop, agent/session/goal state, session logs and persistence, LLM calls, prompts and tools, authority, skills, subagent orchestration, PTY buffers and readiness, LSP protocol state, and E2B SDK/network buffers. The overlay neither uploads nor synchronizes the host workspace.
-
-The adapters retain only substrate mechanics. Filesystem canonicalization crosses the SDK's decoded command transport as strict base64-encoded NUL framing; streamed reads leave byte ceilings with consumers. Subprocess command output and environment snapshots use ASCII/base64 where SDK chunk decoding would otherwise lose bytes, while private control shells isolate profiles and later launches blank discovered credential-shaped names. Process and terminal cleanup uses remote groups and proves quiescence before settlement.
-
-Sandbox state is deliberately ephemeral: timeout and disposal delete the remote files and unmanaged state. The POC adds no reconnect or pause/leave retention, session-persistence backend, template builder, volume, snapshot, network-policy layer, sandbox catalog, workspace synchronization, durable remote handles, or whole-harness execution.
+A remote provider owns mutable files, command and terminal processes, language-server processes, and provider-private runtime files. The host owns Cordis and plugin objects, the agent loop, agent/session/goal state, session logs and persistence, model transport, authority, skills, subagent orchestration, terminal readiness and LSP protocol state. Moving execution does not imply workspace synchronization or durable remote handles.
 
 ## Verification
 
-Focused package suites pin sandbox lifecycle, canonical path framing, filesystem metadata and atomic versions, subprocess publication/rollback, terminal text I/O and session cleanup, output limits, cancellation, disposal, and invariant registration. A credential-gated Loader composition exercises the same three-package provider through source imports and built exports, including FS/Bash visibility, hostile login profiles, byte-split UTF-8 output, process and terminal cleanup, LSP queries, host-workspace isolation, and final sandbox deletion.
+The local filesystem, subprocess, terminal and LSP suites cover path identity, executable lookup, managed cleanup, output limits, cancellation and disposal. Terminal consumer tests retain delayed provider writes, foreground inspection and signalling to verify asynchronous ownership without requiring a remote service.
 
 ## Alternatives considered
 
@@ -60,7 +56,7 @@ Focused package suites pin sandbox lifecycle, canonical path framing, filesystem
 
 **Implement remote filesystem operations only through shell commands.** Rejected because that discards structured filesystem identity, errors, streaming, version guards, and atomic mutation semantics already consumed by the file tools.
 
-**Add a generic distributed-runtime abstraction or reconnect live handles.** Rejected because the existing capability seams carry the demonstrated contracts, while remote identity alone cannot reconstruct callbacks, pending promises, authority, protocol state, or output cursors. A new layer would speculate about persistence and synchronization beyond the POC.
+**Add a generic distributed-runtime abstraction or reconnect live handles.** Rejected because the existing capability seams carry the demonstrated contracts, while remote identity alone cannot reconstruct callbacks, pending promises, authority, protocol state, or output cursors. A new layer would speculate about persistence and synchronization beyond the demonstrated consumer contracts.
 
 ## Consequences
 
@@ -70,4 +66,4 @@ The fundamental interfaces are wider, and a filesystem/subprocess pair must agre
 
 The local implementation absorbs `node-pty` and platform process inspection because it owns local terminal mechanics. On supported Linux hosts, the user-systemd scope retains descendants that call `setsid` or reparent, while process inspection continues to own foreground attribution and synchronous fallback evidence. Other hosts use the observational teardown: disposal sweeps descendants before and after terminating the top-level shell, waits for exact PID-identity-fenced descendants retained during foreground inspection, and retains Linux session members that survive top-level exit. macOS cannot enumerate a POSIX session after its leader exits, so a child that reparents between inspection snapshots remains an explicit local-provider limitation rather than a reason to move process mechanics back into the PTY consumer.
 
-The E2B composition demonstrates that a shared sandbox owner plus filesystem and subprocess adapters are sufficient to move the mutable coding world off-host while leaving higher capabilities provider-neutral. Its POC limits remain explicit: the SDK retains complete command transport in host memory, remote startup cannot publish a PID synchronously, exact terminal stdin-wait and independent signal facts are unavailable, numeric PID/PGID operations are not identity-fenced, the initial environment probe cannot hide unknown sandbox-default secrets from already-running same-UID processes, and adapter artifacts remain until sandbox deletion. These are provider constraints, not justification for compatibility shims or more E2B packages.
+Remote implementations must preserve the existing consumer contracts or reject unsupported operations explicitly. Transport-specific buffering, identity and disconnect limits belong to the provider; they do not justify duplicate Bash, PTY or LSP implementations.

+ 6 - 10
.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md

@@ -26,19 +26,15 @@ Status: implemented
 - `dsh-lsp-stdio` 通过 `ctx.fs` 读取源文件并验证包含关系,通过 `ctx.subprocess` 解析和启动语言服务器,并让由提供方负责的文件 URI 贯穿初始化与结果渲染。一个提供方生命周期信号会在资源释放期间中止文件系统与协议操作,包括取得队列所有权之前的工作区查找;其 JSON-RPC、池化、同步和规范化保持不变。
 - `dsh-terminal-bash` 把持久 shell 语义映射到 `ctx.subprocess.spawnTerminal()`。本地 `node-pty` 与进程检查实现移入 `dsh-subprocess-local`;其他进程管理提供方则提供相同原语。`danger-full-access` 不需要 `ctx.sandbox`;受限模式要求同一执行世界中存在沙箱提供方,未挂载时会在 spawn 前失败。提供方开始写入时,系统会丢弃异步写入前检查期间收集的提示符与静默证据。取消会在在途写入结算期间保留发送预留,随后向前台进程组发送信号,因此延迟字节和该信号都无法落到后续发送;在途就绪检查无法释放该预留,写入被拒绝时也不会发送信号。绝对截止时间会在整个取消期间保持启用。信号发送失败会成为终结性传输失败。陈旧检查完成后,会针对当前发送恢复轮询。启动取消会立即开始终端回滚,而不等待停滞的就绪检查或信号发送调用。关闭操作会拒绝新的公开信号,并把由提供方管理的会话完全停稳委托给句柄上须等待的终止操作。
 
-## E2B POC 边界
+## 远程提供方的职责
 
-可选启用的 E2B 实现在 `packages/e2b/` 下恰好只有三个提供方专用包:`dsh-e2b` 创建一个沙箱,并在超时或资源释放时将其删除;`dsh-fs-e2b` 实现 `ctx.fs`;`dsh-subprocess-e2b` 基于 E2B Commands、PTY 和远程 Linux 进程组实现 `ctx.subprocess`。两个适配器都从所有者取得唯一的 SDK 句柄,绝不创建私有沙箱。
+[E2B 提供方移除决策](../simplification/2026-09-11-remove-e2b-providers.zh.md)取代本决策中的 E2B 实现。文件系统/子进程约定与异步终端约定继续适用于远程实现。
 
-E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终端会话组、语言服务器进程与源文件读取,以及 `.dsh-e2b` 下的适配器私有文件。宿主负责 Cordis 与插件对象、agent loop(智能体循环)、agent 状态、会话状态与目标状态、会话日志与持久化、LLM(大语言模型)调用、提示词与工具、权限、skill(技能)、subagent 编排、PTY 缓冲区与就绪状态、LSP 协议状态,以及 E2B SDK/网络缓冲区。该叠加层既不上传,也不同步宿主工作区。
-
-适配器只保留执行基底机制。文件系统规范化以严格的 base64 加 NUL 分帧穿过 SDK 已解码的命令传输;流式读取把字节上限留给消费方执行。进程管理命令输出与环境快照采用 ASCII/base64,避免 SDK 分片解码丢失字节;私有控制 shell 隔离 profile,后续启动会把已发现且名称呈凭据特征的环境变量置空。进程与终端清理使用远程进程组,并在结算前证明完全停稳。
-
-沙箱状态有意保持短暂:超时与资源释放会删除远程文件和非托管状态。该 POC 不提供重新连接、pause/leave 保留、会话持久化后端、模板构建器、卷、快照、网络策略层、沙箱目录、工作区同步、持久远程句柄,也不会在其中运行整个 harness。
+远程提供方负责可变文件、命令与终端进程、语言服务器进程,以及提供方私有运行时文件。宿主负责 Cordis 与插件对象、智能体循环、智能体/会话/目标状态、会话日志与持久化、模型传输、权限、技能、子智能体编排、终端就绪判断和 LSP 协议状态。移动执行位置不意味着工作区同步或持久远程句柄。
 
 ## 验证
 
-聚焦的包测试套件锁定了沙箱生命周期、规范化路径分帧、文件系统元数据与原子版本、进程管理发布/回滚、终端文本 I/O 与会话清理、输出上限、取消、资源释放和不变式注册。一项受凭据门控的 Loader 组合通过源代码导入与构建后导出运行同一套三包提供方组合,其中包括 FS/Bash 可见性、恶意登录 profile、跨字节边界拆分的 UTF-8 输出、进程与终端清理、LSP 查询、宿主工作区隔离,以及最终沙箱删除。
+本地文件系统、子进程、终端与 LSP 测试覆盖路径身份、可执行文件查找、受管清理、输出上限、取消与资源释放。终端消费方测试保留延迟的提供方写入、前台检查和信号发送,以便在无需远程服务时验证异步所有权。
 
 ## 考虑过的替代方案
 
@@ -60,7 +56,7 @@ E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终
 
 **只通过 shell 命令实现远程文件系统操作。** 不予采纳,因为这会丢弃现有文件工具已消费的结构化文件系统身份、错误、流式输出、版本保护和原子变更语义。
 
-**新增通用分布式运行时抽象,或重新连接活跃句柄。** 不予采纳,因为现有能力 seam 已承载经证实的约定,而仅凭远程身份无法重建回调、待处理 promise、权限、协议状态或输出游标。新增一层只会推测 POC 边界之外的持久化与同步问题。
+**新增通用分布式运行时抽象,或重新连接活跃句柄。** 不予采纳,因为现有能力 seam 已承载经证实的约定,而仅凭远程身份无法重建回调、待处理 promise、权限、协议状态或输出游标。新增一层只会推测 已验证消费方约定之外的持久化与同步问题。
 
 ## 后果
 
@@ -70,4 +66,4 @@ E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终
 
 本地实现承接 `node-pty` 和平台进程检查,因为它负责本地终端机制。在受支持的 Linux 宿主上,user-systemd scope 会保留调用 `setsid` 或发生 reparent 的后代,进程检查则继续负责前台归属与同步 fallback 证据。其他宿主使用观察型拆卸:dispose(资源释放)会在终止顶层 shell 前后清理后代进程,等待前台检查期间保留下来且受精确 PID 身份围栏保护的后代进程,并继续追踪在顶层进程退出后仍存活的 Linux 会话成员。macOS 无法在 POSIX 会话 leader 退出后枚举该会话,因此在两次检查快照之间重新设定父进程的子进程仍是明确的本地提供方限制,而不是把进程机制移回 PTY 消费方的理由。
 
-E2B 组合证明,共享沙箱所有者加上文件系统与进程管理适配器,就足以在保持上层能力与提供方无关的同时,把可变编码世界移出宿主。其 POC 限制仍明确在案:SDK 会把完整命令传输内容保留在宿主内存中;远程启动无法同步发布 PID;无法获得精确的终端 stdin 等待状态与独立信号事实;基于数值 PID/PGID 的操作没有身份围栏;初始环境探测无法向已在运行的同 UID 进程隐藏未知的沙箱默认 secret;适配器产物会一直保留到沙箱删除。这些是提供方限制,不是引入兼容性 shim 或更多 E2B 包的理由。
+远程实现必须保留现有消费方约定,或明确拒绝不支持的操作。传输特定的缓冲、身份与断线限制归提供方负责;这些限制不是复制 Bash、PTY 或 LSP 实现的理由。

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md
-2026-08-19-session-projection-mandatory-seam.md: 371faa254809649e93685a8f3ada3e640d5f391e
+2026-08-19-session-projection-mandatory-seam.md: e1c6835311228fcaf7a22140f1db8da748603186
 2026-08-19-session-projection-mandatory-seam.zh.md: 5d6a5169a556febae3f5c960b2d519c324d99863

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md

@@ -25,7 +25,7 @@ The registry provides `stateOf(session, key)` for one typed host state and keeps
 - **Default missing projected state.** This preserves more partial compositions but makes missing host state indistinguishable from a valid empty value. Rejected because official profiles mount the registry and configuration errors must fail explicitly.
 - **Require every contributor at activation.** This makes the key set uniform but unnecessarily couples contribution lifecycle to service activation. Explicit first-access failure preserves the optional registration form without allowing silent degradation.
 - **Use `snapshot()` for every read.** This keeps one method but computes unrelated wire views and encourages consumers to depend on batch transport data for host logic. Rejected in favor of typed single-key state reads.
-- **Send full host values to clients.** This avoids separate view types but exposes provenance and policy knobs that no client consumes. Rejected in favor of explicit cropped views.
+- **Send full host values to clients.** This avoids separate view types but exposes producer identities and policy knobs that no client consumes. Rejected in favor of explicit cropped views.
 - **Broadcast registry additions and removals across Host and mux streams.** The streams have no shared ordering, so clients need tombstones, buffered frames, and baseline retries to reconcile them. Rejected because plugin-key churn does not justify a second synchronization protocol.
 
 ## Consequences

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.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/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md
-2026-08-21-deepseek-llm-api-request-extensions.md: eadbe2a5f17de446c345120f9a6aeeeb531c43b4
-2026-08-21-deepseek-llm-api-request-extensions.zh.md: f45210adbc075c30484754ce7f8c2b65b515be6c
+2026-08-21-deepseek-llm-api-request-extensions.md: a0e38c1da5af32b0ba5e07df632ac4e25148419e
+2026-08-21-deepseek-llm-api-request-extensions.zh.md: 763f418c7bd6029c08027cd5f4705bffd984b9ee

+ 3 - 3
.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md

@@ -30,7 +30,7 @@ The `events` array contains complete canonical `SessionEvent` objects directly.
 
 `@deepseek-ai/dsh-plugin-package-inventory-deepseek` owns the default-on `dsh_plugin_packages` field from the `llm` package family. It reads active non-group entries from the host Loader tree and, for a live requesting Agent, its standing preset tree. Node package resolution locates the owning manifest without requiring a `./package.json` export. Ordinary entries resolve from their owning tree, while a standing preset root mirrors its Loader's intentional harness-base override and nested includes retain their own bases. An anonymous nearest manifest marks a loose module; a named manifest must carry a version. Exact name/version pairs are deduplicated with deterministic ordering; simultaneously active versions remain separate.
 
-Disabled, pending, failed, unloading, disposed, structural, loose non-package, ordinary dependency, programmatic child-fiber, and in-memory dynamic-plugin entries are outside this package inventory. This definition reports package-backed composition facts the runtime can prove instead of inventing provenance for arbitrary callbacks.
+Disabled, pending, failed, unloading, disposed, structural, loose non-package, ordinary dependency, programmatic child-fiber, and in-memory dynamic-plugin entries are outside this package inventory. This definition reports package-backed composition facts the runtime can prove instead of inventing package identity for arbitrary callbacks.
 
 ## Deferred inventory caching
 
@@ -73,11 +73,11 @@ The receiver would also need to traverse the tagged tree, resolve paths into the
 
 ### Why not omit assistant chunks or overlapping event data?
 
-About 98% of the measured v1 real-session events were `assistant/chunk`. Omitting them after reference encoding reduced the complete identity JSON by another 84.79% for late enable and 6.49% for steady state, but prevented lossless reconstruction and left message provenance dangling. V2 embeds compact streams in attempt settlements; `dsh_session_log` still sends every current canonical event whole and does not omit those embedded records. Fuzzy or normalized substitutions have the same reconstruction defect.
+About 98% of the measured v1 real-session events were `assistant/chunk`. Omitting them after reference encoding reduced the complete identity JSON by another 84.79% for late enable and 6.49% for steady state, but prevented lossless reconstruction and left message source-event references dangling. V2 embeds compact streams in attempt settlements; `dsh_session_log` still sends every current canonical event whole and does not omit those embedded records. Fuzzy or normalized substitutions have the same reconstruction defect.
 
 **Keep the upload cursor only in memory.** Rejected because a normal process restart would resend the entire Session. A canonical acceptance event makes restart recovery best-effort durable without another storage backend; the remaining crash window produces allowed duplicates.
 
-**Inventory every live Cordis fiber.** Rejected because programmatic and in-memory fibers have no authoritative npm package provenance. Loader-backed host and preset entries provide exact resolvable package identity.
+**Inventory every live Cordis fiber.** Rejected because programmatic and in-memory fibers have no authoritative npm package identity. Loader-backed host and preset entries provide exact resolvable package identity.
 
 **Cache one process-global list or expire it on a TTL.** Rejected because one immutable list is incorrect for Loader lifecycle and per-Session presets, while a TTL permits stale metadata between expiry boundaries. The deferred epoch design invalidates on the authoritative active-state transition instead.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md

@@ -73,7 +73,7 @@ Status: implemented
 
 ### 为什么不省略 assistant 分片或重叠事件数据?
 
-实测 v1 真实 Session event 中约 98% 为 `assistant/chunk`。在引用编码后省略它们,会让完整 identity JSON 在延迟启用场景进一步减少 84.79%,在稳态场景进一步减少 6.49%,但会阻止无损重建并让 message provenance 悬空。V2 把紧凑 stream 嵌入 attempt settlement;`dsh_session_log` 仍会完整发送每个当前规范 event,且不会省略这些嵌入式 record。模糊或规范化替换也有相同重建缺陷。
+实测 v1 真实 Session event 中约 98% 为 `assistant/chunk`。在引用编码后省略它们,会让完整 identity JSON 在延迟启用场景进一步减少 84.79%,在稳态场景进一步减少 6.49%,但会阻止无损重建并让 message source-event reference 悬空。V2 把紧凑 stream 嵌入 attempt settlement;`dsh_session_log` 仍会完整发送每个当前规范 event,且不会省略这些嵌入式 record。模糊或规范化替换也有相同重建缺陷。
 
 **只在内存中保留上传游标。** 已否决,因为普通进程重启会重发完整会话。权威接受事件让重启恢复获得尽力而为的持久性,无需另一存储后端;剩余崩溃窗口只会产生允许的重复。
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.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/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md
-2026-08-27-adjacent-agent-steer-messaging.md: 7bd97d5c992acac240a28d61d27e35e1f1058ae3
+2026-08-27-adjacent-agent-steer-messaging.md: 47d9bd05c24871a14f78ebfa0afb8c13a6c2754d
 2026-08-27-adjacent-agent-steer-messaging.zh.md: 1fd0fc39b937287ac34a2862c7cb9cde90256c09

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md

@@ -6,7 +6,7 @@ English | [中文](2026-08-27-adjacent-agent-steer-messaging.zh.md)
 
 ## Problem
 
-Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, provenance, and scheduling.
+Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, source attribution, and scheduling.
 
 A continuable child owns its own Session, so its parent does not automatically receive the child's transcript, tool output, or reasoning. The return path must therefore remain explicit and repeatable: a child may send progress before it finishes, remain available after sending, or fail before it can cooperate. Turning every final assistant message into an implicit result would conflate turn completion with model-selected communication and would not cover abnormal endings.
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.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/implemented/architecture/2026-08-27-outbound-proxy-policy.md
-2026-08-27-outbound-proxy-policy.md: 67927a9d1404e0b14c6e420cc5bea462be5c87ad
-2026-08-27-outbound-proxy-policy.zh.md: cf0b203a061522450a2a1b0c337a060b0c387684
+2026-08-27-outbound-proxy-policy.md: 015b6edd3f4153f29db4084c92e8e8da8cad70e2
+2026-08-27-outbound-proxy-policy.zh.md: c7bdcaee138ac41a55733d76da1063b25f9ba633

+ 4 - 6
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md

@@ -6,7 +6,7 @@ English | [中文](2026-08-27-outbound-proxy-policy.zh.md)
 
 ## Problem
 
-Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`. Every other tool a developer runs — curl, git, npm, pip — honours them, so a user behind a proxy exports the variables once and expects everything to follow. The harness did not: `setGlobalDispatcher`, `ProxyAgent`, and `EnvHttpProxyAgent` appeared zero times across `packages/` and `apps/`, so the model request, every web search, `web_fetch`, MCP over HTTP, the OTLP exporter, and the E2B SDK all connected directly, silently, with no diagnostic anywhere.
+Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`. Every other tool a developer runs — curl, git, npm, pip — honours them, so a user behind a proxy exports the variables once and expects everything to follow. The harness did not: `setGlobalDispatcher`, `ProxyAgent`, and `EnvHttpProxyAgent` appeared zero times across `packages/` and `apps/`, so the model request, every web search, `web_fetch`, MCP over HTTP, and the OTLP exporter all connected directly, silently, with no diagnostic anywhere.
 
 The repository had briefly had an answer and lost it without noticing. PR #971 set `NODE_USE_ENV_PROXY=1` in `bin/dsh`; eleven days later `bbb1b1cc38 cleanup: remove managed source installer` deleted that launcher wholesale, taking the flag with it. What survived was one sentence in `apps/cli/reference/README.md` telling the reader to set a variable that nothing consumed any more.
 
@@ -24,9 +24,7 @@ An earlier revision put it in a new `net/` package group, reasoning that dependi
 
 The plugin that revision shipped is gone with it. It let a composition declare the policy in `cordis.yml`, but no shipped bundle mounted it, so the launcher's path was the only reachable one — and its `Config` was the sole supplier of a configuration branch nothing else could reach.
 
-**Four functions, because the call sites converged rather than the package growing an export each.** An earlier revision exported six: a dispatcher factory, a `node:http` agent factory, a proxy-URL lookup, a policy accessor, an installer, and a child-environment builder. Each existed for one SDK's transport, which is how a transport-policy package turns into a catalogue of other packages' constraints. Review asked whether the call sites could converge instead; they could, and each removal took a whole shape with it. Telemetry stopped being routed at all, retiring the `node:http` factory. `web-fetch-http` builds its own pinning agent under an annotated exemption, retiring the dispatcher factory. E2B reads `route.proxy`, retiring the proxy-URL lookup.
-
-What remains is `installProxyFromEnvironment`, `proxyRouteFor`, `proxyEnvironmentForChild`, and `clearedProxyEnv` — one per way a caller can need the policy, none per SDK. Installation absorbed resolution and diagnostic reporting, which no caller needed apart: a resolved policy that is not installed routes nothing.
+**One operation per caller need.** `installProxyFromEnvironment`, `proxyRouteFor`, `proxyEnvironmentForChild`, and `clearedProxyEnv` cover installation, per-request routing, child inheritance and fixture isolation. SDK-specific factories would expose individual transport constraints through the shared API. `web-fetch-http` consumes the resolved route when constructing its pinning transport; the OTLP exporter remains direct. Installation includes resolution and diagnostic reporting because callers need one operation that resolves and installs routing.
 
 `proxyRouteFor` also closes a defect the old accessor made expressible. `web-fetch-http` read the policy to decide whether to pin, then read it again to build a transport; an unmount between the two returned a direct, unpinned agent for a URL the first read had cleared as proxied. A route carries both, so the branch and the request cannot disagree. Its dispatcher is the process-wide one, closed rather than destroyed on disposal, so a request already in flight when a policy is unmounted still finishes.
 
@@ -48,13 +46,13 @@ The child keeps the user's own values, and that is what once broke it. Node pars
 
 This accepts a documented seam. Such a context matches bypass entries by Node's rules, which differ from this package's in separators and IPv4-range support, and the flag exists only on Node 22.21+ and 24+.
 
-**Two SDKs do not reach `globalThis.fetch`, and reading their code said otherwise.** The audit first classified the OTLP exporter and the E2B SDK as covered, on a grep that found `globalThis.fetch` in `@opentelemetry/otlp-exporter-base`. That match is the *browser* transport; on Node the delegate selects `http-exporter-transport`, which posts through `node:http` — where a global dispatcher does not reach. E2B is a second shape again: it builds its own undici `Agent`/`ProxyAgent` and takes a `proxy` URL that it never reads from the environment. Both were measured direct. E2B is handed `route.proxy` from `proxyRouteFor`, the same call `web-fetch-http` makes. Telemetry is deliberately left direct, and that exclusion is the more interesting half.
+**SDK transports need independent verification.** The OTLP exporter selects `http-exporter-transport` on Node, which posts through `node:http` and bypasses the global fetch dispatcher; a `globalThis.fetch` reference in its browser implementation does not prove Node routing. The [E2B removal](../simplification/2026-09-11-remove-e2b-providers.md) retires a second SDK transport integration without changing this requirement.
 
 **Telemetry stays direct on purpose.** Routing it needs one of two things, and both cost more than the channel is worth. An `http.Agent` reads the environment through `proxyEnv`, which arrived in Node 22.21 and 24.5 — inside the engines range, so 22.19, 22.20, and 24.0–24.4 would stay direct regardless, and the proxy package would have to keep a `createNodeHttpAgent` export for a path that works on some runtimes. Replacing the transport with the SDK's `fetch` delegate covers every runtime, but that delegate has no compression, and the shipped `base` bundle enables gzip: a realistic OTLP batch measures 6.4x smaller with it. An attempt that refused `exporter.compression` instead broke every test that boots the shipped bundle, and one that gzipped at the serializer worked but put transport code in a telemetry plugin to keep it working.
 
 Weighed against that, telemetry is the one outbound channel whose loss costs the user nothing: no tool, no model request, and no session depends on it, and an export that cannot connect is already dropped silently. A user behind a mandatory proxy is left exactly where they were before this change rather than regressed. `egress.spec.ts` now asserts the exclusion — an SDK upgrade that moved the exporter onto `fetch` would start routing telemetry through a proxy silently, and that case is what makes it visible.
 
-**Every call site carries an egress test, because reading the code was not enough.** `egress.spec.ts` in each owning package drives that site's real code path at an unresolvable `.invalid` host through a fake proxy and asserts the proxy saw the request. Nine of them cover the search backends, pi-ai discovery, MCP over HTTP, E2B, a spawned child Node, a worker thread, and telemetry's exclusion. The gate below cannot see inside a dependency; these can, and they are what turns "an SDK changed its transport" from a silent regression into a failing test.
+**Every call site carries an egress test.** Each owning package's `egress.spec.ts` drives its actual transport through a fake proxy and checks the observed route. These tests cover search backends, pi-ai discovery, MCP over HTTP, child Node processes, worker threads and telemetry's direct-route exception. They detect dependency transport changes that a static call-site check cannot observe.
 
 **A gate keeps the defect from returning.** `verify-no-bare-dispatcher` parses the TypeScript AST — `scripts/AGENTS.md` requires syntax-aware discovery, and a line-wise regex missed both the `{ dispatcher }` shorthand this repository already uses and a `new Alias(...)` behind a renamed import. It rejects an undici agent construction and an explicit `dispatcher` option outside the owning package. `proxyRouteFor(url)` is the sanctioned replacement, and the one call site that genuinely owns its transport — `web-fetch-http`, pinning a request to addresses it validated — says so with a `proxy-exempt:` comment. The rule exists because `web-fetch-http`'s original `new Agent` was entirely reasonable when it was written — proxying simply did not exist yet, and nothing would have caught it.
 

+ 4 - 6
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## Problem
 
-Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运行的其他工具——curl、git、npm、pip——都遵循它们,所以代理后面的用户导出一次变量就期待一切随之生效。Harness 并没有:`setGlobalDispatcher`、`ProxyAgent` 与 `EnvHttpProxyAgent` 在 `packages/` 与 `apps/` 中出现次数为零,因此模型请求、每次 web 搜索、`web_fetch`、走 HTTP 的 MCP、OTLP 导出器与 E2B SDK 全部直连,且是静默的,任何地方都没有诊断。
+Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运行的其他工具——curl、git、npm、pip——都遵循它们,所以代理后面的用户导出一次变量就期待一切随之生效。Harness 并没有:`setGlobalDispatcher`、`ProxyAgent` 与 `EnvHttpProxyAgent` 在 `packages/` 与 `apps/` 中出现次数为零,因此模型请求、每次 web 搜索、`web_fetch`、走 HTTP 的 MCP 与 OTLP 导出器全部直连,且是静默的,任何地方都没有诊断。
 
 仓库曾短暂拥有过答案,又在无人察觉时弄丢了。PR #971 在 `bin/dsh` 里设置了 `NODE_USE_ENV_PROXY=1`;十一天后 `bbb1b1cc38 cleanup: remove managed source installer` 整体删除了那个启动器,把该标志一并带走。留下的只有 `apps/cli/reference/README.md` 里的一句话,让读者去设置一个已经无人消费的变量。
 
@@ -24,9 +24,7 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运
 
 那次修订一并引入的插件也随之删除。它让某个组合可以把策略写进 `cordis.yml`,但没有任何随附 bundle 挂载它,因此启动器那条路径是唯一可达的——而它的 `Config` 是那条配置分支唯一的供给方,别处无从到达。
 
-**四个函数——收敛的是调用方,而不是让本包为每个 SDK 各加一个导出。** 早先一版导出六个:dispatcher 工厂、`node:http` agent 工厂、代理 URL 查询、策略访问器、安装器与子进程环境构造器。每一个都为某个 SDK 的传输而存在,而这正是一个传输策略包退化成「别的包的约束目录」的过程。Review 问能不能反过来让调用方收敛;能,而且每删掉一个导出都带走了一整种写法。遥测不再被路由,`node:http` agent 工厂随之退场。`web-fetch-http` 在带注释的豁免下自建 pin agent,dispatcher 工厂随之退场。E2B 读 `route.proxy`,代理 URL 查询随之退场。
-
-剩下的是 `installProxyFromEnvironment`、`proxyRouteFor`、`proxyEnvironmentForChild` 与 `clearedProxyEnv`——按「调用方需要策略的方式」各一个,而不是按 SDK 各一个。安装吸收了解析与诊断上报,因为没有调用方需要把它们分开:解析出来却不安装的策略什么也路由不了。
+**每种调用需求对应一项操作。** `installProxyFromEnvironment`、`proxyRouteFor`、`proxyEnvironmentForChild` 与 `clearedProxyEnv` 分别负责安装、逐请求路由、子进程继承和 fixture 隔离。特定于 SDK 的工厂会通过共享 API 暴露各自的传输约束。`web-fetch-http` 在构造地址固定传输时使用已解析路由;OTLP 导出器保持直连。安装包含解析与诊断上报,因为调用方需要一项同时解析并安装路由的操作。
 
 `proxyRouteFor` 还堵掉了旧访问器让人写得出来的一个缺陷。`web-fetch-http` 先读策略决定是否 pin,再读一次去构造传输;两次读取之间发生卸载,就会为第一次读取已判定走代理的 URL 返回一个直连且未 pin 的 agent。路由把两者一起交出,分支与请求便无从分歧。它携带的是进程级 dispatcher,dispose 时是 close 而非 destroy,因此策略被卸载时已经发出的请求仍会跑完。
 
@@ -48,13 +46,13 @@ URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与
 
 这接受了一处已记录的接缝。此类上下文按 Node 自己的规则匹配绕过条目,其分隔符与 IPv4 区间支持与本包不同,且该标志仅存在于 Node 22.21+ 与 24+。
 
-**有两个 SDK 并不落到 `globalThis.fetch`,而读代码给出的答案是相反的。** 审计最初把 OTLP 导出器与 E2B SDK 判为已覆盖,依据是在 `@opentelemetry/otlp-exporter-base` 里 grep 到了 `globalThis.fetch`。那处命中属于**浏览器**传输;在 Node 上 delegate 选择的是 `http-exporter-transport`,它通过 `node:http` 投递——那里全局 dispatcher 触及不到。E2B 又是另一种形态:它自建 undici `Agent`/`ProxyAgent`,并接受一个自己从不从环境读取的 `proxy` URL。两者都实测为直连。E2B 接收 `proxyRouteFor` 给出的 `route.proxy`,与 `web-fetch-http` 调的是同一个函数。遥测则被有意保留为直连,而这个排除项才是更值得说的一半。
+**SDK 传输需要独立验证。** OTLP 导出器在 Node 上选择 `http-exporter-transport`,通过 `node:http` 投递并绕过全局 fetch dispatcher;其浏览器实现中的 `globalThis.fetch` 引用不能证明 Node 路由。[E2B 移除决策](../simplification/2026-09-11-remove-e2b-providers.zh.md)撤下另一项 SDK 传输集成,但不改变这一要求。
 
 **遥测的直连是有意为之。** 要让它走代理只有两条路,代价都超过这条通道本身的价值。`http.Agent` 通过 `proxyEnv` 读取环境,而该选项自 Node 22.21 与 24.5 才有——落在 engines 范围之内,因此 22.19、22.20 与 24.0–24.4 无论如何仍是直连,而代理包还得为一条只在部分运行时生效的路径保留 `createNodeHttpAgent` 导出。改用 SDK 的 `fetch` delegate 替换传输可以覆盖所有运行时,但该 delegate 没有压缩能力,而随附的 `base` bundle 启用了 gzip:实测一批真实规模的 OTLP 数据启用后体积只有 1/6.4。曾有一版转而在加载期拒绝 `exporter.compression`,结果凡是启动随附 bundle 的测试全部失败;另一版在 serializer 处 gzip 确实能跑通,但代价是把传输层代码塞进了遥测插件。
 
 与之相比,遥测是唯一一条丢失了对用户毫无代价的出网通道:没有任何工具、模型请求或会话依赖它,而连不上的导出本就被静默丢弃。处在强制代理后的用户,只是停留在本次改动之前的状态,而不是被弄坏。`egress.spec.ts` 现在断言这一排除——若某次 SDK 升级把导出器挪到 `fetch` 上,遥测就会开始静默走代理,而该用例正是让这件事暴露出来的东西。
 
-**每个出网点都配一份出网测试,因为读代码不够。** 各所属包中的 `egress.spec.ts` 驱动该点的真实代码路径,目标是无法解析的 `.invalid` 主机,穿过一个假代理,并断言代理确实收到了请求。九份测试覆盖搜索后端、pi-ai 发现、走 HTTP 的 MCP、E2B、派生的子 Node、worker 线程,以及遥测的排除。下面那条门禁看不进依赖内部;这些能,它们把「某个 SDK 换了传输」从静默回归变成失败的测试。
+**每个出网点都配有出网测试。** 各所属包中的 `egress.spec.ts` 通过假代理驱动实际传输,并检查观察到的路由。这些测试覆盖搜索后端、pi-ai 发现、走 HTTP 的 MCP、子 Node 进程、worker 线程和遥测的直连例外。它们可发现静态调用点检查无法观察到的依赖传输变化。
 
 **用门禁防止该缺陷复现。** `verify-no-bare-dispatcher` 解析 TypeScript AST——`scripts/AGENTS.md` 要求 source-ownership 门禁使用语法感知发现,而逐行正则漏掉了本仓库已在使用的 `{ dispatcher }` 简写,以及重命名导入后的 `new Alias(...)`。它在所属包之外拒绝 undici agent 构造与显式 `dispatcher` 选项。`proxyRouteFor(url)` 是受支持的替代;唯一一处确实自有传输的调用点——`web-fetch-http`,它把请求钉在已校验的地址上——用 `proxy-exempt:` 注释说明。这条规则之所以存在,是因为 `web-fetch-http` 里原本那行 `new Agent` 在写下时完全合理——那时根本还没有代理这回事,也没有任何机制会拦下它。
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.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/implemented/architecture/2026-08-28-subprocess-native-containment.md
-2026-08-28-subprocess-native-containment.md: 7523f9d66e7a302f6ce9c77d93671a5b1303a5aa
-2026-08-28-subprocess-native-containment.zh.md: 252a8e9fd058cf37a1a7a70f09394a6811d58580
+2026-08-28-subprocess-native-containment.md: 1b0fd162be77356001bcd9224bb2ee2889e6d6c0
+2026-08-28-subprocess-native-containment.zh.md: f33643f98a6ea6a90c0780432cbe11a35a868562

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 1
.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 1
.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md


+ 2 - 2
.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.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/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md
-2026-09-01-v2-embedded-assistant-streams.md: 98207749028e2182c5e60073fc07985688ecfa30
-2026-09-01-v2-embedded-assistant-streams.zh.md: 90dff5a385cf83071ec52a2fc2b57a893107b861
+2026-09-01-v2-embedded-assistant-streams.md: 219879600f8435a6ecc9593661dd3f463ed1158c
+2026-09-01-v2-embedded-assistant-streams.zh.md: d38c180b1f1de689620f2e0533d2b02f4a71e466

+ 4 - 4
.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md

@@ -23,7 +23,7 @@ Session format v2 has no top-level `assistant/chunk` event. Each model attempt c
 
 `AssistantStreamAccumulator` snapshots each chunk once. Consecutive text, reasoning, or tool-argument deltas for the same block become one compact run with its first timestamp, exact timestamp gaps, and one array member per original delta. Every other chunk remains a timestamped raw record. `expandAssistantStream()` strictly validates and reconstructs the exact timed sequence; compaction never joins delta boundaries.
 
-The migration publication verifier and frozen v2 fixture validator require the embedded stream to reproduce a non-empty `assistant/message`'s content, usage, and replay state. An empty stream remains valid for a migrated legacy message that had no source chunks. Ordinary Session restoration validates the settlement fields needed by the runtime without expanding every historical stream; consumers that expand a compact stream validate its records when they read it. `assistant/message` cannot carry obsolete chunk `sourceEventSeqs`; ordinary user and tool surface provenance remains available.
+The migration publication verifier and frozen v2 fixture validator require the embedded stream to reproduce a non-empty `assistant/message`'s content, usage, and replay state. An empty stream remains valid for a migrated legacy message that had no source chunks. Ordinary Session restoration validates the settlement fields needed by the runtime without expanding every historical stream; consumers that expand a compact stream validate its records when they read it. `assistant/message` cannot carry obsolete chunk `sourceEventSeqs`; ordinary user and tool source-event references remain available.
 
 ### Live presentation and durable replay
 
@@ -35,9 +35,9 @@ The Client event source passes durable settlements through unchanged. The Chat a
 
 ### Released v1 to v2 migration
 
-The adjacent migration validates the complete frozen v1 artifact, groups chunks by turn, step, terminal boundary, and exact message provenance, and then substitutes one settlement per attempt. A successful group's chunks move into its message. An unclaimed group becomes `assistant/attempt` at the last consumed chunk's position. Unrelated interleaved events retain their relative order, and survivors receive dense v2 sequence numbers. The edge compacts embedded streams through the runtime `AssistantStreamAccumulator` from `dsh-llm` instead of a frozen copy, because that package owns the v2 stream encoding. The isolated publication verifier expands and re-assembles the written stream through `expandAssistantStream()` and `BlockAssembler`, then checks each migrated `assistant/message` against it before publication. A later format that changes the stream encoding must freeze copies of these helpers into this edge.
+The adjacent migration validates the complete frozen v1 artifact, groups chunks by turn, step, terminal boundary, and exact message chunk references, and then substitutes one settlement per attempt. A successful group's chunks move into its message. An unclaimed group becomes `assistant/attempt` at the last consumed chunk's position. Unrelated interleaved events retain their relative order, and survivors receive dense v2 sequence numbers. The edge compacts embedded streams through the runtime `AssistantStreamAccumulator` from `dsh-llm` instead of a frozen copy, because that package owns the v2 stream encoding. The isolated publication verifier expands and re-assembles the written stream through `expandAssistantStream()` and `BlockAssembler`, then checks each migrated `assistant/message` against it before publication. A later format that changes the stream encoding must freeze copies of these helpers into this edge.
 
-The edge remaps the finite declared reference inventory: envelope provenance, surface replacement endpoints, command source events, compaction ranges and shadowed lists, and title message lists. The model-visible text of a validated `session/title-llm-request` remains byte-identical in the source sequence namespace while its `messageSeqs` field moves to the v2 namespace; target validation therefore does not reconstruct that text from remapped sequences. A reference to a consumed chunk refuses migration; it is never redirected to a settlement with different meaning. The edge also refuses an inherited cut that splits an attempt.
+The edge remaps the finite declared reference inventory: envelope source-event references, surface replacement endpoints, command source events, compaction ranges and shadowed lists, and title message lists. The model-visible text of a validated `session/title-llm-request` remains byte-identical in the source sequence namespace while its `messageSeqs` field moves to the v2 namespace; target validation therefore does not reconstruct that text from remapped sequences. A reference to a consumed chunk refuses migration; it is never redirected to a settlement with different meaning. The edge also refuses an inherited cut that splits an attempt.
 
 The v2 physical header requires `isSeeded` and stores no numeric cut. A seeded artifact marks its exact cut with `session/end-seed { inherited: true }`; decoding derives the cut from the last tagged marker. The v2 codec writes one durable event per physical row, range-encodes only `sourceEventSeqs`, and validates physical envelopes without freezing ordinary event vocabulary or payload additions. The v1-to-v2 target validator separately freezes the released-v2 inventory, while current restoration uses the installed Session vocabulary. Frozen v0 and v1 codecs retain packed-row decoding for their immutable historical generations.
 
@@ -49,7 +49,7 @@ Generation selection and publication follow the [released Session migration deci
 
 ## Verification
 
-The compact-stream tests pin exact accumulation and expansion for text, reasoning, tool arguments, raw chunks, timestamp gaps, malformed records, and detached snapshots. The v1-to-v2 tests cover successful and failed attempts, interleaving, dense sequence and reference remapping, source-sequence title framing, seed-cut insertion and split refusal, strict source and target validation, one-row v2 encoding, backend-compatible provenance ranges, raw and Zstandard publication, and no-write current reads.
+The compact-stream tests pin exact accumulation and expansion for text, reasoning, tool arguments, raw chunks, timestamp gaps, malformed records, and detached snapshots. The v1-to-v2 tests cover successful and failed attempts, interleaving, dense sequence and reference remapping, source-sequence title framing, seed-cut insertion and split refusal, strict source and target validation, one-row v2 encoding, backend-compatible source-event ranges, raw and Zstandard publication, and no-write current reads.
 
 The pre-merge performance acceptance measured static catalog-routing overhead against direct released-v2 restoration of the same already parsed physical rows across three runs, 100 warmup pairs, and 600 measured pairs; it did not compare v1 with v2 or time backend I/O. Every pooled median and p95 regression stayed within the 5% budget, with a worst p95 regression of 3.150%.
 

+ 4 - 4
.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md

@@ -23,7 +23,7 @@ Session format v2 没有顶层 `assistant/chunk` 事件。每个模型 attempt 
 
 `AssistantStreamAccumulator` 对每个 chunk 只快照一次。同一 block 的连续 text、reasoning 或 tool argument delta 会变成一个紧凑 run,包含首个时间戳、精确时间戳间隔和每个原始 delta 对应的一个数组成员。其他 chunk 保留为带时间戳的 raw record。`expandAssistantStream()` 会严格校验并重建精确的带时间序列;压缩绝不会合并 delta 边界。
 
-Migration publication verifier 与冻结的 v2 fixture validator 要求嵌入式 stream 能复现非空 `assistant/message` 的 content、usage 与 replay state。对于没有源 chunk 的已迁移旧 message,空 stream 仍然有效。普通 Session restore 只校验 runtime 直接依赖的 settlement 字段,不展开全部历史 stream;需要展开 compact stream 的 consumer 会在读取时校验 record。`assistant/message` 不能携带已停用的 chunk `sourceEventSeqs`;普通 user 与 tool surface provenance 保持可用。
+Migration publication verifier 与冻结的 v2 fixture validator 要求嵌入式 stream 能复现非空 `assistant/message` 的 content、usage 与 replay state。对于没有源 chunk 的已迁移旧 message,空 stream 仍然有效。普通 Session restore 只校验 runtime 直接依赖的 settlement 字段,不展开全部历史 stream;需要展开 compact stream 的 consumer 会在读取时校验 record。`assistant/message` 不能携带已停用的 chunk `sourceEventSeqs`;普通 user 与 tool source-event reference 保持可用。
 
 ### 实时呈现与持久回放
 
@@ -35,9 +35,9 @@ Client event source 原样传递持久 settlement。Chat 与 Trajectory 的 Assi
 
 ### 已发布 v1 到 v2 迁移
 
-相邻迁移会校验完整的冻结 v1 产物,按 turn、step、terminal boundary 与精确 message provenance 对 chunk 分组,再为每个 attempt 替换一个 settlement。成功分组的 chunk 移入其 message。未被认领的分组会在最后一个被消费 chunk 的位置变成 `assistant/attempt`。无关的交错事件保持相对顺序,存活事件获得密集 v2 序号。该迁移边通过 `dsh-llm` 运行时的 `AssistantStreamAccumulator` 压缩嵌入 stream,而不持有冻结副本,因为该包拥有 v2 stream 编码。隔离的 publication verifier 通过 `expandAssistantStream()` 与 `BlockAssembler` 展开并重组写入后的 stream,并在发布前检查每个迁移后的 `assistant/message` 是否与其一致。日后若某个格式改变 stream 编码,必须把这些 helper 的冻结副本纳入本迁移边。
+相邻迁移会校验完整的冻结 v1 产物,按 turn、step、terminal boundary 与精确 message chunk reference 对 chunk 分组,再为每个 attempt 替换一个 settlement。成功分组的 chunk 移入其 message。未被认领的分组会在最后一个被消费 chunk 的位置变成 `assistant/attempt`。无关的交错事件保持相对顺序,存活事件获得密集 v2 序号。该迁移边通过 `dsh-llm` 运行时的 `AssistantStreamAccumulator` 压缩嵌入 stream,而不持有冻结副本,因为该包拥有 v2 stream 编码。隔离的 publication verifier 通过 `expandAssistantStream()` 与 `BlockAssembler` 展开并重组写入后的 stream,并在发布前检查每个迁移后的 `assistant/message` 是否与其一致。日后若某个格式改变 stream 编码,必须把这些 helper 的冻结副本纳入本迁移边。
 
-该迁移边会重映射有限的已声明引用清单:信封 provenance、surface replacement 端点、command source event、compaction range 与 shadowed list,以及 title message list。经过校验的 `session/title-llm-request` 模型可见文本会在源序号命名空间中保持逐字节不变,而它的 `messageSeqs` 字段会迁移到 v2 命名空间;因此目标校验不会根据重映射后的序号重建该文本。指向被消费 chunk 的引用会使迁移失败;它绝不会被重定向到含义不同的 settlement。该迁移边也会拒绝切开 attempt 的继承切点。
+该迁移边会重映射有限的已声明引用清单:信封 source-event reference、surface replacement 端点、command source event、compaction range 与 shadowed list,以及 title message list。经过校验的 `session/title-llm-request` 模型可见文本会在源序号命名空间中保持逐字节不变,而它的 `messageSeqs` 字段会迁移到 v2 命名空间;因此目标校验不会根据重映射后的序号重建该文本。指向被消费 chunk 的引用会使迁移失败;它绝不会被重定向到含义不同的 settlement。该迁移边也会拒绝切开 attempt 的继承切点。
 
 v2 物理 header 要求 `isSeeded`,且不存储数值切点。带 seed 的产物用 `session/end-seed { inherited: true }` 标记其精确切点;解码从最后一个 tagged marker 推导切点。v2 编解码器为每个持久事件写一条物理行,只对 `sourceEventSeqs` 做范围编码,并在不冻结普通事件词汇或 payload 新增项的前提下校验物理 envelope。v1-to-v2 target validator 会另行冻结 released-v2 清单,current restoration 则使用 installed Session 词汇。冻结的 v0 与 v1 编解码器继续为不可变历史 generation 解码 packed row。
 
@@ -49,7 +49,7 @@ Generation 选择与发布遵循[已发布 Session 迁移决策](2026-08-31-rele
 
 ## 验证
 
-紧凑 stream 测试固定 text、reasoning、tool argument、raw chunk、时间戳间隔、格式错误 record 与分离 snapshot 的精确累积和展开。v1 到 v2 测试覆盖成功与失败 attempt、交错、密集序号与引用重映射、源序号 title framing、seed 切点插入与切分拒绝、严格源与目标校验、每行一个事件的 v2 编码、与 backend 兼容的 provenance range、原始与 Zstandard 发布,以及无写入的当前读取。
+紧凑 stream 测试固定 text、reasoning、tool argument、raw chunk、时间戳间隔、格式错误 record 与分离 snapshot 的精确累积和展开。v1 到 v2 测试覆盖成功与失败 attempt、交错、密集序号与引用重映射、源序号 title framing、seed 切点插入与切分拒绝、严格源与目标校验、每行一个事件的 v2 编码、与 backend 兼容的 source-event range、原始与 Zstandard 发布,以及无写入的当前读取。
 
 合并前的 performance acceptance 在三轮、100 组 warmup pair 与 600 组 measured pair 下,针对同一批已经解析的物理 row,把静态 catalog routing 与直接 released-v2 restoration 比较;它不比较 v1 与 v2,也不计入 backend I/O。每个 pooled median 与 p95 regression 都保持在 5% 预算以内,最差 p95 regression 为 3.150%。
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md
-2026-09-05-workspace-files-service.md: b2dbdfc99388d8c2f18991a7704599d2d95f070b
-2026-09-05-workspace-files-service.zh.md: d860d220af50a24a6e0f40b640d0a8fb534c4741
+2026-09-05-workspace-files-service.md: 40157508a83abc61245845a1dbe4f1749e175563
+2026-09-05-workspace-files-service.zh.md: 51de0ad5b644f27e30557ea6b11ba62b8ef9a715

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md

@@ -95,7 +95,7 @@ abstract readByteRange(target: FsTarget, range: { offset: number; length: number
 
 It returns the bytes at `[offset, offset + length)`, shorter when the file ends inside the window and empty when `offset` lies at or past the end. The window is the bound: a backend transfers at most `length` bytes beyond the prefix it skips to reach `offset` and never buffers the whole file, so the caller's cap on `length` is the guard against unbounded buffering, sitting beside `readBytes`'s bound rather than replacing it. The parameter order follows `readText`, `streamText`, and `listDir` — target, then the operation's own arguments, then an optional signal — rather than `readBytes`'s signal-in-the-middle form, which is the one exception in the class. Both `offset` and `length` are non-negative integers by precondition; the seam is a typed same-process boundary and validates nothing, and the Remote method validates at the wire.
 
-`fs-local` opens `createReadStream(targetKey, { start: offset, end: offset + length - 1 })` after the same regular-file stat as its other reads, returning an empty array for `length` 0 without opening a stream; `fs-sandbox` extends `LocalFileSystem` and inherits it. `fs-e2b` has an SDK that streams only from a file's start, so it skips `offset` bytes, copies `length` into the window, and cancels the stream the moment the window is full, transferring no more than the window beyond the skipped prefix; a stream that ends first is left to close. The four test doubles that extend `FileSystem` implement the method too.
+`fs-local` opens `createReadStream(targetKey, { start: offset, end: offset + length - 1 })` after the same regular-file stat as its other reads, returning an empty array for `length` 0 without opening a stream; `fs-sandbox` extends `LocalFileSystem` and inherits it. The four test doubles that extend `FileSystem` implement the method too.
 
 ### The Client `file` provider
 
@@ -133,7 +133,7 @@ The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`,
 
 - Workspace file access belongs to the Host/Client faces of `api/workspace-files`; the Session Controller carries neither implementation, and compiler and runtime entries stay separate. Header-only Session scope lets ordinary, subagent, live, and cold Sessions resolve their own relative paths without an Agent lifecycle or parent fallback.
 - A file of any size opens: text by line page, anything by byte window, each costing one page or window of memory on the Host; complete reads instead enforce `maxFileBytes`; the cost is that a consumer assembles pages itself and that a single line above `maxBytes` has no page at all, because pages are cut by lines.
-- Every filesystem provider now offers a windowed raw read. `fs-e2b` pays for it by transferring the skipped prefix, since its SDK cannot seek; `fs-local` seeks.
+- Every filesystem provider offers windowed raw reads; `fs-local` seeks directly to the requested offset.
 - Paths on the wire are canonical: `absolutePath` and change frames spell a file with symlinks resolved. A follower binds to successful `stat.absolutePath`, so another spelling of the same file — a workspace root reached through a symlink — uses that canonical change key.
 - Change frames report the agent's own operations only. A file edited by the user's editor, a shell, or a subprocess raises no frame; an agent merely reading a file that something else changed does raise one, because the read observes a new version.
 - File-kind inspection precedes backend reads, and `list` reports kind before an outside position. A page's `version` may be one write behind its content, and a stalled `changes` consumer grows Host memory because a generation's queue is unbounded; each is a known trade-off recorded in the package README.
@@ -142,7 +142,7 @@ The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`,
 
 ## Testing
 
-Host specs in `packages/api/workspace-files/tests` exercise header-only scope resolution for live and cold subagent Sessions, the deployment fallback, missing identities, and lookup disposal; the paged read (whole file, nested path, empty file, multi-byte UTF-8, the line window's edges, defaults and refused limits, carriage returns kept); the byte window (defaults, a middle window with more following, tail windows exact and short, past-end and empty files, NUL and invalid UTF-8 round-tripping through base64, version parity with `stat`, the cap as `too-large`, bad ranges, a window of a file far above the cap, and `eof` inferred without a size); `stat`; outside-workspace reads and backend refusals; `list` with containment, truncation, symlink children, and `not-directory`; and the `changes` stream driven by `fs/observed` and filtered by root. Client specs cover the provider's frames, the change feed, unsupported addresses, and registration and disposal. `fs/fs`, `fs-local`, and `fs-e2b` specs pin `readByteRange`; `dsh-util-workspace-path` specs pin the file-address grammar. The connection fixture serves `stat`, paged `read`, `list`, and an opt-in `changes` frame for the web e2e suite.
+Host specs in `packages/api/workspace-files/tests` exercise header-only scope resolution for live and cold subagent Sessions, the deployment fallback, missing identities, and lookup disposal; the paged read (whole file, nested path, empty file, multi-byte UTF-8, the line window's edges, defaults and refused limits, carriage returns kept); the byte window (defaults, a middle window with more following, tail windows exact and short, past-end and empty files, NUL and invalid UTF-8 round-tripping through base64, version parity with `stat`, the cap as `too-large`, bad ranges, a window of a file far above the cap, and `eof` inferred without a size); `stat`; outside-workspace reads and backend refusals; `list` with containment, truncation, symlink children, and `not-directory`; and the `changes` stream driven by `fs/observed` and filtered by root. Client specs cover the provider's frames, the change feed, unsupported addresses, and registration and disposal. `fs/fs` and `fs-local` specs pin `readByteRange`; `dsh-util-workspace-path` specs pin the file-address grammar. The connection fixture serves `stat`, paged `read`, `list`, and an opt-in `changes` frame for the web e2e suite.
 
 ## Deferred
 

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md

@@ -95,7 +95,7 @@ abstract readByteRange(target: FsTarget, range: { offset: number; length: number
 
 它返回 `[offset, offset + length)` 处的字节,文件在窗内结束则变短,`offset` 位于或越过末尾则为空。窗口即界:后端最多传输为到达 `offset` 而跳过的前缀之外的 `length` 字节,从不缓冲整个文件,因此调用方对 `length` 的上限就是防无界缓冲的守卫,与 `readBytes` 的界并列而非取代它。参数顺序遵循 `readText`、`streamText` 与 `listDir`——先目标,再操作自己的参数,最后可选 signal——而不是 `readBytes` 把 signal 放中间的形式,那是该类中唯一的例外。`offset` 与 `length` 按前置条件都是非负整数;seam 是类型化的同进程边界,不做任何校验,由 Remote 方法在线路处校验。
 
-`fs-local` 在与其他读取相同的普通文件 stat 之后打开 `createReadStream(targetKey, { start: offset, end: offset + length - 1 })`,对 `length` 为 0 直接返回空数组而不开流;`fs-sandbox` 继承 `LocalFileSystem`,随之继承该方法。`fs-e2b` 的 SDK 只能从文件开头开始流式读取,于是它跳过 `offset` 字节、把 `length` 字节拷入窗口,并在窗口填满的那一刻取消流,除跳过的前缀外传输量不超过窗口;先行结束的流则任其关闭。继承 `FileSystem` 的四个测试替身也实现了该方法。
+`fs-local` 在与其他读取相同的普通文件 stat 之后打开 `createReadStream(targetKey, { start: offset, end: offset + length - 1 })`,对 `length` 为 0 直接返回空数组而不开流;`fs-sandbox` 继承 `LocalFileSystem`,随之继承该方法。继承 `FileSystem` 的四个测试替身也实现了该方法。
 
 ### Client `file` 提供者
 
@@ -133,7 +133,7 @@ Client 导出向 `ctx.resources` 注册一个 `ResourceProvider<'file'>`,存
 
 - 工作区文件访问由 `api/workspace-files` 的 Host/Client 两面共同承担;Session Controller 不携带其中任何实现,两面的编译与运行时入口保持独立。header-only Session scope 让普通、subagent、live 与 cold Session 都能解析自己的相对路径,不需要 Agent 生命周期,也不回退父 Session。
 - 任意大小的文件都能打开:文本按行页、任何文件按字节窗口,在 Host 上各自只花一页或一窗内存;全文读取则受 `maxFileBytes` 约束;代价是消费者自己拼装页面,且单行超过 `maxBytes` 的行没有任何页,因为页按行切。
-- 每个文件系统提供者现在都提供开窗的原始读取。`fs-e2b` 为此付出传输被跳过前缀的代价,因为其 SDK 不能 seek;`fs-local` 能 seek。
+- 每个文件系统提供者都提供开窗的原始读取;`fs-local` 直接定位到所请求的偏移量。
 - 线路上的路径是规范的:`absolutePath` 与变更帧以符号链接已解析的拼法命名文件。跟随者绑定到成功的 `stat.absolutePath`,因此同一文件的另一种拼法——经符号链接到达的工作区根——也使用该规范变更键。
 - 变更帧只报告 agent 自己的操作。用户编辑器、shell 或子进程改动的文件不产生帧;agent 仅仅读取一个被别处改动的文件却会产生帧,因为读取观察到了新版本。
 - 文件类型检查先于后端读取,`list` 也先报种类再报根外位置。页的 `version` 可能落后内容一次写入,停滞的 `changes` 消费者会让 Host 内存增长,因为一代流的队列无界;每一条都是包 README 记录在册的已知取舍。
@@ -142,7 +142,7 @@ Client 导出向 `ctx.resources` 注册一个 `ResourceProvider<'file'>`,存
 
 ## Testing
 
-`packages/api/workspace-files/tests` 中的 Host spec 覆盖 live 与 cold subagent Session 的 header-only scope 解析、部署 fallback、缺失身份与 lookup 释放;分页读取(整文件、嵌套路径、空文件、多字节 UTF-8、行窗口边界、缺省与拒绝的 limit、保留回车);字节窗口(缺省、中段与尾窗、越界与空文件、base64 往返、版本、上限、坏范围以及无大小时的 `eof`);`stat`;工作区外读取及后端拒绝;`list` 的包含、截断、符号链接与 `not-directory`;以及由 `fs/observed` 驱动并按根过滤的 `changes`。Client spec 覆盖提供者帧、变更流、不支持地址及注册与释放。`fs/fs`、`fs-local` 与 `fs-e2b` spec 钉住 `readByteRange`;`dsh-util-workspace-path` spec 钉住文件地址语法。connection fixture 为 web e2e 套件提供 `stat`、分页 `read`、`list` 与一帧可选启用的 `changes`。
+`packages/api/workspace-files/tests` 中的 Host spec 覆盖 live 与 cold subagent Session 的 header-only scope 解析、部署 fallback、缺失身份与 lookup 释放;分页读取(整文件、嵌套路径、空文件、多字节 UTF-8、行窗口边界、缺省与拒绝的 limit、保留回车);字节窗口(缺省、中段与尾窗、越界与空文件、base64 往返、版本、上限、坏范围以及无大小时的 `eof`);`stat`;工作区外读取及后端拒绝;`list` 的包含、截断、符号链接与 `not-directory`;以及由 `fs/observed` 驱动并按根过滤的 `changes`。Client spec 覆盖提供者帧、变更流、不支持地址及注册与释放。`fs/fs` 与 `fs-local` spec 钉住 `readByteRange`;`dsh-util-workspace-path` spec 钉住文件地址语法。connection fixture 为 web e2e 套件提供 `stat`、分页 `read`、`list` 与一帧可选启用的 `changes`。
 
 ## Deferred
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.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/implemented/bug-fix/2026-09-05-nested-terminal-cards.md
-2026-09-05-nested-terminal-cards.md: b369aef20b0b5fbb338e40affd5fd2800b525bcb
+2026-09-05-nested-terminal-cards.md: 03b80382a00c2302d25d2b572c3b06aedff0e1b3
 2026-09-05-nested-terminal-cards.zh.md: bf97fffa2a8d92562e982d498d17b830e1e25f48

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md

@@ -28,7 +28,7 @@ Shell output ending in a recognized spill-policy notice uses generic output: exp
 
 ## Consequences
 
-Rows and Details share terminal derivation for nested calls without a second renderer or presentation hint. Generic fallback and settled-persistent behavior remain separate from terminal-card eligibility. The parent-child relationship still controls tree placement, not terminal rendering. Text recognition cannot authenticate output: a tool can print the same notice. A match selects conservative generic presentation, not proof of spill provenance or process status.
+Rows and Details share terminal derivation for nested calls without a second renderer or presentation hint. Generic fallback and settled-persistent behavior remain separate from terminal-card eligibility. The parent-child relationship still controls tree placement, not terminal rendering. Text recognition cannot authenticate output: a tool can print the same notice. A match selects conservative generic presentation, not proof of spill source or process status.
 
 ## Verification
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.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/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md
-2026-09-05-patch-plugin-file-urls.md: 48112483b4a3eef1fefcb774c2d19a2c901ccbdb
+2026-09-05-patch-plugin-file-urls.md: bad63355ce74b520b5e49b94deeefb2d648b71f4
 2026-09-05-patch-plugin-file-urls.zh.md: 775a576324dd2856f490cf3ce3ba907965737fc2

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md

@@ -18,7 +18,7 @@ The optional `HostResolvedRootInclude` import override separately converts absol
 
 **Convert only the Python fixture with `Path.as_uri()`.** This avoids one failure but leaves user-authored profile and overlay patches exposed.
 
-**Change the shared Loader base.** This loses per-patch provenance and changes bare-package resolution. File URLs preserve the selected local file without changing the resolver base.
+**Change the shared Loader base.** This loses each patch's file source and changes bare-package resolution. File URLs preserve the selected local file without changing the resolver base.
 
 ## Consequences
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.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/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md
-2026-09-05-pi-ai-upgrade-compatibility.md: ed400ab62d221e1b58025617f05500590bfed5e7
+2026-09-05-pi-ai-upgrade-compatibility.md: e28d911facae33fcf278a6824376f889e3416d77
 2026-09-05-pi-ai-upgrade-compatibility.zh.md: 408669ddc963a505254529acaab4c19e9774c6b1

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md

@@ -12,7 +12,7 @@ The pi-ai adapter classifies upstream compatibility fields explicitly and persis
 
 The adapter follows [pi-ai 0.85.1](https://github.com/earendil-works/pi/blob/v0.85.1/packages/ai/CHANGELOG.md). `thinkingTokenBudgetField`, `vllmPriority`, and `supportsMaxOutputTokens` are opt-in gateway controls; `thinking.budget` joins the existing template placeholders. The SDK owns budget resolution and serialization. `supportsMidConvoEffort` and `allowedFallbackModels` remain catalog-owned because their correctness depends on exact Anthropic transports, model capabilities, and fallback pricing.
 
-Optional `providerThinkingLevel` remains in the adapter replay-v2 response metadata so Anthropic history retains its provider-native effort. Absence remains valid; neither the replay version nor the released Session format changes. Replay provenance retains the requested model while `responseModel` retains an Anthropic alias resolution or fallback. Reconstruction restores that native model so pi-ai still applies its cross-model signature rules. The provider-neutral LLM API stays unchanged; the [provider-routed replay ownership rules](../architecture/2026-07-14-provider-routed-llm-adapters.md) still apply. The 0.84.2 Anthropic adapter [initializes `model` from the request](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L510-L515) and [records only response id and usage at message start](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L589-L605). It never writes `responseModel`, so its replay records retain the requested model without native-model metadata.
+Optional `providerThinkingLevel` remains in the adapter replay-v2 response metadata so Anthropic history retains its provider-native effort. Absence remains valid; neither the replay version nor the released Session format changes. Replay metadata retains the requested model while `responseModel` retains an Anthropic alias resolution or fallback. Reconstruction restores that native model so pi-ai still applies its cross-model signature rules. The provider-neutral LLM API stays unchanged; the [provider-routed replay ownership rules](../architecture/2026-07-14-provider-routed-llm-adapters.md) still apply. The 0.84.2 Anthropic adapter [initializes `model` from the request](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L510-L515) and [records only response id and usage at message start](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L589-L605). It never writes `responseModel`, so its replay records retain the requested model without native-model metadata.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.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/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md
-2026-09-05-session-reference-spill-reuse.md: d9f9e3702075417a9b5c742831737e412e75bd00
+2026-09-05-session-reference-spill-reuse.md: 2a24e3adf28c70df8feb726700376df6f06dee87
 2026-09-05-session-reference-spill-reuse.zh.md: 346ed1a39ee65a560cf9e74e519ae45c2ce133e1

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md

@@ -26,7 +26,7 @@ Cancellation after an asynchronous save prevents context publication, even if st
 
 **Put omission and retrieval data inside the bounded preview JSON.** Rejected because that spends the conversation budget on metadata and can hide the notice precisely when the budget is smallest. Separate durable model-visible text preserves both obligations.
 
-**Use tool provenance for every spill.** Rejected because a session reference has no model-issued tool call. Invented tool ids would misattribute the artifact rather than describe its producer.
+**Use a tool source for every spill.** Rejected because a session reference has no model-issued tool call. Invented tool ids would misattribute the artifact rather than describe its producer.
 
 ## Consequences
 

+ 3 - 3
.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.i18n.yaml → .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml

@@ -1,6 +1,6 @@
 # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md
-2026-09-06-agent-request-freeze-provenance.md: 1235a87ea549c6bbd9c53620017cb1d96f8e7cf7
-2026-09-06-agent-request-freeze-provenance.zh.md: 357b25b0f252a9423c485b2cc119f1226ec16687
+#   pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md
+2026-09-11-session-controller-fork-turn-cut.md: 0c8d45c861df964a62586827e80c613704ed17f0
+2026-09-11-session-controller-fork-turn-cut.zh.md: 729a42a596264ac618608d26cd52a1ad836b58db

+ 29 - 0
.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md

@@ -0,0 +1,29 @@
+# Agent Note: Session Controller forks stop at the selected turn end
+
+Status: implemented
+
+English | [中文](2026-09-11-session-controller-fork-turn-cut.zh.md)
+
+## Problem
+
+A user input enters the durable inbox before its `turn/start`. Extending a completed-turn fork through the following between-turn events can copy the next input's insertion without its later removal. Continuing the child then executes an input from beyond the selected turn.
+
+## Decision
+
+The [Session Controller](../../../../packages/api/session-controller/README.md) copies the contiguous prefix through the selected `turn/end`, inclusive. Explicit anchors select the first closing event at or after the anchor; omitted and past-end anchors select the last closing event. No event after that closing event belongs to the seed, including queued input, titles, and model settings.
+
+The lower-level `SessionStore.fork()` retains its explicit stable-event semantics from the [log-only event decision](../simplification/2026-07-28-remove-synthetic-log-only-turns.md). Selecting a completed turn in the controller does not request a later stable event.
+
+## Alternatives considered
+
+**Stop only at the next inbox event.** Event-type exceptions still copy unrelated state changes after the selected turn and require the controller to classify plugin-owned events.
+
+**Copy the tail and clear the child's inbox.** Clearing adds child events to cancel input that lies outside the requested prefix, while still inheriting other state from after the selected turn.
+
+## Consequences
+
+A fork inherits the configuration recorded through its selected turn. Later configuration and title events are excluded. The client may independently assign the child's fork title. Events already inside the selected prefix retain their ordinary replay semantics; this decision does not redefine pending input inserted before the selected closing event.
+
+## Verification
+
+Controller tests execute the production loop and check that sending C after forking A excludes the parent's later B from child history and produces one model request. Message, closing-event, omitted, and past-end anchors share this assertion. Model-routing coverage excludes a later configuration change. The Web message-actions snapshot seeds a queued input after the completed turn and verifies the branch action creates a child without that input or its inbox insertion.

+ 29 - 0
.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md

@@ -0,0 +1,29 @@
+# Agent Note: Session Controller 分叉截到选中轮次的结束事件
+
+Status: implemented
+
+English | [中文](2026-09-11-session-controller-fork-turn-cut.md)
+
+## Problem
+
+用户输入会先进入持久化 inbox,再出现对应的 `turn/start`。如果已结束轮次的分叉继续复制后面的轮次间事件,就可能复制下一条输入的入队事件,却没有复制其后的出队事件。继续子会话时,就会执行选中轮次之后的输入。
+
+## Decision
+
+[Session Controller](../../../../packages/api/session-controller/README.zh.md) 复制截至选中 `turn/end` 的连续前缀,并包含该事件。显式锚点选择位于锚点或其后的第一条结束事件;省略锚点或锚点超出日志末尾时,选择最后一条结束事件。结束事件之后的所有事件均不属于种子,包括排队输入、标题和模型设置。
+
+底层 `SessionStore.fork()` 保留[纯日志事件决策](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)规定的显式稳定事件语义。在控制器中选择已结束轮次,不代表请求复制到更晚的稳定事件。
+
+## Alternatives considered
+
+**仅在下一条 inbox 事件处停止。** 按事件类型添加例外仍会复制选中轮次之后的无关状态变化,并要求控制器分类插件所属的事件。
+
+**复制尾部后清空子会话 inbox。** 清空操作会添加子会话事件,以取消本来就在所请求前缀之外的输入,同时仍然继承选中轮次之后的其他状态。
+
+## Consequences
+
+分叉继承截至选中轮次记录的配置。之后的配置和标题事件被排除。客户端可以独立设置子会话的分叉标题。已经位于选中前缀内的事件保留通常的回放语义;本决策不重新定义在选中结束事件之前入队的待处理输入。
+
+## Verification
+
+控制器测试执行生产循环,检查从 A 分叉后发送 C 时,子会话历史不包含父会话后续的 B,并且只产生一次模型请求。消息、结束事件、省略及越界锚点共用此断言。模型路由覆盖排除了后续配置变更。Web 消息操作快照在已结束轮次后植入排队输入,验证分叉操作创建的子会话不包含该输入或其入队事件。

+ 1 - 1
.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.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/implemented/feature/2026-07-28-continuable-subagent-conversations.md
-2026-07-28-continuable-subagent-conversations.md: 8c3f2e1da593157f17528f13fc8842012aad0284
+2026-07-28-continuable-subagent-conversations.md: af4382a764a59a2d7c02f3cd5feffb32022441f8
 2026-07-28-continuable-subagent-conversations.zh.md: 886e31fa3d88b78f875d51058bb2ec8c525837fe

+ 2 - 2
.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md

@@ -44,7 +44,7 @@ Cold resume does not dispatch through a subagent provider. The continuation mana
 
 `SubagentProvider.start()` and `SubagentRun` remain exclusively on the unchanged one-shot path. A continuable Activation directly owns its `AgentHandle` and never creates, wraps, or retains a `SubagentRun`; `SubagentRun.steer?()` is therefore absent.
 
-`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives durable `agent-message` provenance from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Both start and send return the accepted `MessageId`, and neither reports how the manager materialized the Activation.
+`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives a durable `agent-message` source from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Both start and send return the accepted `MessageId`, and neither reports how the manager materialized the Activation.
 
 For start and follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance. After the operation returns its `MessageId`, the manager owns the Activation independently; later caller cancellation does not cancel the accepted turn or dispose the child.
 
@@ -117,7 +117,7 @@ The shared `sendMessage(sender, targetId, content, options)` service operation a
 
 ### Agent and human scheduling
 
-Every accepted Agent message uses `Agent.steer()`. A running target claims it at the nearest step boundary; an idle or cold-resumed target starts a turn. Browser-authored human input separately carries `delivery: 'queue' | 'steer'` through `subagent.prompt`: Queue opens a later FIFO turn, while Steer uses the same best-effort nearest-step scheduling without changing the message's human provenance. The public service exposes no caller-selectable scheduling mode for Agent messages.
+Every accepted Agent message uses `Agent.steer()`. A running target claims it at the nearest step boundary; an idle or cold-resumed target starts a turn. Browser-authored human input separately carries `delivery: 'queue' | 'steer'` through `subagent.prompt`: Queue opens a later FIFO turn, while Steer uses the same best-effort nearest-step scheduling without changing the message's human source. The public service exposes no caller-selectable scheduling mode for Agent messages.
 
 ### Authority and recorded sender identity
 

+ 2 - 2
.agents/notes/implemented/feature/2026-08-05-agent-teams.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/implemented/feature/2026-08-05-agent-teams.md
-2026-08-05-agent-teams.md: 1fcbe9c586b78db3d5638074e51745c8c189c80c
-2026-08-05-agent-teams.zh.md: 5520b9c1e7fa039be677881f4d2df504f835ae81
+2026-08-05-agent-teams.md: bdf646db9b1617ded7f50f4145887f34a52ae83c
+2026-08-05-agent-teams.zh.md: 5386044edb57dad9fabf114d565808c735846464

+ 8 - 0
.agents/notes/implemented/feature/2026-08-05-agent-teams.md

@@ -20,6 +20,14 @@ The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, whi
 
 The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit.
 
+## Profile delegation
+
+The [Team profile](../../../../packages/experimental/agent-team-profile/README.md) disables `subagent` and `subagent_fork` together with the overlapping global controls. Direct model delegation uses `spawn_teammate` with fresh or fork context, keeping those children in the durable roster. Workflow remains available through the base profile’s fresh `spawn` provider for scripted orchestration; it cannot inherit a teammate’s conversation identity through the model tool. The Subagent services and providers remain shared infrastructure. Ordinary Session forks retain their history without identity correction. Provider-owned child tool visibility remains a [documented limitation](../../../../packages/experimental/tool-agent-team/README.md#known-limitations-and-deferred-work).
+
+## Team identity
+
+The `spawn_teammate` tool prefixes the initial task with a user-role `<system-reminder>` stating `You are teammate "<name>".`. Identity and task enter the same durable inbox message. Shared system policy and all tool schemas stay uniform across members; execution owns role restrictions. Team tools resolve the caller’s Team and accept member names, so the model needs no Team id. Identity follows ordinary history through cold recovery and compaction; the plugin does not inspect reminder retention or add replacement messages. Forks inherit the recorded text without a Lead identity correction. Putting identity in the system prompt changes the prefix before inherited history; keeping it in the initial task preserves that prefix without per-step identity bookkeeping. Existing system-embedded identities may require a one-time prompt reconciliation; retained event generations are unchanged.
+
 ## Provisioning and recovery
 
 Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and mailbox-dispatch transactions, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.

+ 8 - 0
.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md

@@ -20,6 +20,14 @@ Agent Teams 的公开约定仍处于实验阶段,因此需要显式启用的
 
 Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。
 
+## Profile delegation
+
+[Team profile](../../../../packages/experimental/agent-team-profile/README.zh.md) 禁用 `subagent`、`subagent_fork` 以及名称重叠的全局控件。模型直接委派使用支持 fresh 或 fork 上下文的 `spawn_teammate`,使这些子代理进入持久 roster。Workflow 仍通过 base profile 的 fresh `spawn` 提供方执行脚本编排;经模型工具创建的 workflow 子代理不会继承 teammate 的对话身份。Subagent 服务和提供方仍是共享基础设施。普通 Session fork 保留历史,不纠正身份。提供方所拥有的子代理工具可见性仍是[已记录的限制](../../../../packages/experimental/tool-agent-team/README.zh.md#known-limitations-and-deferred-work)。
+
+## Team identity
+
+工具 `spawn_teammate` 在初始任务前加上 user-role `<system-reminder>`,声明 `You are teammate "<name>".`。身份和任务进入同一条持久化收件箱消息。共享 system 策略和全部工具 schema 在成员间保持一致;执行时检查角色权限。Team 工具根据调用者确定 Team,并接受成员名字,因此模型不需要 Team id。身份随普通历史经历冷恢复和压缩;插件不检查提醒是否保留,也不添加替代消息。fork 继承已记录文本,不补发 Lead 身份修正。把身份放进 system prompt 会在继承历史之前改变前缀;把它留在初始任务中既保留此前缀,也无需每步维护身份提醒。已有的 system 内嵌身份可能需要一次提示词协调;保留的事件格式代际保持不变。
+
 ## Provisioning and recovery
 
 创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与 mailbox dispatch 事务,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。

+ 1 - 1
.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.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/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md
-2026-08-06-manager-owned-subagent-settlement-delivery.md: f223571dc91300d085b7fcf0a9e3196daa48b760
+2026-08-06-manager-owned-subagent-settlement-delivery.md: 9d14d4af775809a974349c753a7b4b593a145489
 2026-08-06-manager-owned-subagent-settlement-delivery.zh.md: 4bbee373aa2b50902af5319f9398a89da9cc3143

+ 1 - 1
.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md

@@ -18,7 +18,7 @@ The continuation manager delivers the account itself, from inside the disposal t
 
 When a resident Activation settles, `notifySettlement()` resolves the child's durable direct parent and sends it one user-role message: the epoch's outcome as a sentence the parent can act on, then the child's final assistant content, or a statement that it produced none. Delivery is unconditional for every child whose id a caller actually received. It does not consult whether the child reported, and it keeps no bookkeeping that could make the promise conditional — that unconditionality is what lets `tool-subagent` promise a runtime notice containing the outcome and any final assistant message. A materialization rolled back before its first accepted message stays silent, because the caller was told that child was not established.
 
-### Provenance
+### Runtime source
 
 The notice carries `{ kind: 'subagent-settled', form: 'notice', summary, senderSessionId }`. It is deliberately not the `agent-message` kind used by `send_message`. An Agent message is content the child chose; this is the runtime stating what became of the child. Merging them would credit the child with words it never wrote, and would make a durable log unable to distinguish "the child said it was done" from "the harness observed that it stopped". The `notice` form also gives a UI the collapsed one-line presentation this message wants, where `relay` presents Agent correspondence.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.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/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md
-2026-08-22-fire-and-forget-webhook-sessions.md: 976bccd8b460de7cb696ee45ea8963710cc4c738
+2026-08-22-fire-and-forget-webhook-sessions.md: 08f718b8063c7ca0d78e93f547c0e0c33c183d07
 2026-08-22-fire-and-forget-webhook-sessions.zh.md: f015d993e61864bbc21d9d55fc331c25118fbde3

+ 3 - 3
.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md

@@ -28,7 +28,7 @@ Patch loading anchors relative plugin names in inserted rows to the patch file.
 
 A rule result names a local Workspace path, title, text prompt, agent preset, permission preset, and optional explicit provider/model route with an output cap. Without that route, the runtime snapshots the complete live default, including reasoning effort, until the first request records its durable header. It validates presets before mutation, resolves or creates the canonical Workspace, creates the Agent with that path as Session cwd, mounts the preset before publication, and attaches the Session before admitting the prompt.
 
-The initial follow-up is an ordinary durable user-role message with webhook provider, source, delivery, and rule provenance. Its inbox insertion is the webhook operation's last boundary. Ordinary Session persistence and Agent lifecycle own later work; the runtime neither flushes specially nor waits for a turn.
+The initial follow-up is an ordinary durable user-role message whose source records the webhook provider, source, delivery, and rule identifiers. Its inbox insertion is the webhook operation's last boundary. Ordinary Session persistence and Agent lifecycle own later work; the runtime neither flushes specially nor waits for a turn.
 
 ## Alternatives considered
 
@@ -40,13 +40,13 @@ The initial follow-up is an ordinary durable user-role message with webhook prov
 
 **Restrict rules to a declarative predicate language.** Rejected because programmatic rules explicitly need arbitrary external calls. Trusted Cordis plugins already provide the required authority and lifecycle.
 
-**Let each adapter create Sessions directly.** Rejected because Workspace, preset, permission, title, rollback, and provenance logic would spread across provider packages.
+**Let each adapter create Sessions directly.** Rejected because Workspace, preset, permission, title, rollback, and message-source logic would spread across provider packages.
 
 ## Verification
 
 Package tests pin independent callback execution, fire-and-forget HTTP timing, cancellation and quiescent disposal, request validation, Workspace attachment before prompt admission, rollback, GitHub HMAC and body limits, credential rotation, and exact Loader composition. The assembled Web example sends a signed ready-for-review delivery to an isolated second listener and records the resulting ordinary Workspace conversation.
 
-A real-API e2e test starts the built `dsh web` CLI with the webhook overlay and isolated listener, synthesizes only the signed inbound GitHub delivery, observes Workspace attachment and durable provenance through the public Web API, and waits for the real DeepSeek response. No DSH service, model adapter, or provider call is replaced by a test double.
+A real-API e2e test starts the built `dsh web` CLI with the webhook overlay and isolated listener, synthesizes only the signed inbound GitHub delivery, observes Workspace attachment and the durable message source through the public Web API, and waits for the real DeepSeek response. No DSH service, model adapter, or provider call is replaced by a test double.
 
 Source audits keep execution records, retry timers, dedupe maps, completion events, and Agent-status listeners absent.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.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/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md
-2026-08-27-continuable-subagent-human-inbox-control.md: cf5dfd070dfd600fb64bc529b4a1476a258c1181
+2026-08-27-continuable-subagent-human-inbox-control.md: 6bf8ceb2b8eab7138a1e617bb97e475d5cfa8172
 2026-08-27-continuable-subagent-human-inbox-control.zh.md: 081fb84f75afcc94339ae3bf627480081bc56d89

+ 2 - 2
.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md

@@ -20,7 +20,7 @@ The browser gives a continuable child the ordinary busy Enter/Cmd+Enter Queue/St
 
 The existing `session.updateQueue(itemId, action)` resolves the exact live Agent and admits a subagent-owned Session only when its current projected identity is continuable and the descriptor sequence belongs to the child's own non-seed suffix. A live one-shot Agent and a missing, inherited-only, or invalid identity retain the ownership failure. An absent Agent returns `queue-item-not-found` and does not cold-resume the child. The target Session id is sufficient human authority for a live inbox occurrence mutation; a parent address is not required. Edit and Remove retain their complete existing `nextTurn` and `nextStep` semantics, including plugin-injected context, while Steer requires a queued occurrence and an Agent that reports running when the command begins.
 
-The continuation manager keeps no second message-reservation state. One private `SubagentInbox` delegates Queue and Steer to the Agent inbox and owns the Activation's existing closing promise. Natural settlement waits for `Agent.whenIdle()`, an empty child Inbox, and disposal of every owned child. The manager confirms the Inbox, owned-child set, and wake generation under the child lock, then flushes final Session state while admission remains open. The final child-lock decision revalidates the Session sequence and the same residency facts, then synchronously starts an `Agent.runMaintenance()` task whose entry claims the idle phase and closes the wrapper in the same JavaScript turn. Every pending Inbox occurrence retains the Activation regardless of its delivery mode or provenance. Manager-owned deliveries, Inbox claims or discards, and owned-child release renew the wake generation. Direct Agent work accepted during the flush either changes the final Session or residency observation, remains active and prevents the final maintenance task from starting, or completes before revalidation.
+The continuation manager keeps no second message-reservation state. One private `SubagentInbox` delegates Queue and Steer to the Agent inbox and owns the Activation's existing closing promise. Natural settlement waits for `Agent.whenIdle()`, an empty child Inbox, and disposal of every owned child. The manager confirms the Inbox, owned-child set, and wake generation under the child lock, then flushes final Session state while admission remains open. The final child-lock decision revalidates the Session sequence and the same residency facts, then synchronously starts an `Agent.runMaintenance()` task whose entry claims the idle phase and closes the wrapper in the same JavaScript turn. Every pending Inbox occurrence retains the Activation regardless of its delivery mode or source. Manager-owned deliveries, Inbox claims or discards, and owned-child release renew the wake generation. Direct Agent work accepted during the flush either changes the final Session or residency observation, remains active and prevents the final maintenance task from starting, or completes before revalidation.
 
 QueueDock Steer uses the Agent's best-effort delivery after the command admits a running queued occurrence. If the queued occurrence was claimed first, `queue-item-not-found` leaves its ordinary Queue delivery underway. If active cancellation wins during the synchronous transfer, Agent steering appends the message to `nextTurn`, latches a wake, and the Session command still succeeds. The selected message moves behind the remaining Queue in that fallback case. Newly composed Steer uses the same fallback and remains deliverable when it misses the nearest step.
 
@@ -46,6 +46,6 @@ Continuable child conversations and ordinary Sessions share one human inbox inte
 
 The generic Session command has one narrow ownership-fence exception for a live subagent-owned Agent with a valid own-suffix continuable identity. Because the operation addresses either inbox destination, a caller that knows a pending `MessageId` can edit or remove plugin-supplied next-step input, exactly as on an ordinary Session. QueueDock renders only `queued`-placement rows, so no browser gesture reaches that input; an edit there also keeps the original producer's `MessageSource`, which would attribute human text to that producer.
 
-Inbox notifications retain their occurrence semantics and do not carry continuation residency. Claim and discard notifications only wake settlement after pending work changes; `whenIdle()`, the final idle-phase maintenance task, `Inbox.hasPending`, the owned-child set, the Activation generation, and the Session sequence decide whether disposal is safe without depending on scheduler ordering, message identity, or provenance. The final flush precedes the closing cutoff, so a detached hook, job completion, or direct Agent delivery accepted during that await invalidates the observation instead of being stopped by the resulting disposal. Maintenance that remains active prevents the final task from claiming the idle phase; maintenance that starts and finishes during the flush has completed before disposal. A child left holding only injected context remains resident even though no driver is obliged to claim it; without a later waking delivery, queue removal, or manager teardown, that child and its live ancestors can remain resident for the process lifetime. A replayed Inbox follows the same conservative rule without reconstructing how each pending message was delivered.
+Inbox notifications retain their occurrence semantics and do not carry continuation residency. Claim and discard notifications only wake settlement after pending work changes; `whenIdle()`, the final idle-phase maintenance task, `Inbox.hasPending`, the owned-child set, the Activation generation, and the Session sequence decide whether disposal is safe without depending on scheduler ordering, message identity, or source. The final flush precedes the closing cutoff, so a detached hook, job completion, or direct Agent delivery accepted during that await invalidates the observation instead of being stopped by the resulting disposal. Maintenance that remains active prevents the final task from claiming the idle phase; maintenance that starts and finishes during the flush has completed before disposal. A child left holding only injected context remains resident even though no driver is obliged to claim it; without a later waking delivery, queue removal, or manager teardown, that child and its live ancestors can remain resident for the process lifetime. A replayed Inbox follows the same conservative rule without reconstructing how each pending message was delivered.
 
 Model-side scheduling remains fixed rather than caller-selectable. The adjacent-Agent `send_message` tool always uses Steer, while only the browser human path chooses Queue or Steer.

+ 1 - 1
.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.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/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md
-2026-09-02-in-history-system-prompt-replacement.md: 8e5fa65ec4649537889cefc75459d2a4958165e5
+2026-09-02-in-history-system-prompt-replacement.md: aaaf44d6388e6daf98acf8b1a93a61f1d7c55703
 2026-09-02-in-history-system-prompt-replacement.zh.md: 73003b04de82f45154f9c673654cc492eb62a4fd

+ 2 - 2
.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md

@@ -47,7 +47,7 @@ Web presents an appended in-history node at its own position. `SystemPromptNode`
 
 Trajectory chooses the newer of the preceding real header and the preceding synthetic system header as the comparison state. A real header owns configuration and tools; an appended prompt can advance that state without another real header. Comparing only real headers would report A rather than B as the previous prompt for an A → B → C sequence.
 
-Chat and Trajectory interpret the effective prompt through the pure `uiConversation.inspectSystemPrompt` operation. Each target keeps immutable prefix states for system events and positional replacements, with only surviving system nodes and a map of surviving replacement sequences to inherited surface positions. Each replacement copies that map and removes shadowed entries; historical prefix maps remain immutable. Ordinary appends and streaming updates require no prompt fold. Surface order, rather than event order or provenance citations, determines which nodes survive: a compaction can restore an older prompt without another system event, and a head rewrite can have a greater sequence than a later active prompt. Empty nodes remain addressable but do not override a nonempty prompt. An endpoint older than the earliest relevant loaded event has unknown order unless its replacement position is indexed. The interpreter withholds all subsequent prompt text after such an endpoint until prepend replay resolves the missing prefix; numeric event order cannot establish surface order. Historical cards keep their own prefix state rather than reading the final surface.
+Chat and Trajectory interpret the effective prompt through the pure `uiConversation.inspectSystemPrompt` operation. Each target keeps immutable prefix states for system events and positional replacements, with only surviving system nodes and a map of surviving replacement sequences to inherited surface positions. Each replacement copies that map and removes shadowed entries; historical prefix maps remain immutable. Ordinary appends and streaming updates require no prompt fold. Surface order, rather than event order or source-event citations, determines which nodes survive: a compaction can restore an older prompt without another system event, and a head rewrite can have a greater sequence than a later active prompt. Empty nodes remain addressable but do not override a nonempty prompt. An endpoint older than the earliest relevant loaded event has unknown order unless its replacement position is indexed. The interpreter withholds all subsequent prompt text after such an endpoint until prepend replay resolves the missing prefix; numeric event order cannot establish surface order. Historical cards keep their own prefix state rather than reading the final surface.
 
 ### Compaction
 
@@ -91,7 +91,7 @@ Lifecycle verification requires no event for an unchanged prompt and an appended
 - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` pins the append on a continuing series, the re-baseline at a series start with or without surviving later nodes and with changed or unchanged effective text, the empty-prompt clearing of all active versions, and the replace-only behaviour without the capability.
 - `packages/core/agent-loop/tests/request-reconstruction.spec.ts` pins the appended node under an inherited header with `request/context` carrying `systemPromptUpdate`, the series-start fold into node 0, the compaction-driven re-baseline, and the tool-schema change re-baseline under a `change` header that starts a series.
 - `packages/llm/llm/tests/service.spec.ts`, `packages/llm/llm-deepseek/tests/adapter.spec.ts`, and `packages/test-support/llm-replay/tests/llm-replay.spec.ts` pin the declared mode on resolved model info and the load-time rejection of any other value.
-- `packages/llm/token-meter/tests/context-breakdown-projection.spec.ts` pins newest/middle prompt removal, exact heuristic totals, surface ordering after head rewrites, extra provenance citations, dormant empties and fallback clears, immutable transitions, compact retained checkpoints, late registration, replay, and version invalidation.
+- `packages/llm/token-meter/tests/context-breakdown-projection.spec.ts` pins newest/middle prompt removal, exact heuristic totals, surface ordering after head rewrites, extra source-event citations, dormant empties and fallback clears, immutable transitions, compact retained checkpoints, late registration, replay, and version invalidation.
 - `packages/client/ui-conversation`, `ui-chat`, and `ui-trajectory` client specs pin the update card, the same-step header dedupe, the absent system change after an update, and the synthetic trajectory header.
 - The keyless authored snapshot `snapshots/session/system-prompt-in-history/` declares the capability on the replay route, changes the prompt after the first tool call through a fixture section, and pins the appended `system/message`, the untouched node 0, the single `request/header`, and the `request/context` mode.
 - `packages/llm/llm-deepseek/tests/adapter.e2e.ts` runs a two-step prompt change against the model named by `DEEPSEEK_IN_HISTORY_MODEL`, asserts that the reply follows the appended prompt, and asserts that the appended request reads more cached tokens than the same conversation with a rewritten leading prompt; it skips when the variable is unset.

+ 6 - 0
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.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/feature/2026-09-07-deepseek-messages-adapter.md
+2026-09-07-deepseek-messages-adapter.md: 981ca1ec47f7faa3bcabf5db54393a73474e846c
+2026-09-07-deepseek-messages-adapter.zh.md: 8a1059cfa561b6576518c1ce454f0857450682c8

+ 39 - 0
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md

@@ -0,0 +1,39 @@
+# Agent Note: DeepSeek through the Anthropic Messages protocol
+
+Status: implemented
+
+English | [中文](2026-09-07-deepseek-messages-adapter.zh.md)
+
+## Problem
+
+Deployments expose DeepSeek through Anthropic Messages gateways as well as chat-completions. Messages represents thinking, signatures, tool calls, tool results, and cumulative usage differently. Translating only the endpoint or flattening assistant history loses information needed by subsequent tool turns.
+
+## Decision
+
+The [DeepSeek adapter](../../../../packages/llm/llm-deepseek/README.md) serves multiple protocols under one `deepseek-official` route and `llm-deepseek` settings namespace. `common/` shares configuration, the model catalog, and capability resolution; `protocols/chat-completions/` and `protocols/messages/` own serialization, stream conversion, and transport. Cordis YAML selects the implementation through `protocol`, defaulting to `chat-completions`. The existing `PreparedAdapterCall` freezes protocol, endpoint, credential reference, and model capabilities; retries retain that generation while subsequent calls read new configuration.
+
+The adapter follows the [DeepSeek compatibility documentation](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) and [Anthropic streaming protocol](https://platform.claude.com/docs/en/build-with-claude/streaming). The pi-ai Anthropic implementation informed the handling of adjacent user messages, cumulative usage, fragmented tool arguments, and optional thinking signatures. DeepSeek effort uses `output_config.effort`; an Anthropic thinking token budget does not control DeepSeek effort.
+
+Assistant blocks remain the durable model-visible content. A versioned `ReplayEnvelope` stores only the protocol format, model identity, aligned block kinds, and signatures absent from those blocks. Same-model Messages continuation restores signatures verbatim, including empty signatures; foreign history carries no invented signature. Unusable metadata follows the existing [replay degradation rule](../architecture/2026-07-14-provider-routed-llm-adapters.md): the request omits signatures with a warning while preserving durable content; content validation such as tool argument parsing still fails explicitly. This keeps provider replay data opaque to the loop while preserving it through Session persistence and block pruning.
+
+Image requests use bounded inline base64 versions from the attachment service. Shared attachment offload and DeepSeek token measurement keep request and measurement policy consistent. Files uploads remain outside this adapter because their endpoints and cache ownership differ from chat-completions; adding them requires a Messages-specific lifetime and error policy.
+
+System updates use the existing [route capability](2026-09-02-in-history-system-prompt-replacement.md) when explicitly declared for an endpoint/model. Messages retains the initial top-level system and emits later snapshots as native system turns after the corresponding user/tool-result turn, preserving previously sent prefixes. This placement differs from the loop's system-before-user admission; serialization changes neither the durable log nor conversation-turn order. Undeclared routes consolidate the latest snapshot at the top level, including direct compaction calls. Capability inference from protocol or model names is insufficient because support and update semantics depend on the deployed endpoint.
+
+Web always displays DeepSeek without a protocol selector. Both protocols share `baseURL` and `apiKeyEnv`, with no nested per-protocol configuration map. Without an endpoint override, resolution uses the selected protocol’s official default; Messages uses `https://api.deepseek.com/anthropic`. Switching retains existing endpoint overrides, whose compatibility belongs to the deployment. One model catalog includes `deepseek-flash` text/image and in-history system capabilities and retains the V4 entries.
+
+## Alternatives considered
+
+**Separate plugins per protocol.** This duplicates credentials, catalogs, settings cards, and provider identities, and forces users to reselect models when the wire protocol changes. Protocol folders inside one plugin retain implementation isolation; Responses can add an implementation without changing the user configuration structure.
+
+**Delegate the new route to pi-ai or the Anthropic SDK.** Both provide maintained protocol implementations, but the requested direct adapter needs DeepSeek-specific configuration, attachment policy, credential resolution, and retry ownership. A small stream translator with a maintained SSE parser keeps these responsibilities explicit; the library-backed adapter remains available independently.
+
+**Persist complete native responses or flatten thinking into text.** Full responses duplicate logged content and complicate truncation alignment. Flattening changes the next model input. Minimal aligned replay metadata preserves the missing protocol information without a new Session format.
+
+**Always rewrite the top-level system prompt.** This discards the cache-preserving native update path on capable routes. Explicit capability selection keeps that path while retaining ordinary replacement for other endpoints; converting system instructions to user text would also lose their priority.
+
+## Consequences
+
+The package owns wire validation, stop-reason mapping, cancellation, and error classification, so protocol changes require adapter maintenance. Unsupported content and incomplete streams fail explicitly. The existing retry consumer owns retries; the existing assembler drops incomplete tool calls at the output limit. The shared base and Web default to Chat Completions; Messages requires explicit opt-in.
+
+Verification covers wire fixtures, real Loader composition, per-file unit coverage, [recorded Session replay](../../../../snapshots/session/deepseek-messages-replay/snapshot.yml) with [unknown replay versions](../../../../snapshots/session/deepseek-messages-degraded-replay/snapshot.yml), a Web Messages Session replay, and credential-gated text, thinking, tool continuation, image, and cancellation requests. Live gateway checks establish compatibility with the configured gateway; they do not establish compatibility with every Anthropic proxy.

+ 39 - 0
.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md

@@ -0,0 +1,39 @@
+# Agent Note: 通过 Anthropic Messages 协议调用 DeepSeek
+
+Status: implemented
+
+[English](2026-09-07-deepseek-messages-adapter.md) | 中文
+
+## 问题
+
+部署可以通过 Anthropic Messages 网关或 chat-completions 调用 DeepSeek。Messages 对思考、签名、工具调用、工具结果和累计用量的表示不同。仅替换端点或压平助手历史会丢失后续工具轮次需要的信息。
+
+## 决策
+
+[DeepSeek 适配器](../../../../packages/llm/llm-deepseek/README.zh.md)通过一个 `deepseek-official` 路由和 `llm-deepseek` 设置命名空间支持多个协议。`common/` 共享配置、模型目录和能力解析;`protocols/chat-completions/` 与 `protocols/messages/` 分别负责协议序列化、流转换和传输。`protocol` 配置在 Cordis YAML 中选择实现,默认 `chat-completions`。已有 `PreparedAdapterCall` 冻结协议、端点、凭据引用与模型能力,重试保持同一代配置,后续调用读取新配置。
+
+适配器遵循 [DeepSeek 兼容文档](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) 和 [Anthropic 流协议](https://platform.claude.com/docs/en/build-with-claude/streaming)。pi-ai 的 Anthropic 实现为相邻用户消息、累计用量、工具参数分片和可选思考签名的处理提供参考。DeepSeek 通过 `output_config.effort` 设置思考强度;Anthropic 思考 token 预算不控制 DeepSeek 思考强度。
+
+助手内容块保留持久化的模型可见内容。带版本的 `ReplayEnvelope` 仅保存协议格式、模型标识、对齐的块类型以及内容块未包含的签名。同模型续接原样恢复签名,包括空签名;外部历史不生成虚构签名。不可用的元数据遵循现有[回放降级规则](../architecture/2026-07-14-provider-routed-llm-adapters.zh.md):请求省略签名并记录警告,保留持久化内容;工具参数等内容校验仍会正常报错。提供者回放数据对循环保持不透明,同时能够随 Session 持久化和内容块裁剪保留。
+
+图片请求使用附件服务生成的、有预算限制的内联 base64 版本。共享附件卸载机制和 DeepSeek token 计量使请求与计量策略保持一致。此适配器不负责 Files 上传,因为其端点和缓存所有权与 chat-completions 不同;增加上传支持需要定义 Messages 专属的生命周期和错误策略。
+
+系统提示词更新在端点与模型显式声明支持时,使用现有[路由能力](2026-09-02-in-history-system-prompt-replacement.zh.md)。Messages 保留初始顶层 system,在对应的用户或工具结果轮次之后,将后续快照发送为原生 system 轮次,保留此前发送的前缀。这个位置不同于循环先 system、后 user 的接纳顺序;序列化既不改写持久化日志,也不改变对话轮次的顺序。未声明能力的路由将最新快照归并到顶层,直接压缩调用也如此。仅凭协议或模型名称推断能力并不充分,因为支持情况和更新语义取决于实际部署的端点。
+
+Web 始终显示 DeepSeek,不提供协议选择器。两个协议共用 `baseURL` 与 `apiKeyEnv`,没有嵌套的协议配置表。未提供地址覆盖时使用当前协议的官方默认值;Messages 为 `https://api.deepseek.com/anthropic`。切换协议保留已有端点覆盖,部署者负责其兼容性。模型目录只维护一份,包含 `deepseek-flash` 的文本/图片和历史内 system 更新能力,也保留 V4 条目。
+
+## 考虑过的替代方案
+
+**每个协议独立插件。** 这会重复凭据配置、模型目录、设置卡片和 provider ID,并迫使用户在底层协议变化时重选模型。单插件中的协议目录保留实现隔离;Responses 可以增加自己的实现而不改变用户配置结构。
+
+**把新路由委托给 pi-ai 或 Anthropic SDK。** 两者均提供持续维护的协议实现,但所需的直接适配器需要 DeepSeek 专属配置、附件策略、凭证解析和重试所有权。小型流转换器配合持续维护的 SSE 解析器使这些职责保持明确;库实现适配器仍可独立使用。
+
+**持久化完整原生响应,或把思考压平为文本。** 完整响应重复已记录内容,并使截断对齐复杂化。压平会改变下一次模型输入。最小化的对齐回放元数据可以保留缺失的协议信息,无需新增 Session 格式。
+
+**始终重写顶层系统提示词。** 这会丢弃支持该能力的路由上能够保留缓存的原生更新方式。显式选择能力既保留该方式,也为其他端点保留普通替换;将 system 指令转成 user 文本还会失去其优先级。
+
+## 结果
+
+该包负责协议校验、停止原因映射、取消和错误分类,因此协议变化需要维护适配器。不支持的内容和不完整的流会明确报错。现有重试消费者负责重试;现有装配器在输出达到上限时丢弃未完成的工具调用。共享 base 与 Web 默认使用 Chat Completions;Messages 需要显式启用。
+
+验证覆盖协议夹具、真实 Loader 组合、逐文件单元覆盖率、[已记录 Session 回放](../../../../snapshots/session/deepseek-messages-replay/snapshot.yml)与[未知回放版本](../../../../snapshots/session/deepseek-messages-degraded-replay/snapshot.yml),Web Messages Session 回放,以及凭证控制的文本、思考、工具续接、图片和取消请求。真实网关检查证明与已配置网关的兼容性,不能证明与所有 Anthropic 代理兼容。

+ 6 - 0
.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.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/feature/2026-09-09-headless-machine-readable-run-surface.md
+2026-09-09-headless-machine-readable-run-surface.md: 317f213095f86e94104c013139390cdf988f95fe
+2026-09-09-headless-machine-readable-run-surface.zh.md: 0114f87aef4f25db0887663464973bb2f762b2d5

+ 100 - 0
.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md

@@ -0,0 +1,100 @@
+# Agent Note: Headless machine-readable run surface
+
+Status: implemented
+
+English | [中文](2026-09-09-headless-machine-readable-run-surface.zh.md)
+
+## Problem
+
+`dsh --profile headless` serves a human terminal: the task arrives only through argv, stdout carries one final assistant message, provider reasoning streams to stderr, and every run creates a fresh random session. [Headless is a direct core entry point](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md) owns that transport and completion contract; [headless reasoning progress](../../archived/feature/2026-08-21-headless-reasoning-progress.md) owns the stderr projection.
+
+A supervising process that drives one headless process per wake, such as an external agent runtime, needs three things that contract does not provide. It needs the task over a private pipe rather than argv, because a long prompt exceeds the argument limit and argv is visible to other processes. It needs a machine-readable stream that separates assistant text, reasoning, tool calls and results, turn boundaries, and usage, because scraping stderr yields only reasoning and the final stdout line yields no tool activity. It needs an exact session identity it can pass back on the next wake, because a fresh random session per process makes continuity impossible.
+
+## Decision
+
+The `dsh-headless` bundle owns an opt-in machine-readable run surface. The default invocation keeps the previous contract unchanged: one final assistant message on stdout, reasoning on stderr, exit 0 exactly when the terminal `turn/end` reason is `completed`.
+
+Three additions extend the app-owned command line that [Apps own their command lines](../../archived/architecture/2026-08-06-app-owned-command-line.md) established:
+
+- `--json` replaces the stdout payload with newline-delimited JSON run events. Reasoning becomes an event instead of stderr output, so stderr carries only `dsh:` diagnostics.
+- `--session-id <id>` selects the exact session identity: adopt the persisted session with that id, and fail when no such log exists. Without the flag the run mints `session-<uuid>` as before.
+- The task text also arrives on stdin when no positional task is present, or when the positional is `-`.
+
+A per-run `--model` override is deliberately out of scope; the composition default stays authoritative.
+
+The product change is confined to `packages/bundle/headless`: `src/startup.ts`, `src/index.ts`, the new `src/json-stream.ts`, the package manifest and `tsconfig.json`, and its tests. Around it, the product-profile expectation test in `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` covers both output modes end to end and adds one optional caller-owned `cwd` to the `packages/test-support/loader-smoke` harness so two wakes can share a world. `scripts/check-workspace-constraints.ts` and the package manifest publish the shared `lib/json-stream-*.js` chunk both entries import, and `pnpm-lock.yaml` records the new `@deepseek-ai/dsh-session-query` workspace link. No core session, persistence, session-controller, base composition, or launcher file changes.
+
+### Command-line contract
+
+```text
+dsh --profile headless [--json] [--session-id <id>] [<task>... | -]
+```
+
+Task resolution order: joined positionals, then `-`, then piped stdin. A whitespace-only positional is a usage error on its own, even when stdin is not a terminal, so an accidental blank argument never consumes a pipe; a task that is absent entirely is a usage error only when stdin is a terminal. A lone `-` is the only stdin marker: mixing it with other task words is a usage error instead of a task that starts with a dash. A piped task is sent verbatim, trailing newline included. In `--json` mode every usage error — including commander's own grammar rejections such as an unknown option or a missing option value — writes the `error` event before the process exits, because the runner never mounts to write it; the event message omits commander's `error: ` prefix so one event type carries one message shape.
+
+`--json` changes the stdout payload and the destination of the reasoning projection only. Exit status, shutdown ordering, session flush, and the durable session log are unchanged, so a supervisor classifies a run exactly as it does today.
+
+### Event stream
+
+`--json` writes one JSON object per line to stdout and nothing else. The vocabulary is a projection of the session event log, not the log itself.
+
+| `type` | Fields | Emitted |
+|---|---|---|
+| `session` | `sessionId`, `cwd` | first line, before any model output |
+| `status` | `phase` (`turn_start`, `step_start`, `step_end`, `turn_end`), `turn`, `step`, `usage`, `reason` | one per boundary |
+| `text` | `text` | one committed assistant text block |
+| `thinking` | `text` | one committed reasoning block |
+| `tool_call` | `callId`, `tool`, `input` | once per call |
+| `tool_result` | `callId`, `status`, `result` | once per appended result |
+| `error` | `message` | a failure the runner raises outside a turn |
+| `final` | `text` | last line |
+
+Projection rules:
+
+- Text and reasoning are projected only from a committed `assistant/message`, never from live attempt deltas. A retried or discarded attempt appends `assistant/attempt`, which the projection ignores, so the stream never carries content the durable log does not contain ([publish state only at its commit point](../../../../packages/AGENTS.md)).
+- Each committed content block becomes exactly one `text` or `thinking` event in content order; `tool-call` blocks are not projected because the `tool/call` event owns them. `user/message` echoes and internal session events (title, model selection, projection, checkpoint, goal, subagent) are not projected.
+- A `tool/result` is projected only when its `surfaceOp` is `append`. A compaction replacement of an older result is history, and projecting it would emit a call id with no matching `tool_call`.
+- Every projected string and object key is bounded at 8 KiB, an event with a cut value carries `truncated: true`, and one serialized event line, newline included, is bounded at 32 KiB — an over-long event keeps its scalar fields, drops structured ones, and at the extreme reduces to `type` and `truncated`, while a payload nested 64 levels or deeper is cut at that depth so no legal input can overflow the bounding recursion. This includes the process-level `error` event; a literal `__proto__` argument key is copied as data rather than through the inherited setter, an empty tool-argument string projects as `{}` to match the executor, and arguments that JSON cannot round-trip (an overflowing number such as `1e400`) keep their raw text rather than the `null` that `JSON.stringify` would report. The terminal `final` event is deliberately unbounded: it carries the same lossless answer the default mode prints.
+- Text and reasoning arrive when the step commits, not per token; default-mode stderr reasoning remains the only live text channel. A turn that fails in-turn still ends the stream with `final` and no `error` event, so a supervisor classifies that run from the exit code and the `turn_end` reason even when the stream is well formed.
+- `usage` appears on `step_end` only when every attempt in the step reported a sample, summed across them — including a retried attempt whose only usage sample sits in its discarded `assistant/attempt` stream — so a partial total is never presented as exact.
+- Raw session events stay out of scope. A debug escape hatch can be added later without changing this vocabulary.
+
+### Session identity
+
+The runtime owns identity. A run without `--session-id` mints `session-<uuid>` and reports it in the first event. A supervisor persists that value and passes it back on the next wake.
+
+`--session-id <id>` is adopt-only: observe the persisted session and resume it, failing when the log does not exist. The first wake omits the flag, so the runtime mints the identity and reports it in the `session` event; every later wake names that value and continues the history. A requested id with no stored log is an error rather than a fresh conversation, so a mistyped or stale id cannot silently open an empty history the caller believes it is resuming, and the JSONL store's refusal of an existing log id ([session persistence](../../implemented/architecture/2026-06-14-session-persistence.md)) is never on this path. The id is opaque, so the runner validates non-emptiness on the trimmed value and passes the caller's exact string through, whitespace included.
+
+Adoption compares the persisted session's recorded cwd with the process cwd, since sessions are organized per project directory ([project session directories](../../implemented/architecture/2026-07-24-project-session-directories.md)). A mismatch exits 1 with a `dsh:` diagnostic instead of silently continuing a conversation rooted elsewhere, and a session that recorded no cwd is rejected for the same reason. A session running under an agent preset is rejected because this bundle composes no preset roster: resuming it here would run it under the headless tools and prompts instead of the composition its log records. The check reads the preset the log currently records — the creation header advanced by any `agent-preset/selected` event — because a blank session may switch preset after creation while the header stays a creation fact, and a malformed selection record fails closed rather than reading as no preset. A session linked to a parent or subagent — including a user fork — is rejected. A live Agent already holding the requested id is refused outright: its owner may still drive it, and `whenIdle` is not a single-message signal, so the runner cannot own an exclusive interval over it. The resumed log is checked again after the runner's idle wait, so a preset selected in that window is still rejected. A whitespace-only `sessionId` is rejected on both the CLI and the direct-config path. Two live processes cannot write one id; the store's write lease already rejects the second writer. The runner reads the observation through the composed `sessionQuery` service and fails loudly when `--session-id` is requested without it — the observation is how it finds the id it must resume — or when the requested identity would lack the `sessionPersistence` service that makes it durable.
+
+## Consequences
+
+What landed: `src/startup.ts` parses `--json` and `--session-id <id>`, treats an absent or `-` task as "read stdin", and raises the usage error only when stdin is a terminal. `src/index.ts` resolves the task, adopts the named session — or mints a fresh identity when the flag is absent — and wires either the stderr reasoning projection or the new `src/json-stream.ts` projection. `cordis.patch.yml` forwards the two new settings. `package.json` publishes the shared `lib/json-stream-*.js` chunk both entries import, so the installed tarball loads.
+
+- Default mode is unchanged: a text-only run writes one final assistant line to stdout and nothing to stderr, and exit status still follows the terminal reason.
+- `--json` stdout parses line by line as JSON, starts with `session`, ends with `final`, and contains no plain text. Stderr carries no reasoning in this mode.
+- A step that retries publishes `text` and `thinking` only for the attempt that commits, so a discarded attempt leaves no trace in the stream.
+- Two consecutive runs with the same `--session-id` share history, and a requested id with no stored log exits 1 before the task runs. A run whose cwd differs from the persisted session, that recorded no cwd, that is a subagent or forked session, that runs under an agent preset, that carries a malformed preset record, or whose identity is already live in the process, exits 1 with a diagnostic.
+- A piped task with no positional task is honored, a whitespace-only positional is rejected instead of consuming the pipe, and an interactive invocation without a task still fails with the usage error.
+- Unit coverage lands in `packages/bundle/headless/tests/startup.spec.ts`, `tests/headless.spec.ts`, and `tests/json-stream.spec.ts`. The product headless profile expectation test in `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` covers both output modes end to end.
+
+Deferred and open:
+
+- The per-run `--model` override is unimplemented. A later change must respect the session-local selection precedence owned by the Session Controller rather than overriding a stored selection.
+- Cold start plus log replay grows with session length, so a long-lived conversation pays more per wake than a fresh one.
+- `--json` moves reasoning from stderr to stdout, so a log collector that watches stderr sees nothing on a reasoned run in that mode.
+- Bounded `tool_result` payloads hide full output from the supervisor; the 8 KiB string/key cap and the 32 KiB line cap are owned by `src/json-stream.ts` and should stay constants, with the terminal `final` event the only exemption.
+
+## Alternatives considered
+
+**`--verbose` human text on stderr.** A supervisor parses stdout, so a stderr-only projection is invisible to it. Default-mode stderr reasoning already is the human verbose surface.
+
+**Dump raw session events.** They repeat the assembled message beside its deltas, echo `user/message`, and include internal events. Measured on one prompt, pi's delta stream produced 84 lines and 11.7 KB against opencode's 3 lines and 962 B, with roughly a quarter of pi's bytes spent repeating one message across `message_end`, `turn_end`, and `agent_end`.
+
+**A long-lived SDK process instead of one process per wake.** The SDK already speaks structured events and an explicit session identity, but it replaces the one-process-per-wake model the supervisor is built on. Measured cold start for the headless profile is about 0.45 s warm and 1.2 s cold, small against a real turn.
+
+**Let the supervisor mint the session id.** Identity belongs to the runtime that owns the log. The supervisor records what the first event reports.
+
+**Adopt-or-create `--session-id`.** Creating the requested id when no log exists would let a mistyped or stale id silently open an empty history that the supervisor believes it is continuing; the first wake already omits the flag and reads the minted id from the `session` event, so no caller needs `--session-id` to create.
+
+**Task from argv only.** Long prompts exceed `ARG_MAX` and expose the prompt in the process list.

+ 100 - 0
.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md

@@ -0,0 +1,100 @@
+# Agent Note: Headless 的机器可读运行接口
+
+Status: implemented
+
+[English](2026-09-09-headless-machine-readable-run-surface.md) | 中文
+
+## 问题
+
+`dsh --profile headless` 面向的是人类终端:任务只能通过 argv 传入,stdout 只输出最终一条助手消息,provider 的推理过程流式写到 stderr,而且每次运行都新建一个随机会话。[Headless is a direct core entry point](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md) 拥有那套传输与完成契约;[headless reasoning progress](../../archived/feature/2026-08-21-headless-reasoning-progress.md) 拥有 stderr 投影。
+
+一个"每次唤醒起一个 headless 进程"的监督进程(例如外部 agent 运行时)需要三样该契约没有提供的东西。它需要通过私有管道而不是 argv 传入任务,因为长提示词会超出参数上限,而 argv 对其他进程可见。它需要一条机器可读的流,把助手文本、推理、工具调用与结果、轮次边界和用量区分开,因为抓取 stderr 只能拿到推理,而 stdout 最后一行拿不到任何工具活动。它需要一个精确的会话身份,以便在下一次唤醒时传回去,因为每次进程都新建随机会话意味着无法连续。
+
+## 决策
+
+`dsh-headless` bundle 拥有一个可选的机器可读运行接口。默认调用保持原有契约不变:stdout 输出一条最终助手消息,推理走 stderr,当且仅当终端 `turn/end` 原因为 `completed` 时退出码为 0。
+
+三项新增扩展 [Apps own their command lines](../../archived/architecture/2026-08-06-app-owned-command-line.md) 确立的 app 自有命令行:
+
+- `--json` 把 stdout 负载换成逐行 JSON 运行事件。推理变成一条事件而不再写 stderr,因此该模式下 stderr 只承载 `dsh:` 诊断。
+- `--session-id <id>` 选定精确的会话身份:采用具有该 id 的持久化会话,不存在时就失败。不带该 flag 时,运行仍像以前一样生成 `session-<uuid>`。
+- 没有位置参数、或者位置参数为 `-` 时,任务文本改从 stdin 读取。
+
+每次运行的 `--model` 覆盖被明确排除在范围之外;组合默认模型仍然权威。
+
+产品改动限于 `packages/bundle/headless`:`src/startup.ts`、`src/index.ts`、新增的 `src/json-stream.ts`、包清单与 `tsconfig.json`,以及测试。围绕它,产品 profile 的期望测试位于 `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts`,端到端覆盖两种输出模式,并给 `packages/test-support/loader-smoke` harness 增加了一个可选的调用方自有 `cwd`,让两次唤醒共享同一个世界;`scripts/check-workspace-constraints.ts` 与包清单负责发布两个入口共同引用的共享 chunk `lib/json-stream-*.js`,`pnpm-lock.yaml` 则记录新增的 `@deepseek-ai/dsh-session-query` workspace 链接。不修改任何 core session、持久化、session-controller、base 组合或 launcher 文件。
+
+### 命令行契约
+
+```text
+dsh --profile headless [--json] [--session-id <id>] [<task>... | -]
+```
+
+任务解析顺序:拼接后的位置参数,其次是 `-`,其次是管道 stdin。只有空白的位置参数本身就属于用法错误,即使 stdin 不是终端也一样,因此误传的空白参数绝不会消费管道内容;完全缺失任务时,仅在 stdin 是终端时属于用法错误。单独的 `-` 是唯一的 stdin 标记:把它与其他任务词混用属于用法错误,而不是以连字符开头的任务。管道任务会原样发送,包括结尾换行。在 `--json` 模式下,所有用法错误——包括 commander 自身的语法拒绝,例如未知选项或选项缺少取值——都会在进程退出前写出 `error` 事件,因为 runner 从未挂载来写它;事件 message 省略 commander 的 `error: ` 前缀,使同一事件类型只承载一种消息形态。
+
+`--json` 只改变 stdout 负载和推理投影的去向。退出码、关闭顺序、会话 flush 和持久化会话日志都不变,因此监督进程对一次运行的分类方式与现在完全一致。
+
+### 事件流
+
+`--json` 向 stdout 每行写一个 JSON 对象,不写其他内容。词汇表是会话事件日志的投影,而不是日志本身。
+
+| `type` | 字段 | 发出时机 |
+|---|---|---|
+| `session` | `sessionId`、`cwd` | 第一行,先于任何模型输出 |
+| `status` | `phase`(`turn_start`、`step_start`、`step_end`、`turn_end`)、`turn`、`step`、`usage`、`reason` | 每个边界一条 |
+| `text` | `text` | 一个已提交的助手文本块 |
+| `thinking` | `text` | 一个已提交的推理块 |
+| `tool_call` | `callId`、`tool`、`input` | 每次调用一条 |
+| `tool_result` | `callId`、`status`、`result` | 每次追加的结果一条 |
+| `error` | `message` | runner 在轮次之外抛出的失败 |
+| `final` | `text` | 最后一行 |
+
+投影规则:
+
+- 文本与推理只从已提交的 `assistant/message` 投影,绝不来自实时的 attempt 增量。被重试或丢弃的 attempt 会追加 `assistant/attempt`,投影直接忽略,因此事件流永远不会承载持久化日志中不存在的内容([只在提交点发布状态](../../../../packages/AGENTS.md))。
+- 每个已提交的内容块按内容顺序变成恰好一条 `text` 或 `thinking` 事件;`tool-call` 块不投影,因为 `tool/call` 事件已经拥有它。`user/message` 回显和内部会话事件(标题、模型选择、投影、检查点、目标、子 agent)都不投影。
+- `tool/result` 仅在其 `surfaceOp` 为 `append` 时投影。压缩对旧结果的替换属于历史,投影它会产生没有对应 `tool_call` 的 call id。
+- 每个被投影的字符串与对象键都限制在 8 KiB;被截断的事件带 `truncated: true`,单条序列化事件行(含换行)限制在 32 KiB——超长事件保留标量字段、丢弃结构化字段,极端情况下只剩 `type` 与 `truncated`,嵌套达到 64 层及以上的负载会在该深度被截断,因此任何合法输入都不会让限界递归溢出。进程级 `error` 事件同样受限;字面量 `__proto__` 参数键会作为数据复制,而不经过继承的 setter;空工具参数字符串会投影为 `{}`,与执行器保持一致;JSON 无法往返的参数(例如溢出为 `Infinity` 的 `1e400`)保留原始文本,而不是 `JSON.stringify` 会报告的 `null`。终止 `final` 事件刻意不做限长:它承载与默认模式相同的无损答案。
+- 文本与推理在步骤提交时到达,而不是逐 token 到达;默认模式的 stderr 推理仍是唯一的实时文本通道。轮次内失败的运行仍以 `final` 结束且没有 `error` 事件,因此即使事件流格式良好,监督进程也要用退出码与 `turn_end` 原因来分类该次运行。
+- `usage` 仅在该步每一次 attempt 都上报了样本时出现在 `step_end` 上,并累计这些样本——包括仅在被丢弃的 `assistant/attempt` 流中留下用量样本的重试——因此部分汇总不会被当作精确总量发布。
+- 原始会话事件不在范围内。调试用的逃生口可以以后再加,不必改动这套词汇表。
+
+### 会话身份
+
+身份由运行时拥有。不带 `--session-id` 的运行生成 `session-<uuid>`,并在第一条事件里报告它。监督进程保存该值,并在下一次唤醒时传回。
+
+`--session-id <id>` 是只采用:先观察持久化会话并 resume,日志不存在时失败。首轮不传该 flag,由运行时生成身份并在 `session` 事件里报告;后续每一轮都用该值指名并续接历史。请求的 id 没有持久化日志时报错,而不是开一段新会话,因此写错或过期的 id 不会静默开出一段调用方自以为在续接的空历史,JSONL 存储拒绝已存在日志 id 的问题(见 [session persistence](../../implemented/architecture/2026-06-14-session-persistence.zh.md))也不会出现在这条路径上。标识是不透明的,因此 runner 只在 trim 后的值上校验非空,并把调用方的原始字符串(含空白字符)原样传下去。
+
+采用时会比较持久化会话记录的 cwd 与进程 cwd,因为会话按项目目录组织(见 [project session directories](../../implemented/architecture/2026-07-24-project-session-directories.zh.md))。不一致时以 `dsh:` 诊断退出 1,而不是静默续接一个根目录在别处的会话;未记录 cwd 的会话出于同样理由被拒绝。运行在 agent preset 下的会话被拒绝,因为本 bundle 不组合任何 preset roster:在这里 resume 它,会用 headless 的工具与提示词运行它,而不是它日志当前记录的组合。该检查读取日志当前记录的 preset——创建 header 再叠加任何 `agent-preset/selected` 事件——因为空白会话可能在创建后切换 preset,而 header 始终只是创建事实;畸形的选择记录会失败关闭,而不会读成「无 preset」。带父会话或子 agent 关联的会话——包括用户 fork 出的会话——被拒绝。本进程已存在持有请求 id 的存活 Agent 时直接拒绝:它的 owner 可能仍在驱动它,而 `whenIdle` 不是单条消息的完成信号,runner 无法对它取得独占区间。resume 后的日志会在 runner 等待 idle 后再次检查,因此该窗口内选中的 preset 仍会被拒绝。纯空白的 `sessionId` 在 CLI 与直接配置两条路径上都会被拒绝。两个存活进程不能写同一个 id;存储的写租约已经会拒绝第二个写入者。runner 通过已组合的 `sessionQuery` 服务读取观察结果,并在请求 `--session-id` 却没有该服务时显式失败——观察结果正是它找到待 resume id 的途径;若所请求的身份缺少让它持久化的 `sessionPersistence` 服务,同样显式失败。
+
+## 后果
+
+实际落地:`src/startup.ts` 解析 `--json` 与 `--session-id <id>`,把缺失或为 `-` 的任务视为"从 stdin 读取",并且只在 stdin 是终端时抛出用法错误。`src/index.ts` 解析任务、采用指名的会话——未传 flag 时生成新身份——并接上 stderr 推理投影或新的 `src/json-stream.ts` 投影。`cordis.patch.yml` 转发这两个新设置。`package.json` 发布两个入口共同引用的共享 chunk `lib/json-stream-*.js`,因此安装后的 tarball 可以加载。
+
+- 默认模式不变:纯文本运行向 stdout 写一行最终助手消息、stderr 无输出,退出码仍跟随终端原因。
+- `--json` 的 stdout 逐行可解析为 JSON,以 `session` 开头、以 `final` 结尾,不含纯文本。该模式下 stderr 不承载推理。
+- 发生重试的步骤只为最终提交的 attempt 发布 `text` 与 `thinking`,因此被丢弃的 attempt 不会在事件流中留下任何痕迹。
+- 两次连续的相同 `--session-id` 运行共享历史,而请求一个没有持久化日志的 id 会在任务运行前退出 1。cwd 不一致、未记录 cwd、属于子 agent 或 fork 会话、运行在 agent preset 下、preset 记录畸形,或本进程已存在同 id 存活 Agent 的运行都以诊断退出 1。
+- 无位置参数但 stdin 有管道输入时任务被采纳,只有空白的位置参数会被拒绝而不会消费管道,交互式无任务调用仍以用法错误失败。
+- 单元覆盖落在 `packages/bundle/headless/tests/startup.spec.ts`、`tests/headless.spec.ts` 与 `tests/json-stream.spec.ts`。`apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` 的产品 headless profile 期望测试端到端覆盖两种输出模式。
+
+延期与未决:
+
+- 每次运行的 `--model` 覆盖尚未实现。后续改动必须尊重 Session Controller 拥有的会话局部选择优先级,而不是覆盖已保存的选择。
+- 冷启动加上日志重放会随会话变长而增长,因此长会话每次唤醒的代价高于新会话。
+- `--json` 把推理从 stderr 移到 stdout,因此只监听 stderr 的日志收集器在该模式的有推理运行上什么都看不到。
+- 有界的 `tool_result` 负载会让监督进程看不到完整输出;8 KiB 字符串/键上限与 32 KiB 行上限由 `src/json-stream.ts` 拥有,应保持为常量,终止 `final` 事件是唯一的例外。
+
+## 备选方案
+
+**`--verbose` 人类可读文本写到 stderr。** 监督进程解析的是 stdout,只落在 stderr 的投影对它不可见。默认模式的 stderr 推理已经是人类可读的 verbose 面。
+
+**直接倾倒原始会话事件。** 它们在增量之外重复整条已组装消息,回显 `user/message`,还夹带内部事件。在同一条提示词上实测,pi 的增量流产生 84 行、11.7 KB,而 opencode 是 3 行、962 B;pi 大约四分之一的字节花在把同一条消息在 `message_end`、`turn_end`、`agent_end` 里重复三遍。
+
+**用长驻 SDK 进程代替每次唤醒一个进程。** SDK 已经有结构化事件和显式的会话身份语义,但它会替换掉监督进程所依赖的"一次唤醒一个进程"模型。实测 headless profile 的冷启动约为热态 0.45 s、冷态 1.2 s,相对一个真实轮次很小。
+
+**让监督进程生成会话 id。** 身份属于拥有日志的运行时。监督进程记录第一条事件报告的值即可。
+
+**采用或创建语义的 `--session-id`。** 在日志不存在时创建所请求的 id,会让写错或过期的 id 静默开出一段监督进程自以为在续接的空历史;首轮本就不传该 flag 并从 `session` 事件读到生成的 id,因此没有任何调用方需要用 `--session-id` 来创建。
+
+**任务只走 argv。** 长提示词会超出 `ARG_MAX`,并且把提示词暴露在进程列表里。

+ 2 - 2
.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.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/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md
-2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 887a3666501a97e0930fe7eb4282603f537c17e8
-2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 0202be5c22c75ff4feec225cdff12b772d64d1cf
+2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 91f7913ff0cabafdd1c089e618d7864b9a085147
+2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 315850711adb234fa2bd9a92d9bd69fea5077230

+ 2 - 0
.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md

@@ -24,6 +24,8 @@ Before using `contract`, `boundary`, or `shape`, writers check whether the sente
 
 This decision complements the [documentation tiers and budgets](2026-07-04-doc-tiers-and-budgets.md) decision, which continues to own placement, document form, and word budgets.
 
+The [blocked ambiguous origin label](2026-08-26-ban-ambiguous-origin-label.md) decision partially supersedes the sentence-level policy for one term after that term spread across unrelated contracts.
+
 ## Alternatives considered
 
 **Ban a fixed list of words.** Rejected because a word may be an exact identifier or the clearest term in another contract. For example, caller/callee invariants are real contracts, and process or wire boundaries identify real divisions. Sentence-level review catches ambiguity without rejecting valid names.

+ 2 - 0
.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md

@@ -24,6 +24,8 @@ Status: implemented
 
 该决策补充了[文档层级与字数预算](2026-07-04-doc-tiers-and-budgets.zh.md)决策;后者继续规定内容位置、文档形式和字数预算。
 
+[禁止有歧义来源名称](2026-08-26-ban-ambiguous-origin-label.zh.md)决策对其中一个术语部分取代了逐句判断规则,因为该术语已经扩散到互不相关的约定中。
+
 ## 曾考虑的替代方案
 
 **禁止一份固定词表中的所有词。** 不予采纳:某个词可能是确切的标识符,也可能是另一项约定中最清楚的用词。例如,调用方与被调用方依赖的不变量属于真实的约定,进程边界或协议边界也表示真实分界。逐句审查可以找出歧义,且不会拒绝有效名称。

+ 2 - 2
.agents/notes/implemented/process/2026-08-10-npm-release-sequences.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/implemented/process/2026-08-10-npm-release-sequences.md
-2026-08-10-npm-release-sequences.md: 19e8d8db8550816f542111a9b831a3694a080de5
-2026-08-10-npm-release-sequences.zh.md: d057d77adbf35c20fc260203d3ef0db92e30f6b7
+2026-08-10-npm-release-sequences.md: 3a52c01494921cacd5df3053fb515461831d6007
+2026-08-10-npm-release-sequences.zh.md: f973b442f2bbf6400ef0b75650392744a489ace3

+ 1 - 1
.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md

@@ -178,7 +178,7 @@ What this costs:
 - **The change judgement depends on visible tags.** A shallow clone, or a checkout without tags, degrades the vendored judgement to "publish everything for the first time". `fetch-depth: 0` is a precondition, not an optimization.
 - **The protocol rewrite touched 1504 dependency declarations.** It does not change local resolution — pnpm already resolves from the workspace — but it changes the ranges that go out.
 - **Private packages need credentials to install.** Every consumer — CI, sandbox e2e, outside users — needs scope credentials, including for the Landlock packages, which have never been published and so cut off no existing anonymous path.
-- **`repository` names a different organization than the one running the workflows.** Token-based publication is unaffected; npm provenance (OIDC) requires the two to agree, so adopting it means either repointing `repository` or publishing from the organization it names.
+- **`repository` names a different organization than the one running the workflows.** Token-based publication is unaffected; npm's OIDC attestation requires the two to agree, so adopting it means either repointing `repository` or publishing from the organization it names.
 - **Byte reproducibility is assumed, not measured.** The skip-on-identical-integrity state rests on packing the same commit twice producing the same bytes. Nothing measures that yet: if the build embeds absolute paths or timestamps, a re-run reports a false failure. Measure it before the first publication a re-run might follow, and fall back to comparing per-file content hashes if it does not hold.
 - **Re-running publish over an older artifact can move `latest` backwards.** Publication is decided per version, so an older set republished after a newer one takes the stable dist-tag again. The rehearsals run from a prerelease version, which never takes `latest`.
 - **The first publication is one large step.** Nine vendored packages and the whole dsh set publish at once, so any payload defect surfaces in a single release, which is why a prerelease version drives the complete path first.

+ 1 - 1
.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md

@@ -178,7 +178,7 @@ dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 ven
 - **变更判据依赖 tag 可见。** shallow clone 或未拉 tag 会把 vendored 族的判据退化成「全部首发」。`fetch-depth: 0` 是前提,不是优化。
 - **协议改写触及 1504 处依赖声明。** 它不改变本机解析(pnpm 本来就从 workspace 解析),但改变了发布出去的范围写法。
 - **私有包需要凭据才能安装。** 任何消费方——CI、沙箱 e2e、外部使用者——都要持有 scope 凭据,Landlock 三包也在其中;它们从未发布过,所以没有切断既有的匿名安装路径。
-- **`repository` 指向的组织与运行 workflow 的组织不同。** 用 token 发布不受影响;npm provenance(OIDC)要求二者一致,届时要么把 `repository` 改指过去,要么从它指向的组织发布。
+- **`repository` 指向的组织与运行 workflow 的组织不同。** 用 token 发布不受影响;npm 的 OIDC attestation 要求二者一致,届时要么把 `repository` 改指过去,要么从它指向的组织发布。
 - **字节可复现性是假定的,没有实测。** 「integrity 相同则跳过」这一态建立在「同一 commit 两次 pack 得到相同字节」之上。目前没有任何东西测量过它:若构建嵌入了绝对路径或时间,重跑会误报失败。在第一次可能被重跑的发布之前实测,若不成立就退到比对 tarball 内逐文件内容哈希。
 - **用较旧的 artifact 重跑 publish 会把 `latest` 拉回旧版。** 发布是按版本决定的,所以在较新版本之后重发较旧的一批,会让稳定 dist-tag 再次指向旧版。排练用的是预发布版本,它永远不占 `latest`。
 - **首发是一次大步。** 九个 vendored 包与整个 dsh 集一次发出,任何 payload 缺陷都会集中在同一次发布里暴露——这正是先用预发布版本把完整链路走一遍的理由。

+ 6 - 0
.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.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/process/2026-08-26-ban-ambiguous-origin-label.md
+2026-08-26-ban-ambiguous-origin-label.md: fe06cf5d2dd43a09a2699a4dc48ff5791cffa65e
+2026-08-26-ban-ambiguous-origin-label.zh.md: 1389d13992f5566d5b6091740e20433b06dd6e31

+ 37 - 0
.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md

@@ -0,0 +1,37 @@
+# Agent Note: Ban an ambiguous origin label
+
+Status: implemented
+
+English | [中文](2026-08-26-ban-ambiguous-origin-label.zh.md)
+
+## Problem
+
+The case-insensitive ten-letter ASCII token formed by `prove` followed by `nance` had accumulated unrelated meanings across the repository. It named source-event references, provider and model metadata, context producers, installed artifact identity, configuration origins, browser-recording evidence, and release attestations. A reader could not determine the recorded fact from the label alone.
+
+The existing [concrete prose decision](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md) required sentence-level classification and normally preserved identifiers. That policy improved individual sentences but allowed the same ambiguous label to remain in APIs, durable compatibility fixtures, filenames, generated catalogs, and new prose.
+
+## Decision
+
+Maintained tracked paths and text must not contain that token in any case variant. Each use is replaced with the exact local concept, such as source-event references, provider metadata, model identity, context producer, configuration source, artifact identity, or recorded evidence.
+
+The rule applies to source, tests, documentation, active Agent Notes, prompts, snapshots, workflow files, scripts, identifiers, fields, exported types, and filenames. Because the repository is pre-release, coordinated public symbol and current durable-field renames are preferred over aliases. A compatibility reader may reconstruct the retired durable key at runtime without embedding the token in maintained text.
+
+`verify-concrete-terms` scans tracked filenames, symlink targets, and text case-insensitively after NFKC normalization. It runs as a quick leaf of `doc-sync`, and its tests prove rejection in prose, identifiers, and paths. Vendored sources and frozen archived Agent Notes remain excluded because their repository policies prohibit direct edits.
+
+This decision partially supersedes the earlier decision's rejection of fixed word bans and identifier renames for this one token. The earlier decision remains active for all other abstract language and for choosing each replacement according to its local meaning.
+
+## Alternatives considered
+
+**Continue semantic review without a mechanical ban.** Rejected because the label had already spread through unrelated contracts, and sentence-level review could not prevent recurrence in identifiers, fixtures, or filenames.
+
+**Replace every occurrence with one umbrella term such as `origin` or `source`.** Rejected because one new broad label would preserve the ambiguity between event references, model metadata, build identity, and recorded evidence.
+
+**Exempt identifiers, durable fields, and generated files.** Rejected because callers and generated reference material would continue teaching the retired label. The pre-release policy permits coordinated renames, while the legacy parser can retain read compatibility without retaining the literal token.
+
+**Scan vendored and archived sources.** Rejected because vendored content is updated through its synchronization procedure and archived Agent Notes are frozen records. Their explicit exclusions keep this policy consistent with those ownership rules.
+
+## Consequences
+
+Callers use more specific public names, and current recordings use provider metadata instead of the retired assistant field. Generated catalogs and recorded snapshots must be refreshed when an owning symbol changes. Compatibility code remains responsible for reading the retired durable field.
+
+Future uses fail `doc-sync` with the matching path or line. Reviews still classify the intended meaning before choosing a replacement; passing the gate proves absence of the token, not that the replacement is precise.

+ 37 - 0
.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md

@@ -0,0 +1,37 @@
+# Agent Note: 禁止有歧义的来源名称
+
+Status: implemented
+
+[English](2026-08-26-ban-ambiguous-origin-label.md) | 中文
+
+## 问题
+
+由 `prove` 与 `nance` 拼成的十个 ASCII 字母术语不区分大小写时,在仓库中已经承载了互不相关的含义。它曾表示来源事件引用、提供方与模型元数据、上下文生产方、已安装产物身份、配置来源、浏览器录制证据和发布证明。读者无法仅凭该名称判断实际记录的事实。
+
+现有的[具体表述决策](2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md)要求逐句分类,且通常保留标识符。该规则改善了各个句子,却仍允许同一个有歧义名称留在 API、持久化兼容 fixture、文件名、生成目录和新行文中。
+
+## 决策
+
+仓库维护的已跟踪路径和文本不得包含该术语的任何大小写变体。每处使用都改为当前语境中的确切概念,例如来源事件引用、提供方元数据、模型身份、上下文生产方、配置来源、产物身份或已记录证据。
+
+该规则适用于源码、测试、文档、活跃 Agent Note、提示词、快照、工作流文件、脚本、标识符、字段、导出类型和文件名。由于仓库尚未正式发布,公开符号与当前持久字段应协调重命名,而不是保留别名。兼容读取器可以在运行时构造已退役的持久键,无需在维护文本中嵌入该术语。
+
+`verify-concrete-terms` 会在 NFKC 规范化后,不区分大小写地扫描已跟踪文件名、符号链接目标和文本。它作为 `doc-sync` 的快速叶子门禁运行;对应测试证明行文、标识符和路径中的违规都会被拒绝。vendored 源码与冻结的 archived Agent Note 仍被排除,因为各自的仓库规则禁止直接编辑这些内容。
+
+本决策针对这一个术语,部分取代了旧决策中不采用固定禁词和标识符重命名的选择。旧决策仍适用于其他抽象语言,也仍负责要求根据每处使用的具体含义选择替代名称。
+
+## 曾考虑的替代方案
+
+**继续只做语义审查,不设置机械禁令。** 不予采纳:该名称已经扩散到互不相关的约定中,逐句审查无法防止它在标识符、fixture 或文件名中再次出现。
+
+**用 `origin` 或 `source` 等单一总括术语替换所有位置。** 不予采纳:新的宽泛名称仍会混淆事件引用、模型元数据、构建身份和已记录证据。
+
+**豁免标识符、持久字段和生成文件。** 不予采纳:调用方和生成的参考资料会继续传播已退役名称。预发布政策允许协调重命名,而旧格式解析器可以在不保留字面术语的情况下继续提供读取兼容性。
+
+**扫描 vendored 与 archived 内容。** 不予采纳:vendored 内容通过同步流程更新,而 archived Agent Note 是冻结记录。明确排除两者,可以使本政策与对应的归属规则保持一致。
+
+## 后果
+
+调用方使用更具体的公开名称;当前录制使用提供方元数据,而不再使用已退役的 assistant 字段。归属符号变化后,生成目录和录制快照必须刷新。兼容代码仍负责读取已退役的持久字段。
+
+以后新增的匹配项会使 `doc-sync` 失败,并报告对应路径或行号。审查仍需先判断预期含义再选择替代名称;通过门禁只能证明该术语不存在,不能证明替代名称足够精确。

+ 1 - 1
.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md
-2026-09-06-master-only-platform-ci.md: d32864a4eafa81433c6d4fd0e47892b4d17091b1
+2026-09-06-master-only-platform-ci.md: fcdc413d644c1129635a7f33fe80bdec1b4a0b7a
 2026-09-06-master-only-platform-ci.zh.md: 4fb55d5e95f8fe75e6d1efb8b57295b8e8469e8d

+ 1 - 1
.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md

@@ -16,7 +16,7 @@ Wine runs once as an independent hosted Ubuntu master job. Its existing image-ke
 
 The [superseded-CI cancellation policy](2026-09-09-cancel-superseded-ci.md) applies to the parent and reusable runtime workflows: newer master pushes or manual runs cancel older validation in the same workflow/ref group, while release-owned builds remain protected. A master push schedules all three selected carriers but does not guarantee every intermediate commit reaches a result.
 
-This decision partially supersedes scheduling in the [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md), [native Windows CI](2026-08-08-native-windows-pull-request-ci.md), [serial references](2026-07-21-serial-cross-platform-ci-reference.md), and [failover runbook](2026-07-26-ci-failover-runbook.md). Those notes remain active for artifact provenance, platform fidelity, serial completeness, and trust rules.
+This decision partially supersedes scheduling in the [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md), [native Windows CI](2026-08-08-native-windows-pull-request-ci.md), [serial references](2026-07-21-serial-cross-platform-ci-reference.md), and [failover runbook](2026-07-26-ci-failover-runbook.md). Those notes remain active for artifact origin, platform fidelity, serial completeness, and trust rules.
 
 ## Alternatives considered
 

+ 2 - 2
.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.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/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md
-2026-07-28-remove-synthetic-log-only-turns.md: e5138ad22a68d2c7dd6d201df5522536e1d26414
-2026-07-28-remove-synthetic-log-only-turns.zh.md: 2fb15ff837b4984a069a2a5306f167fe72177f6b
+2026-07-28-remove-synthetic-log-only-turns.md: b35045d036dbb3ea18431b76cec2c41674de5607
+2026-07-28-remove-synthetic-log-only-turns.zh.md: 3dbcd0d9b258f4ea9f4515ba009e75487a96c342

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md

@@ -20,7 +20,7 @@ Core session invariants continue to enforce core-owned execution relations: turn
 
 The title service appends `session/title` directly after its existing service, revision, cancellation, and live-session checks. The bundled model helper appends its literal `session/title-llm-request` record before dispatch. Persistence admits both through the bounded `session/event` path and drains them at ordinary checkpoints and lifecycle teardown; neither append forces a flush merely because it is between turns. A fallback, auxiliary request record, or accepted provider title may therefore appear after `turn/end` and before the next `turn/start`. Manual compaction uses the same between-turn capability for a `compaction/* { turn: null }` bracket, but explicitly flushes the closed attempt because `/compact` promises durability before releasing queued prompt admission.
 
-A session fork may end at any stable event position outside an open turn, not only at `turn/end`. This preserves standalone title and other plugin-owned log-only records in a default fork while still rejecting a prefix cut through active execution.
+`SessionStore.fork()` may end at any stable event position outside an open turn, not only at `turn/end`. This preserves standalone title and other plugin-owned log-only records in a default store fork while still rejecting a prefix cut through active execution. The [Session Controller fork decision](../bug-fix/2026-09-11-session-controller-fork-turn-cut.md) limits its completed-turn operation to the selected closing event.
 
 The historical [universal turn-enclosure decision](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md) remains useful only as the reason the synthetic mechanism was introduced. The [context-injection decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) established the current meaning: one turn represents one model-loop execution. The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) applies that rule to a durable multi-event bracket and owns its marker and admission semantics.
 

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md

@@ -20,7 +20,7 @@ Status: implemented
 
 标题服务会在完成既有的服务状态、修订、取消和活跃会话检查后,直接追加 `session/title`。随附模型辅助函数会在发起调用前追加其字面量 `session/title-llm-request` 记录。持久化通过有界 `session/event` 路径接纳两者,并在常规检查点与生命周期结束时排空;二者都不会仅因为位于轮次之间就强制刷写。因此,回退标题、辅助请求记录或已接受的提供方标题可以出现在 `turn/end` 之后、下一个 `turn/start` 之前。手动压缩(compaction)利用同一项轮次间能力记录 `compaction/* { turn: null }` 标记对,但会显式刷写已闭合的尝试,因为 `/compact` 承诺在放行排队中的提示词前完成持久化。
 
-会话 fork 可以结束于开放轮次之外的任意稳定事件位置,而不限于 `turn/end`。这样,默认 fork 会保留独立标题和其他插件所属的纯日志记录,同时仍拒绝在活跃执行过程中截断前缀。
+`SessionStore.fork()` 可以结束于开放轮次之外的任意稳定事件位置,而不限于 `turn/end`。这样,默认的 store fork 会保留独立标题和其他插件所属的纯日志记录,同时仍拒绝在活跃执行过程中截断前缀。[Session Controller 分叉决策](../bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md)将其已结束轮次操作限制在选中的结束事件处。
 
 历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。
 

+ 2 - 2
.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.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/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md
-2026-08-09-explicit-schedule-time-zone.md: fd8a09df4c6f8b0003e6e01f48510ecb28d7e568
-2026-08-09-explicit-schedule-time-zone.zh.md: 3a840edda83edf70e65d6acf6a8874d1d47b09b5
+2026-08-09-explicit-schedule-time-zone.md: 2f23394e65d37f9eb714c2e55b0556cdf0bc967e
+2026-08-09-explicit-schedule-time-zone.zh.md: aafa5d56853339e51b9a4265a32fca1a24e7b1dd

+ 7 - 7
.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md

@@ -6,17 +6,17 @@ English | [中文](2026-08-09-explicit-schedule-time-zone.zh.md)
 
 ## Problem
 
-Implicit local `at` input made a browser fact into shared product state. Capturing a default zone on Session creation required new Session headers, create/resume/fork conflict rules, JSONL metadata, a SQLite migration, client creation plumbing, Host comparisons, and Schedule logic coupled to time-context markers. Travel, concurrent tabs, missing provenance, and old Sessions then needed a confirmation protocol merely to decide whether an omitted field was safe.
+Implicit local `at` input made a browser fact into shared product state. Capturing a default zone on Session creation required new Session headers, create/resume/fork conflict rules, JSONL metadata, a SQLite migration, client creation plumbing, Host comparisons, and Schedule logic coupled to time-context markers. Travel, concurrent tabs, missing browser-zone records, and old Sessions then needed a confirmation protocol merely to decide whether an omitted field was safe.
 
 Most of that complexity sat outside Schedule. The model already interprets natural language before it calls the tool, so a durable Session default duplicated an assumption instead of strengthening the absolute-time boundary.
 
 ## Decision
 
-Browser zone is request-local provenance. The Web client samples `Intl.DateTimeFormat().resolvedOptions().timeZone` for every prompt. The Host accepts an optional `clientTimeZone`, validates and canonicalizes `UTC` or an IANA Area/Location at the RPC boundary, and logs it on that exact `user-rpc` message. Invalid values reject prompt admission. Non-browser clients may omit it.
+The browser zone is request-local source data. The Web client samples `Intl.DateTimeFormat().resolvedOptions().timeZone` for every prompt. The Host accepts an optional `clientTimeZone`, validates and canonicalizes `UTC` or an IANA Area/Location at the RPC boundary, and logs it on that exact `user-rpc` message. Invalid values reject prompt admission. Non-browser clients may omit it.
 
-Time-context derives unique, mixed, or missing browser facts from original user-rpc messages in the open turn. A unique zone formats the clock and tells the model to interpret otherwise-unqualified dates and times in that zone. Mixed or missing provenance tells the model to ask the user. The configured or process zone is only a display fallback and is never presented as user authority.
+Time-context derives unique, mixed, or missing browser facts from original user-rpc messages in the open turn. A unique zone formats the clock and tells the model to interpret otherwise-unqualified dates and times in that zone. Mixed or missing browser-zone records tell the model to ask the user. The configured or process zone is only a display fallback and is never presented as user authority.
 
-Schedule accepts no implicit local zone. `at` is either a strict offset-bearing RFC 3339 string or exact `{ date, time, time_zone }`. The structured form requires its zone even when time-context just showed the model a browser zone. Schedule does not import time-context, inspect user-message provenance, read a Session header, or produce a confirmation error. Its parser validates the explicit value, rejects daylight-saving gaps, chooses the first instant in overlaps, and stores only canonical UTC `scheduledAt`.
+Schedule accepts no implicit local zone. `at` is either a strict offset-bearing RFC 3339 string or exact `{ date, time, time_zone }`. The structured form requires its zone even when time-context just showed the model a browser zone. Schedule does not import time-context, inspect user-message sources, read a Session header, or produce a confirmation error. Its parser validates the explicit value, rejects daylight-saving gaps, chooses the first instant in overlaps, and stores only canonical UTC `scheduledAt`.
 
 No Session time-zone field, create/resume/fork zone conflict, JSONL header field, SQLite column or migration, connection default, or Schedule-specific Host/client presentation remains. The browser assumption crosses into Schedule only through the model's explicit tool arguments.
 
@@ -26,11 +26,11 @@ No Session time-zone field, create/resume/fork zone conflict, JSONL header field
 
 **Use the most recent browser zone as mutable Session state.** This reduces confirmation prompts but lets one tab silently change another tab's interpretation and makes replay depend on update ordering.
 
-**Let Schedule inspect the latest time-context message.** A prose snapshot is model-visible evidence, not a typed package seam. Consuming it would couple Schedule to AgentLoop history and duplicate validation against original provenance.
+**Let Schedule inspect the latest time-context message.** A prose snapshot is model-visible evidence, not a typed package seam. Consuming it would couple Schedule to AgentLoop history and duplicate validation against the original user-message sources.
 
 **Let the Host inject `time_zone` into tool calls.** The Host cannot know which natural-language expression the model interpreted or whether the user named another zone. Rewriting model arguments hides meaning at the wrong boundary.
 
-**Require the model to ask on every unqualified time.** This is safe but unnecessarily interrupts the common browser-local case. The request-local instruction provides the intended assumption while mixed or missing provenance still asks.
+**Require the model to ask on every unqualified time.** This is safe but unnecessarily interrupts the common browser-local case. The request-local instruction provides the intended assumption while mixed or missing browser-zone records still ask.
 
 ## Verification
 
@@ -42,6 +42,6 @@ Source audits reject `SessionHeader.timeZone`, persistence `time_zone` columns,
 
 - Browser-local natural language works without a persisted Session-zone subsystem.
 - Schedule has one explicit, independently testable absolute-time boundary.
-- Travel and concurrent tabs affect only their own prompts; a turn with mixed provenance asks instead of mutating shared state.
+- Travel and concurrent tabs affect only their own prompts; a turn with mixed browser-zone records asks instead of mutating shared state.
 - Non-browser clients remain valid but must provide enough natural-language context or explicit tool arguments.
 - The model may still make an interpretation error; the tool guarantees only that the explicit calendar value is valid and deterministic.

+ 7 - 7
.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md

@@ -6,17 +6,17 @@ Status: implemented
 
 ## 问题
 
-隐式本地 `at` 输入把浏览器事实变成了共享产品状态。在 Session 创建时捕获默认时区,需要增加新的 Session header、create/resume/fork 冲突规则、JSONL metadata、SQLite migration、client 创建 plumbing、Host 比较,以及与 time-context 标记耦合的 Schedule 逻辑。随后,旅行、并发 tab、缺失 provenance 和旧 Session 都需要一套确认协议,仅仅为了判断省略字段是否安全。
+隐式本地 `at` 输入把浏览器事实变成了共享产品状态。在 Session 创建时捕获默认时区,需要增加新的 Session header、create/resume/fork 冲突规则、JSONL metadata、SQLite migration、client 创建 plumbing、Host 比较,以及与 time-context 标记耦合的 Schedule 逻辑。随后,旅行、并发 tab、缺失浏览器时区记录和旧 Session 都需要一套确认协议,仅仅为了判断省略字段是否安全。
 
 大部分复杂度都位于 Schedule 之外。模型在调用工具前已经解释自然语言,因此持久 Session 默认值只是重复了一个假设,并没有强化绝对时间边界。
 
 ## 决策
 
-浏览器时区是请求本地的 provenance。Web client 会为每条提示词采样 `Intl.DateTimeFormat().resolvedOptions().timeZone`。Host 接受可选的 `clientTimeZone`,在 RPC 边界校验并规范化 `UTC` 或 IANA Area/Location,再将其记录在确切的那条 `user-rpc` 消息上。无效值会使提示词准入被拒绝。非浏览器 client 可以省略它。
+浏览器时区是请求本地的来源数据。Web client 会为每条提示词采样 `Intl.DateTimeFormat().resolvedOptions().timeZone`。Host 接受可选的 `clientTimeZone`,在 RPC 边界校验并规范化 `UTC` 或 IANA Area/Location,再将其记录在确切的那条 `user-rpc` 消息上。无效值会使提示词准入被拒绝。非浏览器 client 可以省略它。
 
-Time-context 从 open turn 中的原始 user-rpc 消息派生唯一、混合或缺失的浏览器事实。唯一时区会用于格式化时钟,并告诉模型把未明确限定时区的日期和时间解释为该时区。provenance 混合或缺失时,模型会被告知询问用户。配置或进程时区只作为显示 fallback,绝不会被呈现为用户权威。
+Time-context 从 open turn 中的原始 user-rpc 消息派生唯一、混合或缺失的浏览器事实。唯一时区会用于格式化时钟,并告诉模型把未明确限定时区的日期和时间解释为该时区。浏览器时区记录混合或缺失时,模型会被告知询问用户。配置或进程时区只作为显示 fallback,绝不会被呈现为用户权威。
 
-Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格符合 RFC 3339 的字符串,要么是精确的 `{ date, time, time_zone }`。即使 time-context 刚向模型展示了浏览器时区,结构化形式仍要求自己的时区。Schedule 不导入 time-context、不检查 user message provenance、不读取 Session header,也不产生确认错误。它的 parser 会校验显式值、拒绝夏令时缺口、在重叠时选择第一个时点,并且只存储规范化后的 UTC `scheduledAt`。
+Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格符合 RFC 3339 的字符串,要么是精确的 `{ date, time, time_zone }`。即使 time-context 刚向模型展示了浏览器时区,结构化形式仍要求自己的时区。Schedule 不导入 time-context、不检查 user message 来源、不读取 Session header,也不产生确认错误。它的 parser 会校验显式值、拒绝夏令时缺口、在重叠时选择第一个时点,并且只存储规范化后的 UTC `scheduledAt`。
 
 不再保留 Session 时区字段、create/resume/fork 时区冲突、JSONL header 字段、SQLite column 或 migration、连接默认值,也不再保留 Schedule 专属的 Host/client 呈现。浏览器假设只会通过模型的显式工具参数跨入 Schedule。
 
@@ -26,11 +26,11 @@ Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格
 
 **把最近的浏览器时区用作可变 Session 状态。** 这会减少确认提示,却允许一个 tab 悄然改变另一个 tab 的解释,并使回放依赖更新顺序。
 
-**让 Schedule 检查最新的 time-context 消息。** prose snapshot(文本快照)是模型可见证据,而不是有类型的包 seam。消费它会使 Schedule 与 AgentLoop history 耦合,并针对原始 provenance 重复校验。
+**让 Schedule 检查最新的 time-context 消息。** prose snapshot(文本快照)是模型可见证据,而不是有类型的包 seam。消费它会使 Schedule 与 AgentLoop history 耦合,并针对原始 user message 来源重复校验。
 
 **让 Host 向工具调用注入 `time_zone`。** Host 无法知道模型解释的是哪个自然语言表达式,也无法知道用户是否指定了另一个时区。重写模型参数会在错误的边界隐藏含义。
 
-**要求模型对每个未限定时区的时间都询问用户。** 这样做是安全的,却会不必要地打断常见的浏览器本地场景。请求本地指令提供预期假设,而 provenance 混合或缺失时仍会询问用户。
+**要求模型对每个未限定时区的时间都询问用户。** 这样做是安全的,却会不必要地打断常见的浏览器本地场景。请求本地指令提供预期假设,而浏览器时区记录混合或缺失时仍会询问用户。
 
 ## 验证
 
@@ -42,6 +42,6 @@ Host 测试固定别名的规范化、可省略行为和进入 Agent(智能体
 
 - 无需持久 Session 时区子系统,浏览器本地自然语言也能工作。
 - Schedule 具有一个显式且可独立测试的绝对时间边界。
-- 旅行与并发 tab 只影响各自的提示词;provenance 混合的 turn 会询问用户,而不是改变共享状态。
+- 旅行与并发 tab 只影响各自的提示词;浏览器时区记录混合的 turn 会询问用户,而不是改变共享状态。
 - 非浏览器 client 仍然有效,但必须提供足够的自然语言上下文或显式工具参数。
 - 模型仍可能产生解释错误;工具只保证显式日历值有效且具有确定性。

+ 2 - 2
.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.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/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md
-2026-08-28-omit-unneeded-invariant-companions.md: d4ab138d62a67c2c6666ae7998028fedb3a53580
-2026-08-28-omit-unneeded-invariant-companions.zh.md: 609bacd6b631bef3f77d373475107c1c677acb26
+2026-08-28-omit-unneeded-invariant-companions.md: 82d421677cb2f2a425c92e203766527aac7ce860
+2026-08-28-omit-unneeded-invariant-companions.zh.md: d0fda9154675d038c1f304609313654fc4e51f23

+ 2 - 2
.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md

@@ -18,7 +18,7 @@ A package publishes `./invariant` only when it can compare observations that may
 
 Service or method presence, plugin metadata or effects, fixed pure examples, and probes that call the same mutation they claim to verify remain type, load, unit, or integration-test concerns. Parser and config input, model or tool JSON, durable files, worker and process messages, and wire input remain validated at their owning input operation.
 
-The `dsh-time-context` companion remains published. Its check compares the plugin-produced context message with independently owned current-turn user-message provenance and durable event time, so attribution, turn position, and elapsed-time relations can diverge even when the formatter itself is correct.
+The `dsh-time-context` companion remains published. Its check compares the plugin-produced context message with independently owned current-turn user-message source and durable event time, so attribution, turn position, and elapsed-time relations can diverge even when the formatter itself is correct.
 
 ### Omission is explicit in the package README
 
@@ -36,7 +36,7 @@ Existing package behavior tests remain responsible for omitted relationships, in
 
 - **Keep explained empty companions.** Rejected because a source file, public subpath, dependency edges, build output, and tests are disproportionate machinery for saying that no check exists; the package README records that conclusion directly.
 - **Keep the webserver probe as a teardown sentinel.** Rejected because it mutates a reserved route on unrelated lifecycle events and verifies only the service method it invokes. Real routing and HMR tests exercise the behavior without production diagnostic effects.
-- **Treat every producer-format parser as self-validation.** Rejected because a parser can compare independent provenance, timing, or durable history even when one producer owns the text. `dsh-time-context` qualifies because its message is checked against current-turn user messages and durable event time; a same-writer payload round trip alone would not qualify.
+- **Treat every producer-format parser as self-validation.** Rejected because a parser can compare independent source identity, timing, or durable history even when one producer owns the text. `dsh-time-context` qualifies because its message is checked against current-turn user messages and durable event time; a same-writer payload round trip alone would not qualify.
 - **Require every package with mutable private state to publish a companion.** Rejected because private state without an independent event or second data source cannot be checked without duplicating the implementation or exposing new API solely for diagnostics.
 
 ## Consequences

+ 2 - 2
.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 
 服务或方法是否存在、插件 metadata 或 effect、固定纯函数示例,以及调用同一变更操作来验证该操作的探针,仍属于类型、加载、单元或集成测试。parser 与 config 输入、模型或工具 JSON、持久文件、worker 与进程消息和 wire 输入,仍在拥有其输入的操作处校验。
 
-`dsh-time-context` 伴生入口继续发布。它把插件产生的 context message 与独立拥有的当前轮用户消息 provenance 和持久事件时间进行对照,因此即使 formatter 本身正确,attribution、轮次位置与 elapsed-time 关系仍可能产生分歧。
+`dsh-time-context` 伴生入口继续发布。它把插件产生的 context message 与独立拥有的当前轮用户消息 source 和持久事件时间进行对照,因此即使 formatter 本身正确,attribution、轮次位置与 elapsed-time 关系仍可能产生分歧。
 
 ### 在包 README 中明确省略
 
@@ -36,7 +36,7 @@ Status: implemented
 
 - **保留带说明的空伴生入口。** 不采用:源文件、公共子路径、依赖边、构建输出和测试是一套过于繁重的机制,不应只用来表达不存在检查;包 README 可以直接记录该结论。
 - **把 webserver 探针保留为清理 sentinel。** 不采用:它会在无关生命周期事件上修改保留路由,并且只验证自己调用的服务方法。真实路由与 HMR 测试可以在没有生产诊断 effect 的情况下覆盖该行为。
-- **把每个生产方格式 parser 都视为自校验。** 不采用:即使文本只有一个生产方,parser 仍可能对照独立 provenance、时间或持久历史。`dsh-time-context` 符合条件,因为其消息会与当前轮用户消息和持久事件时间对照;只对同一写入方的 payload 做往返检查不符合条件。
+- **把每个生产方格式 parser 都视为自校验。** 不采用:即使文本只有一个生产方,parser 仍可能对照独立 source identity、时间或持久历史。`dsh-time-context` 符合条件,因为其消息会与当前轮用户消息和持久事件时间对照;只对同一写入方的 payload 做往返检查不符合条件。
 - **要求每个拥有私有可变状态的包都发布伴生入口。** 不采用:没有独立事件或第二数据源的私有状态只能通过重复实现来检查,或者需要专门为诊断暴露新 API。
 
 ## 后果

+ 6 - 0
.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.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/simplification/2026-09-06-agent-request-freeze-evidence.md
+2026-09-06-agent-request-freeze-evidence.md: e597ba64ca31da5e6a9be0c32b922737c9a1c074
+2026-09-06-agent-request-freeze-evidence.zh.md: b56b14ca0d69261eddd46507deffba9fa945004c

+ 3 - 3
.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md → .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md

@@ -2,7 +2,7 @@
 
 Status: implemented
 
-English | [中文](2026-09-06-agent-request-freeze-provenance.zh.md)
+English | [中文](2026-09-06-agent-request-freeze-evidence.zh.md)
 
 ## Problem
 
@@ -28,7 +28,7 @@ Apple M4 Pro, macOS arm64, Node 24.19.0; independent worktree dependencies and b
 
 The same 800-turn, four-tools-per-historical-turn history and 40 live requests complete in every sample: 13,923 events, no live tool calls. The repeat median is 72.9% below the isolated original. The historical 70 ms M4 expectation rounds above both optimized medians; applying the shared 2× CI scale and 1.25× headroom produced the 175 ms budget used in the table. These remain local reference measurements, not hosted-runner expectations. The explicit hosted calibration below owns the enforced request-history budget; no other case or memory budget changes here.
 
-The first optimized slot also measures cold tool continuation: totals 185.839958, 185.235583, 185.865917, 189.213459, 185.279417; median 185.839958 ms. Every sample completes 40 requests and 160 tool calls with 14,143 events. Retained heap samples are 22.591591, 22.590355, 22.594795, 22.591743, 22.594681 MiB, below the unchanged 28.75 MiB budget. The earlier baseline's approximately 22.295 MiB highlights the small provenance-table cost; weak keys prevent the table itself retaining replaced messages.
+The first optimized slot also measures cold tool continuation: totals 185.839958, 185.235583, 185.865917, 189.213459, 185.279417; median 185.839958 ms. Every sample completes 40 requests and 160 tool calls with 14,143 events. Retained heap samples are 22.591591, 22.590355, 22.594795, 22.591743, 22.594681 MiB, below the unchanged 28.75 MiB budget. The earlier baseline's approximately 22.295 MiB highlights the small freeze-evidence table cost; weak keys prevent the table itself retaining replaced messages.
 
 The same slot's shipped SDK profile completes 100 turns, 200 requests, and 800 real reads per sample. Totals are 1428.555292, 1160.396333, 1139.843500, 1135.834750, 1155.890334 ms; median 1155.890334 ms. The first sample includes 461.829250 ms boot time versus 164–169 ms for the others and is retained, not discarded. Provider serialization, network time, and browser rendering remain excluded as specified by the baseline owner.
 
@@ -70,7 +70,7 @@ Deterministic controls call the timed case's `assertRequestHistoryBudget`. They
 
 Request construction still scans message identities and allocates a fresh array; it avoids recursively traversing already-proven history. Each loop pays one complete traversal for restored history. Local headers remain a per-request cost. Message values, request markers, previous request snapshots, cancellation, and serialized SDK outputs keep their existing behavior.
 
-The [focused tests](../../../../packages/core/agent-loop/tests/request-freeze.spec.ts) exercise shallow-frozen restored roots with mutable descendants, wrapper identity and mutability, successful-only provenance, repeated requests, same-id compaction replacements, a fresh loop, nested tool schemas, adapter and `NO_ADAPTER` stop arrays, held requests, and live cancellation. Reconstruction and cancellation suites cover adjacent loop semantics. Performance measurements use the unchanged [continuation workload](../../../../benchmarks/agent-continuation/workload.ts), not a smaller synthetic microbenchmark.
+The [focused tests](../../../../packages/core/agent-loop/tests/request-freeze.spec.ts) exercise shallow-frozen restored roots with mutable descendants, wrapper identity and mutability, successful-only freeze evidence, repeated requests, same-id compaction replacements, a fresh loop, nested tool schemas, adapter and `NO_ADAPTER` stop arrays, held requests, and live cancellation. Reconstruction and cancellation suites cover adjacent loop semantics. Performance measurements use the unchanged [continuation workload](../../../../benchmarks/agent-continuation/workload.ts), not a smaller synthetic microbenchmark.
 
 Validation runs 646 Agent-loop and LLM tests with 100% statement, branch, function, and line coverage of agent.ts. Keyless TypeScript SDK bash-tool and multi-turn snapshots pass against rebuilt libraries. Python sdk-minimal and sdk-snapshot checks pass against an independently packaged node24-macos-arm64 executable. Neither SDK requires an expected-output change. The packaging deploy temporarily removes workspace dependency links; a frozen-lockfile install restores them before source checks, without a tracked dependency change.
 

+ 1 - 1
.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md → .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md

@@ -2,7 +2,7 @@
 
 Status: implemented
 
-[English](2026-09-06-agent-request-freeze-provenance.md) | 中文
+[English](2026-09-06-agent-request-freeze-evidence.md) | 中文
 
 ## 问题
 

+ 2 - 2
.agents/notes/implemented/simplification/2026-09-07-file-content-scan.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/implemented/simplification/2026-09-07-file-content-scan.md
-2026-09-07-file-content-scan.md: 5c52a6346fb934a4c10be305dfc6393f87b43f6a
-2026-09-07-file-content-scan.zh.md: 87463ba340f68820e49fd2194d0295a53b93eb3c
+2026-09-07-file-content-scan.md: 5a539f0b9c4e00545d9894647599aed713285014
+2026-09-07-file-content-scan.zh.md: 9a8fe390b8b430baf7975bfc1391ccd09ae1db6e

+ 3 - 3
.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md

@@ -6,11 +6,11 @@ English | [中文](2026-09-07-file-content-scan.zh.md)
 
 ## Problem
 
-Every model dispatch checks complete message content for files, including nested tool results. A request-history CPU profile attributes 23.540 ms of self time to `contentHasFile` and 5.584 ms to its callback. This traversal remains necessary even after [loop-owned freeze provenance](2026-09-06-agent-request-freeze-provenance.md) removes repeated request freezing. The hot LLM source is identical at master `bd5917`, master `112a5`, and the measured `f834b002826453e7918eeb558d052b2c24c56a76`; these observations do not establish PR causality.
+Every model dispatch checks complete message content for files, including nested tool results. A request-history CPU profile attributes 23.540 ms of self time to `contentHasFile` and 5.584 ms to its callback. This traversal remains necessary even after [loop-owned freeze evidence](2026-09-06-agent-request-freeze-evidence.md) removes repeated request freezing. The hot LLM source is identical at master `bd5917`, master `112a5`, and the measured `f834b002826453e7918eeb558d052b2c24c56a76`; these observations do not establish PR causality.
 
 ## Decision
 
-[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) uses direct iteration instead of recursive `Array.some` callbacks. It preserves early exit, nested tool-result traversal, and false results for other block kinds. It stores no identities, validation results, or freeze proofs. Image detection, file projection, and request construction keep their existing behavior. The [request-freeze calibration](2026-09-06-agent-request-freeze-provenance.md) owns the request-history budget.
+[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) uses direct iteration instead of recursive `Array.some` callbacks. It preserves early exit, nested tool-result traversal, and false results for other block kinds. It stores no identities, validation results, or freeze proofs. Image detection, file projection, and request construction keep their existing behavior. The [request-freeze evidence](2026-09-06-agent-request-freeze-evidence.md) owns the request-history budget.
 
 ## Measurement evidence
 
@@ -29,4 +29,4 @@ A weak negative-result cache needs proof that every relevant descendant is immut
 
 ## Consequences
 
-The scan remains linear in visited blocks and rereads mutable nested content on each call. [Content tests](../../../../packages/llm/llm/tests/content.spec.ts) cover empty, frozen, nested, and subsequently mutated arrays; service tests preserve file-handle projection, and request-freeze, reconstruction, and resume tests preserve native-history semantics. No model-visible text or Session format changes. The freeze-provenance and [backend-baseline](../testing/2026-09-06-backend-continuation-performance.md) notes retain independent ownership; neither is superseded.
+The scan remains linear in visited blocks and rereads mutable nested content on each call. [Content tests](../../../../packages/llm/llm/tests/content.spec.ts) cover empty, frozen, nested, and subsequently mutated arrays; service tests preserve file-handle projection, and request-freeze, reconstruction, and resume tests preserve native-history semantics. No model-visible text or Session format changes. The freeze-evidence and [backend-baseline](../testing/2026-09-06-backend-continuation-performance.md) notes retain independent ownership; neither is superseded.

+ 3 - 3
.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md

@@ -6,11 +6,11 @@ Status: implemented
 
 ## Problem
 
-每次模型分发都检查完整消息内容中的文件,包括嵌套工具结果。请求历史 CPU profile 将 23.540 ms 自身时间归于 `contentHasFile`,将 5.584 ms 归于其回调。即使[循环自有冻结来源证明](2026-09-06-agent-request-freeze-provenance.zh.md)消除了重复请求冻结,这次遍历仍然必需。master `bd5917`、master `112a5` 与实测的 `f834b002826453e7918eeb558d052b2c24c56a76` 的 LLM 热点源码完全相同;这些观察不能证明 PR 因果关系。
+每次模型分发都检查完整消息内容中的文件,包括嵌套工具结果。请求历史 CPU profile 将 23.540 ms 自身时间归于 `contentHasFile`,将 5.584 ms 归于其回调。即使[循环自有冻结证据](2026-09-06-agent-request-freeze-evidence.zh.md)消除了重复请求冻结,这次遍历仍然必需。master `bd5917`、master `112a5` 与实测的 `f834b002826453e7918eeb558d052b2c24c56a76` 的 LLM 热点源码完全相同;这些观察不能证明 PR 因果关系。
 
 ## Decision
 
-[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) 使用直接迭代,替代递归的 `Array.some` 回调。它保留提前退出、嵌套工具结果遍历,以及其他块类型返回 false 的行为。它不存储身份、校验结果或冻结证明。图片检测、文件投影和请求构建保持既有行为。[请求冻结校准](2026-09-06-agent-request-freeze-provenance.zh.md)拥有请求历史预算。
+[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) 使用直接迭代,替代递归的 `Array.some` 回调。它保留提前退出、嵌套工具结果遍历,以及其他块类型返回 false 的行为。它不存储身份、校验结果或冻结证明。图片检测、文件投影和请求构建保持既有行为。[请求冻结证据](2026-09-06-agent-request-freeze-evidence.zh.md)拥有请求历史预算。
 
 ## Measurement evidence
 
@@ -29,4 +29,4 @@ Apple M4 Pro、Node 24.19.0:九组交替原始/候选配对在全新的普通
 
 ## Consequences
 
-扫描仍与访问块数呈线性关系,每次调用都重新读取可变的嵌套内容。[内容测试](../../../../packages/llm/llm/tests/content.spec.ts)覆盖空数组、冻结数组、嵌套数组与后续变更的数组;服务测试保留文件 handle 投影,请求冻结、重建与恢复测试保留原生历史语义。模型可见文本与 Session 格式均不改变。冻结来源证明与[后端基线](../testing/2026-09-06-backend-continuation-performance.zh.md)记录仍各自拥有独立决策;两者均未被取代。
+扫描仍与访问块数呈线性关系,每次调用都重新读取可变的嵌套内容。[内容测试](../../../../packages/llm/llm/tests/content.spec.ts)覆盖空数组、冻结数组、嵌套数组与后续变更的数组;服务测试保留文件 handle 投影,请求冻结、重建与恢复测试保留原生历史语义。模型可见文本与 Session 格式均不改变。冻结证据与[后端基线](../testing/2026-09-06-backend-continuation-performance.zh.md)记录仍各自拥有独立决策;两者均未被取代。

+ 6 - 0
.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.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/simplification/2026-09-10-derived-workspace-recency.md
+2026-09-10-derived-workspace-recency.md: e8f304110c89bf563164dd24125beeb36e7644a6
+2026-09-10-derived-workspace-recency.zh.md: 60f2da36c277e77d367f152aa8a3159ff68d909c

+ 39 - 0
.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md

@@ -0,0 +1,39 @@
+# Agent Note: Derive Workspace recency independently of manual order
+
+Status: implemented
+
+English | [中文](2026-09-10-derived-workspace-recency.zh.md)
+
+## Problem
+
+An editable activity-promoted list can disagree with its displayed timestamps without a drag: an older Session arriving late is promoted ahead of a newer Session already observed. Repeating the same complete list preserves that inversion. The historical [sidebar-order decision](../../archived/feature/2026-08-11-workspace-sidebar-order-and-folding.md) shared one editable order between Manual and Last updated to preserve positions when switching modes. That trade-off does not satisfy chronological browsing.
+
+## Decision
+
+Last updated is a pure projection of current Session summaries: ordinary rows sort by descending `updatedAt`, with Session id as the tie-break. The selected blank New Session precedes ordinary rows until its first prompt. Grouped, Ungrouped, and flat views use the same policy. Reloads and delayed summaries require no observed-timestamp history or promotion events in the view store.
+
+Only Manual uses browser-persisted Session display order. A Session drag snapshots every active account, applies the move to its browser-local account, and selects Manual atomically. Entering Manual also freezes every active account from the current chronological ordering. Returning to Last updated discards the manual layout; entering Manual again starts from the current timestamps. Reconciliation retains saved members that remain in the Workspace account, removes departed members, and appends newly known members at the end, newest first when several arrive together. New members without summaries wait for the summary, while previously saved slots survive a temporarily missing summary. Workspace membership and Workspace-group order remain Host-owned; Workspace-group drags still call the Host reorder operation.
+
+The selected blank New Session is a display invariant applied after either base order: it remains first and cannot start a drag. Manual persists the observed blank position even while the Workspace stream reconnects, preserving every other saved member until the complete baseline permits reconciliation. Waiting to record that position would lose it if the first prompt arrives before the baseline. Its first prompt removes the blank state. Manual then keeps the existing first slot and allows dragging; Last updated projects it from the prompt timestamp. The ordering subscription and reconciliation remain mounted when the sidebar is collapsed or search replaces the list body.
+
+The persistence key stores grouping, expansion, and saved Manual positions. Persisted observed-timestamp data is removed when active account keys are retained. Current metadata owns timestamp accuracy: a cold summary without prompt metadata can fall back to creation time, independently of view ordering.
+
+## Alternatives considered
+
+**Keep activity promotion and sort again on reconnect.** Late summaries and metadata corrections also arrive within a connection. A reconnect-only repair still allows chronology to depend on observation order.
+
+**Restore a separate manual layout on mode switches.** Replacing the visible list with old positions makes entering Manual surprising. Resetting the layout on a mode change gives Manual the simpler meaning of pausing the current ordering.
+
+**Persist a second chronological order.** Current timestamps already determine that order. A second ledger retains synchronization and recovery work without representing an independent user choice.
+
+**Disable dragging in Last updated.** Switching to Manual on a committed drag keeps the existing affordance and gives the edited order an explicit mode. A cancelled or ineffective drag leaves the selected mode unchanged.
+
+## Consequences
+
+Manual pauses automatic sorting of the current list. The default is Last updated, and reloads retain the selected mode and current manual positions. Chronological browsing gives up persistent drag exceptions. The view store owns manual Session positions but no activity timestamps; the Host Session-account order does not participate in this browser's display order.
+
+The archived sidebar note remains frozen historical evidence; its folding and Workspace-order decisions are outside this change. No active note owns the superseded shared-order policy.
+
+## Testing
+
+Component regressions cover older first arrivals, corrected and decreasing timestamps, delayed summaries, discarded-layout isolation, atomic mode switches, blank insertion and drag prevention, the first-prompt transition, collapsed-sidebar reconciliation, reloads, and automatic Manual selection on drag. The recorded-session Web scenario exercises the shipped composition with persisted positions, native dragging, grouped and flat views, a pinned blank, and reloads. Existing folding cases retain the provisional blank quota.

+ 39 - 0
.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md

@@ -0,0 +1,39 @@
+# Agent Note: 独立于手动顺序派生 Workspace 最近更新顺序
+
+Status: implemented
+
+[English](2026-09-10-derived-workspace-recency.md) | 中文
+
+## 问题
+
+可编辑且按活动置顶的列表即使没有拖拽,也可能与显示的时间不符:较旧的 Session 迟到后,会被提升到已观察到的较新 Session 之前。重复输入同一份完整列表仍会保留这个逆序。历史上的[侧边栏顺序决策](../../archived/feature/2026-08-11-workspace-sidebar-order-and-folding.md)让手动排序和最近更新共用一份可编辑顺序,以便切换模式时保留位置。这个取舍无法满足按时间浏览的需要。
+
+## 决策
+
+最近更新是当前 Session 摘要的纯投影:普通行按 `updatedAt` 降序排列,时间相同时按 Session id 排序。当前选中的空白新会话在首条提示词落地前排在普通行之前。分组、Ungrouped 和单列表视图采用相同策略。重新加载和摘要迟到不需要视图存储中的已观察时间历史或提升事件。
+
+只有手动排序使用浏览器持久化的 Session 显示顺序。拖拽 Session 会一次性记录所有有效记账、在对应的浏览器本地记账中应用移动,并原子地选中手动排序。直接进入手动排序同样会从当前时间顺序冻结所有有效记账。返回最近更新会丢弃手动布局;再次进入手动排序时,从当前时间戳确定的顺序开始。对账会保留仍属于 Workspace 记账的已保存成员、移除已经离开的成员,并把新发现的成员追加到末尾;多条同时到达时按最近更新时间降序排列。尚无摘要的新成员会等待摘要,而已经保存的位置在摘要暂时缺失时仍会保留。Workspace 成员关系和 Workspace 分组顺序仍由 Host 拥有;拖拽 Workspace 分组仍会调用 Host 重排序操作。
+
+当前选中的空白新会话是在两种基础顺序之后应用的显示不变式:它保持首位且无法发起拖拽。即使 Workspace 流正在重连,手动排序也会持久化已观察到的空白行位置,并保留其他所有已保存成员,直到完整基线允许对账。若等待基线后才记录该位置,首条提示词先到达时就会丢失它。首条提示词会移除空白状态;此后,手动排序保留原有首位并允许拖拽,最近更新则按提示词时间戳派生其位置。侧边栏折叠或搜索替代列表主体时,排序订阅和对账仍保持挂载。
+
+持久化 key 保存分组方式、展开状态和手动排序位置。保留有效记账 key 时会移除持久化的已观察时间数据。时间准确性由当前元数据负责:缺少提示词元数据的冷摘要可能回退到创建时间,这与视图排序相互独立。
+
+## 考虑过的替代方案
+
+**保留活动提升,并在重连时再次排序。** 摘要迟到和元数据修正也会发生在一次连接内。仅在重连时修复仍会让时间顺序依赖观察先后。
+
+**切换模式时恢复独立的手动布局。** 用旧位置替换可见列表会让进入手动排序的结果出乎预期。在模式变化时重置布局,让手动排序只表示暂停当前排列。
+
+**持久化第二份时间顺序。** 当前时间戳已经决定该顺序。第二份账本保留了同步和恢复工作,却没有表达独立的用户选择。
+
+**禁止在最近更新中拖拽。** 在提交拖拽时切到手动排序,可以保留既有操作入口,并为编辑后的顺序提供明确模式。取消或未产生有效移动的拖拽保持所选模式不变。
+
+## 后果
+
+手动排序暂停当前列表的自动排列。默认模式为最近更新,重新加载保留所选模式和当前手动位置。按时间浏览放弃持久化的拖拽例外。视图存储拥有 Session 手动位置,但不拥有活动时间戳;Host Session 记账顺序不参与本浏览器的显示顺序。
+
+已归档的侧边栏记录保持为冻结的历史证据;其折叠和 Workspace 顺序决策不在本次修改范围内。没有活动记录拥有被替代的共享顺序策略。
+
+## 测试
+
+组件回归覆盖较旧记录首次到达、时间修正和回退、摘要迟到、已丢弃布局的隔离、原子模式切换、空白行插入与禁拖、首条提示词转换、侧边栏折叠时的对账、重新加载,以及拖拽后自动选中手动排序。录制会话 Web 场景在实际发布组合中验证持久化位置、原生拖拽、分组与单列表视图、固定首位的空白项及重新加载。既有折叠用例保留临时空白行配额。

+ 6 - 0
.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.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/simplification/2026-09-11-remove-e2b-providers.md
+2026-09-11-remove-e2b-providers.md: 87bec5b728f64443e7132aa9b40feb684f9a0d0f
+2026-09-11-remove-e2b-providers.zh.md: 3048a61ed28232bda2f86ddc6ba6b003060ff87d

+ 51 - 0
.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md

@@ -0,0 +1,51 @@
+# Agent Note: Remove the E2B execution providers
+
+Status: implemented
+
+English | [中文](2026-09-11-remove-e2b-providers.zh.md)
+
+## Problem
+
+The E2B experiment supplied one remote filesystem/process world for file tools, Bash, PTY and language servers while the Harness, credentials, model transport and session state stayed on the host. Its three providers demonstrated that those consumers can share the existing filesystem and subprocess interfaces without provider-specific tools.
+
+PTC Node confinement requires bidirectional control traffic separate from arbitrary program stdin/stdout/stderr. The pinned `e2b@2.29.1` SDK exposed standard process streams and PTY output, with no extra descriptor transport. Creating a descriptor inside the VM does not expose it to the Harness host; a remote relay or a separate authenticated stream must bridge it.
+
+Such a bridge owns independent progress when output blocks, bounded retention, channel closure, startup publication, command identity, cancellation and disconnect cleanup. The SDK retained complete stdout/stderr even when a consumer applied backpressure, so stream-level queue bounds alone could not bound host memory. Extending that adapter would turn the experiment into a separate remote transport project.
+
+## Decision
+
+The repository excludes the E2B sandbox owner, filesystem provider and subprocess provider, their SDK dependency, integration fixtures, opt-in workflow and generated registrations. It supplies no E2B execution backend. The removal trades that integration for a smaller maintained implementation set; it does not imply that E2B cannot support a suitable transport or that published packages have no external users.
+
+The [portable execution-world decision](../architecture/2026-07-28-portable-execution-world-consumers.md) remains authoritative for `ctx.fs` and `ctx.subprocess`. The pair describes one path namespace and process world. Providers own path conversion, executable lookup, ordinary streams, terminal allocation and managed cleanup; consumers retain tool semantics, policy and presentation. A local/remote metadata label adds no authority to those operations.
+
+Remote execution remains an intended capability. Terminal allocation, writes, foreground inspection and signalling retain their Promise contracts and pending-operation cancellation guarantees. A local implementation completing these calls synchronously does not justify removing the asynchronous behavior required by remote providers. Process completion, terminal readiness, emulator parsing and managed-range quiescence also retain their existing owners.
+
+This decision removes one provider family. It changes neither PTC execution nor the shared filesystem, subprocess and terminal interfaces. Direct Node confinement and validation of program-authored control traffic require their own implementation and evidence.
+
+## Alternatives considered
+
+**Retain E2B and refuse the control-channel request.** This preserves the existing remote functionality with little new transport code, but retains its SDK and remote lifecycle obligations while excluding the new execution capability. Retirement makes that maintenance tradeoff explicit.
+
+**Multiplex the channel over E2B standard streams.** A relay can frame stdin/control input and stdout/stderr/control output. It also needs channel-specific flow control and closure, bounded queues, and a solution to cumulative SDK output retention. These are remote transport semantics, not descriptor plumbing.
+
+**Use a separate authenticated stream.** This can avoid the SDK's retained-output path and let control traffic progress independently. It adds endpoint access, authentication, network policy, startup coordination and connection-failure cleanup. The removal does not introduce that transport.
+
+**Make terminal operations synchronous.** This simplifies local wrappers but removes delayed-operation behavior needed by remote providers. The shared asynchronous interface and its cancellation/ordering tests remain useful independently of E2B.
+
+## Consequences
+
+Custom compositions that depend on the repository's E2B packages lose that supplied backend. External adoption is unknown. Removing the integration also removes its live remote composition evidence; remaining local and delayed-provider tests protect their own contracts, not an unimplemented replacement.
+
+The [native containment](../architecture/2026-08-28-subprocess-native-containment.md), [outbound proxy](../architecture/2026-08-27-outbound-proxy-policy.md) and [workspace-file service](../architecture/2026-09-05-workspace-files-service.md) decisions remain active. Their E2B implementation and verification inventories are retired while their process ownership, routing and file-access requirements remain intact. The portability decision is partially superseded only in its E2B realization.
+
+## Reintroduction conditions
+
+A remote provider needs a concrete execution use case and evidence for shared file/process coordinates, policy enforcement, bounded transport retention, independent control progress, precise channel closure and managed cancellation. Source and built compositions must exercise those behaviors. A connection loss cannot justify replaying a possibly executed program or claiming unobserved cleanup succeeded.
+
+SSH remains a possible transport for those providers: binary channels avoid the E2B command SDK's retained-output path, but ordinary SSH exec does not map arbitrary child descriptors. A remote helper still owns control-stream bridging, file semantics, process lifetime and remote policy enforcement. The retained interfaces permit that work without claiming a replacement is already available.
+
+## Verification
+
+The removal inventory finds no live E2B package, workflow, import, dependency or catalog entry. Every surviving lockfile importer and package resolution matches its original value. The shared subprocess and terminal implementation, delayed-provider tests and PTC source are unchanged.
+
+The remaining filesystem, subprocess, Bash, terminal and LSP suites pass alongside the affected generator and workflow tests. `pnpm run build`, `pnpm run test:docs`, `pnpm run doc-sync`, `pnpm run lint` and `pnpm run hygiene` pass for the removal. These checks validate the resulting tree; deleted E2B tests provide no evidence for it.

+ 51 - 0
.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md

@@ -0,0 +1,51 @@
+# Agent Note: 移除 E2B 执行提供方
+
+Status: implemented
+
+[English](2026-09-11-remove-e2b-providers.md) | 中文
+
+## 问题
+
+E2B 实验为文件工具、Bash、PTY 和语言服务器提供了一个远程文件系统/进程世界,而 Harness、凭据、模型传输和会话状态留在宿主。三个提供方证明,这些消费方可以共享现有文件系统与子进程接口,无需特定于提供方的工具。
+
+PTC Node 约束需要独立于任意程序 stdin/stdout/stderr 的双向控制通信。固定版本的 `e2b@2.29.1` SDK 提供标准进程流与 PTY 输出,没有额外描述符传输。在 VM 中创建描述符不会使其暴露给 Harness 宿主;必须通过远程中继或独立的认证流进行桥接。
+
+这样的桥接负责输出阻塞时的独立推进、有界保留、通道关闭、启动发布、命令身份、取消和断线清理。即使消费方施加反压,SDK 仍保留完整的 stdout/stderr,因此仅限制流级队列无法约束宿主内存。扩展这一适配器会把实验变成独立的远程传输项目。
+
+## 决策
+
+仓库不包含 E2B 沙箱所有者、文件系统提供方和子进程提供方,也不包含其 SDK 依赖、集成 fixture、可选工作流和生成注册项。仓库不提供 E2B 执行后端。移除用放弃该集成换取更小的维护范围;这不意味着 E2B 无法支持合适的传输,也不意味着已发布包没有外部用户。
+
+[可移植执行世界决策](../architecture/2026-07-28-portable-execution-world-consumers.zh.md)继续负责 `ctx.fs` 与 `ctx.subprocess` 的约定。两者描述同一个路径命名空间与进程世界。提供方负责路径转换、可执行文件查找、普通流、终端分配和受管清理;消费方保留工具语义、策略和呈现。local/remote 元数据标签不会为这些操作增加权限依据。
+
+远程执行仍是预期能力。终端分配、写入、前台检查和信号发送保留 Promise 约定,以及待处理操作的取消保证。本地实现同步完成这些调用,不足以支持移除远程提供方所需的异步行为。进程完成、终端就绪判断、模拟器解析和受管范围停稳也保留现有职责归属。
+
+本决策移除一个提供方族。它既不改变 PTC 执行,也不改变共享文件系统、子进程和终端接口。直接 Node 约束与程序生成的控制通信验证需要各自的实现和证据。
+
+## 考虑过的替代方案
+
+**保留 E2B 并拒绝控制通道请求。** 这只需少量新传输代码即可保留现有远程功能,但仍保留 SDK 与远程生命周期维护责任,同时排除新的执行能力。移除明确表达这一维护取舍。
+
+**通过 E2B 标准流多路复用通道。** 中继可以对 stdin/control 输入和 stdout/stderr/control 输出分帧。它还需要逐通道流量控制与关闭、有界队列,以及累计 SDK 输出保留的解决方案。这些是远程传输语义,不是描述符接线。
+
+**使用独立的认证流。** 这可以避开 SDK 保留输出的路径,并让控制通信独立推进。它增加端点访问、认证、网络策略、启动协调和连接失败清理。本次移除不引入该传输。
+
+**把终端操作改为同步。** 这可以简化本地包装,但会移除远程提供方需要的延迟操作行为。共享异步接口及其取消/排序测试在没有 E2B 时仍然有用。
+
+## 后果
+
+依赖仓库 E2B 包的自定义组合失去该后端。外部采用情况未知。移除集成也会移除其真实远程组合证据;保留的本地与延迟提供方测试保护各自约定,并不验证尚未实现的替代方案。
+
+[原生约束](../architecture/2026-08-28-subprocess-native-containment.zh.md)、[出站代理](../architecture/2026-08-27-outbound-proxy-policy.zh.md)和[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md)决策保持有效。它们的 E2B 实现与验证清单被撤下,进程所有权、路由和文件访问要求保持完整。可移植性决策仅有 E2B 实现部分被取代。
+
+## 重新引入条件
+
+远程提供方需要具体的执行用例,并证明共享文件/进程坐标、策略强制、有界传输保留、独立控制推进、精确通道关闭和受管取消。源代码与构建后组合必须执行这些行为。连接丢失不能成为重放可能已执行程序的理由,也不能据此声称未观察到的清理已经成功。
+
+SSH 仍可作为这些提供方的传输:二进制通道避开 E2B 命令 SDK 保留输出的路径,但普通 SSH exec 不映射任意子进程描述符。远程辅助进程仍负责控制流桥接、文件语义、进程生命周期和远程策略强制。保留的接口允许开展这项工作,但不声称替代方案已经可用。
+
+## 验证
+
+移除清点未发现有效的 E2B 包、工作流、导入、依赖或目录条目。锁文件中每个保留的 importer 与包解析结果都保持原值。共享子进程与终端实现、延迟提供方测试和 PTC 源代码保持不变。
+
+保留的文件系统、子进程、Bash、终端与 LSP 套件,以及受影响的生成器与工作流测试均通过。移除后的 `pnpm run build`、`pnpm run test:docs`、`pnpm run doc-sync`、`pnpm run lint` 与 `pnpm run hygiene` 均通过。这些检查验证得到的代码树;已删除的 E2B 测试不为其提供证据。

+ 1 - 1
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md
-2026-08-23-installed-python-wheel-black-box-ci.md: 8821b93d0d1c5a32c7dbd97f67d78f7559769791
+2026-08-23-installed-python-wheel-black-box-ci.md: 8dfc6d7e344837b9663c1dbe5cff940731f53166
 2026-08-23-installed-python-wheel-black-box-ci.zh.md: 05972560816193ff0b93323e6dcbea3ad5020215

+ 1 - 1
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md

@@ -14,7 +14,7 @@ The Python SDK unit suite drives fake peers, while the packaged-runtime workflow
 
 The required Python runtime workflow builds the pure SDK wheel and each platform runtime wheel before behavior tests. Every native target installs those two local files into a new Python 3.10 virtual environment, changes to a temporary directory outside the repository, unsets `PYTHONPATH` and `DSH_RUNTIME_MODE`, and invokes only the public Python modules plus the packaged executable.
 
-The black-box harness rejects a non-venv process, repository-relative working directory, source or editable import, unequal distribution versions, an SDK dependency that does not exactly pin the runtime version, an executable outside the installed runtime package, or an executable absent from the runtime distribution record. This provenance check runs before the first agent request, so a behavior pass cannot conceal that the wrong code ran.
+The black-box harness rejects a non-venv process, repository-relative working directory, source or editable import, unequal distribution versions, an SDK dependency that does not exactly pin the runtime version, an executable outside the installed runtime package, or an executable absent from the runtime distribution record. This installed-artifact identity check runs before the first agent request, so a behavior pass cannot conceal that the wrong code ran.
 
 ### Keyless behavior
 

+ 2 - 2
.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.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/implemented/testing/2026-09-06-backend-continuation-performance.md
-2026-09-06-backend-continuation-performance.md: 94e9192df473515d39014c8be9e82e0413df47bc
-2026-09-06-backend-continuation-performance.zh.md: 92556ad8e3434a3219ea585503bec9b17bbf576a
+2026-09-06-backend-continuation-performance.md: d72e3145d9bfe0ffabab2822c8a3313785fdd44f
+2026-09-06-backend-continuation-performance.zh.md: 66085e1310fbfb92cb384875015d3baa648650c5

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