DeepSeek Harness goal 机制异常:update_goal 反复失败、stale 循环与无脑催促

故障排查发布于 2026-10-17作者: DeepSeek Plugin 插件市场
DeepSeek Harnessgoal 机制update_goalmax_goal_roundsstale revisionsubagent自治回合
DeepSeek Harness 的 goal 机制有三类问题:模型完成目标时把只适用于 edit 的参数一起带上,导致 update_goal 反复失败;revision 过期后模型握着旧值无限重试;主 agent 在等子代理期间被 goal 反复催促。本文给出参数占位约定、CAS 语义与在途判定三处处置。

DeepSeek Harness(DSH)的 goal 机制是一条「自治回合」链路,它坏起来不是崩溃而是空转:模型完成目标时把只适用于编辑的参数一起带上,update_goal 每次都报 objective and max_goal_rounds are valid only with action edit;版本号过期之后模型握着旧值无限重试,永远撞 stale goal ref;主 agent 明明在等子代理,却被 goal 反复催促、甚至被逼着取消在途子代理。 这三个症状分属三处不同的判定逻辑:参数校验不对称(文本型空串被当未提供、轮次型非零即判失败)、版本比较是 CAS 语义(写错 id 与版本过期共用一个错误码)、以及可驱动判定只看空闲、不看空闲的原因(可继续子代理期间父代理确实「无可见任务」)(#9566、#8955、#9429)。

DeepSeek Harness 参数校验不对称:完成目标时别带编辑专用字段

一句话结论:校验规则两套标准——文本型参数把空串当「未提供」放行,而轮次型参数只要非零就判失败;模型把 get_goal 返回的轮次上限原样回填时,就 100% 撞错。 报错文本是 objective and max_goal_rounds are valid only with action edit(#9566)。

DeepSeek Harness 判据:为什么会「几乎必然踩中」

据报告者观察与另一位用户的会话统计(讨论中给出的量级:123 个会话、64 次调用里 38 次失败,14/14 会话至少失败一次;成功的那 26 次全部是 max_goal_rounds: 0)(#9566):

  1. 模型在完成目标时会倾向「把已知字段都填上」,尤其是刚从 get_goal 拿到的轮次上限;
  2. 校验对轮次字段只判「非零即失败」,于是把合法值原样回填反而变成错误来源;
  3. 报错不给出正确写法,模型无法据此自纠,于是反复重试(#9566)。

DeepSeek Harness 四步处置

  1. 确认判据:只看报错文本即可;如果换成 max_goal_rounds: 0 或干脆不带该字段就成功,即为本条(#9566)。
  2. 加一条项目级提示词规则(当前最有效的办法):要求模型在非编辑动作里不要携带 objective 与 max_goal_rounds;轮次上限只在编辑动作里设置(#9566)。
  3. 验证:重跑同样的完成动作,预期一次成功、不再出现该报错(#9566)。
  4. 上游方向:非编辑动作忽略这两个字段,或把报错改成「给出正确写法」——讨论里明确这两条是修复方向(#9566)。

DeepSeek Harness stale goal ref:这是 CAS 比较,不是「目标不存在」

一句话结论:报错里的 id 与版本号必须完全一致才被接受;写错 id 与版本过期共用同一个错误码,所以从报错本身分辨不出是哪一种——而正确的自愈路径是「从报错里读出现值再重发」。 症状是 update_goal 100% 撞 stale goal ref "…" revision N; current is "…" revision M,模型握着旧值无限重试、不自愈(#8955)。

DeepSeek Harness 机制:两个设计缺陷叠加

据社区逐行核对源码(#8955):

  1. 比较即查找:更新前的检查是对比而不是查找,所以 id 写错和版本过期落到同一个错误码 GOAL_STALE_REVISION;
  2. max_goal_rounds: 0 被静默当「未传」:这会让报错指向一个与真实原因无关的字段,进一步误导模型。

DeepSeek Harness 五步处置

  1. 确认判据:报错里同时出现两个 revision(提交值 vs 当前值);如果模型每次提交的都是同一串旧值,即为本条(#8955)。
  2. 教模型照抄现值自愈:从报错里把 current is "…" revision M 读出来,原样重发一次即可成功——这是最短路径(#8955)。
  3. 加一条项目级硬规则:每一轮更新前先调 get_goal,拿到精确的 id 与 revision 再改;把它写进规则比指望模型自觉稳定(#8955)。
  4. 中间件方案:讨论里给出过一个原型——用 post-execute 钩子把这条报错改写成下一步动作(用 block 返回),从而把「报错」变成「自愈指令」(#8955)。
  5. 验证:重跑同一目标的多轮更新,预期不再出现连续 stale(#8955)。

DeepSeek Harness 无脑催促:判定只看空闲,不看为什么空闲

一句话结论:可驱动判定只判「主 agent 是否空闲」,不判「是否因为派活而空闲」;而可继续子代理运行期间主 agent 整段是空闲、且没有父代理可见的任务条目,于是被判定为「可以驱动」,于是不断催促——严重时会逼主代理取消在途子代理。(#9429) 如果被催促的正是挂上 Agent Teams 后派出的子代理,那么控制与回收还会撞上「工具被 Team 版替换、按名册寻址」那一族坑,见 DeepSeek Harness Agent Teams 冲突:list_agents 同名与子代理停不掉。

DeepSeek Harness 机制与三种派发的差异

据社区逐行核对源码:readyToDrive 谓词只判 agent.status === 'idle';而「可继续」形态的子代理在运行期间不产生父代理可见的任务条目(讨论中对照了另一种实现:正确的做法应在等待子代理时一起等)(#9429)。

  1. 前台一次性:父代理能看到「有任务在跑」,不会被误判空闲;
  2. 后台一次性:同样有可见任务;
  3. 可继续形态:没有可见任务——这是暴露点(#9429)。

DSH插件五步处置

  1. 确认判据:goal 回合在等待子代理期间持续产生催促、上下文里被塞入无意义内容,甚至出现「建议取消在途子代理」这类动作(#9429)。
  2. 规避一(不刚需可继续时):把子代理改成一次性派发,父代理能看到任务在跑,催促自然减少(#9429)。
  3. 规避二(必须保留可继续能力):装社区守卫插件 @argszero/cordis-plugin-goal-progress-guard,它修正了任务调用契约,并利用子代理开始/结束事件维护在途计数(#9429):
bash
npm i @argszero/cordis-plugin-goal-progress-guard
  1. 验证:再跑一次「派子代理 + 等待」的 goal 回合,预期不再出现催促与取消建议(#9429)。
  2. 上游修点:在可驱动判定里增加「是否因派活而空闲」这一个谓词(#9429)。

DSH插件 排查注意事项

goal 这类「空转」问题有个共性:报错文本往往指向错误的字段(stale 指向 revision、valid only with action edit 指向轮次),所以要从「值为什么不同步」去想,而不是从报错字面去修。 八条要点:

  1. stale 是 CAS 比较失败,不是「目标没了」(#8955)。
  2. 从报错里抄现值是最快的自愈:current is … 就是正确的提交值(#8955)。
  3. 把「先取再改」写成硬规则,比依赖模型记忆可靠(#8955)。
  4. max_goal_rounds: 0 会被当未传,这会制造与真实原因无关的报错(#8955)。
  5. 非编辑动作不带轮次参数:这是规避校验不对称的最短路径(#9566)。
  6. 报错不自纠是设计缺口:看到「模型反复重试同一动作」,优先怀疑报错里缺了正确写法(#9566)。
  7. 催促类问题先看派发形态:可继续形态是暴露点(#9429)。
  8. 修复状态如实:三处在讨论所指版本里都未修;空串/零值归一、报错给出正确写法、可驱动判定加谓词都属核心侧改动,用户侧只能靠提示词规则与社区插件兜。

goal 这类自治回合问题,排查时常常要先把「插件与配置」这一层确认下来。与其在各处手敲命令推断,不如用 DSH Plugin Hub 这类可视化插件市场:已安装列表、装卸历史与系统日志集中在一个面板里,先看清环境再动手。

已安装插件列表

来源:Discussion #9566、Discussion #8955、Discussion #9429。

常见问题

DeepSeek Harness 里 update_goal 一直报 objective and max_goal_rounds are valid only with action edit,但我明明只想标记完成,怎么办?

这是 DeepSeek Harness 占位参数约定不清导致的。模型在标记完成时会顺手把只适用于 edit 的参数一起带上(例如把 get_goal 返回的轮次上限原样回填),而校验是对称性不足的:文本型参数把空串当作未提供放行,轮次型参数却只要非零就判失败。处置上先让模型在非 edit 动作里不要携带这两个字段;长期修法是让非 edit 动作忽略它们,或在报错里给出正确写法。

在 DeepSeek Harness 里 update_goal 一直报 stale goal ref 加一串 revision 号,模型拿着旧值无限重试,怎么让它自愈?

在 DeepSeek Harness 里,这条报错是比较不是查找:报错里的 id 与 revision 只有完全一致才会被接受,写错 id 与 revision 过期共用同一个错误码,所以从报错本身看不出是哪一种。可行的自愈路径是从报错中读出现值(current revision)再原样重发,或在每一轮更新前先调 get_goal 拿到精确的 id 与 revision 再改;把它写成项目级规则比靠模型自觉更可靠。

DeepSeek Harness 的 goal 回合在我等待子代理时不停催促,甚至要求取消在途子代理,有办法吗?

在 DeepSeek Harness 里,这是因为驱动判定只看主 agent 是否空闲,不看它空闲的原因。可继续的子代理在运行期间,主 agent 整段都是空闲且没有可见任务,于是被反复唤醒。规避办法是使用会统计在途子代理的社区守卫插件;上游的修点是在可驱动判定里增加「是否因派活而空闲」这一个谓词。

DeepSeek Harness 的 goal 回合里发起的子代理,要不要改成前台执行以避免被催促?

在 DeepSeek Harness 里,三种派发方式受影响程度不同:前台一次性与后台一次性都能被正确观察到,只有可继续形态在「有没有可见任务」这一点上暴露空白,所以它才最容易被误判成主 agent 空闲。如果任务不要求后续继续,改成一次性形态就能显著减少催促;如果必须保留可继续能力,就用守卫插件兜。

在 DeepSeek Harness 里,把 update_goal 的错误写进提示词规则,有用吗?

在 DeepSeek Harness 里有用,而且是当前最稳的做法。因为这两个失败都是「模型手里的值与当前值不同步」导致的,用项目级硬规则要求「先取当前值再更新、非 edit 动作不要携带轮次参数」能直接绕开,不依赖上游修。代价是每轮多一次读取调用。

相关术语

update_goal
update_goal 是 DeepSeek Harness 里更新目标状态的工具,支持编辑目标内容与把目标标记为完成等动作。不同动作接受的字段不同,非编辑动作携带编辑专用字段会被校验拒绝。— https://github.com/deepseek-ai/deepseek-harness/discussions/9566
stale goal ref
stale goal ref 是目标更新被拒时返回的错误,表示提交的目标引用或版本号与当前值不一致。它本质是一次比较失败(CAS 语义),而不是「目标不存在」,因此修法是把当前值取回来再重发。— https://github.com/deepseek-ai/deepseek-harness/discussions/8955
可继续子代理(continuable subagent)
可继续子代理是允许父代理稍后继续与它交互的子代理形态。它在运行期间不产生父代理可见的任务条目,因此在「按是否空闲判断是否可以驱动」的逻辑里,父代理会显得无事可做。— https://github.com/deepseek-ai/deepseek-harness/discussions/9429
goal-round-driver
goal-round-driver 是 DeepSeek Harness 里负责在自治回合中推动目标前进的组件,它按一个可驱动判定决定何时唤醒主代理。当前判定只看主代理是否空闲,未考虑「是否因派活而空闲」。— https://github.com/deepseek-ai/deepseek-harness/discussions/9429

来源