RSS

第 10 章 / 共 11 章

让任务跑过三小时

约 22 分钟 更新于

10.1 从零开始,把整件事跑完一次

前面九章是拆开讲的。你知道了注意力预算是有限的,知道了怎么盘点窗口,拿到了症状-动作矩阵,改过 CLAUDE.md 的海拔,瘦过工具集,会写笔记文件、会收窄检索、会在压实前固化事实、会派只读子代理。

九件工具,各自都懂了。但真到了终端里,你面对的不是九件工具,是一个还没开始的任务和一个空窗口。

这一章不引入新概念。它做一件事:把 shopfront 那次结算迁移从零跑到底,按时间顺序走一遍,每个阶段告诉你三件事——此刻在做哪个动作、上下文正在怎么变、你该在哪里插手

任务还是那个:把结算流程从内部 v1 API 迁到 v2,并补齐测试。涉及 src/checkout/src/api/client.tstests/checkout/docs/api-migration.md。十七处调用,九个文件,加上测试。真做起来三小时打不住,我们按三小时算。

先说一个反直觉的结论,它是这一章的骨架:长任务的成败,一大半在第一个二十分钟决定。等你发现 agent 变笨了再去救,救的成本比一开始就摆好高一个数量级。

10.2 阶段①:立项——在敲第一行需求之前

这二十分钟你一行代码都不会写,但它决定后面三小时能不能撑住。

动作:写。

**第一件事,建笔记文件。**在 docs/ 下建 migration-progress.md,按第 6 章的模板起头。开局它应该只有四个小节,且都很短:

# v1 → v2 结算迁移进度

## 完成判据
- [ ] `src/checkout/``src/api/client.ts` 中不再有 v1 调用
- [ ] `tests/checkout/` 全绿,且新增覆盖 v2 的退款与部分退款路径
- [ ] `docs/api-migration.md` 的字段映射表与实现一致

## 已确认事实
(还没有)

## 待办清单
(调查后填入)

## 已做决定
(还没有)

**第二件事,把完成判据写死。**注意上面那三条判据的共同点:都能验证,不需要谁来判断「差不多了」。这一步的价值在三小时后才显现——那时候你已经累了,agent 已经绕过几次弯,唯一还能告诉你「做完没有」的东西就是这三行。

不写判据的长任务有一个典型死法:跑到两小时四十分,agent 宣布「迁移已完成」,你信了,第二天发现退款路径根本没动。它不是骗你,是你们俩从来没对齐过「完成」是什么。

**第三件事,瘦身工具集。**按第 5 章过一遍这次任务真正需要什么。shopfront 接了一个 MCP 服务器连内部订单系统——这次迁移用得上吗?如果只是改代码和测试,用不上,那就在这次会话里关掉它,它的工具定义不必常驻。如果确实要查真实订单结构验证字段,那就留着,但你要知道它在收费。

上下文此刻的样子:系统提示、CLAUDE.md、一份精简的工具集、一个几乎空白的笔记文件。占用很低,而且这块内容在接下来三小时里基本不该变——记住这句,10.6 会回来算这笔账。

你在哪里插手:全程。这个阶段是你的,不是 agent 的。判据必须你定。

10.3 阶段②:调查——让子代理去脏,主线保持干净

动作:隔。

现在需要那份调用清单。这正是第 9 章那个 v1-usage-scanner 的活。

派它出去,任务描述里带上你已经知道但配置里没写的东西:「注意 docs/ 下的历史文档不算;legacy/v1-adapter.ts 的间接调用要算并标注。」

它翻四十个文件,主对话里什么都没发生。几分钟后回来一张表:十七处,九个文件,外加两条疑点。

关键的一步在这里:清单回来之后,立刻把它写进笔记文件的待办清单,不要让它只躺在对话里。

理由在第 8 章讲过:躺在对话里的东西,一次压实就可能变形。这份清单是接下来三小时的路线图,它必须在窗口外面有一份。写进去的时候顺手排个序——先改被依赖最少的文件,最后改 src/api/client.ts,因为它改了会牵动所有人。

那两条疑点你现在就处理掉。子代理拿不准的地方,你花两分钟看一眼源码,得出结论,写进「已确认事实」。疑点不过夜,留着它们,后面每次撞上都要重新纠结一次。

上下文此刻的样子:常驻部分没变,多了一张十七行的表和几条事实。占用大约一成到一成五。清单来自几万 token 的调查,但那几万 token 不在这里。

你在哪里插手:审清单,处理疑点,定顺序。三件事都不该交给模型。

10.4 阶段③:首个切片——只加载两个文件加一段规范

动作:选。

不要说「开始迁移吧」。这句话会让它把九个文件全读进来。

