Skip to content

任务交接与迁移 ​

当对话的上下文接近模型窗口上限时,继续对话会导致信息被截断或请求失败。这里有两个相关但不同的概念:

概念做什么命令
交接(handoff)停下手头工作,写出一份交接文档。会话照旧运行。/handoff
迁移(migrate)交接 加上 清空历史、以该文档开启新会话。/migrate

所以「迁移」= 「交接 + 新 agent 上岗」:原 agent 交班,新会话从文档接续。

交接文档 ​

每一次交接(手动或自动)都会产出一个 Markdown 文件:

<项目>/.ai-zen/handoffs/<YYYYMMDD-HHmmss>-<会话id前8位>.md
  • 目录在项目内,便于随代码一起审阅、提交;
  • 文件名确定性可预期,并带原始会话 id,方便回溯;
  • 注入新会话的断点消息只包含路径——接手的 agent 在真正需要细节时才 read 它。

模板来自 Skill,不在代码里 ​

文档结构与书写规则由 handoff Skill 定义:

~/.ai-zen/skills/handoff/SKILL.md

它由 SDK 首次 bootstrap 时释放,并在后续版本自动同步:

  • 未被改动(frontmatter 没有 metadata.custom: "true")→ 覆盖为最新版本,模板改进能到达老用户;
  • 标了 custom: "true" → 视为用户自有,永不覆盖。

也可以在项目里放一份 ./.ai-zen/skills/handoff/SKILL.md 覆盖(遵循既有的 Skill 目录优先级)。

代码里不再保留第二份章节清单。它只负责四件事:读 Skill 正文 → 注入 → 校验文件确实写出来了 → 读不到 Skill 时使用内置的最小兜底模板。

默认模板包含的章节:

  • 💬 对话断点 —— 用户最后的要求、你最后交付了什么;
  • 🎯 任务目标 —— 用户到底要什么(意图与验收标准);
  • 📊 当前状态 —— 分支/commit、未提交改动、测试是否通过、有无进程/端口在跑;
  • ✅ 已完成 —— 每项附产出路径与验证方式;
  • 📋 未完成 —— 按优先级,每项给「下一步最小动作」;
  • 🧠 关键决策 —— 取舍理由,以及走过的死路;
  • ⚠️ 约束与偏好 —— 用户强调的规矩、项目约定、不可动的部分;
  • 📁 文件索引 —— 路径 + 作用 + 本次是否改动;
  • 🔔 接手第一步 —— 3 条以内可直接执行的命令。

两种执行者 ​

执行者谁来写优势代价
self(默认)原 agent 停手后在自己的会话里写上下文完整,可用工具核实真实状态多 1~2 次全量请求(输入侧)
summary一次独立模型调用,基于序列化历史摘要便宜、确定无工具、无原会话上下文,保真度低

self 之所以是默认,是因为「保真」正是交接的意义。summary 是逃生口,也是单向降级目标:self 失败(没有 write 工具、文件缺失或过小且重试一次仍不行、工具/护栏报错)时自动降级并告知用户。反向绝不发生——那会在最危险的时刻再加一次全量请求。

self 为什么可靠 ​

  • 「停手」由代码兜底,不只是提示词:交接回合会临时启用一次性 HandoffGuardPlugin,拒绝 edit / batch_edit / rm / rename / mkdir / copy,write 仅在目标路径归一后等于交接文档时放行。拒绝是软拒绝——原因作为工具结果回给模型,它会转回正题,而不是把整轮交接打断。
  • 边界:挡不住 exec(echo > file 总能绕过)。它降低的是误操作概率,不是沙箱。
  • 护栏是放宽而非消失:service.busy 期间 ContextGuardPlugin 从 maxTokens × 1.5 放宽到 × 2——交接恰好发生在上下文最满的时刻,但仍保留熔断。
  • 不会递归:交接回合本身也是一次 send(),AutoMigratePlugin 在 service.busy 时让行,因此「交接触发迁移、迁移再触发交接」不可能发生。

触发方式 ​

手动 ​

/handoff                # 只写交接文档,保持当前会话
/handoff summary        # 强制使用独立摘要执行者
/migrate                # 交接 + 开启新会话(别名 /migration)
/migrate self           # 显式指定执行者

/handoff 不弹确认(非破坏性、可反复执行);而且交接回合会留在 agent.messages 里,你可以接着说「第 3 节写细一点」「补上端口占用情况」,agent 会重写同一个文件。/migrate 会丢历史,因此要确认。

自动(上下文超限) ​

当 usage.prompt_tokens 超过模型的 maxContextTokens,SDK 的 AutoMigratePlugin 触发迁移。交互式终端里 CLI 弹出三选一选择器:

  • 原 agent 自交接(默认高亮)—— 保真度最高;
  • 独立摘要 —— 便宜;
  • Esc —— 跳过本次。历史不受影响;只要用量仍超阈值,下次回复后还会再问。

非交互环境(管道、重定向、e2e 脚本)没有选择通道:按默认执行者迁移,不阻塞等待输入、不额外输出。

迁移链路 ​

  1. onBeforeHandoff —— 进度提示(正在编写任务交接文档…);
  2. 交接回合 —— 中止在途请求,执行 self 回合(或 summary 调用),校验文件(存在 + ≥ 400 字节,失败重试一次,再失败降级);
  3. onHandoff —— 提示文档路径;若是迁移,此刻保存旧会话(已包含交接回合,归档可审计);
  4. migrate 的 prune 策略 —— 物理剔除历史,只保留 definition.messages + 一条只含路径的断点消息;
  5. onMigrated —— 清空 agent.lastUsage(否则护栏会拿陈旧用量误杀新会话的第一条消息),分配新会话 id、落盘、更新「上一轮会话」指针。

旧会话一定在剔除前保存,因此即使交接失败也不会丢历史:可以重试,也可以继续当前对话。

会话 id 只有一个真相源。 onMigrated 写入的新 id 必须立刻对会话层的落盘逻辑可见(CLI 中 ChatSession.currentId 直接代理 ConversationContext.currentId,而不是各存一份)。否则下一轮自动落盘仍按旧 id 写盘,会把 prune 后的空壳(系统提示 + 断点)覆盖到旧会话文件上,表现为「交接之后历史全没了,只剩交接文档」。

成本说明 ​

  • 每次交接文档只写一次,代码不会重新生成或再次摘要;
  • 断点消息只带路径,read 的成本由真正需要细节的一方支付;
  • self 相比 summary 多花的钱几乎全在输入侧(多 1~2 次全量历史),输出侧基本相同(文档正文 + ≤40 字确认)。

相关 ​

  • 实现位于 @ai-zen/agents-sdk:HandoffService(handoff() / migrate())、HandoffGuardPlugin、AutoMigratePlugin、ContextGuardPlugin;出厂 Skill 定义在 config/factory-skills.ts。
  • 其他能力见 内置工具、MCP 支持、Skill 技能。

MIT / ISC License