diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml similarity index 67% rename from .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml rename to .agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml index 79ba95b677..212dd5d306 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml +++ b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.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-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 +2026-09-11-session-controller-fork-turn-cut.md: 9223acd4c0c7bbcc84a0bfeb72f3b57d4bf828e2 +2026-09-11-session-controller-fork-turn-cut.zh.md: cae022ae97896c0b43633edb310afb305d690b4a diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md rename to .agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.md index 0c8d45c861..9223acd4c0 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md +++ b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.md @@ -1,6 +1,7 @@ # Agent Note: Session Controller forks stop at the selected turn end Status: implemented +Archived: 2026-09-16 English | [中文](2026-09-11-session-controller-fork-turn-cut.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md similarity index 96% rename from .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md rename to .agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md index 729a42a596..cae022ae97 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md @@ -1,8 +1,9 @@ # Agent Note: Session Controller 分叉截到选中轮次的结束事件 Status: implemented +Archived: 2026-09-16 -English | [中文](2026-09-11-session-controller-fork-turn-cut.md) +[English](2026-09-11-session-controller-fork-turn-cut.md) | 中文 ## Problem diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json index f1b6aa7b95..74d114c6bb 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -754,6 +754,9 @@ "bug-fix/2026-09-16-movable-mandatory-update-window.i18n.yaml": "sha256:e526ce14ae9557e24364e3686778b130090445ef2152d976b7cc93e76eac6401", "bug-fix/2026-09-16-movable-mandatory-update-window.md": "sha256:67546cea0b3de2b3e32957a508dbde097d91dd919fe067c8dbecb2c65b50c366", "bug-fix/2026-09-16-movable-mandatory-update-window.zh.md": "sha256:20a0aade6ee71c89e84d70e23c89bb51e8a38c674d103dd43022feabb8d27be3", + "bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml": "sha256:4442529ced1c8e48d30f7b50c65ff03eb92dce9815beb438cedbf29f234bce47", + "bug-fix/2026-09-11-session-controller-fork-turn-cut.md": "sha256:a996f24a896c7fc5d81e015db3eae27d8a46c853287572e815623ce35eae1b12", + "bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md": "sha256:6486f0eacf5067ef9850dc32f1c27b2109a7d4e9e3c498366f5c38742bb53ac5", "feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918", "feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b", "feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69", diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml index 98850aed4c..b62c09f26c 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md -2026-06-11-content-block-vocabulary.md: d7d3f6b43a3f65d1421f026e5b6c2cc1ba1eadd2 -2026-06-11-content-block-vocabulary.zh.md: ed4f915dff6dcb6dbc91400f9bfa5384253aea7b +2026-06-11-content-block-vocabulary.md: 917dd5225fdc4ab391e9e4629769abbb68095731 +2026-06-11-content-block-vocabulary.zh.md: 9d228eb34dafbdebab44b45dc3b290c9327d3b2b diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md index d7d3f6b43a..917dd5225f 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md @@ -10,7 +10,7 @@ The harness needs one internal language for messages that the loop, session log, ## Decision -Own the vocabulary: messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs. +Own the vocabulary: user, assistant, and system messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. Tool results are first-class tool-role messages whose own content holds result blocks; released Session format v3 retains the historical `tool-result` content block inside a user-role wrapper, and the adjacent V3-to-V4 edge lifts that wrapper into the first-class role. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs. In-session context injection (`context/message`) and mid-turn steering originally rendered as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. Both now project as plain user content with no wrapper; see [the injected-content-envelope Agent Note](../simplification/2026-07-20-unwrap-injected-content-envelopes.md). Live-adapter validation confirms this rendering for current DeepSeek behavior; a future provider-specific mismatch belongs in that adapter rather than a new canonical role. diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md index ed4f915dff..9d228eb34d 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md @@ -10,7 +10,7 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循 ## 决策 -自主拥有词汇:消息是类型化内容块的数组(`text`、`reasoning`、`tool-call`、`tool-result`),其联合类型派生自可合并扩展的 `ContentBlockMap`,插件通过声明合并添加新的块类型。同一可合并扩展映射模式为所有「字符串化」字段提供类型(`MessageSource`、`FinishReason`、`TurnTrigger`、`TurnEndReason`)。流式输出采用原始分片协议;`BlockAssembler` 是唯一的共享组装实现。适配器负责转换为提供方的协议格式(wire format)——映射成本留在适配器中,正是它该在的地方。 +自主拥有词汇:user、assistant 与 system 消息是类型化内容块的数组(`text`、`reasoning`、`tool-call`),其联合类型派生自可合并扩展的 `ContentBlockMap`,插件通过声明合并添加新的块类型。Tool result 是一等 tool-role message,结果块属于该 message 自己的 content;已发布的 Session format v3 仍将历史 `tool-result` content block 保存在 user-role 包装中,邻接的 V3 到 V4 边会将该包装提升为一等角色。同一可合并扩展映射模式为所有「字符串化」字段提供类型(`MessageSource`、`FinishReason`、`TurnTrigger`、`TurnEndReason`)。流式输出采用原始分片协议;`BlockAssembler` 是唯一的共享组装实现。适配器负责转换为提供方的协议格式(wire format)——映射成本留在适配器中,正是它该在的地方。 会话内上下文注入(`context/message`)和轮次中途 steering(中途引导)最初渲染为带标签的 user-role 信封(system-reminder 模式),而非引入新角色,因此适配器无需承担额外负担。如今两者都投影为无包装的普通用户内容;见[注入内容信封 Agent Note](../simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md)。实际适配器验证已确认此渲染方式符合当前 DeepSeek 的行为;如果未来某提供方出现不兼容,应在该适配器内处理,而非引入新的规范角色。 diff --git a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.i18n.yaml index 98f54d5d26..081f618b1a 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-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-07-20-routed-model-context-and-compaction-policy.md -2026-07-20-routed-model-context-and-compaction-policy.md: 26d8a4a85c284e50b9635017f0dd0bf905a4427f -2026-07-20-routed-model-context-and-compaction-policy.zh.md: 8656200a4b25c2ca70160e8f5949a7518e390168 +2026-07-20-routed-model-context-and-compaction-policy.md: d276354cb21f45eaec1c4e3202c7be1770745efb +2026-07-20-routed-model-context-and-compaction-policy.zh.md: b6e2a0b914f78e19675d80b86d8d962e8ba8c80f diff --git a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md index 26d8a4a85c..d276354cb2 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md +++ b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md @@ -16,7 +16,7 @@ Neither obvious configuration owner is sufficient. Compact-basic is optional and `LlmAdapter.resolveModel(provider, model, signal?)` returns aggregate metadata for one exact route, with optional `LlmModelContext` under its `context` field. `LlmRuntime.resolveModelInfo()` selects the registered route owner, validates a positive integer `contextWindow`, and returns detached metadata. The query is independent of `listModels()`: an unlisted dynamic model may have capacity metadata, and an absent `context` means only that the adapter cannot describe capacity. -The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The two built-in model entries each publish an exact 256,000-token capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model. +The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The built-in model entries each declare a 1,000,000-token context capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model. ### Token measurement remains model-agnostic @@ -26,17 +26,17 @@ The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each config Compact-basic owns consumer policy. Top-level fields define defaults; `modelPolicies` contains partial overrides keyed by the exact `{ provider, model }` pair. Duplicate targets and unknown or invalid fields fail plugin load. `thresholdRatio` defaults to `0.8`, and retention defaults to `retainRatio: 0.16`; callers may use an absolute `retainTokens` instead, but the two retention forms are mutually exclusive. After inheritance, a ratio retention that is not below its threshold ratio also fails plugin load because no model capacity can make that policy valid. -For proactive pressure, compaction-basic reads the latest durable request route, resolves its adapter capacity and exact-target policy, and scales ratios into a `ResolvedCompactSpec`. It performs this resolution on every check, so a provider or model switch in one session changes capacity and policy immediately. An absolute retained budget that is not below the scaled threshold fails when the target capacity first makes that comparison possible. +For proactive pressure, compaction-basic reads the latest durable route and resolves its adapter capacity `W`, request output reservation `O`, and exact-target policy. The trigger is `floor(min(W × thresholdRatio, W − O − headroomTokens))`, with default headroom of 65,536 tokens. The window fraction limits history growth while the second cap leaves extra room after a full output for compaction input and generation; it does not bound arbitrary tool-result growth. Retention scales `W − O` independently. `O` is the effective envelope’s `maxTokens`, else the adapter’s per-request default, else zero. Resolution runs on every check, so route or output-cap changes immediately affect policy. A nonpositive pressure budget or retention at least as large as the trigger fails when target capacity makes that comparison possible. The same exact-target override can select summarization provider/model, summarization output cap, convergence retries, and overflow retry cap. These are compaction concerns and never enter an LLM provider. ### Target-specific pressure failures preserve optional composition -An adapter that lacks capacity metadata remains a valid LLM route. Manual proactive pressure fails with a target-specific configuration error; the automatic listener warns once per exact route and continues with full history. The same per-route suppression applies when resolved capacity exposes an invalid absolute retention budget, while unrelated operational failures remain independently visible. Canonical provider-confirmed overflow does not need capacity metadata: it bypasses the proactive threshold and normal retention budget, attempts one maximal balanced reduction, and preserves the original provider error unless replacement proves progress. +An adapter that lacks capacity metadata remains a valid LLM route. Manual proactive pressure fails with a target-specific configuration error; the automatic listener warns once per exact route and continues with full history. The same suppression applies when output plus headroom exhausts capacity or retention reaches the trigger, while unrelated operational failures remain independently visible. Canonical provider-confirmed overflow does not need capacity metadata: it bypasses the proactive threshold and normal retention budget, attempts one maximal balanced reduction, and preserves the original provider error unless replacement proves progress. ## Testing -Service tests cover detached context metadata, invalid adapter output, catalog independence, and default absence. Adapter tests cover DeepSeek exact/default/unlisted resolution, invalid capacities, and pi-ai exact descriptor resolution. Compact tests cover ratio scaling, exact provider/model overrides, load-time rejection of invalid merged ratios, runtime absolute-budget validation, same-model-id provider switches, target-specific warning suppression, and capacity-independent overflow recovery. Loader fixtures reject the removed token-meter capacity setting, and examples configure capacity on adapters. +Service tests cover detached context metadata, invalid adapter output, catalog independence, and default absence. Adapter tests cover DeepSeek exact/default/unlisted resolution, invalid capacities, and pi-ai exact descriptor resolution. Compact tests cover both trigger caps, default and overridden headroom, envelope output caps and adapter fallback, independent retention scaling, invalid budgets, exact-target overrides, provider switches, warning suppression, and capacity-independent overflow recovery. The keyless `compaction-output-reserve` headless scenario records a nonzero request cap, proactive compaction before the next step, and replacement history. Loader fixtures reject the removed token-meter capacity setting, and examples configure capacity on adapters. ## Alternatives considered @@ -52,6 +52,6 @@ Service tests cover detached context metadata, invalid adapter output, catalog i - The same compaction-basic instance safely handles different windows, provider switches, and identical model ids under different providers without consulting discovery metadata. - LLM-only and meter-only compositions remain valid; loading compaction-basic adds no reverse dependency from adapters. - DeepSeek deployments may set exact per-model capacities, or use `defaultContextWindow` for entries without capacity and unlisted pass-through ids. -- Ratio defaults scale naturally across models, while exact-target absolute retention remains available for deployment-specific behavior. +- Ratio defaults scale naturally across models and exclude the output tokens each request reserves, while exact-target absolute retention remains available for deployment-specific behavior. This note supersedes the global-capacity and no-model-policy parts of the [replay token meter service Agent Note](../../archived/architecture/2026-07-15-replay-token-meter-service.md). Its single-fold measurement decision remains unchanged. diff --git a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.zh.md b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.zh.md index 8656200a4b..b6e2a0b914 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.zh.md @@ -16,7 +16,7 @@ Status: implemented `LlmAdapter.resolveModel(provider, model, signal?)` 返回一条精确路由的聚合元数据,其中可选的 `LlmModelContext` 位于 `context` 字段下。`LlmRuntime.resolveModelInfo()` 选择已注册的路由所属方,验证 `contextWindow` 为正整数,并返回与适配器内部状态分离的元数据。该查询独立于 `listModels()`:不在目录中的动态模型也可以拥有容量元数据,而缺少 `context` 只表示适配器无法描述容量。 -手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。两个内置模型项都公开精确的 256,000 token 容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。 +手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。内置模型项各自声明 1,000,000 token 的上下文容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。 ### Token 计量保持模型无关 @@ -26,17 +26,17 @@ Status: implemented Compact-basic 拥有消费方策略。顶层字段定义默认值;`modelPolicies` 包含以精确 `{ provider, model }` 组合为键的部分覆盖。重复目标、未知字段或无效字段都会让插件加载失败。`thresholdRatio` 默认为 `0.8`,保留策略默认为 `retainRatio: 0.16`;调用方也可以改用绝对 `retainTokens`,但两种保留形式互斥。完成继承后,如果保留比例不小于阈值比例,插件也会加载失败,因为任何模型容量都无法让该策略有效。 -对于主动压力检查,compaction-basic 读取最新持久请求路由,解析其适配器容量与精确目标策略,再把比例缩放为 `ResolvedCompactSpec`。每次检查都会重新解析,因此同一会话切换提供方或模型后,容量与策略会立即变化。若绝对保留预算不小于缩放后的阈值,系统会在目标容量首次允许比较两者时失败。 +对于主动压力检查,compaction-basic 读取最新持久路由,解析适配器容量 `W`、请求输出预留 `O` 和精确目标策略。触发阈值为 `floor(min(W × thresholdRatio, W − O − headroomTokens))`,默认余量为 65,536 tokens。窗口比例限制历史增长,第二项则在一次完整输出后为压缩输入与生成留出额外空间;它无法限制任意工具结果的增长。保留预算独立按 `W − O` 缩放。`O` 取自生效信封的 `maxTokens`,否则取适配器按请求的默认值,再回退到零。每次检查都会重新解析,因此路由或输出上限变化会立即影响策略。若压力预算非正,或保留预算不低于触发阈值,会在目标容量允许比较时失败。 同一精确目标覆盖还可以选择摘要提供方/模型、摘要输出上限、收敛重试次数与溢出重试上限。这些都属于压缩问题,不会进入任何 LLM 提供方。 ### 目标专用压力错误仍保留可选组合 -缺少容量元数据的适配器仍是有效 LLM 路由。手动主动压力检查会返回目标专用配置错误;自动监听器按精确路由只警告一次,并继续保留完整历史。当已解析容量暴露出无效的绝对保留预算时,系统也按路由抑制重复警告;其他运行故障仍会各自对外可见。提供方已经确认的规范化溢出不需要容量元数据:它绕过主动阈值与普通保留预算,尝试一次最大且平衡的缩减,并在替换无法证明进展时保留原始提供方错误。 +缺少容量元数据的适配器仍是有效 LLM 路由。手动主动压力检查会返回目标专用配置错误;自动监听器按精确路由只警告一次,并继续保留完整历史。当输出预留与余量耗尽容量,或保留预算达到触发阈值时,也会抑制重复警告;其他运行故障仍会各自对外可见。提供方已经确认的规范化溢出不需要容量元数据:它绕过主动阈值与普通保留预算,尝试一次最大且平衡的缩减,并在替换无法证明进展时保留原始提供方错误。 ## 测试 -服务测试覆盖与适配器内部状态分离的上下文元数据、无效适配器输出、目录独立性与默认缺失行为。适配器测试覆盖 DeepSeek 的精确容量、默认容量、未列出模型解析及无效容量,以及 pi-ai 的精确描述符解析。压缩测试覆盖比例缩放、精确提供方/模型覆盖、加载期拒绝无效合并比例、运行时校验绝对预算、相同模型 id 的提供方切换、目标专用警告抑制与不依赖容量的溢出恢复。Loader fixture(测试前置数据)会拒绝已经移除的 token-meter 容量设置,示例则在适配器上配置容量。 +服务测试覆盖与适配器内部状态分离的上下文元数据、无效适配器输出、目录独立性与默认缺失行为。适配器测试覆盖 DeepSeek 的精确容量、默认容量、未列出模型解析及无效容量,以及 pi-ai 的精确描述符解析。压缩测试覆盖两项阈值上限、默认和覆盖余量、信封输出上限与适配器回退、独立保留预算缩放、无效预算、精确目标覆盖、提供方切换、警告抑制和不依赖容量的溢出恢复。无需密钥的 `compaction-output-reserve` headless 场景记录非零请求上限、下一步之前的主动压缩和替换后的历史。Loader fixture(测试前置数据)会拒绝已经移除的 token-meter 容量设置,示例则在适配器上配置容量。 ## 考虑过的替代方案 @@ -52,6 +52,6 @@ Compact-basic 拥有消费方策略。顶层字段定义默认值;`modelPolici - 同一个 compaction-basic 实例无需查询发现元数据,就能安全处理不同窗口、提供方切换,以及不同提供方下的相同模型 id。 - 仅 LLM 与仅 meter 的组合仍然有效;加载 compaction-basic 不会让适配器产生反向依赖。 - DeepSeek 部署可以设置精确的逐模型容量,也可以让未提供容量的模型项与未列出的透传 id 使用 `defaultContextWindow`。 -- 比例默认值会随模型自然缩放,同时仍可按精确目标使用绝对保留值,以满足部署专用行为。 +- 比例默认值会随模型自然缩放,并扣除每次请求预留的输出 token;同时仍可按精确目标使用绝对保留值,以满足部署专用行为。 本记录取代[回放式 token 计量服务 Agent Note](../../archived/architecture/2026-07-15-replay-token-meter-service.md) 中的全局容量与无模型策略部分,单折叠计量决策保持不变。 diff --git a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml index 1b91468214..56d5cea939 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.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-identified-immutable-message-values.md -2026-07-28-identified-immutable-message-values.md: 9d2b22a5a1fc87ea7b0ff8eb24b2e8c4b86543e1 -2026-07-28-identified-immutable-message-values.zh.md: 6b36cfec0a24196ca526b0ffb21180967aa640eb +2026-07-28-identified-immutable-message-values.md: 73c9c14479117cf654d10b3da23d155cca77054d +2026-07-28-identified-immutable-message-values.zh.md: 7a5b9d6524beff01ac8a9e2020f75093271b7ab3 diff --git a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.md b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.md index 9d2b22a5a1..73c9c14479 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.md +++ b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.md @@ -16,7 +16,7 @@ This made identity a routing side effect rather than a message invariant. Produc `createMessage(input)` is the canonical role-generic creation boundary. It mints a `MessageId`, detaches the supplied role, content, and source, and deep-freezes the complete value before returning it. `createUserMessage({ content, source })` fixes the user role for prompt and context producers. `createAssistantMessage({ content, source })` fixes both the assistant role and the model source kind, so model-output producers supply only content plus provider, model, and optional replay state. All creation helpers exclude an input id so callers cannot accidentally present creation as import. `freezeMessage(message)` is the separate import or transformation boundary: it detaches and deep-freezes a message whose identity already exists, without minting a replacement. -The message helpers live in `dsh-llm` beside the base message vocabulary because their complete contracts depend only on that vocabulary. They use `dsh-brand`'s stateless `brandString()` constructor for `MessageId` and `dsh-util-values`'s shared `deepFreeze()` implementation after detaching input with `structuredClone()`. `createToolResultMessage()` belongs with the other creation helpers: it couples a tool call id to the exact user-role tool-result block and source without depending on session state or events. `dsh-session` consumes complete messages rather than owning their construction. +The message helpers live in `dsh-llm` beside the base message vocabulary because their complete contracts depend only on that vocabulary. They use `dsh-brand`'s stateless `brandString()` constructor for `MessageId` and `dsh-util-values`'s shared `deepFreeze()` implementation after detaching input with `structuredClone()`. `createToolResultMessage()` belongs with the other creation helpers: it couples a tool call id to a tool-role message carrying the result content, error state, and source without depending on session state or events. `dsh-session` consumes complete messages rather than owning their construction. The `Agent` interface accepts a complete `UserMessage` through `followup`, `steer`, and `inject`. These operations never allocate or return identity; they freeze an imported value whose id the caller already holds. Inbox claims and `agent/pre-step` receive that message directly. A content rewrite creates a frozen replacement with the same id, while an additional context is a separately created `UserMessage` with its own id. diff --git a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.zh.md b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.zh.md index 6b36cfec0a..7a5b9d6524 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.zh.md @@ -16,7 +16,7 @@ harness 曾存在多种形似消息的表示,各自采用不同的标识规则 `createMessage(input)` 是角色通用的规范创建边界。它会生成 `MessageId`,将传入的角色、内容和来源与调用方对象解除引用关系,并在返回完整值前将其深度冻结。`createUserMessage({ content, source })` 为提示词和上下文生产方固定 user 角色。`createAssistantMessage({ content, source })` 同时固定 assistant 角色与模型来源类别,因此模型输出生产方只需提供内容,以及提供方、模型和可选的回放状态。所有创建辅助函数的输入都不包含 id,因此调用方不会意外地把新消息的创建伪装成已有消息的导入。`freezeMessage(message)` 是独立的导入或转换边界:它会将已有标识的消息与调用方对象解除引用关系并深度冻结,不会生成替代标识。 -消息辅助函数位于基础消息词汇旁的 `dsh-llm` 中,因为它们的完整约定只依赖该词汇。它们使用 `dsh-brand` 的无状态 `brandString()` 构造函数生成 `MessageId`,并在通过 `structuredClone()` 分离输入后使用 `dsh-util-values` 的共享 `deepFreeze()` 实现。`createToolResultMessage()` 与其他创建辅助函数同属此处:它将工具调用 id 与确切的 user-role 工具结果块及来源耦合起来,不依赖会话状态或事件。`dsh-session` 只消费完整消息,不负责构造它们。 +消息辅助函数位于基础消息词汇旁的 `dsh-llm` 中,因为它们的完整约定只依赖该词汇。它们使用 `dsh-brand` 的无状态 `brandString()` 构造函数生成 `MessageId`,并在通过 `structuredClone()` 分离输入后使用 `dsh-util-values` 的共享 `deepFreeze()` 实现。`createToolResultMessage()` 与其他创建辅助函数同属此处:它将工具调用 id 与携带结果内容、错误状态及来源的 tool-role 消息耦合起来,不依赖会话状态或事件。`dsh-session` 只消费完整消息,不负责构造它们。 `Agent` 接口通过 `followup`、`steer` 和 `inject` 接收完整的 `UserMessage`。这些操作绝不会分配或返回标识;它们会冻结导入的值,而调用方已经持有该值的 id。inbox 领取和 `agent/pre-step` 会直接接收该消息。改写内容时会创建具有相同 id 的冻结替代值,而每个附加上下文都是单独创建的 `UserMessage`,拥有自己的 id。 diff --git a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml index f5d261079d..cabffaebf6 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.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-29-dsh-source-launch-tsx-esm.md -2026-07-29-dsh-source-launch-tsx-esm.md: 5401b0257faa69eaf76b11e7b51d06afd7ec2b8c -2026-07-29-dsh-source-launch-tsx-esm.zh.md: f32fa3ece91bf7b9829d7012ceb090cabaa833a9 +2026-07-29-dsh-source-launch-tsx-esm.md: 700577cb9b137d67f20baeb639f99f4c0877011d +2026-07-29-dsh-source-launch-tsx-esm.zh.md: 4d322d3dcf49442ee62f82203f17bd2ab16467fe diff --git a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md index 5401b0257f..700577cb9b 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md +++ b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md @@ -14,11 +14,13 @@ Startup latency also mattered: the off-thread `module.register()` hooks worker s ## Decision -The `dsh` TUI, Web, and headless source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. The root `dsh` script uses that vector directly from the repository root; artifact generation is a separate operation under the [source-launch/build separation decision](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md). The CJS hook stays off because the CLI source graph is ESM-only; measured runtime launch to the TUI banner is ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain. +The `dsh` CLI source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. The root `dsh` script uses that vector directly from the repository root; artifact generation is a separate operation under the [source-launch/build separation decision](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md). The CJS hook stays off because the CLI source graph is ESM-only; the implementation-time measurement to the TUI banner was ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain. -`scripts/tspath-loader.ts` and `apps/cli/src/tsconfig-paths-loader.ts` are deleted. With them went the loader's runtime rule of mapping a workspace import only for declared runtime dependencies — tsx applies the `paths` map unconditionally. Declaration completeness now rests on the static gates alone: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. (That runtime rule found real bugs: `dsh-plan-mode` and `dsh-tool-jobs` imported `@deepseek-ai/dsh-llm` while declaring it only in devDependencies; since fixed.) +tsx owns workspace `paths` mapping without checking whether the importer declares each package as a runtime dependency. Declaration completeness rests on the static gates: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. The removed repo-owned paths loaders enforced those declarations at runtime and caught imports of `@deepseek-ai/dsh-llm` declared only in devDependencies; tsx does not provide that check. -The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`): a keyless piped-stdio launch of the exact production runtime vector asserting the non-zero-exit TTY refusal. Any future Node change to module hooks or TypeScript handling turns this gate red instead of breaking developers' `pnpm dsh`. +tsx skips the paths map when the importer URL contains `/node_modules/`. The [runtime resolution](2026-09-09-profile-resolution-generations.md) therefore records real declaring-package anchors, including recursive dependencies. This lets workspace fallback imports resolve to `src/` consistently, rather than mixing built `lib/` providers with source callers whose module-local Symbols differ. Packages without a workspace mapping retain ordinary export resolution. + +The node-compat CI matrix runs `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`) without a build: keyless launches assert the required-profile diagnostic and profile dependency resolution without mixed Tools/AgentLoop `src/` and `lib/` instances. The source and built-bin suites share ordinary-directory and npm-link profile cases; the latter launches plain Node against built exports. The required build-backed smoke also runs `apps/cli/tests/profiles/headless/tests/source-tool.built.e2e.ts`: it checks real headless tool dispatch through tsx after the native addon and generated typert contributors are available, while still requiring core workspace modules to load from source. Future changes to module hooks or TypeScript handling are checked through these real entry paths. ## Alternatives considered @@ -35,4 +37,4 @@ The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (` - One launch vector across the whole engines range, including future Node lines that change native TypeScript support; the smoke gate enforces it per matrix line. - TypeScript transformation is delegated to tsx/esbuild again, reversing the prior note's goal of proving Node-native transformation; that goal is unreachable while vendored sources use non-erasable syntax and Node ships no transform mode. - The runtime declared-dependency enforcement in source launches is gone; undeclared workspace imports now surface only through static gates or built-mode resolution failures. -- Runtime launch improves ~0.4s over the full tsx default; ACP keeps `--import tsx` because its graph was not audited for CJS-hook dependence and its launch latency is not on the interactive path. +- Every CLI source profile uses the same ESM-only hook, including ACP. Workspace runtime entries reached by this launch must expose ESM exports; the launcher does not install a CJS TypeScript transform hook. diff --git a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md index f32fa3ece9..4d322d3dcf 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md @@ -14,11 +14,13 @@ Status: implemented ## 决策 -`dsh` 的 TUI、Web 与无头源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。根目录的 `dsh` 脚本直接从仓库根目录使用同一启动方式;产物生成是独立操作,由[源码启动与构建分离决策](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md)规定。CJS 钩子保持关闭,因为 CLI(命令行界面)源码图是纯 ESM;实测运行时启动至 TUI banner 耗时约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。 +`dsh` CLI(命令行界面)源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。根目录的 `dsh` 脚本直接从仓库根目录使用同一启动方式;产物生成是独立操作,由[源码启动与构建分离决策](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md)规定。CJS 钩子保持关闭,因为 CLI 源码图是纯 ESM;实现时测得启动至 TUI banner 耗时约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。 -`scripts/tspath-loader.ts` 与 `apps/cli/src/tsconfig-paths-loader.ts` 已删除。随之消失的还有该 loader「仅为已声明运行时依赖映射 workspace import」的运行时规则——tsx 无条件应用 `paths` 映射。声明完整性现在仅由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。(该运行时规则确实发现过真实缺陷:`dsh-plan-mode` 与 `dsh-tool-jobs` 导入 `@deepseek-ai/dsh-llm` 却只声明在 devDependencies;后已修复。) +tsx 负责 workspace `paths` 映射,不检查导入方是否把每个包声明为运行时依赖。声明完整性由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。已移除的仓库自有 paths loader 曾在运行时强制这些声明,并发现过将 `@deepseek-ai/dsh-llm` 仅声明在 devDependencies 中的 import;tsx 不提供这项检查。 -node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):以精确的生产运行时启动向量做 keyless 管道 stdio 启动,断言进程会因 TTY 拒绝而以非零状态退出。未来 Node 对模块钩子或 TypeScript 处理的任何改动都会让该门禁变红,而不是破坏开发者的 `pnpm dsh`。 +导入方 URL 含有 `/node_modules/` 时,tsx 会跳过 paths 映射。因此,[runtime resolution](2026-09-09-profile-resolution-generations.zh.md)记录声明包的真实锚点,也覆盖递归依赖。这让 workspace fallback import 一致解析到 `src/`,避免混用构建后的 `lib/` 提供方与模块本地 Symbol 不同的源码调用方。没有 workspace 映射的包仍使用普通导出解析。 + +node-compat CI 矩阵在不执行 build 的情况下运行 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):无密钥启动断言必需 profile 的诊断,以及不混用 Tools/AgentLoop `src/` 与 `lib/` 实例的 profile 依赖解析。源码与构建入口测试共享普通目录和 npm-link profile 用例;后者通过普通 Node 加载构建后的导出。必跑的 build-backed smoke 还运行 `apps/cli/tests/profiles/headless/tests/source-tool.built.e2e.ts`:在 native addon 和生成的 typert 贡献文件就绪后,通过 tsx 检查真实 headless 工具派发,同时仍要求核心 workspace 模块从源码加载。模块钩子或 TypeScript 处理的后续变化由这些真实入口检查。 ## 备选方案 @@ -35,4 +37,4 @@ node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(` - 整个 engines 范围(包括未来改变原生 TypeScript 支持的 Node 版本线)只有一个启动向量;冒烟门禁按矩阵行强制执行。 - TypeScript 转换重新委托给 tsx/esbuild,逆转了前一篇 Agent Note「证明 Node 原生转换可用」的目标;在 vendor 源码使用不可擦除语法且 Node 不再提供 transform 模式的情况下,该目标不可达。 - 源码启动中的运行时依赖声明强制不复存在;未声明的 workspace import 现在只能通过静态门禁或构建模式的解析失败暴露。 -- 运行时启动相比完整 tsx 默认形态快约 0.4s;ACP(Agent Client Protocol)保留 `--import tsx`,因为它的依赖图尚未就 CJS 钩子依赖性做审计,且其启动延迟不在交互路径上。 +- 每个 CLI 源码 profile 都使用相同的 ESM-only 钩子,包括 ACP(Agent Client Protocol)。该启动方式访问的 workspace 运行时入口必须提供 ESM 导出;启动器不安装 CJS TypeScript 转换钩子。 diff --git a/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.i18n.yaml index 4ebcbc33d9..c58f37c2b3 100644 --- a/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md -2026-08-05-profile-plugin-bundles.md: 48786a9c1ccaceb5f16c9eefed01e556cf759e6d -2026-08-05-profile-plugin-bundles.zh.md: c88cbf98e4276549a6fae6a5b1253d3d838a64ca +2026-08-05-profile-plugin-bundles.md: 0607f1c9a2414cc16515967b4520da8a325853fd +2026-08-05-profile-plugin-bundles.zh.md: 9d25d3bea6337c472d161c721e44a66dca8470c9 diff --git a/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md b/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md index 48786a9c1c..0607f1c9a2 100644 --- a/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md +++ b/.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md @@ -14,14 +14,14 @@ Everything becomes a **profile**: a directory `$DSH_HOME/profiles/` with a The default Profile templates use `@deepseek-ai/dsh-base` as the shared core for `web`, `headless`, `sdk`, and `acp`, with one mode bundle above it. The [standalone `sdk-minimal` profile](../../../../packages/bundle/sdk-minimal/README.md) instead lists one bundle that owns its complete explicit tree. Generic `dsh --profile ` hands its remaining arguments to that profile's command-line startup row: Web owns its flag family, headless owns its task positional, and the protocol profiles accept no app options. Patch overlays use launcher-owned `--patch`. A new, non-shipped target can use `--from-default-profile