Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-26-todo-parallel-in-progress.zh.md
T
Chinesezjc 07f9959d55 fix(gui): keep the parallel-active count outside the ellipsized hint
Both todo one-line surfaces truncate the active hint with overflow: hidden
and text-overflow: ellipsis. A "+N" appended to the first active task's name
therefore sat at the far end of the truncatable text, so a long task name or a
narrow viewport clipped exactly the part that reports the other running tasks,
leaving a parallel plan indistinguishable from a sequential one.

planSummary now returns activeContent and activeExtra as separate fields
instead of one joined activeHint, and each surface renders the count in its own
flex: none span beside the ellipsized name: .activeExtra in the collapsed plan
strip header, .extra in the todo_write row. Putting the count in front of the
name was rejected — the task name is what the reader looks for first.

The parallel-plan cases in todo-panel.spec.tsx now assert the count is a
separate element from the name, and both fail if the two are rejoined. The
assembled web snapshot re-records: the flex gap supplies the visual space, so
the transcript reads "实现 fixture 样本+1" with no space in the text nodes.
2026-07-27 15:07:43 +08:00

5.6 KiB
Raw Blame History

Agent Note: 允许同时存在多个 in_progress todo

Status: implemented

English | 中文

问题

原始 todo_write 设计在 execute 和持久日志不变式中都强制每个列表至多一个 in_progress 任务。该不变式假设工作是顺序进行的,但 harness 会运行真正并行的工作(通过委派工具启动的并发 subagent、后台 bash 命令、工作流扇出),而一个只能标出单个活跃任务的列表无法表示这种情况。模型被迫要么把并行任务错误标记为 pending,要么把它们合并成一个含糊的条目,导致 UI 进度清单少报了实际正在运行的工作。

决策

在所有强制它的位置移除单一 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(并行工作时可以有多个,顺序工作时只有一个),并在仍有工作未完成时至少保留一个。

其余编码的不变式保持不变:content 去除首尾空白后非空且唯一、status 为合法枚举值。本决定取代原始设计的校验决策中「至多一个活跃」的条款;该 Agent Note 的其余部分(整列表替换、日志支撑的状态、单一所有者)依然成立。

为何用指引而非感知并行的不变式

编码的不变式只能看到列表,看不到运行时:两个 in_progress 条目是否合理,取决于工作是否真的在并发运行,而这一点工具无法观测。因此,恰恰在并行让上限变得重要的场景里,强制上限反而是错的;任何替代方案(例如把活跃条目数限制为在线 subagent 的数量)都会把工具耦合到它有意一无所知的运行时上。把 in_progress 标记与真正并发的工作对应起来这一纪律,转移到工具描述中,也就是排序与列表新鲜度已经所在的地方。

曾考虑的替代方案

  • 保留上限并增加一个显式的并行 opt-in 标志——为服务常见场景而给每次调用增加一个额外参数;这个标志对顺序工作而言只是噪声,而且仍然无法验证。
  • 把活跃条目限制在一个可配置的上限内——任何固定数字都是任意的,而为列表连贯性设一个随部署变化的可调参数没有原则性价值。

展示面是本次改动的一部分

解除上限使一种此前任何渲染器都不曾收到的列表形状变得可达,因此本分支 stack(栈叠)在 web todo 展示之上,而不是与之并行落地:两者都改 tool-todo,而 GUI 正是并行计划变得可见的地方。web 有两处用 todos.find(t => t.status === 'in_progress') 推导单行摘要——折叠态的计划横条表头与 todo_write 工具行——在旧上限下这个 find 是完备的,因为最多只能有一个条目匹配。一旦有多个活跃项,它会静默丢掉除第一个之外的全部活跃条目:一个四条目、三个任务在跑的计划折叠后只显示其中一个的名字,工具行读作 0/8 已完成 · <一个任务>,而另外七个仍在进行。展开态的列表始终正确(它遍历每个条目),这也是两个 PR 的测试都没抓到它的原因——只有折叠表头与工具行丢失了信息。

现在两处都改用 contract/todo-plan-model.ts 中的 planSummary,即 skeleton 与 toolviews 两个 domain 都可导入的域间共享面。重复的推导正是一处 find 被修好而另一处仍然错误的原因,而计数本来就被算了两遍。提示语给出第一个活跃条目,并计数其余活跃项,因此折叠行报告的是有多少任务在跑,而不是暗示只有一个。列出全部活跃条目被否决了:提示语是紧邻输入框的单行,无上界的拼接会溢出——在列表做不到的地方,计数能够可预测地降级。

planSummary 把任务名与计数作为两个独立字段返回,而不是一个拼好的字符串,因为两处面都用 overflow: hidden / text-overflow: ellipsis 截断该提示。计数接在任务名之后时位于可截断文本的末端,于是恰恰是让计数变得有意义的那些场景——窄视口、长任务名——会把它裁掉,让并行计划看起来与顺序计划无异。因此两处各自把计数渲染在自己的 flex: none span 中,与被省略号截断的任务名并列;共享一个预先拼好的字符串无法表达这个切分,而把计数放到任务名之前也被否决了:读者首先要找的是任务名。

后果

现在 todo 列表可以忠实反映并行执行,并且每个 UI 都能一次渲染多个活跃标记:TUI 按状态区分的前缀无需改动,web 各展示面则需要上述共享推导。工具不再拒绝一种此前无效的快照形状,因此该改动兼容此前所有合法的调用;被移除的只是错误路径。面向模型的描述发生了变化,这重新记录了 tool-catalog 页面以及固定 schema 的组装后快照 transcript(文本记录)。web fixture 的 todo 样本现在有两个条目处于 in_progress,因此组装后的 web transcript 回放的是一个并行计划;若任一展示面退回单活跃项推导,它会再次失败。