docs(i18n): re-translate RFC batch with the prompt-v4 pipeline

146 篇 RFC 译文按 v4 基线(#348)重出:v4 模板+术语表、金标
few-shot、三段协议、切换行后处理;全量机械核对零异常(一处
task id 术语违规已修)。三篇超长 RFC(code-mode 已入,web-seam/
agent-scope/sandbox/cds-core 仍在长文档通道产出)随后补。
This commit is contained in:
ZiyaZhang
2026-07-22 03:07:36 -07:00
parent 839b88a53a
commit 8ea5cdd894
292 changed files with 2819 additions and 2820 deletions

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-30-pre-tool-input-rewrite.md: add84bfc76434eb25870f09e71860279663d291e
2026-06-30-pre-tool-input-rewrite.zh.md: c636cf42d5c55f0ee1192a46283f8cdbe8c4cadc
2026-06-30-pre-tool-input-rewrite.zh.md: 13d66208be07992fd414f2984c493557ad1e87a3

View File

@@ -1,53 +1,53 @@
# RFC工具执行前输入写——一致性设计
[English](2026-06-30-pre-tool-input-rewrite.md) | 中文
# RFC工具执行前输入写——一致性设计
Status: proposed
[English](2026-06-30-pre-tool-input-rewrite.md) | 中文
## 问题
[拦截 seam RFC](../../implemented/feature/2026-06-30-interception-seams.md) 将 `tools/pre-execute` 定义为一道 allow/deny/ask 门禁,作用于身份已受保护、参数已被深度冻结的执行对象。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的写机制。写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。
[拦截 seam RFC](../../implemented/feature/2026-06-30-interception-seams.md) 将 `tools/pre-execute` 定义为一道针对执行的允许/拒绝/询问门禁,此时执行的身份标识已受保护、参数已被深度冻结。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的写机制。写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。
## 问题本质:执行前参数的三个读取方
在 agent loop智能体循环工具调用的参数在工具执行之前就已提交到日志并被活跃消费方读取:
在 agent loop智能体循环工具调用的参数在工具执行**之前**就已提交到日志并被实时消费方读取:
1. **`assistant/message`** 在工具分发之前追加——它是 `deriveMessages()` 回放时的模型历史来源,因此携带的是模型自身生成的工具调用参数。
1. **`assistant/message`** 在工具分发之前追加——它是 `deriveMessages()` 回放时的模型历史来源,因此携带模型自身输出的工具调用参数。
2. **`tool/call`** 是持久化的审计记录,在 `ctx.tools.execute()` 之前追加。
3. **展示层实时读取 `tool/call.arguments`**ACP 桥接记住这些参数并传给 `presentResult``dsh-tool-bash` 从中派生卡片标题、rawInput、cwd 以及终端/后台处理方式。
3. **展示层实时读取 `tool/call.arguments`**ACPAgent Client Protocol桥接记住这些参数并传给 `presentResult``dsh-tool-bash` 从中派生卡片标题、rawInput、cwd 以及终端/后台处理方式。
如果只做执行层面的UI 会示一条命令而实际运行的是另一条,并且结果会对着错误的参数渲染。注册表目前阻止了这种失败模式:`arguments` 做 structured-clone 并深度冻结,将执行身份属性设为不可写,且不暴露任何可替换它们的测试 shim 或监听路径。写设计必须保持这一受保护的身份边界,而非削弱它。
如果只做执行层面的UI 会示一条命令而实际运行的是另一条,并且结果会对着错误的参数渲染。注册表目前通过以下方式防止这种失败模式:对 `arguments` 做 structured-clone 并深度冻结,将执行身份属性设为不可写,且不暴露任何可替换它们的测试 shim 或监听路径。写设计必须维护这一受保护的身份边界,而非削弱它。
## 提案
写是一「身份构造前的一致性事务」。当钩子提供 `updatedInput` 时,有效值必须在注册表构造不可变的 `ToolExecution` 之前确定,并原子地反映到全部三个读取方:
写是一「身份标识创建前的一致性事务」。当钩子提供 `updatedInput` 时,有效值必须在注册表构造不可变的 `ToolExecution` 之前确定,并且必须原子地反映到全部三个读取方:
- `tool/call` 审计事件记录**写后**的参数(原始参数保留在一个 sidecar 字段中用于审计追踪——钩子改了调用,原始参数生效参数都是值得保留的事实)。
- 派生历史中的 `assistant/message` 必须与实际执行一致——待评估的选项:就地写 assistant 消息中的工具调用块(改变模型「看到自己说过的话」),或记录一条单独的修正下一次请求携带。CC 的模型是让模型看到写已生效。
- 展示层(`presentCall`/`presentResult`)读取写后的参数UI 展示的是实际运行的内容。
- `tool/call` 审计事件记录**写后**的参数(原始参数保留在一个伴随字段中,作为审计线索——钩子改了调用,原始参数生效参数都是值得保留的事实)。
- 派生历史中的 `assistant/message` 必须与实际执行一致待评估的选项:就地写 assistant 消息中的工具调用块(改变模型「看到自己说了什么」),或记录一条单独的修正下一次请求携带。Claude Code 的模型是让模型看到写已生效。
- 展示层(`presentCall`/`presentResult`)读取写后的参数,使 UI 显示实际运行的内容。
`PreToolDecision` 当前的触发点上做扩展不够:此时两条持久化记录已存在,执行身份已受保护。实现必须要么将相关决策移到日志提交之前,要么在待处理的模型调用上增加一个专门的更早期改写决策。当循环将生效参数提交到历史和审计之后,再按常规构造不可变执行对象,并照常运行现有的 allow/deny/ask 与工具流水线。
`PreToolDecision` 当前的触发点上做扩展不够:此时两条持久化记录已存在,执行身份已受保护。实现必须将相关决策移到日志提交之前,或者增加一个专门的更早的重写决策点来处理待定的模型调用。agent loop 将生效参数提交到历史和审计之后,再构造普通的不可变执行对象,并照常运行现有的允许/拒绝/询问和工具流水线。
## 曾考虑的替代方案
### 为什么不直接修改执行对象?
允许 pre-execute 监听器赋值 `exec.arguments` 只能提供执行层面的写,模型历史、审计和展示层不会跟着变。保持身份受保护使得这种局部行为无法被表达。在一致性事务实现之前CC/Codex 桥接对 `updatedInput` 只做日志记录并发出警告,而非声称已兑现;循环分发`TODO(pre-tool-input-rewrite)` 锚定了这个缺失的更早阶段。
允许 pre-execute 监听器赋值 `exec.arguments` 只能提供执行层面的写,模型历史、审计和展示层不会随之改变。保持身份标识受保护使得这种局部行为不可表达。在一致性事务实现之前CC/Codex 桥接对 `updatedInput` 记录日志并发出警告,而非声称已兑现;循环分发`TODO(pre-tool-input-rewrite)` 标记了缺失的更早阶段。
## 验收标准
- 请求的写在 `ToolExecution` 身份创建之前完成解析,并原子地反映到全部三个读取方:`tool/call` 审计记录写后的参数(原始参数保留在 sidecar 字段)、派生历史与实际执行一致、展示层渲染写后的参数。
- 生效的 `ToolExecution.arguments` 在 pre-policy、guards、dispatch、post-policy 和最终观测的全过程中保持深度冻结且不可写;不引入任何可变 shim。
- CC/Codex 桥接兑现 `updatedInput`,不再输出忠实但降级的警告。
- 请求的写在 `ToolExecution` 身份标识创建之前解决,并原子地反映到全部三个读取方:`tool/call` 审计记录写后的参数(原始参数保留在伴随字段)、派生历史与实际执行一致、展示层渲染写后的参数。
- 生效的 `ToolExecution.arguments` 在 pre-policy、守卫、分发、post-policy 和最终观测全程保持深度冻结且不可写;不引入任何可变 shim。
- CC/Codex 桥接兑现 `updatedInput`,不再记录忠实但降级的警告。
## 风险
- `assistant/message` 中的工具调用块会改变模型「看到自己说过的话」;是否有提供方在回放时拒绝这种改,是一个必须在决策形态冻结前通过实验验证的开放问题。
- 更早期的改写阶段改变了 `assistant/message``tool/call`、钩子审计事件与执行之间的顺序关系;设计必须固定这一顺序,同时不削弱轮次封闭性或 call/result 邻接性。
- `assistant/message` 中的工具调用块会改变模型「看到自己说了什么」;是否有提供方在回放时拒绝这种改,是一个需要通过实验确定的开放问题,必须在决策形状冻结之前解决
- 更早的重写阶段改变了 `assistant/message``tool/call`、钩子审计事件与执行之间的顺序关系;设计必须固定这一顺序,同时不削弱轮次封闭性或调用/结果邻接性。
## 开放问题
- `assistant/message` 中的工具调用块是否会破坏某些提供方在回放时的预期?还是记录一条单独的修正更安全?
- 原始参数是否应保留在 `tool/call` 事件(审计)上?如果是,放在哪个字段?
- 写决策是移到日志提交之前,还是成为一个专门的更早 seam现有的 pre-tool allow/deny 钩子如何避免运行两次?
- 这与未来的权限 `ask` 流程(用户批准一个被写的调用)如何交互?
- `assistant/message` 中的工具调用块是否会破坏某些提供方在回放时的预期?还是单独的修正更安全?
- 原始参数是否应保留在 `tool/call` 事件(审计)上?如果是,放在什么字段?
- 写决策是移到日志提交之前,还是成为一个专门的更早 seam现有的 pre-tool 允许/拒绝钩子如何避免运行两次?
- 这与未来的权限 `ask` 流程(用户批准一个被写的调用)如何交互?

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-07-claude-code-and-codex-subagent-backends.md: 1ebf01dd8df0980f6c464be8b27033bdfab942f3
2026-07-07-claude-code-and-codex-subagent-backends.zh.md: 0ed5b42bc9d60b54ac610a8f34f8261f3aeefaed
2026-07-07-claude-code-and-codex-subagent-backends.zh.md: dd26a49962ee46a8ce0965557ff3dbd5805fdcfe

View File

@@ -1,4 +1,4 @@
# RFCClaude Code 与 Codex subagent 后端(进程外委派至外部编码 agent
# RFCClaude Code 与 Codex subagent 后端(外部编码 agent 的进程外委派
[English](2026-07-07-claude-code-and-codex-subagent-backends.md) | 中文
@@ -6,84 +6,84 @@ Status: proposed
## 问题
为 Claude Code 和 Codex 添加隔离的 subagent 提供方。既有的[命名提供方 seam](../../implemented/feature/2026-06-21-subagent-capability-seam.md) 和 [ACP 后端](../../implemented/feature/2026-06-22-acp-subagent-backend.md)已确立了进程边界的形状。harness 的一个轮次应当能够将一个自包含任务委派给上述任一产品,并接收其最终答,同时不暴露父进程的密钥,也不继承来自 `~/.claude``~/.codex` 的宿主配置。
为 Claude Code 和 Codex 添加隔离的 subagent 提供方。既有的[命名提供方 seam](../../implemented/feature/2026-06-21-subagent-capability-seam.md) 和 [ACP 后端](../../implemented/feature/2026-06-22-acp-subagent-backend.md)已确立了进程边界的形状。harness 的一个轮次应将一个自包含任务委派给上述任一产品,并接收其最终答,同时不暴露父进程的密钥,也不继承来自 `~/.claude``~/.codex` 的宿主配置。
##
##
两个兄弟提供方包ACP 后端的结构变体),加一次提取:
- `@deepseek-ai/dsh-subagent-claude-code`:通过 `@anthropic-ai/claude-agent-sdk``query()` 驱动一个 Claude Code 子进程SDK 在父进程中运行,并将其捆绑`claude` CLI 作为子进程 spawn。提供方名称 `claude-code`:子进程是 Claude Code 这个**产品**,而非 Anthropic 模型适配器——"claude" 保留给未来的 `dsh-llm` 适配器。
- `@deepseek-ai/dsh-subagent-codex`spawn `codex app-server`,通过其 JSON-RPC-over-stdio 协议,使用包内一个手写的换行 JSON 客户端(约 200300 行)驱动一个 thread/turn
- `@deepseek-ai/dsh-subagent-process`:纯库(`subagent-inprocess` 先例),提取 `dsh-subagent-acp` 已有且两个新后端都需要的内容:凭证环境清洗(`SENSITIVE_ENV_PATTERN`/`buildChildEnv`、EOF → SIGTERM → SIGKILL 的 dispose 阶梯,以及新的隔离配置目录辅助函数(`mkdtemp` 创建、尽力删除。ACP 后端迁移到该库上;`bash-local` 的兄弟副本保持不动以限制变更范围。
- `@deepseek-ai/dsh-subagent-claude-code`:通过 `@anthropic-ai/claude-agent-sdk``query()` 驱动一个 Claude Code 子进程SDK 在父进程中运行,并将其内置`claude` CLI 作为子进程 spawn。提供方名称 `claude-code`:子进程是 Claude Code 这个**产品**,而非 Anthropic 模型适配器——"claude" 保留给未来的 `dsh-llm` 适配器。
- `@deepseek-ai/dsh-subagent-codex`spawn `codex app-server`,通过其 JSON-RPC-over-stdio 协议驱动一个 thread/turn,使用包内一个手写的换行 JSON 客户端(约 200300 行)。
- `@deepseek-ai/dsh-subagent-process`:纯库(沿用 `subagent-inprocess` 先例),提取 `dsh-subagent-acp` 已有且两个新后端都需要的内容:凭证环境清洗(`SENSITIVE_ENV_PATTERN`/`buildChildEnv`、EOF → SIGTERM → SIGKILL 的 dispose 阶梯,以及新的隔离配置目录辅助函数(`mkdtemp` 创建、尽力删除。ACP 后端迁移到该库上;`bash-local` 的兄弟副本保持不动以限制变更范围。
两个提供方遵循 ACP 后端契约:每次 `start` 创建一个全新子进程、一次 prompt 往返、不继承父上下文也不声明可选能力、忽略 `request.parent``request.agentOptions`、使用随机品牌 agent id。`result` 不 reject子进程失败映射为 stop reason原始错误送入 logger。每个提供方不同的工具名挂载 `dsh-tool-subagent`。工具结果是唯一新增的模型可见产物,因此不需要新的会话事件;工作区变更仍是 transcript 回放之外的环境副作用。
两个提供方遵循 ACP 后端契约:每次 `start` 创建一个全新子进程、一次 prompt 往返、不继承父上下文也不声明可选能力、忽略 `request.parent``request.agentOptions`、使用随机品牌 agent id。`result` 不 reject子进程失败映射为 stop reason原始错误送入 logger。每个提供方不同的工具名挂载 `dsh-tool-subagent`。工具结果是唯一新增的模型可见产物,因此无需新的会话事件;工作区变更仍是 transcript(文本记录)回放之外的环境副作用。
## 已验证的接口事实(固定版本)
两个集成面在本提案之前均已针对固定实现进行了验证——读类型与捆绑源码、运行 keyless spike——而非仅依赖厂商文档。固定版本是验证基线不是运行时契约后端不执行运行时版本探测`codex --version`、无 SDK 版本嗅探)。兼容性在开发时强制执行——每次依赖升级都重跑 keyless 套件以验证真实加载路径——运行时则通过大声失败来保障:协议层的意外通过 `onError` 结算为 `error`,绝不静默异常。
两个集成面在本提案之前均已针对固定版本进行了验证——读类型与打包源码、运行无需密钥的 spike——而非仅依赖厂商文档。固定版本是验证基线不是运行时契约后端不执行运行时版本探测`codex --version`、无 SDK 版本嗅探)。兼容性在开发时强制执行——每次依赖升级都会针对真实加载路径重跑无密钥套件——运行时则通过大声失败来保障:协议层的意外通过 `onError` 结算为 `error`,绝不静默异常。
**`@anthropic-ai/claude-agent-sdk` 0.3.202。** `options.env` 会**替换**子进程环境(不与 `process.env` 合并),这正是清洗所需的行为`settingSources` 默认加载所有文件系统设置——隔离要求显式传入 `[]`。结果子类型为 `success` | `error_during_execution` | `error_max_turns` | `error_max_budget_usd` | `error_max_structured_output_retries`。中止时 SDK 自行升级 CLI 子进程:立即关闭 stdin若子进程忽略则约 2 秒后发送 SIGTERM已观察到无残留进程——无需自定义 kill 回退。`outputFormat: {type: 'json_schema'}``agents` 选项已存在,为 seam 的 `outputSchema` 能力和命名 subagent 类型提供了未来着陆点;者均不在本 RFC 范围内。
**`@anthropic-ai/claude-agent-sdk` 0.3.202。** `options.env` 会**替换**子进程环境(不与 `process.env` 合并),恰好满足清洗需求`settingSources` 默认加载所有文件系统设置——隔离要求显式传入 `[]`。结果子类型为 `success` | `error_during_execution` | `error_max_turns` | `error_max_budget_usd` | `error_max_structured_output_retries`。中止时 SDK 自行升级 CLI 子进程:立即关闭 stdin约 2 秒后若子进程未退出则发送 SIGTERM已观察到无残留进程——无需自定义 kill 回退。`outputFormat: {type: 'json_schema'}``agents` 选项已存在,为 seam 的 `outputSchema` 能力和命名 subagent 类型提供了未来着陆点;者均不在本 RFC 范围内。
**codex CLI 0.142.5`codex app-server`v2 词汇)。** LF 分隔的 JSONJSON-RPC 2.0 形状但省略 `"jsonrpc"` 头。
- 生命周期:`initialize{clientInfo}` + `initialized``thread/start`(接受 `cwd``model``sandbox``approvalPolicy``ephemeral`;未认证即可成功)→ `turn/start{threadId, input:[{type:'text',text}]}` 立即返回一个 `inProgress` 的 turn终止信号是携带 `Turn{status: completed|interrupted|failed|inProgress, error}``turn/completed` 通知。
- 审批服务端发起的请求——`item/commandExecution/requestApproval``item/fileChange/requestApproval``item/permissions/requestApproval``item/tool/requestUserInput``mcpServer/elicitation/request`——以 `accept`/`decline` 系列决策应答。
- 认证:`account/login/start{type:'apiKey', apiKey}` 是一等 RPC`account/read` 报告 `requiresOpenaiAuth`——且未认证的 `turn/start` 不会快速失败(它会挂在重试中),因此后端必须预检认证并大声结算 `error`,而非等待 turn。
- 隔离:`CODEX_HOME` 重定向被尊重(`initialize` 响应会回显它,测试可据此断言隔离),`ephemeral: true` 的 thread 完全不留会话文件。
- 审批服务端发起的请求——`item/commandExecution/requestApproval``item/fileChange/requestApproval``item/permissions/requestApproval``item/tool/requestUserInput``mcpServer/elicitation/request`——以 `accept`/`decline` 系列决策应答。
- 认证:`account/login/start{type:'apiKey', apiKey}` 是一等 RPC`account/read` 报告 `requiresOpenaiAuth`——且未认证的 `turn/start` 不会快速失败(它会挂在重试中),因此后端**必须**预检认证状态,并在失败时大声结算 `error`,而非等待 turn。
- 隔离:`CODEX_HOME` 重定向被尊重(`initialize` 响应会回显它,测试可据此断言隔离),`ephemeral: true` 的 thread 不留任何会话文件。
## 隔离与凭证
认证仅使用 API key。每次运行使用一个全新的配置目录Claude Code 用 `CLAUDE_CONFIG_DIR` 配合 `settingSources: []`Codex 用 `CODEX_HOME`dispose 时尽力删除;配置也可选择一个持久目录。共享的子进程环境辅助函数转发 `PATH``HOME``TMPDIR`、locale代理设置等普通值,移除凭证形的名称,并叠加显式的 `config.env`。Claude Code 通过该叠加接收 API keyCodex 通过 `account/login/start` 接收,而非手写认证文件。
认证方式仅限 API key。每次运行使用一个全新的配置目录Claude Code 用 `CLAUDE_CONFIG_DIR` 配合 `settingSources: []`Codex 用 `CODEX_HOME`dispose 时尽力删除;配置也可选择一个持久目录。共享的子进程环境辅助函数转发 `PATH``HOME``TMPDIR`、locale代理设置等普通值,移除凭证形的名称,并叠加显式的 `config.env`。Claude Code 通过该叠加接收 API keyCodex 通过 `account/login/start` 接收,而非手写认证文件。
## 权限与审批策略
每个后端暴露其引擎原生策略词汇。Claude Code 默认 `permissionMode: default` 配合 `permission: reject`Codex 默认 `sandboxMode: read-only``approvalPolicy: never`,以及相同的拒绝回退。示例可选择启用 `acceptEdits``workspace-write`。已知的审批、用户输入和 elicitation 请求接收配置的应答;未知方法接收 method-not-found未知通知被消费。没有 prompt 到达人类,子进程也不会因等待不可用的输入而无限挂起。
每个后端暴露其引擎原生策略词汇。Claude Code 默认 `permissionMode: default` 配合 `permission: reject`Codex 默认 `sandboxMode: read-only``approvalPolicy: never`,以及相同的拒绝回退。示例可选择启用 `acceptEdits``workspace-write`。已知的审批、用户输入和 elicitation 请求接收配置的应答;未知方法接收 method-not-found未知通知被消费。没有 prompt 到达人类,子进程也不会因等待不可用的输入而无限挂起。
## StopReason 映射
Claude Code`success``completed``error_max_turns``error_during_execution``error_max_budget_usd``error_max_structured_output_retries``error`(与 ACP 对 `max_turn_requests` 的处理对齐:未完成的任务不成功);生成器中止 → `aborted`;未知值 → `error`。Codex`Turn.status` `completed``completed``interrupted``aborted``failed``codexErrorInfo: 'contextWindowExceeded'``max-tokens`,其他 `failed``error`;传输/spawn/认证预检失败 → `error`(若已请求取消则为 `aborted`)。两者中,`cancel()` 采用 ACP 形状:标志位 + abort/interrupt + 一个 cancel-settled 竞争分支,使不合作的子进程无法阻塞结果。
Claude Code`success``completed``error_max_turns``error_during_execution``error_max_budget_usd``error_max_structured_output_retries``error`(与 ACP 对 `max_turn_requests` 的处理对齐:未完成的任务不成功);生成器中止 → `aborted`;未知值 → `error`。Codex`Turn.status` `completed``completed``interrupted``aborted``failed``codexErrorInfo: 'contextWindowExceeded'``max-tokens`,其他 `failed``error`;传输/spawn/认证预检失败 → `error`(若已请求取消则为 `aborted`)。两者中,`cancel()` 采用 ACP 形状:标志位 + abort/interrupt + 一个 cancel-settled 竞争分支,使不合作的子进程无法阻塞结果。
活性姿态明确声明teardown 时序是配置项turn 时长不是。两个后端将 dispose 阶梯的宽限期作为带默认值的已验证配置字段ACP 后端的 `disposeEofGraceMs`/`disposeGraceMs` 形状,由提取库承载),但刻意**不设** turn 时长或启动超时——与 ACP 一致turn 期间的活性由调用方通过 `cancel()`/abort signal 掌控subagent turn 合理地可达数分钟, Codex 认证预检消除了唯一验证的必然挂起场景;需要墙钟上限的部署从父进程取消即可。
活性姿态明确声明teardown 时序是配置项turn 时长不是。两个后端将 dispose 阶梯的宽限期作为带默认值的已验证配置字段ACP 后端的 `disposeEofGraceMs`/`disposeGraceMs` 形状,由提取库承载),但**刻意不设** turn 时长或启动超时——与 ACP 一致turn 期间的活性由调用方通过 `cancel()`/abort signal 掌控subagent turn 合理地可达数分钟, Codex 认证预检消除了唯一验证的必然挂起场景;需要墙钟上限的部署从父取消即可。
## 测试
每个适用层级都要求覆盖:
- **Keyless 单元/集成:** 通过真实 SDK 驱动一个假 Claude CLI通过真实 wire 客户端驱动一个脚本化的 Codex app-server。在逐文件 100% 覆盖率下,覆盖往返、每 stop 映射、两条取消路径及预中止、权限策略、未知消息、spawn 失败、reload 清理、导出形状、清洗后的环境、临时目录删除,以及 Codex 认证预检失败。
- **带 key 的 e2e** 每个真实引擎在 `acceptEdits``workspace-write` 下执行文件操作;跳过时命名缺失的二进制文件或 key,并断言无残留子进程。
- **快照:** `TODO(claude-code-subagent-replay)``TODO(codex-subagent-replay)` 延后,等待 [subagent 回放 RFC](../../implemented/testing/2026-06-22-subagent-snapshot-replay.md) 描述的进程特定回放形状。
- **无密钥单元/集成测试** 通过真实 SDK 驱动一个假 Claude CLI通过真实协议客户端驱动一个脚本化的 Codex app-server。在逐文件 100% 覆盖率下,验证往返、每 stop 映射、两条取消路径及预中止、权限策略、未知消息、spawn 失败、reload 清理、导出形状、清洗后的环境、临时目录删除,以及 Codex 认证预检失败。
- **有密钥 e2e 测试** 每个真实引擎在 `acceptEdits``workspace-write` 下执行文件操作;跳过时命名缺失的二进制或密钥,并断言无残留子进程。
- **快照测试** 标记为 `TODO(claude-code-subagent-replay)``TODO(codex-subagent-replay)` 推迟,等待 [subagent 回放 RFC](../../implemented/testing/2026-06-22-subagent-snapshot-replay.md) 描述的进程特定回放形状。
## 曾考虑的替代方案
### 为什么不用官方 `@openai/codex-sdk` 而手写客户端?
### 为什么不用官方 `@openai/codex-sdk` 而手写客户端?
dispose 阶梯和环境清洗要求拥有子进程spawn 参数、env、信号、exit 等待SDK 隐藏了进程。协议格式极其简单LF JSON形状可按固定版本生成`codex app-server generate-json-schema`仓库先例(`hook-protocol`)是自有精简协议核心而非包装他人运行时。SDK 能节省协议演进的维护成本,但代价是失去本后端存在的意义所在的精确控制。
dispose 阶梯和环境清洗要求拥有子进程spawn 参数、env、信号、exit 等待SDK 隐藏了进程。协议格式极其简单LF JSON形状可按固定版本生成`codex app-server generate-json-schema`),仓库先例(`hook-protocol`)是拥有薄协议核心而非包装他人运行时。SDK 能节省协议演进的维护成本,但代价是失去本后端存在的意义所在的精确控制。
### 为什么不用模型可见的 `subagent_type` 参数(单一 Task 风格工具)?
Claude Code 自的 Task 工具将 subagent 类型放在模型可见的 schema 中,选择一个 prompt + 工具集人格。这里的选择是在**执行引擎**之间做出的,而只有部署者知道哪些引擎配置了凭证——因此选择留在部署配置,保持 `dsh-tool-subagent` 文档化的一提供方一工具契约。人格的类型选择器应是针对工具的独立 RFC而非后端。
Claude Code 自的 Task 工具将 subagent 类型放在模型可见的 schema 中,选择一个 prompt + 工具集人格。这里的选择是在**执行引擎**之间做出的,而只有部署者知道哪些引擎配置了凭证——因此选择留在部署配置,保持 `dsh-tool-subagent` 文档中的「一个提供方对应一个工具契约。人格风格的类型选择器应是针对工具的另一个 RFC而非针对后端。
### 为什么不用登录态凭证和用户自的配置?
### 为什么不用登录态凭证和用户自的配置?
继承 `~/.claude` / `~/.codex`订阅登录、用户设置、skill、MCP 服务器)会子进程行为依赖宿主机状态,并在 ACP 后端和 bash 执行器确立的「凭证通过 `config.env` 显式进入,绝不隐式继承」规则上打一个隐式例外。仅 API key 加强制配置目录隔离保持了运行可复现;需要共享状态的部署可以意将配置目录字段指向一个持久目录。
继承 `~/.claude` / `~/.codex`订阅登录、用户设置、skill、MCP 服务器)会使子进程行为依赖宿主机状态,并在 ACP 后端和 bash 执行器确立的「凭证通过 `config.env` 显式进入,绝不隐式继承」规则上打一个隐式例外。仅 API key 加强制配置目录隔离使运行可复现;需要共享状态的部署可以意将配置目录字段指向一个持久目录。
### 为什么不为 Claude Code keyless 测试注入一个驱动 seam
### 为什么不为 Claude Code 无密钥测试注入驱动 seam
注入一个`query()` 会 mock 我们自己的边界,使真实 SDK 加载路径未被测试docs/testing.md 中的 real-over-mock 策略。曾考虑此方案的风险——SDK↔CLI 的 stream-json 控制协议是内部——已被 spike 消除:假 CLI harness 今天能对真实固定版本的 SDK 工作。如果 SDK 升级破坏了 mockkeyless 套件会让升级 PR 失败,这正是门禁在发挥作用。
注入假 `query()` 会 mock 我们自己的边界,使真实 SDK 加载路径未被测试docs/testing.md 中的 real-over-mock 策略。曾考虑此方案的风险——SDK↔CLI 的 stream-json 控制协议是内部实现——已被 spike 消除:假 CLI harness 今天能对真实固定版本的 SDK 正常工作。如果 SDK 升级破坏了 mock无密钥套件会让升级 PR 失败,这正是门禁在发挥作用。
### 为什么不用 ACP 适配器(如 `claude-code-acp`)复用既有后端?
社区 shim 将两个引擎包装为 ACP这会它们在 `dsh-subagent-acp` 上变成「仅配置」。但这在 harness 与引擎之间插入了一个非官方第三方层,抹了本 RFC 暴露的原生控制面permissionMode、sandboxMode/approvalPolicy、配置目录隔离、apiKey RPC并以 shim 的发布节奏换取第一方协议的稳定性。第一方接口——Agent SDK 和 app-server——才是受支持的集成点。
社区 shim 将两个引擎包装为 ACP这会使它们在 `dsh-subagent-acp` 上变成「仅配置」。但这在 harness 与引擎之间插入了一个非官方第三方层,抹了本 RFC 暴露的原生控制面permissionMode、sandboxMode/approvalPolicy、配置目录隔离、apiKey RPC并以 shim 的发布节奏替换了第一方协议的稳定性。第一方接口——Agent SDK 和 app-server——才是受支持的集成点。
## 验收标准
同时配置了两个引擎和 key 的机器上:一个 REPL 驱动的模型通过 `subagent_claude_code` 完成一个真实文件任务,通过 `subagent_codex` 完成另一个,工具结果为子进程的最终答,父会话日志中仅有 `tool/call` + `tool/result`Keyless 套件在无凭证环境以逐文件 100% 覆盖率通过,断言隔离(清洗后的子进程 env、dispose 后无残留临时配置目录)以及 `~/.claude` / `~/.codex` 的存在与否不影响子进程行为。取消父轮次后两个后端在有界时间内静默无残留子进程。e2e 套件干净地自跳过,命名缺失的前置条件。
两个引擎和密钥均已配置的机器上:一个 REPL 驱动的模型通过 `subagent_claude_code` 完成一个真实文件任务,通过 `subagent_codex` 完成另一个,工具结果为子进程的最终答,父会话日志中仅有 `tool/call` + `tool/result`无密钥套件在无凭证环境以逐文件 100% 覆盖率通过,断言隔离(清洗后的子进程环境、dispose 后无残留临时配置目录),并断言 `~/.claude` / `~/.codex` 的存在与否不影响子进程行为。取消父轮次后两个后端在有界时间内静默无残留子进程。e2e 套件干净地自跳过,命名缺失的前置条件。
## 风险
- `codex app-server` CLI flag 标记为实验性,其 v1/v2 词汇共存;客户端固定 0.142.5、仅实现 v2、消费未知方法/通知而不崩溃,但未来 codex 升级仍可能迫使返工(每次升级重新生成 schema 并重跑 keyless 套件——这是上述「不做运行时版本探测」立场背后的开发时强制执行)。
- Claude Code 假 CLI mock 依赖一个内部协议:任何 SDK 升级都必须通过 keyless 套件,控制协议的破坏性变更意味着返工 mock回退方案上面否决的驱动注入 seam 成为逃生口)。
- SDK 的 optionalDependencies 每平台约 280MB——已接受限制在单个后端包内。
- SDK 的 SIGKILL 分支EOF→SIGTERM 之后)未被观察到,信任其存在e2e 保留无残留进程断言。
- Codex 是部署前置条件(无 npm 捆绑的二进制文件);缺失或不兼容的二进制文件表现为大声的 spawn/协议 `error`,而非版本探测。
- 每次运行付出一个全新子进程的代价,且仅最终答浮出——思考、工具卡片和用量被消费后丢弃;池、中间进度浮出、`sendMessage`/`resume`、通过 SDK 的 `outputFormat` 实现 `outputSchema`、以及通过 SDK 的 `agents` 选项实现命名 subagent 类型,均为刻意延后
- `codex app-server` CLI 标记为实验性,其 v1/v2 词汇共存;客户端固定 0.142.5、仅实现 v2、未知方法/通知消费而不崩溃,但未来 codex 升级仍可能迫使返工(每次升级重新生成 schema 并重跑无密钥套件——这是上述「不做运行时版本探测」立场背后的开发时强制执行)。
- Claude Code 假 CLI mock 依赖一个内部协议:任何 SDK 升级都必须通过无密钥套件,控制协议的破坏性变更意味着返工 mock回退方案上面否决的驱动注入 seam 成为逃生口)。
- SDK 的 optionalDependencies 每平台约 280MB——已接受限制在单个后端包内。
- SDK 的 SIGKILL 分支EOF→SIGTERM 之后)未被观察到,信任其实现e2e 保留无残留进程断言。
- Codex 是部署前置条件(无 npm 内置二进制);缺失或不兼容的二进制大声的 spawn/协议 `error` 呈现,而非版本探测。
- 每次运行付出一个全新子进程的代价,且仅最终答浮出——思考、工具卡片和用量被消费后丢弃;连接池、中间进度浮出、`sendMessage`/`resume`、通过 SDK 的 `outputFormat` 实现 `outputSchema`、以及通过 SDK 的 `agents` 选项实现命名 subagent 类型,均为刻意推迟

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-08-interactive-side-sessions.md: 250a906d9ec339399a0e0e29e70b2b8dc189fa72
2026-07-08-interactive-side-sessions.zh.md: 17d416e2297320e8dfa238569230ecdec91dfa32
2026-07-08-interactive-side-sessions.zh.md: d86a2b69232bc8ccad78555911b44cf727780e0c

