首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >DeepSeek Harness 设了目标,agent 为什么不动?不是坏了,是没被授权继续跑

DeepSeek Harness 设了目标,agent 为什么不动?不是坏了,是没被授权继续跑

原创
作者头像
术哥
发布2026-09-12 10:09:39
发布2026-09-12 10:09:39
1540
举报
文章被收录于专栏:运维有术运维有术

🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 204 篇,DeepSeek Harness最佳实战「2026」系列第 07

大家好,欢迎来到 术哥无界 | ShugeX | 运维有术

我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者

Talk is cheap, let's explore。无界探索,有术而行。

封面:DeepSeek Harness · 不是坏了,是没被授权继续跑
封面:DeepSeek Harness · 不是坏了,是没被授权继续跑

01 设了目标,agent 为什么不动?

你在 DSH 里给 agent 设了个目标,它跑了几轮,停了。你重启会话,目标还在(状态栏明明写着 active),agent 却一动不动。再重启一次,还是一样。

我翻源码(packages/goal)之前,会以为这是 bug。读完源码,确定不是 bug。

glossary.zh.md 里有一句话,是整个 Goal 机制的钥匙:“目标是一种状态,不是调度器,也不是一段独立对话;会话日志仍是其真源。”

翻译成白话:你设的“目标”不是一段会自己跑的程序,它只是一条被记录下来的状态;真正让 agent 干活的是另一个东西:调度器。在 DSH 里,调度器是一个独立的包,goal-round-driver,整个 goal 域里唯一“干活”的包。goal 包本身只存状态、不调度,文档里白纸黑字:“自动续行必须单独启用。”

这句话拆开,是两根正交的开关:

  • 持久 phaseactive / paused / blocked / complete 四态,写进会话日志,会一直活着;
  • 进程本地 activationarmed / disarmed,只活在当前进程里,绝不持久化。

关键在这根 activation。源码里,任何会话开始(agent/session-start)一律执行 disarm(index.ts 第 255–257 行)。也就是说:只要你重启了会话(不管目标是 active 还是 paused),自动续行的权限都被清零。agent 不动,不是它坏了,是它没被授权继续跑。要重新跑起来,唯一路径是人类授权 resume(/goal resume,或模型工具 resume)。

这里有个很容易误读的地方:goal 的 create 源码上来就是 armed,看起来跟“自动续行必须单独启用”打架。不打架。因为 create 有个硬前提:当前轮次必须有直接的人类消息(requireDirectHuman)。create 的 armed,等于“人类显式创建即授权”:你亲手创建目标的那一刻,就是授权;而你重启会话,不是授权。

这个设计,我概括为:DSH 的 Goal 是安全优先于顺滑的。被否决的方案里就有一条,激活不持久化:宁可让你每次恢复后多按一次 resume,也绝不让 agent 在你看不见的地方自己续命。

02 DSH 到底怎么让 agent 持续干活?

02 DSH 到底怎么让 agent 持续干活?
02 DSH 到底怎么让 agent 持续干活?

顺着这个疑问往下挖,会撞上 DSH 一个更大的姿态:它不做一个“万能续行器”,而是给持续干活准备了三套并列的机制。

  • Goal:同一会话里续跑(刚说的那个);
  • Ralph:全新尝试,把活交给一个不记得上次对话的新 agent 接着干;
  • Workflow:脚本编排,模型写一段脚本,一次扇出几十上百个子 agent。

Goal(同会话续跑)

Ralph(全新尝试)

Workflow(脚本编排)

解决什么

目标别忘、会话别断、轮次有预算

上下文污染、agent 越做越糊涂

大扇出、多 agent 并行编排

怎么“续”

同一会话内排下一个 Goal Round

全新子会话,无对话种子,只给上一份有界报告

不“续”:脚本一次编排完

代价谁承担

人(恢复后须授权 resume)

遗忘(未提交的推理消失)

模型当脚本作者 + 每次运行付一个 worker 线程

读这张表能看出一件事:可以把这三套机制看成持续干活的三个正交问题:延续对话、延续工作区、延续编排。判据一句话:先问你想让什么活下来。对话记忆要活下来,用 Goal;工作区成果要活下来,用 Ralph;并行的编排结构要活下来,用 Workflow。看完这篇,你能回答什么时候用哪个。

