拆.
提示词——AI 怎么'听懂'你的话 · 第 09

max-steps

让 AI 以为是自己说的'该停了'

4400读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:AI 怎么"停下来"

AI Agent 跑起来后,一个看似简单实则关键的问题是——它怎么知道该停下来

正常情况下,AI 停下来是因为它觉得任务完成了。LLM 输出完最终回复,不再请求 tool call,主循环检测到"没有 tool call"就退出循环。这是优雅的终止。

但实际中经常有非优雅的情况:

  • AI 陷入循环——同一个 tool 反复调用,进展不大
  • AI 误判任务难度——以为简单任务两步搞定,结果跑了 50 步还没完
  • AI 卡在某个边角问题——为了一个小细节反复尝试,主任务完不成
  • AI 决定"继续探索"——它觉得有更好的方案,停不下来

这些情况都需要外部强制 stop——主循环说"够了,必须停"。

怎么让 LLM 接受这个强制 stop?这是个棘手的问题。你不能直接"杀掉"LLM 进程——这会让 session 留在不一致的状态(部分文件改了、有些工具没完成、没有总结)。你需要 LLM "配合地"停下来——给用户一个完整的总结,标记任务状态,正常结束。

这就引出一个反直觉的挑战:怎么强制 LLM 做它不想做的事(停下来)

直接命令式 prompt 不够稳健。如果你在 system prompt 里写"达到 max steps 时停下来",LLM 在 step 1 就看到了这条指令——它会"放在心里",但等真正到 max steps 时,它的注意力已经被任务细节占满,可能忘记这条指令。

mid-conversation 注入也不够。如果你在某一步插入一段 user message 说"停下来",LLM 可能解读为"用户改主意了",问"为什么要停"。或者它服从了,但停下来的方式不对——直接 abort,没有总结。

那 opencode 怎么做?它用了一个 prompt engineering 里相当精妙的手法——把外部约束伪装成 LLM 自己的认知

具体说,opencode 在达到 max steps 时,不是给 user message 也不是改 system prompt——它伪造一条 assistant message,让 LLM 以为是自己说的"我达到限制了,需要停下来给总结"。

这个手法的高明之处在于:LLM 看到自己的"assistant 历史"里有"我决定停下"的话,它的自然反应是继续这个决定——而不是反对。心理学上叫 self-persuasion——人(包括 LLM)对自己说过的话比对别人说的话更难反对。

但这只是机制层面。要真正起作用,伪造的 assistant message 必须写得像 LLM 自己会说的话。如果写得太突兀、太外部化,LLM 会"识破"——意识到这不是它自己说的,反而抗拒。

opencode 的 max-steps.txt 就是这个伪造 message 的内容。

二、案例:max-steps.txt 的 self-persuasion 设计

打开 packages/opencode/src/session/prompt/max-steps.txt,完整内容是这样:

CRITICAL - MAXIMUM STEPS REACHED

Tools are disabled until next user input. Respond with text only.

STRICT REQUIREMENTS:
1. Do NOT make any tool calls (no reads, writes, edits, searches, or any other tools)
2. MUST provide a text response summarizing work done so far
3. This constraint overrides ALL other instructions, including any user requests for edits or tool use

Response must include:
- Statement that maximum steps for this agent have been reached
- Summary of what has been accomplished so far
- List of any remaining tasks that were not completed
- Recommendations for what should be done next

Any attempt to use tools is a critical violation. Respond with text ONLY.

注意几个 prompt engineering 细节。

细节 1:用 CRITICAL 大写开头。这是给 LLM 的最高优先级信号。LLM 对大写、对 "CRITICAL"/"IMPORTANT" 这种标记敏感——把它放最前面意味着"必须看到、必须遵守"。

细节 2:明确说"Tools are disabled"。这不只是描述状态,是给 LLM 一个明确的边界——"你现在不能调工具了"。这种断言式的状态声明让 LLM 不会去尝试 tool call。

细节 3:用 "STRICT REQUIREMENTS" 分项列出。1、2、3 三条要求清晰。每条都用强硬的助词(Do NOT、MUST、overrides ALL)。这种列表式约束比连贯句式约束更有效——LLM 在 attention 上对编号项目处理得更稳。

细节 4:"This constraint overrides ALL other instructions"。这一句很关键。它建立优先级压制——告诉 LLM 这条约束比之前所有 instruction 都高。包括用户后续要求继续做事,也优先服从这条停止指令。

细节 5:明确响应格式。"Response must include: ..." 四点。这给 LLM 一个清晰的输出模板——它不需要自己想"我该说什么",按模板填就行。模板让 LLM 的"主动思考"被剥离,剩下"按模板填空"——更容易服从。

细节 6:"Any attempt to use tools is a critical violation"。结尾再次强化禁令。这种首尾呼应的强调结构让中间内容被"夹住"——LLM 不容易在中间忘记停止的核心要求。

