DeepSeek Harness goal 机制异常:update_goal 反复失败、stale 循环与无脑催促
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):
- 模型在完成目标时会倾向「把已知字段都填上」,尤其是刚从
get_goal拿到的轮次上限; - 校验对轮次字段只判「非零即失败」,于是把合法值原样回填反而变成错误来源;
- 报错不给出正确写法,模型无法据此自纠,于是反复重试(#9566)。
DeepSeek Harness 四步处置
- 确认判据:只看报错文本即可;如果换成
max_goal_rounds: 0或干脆不带该字段就成功,即为本条(#9566)。 - 加一条项目级提示词规则(当前最有效的办法):要求模型在非编辑动作里不要携带
objective与max_goal_rounds;轮次上限只在编辑动作里设置(#9566)。 - 验证:重跑同样的完成动作,预期一次成功、不再出现该报错(#9566)。
- 上游方向:非编辑动作忽略这两个字段,或把报错改成「给出正确写法」——讨论里明确这两条是修复方向(#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):
- 比较即查找:更新前的检查是对比而不是查找,所以 id 写错和版本过期落到同一个错误码
GOAL_STALE_REVISION; max_goal_rounds: 0被静默当「未传」:这会让报错指向一个与真实原因无关的字段,进一步误导模型。
DeepSeek Harness 五步处置
- 确认判据:报错里同时出现两个 revision(提交值 vs 当前值);如果模型每次提交的都是同一串旧值,即为本条(#8955)。
- 教模型照抄现值自愈:从报错里把
current is "…" revision M读出来,原样重发一次即可成功——这是最短路径(#8955)。 - 加一条项目级硬规则:每一轮更新前先调
get_goal,拿到精确的 id 与 revision 再改;把它写进规则比指望模型自觉稳定(#8955)。 - 中间件方案:讨论里给出过一个原型——用
post-execute钩子把这条报错改写成下一步动作(用block返回),从而把「报错」变成「自愈指令」(#8955)。 - 验证:重跑同一目标的多轮更新,预期不再出现连续 stale(#8955)。
DeepSeek Harness 无脑催促:判定只看空闲,不看为什么空闲
一句话结论:可驱动判定只判「主 agent 是否空闲」,不判「是否因为派活而空闲」;而可继续子代理运行期间主 agent 整段是空闲、且没有父代理可见的任务条目,于是被判定为「可以驱动」,于是不断催促——严重时会逼主代理取消在途子代理。(#9429) 如果被催促的正是挂上 Agent Teams 后派出的子代理,那么控制与回收还会撞上「工具被 Team 版替换、按名册寻址」那一族坑,见 DeepSeek Harness Agent Teams 冲突:list_agents 同名与子代理停不掉。
DeepSeek Harness 机制与三种派发的差异
据社区逐行核对源码:readyToDrive 谓词只判 agent.status === 'idle';而「可继续」形态的子代理在运行期间不产生父代理可见的任务条目(讨论中对照了另一种实现:正确的做法应在等待子代理时一起等)(#9429)。
- 前台一次性:父代理能看到「有任务在跑」,不会被误判空闲;
- 后台一次性:同样有可见任务;
- 可继续形态:没有可见任务——这是暴露点(#9429)。
DSH插件五步处置
- 确认判据:goal 回合在等待子代理期间持续产生催促、上下文里被塞入无意义内容,甚至出现「建议取消在途子代理」这类动作(#9429)。
- 规避一(不刚需可继续时):把子代理改成一次性派发,父代理能看到任务在跑,催促自然减少(#9429)。
- 规避二(必须保留可继续能力):装社区守卫插件
@argszero/cordis-plugin-goal-progress-guard,它修正了任务调用契约,并利用子代理开始/结束事件维护在途计数(#9429):
npm i @argszero/cordis-plugin-goal-progress-guard
DSH插件 排查注意事项
goal 这类「空转」问题有个共性:报错文本往往指向错误的字段(stale 指向 revision、valid only with action edit 指向轮次),所以要从「值为什么不同步」去想,而不是从报错字面去修。 八条要点:
stale是 CAS 比较失败,不是「目标没了」(#8955)。- 从报错里抄现值是最快的自愈:
current is …就是正确的提交值(#8955)。 - 把「先取再改」写成硬规则,比依赖模型记忆可靠(#8955)。
max_goal_rounds: 0会被当未传,这会制造与真实原因无关的报错(#8955)。- 非编辑动作不带轮次参数:这是规避校验不对称的最短路径(#9566)。
- 报错不自纠是设计缺口:看到「模型反复重试同一动作」,优先怀疑报错里缺了正确写法(#9566)。
- 催促类问题先看派发形态:可继续形态是暴露点(#9429)。
- 修复状态如实:三处在讨论所指版本里都未修;空串/零值归一、报错给出正确写法、可驱动判定加谓词都属核心侧改动,用户侧只能靠提示词规则与社区插件兜。
goal 这类自治回合问题,排查时常常要先把「插件与配置」这一层确认下来。与其在各处手敲命令推断,不如用 DSH Plugin Hub 这类可视化插件市场:已安装列表、装卸历史与系统日志集中在一个面板里,先看清环境再动手。

常见问题
这是 DeepSeek Harness 占位参数约定不清导致的。模型在标记完成时会顺手把只适用于 edit 的参数一起带上(例如把 get_goal 返回的轮次上限原样回填),而校验是对称性不足的:文本型参数把空串当作未提供放行,轮次型参数却只要非零就判失败。处置上先让模型在非 edit 动作里不要携带这两个字段;长期修法是让非 edit 动作忽略它们,或在报错里给出正确写法。
在 DeepSeek Harness 里,这条报错是比较不是查找:报错里的 id 与 revision 只有完全一致才会被接受,写错 id 与 revision 过期共用同一个错误码,所以从报错本身看不出是哪一种。可行的自愈路径是从报错中读出现值(current revision)再原样重发,或在每一轮更新前先调 get_goal 拿到精确的 id 与 revision 再改;把它写成项目级规则比靠模型自觉更可靠。
在 DeepSeek Harness 里,这是因为驱动判定只看主 agent 是否空闲,不看它空闲的原因。可继续的子代理在运行期间,主 agent 整段都是空闲且没有可见任务,于是被反复唤醒。规避办法是使用会统计在途子代理的社区守卫插件;上游的修点是在可驱动判定里增加「是否因派活而空闲」这一个谓词。
在 DeepSeek Harness 里,三种派发方式受影响程度不同:前台一次性与后台一次性都能被正确观察到,只有可继续形态在「有没有可见任务」这一点上暴露空白,所以它才最容易被误判成主 agent 空闲。如果任务不要求后续继续,改成一次性形态就能显著减少催促;如果必须保留可继续能力,就用守卫插件兜。
在 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
来源
- #9566 — update_goal 占位参数约定不明确,模型完成 Goal 时容易反复调用失败· deepseek-ai(GitHub Discussions)
- #8955 — update_goal 反复报 stale 错:模型拿着过期 revision 无限重试,不自愈· deepseek-ai(GitHub Discussions)
- #9429 — goal 机制在主 agent 等待 subagent 时会无脑不断触发唤醒主 agent· deepseek-ai(GitHub Discussions)