三套机制,各拆一层。Goal 最细,因为它把续行的讲究全写进了代码。

03 Goal:同会话续跑,但每一步都要人兜底

03 Goal:同会话续跑,但每一步都要人兜底
03 Goal:同会话续跑,但每一步都要人兜底

先锚定共享术语。glossary 定义 Goal Round:“为当前目标接纳的一次续行周期。同会话驱动器将 Goal Round 具体化为一个由目标触发的轮次,其中可包含零个或多个步骤;同一会话中无关的人类轮次不消耗 Goal Round 上限。”注意两个词:同会话、人类轮次不算数。

Round 计数器归该策略所有,不统计会话里的每个轮次;这个“计数归属”的设定,Ralph 一节还会遇到第二次。

Goal 的状态机很简单:四态加七个动词(create/edit/pause/resume/complete/block/clear)。挑三个关键的说:

  • pause:只有 active 能进 paused,同时激活 disarm,停了就是真停;
  • block:只有 active 能进 blocked,阻塞原因写成一个稳定 code。任何停摆原因(预算耗尽、执行错误、要人输入)共用这一个 phase,不扩生命周期状态,“为什么停”交给 code 字段;
  • resume:可以恢复 active/paused/blocked 三态,但耗尽了额度就拒绝。

真正干活的是 driver。它零配置:上限在 goal、阻塞阈值在 tool-goal,“在驱动器中重复任一数值都可能产生分歧策略”,所以这些数值不在 driver 里重复。driver 的调度链路一句话讲是先预留、后准入

  1. agent 空闲、目标 active 且 armed、还有额度时,driver 预留下一个 Goal Round 编号,把带轮次信息的提示词排进会话队列;
  2. agent 真正要进入这一步时,再核对预留是否有效:claim 了、没被修订顶掉、内容一字不差;
  3. 被拦下的轮次(中途改了目标、预留过期)不消耗 Round 编号。

这条链的价值在防护:额度是硬的。默认 maxGoalRounds = 256,而且只计“已准入进入步骤的 goal 轮次”,不计量 token、货币、时间:人类消息不算,被 reject 的轮次不算。跑了很多轮、roundsStarted 却不大?很正常。我倾向于把它理解为“模型历史预算的代理量”。

会话日志是唯一真源。goal 的每次变更追加一条持久事件(goal/change),回放时严格校验:revision 连续、相位转换合法、轮次编号连续;任何一帧损坏,goal 访问整体拒绝,而不是跳过。这里藏着开头的伏笔:投影只反映持久 phase,activation 刻意不出现在投影里:“回放出 active”不等于“允许自动续行”

还有一处不对称,最能看出这块设计的性格:模型不能 resume 一个 paused 的目标。工具层直接拒绝(错误码 GOAL_TOOL_RESUME_PAUSED,原文:“the model cannot resume a paused goal; the user must resume it”)。paused 只属于人的恢复路径。人可以直接 pause(仅当 goal 处于 active 态)或 clear(任意非 complete 当前 goal),且都不受“连续 3 轮”机械阈值约束;相比之下,模型自主收尾里的“报阻塞”则有机械下限:连续 3 轮之前,GOAL_TOOL_BLOCK_THRESHOLD 直接拒绝。

Goal 这一节的代价小结:它的续行完全建立在人授权上,恢复/fork 后要人 resume,paused 只能人恢复,异常不隐式重试(“暂时性的提供方与持久化失败……由用户授权 resume,而不会采用隐式重试策略”)。我把整套设计概括成一句话:安全优先于顺滑。

goal 域四包(goal / tool-goal / command-goal / goal-round-driver)当前都是 0.1.5-rc.2;Workflow 侧各包的版本号,这篇就不逐一标注了。

04 Ralph:续行,但忘掉一切重来

04 Ralph:续行,但忘掉一切重来
04 Ralph:续行,但忘掉一切重来

如果问题正好反过来呢?

Goal 的同会话续跑,隐含一个前提:agent 不能忘事,不能被自己的上下文污染。可现实里另一类问题恰恰相反:agent 越做越糊涂,几轮对话之后,它把上一轮的猜测当成了事实。这时候你想要的,是一个不记得上次对话的 agent。

Ralph 就是干这个的。glossary 定义得很硬:Ralph Round 是“Ralph 循环中的一个全新子会话。子会话不接收父会话或此前子会话的对话种子”。

