1. 为什么会话接力是跨 CLI Agent 协作里最被低估的一环如果你同时用 Claude Code 和 Codex CLI 干活大概率遇到过这种场景在 Claude Code 里把需求聊透了、方案对齐了、上下文堆了一大堆结果某个环节你想换 Codex 来跑——因为它在某类任务上更顺手或者你只是想对比两边的输出质量。这时候你面对的现实是新开的 CLI 是一个完全失忆的进程你得把刚才几十分钟的对话重新喂一遍或者干脆放弃切换硬着头皮在原工具里继续。这就是会话接力要解决的问题。它不是简单的复制粘贴聊天记录而是让一个 CLI Agent 能够继承另一个 CLI Agent 已经建立的工作状态——包括任务目标、已确认的约束、已经排除的方案、当前卡在哪一步。关键词里的 CLI、Agent、会话接力、Claude Code、Codex本质上指向的是同一件事多个命令行 AI 编程工具之间如何做到上下文的低成本迁移。我自己的日常是 Claude Code 处理架构设计和重构类任务Codex CLI 处理批量代码生成和脚本编写。两边各有脾气但真正让我头疼的从来不是工具本身的能力而是切换成本。一次完整的上下文重建快则五分钟慢则半小时而且经常漏掉之前明确否定过的方案导致新 Agent 又走一遍弯路。这篇内容适合三类人一是已经在用至少一个 CLI Agent、想扩展到多工具协作的开发者二是被每次换工具都要重新解释一遍折磨过的团队三是对 Agent 上下文管理机制感兴趣、想自己动手做一层接力层的人。我会从原理讲到实操把会话接力的完整链路拆开包括状态怎么提取、怎么序列化、怎么注入到目标 CLI以及实测中那些文档里不会写的坑。需要先说明一个前提不同 CLI Agent 的会话存储格式、上下文窗口策略、系统提示词结构都不一样所以接力没有银弹式的通用方案。但有一套可复用的方法论理解了它你可以针对任意两个 CLI 搭出接力通道。2. 拆解 CLI Agent 的会话状态到底哪些东西值得接力2.1 会话状态不是聊天记录而是四层结构很多人第一反应是把聊天记录导出来不就行了。实测下来直接搬运原始对话是最低效的做法因为里面大量内容是寒暄、试错、重复确认真正有价值的信息密度很低。我习惯把一次 CLI 会话的状态拆成四层任务层当前要达成的目标、验收标准、明确的边界条件。这是最核心的丢了它接力就失去意义。约束层已经确认的技术选型、不能动的文件、必须遵守的代码规范、用户明确否决的方案。进度层已经完成了哪些步骤、当前卡在哪个环节、下一步计划做什么。环境层工作目录、涉及的关键文件路径、依赖版本、运行命令。这四层里任务层和约束层是必须接力的进度层强烈建议接力环境层可以部分接力因为目标 CLI 可能在不同的工作目录启动。提示不要试图接力完整对话历史。上下文窗口是稀缺资源把噪声喂进去只会稀释有效信息反而让目标 Agent 表现变差。2.2 为什么原始 transcript 直接注入会翻车我踩过这个坑。早期图省事直接把 Claude Code 的会话记录整段贴给 Codex结果出现两个问题一是目标 Agent 把历史对话里的中间态结论当成了最终结论比如我中途说过也许可以用 Redis后来否决了但它只看到前半句就顺着 Redis 往下设计了二是 token 消耗爆炸一次接力吃掉大半个上下文窗口后续真正干活的空间被压缩。根本原因在于原始对话是线性的、包含大量被推翻的分支而 Agent 需要的是收敛后的状态。所以接力的第一步不是搬运而是提炼。你需要一个中间表示层把会话压缩成结构化的状态描述再注入目标 CLI。2.3 一个可落地的状态描述格式我目前用的格式是一段结构化 Markdown字段固定方便程序生成也方便人读## 接力状态包 ### 任务目标 [一句话说清要做什么以及完成的标准] ### 已确认约束 - 技术栈... - 禁止事项... - 已否决方案...附否决原因 ### 当前进度 - 已完成... - 进行中... - 待办... ### 关键文件与环境 - 工作目录... - 核心文件... - 运行命令...这个格式的好处是目标 Agent 读一遍就能建立完整心智模型token 占用通常控制在几百到一千以内而且人也能快速核对有没有遗漏。后面所有的实操步骤都是围绕如何自动生成这个状态包和如何把它注入目标 CLI展开的。3. 从 Claude Code 侧提取会话文件在哪、怎么读、读什么3.1 Claude Code 的会话落盘位置与结构Claude Code 会把每个项目的会话以 JSONL 格式存在本地路径通常在用户目录下的项目专属文件夹里按项目路径做哈希分目录。每个会话是一个独立的.jsonl文件每行一条消息记录包含角色、内容、时间戳、工具调用等字段。你要做的第一件事是定位当前活跃会话文件。最稳的方式不是猜路径而是按修改时间排序取最新# 找到最近修改的会话文件示意实际路径以本机为准 find ~/.claude/projects -name *.jsonl -type f -printf %T %p\n \ | sort -rn | head -1 | awk {print $2}拿到文件后逐行解析。每条记录大致长这样字段名以实际为准{type:user,message:{role:user,content:...},timestamp:...} {type:assistant,message:{role:assistant,content:[{type:text,text:...}]},timestamp:...}3.2 提取时该过滤掉什么直接全量读取会得到一堆噪声。我的过滤规则是丢掉工具调用的原始返回文件读取、命令执行的大段输出对目标 Agent 没价值只保留做了什么的摘要。丢掉被否决的中间分支这个靠人工或启发式判断简单做法是只保留最后 N 轮对话加上用户明确说就这样确认之类的锚点。保留用户消息的完整内容用户说的话往往包含最硬的约束不要截断。保留 assistant 的结论性段落跳过让我想想我先看看这类过程性表述。3.3 用脚本把会话压成状态包下面是一个可复用的提取脚本骨架思路是读 JSONL、按角色分流、做一轮摘要压缩import json import sys def load_session(path): msgs [] with open(path, encodingutf-8) as f: for line in f: line line.strip() if not line: continue try: msgs.append(json.loads(line)) except json.JSONDecodeError: continue return msgs def extract_text(msg): 从不同结构的消息里抽出纯文本 m msg.get(message, {}) content m.get(content, ) if isinstance(content, str): return content if isinstance(content, list): parts [] for block in content: if isinstance(block, dict) and block.get(type) text: parts.append(block.get(text, )) return \n.join(parts) return def build_state_package(msgs, max_turns20): 取最近若干轮拼成状态包草稿 recent msgs[-max_turns:] lines [## 接力状态包, ] for msg in recent: role msg.get(type, unknown) text extract_text(msg).strip() if not text: continue tag 用户 if role user else 助手 lines.append(f### {tag}) lines.append(text[:2000]) # 单条限长防止爆炸 lines.append() return \n.join(lines) if __name__ __main__: path sys.argv[1] msgs load_session(path) print(build_state_package(msgs))这个脚本产出的是草稿不是最终状态包。真正注入前我一般会再过一遍手动把任务目标已确认约束这两块补全——因为这两块往往散落在多轮对话里纯自动提取容易漏。注意不同版本的 CLI 会话文件字段名可能变化脚本里的字段名要按你本机的实际格式调整。建议先打印一条原始记录看看结构再写解析逻辑。4. 把状态包喂给 Codex CLI注入方式与上下文对齐4.1 三种注入路径的取舍把状态包送进目标 CLI有三条路各有适用场景注入方式做法优点缺点首条消息注入新会话第一条消息就是状态包简单直接无需改配置占用对话轮次后续可能被稀释项目级指令文件写入目标 CLI 约定的指令文件持久生效每次启动自动加载需要目标 CLI 支持且会污染项目环境变量/启动参数通过参数传入初始上下文干净不落盘支持度因工具而异长度受限我日常首选首条消息注入因为它对工具零依赖任何 CLI 都能用。项目级指令文件适合这个项目长期要用某个 Agent 干活的场景但要注意别把一次性的接力状态写进去否则下次启动会带着过期上下文。4.2 首条消息注入的实操模板在 Codex CLI 里新开会话后第一条消息直接贴状态包并在末尾加一句明确的指令告诉它这是接力上下文而非新需求以下是我从另一个编程 Agent 会话中整理出的接力状态包 请先完整理解不要急着动手。理解后回复已就绪 并简要复述你理解的任务目标和约束等我确认后再开始。 [粘贴状态包]这个先复述再动手的动作非常关键。它相当于一次握手校验如果目标 Agent 复述的内容和你预期不符说明状态包有歧义或信息丢失此时纠正成本极低等它开始改代码了再发现理解偏差返工成本就高了。4.3 上下文窗口的预算分配接力状态包会占用目标会话的上下文。我的经验值是状态包控制在总窗口的 5% 到 10% 以内。假设目标 CLI 的有效上下文是 100K token状态包最好别超过 5K 到 10K。超过这个比例后续干活的空间会被明显挤压Agent 容易忘事。如果状态包压不下去说明你试图接力的信息太多。这时候要狠心砍只保留任务层和约束层进度层用一句话概括环境层让目标 Agent 自己去看文件。4.4 环境层对齐别让目标 Agent 在错误的目录里干活一个特别容易被忽略的点两个 CLI 的工作目录可能不一样。Claude Code 在 A 目录聊的方案Codex 可能在 B 目录启动。如果状态包里没写清楚工作目录和关键文件路径目标 Agent 会基于自己的当前目录去理解相对路径直接跑偏。所以状态包的关键文件与环境部分路径一律用绝对路径并且明确写出启动命令。这一步多花十秒能省掉后面半小时的文件找不到排查。5. 反向接力与双向同步让两个 Agent 真正协作起来5.1 为什么单向接力只解决了一半问题前面讲的是 Claude Code 到 Codex 的单向接力。但真实工作流往往是来回的Codex 跑完一批代码生成你想回到 Claude Code 做审查和重构。这时候需要反向接力——把 Codex 的会话状态提取出来注入回 Claude Code。反向接力的难点在于Codex CLI 的会话存储格式和 Claude Code 不同提取逻辑要重写。但方法论是一样的定位会话文件、解析、压缩成状态包、注入。所以真正值得投入的是把状态包格式标准化让两个方向的提取脚本都输出同一种结构注入侧就不用改。5.2 双向同步的冲突处理来回接力几次之后会出现状态分叉Claude Code 这边认为方案是 ACodex 那边基于旧状态包还在按方案 B 推进。这时候如果直接把两边的状态包合并会产生矛盾指令。我的处理原则是以时间戳最新的一方为准但把冲突显式标注出来。状态包里加一个冲突提示字段### 冲突提示 - 本状态包生成于 [时间] - 与上一版状态包的差异方案从 B 改为 A原因是 [简述] - 如果目标 Agent 发现与自身理解不符请先停下确认这样目标 Agent 遇到矛盾时不会自作主张而是会停下来问避免在错误方向上越跑越远。5.3 用文件系统做接力中转站如果两个 CLI 都在同一台机器上最省事的接力中转站就是一个约定路径下的状态包文件。比如固定放在项目根目录的.agent-handoff/state.md提取脚本写它注入脚本读它。这样你甚至不需要手动复制粘贴切换工具时目标 CLI 直接读这个文件即可。# 提取侧把状态包写到约定位置 python extract_session.py session_file .agent-handoff/state.md # 注入侧新会话里让 Agent 先读这个文件 # 在目标 CLI 首条消息里写 # 请先读取 .agent-handoff/state.md 作为接力上下文理解后复述任务目标。这个模式的好处是可追溯每次接力的状态包都留在文件里出问题能回看是哪一版状态包导致的偏差。6. 实测踩坑记录那些文档不会告诉你的问题6.1 状态包里的已否决方案反而会误导 Agent我一度在状态包里详细列出已否决方案及原因本意是防止目标 Agent 重走弯路。结果发现某些 Agent 会对被否决的方案产生过度关注甚至在后续推理里反复提及。后来我改成只写已确认方案被否决的只在原因里一句话带过不展开细节。信息给得越少越聚焦Agent 反而更稳。6.2 时间戳和版本号缺失导致接力错乱有一次我连续接力了三次中间忘了更新状态包的时间戳结果目标 Agent 把两版状态混在一起理解任务目标直接跑偏。从那以后状态包头部强制带生成时间和一个递增版本号注入时也明确告诉 Agent这是最新版忽略任何与之矛盾的历史信息。6.3 目标 CLI 的默认系统提示词会覆盖接力意图不同 CLI 有自己的默认行为倾向。比如有的偏保守、动不动就问确认有的偏激进、直接开干。接力状态包里的指令如果和它的默认倾向冲突往往默认倾向会赢。解决办法是在状态包开头用强指令明确本次会话的行为模式比如本次会话请直接执行不要反复确认把行为预期前置。6.4 长会话提取时的性能问题会话文件跑长了能到几万行全量解析再压缩会明显卡顿。我的优化是只读文件尾部用tail取最后若干行或者解析时从后往前读凑够需要的轮次就停。实测这样能把提取时间从十几秒压到一两秒体验差别很大。6.5 接力后 Agent 表现反而变差的排查顺序如果接力后目标 Agent 表现不如预期按这个顺序排查状态包是否超长先看 token 占用超了就砍。任务目标是否单一状态包里塞了多个任务Agent 会顾此失彼拆成多次接力。约束是否自相矛盾检查已确认约束里有没有互相打架的条目。环境路径是否正确绝对路径、工作目录、启动命令逐项核对。是否触发了默认行为覆盖加一句强指令试试。这套顺序基本能覆盖九成的接力翻车场景。7. 把接力做成习惯日常使用中的几个经验用久了之后我对会话接力的定位从救急手段变成了常规操作。现在我的习惯是每完成一个阶段性目标就主动生成一次状态包不管当下要不要切换工具。这样一旦需要换 Agent状态包是现成的不用临时抱佛脚去翻聊天记录。另一个经验是状态包要短、要硬、要可执行。短是指 token 少硬是指约束明确不含糊可执行是指任务目标能直接转成下一步动作。我见过太多状态包写得像会议纪要信息全但没法指导行动Agent 读完还是不知道从哪下手。还有一点别指望全自动。提取脚本能帮你省掉八成机械劳动但任务目标和已确认约束这两块人工过一遍的价值极高。我试过纯自动接力翻车率明显高于人工校对过的版本。花两分钟核对换来的是目标 Agent 一次跑对这笔账很划算。最后分享一个我最近在用的技巧在状态包末尾附上**下一步建议动作**用一两句话给出你期望目标 Agent 首先做的事。这相当于给它一个明确的起跑线避免它在理解完状态后还要自己规划从哪开始能显著减少理解正确但起步犹豫的情况。这个字段不是必须的但在任务复杂、步骤多的时候效果立竿见影。