feat(todo): make the parallel in_progress policy configurable
Whether concurrent active tasks are legitimate depends on runtime concurrency the tool cannot observe, but whether a deployment's agents ever fan out is knowable at composition time. `allowParallelInProgress` (default true) therefore replaces the hardcoded policy: the flag moves the model-facing instruction and the accepted input together, so a deployment running strictly sequential agents can restore the single-active discipline from cordis.yml. The durable-log invariant does not follow the flag. A log written while parallel work was allowed must still replay after a deployment tightens the policy, so the invariant stays silent on the active count.
This commit is contained in:
@@ -10,11 +10,10 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
在所有强制它的位置移除单一 `in_progress` 上限,允许任意数量的任务处于 `in_progress`:
|
||||
把单一 `in_progress` 上限从固定规则改为部署策略,默认允许多个:
|
||||
|
||||
- `packages/todo/tool-todo/src/index.ts` 中的 `execute` 不再统计 `in_progress` 条目;`at most one task may be in_progress` 错误已从工具稳定的失败集合中移除。
|
||||
- `packages/todo/tool-todo/src/invariant.ts` 中的持久日志不变式不再拒绝含多个活跃条目的快照,因此此前持久化的日志不受影响,并行快照也能干净回放。
|
||||
- 工具描述现在指示模型把每个正在处理的任务标记为 `in_progress`(并行工作时可以有多个,顺序工作时只有一个),并在仍有工作未完成时至少保留一个。
|
||||
- `packages/todo/tool-todo/src/index.ts` 新增 `Config.allowParallelInProgress`(默认 `true`)。为 `true` 时,`execute` 接受任意数量的活跃条目,描述指示模型把每个正在处理的任务标记为 `in_progress`(并行工作时可以有多个,顺序工作时只有一个),并在仍有工作未完成时至少保留一个;为 `false` 时,描述要求恰好一个,`execute` 拒绝标记更多的调用。
|
||||
- `packages/todo/tool-todo/src/invariant.ts` 中的持久日志不变式不再拒绝含多个活跃条目的快照,且不跟随该配置,因此此前持久化的日志不受影响,并行快照在任一策略下都能干净回放。
|
||||
|
||||
其余编码的不变式保持不变:`content` 去除首尾空白后非空且唯一、status 为合法枚举值。本决定取代[原始设计的校验决策](2026-06-29-todo-write-tool.md)中「至多一个活跃」的条款;该 Agent Note 的其余部分(整列表替换、日志支撑的状态、单一所有者)依然成立。
|
||||
|
||||
@@ -22,10 +21,19 @@ Status: implemented
|
||||
|
||||
编码的不变式只能看到列表,看不到运行时:两个 `in_progress` 条目是否合理,取决于工作是否真的在并发运行,而这一点工具无法观测。因此,恰恰在并行让上限变得重要的场景里,强制上限反而是错的;任何替代方案(例如把活跃条目数限制为在线 subagent 的数量)都会把工具耦合到它有意一无所知的运行时上。把 `in_progress` 标记与真正并发的工作对应起来这一纪律,转移到工具描述中,也就是排序与列表新鲜度已经所在的地方。
|
||||
|
||||
## 该策略是部署层的选择
|
||||
|
||||
并发的活跃任务是否合理,取决于工具无法观测的运行时并发情况——但一个部署的 agent 是否会并发展开工作,在组装期就是可知的。因此该策略是 `Config` 字段而非常量:`allowParallelInProgress`(默认 `true`)从 cordis.yml 设置,agent 从不并行展开的部署可以恢复单活跃项纪律。
|
||||
|
||||
该开关会同时改变面向模型的指令与接受的输入。把两者拆开才是 bug:描述要求只保留一个活跃任务、而 `execute` 却接受多个,等于教给模型一条工具并不遵守的规则;反过来则会拒绝描述所邀请的调用。描述中只有活跃状态那一句会变化,因为这是该策略唯一改变的指令。
|
||||
|
||||
持久日志不变式刻意**不**跟随该开关。在允许并行时写下的日志,在部署收紧策略之后仍必须可回放,因此把 `invariant.ts` 绑定到当前配置会拒绝在写入当时合法的历史。不变式对活跃数量保持沉默;策略生效之处是工具,时机是写入的那一刻。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **保留上限并增加一个显式的并行 opt-in 标志**——为服务常见场景而给每次调用增加一个额外参数;这个标志对顺序工作而言只是噪声,而且仍然无法验证。
|
||||
- **把活跃条目限制在一个可配置的上限内**——任何固定数字都是任意的,而为列表连贯性设一个随部署变化的可调参数没有原则性价值。
|
||||
- **把活跃条目限制在一个可配置的上限内**——任何固定数字都是任意的。这正是该配置字段是布尔策略开关而非数量的原因:「是否允许多个任务同时活跃」是部署的属性,而「最多 N 个」凭空发明了一个无从论证的阈值。
|
||||
- **把并行策略硬编码**——本分支的第一版就是如此,这也是 `allowParallelInProgress` 之所以必要的原因:运行严格顺序 agent 的部署没有任何办法回到它想要的纪律。
|
||||
|
||||
## 展示面是本次改动的一部分
|
||||
|
||||
@@ -39,4 +47,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
现在 todo 列表可以忠实反映并行执行,并且每个 UI 都能一次渲染多个活跃标记:TUI 按状态区分的前缀无需改动,计划横条的表头会计数活跃条目,工具行则需要上述推导。工具不再拒绝一种此前无效的快照形状,因此该改动兼容此前所有合法的调用;被移除的只是错误路径。面向模型的描述发生了变化,这重新记录了 tool-catalog 页面以及每个带有 todo schema 的 `tool-schemas.expected.json` sidecar(树中八个里有七个)。组合出相同 header 的场景通过 `toolSchemasSource` 共用同一份 sidecar,而非各自保留副本,因此这个数量对应的是不同的 header 组合,而不是场景数;改动工具描述的分支仍须刷新它分叉之后落地的那些 sidecar —— `pnpm run test:snapshot:refresh` 可以无 key 完成。web fixture 的 todo 样本现在有两个条目处于 `in_progress`,因此组装后的 web transcript 回放的是一个并行计划;若任一展示面退回单活跃项推导,它会再次失败。
|
||||
现在 todo 列表可以忠实反映并行执行,并且每个 UI 都能一次渲染多个活跃标记:TUI 按状态区分的前缀无需改动,计划横条的表头会计数活跃条目,工具行则需要上述推导。在默认策略下,工具不再拒绝一种此前无效的快照形状,因此该改动兼容此前所有合法的调用;设置了 `allowParallelInProgress: false` 的部署仍保留旧的拒绝行为,而持久日志不变式两者都接受。面向模型的描述发生了变化,这重新记录了 tool-catalog 页面以及每个带有 todo schema 的 `tool-schemas.expected.json` sidecar(树中八个里有七个)。组合出相同 header 的场景通过 `toolSchemasSource` 共用同一份 sidecar,而非各自保留副本,因此这个数量对应的是不同的 header 组合,而不是场景数;改动工具描述的分支仍须刷新它分叉之后落地的那些 sidecar —— `pnpm run test:snapshot:refresh` 可以无 key 完成。web fixture 的 todo 样本现在有两个条目处于 `in_progress`,因此组装后的 web transcript 回放的是一个并行计划;若任一展示面退回单活跃项推导,它会再次失败。
|
||||
|
||||
Reference in New Issue
Block a user