群里有人报了一个导出失败的问题。修复代码跑到一半,GitHub 上又来了一条评审意见;与此同时,群里有人报了另一个故障。如果这三件事分别落在三个聊天窗口里,谁来判断评审意见属于哪次修复,又该通知谁验收?

DeepSeek Harness 团队的 Super-agent 设计探索 把这部分协调交给了系统。Harness 可以理解为模型外面那套接消息、调工具、保存进度的程序。人照常在群聊和 GitHub 上沟通,系统把相关消息归到同一项工作,后台的执行会话则可以各自推进。

多个地方都能找 Agent,后台可以同时做两件事

以导出故障为例。群里提出修复请求,统一接待的 Router 登记这项工作,把查代码和跑测试交给一个 Worker。另一个故障可以分配给另一个 Worker。前者把修复方案做成 PR(供人评审的代码改动),Router 再按渠道回报进展。群里发结论和链接,GitHub 上留验证细节。图中的任务是示例。

群聊与 GitHub 接入同一个 Router,两个独立 Worker 并行处理各自任务。

如果所有工作都挤在同一场会话里,耗时任务会占住处理新请求的时间,故障 A 的日志还会混进故障 B 的上下文。如果每条消息都开新会话,评审者只说一句“先补一个空文件的用例”,新的会话可能连改的是哪个功能都不知道。

Router 保存跨任务需要共享的决定和关联,Worker 在各自的会话里执行具体任务。复杂的修复仍可在一个 Worker 内部再拆给子 Agent;那是在分解一项工作,图里的两个 Worker 则在推进两项独立工作。会话隔离也只隔开模型看到的内容。若两个 Worker 都能写同一文件,还要另外处理写入冲突。

为什么一条 ReAct 循环不够?

ReAct 描述的是一次任务怎样交替进行:先根据已有信息推理,再行动,再用观察到的结果继续推理。站内的 ReAct 图解演示了这个过程。DeepSeek 所说的「单个 ReAct Loop」指所有工作共用一个 Session,在同一条轨迹上串行推进。设想它正在等导出测试:另一项修复也要等;给每项任务另开会话虽能并行,但群里说「按方案 A」,PR 上又说「改成方案 B」,两段会话还得有办法知道它们说的是同一项工作。

图 1a:一条 ReAct 循环缺哪些能力?

串行会话的主要取舍在连续和高效之间。主动和持久还需要外部机制:没有监听器,CI 的失败不会自动唤醒循环;没有持久状态和恢复程序,进程退出就丢了执行位置。一条程序级事件循环仍可以协调多个可暂停的 ReAct 会话:A 在等工具时运行 B,有新事件就收下,并把状态写到外面。DeepSeek 区分的是两种循环的职责。ReAct 负责把一项任务往前推,Harness 负责什么时候启动、切换或恢复哪项任务。

第一条消息要分派,后续评论可以直达

群消息、PR 评论、自动测试结果,进来以后都是事件(Trigger)。Thread 是平台上的一串消息,Ticket 是系统登记的一项工作,Worker Session 是执行这项工作的会话。 Router 接下第一条请求时,创建 Ticket 并登记对来源 Thread 的订阅。后续事件先由来源适配器算出 Thread 键,再由程序查订阅表;这一步无需模型逐句分类。

图 2:同一话题查订阅,新话题看引用

公开原型在飞书适配器里用「群 ID + 根消息 ID」定位话题。同群另发一条顶层消息,即使措辞一样,也会拿到新键。GitHub 适配器面向单个仓库,PR 评论和该 PR 的 CI 结果沿用 PR 编号。原型也能按工作分支等信息绑定 Worker 开出的 PR。绑定完成后,PR 上只说「补个空文件用例」,程序也知道该送到哪里。是否还要额外评估这条事件,则由入口策略决定。