注意这与 Goal 是镜像关系:Goal Round 是同一会话里排下一轮,Ralph Round 是每轮一个全新子会话。每个 Round 的提示词自己就写着:“你收不到父对话、收不到先前子会话。”(译自 RALPH_SCRIPT 提示词原文)

那跨 Round 的东西怎么传?两样:共享工作区(长期记忆和真源,所有轮子共用);外加一份有界的 Ralph 交接,一份规范化报告,含状态、摘要、证据、后续步骤、阻塞说明,上限 16384 字符,五个字段全部必填、不许出现额外字段。glossary 有一句定权的话:它“补充共享工作区,而不取代工作区的权威地位”。

这里值得停下来看一个设计:Ralph 的循环不是模型写的,是部署方拥有的固定脚本(一段经 ctx.workflowEngine.start 提交、挂在 workflow 引擎上的脚本循环),glossary 特意称它为工具策略、而非通用工作流脚本功能。模型只提供客观数据(objectivemaxRounds),改不了循环、改不了 schema、改不了提供方路由;工具文档明说,它“不会向 agent-loop 添加 Ralph 模式或全新 agent loop,同会话的 goal 领域也保持独立”。与普通 workflow 的差异,在脚本所有权(部署方 vs 模型)和子 agent 形态(每轮恰好一个全新子 agent、无扇出 vs 自由编排扇出)。

代价是什么?Ralph 的已知限制里有一句很坦率:“每个子 agent 结束后,未提交的对话推理都会消失。”完成和阻塞都是 worker 自己报的。渲染措辞都刻意用“自报”:“Ralph worker reported completion after N rounds.”没有独立评估器来验证“你真的完成了”。失败不重试:一轮子 agent 失败,直接返回失败轮次和上一份成功交接,到此为止。还只能前台跑:没有后台收集、没有检查点、没有恢复。

所以 Ralph 的续行逻辑是反直觉的:续行的是工作区,不是记忆。忘了上次说了什么,但上次做出来的东西还在工作区里;下一轮的全新 agent 对照工作区干活,上一份报告只是“仅供参考、以工作区为准”的交接。

一个数字:maxRounds 默认 256,而且 256 就是部署上限:它被直接塞进引擎的子 agent 总数闸(maxTotalAgents)里,模型传的值不能抬高它。有意思的是,Goal 的默认额度也是 256。两个数字分别来自两套机制的素材,“这 256 和那 256 是不是同一个预算哲学”——我倾向于认为这不是巧合,但素材里也明说了它可能是巧合。

05 Workflow:模型写脚本,脚本只编排

05 Workflow:模型写脚本,脚本只编排
05 Workflow:模型写脚本,脚本只编排

再换个问题:如果这活天生就该并行扇出呢?几十个文件要审计、一个迁移要拆成一百个独立步骤,一轮轮续,反而低效。

Workflow 的姿态最激进:让模型自己写编排脚本。模型通过 workflow 工具,把一段纯 JS 脚本作为字符串提交上去,meta、args 是普通 JSON 数据。工具描述里写着它的场景:“对很多独立片段做扇出——审计大量文件、迁移、多角度研究、对发现的对抗式验证——你把编排写成脚本,而不是一轮轮委派。”(译自工具 DESCRIPTION)

这段脚本权力很大,边界也很硬:

  • 引擎“绝不会通过对脚本文本求值来获取” meta/args:它们是数据,不是代码;
  • 脚本里没有文件系统、没有网络、没有 timer、没有 Node.js API。能力约束那句原文是:“the agents do the work, the script only coordinates them”,干活的是 agent,脚本只做协调。

