全部课程
亲手改一个 Agent/ 改造实录② · 给 agent 加一个"打包带走"/ 改造实录②:给 agent 加一个"打包带走"——从需求到落地 实录
改造实录② · 给 agent 加一个"打包带走"

改造实录②:给 agent 加一个"打包带走"——从需求到落地

上一集是"捡现成的、修好它"。这一集反过来——从我自己的一个真需求出发,亲手设计一个 pi 里没有的扩展:把一段会话攒下的判断,打包成一份能发给别人的交接件。你会看到一个非开发者完整的开发流程:先划边界、先定验收,再动手;以及一个关键手艺——改扩展常常不是从零写,是借官方一个现成能力、换掉一个关节。

上一集我们捡了个现成的扩展、把它修好。这一集反过来:从我自己的一个真需求出发,亲手做一个 pi 里没有的扩展。

需求很具体。我用 pi 干了半天活,会话里攒下一堆判断——做到哪了、为什么这么定、哪几个坑踩过。可这些东西,换个人接手、或者换个 agent 续干,全丢了。我想要一个命令:一键把这段会话的判断,打包成一份能发给别人的文件。

pi 官方其实有个 handoff 扩展——但它交接给”下一个自己”(开一个新会话接着聊)。我要的不一样:我要交给别人 / 别的系统。这半,官方没做。那就自己做。


一、先划边界:做到”导出一份文件”就停

动手前,先做减法。这是精益的老规矩——先想清楚不做什么,比想做什么更重要。

一个”打包带走”的功能,很容易越做越大:导出后要不要自动发邮件给同事?要不要自动上传知识库?要不要自动喂给下一个 agent 接着跑?

我把边界划在很前面的一条线上:

生成一份 .md 文件,就停。发给谁、喂给哪个 agent,人来点。

为什么卡这么死?两个理由,都是这门课反复讲的:

  • 判断留给人:交接件里可能有客户名、密钥、内部路径。这东西该发给谁、能不能出这台电脑,是判断,不能让程序替我拍板自动送出去。
  • 一件事做透:先把”提炼 + 落地”这一段做扎实,别一上来就纠缠”自动投递”那摊复杂事(那是另一个项目,后面单独做)。

不做清单也顺手定死:❌ 不开新会话(官方那个已经做了)❌ 不自动发送/上传 ❌ 先只出 .md,不搞多格式。


二、先定验收:三条,缺一条就不算做好

Lean 里有句话:先定义好”什么算做好”,再动手。 别等做完了才想验收标准,那时候你已经被自己写的东西绑架了。

这个扩展,我先把三条验收写死:

  1. 交接件必须是这五栏——因为读者是”别人”,不是”我自己”:

    ① 项目是什么 · ② 现在到哪了 · ③ 下一步干嘛 · ④ 有哪些坑别踩 · ⑤ 关键决定为什么这么定

  2. 落盘前必须弹出来让人改一眼——脱敏、补一句只有我知道的上下文。这一步不能跳,是整个功能的魂。
  3. 人按了取消(Esc),就绝不写文件——“人过一眼”要是能被绕过,那道关就是假的。

第 4 栏”有哪些坑别踩”我格外看重,所以给它加了条硬要求:必须具体到”改了 X 会导致 Y”,不许泛泛而谈。那一栏是整份交接件最值钱的地方,空话等于没写。


三、怎么实现:站在官方肩上,只换一个关节

这是这一集最想让你记住的手艺。改扩展,常常不是从零写——是找到一个已经做好八成的现成东西,只换掉其中一个关节。

官方 handoff 扩展,整条链路早替我铺好了:拿会话账本 → 处理压缩 → 序列化成文本 → 调模型提炼 → 弹编辑器让人改。我要的前面全一样,只有最后一个关节不同:

关节官方 handoff我的 handoff-doc
拿账本ctx.sessionManager.getBranch()一样
提炼ctx.modelRegistry.complete()一样,只换提示词(五栏)
人过一眼ctx.ui.editor()一样
落地ctx.newSession() 开新会话写一份 .md 文件 ← 只改这里

所以我干的活,本质是:把官方那条链原样借来,把末端的”开新会话”换成”写文件”。 复用的那几件是官方现成导出的函数,我一行序列化逻辑都没自己写。

只新增了一件小事——安全写文件。这里有个新手看不见的坑:agent 可能同时在跑别的工具改文件,你直接 fs.writeFile 会跟它内置的写文件工具打架。pi 早想到了,导出了一个 withFileMutationQueue——把针对同一个文件的写入自动排队。用它包一下就行:

