任务边界不清,AI 自行扩大 scope

你让 AI 改一个函数,它顺手重格式化了另外两个、还改了常量名。在 prompt 里画出显式的 in-scope / out-of-scope 边界,让这次修改守得住。

你让模型修一个函数。它修了。还”顺便”重格式化了相邻两个函数、改了一个常量名、在文件顶部加了一段”应该说明模块用途”的注释。你 prompt 里一句没提,但也没明令禁止。模型扩大 scope,是因为它被训练成”留下比拿到时更好的东西”,而”更好”在你不画边界时是无界的。

最快的修法: 别去修输出,去修 prompt。把开放动词(“改进”)换成受限动词加明确对象(“只编辑函数 validate_email”),再加一份逐条列出的 out-of-scope 清单,并要求它输出 diff。对 agent 来说,边界必须是机械的、不能只是一句话:用 Cursor 的编辑范围、Claude Code 的 permissions.deny 规则,或 Codex 的 --sandbox read-only,把模型锁死在目标文件或目录上。本文同时讲 prompt 级和工具级两层边界。

先看你属于哪一类

症状最可能的原因跳到
你点名的那个文件里多出了别的改动开放动词 + 没有 out-of-scope 清单Step 1-2
你贴进去的相邻函数 / 段落被重写长上下文,没有边界Step 1-3
多步对话里 scope 一步比一步大步与步之间边界丢了Step 5
你从没点名的文件被动了agent 有大范围写入权限Step 6
模型嘴上同意边界,转头还是破了只有 prompt 级边界,没有强制”如果还是没修好”

常见原因

1. 开放动词授权扩展

“改进”、“清理”、“润色”、“重构”全都暗示”凡是能改的地方都改”。模型会照字面执行。

如何判断:你的动词是开放的。受限动词是”编辑函数 X”、“替换第 Y 行”、“重命名变量 Z”。

2. 没有显式的 out-of-scope 清单

你说了要做什么,但没说要留下什么不动。模型就把视野里的一切当成可下手的对象。

如何判断:你的 prompt 有 in-scope 集合,却没有 out-of-scope 集合。

3. 长上下文里有看起来可修的相邻问题

你把整个文件贴了进去。模型看到的不只是目标,还有另外五处看着可以顺手修的地方。没有边界,它就全修了。

如何判断:scope 扩展落在 prompt 里能看到的相邻代码或内容上。

4. 多步任务每步没有边界

第 1 步限定了函数 X。第 2 步说”现在改进这个模块”,边界就蒸发了。到第 3 步,模型已经把整个文件重写了。

如何判断:scope 漂移和步数相关(出现在多步工作流里)。

5. Agent 有大范围写入权限

在 Cursor 的 Agent mode、Claude Code,或 workspace 可写的 Codex 里,模型能动 scope 内的任何文件。“scope”在机械层面就变成了”它能读、能写的全部”。

如何判断:改动出现在你从未点名的文件里。

动手前先确认

  • 标清楚什么该改(in-scope)。
  • 标清楚什么不该改(out-of-scope)。
  • 决定相邻问题是让它 flag 出来,还是直接忽略。
  • 多步工作每一步都规划好边界。
  • agent 运行前,先把工具权限收缩到目标文件或目录。

需要收集的信息

  • 当前的 prompt。
  • 越界的输出(代码用 git diff)。
  • 不该发生的改动清单。
  • in-scope 和 out-of-scope 两个集合。
  • 当时生效的模型、agent 和工具权限。

最短修复路径

Step 1:开放动词换成受限动词

差:  改进 user.py 文件。
好:  只编辑 user.py 里的函数 `validate_email`。其他函数 byte 级
      保持原样。未改动的行不要重新格式化。

受限动词加上明确对象,消掉了”改进”留下的那块歧义。

Step 2:声明 out-of-scope 清单

Out of scope(不许动):
- validate_email 以外的任何函数。
- import 和 export。
- MAX_RETRIES 常量。
- 现有注释。
- 文件级 docstring。

正常情况下你会"顺手清理"的内容,请写到 SUGGESTED_FOLLOWUPS
区块里,不要直接动。

SUGGESTED_FOLLOWUPS 模式让模型把它的观察浮出来却不动手——你发现越界编辑时,想要的其实通常就是这个。

Step 3:内容类任务用标记编辑区

对散文、markdown 或配置这类不方便用行级指令的场景,把可编辑区域围起来:

只编辑下方标记之间的文本。标记之外的内容原样保留。

# AI-EDIT-START
def validate_email(email: str) -> bool:
    return "@" in email
# AI-EDIT-END

物理标记比一句话更经得起 scope 漂移——句子模型可以重新解读,标记不行。

Step 4:要 diff,不要整文件重写

按 unified diff 输出。只包含编辑区内改动的 hunk。不要输出整个文件。

diff 让越界改动在机械层面无所遁形,而且模型知道你会去读 diff 时,行为会更保守。用 agent 时这一条是白送的:Cursor 的 Agent 在应用前会把每一处改动都以可审阅的 diff 形式展示出来——去读它,别直接点 Accept All。

Step 5:多步工作,每一步都重申边界