View File

@@ -1,41 +1,41 @@
# RFC交互式侧会话与合并回写
Status: proposed
[English](2026-07-08-interactive-side-sessions.md) | 中文
Status: proposed
## 问题
用户可能希望在不改变当前会话主上下文的前提下探索一个问题。现有原语无法提供这种产品形态:[session-store fork](../../implemented/feature/2026-06-30-session-store-fork-api.md) 创建的是一个无关联的会话,而 [fork subagent](../../implemented/feature/2026-06-21-subagent-capability-seam.md) 是模型驱动的任务,其 transcript文本记录会折叠为一条工具结果。两者都不能给用户一个独立的对话也都不能将结论带着来源信息写回父会话。
用户可能希望在不改变当前会话主上下文的前提下探索一个来自活跃会话的问题。现有原语无法提供这种产品形态:[session-store fork](../../implemented/feature/2026-06-30-session-store-fork-api.md) 创建的是一个无关联的会话,而 [fork subagent](../../implemented/feature/2026-06-21-subagent-capability-seam.md) 是模型驱动的任务,其 transcript文本记录会折叠为一条工具结果。两者都不能给用户一个独立的对话也都不能将结论带着出处信息记录回父会话。
## 提案
**侧会话side session** 是一个普通的活跃会话,从源会话最后一个已完成轮次 fork 而来,附属于自己的 agent只读顾问的角色运行,并能**合并回写**一条精笔记。
**侧会话side session** 是一个普通的活跃会话,从源会话最后一个已完成轮次 fork 而来,绑定到自己的 agent定位为只读顾问,并能**合并回写**一条精笔记。
- **Fork 并附属** 以父会话的衡已完成轮次前缀创建子会话,并在其元数据中标记 `parentSession``seedLength`。这组合了 `ctx.agents.create({ seed, meta })`;不新增核心服务或 session-store 方法。
- **顾问框架** 创建后注入一条插件来源的 `context/message`,告知子会话只做解释,不执行变更或继续任务。保持系统提示词逐字节一致,以保留提供方对继承历史的前缀缓存。
- **合并回写:** 向子会话请求一条有长度上限的交还内容,然后向父会话注入一条插件来源的 `context/message`。父会话的下一次请求在其日志位置看到,保持回放与[请求可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md),无需新增会话事件。
- **呈现:** 调用方式、会话切换与交还内容的渲染属于首个客户端拥有的界面。本 RFC 仅规定与界面无关的机制。
- **Fork 并绑定** 以父会话的衡已完成轮次前缀创建子会话,并在其元数据中标记 `parentSession``seedLength`。这组合了 `ctx.agents.create({ seed, meta })`;不新增核心服务或 session-store 方法。
- **顾问定位** 创建后注入一条插件来源的 `context/message`,告知子会话只做解释,不执行变更或继续任务。保持系统提示词逐字节一致,可在继承的历史上保留提供方的前缀缓存。
- **合并回写:** 向子会话请求一条有长度上限的 handback,然后向父会话注入一条插件来源的 `context/message`。父会话的下一次请求在其日志位置看到该消息,保持回放与[请求可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md),无需新增会话事件。
- **呈现:** 调用方式、会话切换与 handback 渲染属于首个客户端拥有的界面。本 RFC 仅规定与界面无关的机制。
回退产品化、会话树视图、面向模型的侧会话工具,以及 `forkName`/`mergedInto` 元数据不在本 RFC 范围内。一次 live-adapter 原型验证了源日志隔离、继承上下文、多轮子会话交互,以及合并回写在父会话下一轮次中的可见性。
回退产品化、会话树视图、面向模型的侧会话工具,以及 `forkName`/`mergedInto` 元数据不在本 RFC 范围内。一次 live-adapter spike 已验证了源日志隔离、继承上下文、多轮子会话交互,以及合并回写在父会话下一轮次中的可见性。
## 曾考虑的替代方案
- **使用 subagent seam** 否决。侧会话是用户驱动的、客户端可见的,且可能父会话的一个轮次存活更久subagent 是模型驱动的运行,返回一条工具结果。
- **修改子会话的系统提示词:** 默认否决,因为任何字节变化都会从第零个 token 起使前缀缓存失效。部署方仍可选择更强的隔离。
- **新增 `sidechat/*` 事件:** 推迟。插件来源的 `context/message`提供持久性、来源信息与回放能力;只有当某个界面需要区分渲染时,专用事件才有正当理由。
- **现在就绑定协议界面:** 否决。当前 UI 由客户端拥有。实时呈现最终必须从持久消息派生,以确保回放渲染出相同的记录。
- **使用 subagent seam** 否决。侧会话是用户驱动的、客户端可见的,且可能存活超过父会话的一个轮次subagent 是模型驱动的运行,返回一条工具结果。
- **修改子会话的系统提示词:** 默认否决,因为任何字节变化都会从第零个 token 起使前缀缓存失效。部署方仍可选择这种更强的隔离方式
- **新增 `sidechat/*` 事件:** 延后。插件来源的 `context/message` 已提供持久性、出处与回放能力;只有当某个界面需要差异化渲染时,专用事件才有正当理由。
- **现在就绑定一个协议界面:** 否决。当前 UI 由客户端拥有。实时呈现最终必须从持久消息派生,以使回放渲染出相同的记录。
## 验收标准
- Fork 不改源会话,创建一个子会话,子会话具有衡的已完成轮次前缀、`parentSession``seedLength`,以及逐字节一致的系统提示词。
- 顾问框架在子会话追加历史的头部恰好添加一条插件来源的 `context/message`,而非修改其系统提示词。
- Fork 不改源会话,创建子会话具有衡的已完成轮次前缀、`parentSession``seedLength`,以及逐字节一致的系统提示词。
- 顾问定位在子会话追加历史的头部恰好添加一条插件来源的 `context/message`,而非修改其系统提示词。
- 合并回写恰好添加一条有长度上限的 `context/message`,来源为 `plugin: sidechat`;父会话的下一次请求与回放在相同位置看到它。
- 父会话与子会话并发运行,日志流之间无串扰。
- 父会话与子会话并发运行,日志流之间无串扰。
- 单元测试覆盖 fork/attach 与合并回写;快照覆盖率随首个绑定界面一起落地。
## 风险
- 只读行为在 `tools/pre-execute` 拒绝门禁强制执行之前仅为建议性[拦截 seam](../../implemented/feature/2026-06-30-interception-seams.md) 可在不改变本机制的前提下添加该门禁。
- 经过压缩compaction的源会话 fork 出的是其压缩视图,因此绑定界面应当告知用户子会话继承的是摘要而非被替换的轮次。
- 反复的交还内容会消耗父会话上下文。每次合并的长度上限约束了单条笔记的大小;后续整合属于压缩的职责。
- 只读行为在 `tools/pre-execute` 拒绝门禁强制执行之前仅为建议性[拦截 seam](../../implemented/feature/2026-06-30-interception-seams.md) 可在不改变本机制的前提下添加该门禁。
- 经过压缩compaction的源会话 fork 出的是其压缩视图,因此绑定界面应当告知用户子会话继承的是摘要而非被替换的轮次。
- 反复的 handback 会消耗父会话上下文。每次合并的长度上限约束了单条笔记的大小;后续的合并整理属于上下文压缩的职责。

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-10-sqlite-session-query-provider.md: 8b67baf420433feca9d5cd09d58852bb9b1545a9
2026-07-10-sqlite-session-query-provider.zh.md: de97e59ac6f2f90a738ff5c8b9c2872d54ba3954
2026-07-10-sqlite-session-query-provider.zh.md: ad6b44363ab54b66b597941adb13938f27971bd5

