会话移交如何在多个 AI 编码会话之间保持上下文
AI 编码助理在会话结束时会忘记一切。一份简短的交接笔记记录了你做了什么、有哪些未完成的工作,以及接下来要做什么。
AI 编程助手有一个有限的上下文窗口,而一个真实的任务很少能完全容纳其中。会话会被填满,或者你会结束一天的工作,或者为了保持模型快速响应而开启新的对话。当这些情况发生时,助手所知晓的关于工作的一切都会消失:你做了什么更改、你为什么更改、还有什么问题没有解决,以及你接下来打算做什么。解决方法老套又乏味。当人类交接班时,他们会留下一份笔记。一个 AI 会话也应该留下同样的笔记,并且下一个会话应该首先阅读它。
为什么 AI 会话会思维混乱
上下文窗口是智能体的全部工作记忆。它包含了你打开的文件、你反复论证过的决策、你已经排除的死胡同,以及你脑海中从未写下的半成品计划。所有这些都存在于一次对话中,别无他处。
这种记忆是易失的。由于三个常见原因,它无法在会话结束后保留下来。在执行长任务时,窗口会被填满,最早的消息会超出范围。你合上笔记本电脑,第二天回来时面对的是一个全新的对话。或者,你因为臃肿的上下文会使模型变慢且更容易分散注意力而故意重新开始。在每种情况下,下一次会话都从空白开始,而你付出了同样的代价:重新解释代码库、重申目标,并重新推导你一小时前已经做出的决策。对于一个跨越三到四个会话的任务,大部分的重新解释纯属浪费。
问题不在于智能体健忘。问题在于它的记忆从未被写入任何持久化的地方。你在本次会话中所说的一切都没有保存在磁盘上。解决了这个问题,会话的边界就不再是悬崖峭壁了。
将内存外部化至磁盘
整个想法就是一个举动:将原本存在于上下文窗口中的状态取出,并将其放入一个文件中。交接记录就是那个文件。它是一个小巧、持久的记录,记录了工作的进展情况,在一个会话结束时写入,以便下一个会话开始时可以加载它。
这反映了人们已有的工作方式。轮班工会留下日志。一个要去度假的开发者会编写一份交接文档,以便团队成员可以接手任务。没人会指望下一个人能从一个 diff 中重建三天的思考过程。这份记录承载了思考过程,跨越了这段间隙。出于同样的原因,AI 会话也需要同样的东西,只不过这段间隙不是在两个人之间,而是在与同一个工具的两次对话之间。
一旦状态存在于文件中,连续性就不再依赖于内存了。任何会话,无论是今天还是下周,都可以读取该记录,并带着完整的全局信息继续。上下文窗口就变得可抛弃了,因为重要的部分已经被保存下来了。
一份交接文档实际包含什么
一份好的交接文档只回答四个问题,不多不少。它是一份状态报告,而不是一篇日记。在这里,长度是敌人,因为无论是人还是模型,都不会去阅读大段的文字来寻找相关的那一行。
| 部分 | 包含内容 | 篇幅限制 |
|---|---|---|
| 已完成 | 本次会话完成的工作,每项一行 | 几个要点 |
| 关键决策 | 影响代码的抉择及其原因 | 那些会让人感到意外的决策 |
| 未完成与下一步 | 未完成的工作,以及下一次会话应避免的陷阱 | 最重要的事情放在第一位 |
| 参考指引 | 查找位置:文件、提交、工单、日志 | 确切的路径,而非模糊的提示 |
第一部分是结果摘要,这样下一次会话就知道哪些工作已经完成。第二部分是重新构建起来成本最高昂的部分:决策背后的原因、你拒绝的选项及其理由、代码中不明显的约束条件。第三部分是真正实现连续性的地方,因为它指明了下一步以及附近的“地雷”。第四部分将笔记变成一个索引,这样下一次会话只需阅读三个文件,而不是三十个。
保持内容精炼。一份含糊其辞、内容重复的交接文档,没有人会读完,也就失去了交接的意义。
一个交接模板
固定的格式让笔记写得快,扫读也快。每次都使用相同的标题,意味着接手者能确切地知道去哪里寻找下一步。这是一个在长期任务中经受住考验的模板:
# Handoff: <task name> (<date>)
## Done this session
- Wired the payment webhook to the new queue
- Added retry with backoff, capped at 5 attempts
## Key decisions
- Idempotency key is the provider event id, not our order id.
The provider can resend an event, and order id is not unique per event.
- Chose an at-least-once queue over exactly-once. Handlers are already
idempotent, so duplicates are safe and the setup is far simpler.
## Open / next
- NEXT: the dead-letter path is not wired yet. Failed events vanish
after 5 retries. Start here.
- Trap: the staging webhook secret differs from prod. Do not copy the
prod value into staging config.
## Pointers
- Handler: internal/payments/webhook.go
- Queue setup: internal/queue/consumer.go:42
- Failing test: TestWebhookRetry (currently skipped)
- Related commit: a1b9f30
有两个细节使其行之有效。下一步被明确标出并置于其所在部分的顶部,因此恢复工作时可以直接执行,无需到处寻找。并且,陷阱被明确地表述为陷阱,而不是埋藏在文字描述中,因为交接的全部价值就在于警告接手者,避免犯下他们无法预见的错误。
自动化交接
每次都手动写笔记会产生阻力,而阻力意味着这件事不会发生。更好的方式是自动化。留意会话即将结束的信号,并在那一刻生成或刷新交接内容。
这些信号很容易捕捉。比如,有人输入“我们今天就到这里吧”、“今天就到此为止”或“开始一个新会话”。工具可以检测到上下文窗口即将达到其限制。这些情况中的任何一种都可以触发笔记的生成。笔记内容会根据工作状态进行调整。如果任务已完成,交接内容会很简短,用几行字说明任务已完成及其最终成果。如果工作仍在进行中,笔记会侧重于未完成和下一步的部分,因为这是下一次会话最需要的内容。
这样做的好处是,无需自律,笔记也能保持最新。你不需要记住去写它,也不需要记住去更新它。在你发出完成信号的那一刻,工作状态已经保存在磁盘上,下一次会话也已经有内容可供读取。
何时交接不值得
交接是一种开销,而只有当这种开销能换回些什么时,它才值得。对于一个在单次工作时段内开始并完成的简短、一次性任务,交接带不来任何收益。重命名变量、修复拼写错误、编写一个小型函数:这些任务不存在需要弥合的鸿沟,因此编写记录纯粹是成本。不要这么做。
其价值体现在两种类型的工作中。第一种是任务过于庞大,无法在一次工作时段内完成,此时记录可以跨越你自己的工作时段边界来传递上下文。第二种是在多个人或多个代理之间传递的工作,此时记录就是他们之间的接口。在这两种情况下,与从头开始重建一个下午的上下文所需付出的成本相比,编写几条信息密集的要点的成本是微不足道的。
一个实在的规则是:当工作将超出单次工作时段时,就进行交接。反之则跳过。往成本低的方向猜错,即写了本不需要的记录,会让你花费一分钟。往成本高的方向猜错,即在一个需要多次工作时段的任务上跳过了记录,会让下一次工作时段付出一小时的代价。
保持交接的真实性
交接有一种比没有交接更糟糕的失败模式:变得过时。如果笔记上说死信路径未连接,但你在上一次会话中悄悄地连接了它却没有更新笔记,那么下一次会话读到的就是谎言。它会基于一幅错误的图景进行工作,并浪费时间去发现地图与实际情况不符。错误的交接比缺失的交接更危险,因为缺失的笔记会让你在明知没有任何上下文的情况下重建它,而过时的笔记则会让你相信一个已经错误的上下文。
这就是为什么自动化不仅关乎便利性,而且至关重要。在每次会话结束时刷新的笔记能够追踪现实情况,因为它每次都是根据当前状态重写的,而不是通过手动编辑而慢慢偏离。保留一个规范的交接文件并覆盖它。不要积累一堆带日期的笔记,让读者不得不猜测哪一份是当前的。
这个机制由来已久,而这正是关键所在。自从有轮班制度以来,人们就一直用日志来交接班次。唯一的新情况是,下一班的同事是同一个工具的另一个实例,它对上一班没有任何记忆,除了你留下的笔记之外,没有别的方法获取信息。写下笔记。保持简短。保持真实。下一次会话,无论由谁或什么来运行,都将精确地从你离开的地方开始。