// 不裸写文件,用官方的队列包一层——自动跟内置 write/edit 工具排队,不打架
await withFileMutationQueue(filePath, async () => {
  await fs.promises.mkdir(dir, { recursive: true });
  await fs.promises.writeFile(filePath, `${frontmatter}${finalText}\n`, "utf8");
});

一个非开发者的取巧:我没本事从零写一个”提炼会话”的功能。但我能看懂官方 handoff 每一步在干嘛,认出”哦,它只有最后一步跟我要的不一样”,然后只改那一步。改扩展的高性价比打法,八成是这种”借链换关节”,不是从白纸开始。


四、真机跑一遍:它活了

装上扩展,我随手在 pi 里聊了段真实的活(让它给一份”写公众号”列了七步任务清单),然后敲:

/handoff-doc 交给同事接手

模型提炼完,五栏草稿弹进了一个编辑器——注意顶上那句”检查交接件(可脱敏 / 补充 / 删减)“,这就是那道”人过一眼”的关:

pi 的编辑器里弹出交接件草稿,标题"检查交接件(可脱敏/补充/删减)",可见"四、有哪些坑别踩""五、关键决定为什么这么定"两栏,内容具体(如"/settings todos 不要猜着执行"),底部是编辑器快捷键 enter submit / escape cancel

我在编辑器里加了一句”稍微简化一下,目前只是测试”,回车。它落盘了:

pi 底部提示"交接件已导出:handoffs/handoff-2026-08-09T13-15-51.md"

打开那个 .md,五栏齐整,而且每一栏都是实的——它甚至记住了会话里我一个悬而未决的动作(/settings todos),把它列进了”下一步”和”坑”里:

---
type: "交接件"
generated_at: "2026-08-09T13:15:51.096Z"
session_id: "019fe5b1-a5da-7e16-97fa-f40d7d511ace"
intent: "交给同事接手"
---
## 一、项目是什么
用户希望在 AI 助手协作下,管理微信公众号文章的完整创作流程……

## 四、有哪些坑别踩
1. 不要擅自假设文章主题:会话开头需求表述模糊,助手第一次直接追问澄清……接手后遇到歧义也应先问再动手。
2. `/settings todos` 不要猜着执行:这是个含义不明确的斜杠命令,应先向用户确认。
…

## 五、关键决定为什么这么定
2. 任务清单粒度定为 7 步:覆盖选题→素材→大纲→初稿→润色→配图→发布……属通用模板,接手后可增删。

顶上那段 --- 包起来的东西(type / generated_at / session_id / intent)是自动加的元信息——存进知识库后能被检索。而且它是在你改完正文之后才拼上的:你改的是内容,元数据机器管。

注意那句”目前只是测试”是我手打进去的。 这就证明了第 2 条验收是真的——落盘前那一眼,人真的能改。 顺手我也试了按 Esc:它明确提示”已取消,未写入文件”,一个字都没落。那道关,不是摆设。


五、闭环的另一半:交接件被”另一个 agent”接住

导出成功那一刻,我心里冒出个问题——这文件导是导出来了,可它没被另一个 agent 自动用上啊?

这个问题问得好,答案正是这个设计的要害:它”没被自动使用”,是故意的(还记得第一节那条红线吗)。但”被另一个 agent 使用”这半——存在,而且方式恰恰是”人指一下、agent 接手”,不是自动传送。

我当场验了一下。写这份交接件的是我 pi 里那个会话;我把这个 .md 交给另一个 agent(另一个 Claude 会话,跟写它的完全是两个),让它读。它读完,接住了:

根据这份交接件,我接手后该做的是:① 先处理遗留的 /settings todos(先问清是想查看还是改设置,别猜着执行)② 确认文章主题定了没(建清单时没指定)③ 主题定了,推进第 1 项选题。坑我也记下了:别自作主张假设主题、别搞混”建清单”和”写 todo 文章”。

看——交接件被”另一个 agent”接住了。 它读进去、接着干。这就是它的用法:你把这份 .md 发给同事、粘给另一个会话、丢进知识库让检索型 agent 读——接手方(人或 agent)读它、续干。

这是接力——控制权交出去了,由接手方续干。但你可能立刻会问:这跟”派一个子 agent 去帮我跑一段”有什么不一样?问到点子上了。这俩是两种完全不同的委托机制,分不清就会把功能做拧——下一节专门掰扯。


