Lesson 0006 · Productivity · 只能人启动(user-invoked)· 桥接课

handoff:跨会话的桥

下午五点。你和 agent 已经聊了三个小时,想法被 grill-with-docs 面试得差不多了, 但 spec 还没写;你注意到回复开始变慢、变钝——会话正在逼近 smart zone(大约 12 万 token 之后,模型推理质量开始下滑的区间)。 硬撑下去,后面的 spec 质量会打折;直接关窗,三个小时的讨论就蒸发了。 handoff 就是为这个时刻准备的:你敲 /handoff, 它把当前会话压缩成一份「移交文档」,存进操作系统的临时目录; 你新开一个会话、把这份文档指给新 agent 看,讨论从断点继续。 它的 SKILL.md 一共只有 16 行,是全套 skill 里最短的一批—— 这节课我们就逐行把它读透,再把它在整条流程里的两次登场讲清楚。

1. 它在整个系统里站在哪

0001 把 22 个 skill 分成三层:配置层、编排层、纪律层。handoff 哪一层都不太像—— 它不在主流程「想法 → 交付」的链条里面占某一步,而是趴在两个会话之间的缝上。 ask-matt 的地图专门给它开了一节,叫 Crossing sessions(跨会话),同一节里只放着它和 harness 自带的 /compact 两个;docs 的说法是:它是一个「随时可以伸手拿」的 standalone, 「坐在两个会话的接缝上,而不是坐在某条构建链里面」。

别和 0005 的 seam 混为一谈 docs 原文说 handoff「sits at the seam between two sessions」——这里的 seam 是日常英语里的「接缝」, 是个比喻。codebase-design(0005)里那个 seam 有精确定义:模块接口所在的位置、 不改代码就能换行为的点。两个词拼写相同、精度不同,谈模块形状时只用 0005 那个意思。

它是 user-invoked(只能人启动)的:只有你亲手敲 /handoff 它才会跑, 模型不会自己加载它,任何其它 skill 也调不动它(第 7 节细讲这个机制)。 所以你在 ask-matt 的正文里会看到,凡是该用 handoff 的地方,措辞都是让「你」去敲—— 路由图能把人带到桥边,过桥的动作得人来完成。

在 0001 的系统地图里,它出现在两处:主流程图上「嘴上说不清?」那条岔路的两端 (/handoff/prototype/handoff), 以及「跨会话桥」那条常驻角色里——换窗口用 handoff,同窗口压缩用 /compact

罗盘:MISSION.md · 地图:0001 · 路由规则:ask-matt/SKILL.md 的 Crossing sessions 一节 · 人读文档:docs/productivity/handoff.md

2. 它是什么、什么时候用

2.1 一句话:把会话压缩成可恢复的核心

handoff 把当前会话总结成一份 handoff document(移交文档)—— 一份写给新 agent 看的 markdown 文档,新 agent 读完就能接着把活干下去。 docs 反复强调的是同一个词:compaction(压缩)—— 把整段对话挤到只剩「可以恢复的核心」,新 agent 继承的是势头,不是噪音。 它不等同于「把聊天记录存个档」:已经落盘成正式产物的东西(spec、ADR、issue、提交、diff) 一律只引用、不复制,文档里只装还飘在空中、没落地的讨论。第 5 节把这条纪律拆开讲。

2.2 什么时候伸手

调用方式只有一种:你敲 /handoff,可以带一句参数说明下一个会话要干什么 (frontmatter 里的 argument-hint 写的就是这句提示: "What will the next session be used for?")。模型不会自己伸手—— docs 原话:「the agent won't reach for it on its own」。 典型场景有四类,加上两类容易误用的对照:

情境 该用 handoff 更该去哪 / 注意
会话逼近 smart zone,回复开始变钝,但活还没干完 。ask-matt 明说:别带着变钝的窗口硬撑(don't push on degraded),handoff 换新线程继续
一天收工,明天(或下周)接着干 。把还在空中的讨论带走,落盘的产物明天还在原地
要把手头的活交给另一个 agent(或另一个人) 。移交文档本来就是写给「another agent」看的
主流程面试到一半,发现问题嘴上说不清,需要可运行的答案 。这正是 prototype 岔路两端的桥(第 6.1 节) /prototype(0008)做原型,handoff 负责往返
只是想压缩一下上下文,但人还想留在当前窗口继续聊 不该 用 harness 自带的 /compact(第 4 节对照)
grill → spec → tickets 还没走完,窗口有点挤了,想 compress 一下 不许 compact;撑不住才 handoff ask-matt 的 context hygiene:这三步要在一个不中断的窗口里走完;真要断,用 handoff 换窗,不用 compact 原地压(第 4 节)
活已经干完,想给成果留个记录 不该 那是 spec、issue、ADR、commit 的职责;handoff 只装「还没落地」的线索
不知道该走哪条流程 不该 ask-matt(0019)

权威原文: skills/productivity/handoff/SKILL.md · 场景描述: docs/productivity/handoff.mdaihero.dev/skills-handoff)· smart zone 规则: ask-matt/SKILL.md 的 Context hygiene 一段

3. 逐行精读 SKILL.md(全文 16 行)

这个 skill 的全部指令只有 16 行,值得逐句拆开——每一行都对应一个「为什么」。 先看全文(frontmatter 是文件头部两条 --- 之间的元数据区,给 harness 读的; 正文才是给 agent 的行为指令):

---
name: handoff
description: Compact the current conversation into a handoff document for another agent to pick up.
argument-hint: "What will the next session be used for?"
disable-model-invocation: true
---

Write a handoff document summarising the current conversation so a fresh agent
can continue the work. Save to the temporary directory of the user's OS - not
the current workspace.

Include a "suggested skills" section in the document, which suggests skills
that the agent should invoke.

Do not duplicate content already captured in other artifacts (specs, plans,
ADRs, issues, commits, diffs). Reference them by path or URL instead.

Redact any sensitive information, such as API keys, passwords, or personally
identifiable information.

If the user passed arguments, treat them as a description of what the next
session will focus on and tailor the doc accordingly.

3.1 frontmatter 四行

作用 为什么这样定
name: handoff skill 的名字,也是你敲的命令 /handoff
description: Compact the current conversation… 一句话说明它是干什么的 user-invoked skill 的 description 是写给人看的——人在浏览 slash 命令列表时读它。仓库的 invocation 规则明确:人读的 description 不要带 "Use when the user says…" 这类给模型看的触发词清单。所以这一行很短,没有触发条件
argument-hint: "What will the next session be used for?" 你敲 /handoff 时,输入框里给你的参数提示:下一个会话要用来干什么? 和正文最后一条指令配套:你回答这个问题,文档就按你的回答裁剪(见 3.2 第 5 条)
disable-model-invocation: true Claude Code 侧的开关:禁止模型自己调用这个 skill 移交时机是人对窗口质量的判断(是不是变钝了、是不是收工了),作者把它留给人。配套的 Codex 侧开关在 agents/openai.yamlpolicy.allow_implicit_invocation: false,两个 harness 保持一致(第 7 节细讲)

3.2 正文五条指令

  1. 写移交文档,存到操作系统的临时目录——不是当前工作区。 macOS 上是 $TMPDIR,Linux 上是 /tmp,Windows 上是 %TEMP%。 为什么偏偏不放仓库里?docs 给了答案:这样它「就永远不会变成又一个需要维护的产物」。 移交文档是一次性脚手架——两个会话之间的桥,过完桥就没用了; 仓库自己的记忆(spec、ADR、issue、代码提交)才是长期档案。 把短期脚手架混进长期档案,它很快就会过时、然后开始对下一个人撒谎。 0005 讲过 improve-codebase-architecture 的报告也写临时目录,是同一个道理: 用完即弃的东西,不进仓库。
  2. 文档里带一节 "suggested skills"。 这一节建议接手的 agent 接下来该调用哪些 skill。 也就是说,handoff 传递的不只是上下文,还有路由信息—— 「下一个会话应该从哪个 skill 继续」。这是 22 个 skill 里唯一一个 把「建议下一步调谁」写进自己产物的 skill:它是桥,桥头还立着路标。
  3. 已经被其它产物记录过的内容,不要复制,用路径或 URL 引用。 括号里点名了六类产物:specs、plans、ADRs、issues、commits、diffs。 这条是移交文档能保持「薄」的根本原因——落盘的东西留在原地,文档只带还飘在空中的。 为什么要这么严格?因为复制制造第二个事实来源:spec 抄进 handoff 文档之后, spec 一改,文档里的副本就开始骗人。引用则永远指向最新的原文。 第 5 节会把「什么归仓库、什么归 handoff」的分工画成表。
  4. 脱敏:API key、密码、个人可识别信息(PII)统统抹掉。 PII 指能定位到具体个人的信息(姓名、邮箱、电话之类)。 为什么这一条要明文写出来?因为 handoff 是一次「导出」动作: 文档会被带到另一个会话、甚至另一个 agent 或另一个人手里, 离开当前窗口的上下文必须先打码。会话里聊到的密钥,不该跟着桥过河。
  5. 如果你敲命令时带了参数,把参数当成「下一个会话的焦点」,按它裁剪文档。 比如你敲 /handoff 接下来把讨论落成 spec, 文档就会朝「方便接着跑 /to-spec」的方向组织—— 跟 spec 相关的决策往前放,suggested skills 里大概率第一个就是 to-spec。 这就是 frontmatter 里那句 argument-hint 的落点。

