下午五点。你和 agent 已经聊了三个小时,想法被 grill-with-docs 面试得差不多了,
但 spec 还没写;你注意到回复开始变慢、变钝——会话正在逼近
smart zone(大约 12 万 token 之后,模型推理质量开始下滑的区间)。
硬撑下去,后面的 spec 质量会打折;直接关窗,三个小时的讨论就蒸发了。
handoff 就是为这个时刻准备的:你敲 /handoff,
它把当前会话压缩成一份「移交文档」,存进操作系统的临时目录;
你新开一个会话、把这份文档指给新 agent 看,讨论从断点继续。
它的 SKILL.md 一共只有 16 行,是全套 skill 里最短的一批——
这节课我们就逐行把它读透,再把它在整条流程里的两次登场讲清楚。
0001 把 22 个 skill 分成三层:配置层、编排层、纪律层。handoff 哪一层都不太像——
它不在主流程「想法 → 交付」的链条里面占某一步,而是趴在两个会话之间的缝上。
ask-matt 的地图专门给它开了一节,叫 Crossing sessions(跨会话),同一节里只放着它和
harness 自带的 /compact 两个;docs 的说法是:它是一个「随时可以伸手拿」的 standalone,
「坐在两个会话的接缝上,而不是坐在某条构建链里面」。
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
handoff 把当前会话总结成一份 handoff document(移交文档)——
一份写给新 agent 看的 markdown 文档,新 agent 读完就能接着把活干下去。
docs 反复强调的是同一个词:compaction(压缩)——
把整段对话挤到只剩「可以恢复的核心」,新 agent 继承的是势头,不是噪音。
它不等同于「把聊天记录存个档」:已经落盘成正式产物的东西(spec、ADR、issue、提交、diff)
一律只引用、不复制,文档里只装还飘在空中、没落地的讨论。第 5 节把这条纪律拆开讲。
调用方式只有一种:你敲 /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.md (aihero.dev/skills-handoff)· smart zone 规则: ask-matt/SKILL.md 的 Context hygiene 一段
这个 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.
| 行 | 作用 | 为什么这样定 |
|---|---|---|
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.yaml 的 policy.allow_implicit_invocation: false,两个 harness 保持一致(第 7 节细讲) |
$TMPDIR,Linux 上是 /tmp,Windows 上是 %TEMP%。
为什么偏偏不放仓库里?docs 给了答案:这样它「就永远不会变成又一个需要维护的产物」。
移交文档是一次性脚手架——两个会话之间的桥,过完桥就没用了;
仓库自己的记忆(spec、ADR、issue、代码提交)才是长期档案。
把短期脚手架混进长期档案,它很快就会过时、然后开始对下一个人撒谎。
0005 讲过 improve-codebase-architecture 的报告也写临时目录,是同一个道理:
用完即弃的东西,不进仓库。
/handoff 接下来把讨论落成 spec,
文档就会朝「方便接着跑 /to-spec」的方向组织——
跟 spec 相关的决策往前放,suggested skills 里大概率第一个就是 to-spec。
这就是 frontmatter 里那句 argument-hint 的落点。
目录里还有一个三行多的 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
/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-with-docs 到 /to-tickets
要保持在一个不中断的上下文窗口里——面试、需求文档、拆票共享同一套推理,
中途不要 compact。那窗口实在要满了怎么办?规则给了唯一出口:
别带着变钝的窗口硬撑,用 /handoff 换新线程继续。
也就是说这段路上 compact 是禁用的,handoff 是应急出口。
docs 用「What the document carries」一节列了四样东西。逐项加上定义:
把「引用,不是副本」这条推到极端,就得到一张分工表——每类信息各有自己的家, 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 文档的正文:做了什么、为什么、接下来呢 |
主流程第 2 步(ask-matt 原文):面试过程中发现有的问题光靠对话定不下来——
状态机的手感、业务逻辑的取舍、必须亲眼看到的 UI——就岔出去做原型,
两端都用 /handoff 当桥:
注意回来的那一步:答案是被主线程引用的,不是把原型代码搬进主线——
prototype 的纪律是「留下答案、删掉代码」(0008 详讲),
handoff 正好是这个纪律的搬运工:它把「答案」从原型会话运回主线程,
代码本身一文不带。0003 的表格里也写了这条岔路的完整走法。
第 4 节那条 callout 已经讲了规则本身,这里补全它的位置:
smart zone 是 ask-matt 引用的一个外部词条,指「模型推理仍然敏锐的窗口范围」,
对当今第一梯队的模型大约是 12 万 token。规则的应用场景写得很具体:
如果会话在 /to-tickets 之前就逼近了这个范围,
不要硬撑——/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
仓库的 .agents/invocation.md 把 user-invoked 的机制写得很死:
frontmatter 里 disable-model-invocation: true(Claude Code 侧)
加上 agents/openai.yaml 里 policy.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 的措辞就不会觉得别扭了: 它不是客气,是机制上够不到。
这个机制还有一个实用的推论:description 里没有触发词清单, 所以「模型很少主动建议 handoff」不是 bug,是设计的直接后果。 想让 handoff 在对话里被更频繁地提起,该改的是 ask-matt 的路由文案, 不是给 handoff 的 description 塞 "Use when…"(第 9.3 节的对照表里还有这条)。
机制规则:.agents/invocation.md · 开关本体:handoff/SKILL.md frontmatter + handoff/agents/openai.yaml
概念上指向(引用,不调用)
┌──────────────────────────────────────────────────┐
│ 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 一节 │
└──────────────────────────────────────────────────┘
prototype(三明治结构)和对照物 /compact。
但因为它是 user-invoked,这些「需要」全部以「建议人去敲」的形式存在,
不存在任何 skill 到 skill 的调用边。
| 会不会碰 | 结论 |
|---|---|
| 写文件 | 写一份:操作系统临时目录里的 markdown 移交文档($TMPDIR / /tmp / %TEMP%),不进仓库 |
| 仓库里的文件 | 一律不碰——不建目录、不改 CONTEXT.md、不加 docs |
| Issue tracker | 不读不写。issue 只作为被引用的对象出现在文档里 |
| git | 不提交、不建分支。commit 和 diff 同样只是被引用的对象 |
/handoff 不开新会话,等于把行李打包好然后不出门。
/to-spec、/implement……)。
/to-tickets 之后:
移交载体切换成 Issue tracker 里的 issue(第 6.3 节),之后换窗不再需要 handoff。
| 症状 | 先查 / 先改 | 不要误改 |
|---|---|---|
| 移交文档把 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.yaml 的 interface 块 |
同一个文件里的 policy 块——那是调用性质,不是展示文案 |
先别往回翻,凭记忆答。选项的长度刻意对齐,不会从版式泄题。答错的题回到对应小节重读。
本课主一手材料(请打开原文读,不要只背本页摘要):
skills/productivity/handoff/SKILL.md
—— 全部 16 行指令,本课第 3 节逐行精读的对象。
skills/productivity/handoff/agents/openai.yaml
—— Codex 侧的显示元数据与调用开关。
docs/productivity/handoff.md
—— 给人看的叙事版(aihero.dev/skills-handoff):
「compaction」「坐在两个会话的接缝上」「文档带什么」。
skills/engineering/ask-matt/SKILL.md
—— step 2(prototype 岔路)、Context hygiene(smart zone 应急出口)、
Crossing sessions(handoff vs /compact)三节。
.agents/invocation.md
—— user-invoked 的机制规则:谁能触发、description 给谁看、双 harness 同步。
速查页(本课同步): reference/handoff.html
导航: 上一课 0005 codebase-design (词汇地板之二;注意本课第 1 节那个「seam 一词两义」的提醒)。 总览仍回 0001 系统地图; prototype 岔路的主线背景见 0003 grilling, 岔路本身见 0008 prototype(并行编写中)。
建议下一课(0007,并行编写中):
research——另一个 standalone:
把阅读跑腿活派给后台 agent,结论落成仓库里一份带引用的 markdown。
和本课的交接点很直接:research 产出的那份 markdown,
正是 handoff 文档「用路径引用、绝不复制」的那类产物——
落盘的东西归仓库,空中的东西归 handoff。
SKILL.md / ask-matt / invocation.md 的原文,不会临场编造。
做完检索练习后,回复「练习结果 / 哪里卡住 / 开 0007 或先补 0005」,我们安排下一课。