View File

@@ -6,48 +6,48 @@ Status: proposed
## 问题
精确读取服务 `ctx.sessionQuery` 有意不维护派生索引。大规模持久化的历史记录需要全文搜索,而不能在每次查询扫描所有事件;同时,当前活跃会话需要一个比上次持久性检查点更新的覆盖层。搜索还需要具体的排序、摘要片段、过滤、分页、取消以及重建行为。
精确读取 `ctx.sessionQuery` 服务有意不维护派生索引。大规模持久化的历史记录需要全文搜索,而不每次查询扫描全部事件;当前活跃会话需要一个比上次持久性检查点更新的覆盖层。搜索还需要具体的排序、摘要片段、过滤、分页、取消以及重建行为。
如果把这些关注点拆分到一个推测性的 provider 协调器和一个数据库实现会产生两个耦合的协调状态机。第一个真实实现应当将源观察、提取、SQLite 事务、generation 管理和查询作为一个完整生命周期来拥有。
如果把这些关注点拆分到一个推测性的 provider 协调器和一个数据库实现之间会产生两个耦合的协调状态机。第一个真实实现应当将源观察、提取、SQLite 事务、generation 管理和查询作为一个完整生命周期来拥有。
## 提案
在精确读取包旁新增 `@deepseek-ai/dsh-session-query-sqlite`。该包将暴露一个搜索服务或以其实际消费方所需的最小 API 扩展现有服务族;第一阶段不预先承诺 provider 注册协议。它将依赖 `ctx.sessions` 和可选的 `ctx.sessionPersistence`,拥有一个独立的派生 SQLite 数据库,并复用规范的 `foldSurface()` 分类。
在精确读取包exact-read package旁新增 `@deepseek-ai/dsh-session-query-sqlite`。该包将暴露一个搜索服务或以其实际消费方所需的最小 API 扩展服务族;第一阶段不预先承诺 provider 注册协议。它将依赖 `ctx.sessions` 和可选的 `ctx.sessionPersistence`,拥有一个独立的派生 SQLite 数据库,并复用规范的 `foldSurface()` 分类。
实现拥有一个串行化的协调/数据库事务状态机。一次事务观察权威的持久化元数据和活跃快照,提取语义文档,更新派生表,推进相关的游标 generation并执行或启用应的查询。没有第二个服务维护并行的指纹、脏标记、活跃 ID 集合或失效 generation。
实现拥有一个串行化的协调/数据库事务状态机。一次事务观察权威的持久化元数据和活跃快照,提取语义文档,更新派生表,推进相关的游标 generation并执行或启用应的查询。没有第二个服务维护并行的指纹、脏标记、活跃 ID 集合或失效 generation。
持久化文档在重启后保留。活跃覆盖层是连接局部的,同一会话遮蔽持久化行,在活跃所有者或数据库关闭时消失。派生数据库与规范持久化分离,因此索引重置、损坏、分词器变更和 schema 变动不会危及持久的对话日志。
持久化文档在重启后存活。活跃覆盖层是连接本地的,同一会话持久化行进行遮蔽,在活跃所有者或数据库关闭时消失。派生数据库与规范持久化分离,确保索引重置、损坏、分词器变更和 schema 变动不会危及持久的对话日志。
## 随实现确定的搜索语义
实现必须从可执行的用例出发定义跨会话和会话内两种搜索范围。每个可搜索事件是一个文档包含会话元数据、事件元数据、surface 分类、归一化语义文本和有界的纯文本摘要片段。会话级结果按其最强匹配事件分组;数值化的后端分数保持私有。
实现必须从可执行的用例出发定义跨会话和会话内两种搜索范围。每个可搜索事件是一个文档包含会话元数据、事件元数据、surface 分类、归一化语义文本和有界的纯文本摘要片段。会话级结果按其最强匹配事件分组;数值化的后端分数保持私有。
过滤器在排序之前编译为参数化 SQL。查询语法为数据处理。排序包含稳定的平局字段。不透明游标绑定到归一化的请求形状和最小相关 generation不相关的会话变更不应使会话内游标失效。取消操作必须停止调用方等待并在运行时允许的范围内中断 SQLite 工作。
过滤器在排序之前编译为参数化 SQL。查询语法被视为数据。排序包含稳定的平局字段。不透明游标绑定到归一化的请求形状和最小相关 generation不相关的会话变更不应使会话内游标失效。取消操作必须停止调用方等待并在运行时允许的范围内中断 SQLite 工作。
分词器选择仍是一个实现实验。FTS5 trigram 支持子串召回,但会拒绝短于三字符的有用词项并增大索引体积;提案在将其入契约之前,必须对默认 Unicode 分词器基准测试。
分词器选择仍是实现层面的实验。FTS5 trigram 支持子串召回,但会拒绝短于三字符的有用词项并增大索引体积;提案在将其入契约之前,必须对该权衡与默认 Unicode 分词器进行基准测试。
## 提取与协调
该包首先为消息、推理(reasoning、工具调用/结果、被拦截的提示词、上下文、steering中途引导、待办事项和错误/状态详情提供第一方语义提取。结构性事件和流式分片不贡献文档。未知的声明合并事件/内容类型保持不可搜索,除非有真实的扩展消费方证明需要公开的提取器注册表。
该包首先为以下内容提供第一方语义提取:消息、reasoning、工具调用/结果、被阻止的提示词、上下文、steering中途引导、待办事项和错误/状态详情。结构性事件和流式分片不贡献文档。未知的声明合并事件/内容类型保持不可搜索,除非有真实的扩展消费方证明需要公开的提取器注册表。
协调可以使用稳定指纹来避免重写未变更的持久化会话,但指纹的计算和存储由数据库包拥有。当源观察或提取失败时它绝不能报告某行为最新。provider-schema 不匹配只重置派生数据库;普通的源变更使用事务性 upsert/delete。已挂载但不可读的持久化使受影响的搜索失败但不影响规范写入或已知的活跃精确读取。
协调可以使用稳定指纹来避免重写未变更的持久化会话,但数据库包拥有指纹的计算和存储。当源观察或提取失败时它绝不能报告某行为最新。provider-schema 不匹配只重置派生数据库;普通的源变更使用事务性 upsert/delete。已挂载但不可读的持久化使受影响的搜索失败,但不影响规范写入或已知的活跃精确读取。
## 曾考虑的替代方案
- **规范持久化数据库中添加 FTS 表**:否决可重建的索引不应与权威日志共享 schema/重置/故障边界。
- **在第一阶段重新引入 provider 协调**:否决只有一个计划中的实现,没有证据表明存在稳定的多 provider seam。
- **立即持久化活跃覆盖层**:否决活跃事件在现有检查点提交之前不是规范的。
- **返回 BM25 分数**:否决。提供方特有的数值尺度在语料变化时不稳定。
- **将 FTS 表添加到规范持久化数据库中**:否决,因为可重建的索引不应与权威日志共享 schema/重置/故障边界。
- **重新引入第一阶段的 provider 协调**:否决,因为只有一个计划中的实现,没有证据表明存在稳定的多 provider seam。
- **立即持久化活跃覆盖层**:否决,因为活跃事件在现有检查点提交之前不是规范的。
- **返回 BM25 分数**:否决,因为 provider 特定的数值尺度在语料变化时不稳定。
## 验收标准
- 重启测试覆盖未变更、新增、变更和删除的持久化会话,且不重建整个索引。
- 重新打开时保留持久化行并移除活跃行;活跃行先遮蔽、后显露其持久化基
- 重启测试覆盖未变更、新增、变更和删除的持久化会话,且不重建整个索引。
- 重新打开时保留持久化行并移除活跃行;活跃行先遮蔽、后显露其持久化基
- 测试覆盖两种搜索范围、元数据过滤、surface 默认值、摘要片段、转义、确定性平局、分页、范围内的陈旧游标、取消、动态持久化挂载/卸载,以及事务失败后的恢复。
- schema 不匹配只重置派生数据库。
- 一个 key 的端到端测试将真实的持久化后端与真实的 SQLite 搜索包组合使用。
- 在移 `implemented/` 之前,本 RFC 须修订为实际实现的分词器和公开 API。
- 一个 keyless 的端到端测试将真实的持久化后端与真实的 SQLite 搜索包组合使用。
- 在移 `implemented/` 之前,本 RFC 须修订为实际实现的分词器和公开 API。
## 风险
单一所有者比提供方无关的 seam 更简单,但初期可复用性较低。这是有意为之:第二个真实后端揭示应当抽取什么。SQLite 运行时差异可能影响 FTS 排序和摘要片段,因此测试只能固定契约控制的排序和呈现。独立数据库增加了配置和生命周期工作,但保全了规范存储的安全边界。
单一所有者比提供方无关的 seam 更简单,但初期可复用性较低。这是有意为之:第二个真实后端可以揭示应当抽取什么。SQLite 运行时差异可能影响 FTS 排序和摘要片段,因此测试只能固定契约控制的排序和呈现。独立数据库增加了配置和生命周期工作,但保全了规范存储的安全边界。

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-13-stream-workflow-progress-through-tool-calls.md: 525f2793052a80d82de29d2d370cfd747d002af6
2026-07-13-stream-workflow-progress-through-tool-calls.zh.md: b5cc86df3ca42e513bda7e56b485a8287f275613
2026-07-13-stream-workflow-progress-through-tool-calls.zh.md: 8dcb4aea2de50cdd278c702c9f6c85ab66e6be34

View File

@@ -6,38 +6,38 @@ Status: proposed
## 问题
工作流引擎有意为 run、phase、narration 和子 agent 进度发出成对平衡`workflow/*` 观察事件,但目前没有生产消费方呈现它们。因此编辑器在最终结果到来之前只显示一张 pending 状态的工作流工具卡片,尽管引擎已经报告了当前活跃的 phase、脚本日志内容以及哪些子 agent 已启动或已完成。[dynamic-workflows 决策](../../implemented/feature/2026-07-05-dynamic-workflows.md)明确将 ACP 进度 UI 保留给这事件流。
工作流引擎有意为 run、phase、narration 和子 agent(智能体)进度发出成对的 `workflow/*` observation 事件,但目前没有生产消费方呈现这些事件。因此编辑器在最终结果返回之前只显示一张 pending 状态的工作流工具卡片,尽管引擎已经报告了当前活跃的 phase、脚本日志内容以及哪些子 agent 已启动或已结束。[dynamic-workflows 决策](../../implemented/feature/2026-07-05-dynamic-workflows.md)明确将 ACPAgent Client Protocol进度 UI 保留给这事件流。
如果让 `dsh-acp` 直接监听工作流事件,会反转能力边界:通用的 UI 桥接层将依赖一个可选的工作流包package并对一个工具名做特殊处理。工具流水线已经拥有实时更新所需的路由信息agent 和 call id但只暴露了纯粹的 pending/final 展示器,因此长时间运行的工具没有提供方无关的方式在二者之间报告瞬态 UI 状态。
如果让 `dsh-acp` 直接监听工作流事件,会反转能力边界:通用的 UI 桥接层将依赖一个可选的工作流包package并对一个工具名做特殊处理。工具流水线已经拥有实时更新所需的路由信息agent 和 call id但只暴露了纯粹的 pending/final 展示器,因此长时间运行的工具没有提供方无关的方式在二者之间报告瞬态 UI 状态。
## 提案
`dsh-tools` 添加一条实时进度通道。注册表有的 `ToolExecution` 新增 `reportProgress(view): boolean`,其中 `view` 是一个独立的、提供方无关的通用进度快照,包含可选的替换标题和面向 UI 的内容块。进度不能改调用的 args 派生卡片标签、kind、原始输入、locations、terminal intent 或 diff intent它只更新初选定的展示形式中的实时标题/内容。执行活跃期间,该方法校验并快照 view然后发一个受限的、agent 作用域的 `tools/progress` 观察事件,携带权威的执行标识与快照。一旦 final-result 处理开始,方法返回 `false` 且不再发,确保迟到的异步报告者无法覆盖终态卡片。观察者异常被记录不会导致工具失败。
`dsh-tools` 添加一条实时进度通道。注册表有的 `ToolExecution` 新增 `reportProgress(view): boolean`,其中 `view` 是一个独立的、提供方无关的通用进度快照,包含可选的替换标题和面向 UI 的内容块。进度不能改调用的 args 派生卡片标签、kind、原始输入、locations、terminal intent 或 diff intent它只更新在最初选定的展示方式内的实时标题/内容。执行处于活跃状态时,该方法校验并快照 view然后发一个受限的、agent 作用域的 `tools/progress` observation,携带权威的执行标识与快照。一旦 final-result 处理开始,方法返回 `false` 且不再发,因此迟到的异步报告者无法覆盖终态卡片。观察者异常被记录日志,不会导致工具失败。
`dsh-acp` 以通用方式消费 `tools/progress`。它通过有的 agent-to-session 映射解析执行所属的 agent并为同一 call id 发出一条 in-progress 的 `tool_call_update`。由于报告仅在工具执行流水线内可用,持久化的 `tool/call` 及其 ACP `tool_call` 始终先于第一条 update`tools/result` 之前关闭报告者确保没有进度更新出现在 completed/failed 卡片之后。进度是实时 UI 状态而非模型输入或持久历史:会话回放继续从 `tool/call``tool/result` 重建 pending 与 final 卡片,无需重放瞬态更新。
`dsh-acp` 以通用方式消费 `tools/progress`。它通过有的 agent-to-session 映射解析执行所属的 agent并为同一 call id 发出 in-progress 的 `tool_call_update`。由于报告仅在工具执行流水线内可用,持久化的 `tool/call` 及其 ACP `tool_call` 始终先于第一条 update`tools/result` 之前关闭报告者确保进度更新不会出现在 completed/failed 卡片之后。进度是实时 UI 状态而非模型输入或持久历史:会话回放继续从 `tool/call``tool/result` 重建 pending 与 final 卡片,无需重放瞬态更新。
`dsh-tool-workflow` 成为第一个生产者。每次工具执行在调用 `ctx.workflows.start()` 之前安装一个紧凑的事件捕获器,因为合法的引擎可能在 `start()` 内部同步发出进度。在调用返回之前,捕获器将观察到的事件按 `WorkflowRunInfo.id` 归约为候选状态;随后选取返回的 `WorkflowRun.id`丢弃其他候选报告累积的快照,并将后续匹配事件直接路由。如果 `start()` 抛出异常,捕获器被 dispose其候选被丢弃。这在不向 `WorkflowStartRequest` 添加观察者关联、也不要求进度等到 `start()` 返回的前提下,保持了引擎的可替换性。
`dsh-tool-workflow` 成为第一个生产者。每次工具执行在调用 `ctx.workflows.start()` 之前安装一个紧凑的事件捕获器,因为合法的引擎可能在 `start()` 内部同步发出进度。在调用返回之前,捕获器将观察到的事件按 `WorkflowRunInfo.id` 归约为候选状态;随后选取返回的 `WorkflowRun.id`丢弃其他候选报告累积的快照,并将后续匹配事件直接路由。如果 `start()` 抛出异常,捕获器被 dispose(资源释放),其候选状态被丢弃。这在不向 `WorkflowStartRequest` 添加观察者关联、也不要求进度等到 `start()` 返回的前提下,保持了引擎的可替换性。
归约器消费有的 start、phase、log、agent-start、agent-end 和 end 事件,报告一个替换快照,包含当前 phase、最新日志行、活跃子 agent 标签以及 completed/failed/cancelled 计数。它不累积 narration transcript;已完成的子 agent 离开活跃集合、转为计数。`workflow/end`、工具结算或插件 dispose 移除归约器条目和事件捕获器。六种工作流事件、它们的元数据、成对的子 agent 生命周期、run handle、取消通道和观察者隔离保持不变第三方观察者可继续直接消费它们
归约器消费有的 start、phase、log、agent-start、agent-end 和 end 事件,报告一个替换快照,包含当前 phase、最新日志行、活跃子 agent 标签以及 completed/failed/cancelled 计数。它不累积 narration transcript(文本记录);已结束的子 agent 离开活跃集合,变为计数`workflow/end`、工具结算或插件 dispose 移除归约器条目和事件捕获器。六种工作流事件及其元数据、成对的子 agent 生命周期、run handle、取消通道和观察者隔离保持不变第三方观察者可继续直接消费这些事件
更新工具执行/展示文档、生成的事件与 API 目录、工作流包文档以及工作流数据结构目录。ACP 集成覆盖率必须使用脚本化的模型边界真实的工作流工具和 worker seam 进行测试;主 ACP 快照套件新增一个 workflow-progress 场景,因为此变更改变了面向编辑器的 transcript。
更新工具执行/展示文档、生成的事件与 API 目录、工作流包文档以及工作流数据结构目录。ACP 集成覆盖率必须使用脚本化的模型边界测试真实的工作流工具和 worker seam主 ACP 快照套件新增一个 workflow-progress 场景,因为改变了面向编辑器的 transcript。
## 曾考虑的替代方案
**删除工作流观察面。** 在 [collapse-workflow 简化提案](../../rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md)中被否决:这些事件及其平衡的生命周期是有意设计的,缺失的部分是消费方。
**删除工作流 observation 表面。** 在 [collapse-workflow 简化提案](../../rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md)中被否决:这些事件及其成对生命周期是有意设计的,缺少的是消费方。
**让 ACP 直接了解工作流。** 这可以将 `WorkflowRunInfo` 映射到会话和卡片,但会使通用桥接层依赖一个可选能力,并绕过「工具拥有展示意图」的规则。工具进度通道为所有长时间运行的工具解决了同的路由问题。
**让 ACP 直接了解工作流。** 这可以将 `WorkflowRunInfo` 映射到会话和卡片,但会使通用桥接层依赖一个可选能力,并绕过「工具拥有展示意图」的规则。工具进度通道为每个长时间运行的工具解决了同的路由问题。
**将每进度更新持久化为会话事件。** 这会使实时 narration 可回放,但会用一种权威持久结果已由 tool call/result 对表达的状态永久膨胀日志。如果可恢复的工作流进度成为产品需求,需要一个工作流日志化设计,而非伪装成持久事实的 UI 快照。
**将每进度更新持久化为会话事件。** 这会使实时 narration 可回放,但会用一种状态永久膨胀日志,而该状态的权威持久结果已经是工具调用/结果对。如果可恢复的工作流进度成为产品需求,需要一个工作流日志化设计,而非伪装成持久事实的 UI 快照。
## 验收标准
- `ToolExecution.reportProgress()` 由注册表有、agent 作用域、快照化、观察者隔离,且在终态处理开始后返回 `false` 而不发。
- ACP 将进度路由到正确实时会话中的正确调用;不同会话中的并发工作流不能串扰,且 `tool_call_update` 不会出现在其 `tool_call` 之前或终态更新之后。
- 工作流进度显示当前 phase、最新日志行、活跃子 agent 和结果计数,同时保所有`workflow/*` 事件和 run 语义;一个在 `start()` 内部同步发出 start、phase、log、child 和 end 事件的 seam 测试引擎不会丢失任何归约器状态。
- `ToolExecution.reportProgress()` 由注册表有、agent 作用域、快照化、观察者隔离,且在终态处理开始后返回 `false` 而不发。
- ACP 将进度路由到正确实时会话中的正确调用;不同会话中的并发工作流不能串扰,且 `tool_call_update` 不会出现在其 `tool_call` 之前或终态更新之后。
- 工作流进度显示当前 phase、最新日志行、活跃子 agent 和结果计数,同时保所有`workflow/*` 事件和 run 语义;一个在 `start()` 内部同步发出 start、phase、log、child 和 end 事件的 seam 测试引擎不会丢失任何归约器状态。
- 取消、worker 死亡、工具失败、会话关闭和插件 dispose 释放归约器状态;回放仅发出持久的 pending/final 卡片对。
- 单元测试、工作流集成测试、ACP 集成测试、快照、类型检查、覆盖率、doc-sync、module-graph、构建和 hygiene 门禁全部通过。
## 风险
此变更向工具 seam 添加了一个公开的实时进度方法和事件,因此实现方必须精确维护 active/terminal 边界并在观察者看到快照之前将其分离。pre-start 捕获器可能短暂观察到无关的工作流 run因此它仅按 run id 持有紧凑的候选状态,并在 `start()` 返回后立即丢弃所有不匹配的候选。一个工作流可能发出大量进度变更;有界归约器避免了 transcript 增长,但在关联后仍会为每个有意义的事件发送一 UI 更新。如果实测客户端需要合并更新,必须通过带默认值的、经过校验的桥接配置实现,而非硬编码的节流。瞬态进度在回放时有意消失,因此最终工具结果仍是唯一持久的工作流卡片内容。
本提案向工具 seam 添加了一个公开的实时进度方法和事件,因此实现方必须精确维护 active/terminal 边界并在观察者看到快照之前将其分离。pre-start 捕获器可能短暂观察到无关的工作流 run因此它仅按 run id 持有紧凑的候选状态,并在 `start()` 返回后立即丢弃所有不匹配的候选。一个工作流可能发出大量进度变更;有界归约器避免了 transcript 增长,但在关联完成后仍会为每个有意义的事件发送一 UI 更新。如果经测量的客户端需要合并更新,必须是一个带默认值的、经过校验的桥接配置,而非硬编码的节流。瞬态进度在回放时有意消失,因此最终工具结果仍是唯一持久的工作流卡片内容。