要说:「读 src/checkout/cart.tsdocs/api-migration.md 的『购物车接口映射』那一节,把 cart.ts 里的两处 v1 调用改成 v2。别动其他文件。」

三个约束叠在一起:具体的文件、文档的具体一节、明确的不要做什么。第三条尤其重要——不加这句,它改完 cart.ts 会顺手去看 checkout.ts,因为它觉得那样更周到。周到是要付 token 的。

第一个切片故意选小的、依赖少的。它有两个作用,都不是「改完这个文件」:

**一是校准。**改完你要仔细看这一处 diff。它对 v2 接口的理解对不对?错误处理的写法符不符合团队约定?如果第一个切片就跑偏,后面十六处会用同一种方式跑偏,而你要到很晚才发现。这是全程最值得你花时间读代码的十分钟。

**二是产出模式。**第一个切片做完,你就有了一个可复述的做法:「读那个文件加映射表那一节 → 改调用 → 更新对应测试 → 追加一行笔记」。后面十六处都套这个模式。

改完立刻在笔记文件里追加一行,格式固定:

- [x] src/checkout/cart.ts — 2 处已迁移。v2 的 amount 单位是分,与 v1 一致。错误码从 code 改为 error.code。

注意这行里塞了两条事实。它们本来只存在于这一轮的对话里,现在到了窗口外面。

上下文此刻的样子:两个文件的内容、一段 diff、一轮讨论。占用大概两成。

你在哪里插手:审第一份 diff,逐行看。就这一次,之后可以放松。

10.5 阶段④:边界压实——在切片完成时压,不在满了时压

动作:压。

现在假设你已经做完三四个切片,占用到了五成。

诱惑是继续跑,反正还有一半。第 8 章说过为什么不行:等到自动压实在约 95% 触发(这是 Claude Code 的产品行为,随版本可能变),你没有选择权。它在你手上正做着一半的编辑时切进来,摘要出来是什么样,你只能接受。

主动压的时机不是「占用到了多少」,是「刚好做完一个切片、还没开始下一个」。这个时刻上下文里的状态最干净:没有半截的编辑,没有跑到一半的测试,笔记文件刚更新过。

压之前,按第 8 章的顺序把四类事实固化进笔记文件:

  1. 已确认的接口事实——amount 单位是分、错误码字段从 code 变成 error.code、v2 的退款接口需要额外传 reason
  2. 已做的决定和理由——「legacy/v1-adapter.ts 保留不动,因为订单后台还在用,第二期再删」。理由必须写,否则下一轮 agent 会重新提议删掉它。
  3. 失败过的路径——「试过直接改 client.ts 的基础路径来一次性切换,导致 tests/checkout/refund.test.ts 挂了四个用例,已回滚,改为逐文件迁移」。这条最容易漏,也最贵,10.7 单独讲。
  4. 当前进度与下一步——改到第几个,下一个是谁。

固化完再压。压完做三件事验证:让它复述完成判据(错了就是压丢了目标)、让它说出下一个要改的文件(错了就是压丢了进度)、随机问一条已确认事实比如「amount 的单位」(错了就是压丢了关键结论)。

三问都对,继续。有一项错,把笔记文件重新读一遍再问。

上下文此刻的样子:从五成掉回一成半左右,但笔记文件长了三十行。信息没丢,只是换了个地方存。

你在哪里插手:决定压的时机,检查四类事实齐不齐,做压后三问。

10.6 稳定前缀:这三小时里最贵的一个隐形指标

在讲后面两个阶段之前,插一段账。它解释了为什么 10.2 强调「常驻部分不该变」。

Manus 团队做生产 agent 的经验里有一条很刺眼的结论:KV-cache 命中率是生产 agent 最重要的单项指标。不是准确率,不是工具调用成功率,是缓存命中率。

原因是 agent 的负载形状很特殊。他们给了两个量级参考:输入输出 token 比约 100

一个任务平均约 50 次工具调用。也就是说 agent 干活时,绝大部分算力花在反复重放一个越来越长的输入上,而不是生成内容。每次工具调用后,整个上下文要再过一遍模型。

如果输入的前缀没变,这部分可以走缓存。命中和未命中的输入 token 单价可以差约 10 倍(这是他们撰文时的定价,定价会变,这里只用来说明量级,别当成当前报价)。

前缀一旦在某个位置变了,从那个位置往后的缓存全部失效。所以他们的做法是保持提示前缀稳定、上下文只追加不改写;要禁用某个工具时,宁可掩码 logits 也不把工具从定义里删掉——删掉会让缓存整段作废。

你不是在做平台,掩码 logits 这种事你也做不了。但对普通开发者,这条经验有三个能直接照做的推论:

**第一,别在会话中途反复改系统提示和工具集。**跑到一小时的时候你觉得 CLAUDE.md 里少了一条约定,顺手加上去——这一改,整个会话的缓存前缀从头失效。要改,就在 10.2 那个阶段改完,或者忍到下一次重开会话再改。中途需要补的约定,写进笔记文件,那是追加,不是改写。

**第二,别在提示里塞每轮都变的东西。**时间戳是最典型的。「当前时间:2026-09-05 14:32

」放在系统提示开头,看着无害,实际是每一轮都把整个前缀作废一次。同类的还有每轮重新计算的进度百分比、随机 ID、动态排序的文件列表。

**第三,上下文尽量只追加不改写。**这条和笔记文件的写法直接相关:追加一行「cart.ts 已完成」比回头去改上面某一行的状态更好。前者是追加,后者会动到历史。

这三条不难做,难的是意识到「顺手改一下系统提示」是有代价的。在一次三小时、五十次工具调用的任务里,这个代价按轮次累积。

第10章:一次三小时任务的上下文占用曲线

图 10.1:横轴是时间,纵轴是上下文占用。看两件事。一是曲线形状——它是锯齿状的,每次到边界压实就掉一次,而不是一路爬到 95% 撞线。二是四种动作发生的位置:写贯穿全程(下方那条一直在长的笔记文件),隔集中在早期调查,选发生在每个切片开头,压只在切片之间的边界。虚线那条是「什么都不做」的对照,它一路爬到顶,然后被自动压实拦腰砍断。

10.7 阶段⑤:批量推进——保留错误,以及别让它陷进定式

动作:循环。选 → 做 → 写笔记 → 到边界压。

剩下的十几处调用套第一个切片的模式走。这段是任务的主体,一小时到一个半小时,你介入很少。但有两个陷阱专门在这段里出现。

陷阱一:顺手把失败清掉。

第六个切片,agent 改 src/checkout/refund.ts,改完跑测试挂了三个。你看到一堆红色报错,本能反应是让它重来,或者干脆清一下上下文让它「换个思路」。

别清。保留失败的动作和报错,这是 Manus 那份经验里的另一条,而且是最违反直觉的一条。

那三个失败的用例是模型手里最硬的证据。它看到「传了 reason 但服务端返回 400」这个具体报错,才能推出 v2 的 reason 字段有枚举约束。你把报错清掉,它就只剩猜测,很可能换一种方式再错一次。

具体的做法是:报错留在上下文里直到问题解决,解决之后把结论写进笔记文件的「失败过的路径」,然后这段原始报错可以在下次压实时安心丢掉。留证据是短期的,留结论是长期的。

最坏的做法是一挂就 /clear。那等于每次踩坑都把踩坑的记忆一起删掉,agent 会在同一个坑里循环,而你看着它循环还以为它笨。

陷阱二:它开始机械复读。

跑到第十一二个切片,你会注意到输出变得高度同质。每次都是同样的开场白,同样的四步,同样的措辞。一开始你觉得这是好事——很规范嘛。

然后它开始出错,而且是那种「格式对、内容错」的错。比如它给 src/checkout/coupon.ts 也加了退款相关的错误处理,而这个文件压根没有退款逻辑。它只是在重复前十个文件的形状。

这就是 few-shot 定式:上下文里躺着十几段结构完全相同的操作记录,模型把它们当成了范例。第 3 章那个「上下文分心」在这里具体化了——上下文里的模式压过了模型本来的判断力。

打断它的办法不需要多复杂:在措辞和序列化上引入轻微变化。有时候说「改一下 coupon.ts 里的 v1 调用」,有时候说「coupon.ts 还剩两处没迁,处理掉」。笔记文件里追加的行,有时候写完整句,有时候只写要点。测试有时候单跑一个文件,有时候跑整个目录。

这些变化本身没有任何信息量,它们的作用只是让上下文不要形成一个太整齐的模板。听起来像迷信,但它对应一个具体的机制:模型在做下一步预测,而一个高度规整的上下文会让「继续照抄这个模式」成为最高概率的续写。

介入的信号有两个,看到任意一个就动手:它开始给不需要的文件加相同的东西,或者它的解释里出现了和当前文件明显不符的措辞(比如在讲 coupon.ts 时提到「退款流程」)。

10.8 阶段⑥:收口——让失败信息帮它纠偏

动作:压 + 写。

最后半小时。所有调用都迁完了,现在跑 tests/checkout/ 全量。

第一次跑,大概率不会全绿。这很正常——前面每个切片只跑了局部测试,跨文件的交互问题现在才暴露。

这时候不要压实。测试输出正是最有价值的上下文,而且是你唯一一次能拿到全局视图的时刻。让它带着完整的失败列表去查。

