九凤
AI 为什么总会偏离主线:Task Packet 与上下文治理

AI 为什么总会偏离主线:Task Packet 与上下文治理

从权威事实源、当前唯一目标、旁支分流到机器身份绑定,解决长任务中的上下文丢失与主线漂移。含一份真实施工单自相矛盾导致范围蔓延的案例——AI 没跑偏,是我们给了它两个打架的指令。

Vibe Coding

「vibe coding 工程化」系列第三篇。第一篇讲为什么需要 Loop, 第二篇讲怎么用 Claude Code + Codex 搭起闭环。 这一篇解决你搭完之后必然会遇到的那个问题:明明有实施计划,AI 就是不按计划走。

先把结论放前面:

AI 偏离主线,通常不是模型能力不足,而是它同时面对太多相互竞争的目标, 却没有一个可机器核验的当前任务边界。

这句话里每个限定词都重要。不是「它不听话」,是「它面前有太多同样合理的事可做」。

一、AI 为什么容易偏离主线

上下文越长,不代表优先级越清晰。

这是最反直觉的一条。很多人的第一反应是「上下文不够,多喂点」——于是把需求、架构、历史讨论、上一轮的发现全塞进去。结果是:现在有一百件事同样「在场」,而在场不等于有序

你开头那句「本轮只做 A」,到第 40 轮对话时,跟另外九十九件事在同一个池子里争夺注意力。它没有忘记那句话,它只是不再认为那句话最重要

需求、架构、历史讨论和临时发现会互相竞争。

它们全都真实、全都相关、全都值得做。模型看到「这里有个明显的 bug」时,那个 bug 是具体的、当下的、可解决的;而「本轮唯一目标」是一句抽象的话。具体的东西天然更有引力。

模型倾向于解决眼前最具体的问题。

这不是缺陷,人也一样。区别在于人有一个「等会儿,这不是我今天该干的」的刹车,而这个刹车来自外部的、可核验的边界——你的排期、你的 PR 范围、你的同事会问「你怎么改这个」。

AI 没有这些外部约束。除非你给它。

聊天记录不是可靠的项目状态。

这条最要命。很多人把「我们讨论过」当成「这事定了」。但聊天记录是线性的、含糊的、没有版本的。三天前的一句「那就先这样吧」,你记得它是敷衍,模型记得它是决议。

二、常见但无效的解决办法

在讲对的做法之前,先把几条死路说清楚——因为它们都很直觉。

❌ 不断追加「请严格按照计划」。 这句话加十遍,权重不会变成十倍。它只是池子里的第一百零一件事。

❌ 把所有架构文档一次性塞进 Prompt。 你以为在提供上下文,实际在制造竞争。塞得越多,「本轮唯一目标」的相对权重越低。

❌ 每轮重新解释完整背景。 成本高,且每次解释都可能引入新的措辞差异——今天你说「候选序列」,明天说「重试队列」,模型会认真地把它们当成两个概念。

❌ 用一个越来越大的 control.md 复制项目状态。 这是最隐蔽的一条。你会得到一份跟真实项目状态永远差半拍的副本。而两份状态一旦不一致,模型会挑它更容易理解的那份——通常是你手写的那份,因为它更整齐。

❌ 发现一个问题就立即切换任务。 「反正都改到这儿了」——这句话是主线漂移的起点,不是终点。

这五条的共同点:它们都在往里加东西。 而问题不是「东西不够」,是「东西太多且没有优先级」。

三、Task Packet 是什么

Task Packet 是本轮的施工单。它不是文档,是边界

一份最小的施工单只回答七个问题:

栏位回答
当前唯一目标这一轮只做哪一件事
权威文档引用事实在哪儿(引用,不复制
架构不变量这一轮不得破坏什么
允许修改范围能动哪些路径(机器可解析
明确不做哪些事这一轮禁止
验收证据什么命令、什么退出码算完成
停止和裁决条件遇到什么必须停下来问人

注意最后一栏。大部分施工单模板都没有它,而它恰恰决定了 AI 在遇到岔路时是「自己选一个」还是「停下来」。

四、权威文档与 Packet 的关系

这是很多人搞错的地方:Packet 不是一份小型架构文档

文档回答
需求为什么
架构最终要成为什么
详细方案采用什么技术路径
实施计划现在做到哪一步
台账哪些事已经裁决
Task Packet这一轮具体做什么

Packet 的每一行都应该是指针,不是副本:

| 权威输入 | 对齐位置 | 与本轮的关系 |
|---|---|---|
| 技术架构 | <path>#<section> | 不得破坏的架构约束 |
| 实施计划 | <path>#<item>    | 当前实施项及前后依赖 |
| 台账记录 | <path>#<decision>| 已裁决项、遗留风险 |

为什么必须是引用而不是复制:复制会产生第二份真相。两份一旦不一致——而它们一定会不一致——你就有了一个新问题:哪份算数?而模型不会问你,它会自己挑一份。

五、「当前唯一目标」为什么重要

这一栏是整份施工单的核心。看两个写法:

❌ 错误的写法:

完成健康路由相关能力,并处理发现的问题。

这句话有什么问题?它每个词都对,但没有边界

  • 「健康路由相关能力」——相关到什么程度?改了路由,那监控算不算相关?
  • 「处理发现的问题」——发现什么处理什么,等于授权无限扩张

这不是目标,这是一张空白支票。

✅ 正确的写法:

在默认关闭的 flag 下,让异步重试消费已经持久化的候选序列,并保持旧路径字节级不变。

对比一下它多了什么:

作用
「在默认关闭的 flag 下」发布边界——不影响线上
「让异步重试消费」动作——具体到能验证
「已经持久化的候选序列」前置条件——不含「顺便把持久化也做了」
「保持旧路径字节级不变」不变量——一句可被 diff 验证的话

好的唯一目标,读完之后你知道什么算做完,也知道什么算越界。

一个判断标准:如果这句话写不下一行,说明它不是一件事,该拆成两轮。

六、真实案例:施工单自己打架,AI 当然选择做

这是这篇最值钱的部分,而且比「AI 跑偏了」的故事更有意思——因为跑偏的不是 AI

一份限流的施工单,两栏是这么写的:

当前唯一目标:给认证端点(登录家族 + 注册 + 口令重置核验)补挂跨副本生效的按 IP 限流,不误伤正常用户。

超出范围:注册、/user/reset/user/reset_by_code —— 待用户裁决

看出来了吗?同一份施工单,目标里包含注册和重置,范围里又把它们标成「待裁决」。

Claude 的实际 diff 把三者一并改了。

然后 Codex 的审查意见(脱敏后):

MAJOR — Packet 与实际 diff 存在主线冲突:Packet 一面将 register、两个重置端点标为「超出范围、待用户裁决」,实际 diff 已将三者并入;另一面又在「当前唯一目标」中包含它们。该范围扩展涉及安全策略和优先级,需用户明确裁决并统一 Packet 后才能批准。

注意 Codex 说的是「统一 Packet 后才能批准」——它没有让 Claude 把代码改回去,而是指出施工单本身有问题

这个案例说明了一件事,值得所有人记住:

主线漂移的源头,经常不在执行方,在施工单。

你给了它两个打架的指令,它选了更具体的那个(目标里明确写了「注册」),这不叫跑偏,这叫按指令执行

所以每次觉得「AI 又不听话了」,先回头看施工单:你是不是同时说了「做」和「别做」?

七、旁支问题怎么处理

实施过程中一定会发现别的问题。发现旁支问题不等于要立刻修——这条不写死,Loop 会在第二天变成一个无穷无尽的 TODO 收集器。

四个去处,必须选一个:

分类什么时候用动作
IMPLEMENT_NOW不修就完不成当前目标,且仍在允许范围内本轮修掉并测试
ACCEPTED_RISK风险明确、当前目标可交付、人已接受记账,不阻塞
LATER有价值但不属于当前目标进下一个 Packet,不切换主线
ESCALATE_USER改变架构语义、范围、优先级或发布风险立即停,给证据和建议

用法上有个细节容易错:IMPLEMENT_NOW 的判据不是「这个问题严重吗」,是「不修能不能完成当前目标」。

一个严重但不阻塞当前目标的问题,正确去处是 LATERESCALATE_USER,不是「既然严重那就顺手修了」。严重程度决定它多快被处理,不决定它在这一轮被处理。

八、Heartbeat 怎么证明主线在推进

执行方每轮的进度汇报,只需要回答五个问题:

  1. 当前目标是什么(用业务语言,不是编号)
  2. 本轮增加了什么可验证的能力
  3. 是否偏离;如有,停在哪、需要什么裁决
  4. 还差什么
  5. 为什么下一步是当前最高优先级

第 2 条是核心:「可验证的能力」,不是「我改了三个文件」。改了三个文件不是进展,能跑通一条以前跑不通的路径才是

还有一条反模式要挡掉:别只输出 A1、B3、Phase 2 这种内部编号。

编号是给机器看的。你一眼扫过去看到「A1 完成,进入 B3」——你根本不知道发生了什么。所以我的规则里有一条「编号字典」:

只有在编号表里定义过的编号,才允许出现在正文、Heartbeat、审查报告和证据包里。 同步给用户时必须先写人类可读名称,再写编号

这条看着琐碎,实际拦住的是术语漂移:三天后 A1 变成了另一个意思,而没人发现。

九、怎么机器化地防止串任务

前面讲的都是「怎么写」。但能被机器执行的约束,才是约束。四条机器边界:

1. Packet、checkout、HEAD、Guard、Review Prompt 绑定在一起。 一个任务一套身份。不是「我记得这是那个任务」,是每个产物都带着任务标识

2. 修改范围由 allowlist 机器检查。 施工单里「允许修改范围」那一栏要写成 path/**别写自然语言——它要能被解析成 allowlist,然后跟真实 diff 比对。

这就是为什么我说**「允许修改范围」比「目标」更重要**:目标是一句自然语言,执行方可以有一百种理解;允许范围是一组路径,机器可以直接比对。

3. 多任务用独立 worktree。 两个任务共用一个工作树,diff 会串。这不是纪律问题,是物理问题。

4. 审查先核对主线,再看代码实现。 这条顺序很关键。上面那个限流案例里,Codex 的第一条 MAJOR 就是「Packet 与 diff 主线冲突」——它先问「你做的是不是这一轮该做的」,再问「你做得对不对」

顺序反了,你会得到一份「代码写得很好、但不该在这轮写」的审查报告。

十、最后一条兜底信号

前面九条都是预防。但流程会自己变形,所以需要一条检测:

连续两轮只在改施工单、审查提纲、台账,没有代码或裁决增量——那不是流程严谨,那是空转。

出现这个信号,必须重新回答一个问题:这一步是否直接让当前唯一目标更接近完成?

如果答案是「不,但它让流程更完善了」——那就是漂移。而且是最难察觉的一种漂移,因为它看起来非常勤奋、非常有条理、每一轮都有产出。

它只是没有产出目标

结论

上下文治理不等于给模型更多上下文。

真正重要的是三件事:

  • 明确事实源——需求、架构、台账各管一段,Packet 只引用不复制。两份真相等于没有真相。
  • 明确优先级——「当前唯一目标」只允许一件事。写不下就该拆。
  • 明确当前边界——允许改哪些路径,机器说了算,不是执行方自己理解。

所以 Task Packet 的价值不是增加了一份文档。

它的价值是减少 AI 当前需要做出的选择。

它面前的事越少,跑偏的可能就越小。这跟模型强不强没关系——模型越强,能同时看见的合理选项越多,就越需要有人告诉它这一轮只做哪一件。


下一篇:机器门禁——为什么「请遵守」永远不够,以及哪些约束值得花代价变成一道会返回非零退出码的关卡。