六、handoff 不是 subagent:两种委托机制,别做拧了

做这一集,我自己差点踩个思维坑:一度想给 handoff 加一个”导出后自动派给子 agent 接着跑”。停下来一想——这是把两个方向相反的机制焊在一起。 掰清楚它俩,是这一集真正的”厚度”:

Handoff · 交接 控制权转移 · 跨会话 / 跨工具 / 给别人 会话 A 你收尾 .md 交接件 会话 B 全新 agent 接手 A 结束离场,B 从文档重新开始——看不到 A 的上下文 场景:上下文满了 · 换工具 · 交给同事 · 阶段交接 产出:一份可移植、可审计的文件(工作要"旅行") Subagent · 分身 控制权保留 · 隔离 / 并行 / 能力围栏 父 agent 始终主导 子 agent · 隔离上下文 子 agent · 能力围栏 派发 结果回流(只回摘要) 子 agent 干完消失,父 agent 拿结果综合决策 场景:隔离子任务 · 并行探索/审查 · 危险操作关笼里跑

一句话分清:

  • Handoff(交接):把判断压成一份可移植的文档,控制权转移——你收尾,一个全新的 agent / 会话 / 工具 / 同事,从文档重新开始。这一集做的就是它。
  • Subagent(分身):在当前会话里拉起一个隔离的临时子 agent,控制权始终在你手上——它干完把摘要结果交回来,你继续主导;还能一次派好几个并行跑。
维度Handoff 交接Subagent 分身
本质生成可移植的交接文档拉起隔离的临时子 agent
控制权转移,新会话接管始终在父 agent
上下文新 agent 从文档重新开始子 agent 独立窗口,结果回流
生命周期跨会话、跨工具当前会话内、临时
并行通常不强调天然支持多子 agent 并行
最佳场景上下文满了、换工具、交给别人隔离子任务、并行探索/审查

怎么选(一句话):

  • 想让另一个全新的 agent / 会话完整接手当前工作 → Handoff(这一集)。
  • 想在当前会话派一个助手去干一块能返回结果的干净子任务 → Subagent(下一集)。
  • 俩还能组合:先派 subagent 做调研、拿到结果,再 handoff 把整个阶段干净交给下一个会话。

顺带一个”扩展 vs 技能”的分界:同样是 handoff,在 Claude Code 里常做成一个 Skill(一段 markdown 指令,让模型自己把对话总结成文档);在 pi 里却是 代码扩展。为什么?因为 pi 的扩展能直接读真账本(连压缩结构都拿得到)、用受控参数调模型、安全写文件——这些是纯 prompt 的 skill 够不到的机制层。同一个功能,该做成 skill 还是扩展,取决于它需不需要碰”机制”。 这条线,你以后自己造工具时天天要用。


一个小插曲:一次 pull,坐标漂了 6 处

做这集时踩到一件事,正好当反面教材。

搭原型期间,pi 仓库被更新了一次(0.83.0 → 0.84.1)。我事先记的那些”官方 handoff 第几行干什么”的行号,一次就漂了 6 处——getBranch 从第 103 行挪到 102,编辑器那步从 166 挪到 167……全串了一位。

教训很直接:引用别人代码时,认符号名(getBranch、ctx.ui.editor),别死记行号。 行号是路标,换一版就可能挪;符号名才是门牌。这条纪律,对你看任何开源项目都成立——你在课里、笔记里记的坐标,标一句”基于哪个版本”,将来对不上时才知道去哪找。


带走这一句

改一个 pi 里没有的扩展,一个非开发者的完整打法是:① 先划边界(做到哪儿停,尤其对外那条红线卡在哪);② 先定验收(什么算做好,写死,再动手);③ 借链换关节(找个做好八成的官方能力,只换掉那一个跟你要的不一样的关节);④ 真机跑一遍、对回验收。这一集做出来的东西,把一段会话攒下的判断,变成了一份可移动、可脱敏、能被别人或别的 agent 接住的交接件——账本 → 提炼五栏 → 人过一眼 → 落成 .md → 有人接手。

下一集

这一集是 handoff——把判断交出去,控制权转移给别人。下一集换另一种委托:subagent 分身——在当前会话里派一个(或几个)隔离的子 agent 去干活,你始终主导、结果回流。重点在它怎么安全:不靠每步盯着,靠能力围栏(只给它够用的工具、只读、沙箱),让它就算自主跑,也够不着危险动作。这才是 Claude Code 里”高级模型自己派子 agent 干活”背后的安全底座。