跨 Thread 才需要语义判断。新消息若带上 PR 链接,程序能先查出候选 Ticket;Router 的默认提示词要求有明确引用才把新话题挂上去。主题相似不能当作同一任务。这个要求写在提示词里,模型仍可能误判;同一话题里也会有人插一句无关的话。原型允许 Worker 退回不该处理的事件,或撤销后来挂错的话题。

测试还在跑,群里已经可以说“暂停”

即使只有一个 Worker,长命令也会影响交互。同步调用要等测试跑完才返回;异步调用只先返回“已启动”,测试继续在后台跑。用户的新指令可以在当前推理步骤结束后处理,测试跑完再投递一条结果事件。收到叫停指令时,可以请求停止后台命令;已经写入的文件或发出去的请求,要另行处理。

同步调用等测试结束才返回。异步调用先确认启动,结果随后送回。

因此,十分钟的测试还是要跑十分钟。变化是这十分钟里,执行会话不用一直等在那次工具调用上。跨任务并行与异步工具解决的是两种等待:一个让别的工作有地方执行,一个让正在执行的工作还能接收新指令。

电脑合盖以后,哪些事还能继续?

设计文第 5、6 节把三件事拆开。云上的程序推进任务,数据库保存进度,命令在适合的环境中运行。云上的程序重启时,可以从数据库找回记录;开发机离线时,云上仍能接消息,但依赖那台机器的测试要等它上线。这和 Anthropic 的“脑手分离”设计有相似之处;DeepSeek 还特别讨论了怎样接入会间歇离线、留有未提交代码的个人机器。

云端负责推进,数据库保存进度,实际命令在可用的执行环境运行,独立 Watchdog 检查异常。

进程还活着,任务也可能已经卡住。独立的 Watchdog(巡检程序)定时检查接口和工作进展,例如发现某个 Worker 一直重复同一条失败命令,再尝试介入。恢复任务前,还得确认外部操作做过没有:如果中断前已经开了 PR,接着执行时应该找到它,而不是再开一个。后一点是工程上的恢复要求,不能从“会话状态存入数据库”直接推出。

实验里,分工和直通各省了什么?

原文第 7.1 节用约 300 条合成消息模拟团队的一轮工作,其中约 20 条是任务请求。四种方案各跑 5 次,实际编码用计时工具代替,消息间隔和工作时长按四分之一缩放,模型调用用真实耗时。图里的 p95 可以读作:约 95% 的任务在这个时间以内返回结果。

有直通的双层、无直通的双层、用中性提示的单会话、优先委派的单会话,完成耗时 p95 依次为 15.7、15.5、29.5、29.0 分钟;对应读缓存 token 为 2323、3177、605、1082 万。

在这组负载下,双层方案少了长时间排队的任务,读缓存的 token 也比单会话方案多。去掉订阅直通后,双层方案的完成时间接近,但 Router 要多做约三成决定,总读缓存 token 从 2323 万升到 3177 万。这里的直通主要减少了重复判断。读缓存量不能直接换算成账单;这组实验也没有比较真实修复的正确率。

Trigger 一直来,Router 会不会被撑爆?

会有上限。规则先筛掉无关事件,订阅把可以直通的消息送给 Worker,余下仍要 Router 判断的事件可以攒在同一次模型调用里。原文第 7.2 节用真实模型跑 Router,用模拟程序代替 Worker。需决策的事件从每分钟约 1 条提高到 40 条,平均每批从 1.0 条增到 2.8 条,单次调用从 2.5 秒增到 3.9 秒。40 条是实验施加的到达速率,原文没有测出可长期承载的最大速率。

图 6:Router 少做判断,仍有吞吐和窗口上限

这里有两个不同的「撑爆」。把太多长消息塞进一次请求,会先碰到 token 上限;即使塞得下,任务挤在同一批也可能判错。若每次都只取小批,队列仍可能越积越长。举例说,若每分钟有 60 条需要判断、Router 只能处理 40 条,等候数就会每分钟多 20 条。这是假设数字,和上面的实测速率无关。持久化队列能让积压在重启后继续处理,却不能消掉进出速度之差。

