- NO.
- 034
- DATE
- READ
- ~5 min
- KIND
- 笔记
- STATUS
- 原文
我不再让一个 Agent 直接接管另一个 Agent 的会话
一次让 Pi 接管 Claude Code 会话的经历:4 条消息烧掉近 2000 万 Token。我据此把 Coding Agent 的交接从恢复会话,改成 Git + HANDOFF + 新会话。
最近用 Pi 做一个开发任务时,我踩到一个成本问题:一次看起来很便宜、缓存命中率超过 90% 的会话,仍然烧掉了近两千万 Token。根子不在模型贵,也不在缓存没生效,而在于我让一个 Agent 去接管了另一个 Agent 没跑完的会话。这篇记下这个结构错在哪,以及我最后把交接流程改成了什么。它是我的成本控制经历,不是什么模型限制或通用结论。
这个任务之前在 Claude Code 里做了一部分,所以我很自然地给 Pi 下了一条指令:接着这个未完成的会话做完,claude --resume <session-id>。从人的角度看这很合理——Claude Code 已经知道前因后果,恢复会话让 Pi 接着监督它完成,不必重新理解项目。
跑了一段时间后我看了一眼 API Usage,发现不太对。这次 Pi 会话里只有 4 条用户消息,却产生了:
Assistant: 53
Tool calls: 52
Input: 19.86M tokens
Cached: 18.02M
Cache hit: 90.7%
缓存命中率其实很健康。问题不在缓存有没有工作,而在于 Agent 在一个接近 1M Token 的上下文上连续跑了很多轮——绝大部分内容命中缓存,但命中的缓存依然不是免费的。这让我重新想一个问题:一个 Coding Agent 没做完的任务,到底应该怎么交给另一个 Agent?
问题不是 Resume,而是 Agent 套 Agent
claude --resume 本身没问题。如果 Claude Code 因为网络或终端中断,我重新 claude --resume <session> 继续工作完全合理。问题出在我实际搭出来的结构:
Pi
└── shell
└── Claude Code
└── Model API
Pi 是一个 Agent,Claude Code 又是另一个完整的 Agent。于是我做的其实不是让 Pi「继承 Claude Code 的任务」,而是让一个 Agent 启动了另一个完整的 Agent Runtime。而 Claude Code 的终端输出、工具结果、历史信息,又源源不断地进入 Pi 的上下文。
这次的上下文规模变化很直白,从几十 K 很快涨上去,之后大量模型调用都维持在接近 1M 的规模:
~35K → ~490K → ~1.02M
真正贵的不是某一次特别长的回答,而是把接近 1M 的上下文乘以很多次 Agent Loop。 模型调一次工具、回来继续推理,就要重新过一遍当前上下文;几十次工具调用很容易累积成几千万 Token 的处理量。
我原来的理解错在哪
我以前潜意识里把「会话」约等于「项目状态」,觉得只要把会话恢复回来,Agent 就能无缝接着干。但对代码项目,这不是个好的状态模型。真正可靠的项目状态已经存在于 Git、源码、配置、测试和构建结果里;聊天记录额外保存的,只有「为什么这么做」和「接下来准备做什么」,而这部分根本不需要几十万 Token 的历史对话。
所以 Coding Agent 的会话应该被当成一个临时计算环境,而不是项目数据库。 真正该持久化的是 Repo。
我现在使用的方案
原则很简单:一个任务只保留一个主 Agent,不同 Agent 之间用文件交接,而不是用会话交接。 假设 Claude Code 做到一半要换 Pi 接管,我不再直接让 Pi 去 claude --resume,而是分两步。
第一步,如果 Claude Code 还能恢复,我先进去,但这次不让它继续开发,只给它一个收尾任务:
停止继续开发。把当前任务继续执行所必需的信息写入 .ai/HANDOFF.md:
- 最终目标
- 已完成的工作
- 尚未完成的工作
- 修改过的重要文件
- 已确定的设计决策
- 当前测试状态
- 已知问题
- 推荐的下一步
- 最终验证方式
不要复制完整聊天记录、完整源码或大量日志。
确保当前修改已保存在工作区后退出。
第二步,Pi 开一个全新的会话接管:
接管当前任务。先读取 .ai/HANDOFF.md、git status、git diff --stat,
然后只读取完成剩余任务所必需的文件。
先验证 HANDOFF 是否与当前代码一致;如果冲突,以 Git 和实际代码为准。
确认后继续完成剩余工作。
结构于是从「Pi → Claude Code → 历史会话 → 更多输出 → 回灌 Pi 上下文」变成:
Claude Code → HANDOFF.md → Git / Workspace → 新的 Pi 会话
两个 Agent 之间真正传递的只有任务状态,而不是整个思考历史。
HANDOFF 其实不需要很复杂
我现在给项目统一保留一个 .ai/HANDOFF.md,内容保持很短:
# Handoff
## Goal
最终需要实现什么。
## Completed
已经完成什么。
## Remaining
- [ ] 剩余任务 A
- [ ] 剩余任务 B
## Changed Files
- `src/a.ts`
- `src/b.ts`
## Decisions
已经确定的重要设计决策,以及为什么。
## Tests
已经运行哪些测试,目前结果如何。
## Known Problems
当前还存在什么问题。
## Next Step
下一个 Agent 最应该先做什么。
## Verification
任务完成后如何验证。
如果一个 Agent 没法用这么一份文件说清自己做到哪里,那往往意味着任务本身还没被很好地结构化。
会话也需要主动结束
这次还有一个教训:不该等 Context 真塞满了才考虑换会话。Pi 能直接显示当前 Context 使用率,我现在用一条很粗的规则:
< 50% 正常继续
50% ~ 70% 任务快结束就继续;马上进新阶段就准备 HANDOFF
> 70% 不再开大型新任务,先收尾当前步骤并换会话
这不是模型的技术限制,只是我的成本控制线。Coding Agent 场景里一次用户指令可能触发几十次工具调用,在 70% 的 Context 上再起一个「顺便把这个模块重构一下」,接下来几十次模型请求都得带着巨大的历史上下文跑,而新开一个会话的成本通常反而低得多。
模型也尽量在会话边界切换
另一个容易忽略的点是缓存。这次我在长上下文状态下切过模型,其中一次接近百万 Token 的上下文需要重新建立缓存。所以我现在也尽量避免在 900K 上下文里直接把 Opus 换成另一个模型再继续,而是先在当前会话把这一阶段做完,写 HANDOFF,开新会话,再换模型——让模型切换和 Context 重置发生在同一个边界上,逻辑也更干净。
我最后只保留了四条规则
折腾一圈后,我觉得 Coding Agent 工作流没必要设计得特别复杂,目前只准备长期遵守四条:
- Repo 才是项目状态,会话只是临时上下文。
- 一个任务只保留一个主 Agent,不默认 Agent 套 Agent。
- 不同 Agent 用
HANDOFF.md + Git交接,而不是恢复对方的完整会话。 - 长 Context 不硬撑,进入新阶段时主动换会话。
至于缓存命中率、模型价格、Context Window 有多大,这些当然值得关注,但更像第二层优化。第一层真正重要的是:不要让项目的连续性依赖某一个 AI 的聊天记录。
我现在判断一个 Coding Agent 工作流是否健康,会问自己一个问题:如果现在把所有 Pi、Claude Code、Codex 的会话全部删掉,一个新的 Agent 能不能只靠 Git 和项目里少量的状态文件继续工作?如果能,那说明项目状态真正属于项目,Agent 只是暂时来这里工作。
评论
评论由 GitHub Discussions 提供。登录 GitHub 后即可评论。 前往对应 Discussion