Step 1:只编辑 validate_email。Out of scope:其他全部。
Step 2:第 1 步审过之后,只编辑 send_email。Out of scope:
        validate_email(已冻结)和其他全部。

把”已冻结”清单一路往后传,让 scope 单调收紧而不是越积越多。一条只在长对话开头说过一次的边界活不下来,要重申。

Step 6:对 agent,在工具层收缩,而不是在 prompt 里

prompt 里的边界是一个请求;权限边界是工具强制执行的,不管模型自己想怎么做。截至 2026 年 6 月:

  • Cursor。 改动是局部的时候,用 Edit mode(单文件、外科手术式),别用 Agent mode。要彻底把某个文件挡在 agent 触及范围之外,把它加进 .cursorignore(gitignore 语法)——但要注意这是 best-effort 的,目前会同时挡掉读和写,而且 agent 有时仍能通过 shell 命令摸到被忽略的文件,所以还是要审 diff。见 Cursor 的 agent 安全文档
  • Claude Code。 .claude/settings.json 里的权限规则由 Claude Code 强制执行,不是由模型执行。用 gitignore 风格的路径规则拒掉目标之外的编辑,例如 "deny": ["Edit(/src/legacy/**)"];或者把整个会话切到只读探索,把 /permissions 设成 plan mode。只有 --add-dir 加进来的目录可写,所以从目标子目录里启动本身就是一道边界。CLI 等价写法是 --disallowedTools "Edit"。见 Claude Code 权限文档
  • Codex。--sandbox read-only 做只规划不写入,或 --sandbox workspace-write 把编辑限制在工作目录内;即便如此,.git.codex.agents 仍保持只读。会话中途可用 /permissions 降级到只读。见 Codex 的 sandboxing 文档

机械权限每次都胜过 prompt 级指令。

怎么确认已经修好

  • diff 限定在 in-scope 集合内。
  • out-of-scope 的文件、函数、段落是 byte 级一致——用 git diff --stat 确认(只该出现你预期的那些文件),再对每个文件跑 git diff
  • SUGGESTED_FOLLOWUPS(如果有)列出了模型注意到但没动的相邻问题。
  • 重跑 prompt,diff 的形态相近。
  • 同事审 diff 时找不出一处意外的改动。

如果还是没修好

  1. 模型嘴上同意了边界、转头又破了:别再靠散文。换成工具强制的边界(Step 6)——Claude Code 的 Edit(...) deny 规则和 Codex 的 read-only sandbox 由工具检查,不由模型。
  2. agent 的权限比你 prompt 暗示的更宽:去确认实际可写的范围(Cursor 是 Edit 还是 Agent、Claude Code 的工作目录、Codex 的 sandbox mode),而不是去确认 prompt。
  3. 重复性工作,把边界锁进 system prompt 或项目指令(CLAUDE.md.cursor/rulesAGENTS.md),不要写在 user message 里,这样它对每一轮都生效。
  4. 换一个模型。不同模型行为不一样,在某个任务上过于积极的模型,在另一个任务上可能反而守得住 scope。

常见问题

我明明没让它扩,模型为什么要扩 scope? 前沿模型被调成乐于助人、并且倾向于”留下比拿到时更好的代码”。没有明说边界时,“助人”就包含修你没让它修的东西。这个扩展是一项没加约束的功能在起作用,不是 bug——所以修法是把约束加上。

只说一句”别的都不要改”够吗? 有帮助,但单条否定指令在一个塞满”看着可修”代码的长上下文面前很弱。把它和一份具体的 out-of-scope 清单(Step 2)以及一个 diff 要求(Step 4)配在一起,让越界改动可见。对 agent,再用一条权限规则(Step 6)兜底。

我的 agent 改了我从没提过的文件,专门怎么治这个? 这是权限问题,不是 prompt 问题。在 Cursor 里切到 Edit mode,或把禁区路径加进 .cursorignore。在 Claude Code 里,往 .claude/settings.json 加一条 Edit(...) deny 规则,或从那个窄子目录里启动。在 Codex 里跑 --sandbox read-onlyworkspace-write。工具不让它写的东西,模型就碰不到。

怎么在多步对话里限定 scope,又不用每步都重贴一遍? 维护一份很短的”已冻结”清单,每步开头重申一次:“Frozen(不要动):validate_email、send_email。只编辑:parse_headers。“每步多花几个 token,就能挡住边界随着对话变长而被侵蚀。

更小的模型真的更守 scope 吗? 有时是,但这取决于任务,不是一条规律。别把换模型当成第一步——一份清晰的 out-of-scope 清单加上一个 diff 要求,在你现在用的那个模型上就能解决大多数情况。

预防建议

  • 默认动词:“edit”、“rename”、“replace”——不加 scope 的”improve”、“refactor”、“polish”永不用。
  • 永远声明 out-of-scope。如果什么都不设禁区,就明说。
  • 内容类任务用标记编辑区;agent 用工具强制的边界。
  • agent 工作流里,开工前就机械收缩工具 scope,而不是越界之后再补。
  • SUGGESTED_FOLLOWUPS 模式:让模型报告观察而不动手。
  • 每次都审 diff。不要信模型对自己改了什么的自述总结。

相关阅读

标签: #排查 #Prompt #Prompt 质量 #Prompt 工程