多会话交接 本节摘要:会话会结束,工作不会。交接包(handoff packet)是把「Agent 干了一小时」变成「下一会话第一分钟就出活」的工件。要有意建它,而非事后补。诊断很经典:会话结束,Agent 说「很好,我们取得了进展」;下次会话打开,下一个 Agent 问「我们上回到哪了?」——第一个 Agent 的答案没了。下一个 Agent 重新发现、重跑同样命令、重问人同样问题,烧 30 分钟去恢复上回会话最后 30 秒。坏交接的代价,在任务寿命里每次会话都要付。修法是一份会话结束时自动生成的包:改了什么、为什么、试了什么、失败了什么、还剩什么、下次先做什么。
本节摘要:会话会结束,工作不会。交接包(handoff packet)是把「Agent 干了一小时」变成「下一会话第一分钟就出活」的工件。要有意建它,而非事后补。诊断很经典:会话结束,Agent 说「很好,我们取得了进展」;下次会话打开,下一个 Agent 问「我们上回到哪了?」——第一个 Agent 的答案没了。下一个 Agent 重新发现、重跑同样命令、重问人同样问题,烧 30 分钟去恢复上回会话最后 30 秒。坏交接的代价,在任务寿命里每次会话都要付。修法是一份会话结束时自动生成的包:改了什么、为什么、试了什么、失败了什么、还剩什么、下次先做什么。本节定义交接包的七个字段(summary 一段总结、changed_files 一眼 diff、commands_run 真跑过的、failed_attempts 试过且为何不成的、open_risks 可能咬下次会话的带严重度、next_action 下次第一个具体步、verdict_pointer 验证+审查报告路径)——其中
next_action是承重的:有它一切却没它的交接是状态报告,不是交接。核心纪律是交接是生成的,不是写的(手写的交接是艰难日里被跳过的;生成器读工作台工件发包,Agent 的活是把工作台留在生成器能总结的状态,而非写总结);两种形式(handoff.md人读、handoff.json下个 Agent 加载,同源,分歧时 JSON 赢);反馈日志裁剪(全日志可能上百条,包只带最后 K 条加每条非零退出)。本节还强调留干净状态:交接描述工作,干净状态让工作可续——不是同一件事;完美的handoff.md在下一会话对着半个 diff、临时文件、游离分支、跑不起来的测试时一文不值;所以清理是交接前独立的一阶段,发clean_state.json阻塞问题清单,空列表是交接生成器写包前断言的前置条件。读完本节,你能用生成器把会话结尾变成下次会话的快启动。
对应原课程:Phase 14 · Lesson 40 ·
multi-session-handoff(原英文phases/14-agent-engineering/40-multi-session-handoff/docs/en.md)。前置:第 34 节(仓库记忆)、第 38 节(验证)、第 39 节(审查者)。
阅读完本节,你应当能够:
会话结束。Agent 说「很好,我们取得了进展」。下次会话打开。下一个 Agent 问「我们上回到哪了?」第一个 Agent 的答案没了。下一个 Agent 重新发现、重跑同样命令、重问人同样问题,烧 30 分钟恢复上次会话的最后 30 秒。
坏交接的代价,在任务寿命里每次会话都要付。修法是一份会话结束时自动生成的包:改了什么、为什么、试了什么、失败了什么、还剩什么、下次先做什么。
| 字段 | 回答的问题 |
|---|---|
summary |
做了什么的一段总结 |
changed_files |
一眼的 diff |
commands_run |
真正执行了什么 |
failed_attempts |
试了什么、为何不成 |
open_risks |
什么可能咬下次会话,带严重度 |
next_action |
下次会话迈出的第一个具体步 |
verdict_pointer |
验证 + 审查报告的路径 |
next_action 是承重的那个。一个有了一切却没 next_action 的交接是状态报告,不是交接。
手写的交接是艰难日里被跳过的交接。生成器读工作台工件发包。Agent 的活是把工作台留在生成器能总结的状态,不是写总结。
handoff.md 是人读的。handoff.json 是下个 Agent 加载的。两者来自同样的源工件。若分歧,JSON 赢。
全 feedback_record.jsonl 可能上百条。交接只带最后 K 条加每条非零退出。下次会话需要时加载全日志,但包保持小。
交接描述工作。干净状态让工作可续。不是同一件事。一个完美的 handoff.md,在下次会话对着半个应用的 diff、Agent 忘了的临时文件、游离分支、还没跑就报错的测试时,一文不值。下个 Agent 头十分钟花在给上个 Agent 擦屁股而非建造上,代价每次会话累积。
所以会话不在功能跑通时结束。它在工作台处于生成器能总结、下次会话能信任的状态时结束。清理是独立的一阶段,在交接前跑,而且是一道检查,不是一个习惯——因为习惯是艰难日里被跳过的东西。
| 检查 | 干净意味着 | 脏会阻塞因为 |
|---|---|---|
| 工作树 | 每处改动已提交或显式带便签 stash | 半个 diff 在下个 Agent 看着像有意的工作 |
| 临时工件 | 无 *.tmp、scratch 目录、debug 打印、注释块遗留 |
游离文件污染 diff 与下个 Agent 的心智模型 |
| 测试 | 绿,或红但失败名在 open_risks |
静默的红测试是下个会话踩的陷阱 |
| 功能板 | feature_list.json 状态反映现实(第 36 节) |
过期板把下次会话派去已做完的工作 |
| 分支 | 在预期分支,无 detached HEAD,无孤儿分支 | 错分支意味着下次会话首次提交落错地方 |
清理阶段发一份阻塞问题清单的 clean_state.json;空列表是交接生成器写包前断言的前置条件。建在脏树上的交接不是交接,是转发的烂摊子。两工件配对:清理证明工作台可安全留下,交接证明下次会话知道从哪开始。
原课程 code/main.py 实现:
WorkbenchSnapshot。generate_handoff(snapshot) -> (markdown, payload) 函数。handoff.md 与 handoff.json 到脚本旁的演示运行。def load_snapshot(): return WorkbenchSnapshot( state = json.load(open("agent_state.json")), verdict = json.load(open("outputs/verification/latest.json")), review = json.load(open("outputs/review/latest.json")), feedback = read_jsonl("feedback_record.jsonl"), diff = git_diff_vs_main())
def cleanup_checks(): issues = [] if git_has_uncommitted_partial(): issues.append(block("工作树", "半个未提交 diff")) if find_temp_artifacts(): issues.append(block("临时工件", str(find_temp_artifacts()))) if tests_silent_red(): issues.append(block("测试", "静默红测试")) atomic_write("clean_state.json", json.dumps(issues)) return len(issues) == 0 # 空即干净,允许写包
def generate_handoff(snap): if not cleanup_checks(): raise DirtyTree("拒绝在脏树上写交接") failed = [r for r in snap.feedback if r["exit_code"] not in (0, None)] recent = snap.feedback[-5:] # 最后 K 条 payload = { "summary": summarize(snap.diff, snap.state), "changed_files": files_in(snap.diff), "commands_run": [r["command"] for r in recent], "failed_attempts": [{"cmd": r["command"], "why": r["stderr_tail"]} for r in failed], "open_risks": snap.state.get("blockers", []), "next_action": snap.state["next_action"], # 承重字段 "verdict_pointer": "outputs/verification/latest.json"} atomic_write("handoff.json", json.dumps(payload, indent=2)) atomic_write("handoff.md", to_markdown(payload))
运行 python3 code/main.py 会打印交接正文,并把两文件写盘。
Codex CLI、Claude Code、OpenCode 各发一套不同的压缩(compaction)故事;结构化交接包叠在三者之上。
压缩策略各异;包模式不变。 Codex CLI 的 POST /v1/responses/compact 是服务端不透明 AES blob(OpenAI 模型快路径);回退是本地「交接摘要」作 _summary 用户角色消息追加。Claude Code 在 95% 上下文跑五阶段渐进压缩。OpenCode 做基于时间戳的消息隐藏加 5 标题 LLM 摘要。三种不同机制,同一需求:把挺过压缩的东西序列化成可移植工件。包就是那个工件。
新会话交接不是压缩。 压缩延长会话;交接干净关闭一个、开始下一个。Hermes Issue #20372(2026-04)的框定对:当原地压缩开始降质,Agent 应写紧凑交接、结束会话、在干净上下文里续。包让那切换便宜。错误是压缩到质量崩溃;修法是预算一次早而干净的交接。
每分支每话题一个活动交接。 多智能体协调在过期交接上崩,比在坏模型输出上崩得多。永远包含 branch、last_known_good_commit、与 active | superseded | archived 的 status。过期交接归档;只有活动的驱动下次会话。这是「交接即笔记」与「交接即状态」的差别。
50-75% 上下文时收尾,而非撞墙。 手写模式 playbook(CLAUDE.md + HANDOVER.md)报告:会话在 50-75% 上下文预算结束而非 95% 时结果最好。包生成器在压缩工件污染源状态前干净跑。上下文完整时便宜写;模型已经迷失时贵。
💡 设计要点:交接包的本质是把易失的会话状态序列化成持久的工件——这与第 34 节「聊天易失、仓库持久」、第 31 节「循环闭合在状态文件非聊天」一脉相承。生成而非写,是因为「手写的在艰难日被跳过」——这把纪律从「指望 Agent 自觉」变成「生成器强制」。清理作为独立阶段同理:它是一道检查,不是一个习惯,因为习惯是艰难日被跳过的东西。这两条纪律,把工作台的可靠性从「好日子」抬到「每一天」。
| 运行时 | 交接如何用 |
|---|---|
| 会话结束钩子 | 运行时在用户关聊天时触发生成器;包进 outputs/handoff/<session_id>/ |
| PR 模板 | 生成器的 markdown 也是 PR 体;审查者读它而无需开五个其他文件 |
| 跨 Agent 交接 | 用一个产品(Claude Code)建造,用另一个(Codex)续;包是通用语 |
包小、规整、便宜产出。成本节省每次会话累积。
原课程 outputs/skill-handoff-generator.md:产一个调到项目工件路径的生成器、一个跑它的会话结束钩子、一个下次 Agent 启动读的 handoff.json 模式。
assumptions_to_validate 字段,浮现建造者记录但审查者未打超 1 分的每个假设。下一节,我们把第 31~40 节的全部工作台面,落到真实仓库上——用一个端到端的最小实现,展示指令、状态、范围、反馈、验证、审查、交接如何在一个能改真实代码的工程级 Agent 里协同工作。