有一个细节特别能说明“源码比文档更诚实”:源码里专门加了一段检测,脚本正文如果以 export const meta 开头,直接报错并指出正确写法。注释原文:这是“模型最可能犯的写作错误”(the model's likeliest authoring slip)。模型一边当脚本作者,一边最可能把不该写进脚本的东西写进脚本:系统提前在那儿等着它。

脚本被装进一个 worker_threads 隔离舱里跑。这里必须划清一个边界,因为几乎所有做 agent 框架的人都会想当然:这个隔离是可用性隔离,不是安全边界。 文档原话:“这种隔离可以限制可用性故障,但不是安全边界;真正不可信的脚本需要独立进程或容器。”

隔离舱的实际价值有两个:一是脚本死循环、同步自旋,不会卡住 harness(它在独立 worker 线程里烧自己的 CPU);二是脚本哪怕无视取消信号,若在宽限内不自行结算,则会在 disposeGraceMs(5000ms)后被无条件 worker.terminate() 终止,等待结果的调用方绝不会无限挂起。环境凭证过不去:worker 以清洗后的环境启动,env 里的凭据不会通过 process.env 跨界。一个失控的循环还有后备闸:单次运行子 agent 总数上限默认 1000,模型传的值只能调低、绝不能提高

代价同样明确:“每次运行支付一个 worker thread”,没有连接池、没有预热、没有跨运行缓存。

也没有跨运行状态:仅前台、无检查点、无恢复,连发布给观察者的事件里都刻意不携带结果值。

同步拒绝与结果结算的分界线,就一个:run 是否已经存在。四类前置错误(meta 无效、脚本解析失败、提供方没注册、上限非法)在 worker 创建之前同步抛出;一旦 run 已经返回,之后所有失败都走 result.stopReason 结算(completed / cancelled / error),result 绝不无限挂起。引擎 start() 的 JSDoc 原话是:“Throws WorkflowError synchronously (META_INVALID for a malformed meta block, SCRIPT_PARSE for a body that does not compile) for a request that cannot begin; once a run is returned, every failure resolves through result.stopReason instead.”

Workflow 的“不续”姿态同样明确:工具阻塞父轮次直到整场工作流结算,模型只看到最终结果,“永远不会看到中间子 agent 消息”。

06 判据:先决定让什么活下来

三套机制拆完了,回到开头的判断:持续干活不是机制问题,是决策问题。决策的判据一句话:

你想让什么活下来?

  • 对话记忆要延续:Goal。目标别忘、会话别断、轮次有预算,每一步由人兜底授权;
  • 工作区成果要延续:Ralph。忘掉对话,只续成果,每轮全新视角;
  • 并行的编排结构要延续:Workflow。脚本一次编排完,模型当作者,worker 当隔离舱。

反过来说,各机制有明确的别碰场景(证据都来自系统提示词和 README):

  • Goal:paused 只能人恢复,模型 resume 会被拒(GOAL_TOOL_RESUME_PAUSED),别指望模型自己把停掉的目标拉起来;
  • Ralph:需要连续推理、上下文依赖的活别用,无种子即无记忆,上一轮说过的话这一轮真的不存在;
  • Workflow:一两项委派别用,系统提示词明说“For one or two delegations, prefer plain subagent calls”,为两件小事付一个 worker 线程不值得。

还有一个读者必然会问的排除项:plan/todo 是不是第四种续行机制?不是。 plan 是执行前的人类审批门,它只改变每次请求里带不带一段规划指令,批准后追加一条只读状态事件,不启动任何子 agent、不驱动任何循环、不创建任何 worker;todo 是进度可见性与并行纪律,整表替换、日志落盘,不驱动执行、不阻塞步骤。两者都不拥有继续干活的调度权。

最后落到架构。这三套机制在代码里是并列的独立插件:goal 域对 plan/todo 零引用(grep 证据);tool-ralph 文档明说“同会话的 goal 领域也保持独立”;goal-round-driver 明说“不做 Ralph 式独立尝试(该工作流属于单独的插件层)”。可以说,复杂度没有集中在一个单一调度器里,而是分摊在插件里,每个插件各自把边界写死。

文档里有一句系统的自白:“这种隔离可以限制可用性故障,但不是安全边界。”文档写得含蓄,源码很诚实。DSH 的三套持续干活机制也是这样:每一套都坦率地标好了自己的代价,没有一套假装自己能在无人看管的情况下一直干到完成。

好啦,谢谢你观看我的文章,如果喜欢可以点赞转发给需要的朋友,我们下一期再见!敬请期待!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 01 设了目标,agent 为什么不动?
  • 02 DSH 到底怎么让 agent 持续干活?
  • 03 Goal:同会话续跑,但每一步都要人兜底
  • 04 Ralph:续行,但忘掉一切重来
  • 05 Workflow:模型写脚本,脚本只编排
  • 06 判据:先决定让什么活下来
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档