3.3 旁边的 agents/openai.yaml

目录里还有一个三行多的 agents/openai.yaml,是 Codex 侧的元数据: interface.display_name(选择器里显示的名字 "Handoff")、 interface.short_description(一句话简介), 以及关键的 policy.allow_implicit_invocation: false—— 它和 frontmatter 的 disable-model-invocation: true 是一对, 让「只能人启动」在两个 harness 里同步成立。 仓库的 invocation 规则要求这两个开关永远同进同退:一个 skill 要么在两个 harness 里都是 user-invoked,要么都不是。

逐行对象:skills/productivity/handoff/SKILL.md · Codex 元数据:skills/productivity/handoff/agents/openai.yaml · 双 harness 同步规则:.agents/invocation.md

4. handoff vs /compact:分叉还是原地继续

/compact 是 harness 自带的命令(不是本仓库的 skill):它把当前会话前面的轮次总结掉, 你留在同一个窗口接着聊。它和 handoff 做的是两件不同的事, ask-matt 用一句话钉死了区别:「/handoff forks; /compact continues.」—— handoff 是分叉(开一个新窗口),compact 是继续(留在原窗口)。

/handoff(本仓库 skill) /compact(harness 自带)
人去哪 新开一个会话,把移交文档指给它看;不在原地继续 留在原会话,前面的轮次被总结后接着聊
留下什么 操作系统临时目录里的一份 markdown 移交文档 没有独立文件;摘要只存在于会话内部
保真方式 选择性保真:还在空中的讨论用对话自己的话说清楚,已落盘的产物用路径/URL 引用 有损压缩:逐字历史被摘要替换,细节会丢
适用时机 窗口要满了、要收工了、要交给别人/别的 agent、要岔出去做 prototype 阶段之间的有意断点,且你不介意丢掉逐字历史
主要风险 人忘了真的去开新会话引用文档——桥搭了没人过 在阶段中途压缩,agent 会迷路(ask-matt 原话:Don't compact mid-phase — the agent can lose its way)
grill → spec → tickets 这段路,两种压缩都不许随便用 ask-matt 的 context hygiene 规则:从 /grill-with-docs/to-tickets 要保持在一个不中断的上下文窗口里——面试、需求文档、拆票共享同一套推理, 中途不要 compact。那窗口实在要满了怎么办?规则给了唯一出口: 别带着变钝的窗口硬撑,用 /handoff 换新线程继续。 也就是说这段路上 compact 是禁用的,handoff 是应急出口。

5. 移交文档里装什么、不装什么

docs 用「What the document carries」一节列了四样东西。逐项加上定义:

  1. The live thread(还在空中的一线讨论)——正在做什么、为什么这么做、下一步是什么, 用这段对话自己的语言写。已经写进其它文件的部分,从这份文档里减掉。
  2. Suggested skills(建议接手的 skill)——指给下一个 agent 看: 继续干活该调用哪些 skill(见第 3.2 节第 2 条)。
  3. References, not copies(引用,不是副本)—— spec、计划、ADR、issue、diff 这些已经落了地的细节,用链接和路径指过去。
  4. Redacted secrets(打过码的敏感信息)—— API key、密码、PII 在写盘之前抹掉。

把「引用,不是副本」这条推到极端,就得到一张分工表——每类信息各有自己的家, handoff 只收留还没找到家的:

信息 它的家在哪 handoff 文档怎么处理它
定下来的需求 spec(to-spec 产出,发进 Issue tracker 或仓库) 引用路径/URL
拆好的工作票 Issue tracker 里的 issue(to-tickets 产出;本地 tracker 是 .scratch/<feature>/issues/ 下一票一文件) 引用 issue 编号或路径
领域术语和难逆转的决策 CONTEXT.md 和 ADR(架构决策记录,domain-modeling 维护,见 0004) 引用文件路径
已经写好的代码改动 git 的 commit 和 diff 引用提交或分支
调研结论 research 留在仓库里的 markdown(0007) 引用文件路径
还在讨论、没落成任何产物的东西 没有家 这是 handoff 文档的正文:做了什么、为什么、接下来呢
为什么「薄」是这个 skill 的设计目标 移交文档越薄,过桥的成本越低:新 agent 读一份只装活线索的文档, 需要细节时顺着引用去读 spec 和 ADR 的最新原文。 如果文档把什么都抄一份,它立刻开始腐烂——spec 每改一次,文档就旧一分。 「References, not copies」不是洁癖,是让这份文档在写完那一刻之后仍然可信的唯一办法。

6. 它在主流程里的两次登场

6.1 登场一:prototype 岔路两端的桥

主流程第 2 步(ask-matt 原文):面试过程中发现有的问题光靠对话定不下来—— 状态机的手感、业务逻辑的取舍、必须亲眼看到的 UI——就岔出去做原型, 两端都用 /handoff 当桥

主线程(grill 到一半,问题说不清) │ │ /handoff 出去(带上「新会话用来回答这个问题」的参数) ▼ 新会话 ── /prototype ── 拿到可运行的答案(原型代码用完即弃) │ │ /handoff 回来(把答案压缩成移交文档) ▼ 主线程引用这份文档,继续面试 → /to-spec → …

注意回来的那一步:答案是被主线程引用的,不是把原型代码搬进主线—— prototype 的纪律是「留下答案、删掉代码」(0008 详讲), handoff 正好是这个纪律的搬运工:它把「答案」从原型会话运回主线程, 代码本身一文不带。0003 的表格里也写了这条岔路的完整走法。

6.2 登场二:smart zone 的应急出口

第 4 节那条 callout 已经讲了规则本身,这里补全它的位置: smart zone 是 ask-matt 引用的一个外部词条,指「模型推理仍然敏锐的窗口范围」, 对当今第一梯队的模型大约是 12 万 token。规则的应用场景写得很具体: 如果会话在 /to-tickets 之前就逼近了这个范围, 不要硬撑——/handoff,换一个新线程继续。 这是主流程上唯一官方认可的「中断后继续」手段。

6.3 对照:implement 的换窗不需要 handoff

主流程后半段也频繁换会话——每张票都新开一个干净会话跑 /implement—— 但那里没有人敲 /handoff。区别在于移交的载体

计划外的换窗(handoff) 计划内的换窗(implement)
触发 窗口要满了、要收工了、要岔出去——上下文还没落成正式产物 票已经拆好——上下文已经落成正式产物
载体 临时目录里的移交文档(一次性,过桥即弃) Issue tracker 里的 issue(长期产物,本身就是写给 agent 看的)
谁发起 人敲 /handoff 人新开一个会话敲 /implement,带上那张票

作者级的心智模型是:/to-tickets 是移交载体切换的分界线。 拆票之前,上下文活在对话里,换窗只能靠 handoff 文档搬运; 拆票之后,上下文已经搬进 Issue tracker,每张 issue 就是一份长效的移交文档, 新会话直接拿票开工,不再需要临时桥。

岔路与应急出口:ask-matt/SKILL.md 的 step 2、Context hygiene、Crossing sessions 三节 · 岔路的表格版:0003 · smart zone 词条:aihero.dev/ai-coding-dictionary/smart-zone

7. 为什么只有人能启动它

仓库的 .agents/invocation.md 把 user-invoked 的机制写得很死: frontmatter 里 disable-model-invocation: true(Claude Code 侧) 加上 agents/openai.yamlpolicy.allow_implicit_invocation: false(Codex 侧), 效果是「除了人亲手敲名字,没有任何东西能触发它」——模型不能,其它 skill 也不能。 这和 model-invoked skill 形成鲜明对比:0005 讲过,别的 skill 想用 codebase-design, 只要在正文写一句自然语言的「Run the /codebase-design skill」就能把它拉进来(prose 调用); 而对 user-invoked 的 handoff,不存在任何 prose 调用的通道

所以 ask-matt 地图里凡是该用 handoff 的地方,主语都是「你」—— 它能做的只是把路由建议写到人眼前。整个 prototype 岔路上那两次 handoff, 都是人亲手搬的。理解这一点,看 ask-matt 的措辞就不会觉得别扭了: 它不是客气,是机制上够不到。

为什么作者把它做成 user-invoked?(推断,原文未明说) SKILL.md 和 docs 都没有解释动机,以下是从机制反推的读法,供你判断: 移交时机本质上是一个人对会话质量的主观判断——窗口是不是变钝了、今天是不是该收工了、 这个问题值不值得岔出去——这些判断模型自己做不稳; 而且 handoff 的语义包含「当前会话即将被放弃」,放弃哪个会话是人的决定, 不适合让模型自作主张。把它做成 user-invoked,等于把这个判断的扳机留在人手里。

这个机制还有一个实用的推论:description 里没有触发词清单, 所以「模型很少主动建议 handoff」不是 bug,是设计的直接后果。 想让 handoff 在对话里被更频繁地提起,该改的是 ask-matt 的路由文案, 不是给 handoff 的 description 塞 "Use when…"(第 9.3 节的对照表里还有这条)。

机制规则:.agents/invocation.md · 开关本体:handoff/SKILL.md frontmatter + handoff/agents/openai.yaml

8. 依赖谁、被谁需要

                 概念上指向(引用,不调用)
   ┌──────────────────────────────────────────────────┐
   │  to-spec 的 spec · to-tickets 的 issue ·          │
   │  domain-modeling 的 CONTEXT.md / ADR ·            │
   │  research 的 markdown · implement 的 commit/diff   │
   └──────────────────────▲───────────────────────────┘
                          │ references by path/URL
                 ┌────────┴─────────┐
                 │     handoff      │  user-invoked:没有 skill 调得动它
                 └────────┬─────────┘
                          │ 被人路由到它(不是被 skill 调用)
   ┌──────────────────────┴───────────────────────────┐
   │  ask-matt:step 2 的 prototype 岔路(人敲两次)     │
   │  ask-matt:Context hygiene 的 smart-zone 应急出口   │
   │  ask-matt:Crossing sessions 一节                  │
   └──────────────────────────────────────────────────┘

9. 会留下什么、用完接什么、想微调改哪里

9.1 副作用:全仓库最干净的一类

会不会碰 结论
写文件 写一份:操作系统临时目录里的 markdown 移交文档($TMPDIR / /tmp / %TEMP%),不进仓库
仓库里的文件 一律不碰——不建目录、不改 CONTEXT.md、不加 docs
Issue tracker 不读不写。issue 只作为被引用的对象出现在文档里
git 不提交、不建分支。commit 和 diff 同样只是被引用的对象

9.2 用完之后,下一步去哪

  1. 必须做的一步:新开一个会话,把移交文档指给它看。 ask-matt 说得很直白:You don't continue in place——你不在原地继续。 桥搭好了要人过桥;只敲 /handoff 不开新会话,等于把行李打包好然后不出门。
  2. 新会话里:agent 读文档,按 suggested skills 一节的建议接着跑—— 通常是回到主流程的下一个环节(/to-spec/implement……)。
  3. 如果是 prototype 岔路回来的那份:在原来的主线程里引用它,继续面试 (0003 的表格:主线引用原型得出的结论)。
  4. 如果进度已经推进到 /to-tickets 之后: 移交载体切换成 Issue tracker 里的 issue(第 6.3 节),之后换窗不再需要 handoff。

9.3 行为不对时,去改哪个文件的哪一段

症状 先查 / 先改 不要误改
移交文档把 spec、ADR 的内容整个抄了一遍,又厚又旧 handoff/SKILL.md 正文第三条指令(references, not copies)的措辞 to-spec 的文档模板——问题不在 spec 写得怎么样
文档被写进了仓库,成了又一个要维护的文件 handoff/SKILL.md 正文第一条指令里「temporary directory…not the current workspace」那句 setup-matt-pocock-skills 的目录约定——那是配置层的另一件事
suggested skills 一节建议得离谱(比如让下一个 agent 去 triage 自己写的票) handoff/SKILL.md 第二条指令;建议是否靠谱的知识源头在 ask-matt 的 flow 描述 .claude-plugin/plugin.json 的 skills 数组——发布清单没有错要在这里改
密钥、邮箱跟着文档过了桥 handoff/SKILL.md 第四条指令(redact)的覆盖范围 各上游 skill 的写法——脱敏是导出这一刻的职责
带参数和不带参数生成的文档没有差别 handoff/SKILL.md 第五条指令;提示语本身在 frontmatter 的 argument-hint description——它不是参数说明的落点
模型从不主动建议 handoff,想让它更常出现 这是 user-invoked 的设计后果:改 ask-matt/SKILL.md Crossing sessions 等处的路由文案,让人更常在地图上看到它 给 handoff 的 description 加 "Use when…" 触发词——invocation 规则明说人读的 description 不带触发清单;也别动 disable-model-invocation,除非你真的想改它的调用性质
Codex 的 skill 选择器里名字或简介不对 handoff/agents/openai.yamlinterface 同一个文件里的 policy 块——那是调用性质,不是展示文案

10. 检索练习

先别往回翻,凭记忆答。选项的长度刻意对齐,不会从版式泄题。答错的题回到对应小节重读。

自测(立即反馈)

1. 敲 /handoff 后,移交文档默认写到哪里?
2. handoff 与 /compact 最准确的区别是?
3. 已经写进 spec / ADR / issue / diff 的内容,handoff 文档该怎么处理?
4. 谁有资格触发 handoff?
5. 敲 /handoff 时带上参数(说明下一个会话干什么)的效果是?
6. grill → spec → tickets 中途窗口逼近 smart zone,ask-matt 建议?
7. 移交文档里的 "suggested skills" 一节是干什么的?
8. 主流程的 prototype 岔路上,handoff 的正确用法是?
额外提取练习(无选项) 合上本页,默写移交文档必带的四样东西(live thread、suggested skills、references、redaction), 并各配一句「为什么」。然后用自己的话讲清 handoff 和 /compact 的区别, 以及「to-tickets 是移交载体切换的分界线」这句话的意思。 最后回想你最近一次窗口拖到变钝的会话:那一刻该敲什么?文档里该引用什么、该带上什么参数?

11. 下一课与一手材料

本课主一手材料(请打开原文读,不要只背本页摘要):

速查页(本课同步): reference/handoff.html

导航: 上一课 0005 codebase-design (词汇地板之二;注意本课第 1 节那个「seam 一词两义」的提醒)。 总览仍回 0001 系统地图; prototype 岔路的主线背景见 0003 grilling, 岔路本身见 0008 prototype(并行编写中)。

建议下一课(0007,并行编写中): research——另一个 standalone: 把阅读跑腿活派给后台 agent,结论落成仓库里一份带引用的 markdown。 和本课的交接点很直接:research 产出的那份 markdown, 正是 handoff 文档「用路径引用、绝不复制」的那类产物—— 落盘的东西归仓库,空中的东西归 handoff。

老师就在会话里。 对本课任何一处有疑问——比如「窗口变钝」到底怎么判断、带参数的 handoff 实际会裁掉什么、 implement 换窗和 handoff 换窗的边界案例、user-invoked 能不能临时改成 model-invoked——直接在对话里问。 回答会回到 SKILL.md / ask-matt / invocation.md 的原文,不会临场编造。 做完检索练习后,回复「练习结果 / 哪里卡住 / 开 0007 或先补 0005」,我们安排下一课。