但要控制体积。全量测试输出可能几千行,里面绝大部分是通过的用例。这是第 9 章说的沙箱式隔离的用武之地:让它把输出过滤掉通过项,只留失败用例的名字、断言和堆栈的前几行。几千行变几十行,信息一点没丢。

修完之后,最后一件事是对照完成判据。逐条念出来,逐条验证:

  • v1 调用还有没有?重新派一次 v1-usage-scanner。这次它应该回「共 0 处」。这是全程最漂亮的一个瞬间——同一个子代理,开局给你十七处,收官给你零。
  • 测试全绿了吗?跑,看。
  • docs/api-migration.md 的映射表和实现一致吗?让它对一遍,不一致就改文档。

三条都过,任务结束。最后把笔记文件收个尾:把「待办清单」清空,把「已确认事实」留着——那些事实对下一期删 legacy/ 的工作还有用。

10.9 长任务四检查点

上面六个阶段里有四个时刻你必须停下来看一眼。它们不是提醒,是关卡——不合格就别往下走,往下走的成本只会更高。

检查点触发条件你要看什么不合格怎么办
开局校准第一个切片做完,diff 刚出来逐行读这份 diff:对 v2 的理解对不对、错误处理符不符合团队约定、有没有顺手改无关的东西别急着改第二个文件。先把偏差的原因写进笔记的「已确认事实」或补进 CLAUDE.md,再让它重做第一个切片。第一次没校准,后面十六次会用同样的方式错
边界压实一个切片刚完成、下一个还没开始,且占用过半四类事实齐不齐(接口事实、决定与理由、失败路径、进度与下一步);压完做三问:复述完成判据、说出下一个文件、随机抽一条已确认事实三问有一项答错,就是压丢了。重新读笔记文件再问一次;还错,说明笔记文件本身写得不够,补完它再压
定式检测连续做完五六个结构相同的切片之后它有没有给不需要的文件加相同的处理;它的解释里有没有和当前文件不符的措辞;输出是不是每次一模一样换措辞、换任务粒度、换测试跑法,人为制造一点不规则。如果已经错了,把错误的那次改动回滚,并在笔记里写明「coupon.ts 无退款逻辑,不要加退款错误处理」
收口验收自认为做完了完成判据逐条验证,不接受「应该没问题了」。重新派只读子代理扫一次 v1 调用,跑全量测试,对文档有一条不过就不算完成。特别注意:不要在这个阶段接受任何「这个用例本来就不稳定」之类的解释,除非你自己确认过

四个检查点的共同点是:它们都要求你看具体的东西,而不是问 agent 感觉如何。问它「都做完了吗」,它会说做完了。看 v1-usage-scanner 返回的数字,那是零或者不是零。

10.10 三个信号说明该重开一局了

不是所有长任务都能救。有时候正确的动作是放弃续跑,带着笔记文件重开一个窗口。

问题是「重开」这个动作代价不小——你要重建对话里的默契,还要花时间让新会话进入状态。所以要有明确信号,不能凭心情。三个:

**信号一:压实之后三问答不对,读了笔记文件还是答不对。**这说明信息已经不在窗口里、也不在文件里了,你在一个既没有状态、也没有干净起点的会话里挣扎。重开,同时补笔记文件——它答不对的那部分,就是你笔记里缺的那部分。

**信号二:出现了上下文投毒,而且你已经在同一个会话里纠正过。**agent 引用一个不存在的函数,你纠正了,几轮之后它又用回去。第 3 章说过为什么:错误的原文还在窗口里,你的纠正只是在旁边加了一句相反的话,制造了一次冲突。这种情况下继续纠正是纯浪费。重开,并在笔记的「已确认事实」里明确写一行「applyV2Discount 不存在,正确方法是 applyDiscount」。

**信号三:同一个坑,第三次。**同一个文件第三次改回错的版本,同一个测试第三次用同样的方式挂掉,同一个约定第三次被违反。前两次可能是意外,第三次说明这个错误模式已经在上下文里扎根了——每一轮它都在给自己提供「上次就是这么干的」的证据。

反过来,有三种情况不该重开,它们看起来像信号但不是:

  • **只是变慢了。**慢是占用高的正常表现,压实能解决,不用重开。
  • **只是一次答错。**长任务里偶尔答错很正常,重问一次或者让它读笔记文件,通常就好了。
  • **只是你烦了。**这个最常见。烦的时候人倾向于清空重来,因为那个动作给人掌控感。但如果状态都在笔记文件里,重开的成本确实不高;如果不在,那你烦躁时做的这个决定会让你损失半小时。判断标准很简单:看一眼笔记文件,如果只凭它就能让一个新会话继续干活,那随时可以重开;如果不能,先把它补完再说。

这条其实是整章的检验:一份好的笔记文件,能让「重开」从一次灾难变成一次例行操作。

10.11 把一次真实长任务跑成锯齿曲线