系统跑久了,还有第三个边界:历史记录会增长。Router 可以按 ID 找旧任务,把相关历史留在当前窗口之外;公开原型则明确说旧记录目前不做清理。生产系统还得选择什么时候合并重复状态、优先处理人工叫停、限制新工作进入,或按业务域拆分 Router。拆分能分担流量,跨域的一项工作也更难维持连续;原文第 8.1 节把这件事留作开放问题。

Inline / Spill 是上下文换页吗?

可以用换页来理解。按原文第 3.3 节的定义,Inline 就是把当前用得上的消息、相邻几条回复、摘要或编号直接放进请求;Spill 就是把长内容留在外部,窗口里只放预览和读取入口。有些全文从未进入过窗口,所以 Spill 也包括「一开始就不装进去」。DeepSeek 进一步让一次读取带回下一跳的 ID:从群消息找到话题,再找到处理这项工作的会话,最后定位到 PR 和日志。

内存类比的边界在「缺页」上。CPU 访问的是确定的地址,缺了哪页就取回那页的原字节。模型往往得先判断「只看失败摘要不够,还要看断言附近的原文」,然后按 ID 或搜索定位。它也可能没意识到自己缺了信息。要准确找回历史,还得保存当时的版本:拿今天的构建日志,无法验证昨天那条错误。

图 7:留着全文,只展开当前需要的一段

图里的局部展开和收起是一种设计设想。模型可以请求放大某段日志、缩短另一段背景;Harness 在下一次请求前校验权限、读取原文并按预算重新装配,当前推理中的输入不会改变。窗口快满时,可以先收起较少用的证据,再展开新的片段,任务约束仍留在原处。摘要能换回原文切片,是因为原文另有一份。「全文」也不一定是一份文件,可以是有稳定引用的消息、工具结果和代码版本。

这件事早有人做。MemGPT(2023)让模型通过工具查回换出的旧消息。Claude Code 的 Bash 工具在成功输出过长时就返回预览和文件路径,之后可以按需读。DeepSeek 原型也有按 ID 查询及分页读取;它没有承诺任意历史片段都能自动回到模型窗口。

会话压缩解决的是另一个问题:Claude Code会总结先前对话,再重读少量文件;OpenCode会保留摘要和最近一段消息,更早的消息仍在存储中。Codex 源码也有按 ID 查旧记录的实验机制,公开配置文档仍标为不可用。保留外部历史和提供局部读取是两步。

为什么没有默认把整场对话都做成可任意展开的页面?模型要知道哪段缺了、能定位原版本、还能把它装进有剩余空间的下一轮。做不到第一步,留了全文也不一定会读;每次都先查一遍,又会多出工具往返。Anthropic 的上下文工程说明讨论了这两种代价;其 API 文档也给出了改动旧内容使提示词缓存失效的例子。

站内的 《Context Paging》已把选择、压缩和恢复分开;《上下文管理全景》也区分了截断单条输出、压缩消息历史和外部化。把这个区分用在导出修复上:会话压缩适合保住「任务做到哪了」,局部换页适合核实「那条断言到底是什么」。两者可以共用一份外部历史,窗口仍然保持有界。

可以试的是原型,权限要单独设计

公开的 dsh-superagent-prototype 是面向单个 GitHub 仓库的研究原型,可以选接飞书。它把获准的请求记成工作项,在独立会话和目录中执行。仓库明确说明,公开版经过调整,设计文中的一部分设想尚未在原型里实现。

接入真实环境时,最需要区分的是“收到一条消息”和“得到执行权限”。群里一条反馈可以成为调查线索,里面附带的命令仍是不可信输入。一个实用的起点是允许按规则复现、跑测试和准备改动;合并代码、部署或接触本地凭据则单独设权限。这样,团队少管几个聊天窗口,仍能在关键操作前作决定。

延伸阅读:DeepSeek Harness 的插件机制讨论底层如何装配模型、工具和会话;持久 Agent进一步解释任务恢复时为什么要防止重复执行。