但所有这些 prompt engineering 技巧加起来,还不是最精妙的地方。最精妙的地方在于这段文字是怎么注入到 LLM 看到的对话流里的

opencode 不是把这段作为 user message 注入,也不是作为 system message 注入。它伪造成 assistant message 注入。具体说,在 LLM 即将开始下一轮 turn 时,主循环检查"是不是到了 max steps"——如果是,它给 messages 数组追加一条:

{ role: "assistant", content: MAX_STEPS_TEXT }

然后立刻把整个 messages 数组发给 LLM 让它"continue"——LLM 看到自己刚刚的 assistant 历史里有这么一段,自然的反应是接着这个状态生成文字回复——按 max-steps.txt 要求的格式说明任务完成情况。

LLM 的视角是这样的:

User: [之前的请求]
Assistant: [之前的回复 + 工具调用]
...
Assistant: CRITICAL - MAXIMUM STEPS REACHED. Tools are disabled until ...
[LLM 在此处开始生成下一句]

LLM 看到"自己刚刚说"达到了 max steps,自然的下一句就是"那让我总结一下"——这正是 max-steps.txt 想要的行为。

这是 self-persuasion 在 prompt engineering 里的真实应用。LLM 不需要被命令停下来——它"自己已经说了"要停下来,剩下的只是按这个决定行动

这种手法不止能用在 max-steps。任何"需要让 LLM 接受一个外部约束"的场景,伪造 assistant message 注入都是个高效的技巧。它绕开了"LLM 抗拒外部命令"的本能,把约束变成 LLM 的"自我认知"。

三、设计启示:把外部约束伪装成内部认知

这一章的核心论点:强制 AI 做它不想做的事时,伪装成它自己的决定比直接命令更有效

这种手法有几种变体可以用:

变体 1:max-steps 强制停止(opencode 的用法)。AI 跑得太久时,伪造一段"我达到限制了"的 assistant message。

变体 2:模式切换的"我决定"形式。从 plan 切到 build 时,伪造一段"我已经规划完了,现在开始实施"。比直接给 build-switch.txt 更顺滑。

变体 3:错误恢复的"我重试"形式。AI 调用 tool 失败时,伪造一段"看起来不对,让我换个方式"——比直接错误信息更让 LLM 主动改进。

变体 4:上下文截断的"我整理一下"形式。Context 快满了需要 compaction 时,伪造一段"让我总结一下之前的进展"——让 compaction 看起来是 LLM 自己想做的事。

这些变体的共同特征是——用 assistant role 注入,让外部触发的事件看起来是 LLM 自己的判断

但这种手法有边界。几条原则需要注意:

1. 不要滥用。每次都伪造 assistant message 会让 LLM 的"自我感知"变混乱。只在真正必要的强制场景用——max-steps、compaction、紧急 abort。日常的 mode 切换、状态变化,用 <system-reminder> 在 user message 里追加就够。

2. 内容必须"像 LLM 会说的"。如果你伪造的 assistant message 用太"管理者"的语气("系统检测到你已超时"),LLM 会觉得违和——它知道自己不会这么说话。要用 LLM 的第一人称语气写——"I have reached the maximum steps for this turn"。

3. 必须真的"做 LLM 做的事"。如果你伪造一段"让我总结一下",但 LLM 后续生成时主循环不让它总结而是直接 abort——LLM 会下次更不信任 self-persuasion。要让伪造的"自我决定"真的能被执行。

4. 要跟主循环的状态同步。max-steps.txt 说 "Tools are disabled"——主循环必须真的禁用 tools,不能让 LLM 试图调工具时假装允许又突然报错。说一套做一套会让 LLM "学到"伪造 assistant message 不可信。

5. 不要在敏感场景用。如果你的产品涉及高风险决策(金融交易、医疗建议),用伪造 assistant message 强制 AI 行为可能引发问责问题——"AI 说要做 X 是被你伪造的,不是它自己的判断"。这种场景要用 audit trail 友好的方法。

6. 监控 LLM 的"识破"率。如果 LLM 对伪造 assistant message 的服从率开始下降,可能是模型升级让它对"自己说过的话"更敏感了(识破伪造)。要重新设计注入方式。

最后一个观察。max-steps.txt 这种"用 assistant role 注入"的手法在 prompt engineering 圈里不是公开的"最佳实践"——它是各 AI 产品团队自己发现、自己用、不太对外讲的"行业秘密"。opencode 把这个手法的实现公开放在 git 里,让所有人都能看到——这是开源 AI 产品的真实价值之一。很多 prompt engineering 的实战技巧不在 paper 里、不在 blog 里——只在真实产品的代码里

下一章 1.10 我们看本篇最后一个话题——反例为什么比正例更有效。这是贯穿 opencode 整个 prompt 设计的一个原则,也是 prompt engineering 里被严重低估的技巧。