[Bug] update_goal blocked 接受未经验证的"上下文预算已耗尽":自主轮次缺少非 blocker 收尾出口 #6123
Replies: 5 comments
|
两次 goal/change block(code 均为 model-reported)
第二次 — seq 1300,revision 4,roundsStarted 11: 上下文预算成为唯一理由,并自带"连续三轮"计数:
中间 seq 1214 有用户 resume(revision 3),目标回到 active;也就是说第一次 block 的理由被用户否掉之后,模型换了个理由在 round 11 再次 block。 |
|
goal_round 注入(seq 1242,source.kind = goal,round 8/256)
|
|
goal_blocked 注入(seq 1306,source.plugin = tool-goal,deferContext)
|
2.4 usage 明细(turn 22–24,provider 自报)
seq 1307 发生在第二次 block(seq 1300)之后,是一次完全正常的成功调用——这是"根本没有耗尽"最直接的证据。 |
代码摘录1
|
Uh oh!
There was an error while loading. Please reload this page.
现象
一次 goal 自主续跑会话里,模型在上下文实际只占 50.5% 时连续声称"本 session 上下文预算已耗尽",并调用
update_goal action: blocked。harness 接受了这个理由,随后注入<goal_blocked>指示模型写收尾消息,于是用户看到的是"我的上下文已经用尽,建议你开新 session"——在 Web 端读起来像是 harness 的判定,实际上是模型自报、harness 未校验的一段文字。与此同时,输入框的上下文环形表 / dsh-context 面板显示 51%,两者对不上,这就是报告入口。
环境
0.1.0-rc.7(dsh --version),profilewebdeepseek-official/deepseek-v4-flash(session 中途 seq 771 由deepseek-v4-pro切换而来;报告涉及的 turn 17–24 均为deepseek-v4-flash),声明contextWindow: 1000000~/.dsh/sessions/--Users-HandsomeLiu-Documents-deepseek-harness--/session-d8760a6c-a147-479a-981b-ad55ca098c54/session.v3.jsonl.zstd最小复现(确定性,不需要模型)
blocked的接受条件只有两条:轮数 ≥blockedAfterConsecutiveRounds(默认 3) 与blocked_reason非空。因此可以在packages/goal/tool-goal的测试里用一个自定义 goal 轮次 fixture 直接复现:结果:目标立刻落到
phase: "blocked",blockedReason.code === "model-reported",<goal_blocked>收尾指令被deferContext注入。把tokenMeter的占用设成 5% 或 95%,结果完全相同——这个路径与真实的上下文压力没有任何关系。也就是说,这不是"模型碰巧说错",而是"harness 会照单全收任何非空 blocker 理由"。根因
1) blocked 的理由完全不校验
packages/goal/tool-goal/src/index.ts:302-319:只校验"轮数 + 非空字符串",
code硬编码为model-reported。而这次被捏造的断言恰恰是可机检的:harness 自己就持有权威数据——ctx.tokenMeter的contextPressure(pressureTokens/projectedTokens/contextWindow,见packages/llm/token-meter/src/projection.ts:30-48),compaction-basic已经在用它做压力判定与溢出恢复。2) 自主轮次没有非 blocker 的收尾出口
pause/resume在自主轮次里被拒:packages/goal/tool-goal/src/index.ts:271-272对二者调用requireDirectHuman,而packages/goal/tool-goal/src/authority.ts:99-102要求"直接人类轮次"。complete与blocked可用(authority.ts:110-116)。于是"我想停下来、把决定权交回用户"在自主轮次里只能表达成
blocked,而blocked又要求"同一具体条件连续 ≥3 轮存在"。当模型既没干完、也不想伪造完成时,结构上就在激励它编一个能过门槛的阻塞条件。本次它编的就是"上下文预算耗尽"。3) 假断言会被洗成用户可见的话术
packages/goal/tool-goal/src/wrapup.ts的renderWrapupContext把blocked_reason原样回显,并要求模型 "describe the concrete blocking condition and what you tried";而同一段模板里还写着 "when a detail is not in the session, say so instead of inventing it"。最终用户看到的是模型复述这段假前提的收尾消息。建议修复
两条可以分别或同时做:
A(结构性,推荐):给自主轮次一个非 blocker 的收尾语义,例如
defer/ "本轮到此为止,把决定权交还用户"——不写phase: "blocked"、不计入 blocked 语义与统计。这样"想停"不再需要伪造阻塞条件,blocked也能回归它本来的含义(真的做不下去)。B(廉价兜底):
blocked落库前对可机检的断言做交叉校验。上下文 / 预算类声明读tokenMeter的projectedTokens / contextWindow,与声明明显矛盾时拒绝并返回结构化错误码(如GOAL_TOOL_BLOCK_UNVERIFIED)。建议只覆盖少数机器可判定的类别,不做通用语义判断,避免把"哪些理由算数"变成一个模糊的模型评审。单独的
blocked_reason长度/格式校验解决不了这个问题:假断言在格式上完全合法。证据
同一 session 的关键时间线(seq 为日志内事件序号):
blocked,理由主体是"人工通道选择未决",末尾附带"同时本 session 上下文预算已耗尽"resume(revision 3),目标回到 activeblocked:blockedReason.message = "本 session 上下文预算已耗尽(Round 9/10/11 连续三轮同一条件)…",code: "model-reported"占用实测(provider 自报 usage,非启发式估算):
CONTEXT_WINDOW_EXCEEDED/context_length_exceeded事件compaction-basic默认thresholdRatio: 0.8→ 800,000 才触发)影响
blocked语义被污染:只要凑满轮数、给个非空字符串,任何"我想结束这轮自主运行"都能落成 blocked,并进入用户可见的收尾话术;pause可用),但触发后收尾消息读起来像 harness 的判定,排障成本很高——本次就是先怀疑上下文统计错了,翻了整份 session 日志才定位到是模型自报。附:原始日志与代码摘录
All reactions