1. 为什么需要跨 CLI 编程 Agent 的会话接力
1.1 一个真实到让人抓狂的场景
我平时写代码的习惯是终端不离手,Claude Code 和 Codex CLI 这两个工具基本是轮着用。Claude Code 在理解大段上下文、做代码重构的时候特别顺手,Codex CLI 在快速生成脚本、跑一次性任务的时候响应很快。问题来了:我经常在 Claude Code 里聊到一半,发现某个任务其实更适合丢给 Codex 去跑,或者反过来,在 Codex 里生成的东西需要 Claude Code 来帮我做深度审查。
这时候就尴尬了。两个 CLI 工具各自维护自己的会话历史,互相之间完全不认识。你在 Claude Code 里积累的那一大段上下文——项目结构、需求描述、已经排除的方案、踩过的坑——到了 Codex 那边全部归零,你得重新讲一遍。讲一遍还不算完,两个工具对同一段对话的理解可能还不一样,来回切换几次,你自己都记不清哪个版本是最新的。
这就是我动手做跨 CLI 编程 Agent 本地会话接力的直接原因。说白了,就是让 Claude Code、Codex CLI 这些不同的命令行编程助手,能够共享同一份本地会话记录,你在 A 工具里聊的内容,切到 B 工具能接着聊,不用重新交代背景。
1.2 会话接力的本质是什么
很多人第一反应是"这不就是复制粘贴吗"。如果你只是偶尔用一次,复制粘贴确实够了。但实际用下来你会发现几个问题:第一,手动复制会丢失结构化的消息角色(谁说的、什么时候说的、是工具调用还是普通回复);第二,长会话复制起来非常痛苦,终端里翻页都翻不完;第三,不同 CLI 的消息格式不一样,直接粘贴过去对方可能理解不了。
所以真正的会话接力,核心是三件事:统一的消息格式、可靠的本地存储、可被不同 CLI 读取的接口。我把它理解成一个"会话中转站"——每个 CLI 工具在开始新会话时,先去中转站看看有没有可接续的历史,有就加载进来,没有就开新的。会话过程中产生的消息,也实时写回中转站。
这个思路听起来简单,但落地的时候有一堆细节要处理。比如消息的 token 预算怎么控制,历史太长会撑爆上下文窗口;比如工具调用的结果怎么序列化,不同 CLI 对 tool result 的格式要求不一样;再比如并发写入的问题,两个 CLI 同时往一个会话文件里写会冲突。这些坑我在后面会一个个拆开讲。
1.3 适合谁来参考这套方案
如果你符合下面任意一条,这套方案对你就有直接价值:
- 日常同时使用两个以上 CLI 编程工具,经常需要在它们之间切换
- 在做 Agent 相关的开发,需要理解会话状态如何在不同的执行器之间传递
- 对本地优先的数据管理有兴趣,不想把会话历史托管到云端
- 想给自己的工具链做一层统一的抽象,减少重复交代背景的成本
不需要你有多深的底层开发经验,但至少要熟悉命令行的基本操作,知道 JSON 长什么样,能看懂简单的 Python 或 Node.js 脚本。我会尽量把每一步都讲清楚,包括为什么这么设计、不这么设计会出什么问题。
2. 整体架构设计与核心思路拆解
2.1 三种可选方案及取舍逻辑
在动手之前我调研了三种思路,每种都有明显的优缺点,最后选了第三种。
方案一:直接读取各 CLI 的原生会话文件。Claude Code 和 Codex CLI 都会在本地某个目录下存会话记录,理论上你写个脚本把它们互相转换就行。我试过,问题是这些文件的格式没有公开文档,版本一升级就变,维护成本极高。而且不同工具的字段命名、嵌套结构差异很大,转换逻辑写起来像在拆炸弹。
方案二:用环境变量或启动参数注入历史。有些 CLI 支持通过参数传入初始上下文,比如--context-file之类的。这个方案最省事,但限制也很明显:不是所有工具都支持,而且注入的内容通常只作为系统提示,不参与真正的对话历史,模型对它的重视程度不够。
方案三:自建本地会话中转层。这是我现在用的方案。核心是一个本地 SQLite 数据库加一个轻量的 HTTP 服务,所有 CLI 工具通过统一的接口读写会话。CLI 本身不需要改造,只需要在启动时做一次"会话加载",结束时做一次"会话保存"。中间的过程通过一个包装脚本自动完成。
选方案三的理由很直接:可控性最高,不依赖任何工具的私有格式,升级不会失效。代价是要多写一些胶水代码,但这部分代码是一次性的,写完就不用管了。
2.2 数据模型设计:消息怎么存才不丢信息
会话接力的核心是消息模型。我设计的时候参考了主流大模型 API 的消息格式,但做了简化,最终每条消息包含这几个字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 自增主键,用于排序 |
| session_id | TEXT | 会话唯一标识,跨工具共享 |
| role | TEXT | 角色:system / user / assistant / tool |
| content | TEXT | 消息正文,纯文本或 JSON 字符串 |
| tool_calls | TEXT | 工具调用信息,JSON 格式,可为空 |
| tool_call_id | TEXT | 工具调用结果对应的调用 ID |
| source_cli | TEXT | 来源工具标识,如 claude-code / codex |
| created_at | INTEGER | Unix 时间戳,毫秒级 |
| token_count | INTEGER | 预估 token 数,用于预算控制 |
这里有几个设计决策值得展开说。
为什么用 session_id 而不是文件名区分会话。文件名容易冲突,而且不同工具对会话的命名规则不一样。用一个 UUID 作为 session_id,各工具通过一个映射表找到自己对应的会话,互不干扰。
为什么保留 source_cli 字段。这个字段在排查问题的时候特别有用。当你发现某条消息格式不对,能立刻知道是哪个工具写进来的。另外在做会话合并的时候,也可以根据来源做差异化处理。
token_count 为什么要存。上下文窗口是有限的,Claude Code 和 Codex 的窗口大小还不一样。存下每条消息的 token 数,加载会话的时候就能从后往前累加,超过预算就截断,保证不会因为历史太长导致请求失败。
2.3 中转服务的接口设计
中转服务我用了 FastAPI,因为它写起来快,自带文档,调试方便。核心接口只有四个:
# 创建或获取会话 POST /session/get_or_create Body: {"session_id": "xxx", "cli": "claude-code"} Response: {"session_id": "xxx", "messages": [...]} # 追加消息 POST /session/append Body: {"session_id": "xxx", "cli": "codex", "message": {...}} Response: {"ok": true, "message_id": 123} # 获取会话历史(带 token 预算) GET /session/{session_id}/history?max_tokens=8000 Response: {"messages": [...], "total_tokens": 7500} # 列出所有会话 GET /sessions Response: {"sessions": [{"session_id": "...", "last_active": 1234567890}]}接口设计上我刻意保持简单,没有做复杂的权限和认证,因为这是本地服务,只监听 127.0.0.1,外部访问不到。如果你有安全方面的顾虑,可以加一个简单的 token 校验,但对我来说没必要。
提示:中转服务一定要绑定 127.0.0.1 而不是 0.0.0.0,否则同局域网的其他设备可能访问到你的会话数据。这个细节很多人会忽略。
2.4 为什么不用现成的方案
有人可能会问,市面上不是有一些会话管理的工具吗,为什么不直接用。我的看法是,通用的会话管理工具解决的是"存和查"的问题,但跨 CLI 接力的核心难点在于"格式转换"和"上下文适配"。每个 CLI 对消息的理解不一样,Claude Code 喜欢把工具调用和结果分开存,Codex 可能把它们合并成一条。这种差异必须在中转层处理掉,通用工具做不到这么细。
另外,自己写的好处是完全透明。出问题的时候我知道去哪看日志,知道数据存在哪,不用担心某个工具突然改了策略导致数据丢失。对于会话历史这种长期积累的资产,可控性比便利性更重要。
3. 核心细节解析与实操要点
3.1 消息格式的归一化处理
这是整个方案里最费心思的部分。Claude Code 和 Codex CLI 的消息结构差异比想象中大,我拿实际数据对比一下。
Claude Code 的一条 assistant 消息大概长这样:
{ "role": "assistant", "content": [ {"type": "text", "text": "我来帮你看看这个函数"}, {"type": "tool_use", "id": "toolu_xxx", "name": "Read", "input": {"file_path": "/tmp/a.py"}} ] }Codex CLI 的对应消息可能是:
{ "role": "assistant", "content": "我来帮你看看这个函数", "function_call": { "name": "read_file", "arguments": "{\"path\": \"/tmp/a.py\"}" } }你看,同样是"读文件"这个动作,字段名、嵌套层级、参数格式全都不一样。如果直接存原始格式,加载的时候另一个工具根本解析不了。
我的处理方式是定义一个中间格式,所有消息进来先转成中间格式,出去的时候再转成目标工具需要的格式。中间格式长这样:
{ "role": "assistant", "text": "我来帮你看看这个函数", "tool_calls": [ { "id": "call_xxx", "name": "read_file", "arguments": {"path": "/tmp/a.py"} } ] }转换逻辑写在两个适配器里,一个负责 Claude Code 格式,一个负责 Codex 格式。适配器是纯函数,输入输出都是 JSON,测试起来很方便。
注意:工具名称的映射需要维护一张对照表。比如 Claude Code 叫
Read,Codex 叫read_file,你得知道它们是同一个操作。这张表要随着工具版本更新,建议放在配置文件里而不是硬编码。
3.2 会话加载的 token 预算控制
上下文窗口是硬约束。Claude Code 的窗口大概是 200K token,Codex 也差不多,但实际可用空间要扣掉系统提示、工具定义这些固定开销,真正留给历史消息的可能只有一半左右。
我的策略是从最新消息往前累加,超过预算就停止。这样保证最近的上下文一定在,早期的历史被截断。具体实现:
def load_history(session_id, max_tokens=8000): messages = query_all_messages(session_id) selected = [] total = 0 for msg in reversed(messages): if total + msg.token_count > max_tokens: break selected.insert(0, msg) total += msg.token_count return selected, total这里有个细节:截断的时候不能把工具调用和它的结果拆开。如果一条 assistant 消息里有 tool_call,紧接着的 tool 结果消息必须一起保留,否则模型会看到一个没有结果的调用,行为会很奇怪。所以我在循环里加了一个检查,遇到 tool 结果消息时,往前找它的调用消息,要么一起留,要么一起丢。
token 数的估算我用的是 tiktoken 库,虽然不同模型的 tokenizer 有差异,但误差在可接受范围内。如果你不想引入依赖,也可以用简单的字符数除以 4 来估算,对英文够用,中文会偏低估,建议除以 2。
3.3 并发写入的冲突处理
两个 CLI 同时往一个会话写消息,这个场景在自动化脚本里很常见。SQLite 默认的锁机制能保证不损坏数据,但会出现写入失败的情况。
我的处理方式是加一个应用层的写队列。所有写请求先进队列,由一个单独的线程串行执行。这样虽然牺牲了一点并发性能,但保证了顺序性,不会出现消息乱序的问题。对于本地使用场景,这点性能损失完全可以接受。
import queue import threading write_queue = queue.Queue() def writer_worker(): while True: task = write_queue.get() try: do_write(task) except Exception as e: log_error(e) finally: write_queue.task_done() threading.Thread(target=writer_worker, daemon=True).start()另外,SQLite 连接要设置check_same_thread=False,并且开启 WAL 模式,这样读写可以并行,性能会好很多。
conn = sqlite3.connect('sessions.db', check_same_thread=False) conn.execute('PRAGMA journal_mode=WAL')提示:WAL 模式会在同目录下生成 -wal 和 -shm 两个文件,备份的时候要一起备份,否则可能丢数据。
3.4 会话标识的传递机制
不同 CLI 怎么知道该用哪个 session_id,这是接力能不能跑通的关键。我的做法是用一个环境变量AGENT_SESSION_ID,包装脚本在启动 CLI 之前设置好,CLI 内部通过读取这个变量来决定加载哪个会话。
具体流程是这样的:
- 用户执行
agent-run claude或agent-run codex - 包装脚本检查当前目录有没有
.agent-session文件 - 有就读取里面的 session_id,没有就生成一个新的并写入
- 设置环境变量,启动对应的 CLI
- CLI 启动时通过一个 hook 脚本读取环境变量,从中转服务加载历史
- CLI 退出时,hook 脚本把本次新增的消息写回中转服务
.agent-session文件放在项目根目录,这样同一个项目的不同 CLI 自动共享会话。如果你想让不同项目隔离,只要在不同目录下运行就行,天然隔离。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
先把基础环境搭起来。我用的是 Python 3.11,理论上 3.9 以上都能跑。依赖不多,主要是 FastAPI、uvicorn、tiktoken 和 requests。
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install fastapi uvicorn tiktoken requests数据库用 SQLite,Python 标准库自带,不用额外装。整个项目结构我建议这样组织:
agent-relay/ ├── server/ │ ├── main.py # FastAPI 服务入口 │ ├── db.py # 数据库操作 │ ├── adapters/ │ │ ├── claude.py # Claude Code 格式适配 │ │ └── codex.py # Codex 格式适配 │ └── models.py # 数据模型 ├── client/ │ ├── load.py # 会话加载脚本 │ └── save.py # 会话保存脚本 ├── bin/ │ └── agent-run # 包装脚本 └── config.yaml # 工具名称映射等配置这个结构的好处是服务端和客户端分离,服务可以常驻后台,客户端脚本很轻量,启动快。
4.2 数据库初始化与表结构
建表语句我直接贴出来,字段含义前面已经解释过:
CREATE TABLE IF NOT EXISTS sessions ( session_id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, last_active INTEGER NOT NULL, metadata TEXT ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT, tool_calls TEXT, tool_call_id TEXT, source_cli TEXT, created_at INTEGER NOT NULL, token_count INTEGER DEFAULT 0, FOREIGN KEY (session_id) REFERENCES sessions(session_id) ); CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, id);索引建在(session_id, id)上,因为查询历史的时候总是按会话过滤再按 id 排序,这个复合索引能覆盖大部分查询。
4.3 服务端核心代码实现
服务端我写得比较紧凑,核心就是几个路由函数。先看会话获取:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import time import uuid app = FastAPI() class GetOrCreateReq(BaseModel): session_id: str | None = None cli: str @app.post("/session/get_or_create") def get_or_create(req: GetOrCreateReq): sid = req.session_id or str(uuid.uuid4()) now = int(time.time() * 1000) with get_conn() as conn: row = conn.execute( "SELECT session_id FROM sessions WHERE session_id = ?", (sid,) ).fetchone() if not row: conn.execute( "INSERT INTO sessions (session_id, created_at, last_active) VALUES (?, ?, ?)", (sid, now, now) ) else: conn.execute( "UPDATE sessions SET last_active = ? WHERE session_id = ?", (now, sid) ) messages = load_history(sid, max_tokens=8000) return {"session_id": sid, "messages": messages}追加消息的接口类似,重点是 token 计数的计算:
import tiktoken enc = tiktoken.get_encoding("cl100k_base") def count_tokens(text: str) -> int: if not text: return 0 return len(enc.encode(text)) @app.post("/session/append") def append_message(req: AppendReq): tokens = count_tokens(req.message.get("text", "")) if req.message.get("tool_calls"): tokens += count_tokens(str(req.message["tool_calls"])) with get_conn() as conn: cur = conn.execute( """INSERT INTO messages (session_id, role, content, tool_calls, tool_call_id, source_cli, created_at, token_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", (req.session_id, req.message["role"], req.message.get("text"), json.dumps(req.message.get("tool_calls")) if req.message.get("tool_calls") else None, req.message.get("tool_call_id"), req.cli, int(time.time() * 1000), tokens) ) return {"ok": True, "message_id": cur.lastrowid}启动服务用 uvicorn,我习惯加--reload方便调试,正式用的时候去掉:
uvicorn server.main:app --host 127.0.0.1 --port 8765 --reload4.4 客户端加载与保存脚本
加载脚本的作用是在 CLI 启动时把历史消息转成目标工具能理解的格式,然后通过标准输入或者临时文件注入。不同 CLI 的注入方式不一样,Claude Code 支持--resume参数配合会话文件,Codex 可以用--history-file。
import os import requests import json from adapters import claude, codex RELAY_URL = "http://127.0.0.1:8765" def load_for_cli(cli_name: str): sid = os.environ.get("AGENT_SESSION_ID") if not sid: return resp = requests.post(f"{RELAY_URL}/session/get_or_create", json={"session_id": sid, "cli": cli_name}) data = resp.json() messages = data["messages"] if cli_name == "claude-code": formatted = claude.to_native(messages) elif cli_name == "codex": formatted = codex.to_native(messages) else: formatted = messages with open(f"/tmp/agent-history-{cli_name}.json", "w") as f: json.dump(formatted, f) print(f"Loaded {len(messages)} messages for {cli_name}")保存脚本反过来,从 CLI 的会话文件里读出新增消息,转成中间格式写回中转服务。这里的关键是只写新增的部分,不要全量覆盖,否则会重复。
def save_from_cli(cli_name: str, native_file: str): sid = os.environ.get("AGENT_SESSION_ID") with open(native_file) as f: native = json.load(f) if cli_name == "claude-code": messages = claude.from_native(native) elif cli_name == "codex": messages = codex.from_native(native) for msg in messages: requests.post(f"{RELAY_URL}/session/append", json={"session_id": sid, "cli": cli_name, "message": msg})4.5 包装脚本把一切串起来
最后是agent-run脚本,用户只需要跟它打交道:
#!/bin/bash set -e CLI_NAME=$1 shift # 确定 session_id SESSION_FILE=".agent-session" if [ ! -f "$SESSION_FILE" ]; then uuidgen > "$SESSION_FILE" fi export AGENT_SESSION_ID=$(cat "$SESSION_FILE") # 加载历史 python client/load.py "$CLI_NAME" # 启动 CLI case "$CLI_NAME" in claude) claude --resume /tmp/agent-history-claude-code.json "$@" ;; codex) codex --history-file /tmp/agent-history-codex.json "$@" ;; *) echo "Unknown CLI: $CLI_NAME" exit 1 ;; esac # 保存会话 python client/save.py "$CLI_NAME"实际用的时候,agent-run claude启动 Claude Code,聊完退出,再agent-run codex,Codex 就能看到刚才的对话。整个过程用户无感,就像在用一个工具一样。
注意:
--resume和--history-file这些参数是我当时用的版本支持的,不同版本可能不一样。如果你的 CLI 版本不支持,可以改成把历史内容作为第一条用户消息注入,效果差一点但也能用。
5. 常见问题与排查技巧实录
5.1 会话加载后模型"失忆"
这是最常见的问题。表现是历史明明加载了,但模型回答的时候完全不提之前聊过的内容。原因通常有三个:
第一,历史被当成了系统提示而不是对话历史。有些 CLI 的注入参数只把内容放到 system 角色里,模型对 system 的重视程度不如真正的对话轮次。解决办法是尽量用支持完整对话历史注入的参数,实在不行就把历史拼成一条长的 user 消息。
第二,token 预算设得太小。我一开始设的 4000,结果稍微长一点的会话就被截得只剩最后几条。后来调到 8000 才比较舒服。你可以根据自己常用的会话长度调整,原则是至少保留最近 10 轮对话。
第三,消息顺序反了。加载的时候如果没注意排序,把最新的消息放在最前面,模型会以为对话是从后往前进行的,行为会很怪。一定要按 created_at 升序排列。
5.2 工具调用结果丢失
这个问题的表现是模型看到了一条 assistant 消息里有 tool_call,但找不到对应的结果,于是反复重试同一个调用,陷入死循环。
根因是截断逻辑没处理好。前面提到过,tool_call 和它的结果必须成对保留。我在load_history里加了一个后处理步骤:
def fix_tool_pairs(messages): result = [] i = 0 while i < len(messages): msg = messages[i] if msg["role"] == "assistant" and msg.get("tool_calls"): # 检查后续是否有对应的 tool 结果 call_ids = {c["id"] for c in msg["tool_calls"]} j = i + 1 found = set() while j < len(messages) and messages[j]["role"] == "tool": if messages[j].get("tool_call_id") in call_ids: found.add(messages[j]["tool_call_id"]) j += 1 if found == call_ids: result.extend(messages[i:j]) i = j continue else: # 结果不全,整组丢弃 i = j continue result.append(msg) i += 1 return result这段逻辑有点绕,但效果很好。宁可丢掉一组不完整的调用,也不要让模型看到残缺的信息。
5.3 中文内容 token 估算偏差大
tiktoken 的 cl100k_base 编码对中文的估算其实还行,但如果你用的是其他编码,或者消息里混了大量代码,偏差会比较大。我的经验是代码和中文都按字符数除以 2 来估算,比除以 4 更接近实际。
如果你对精度要求高,可以在每次请求后从 API 返回的 usage 字段里拿到真实 token 数,回写到数据库。这样下次加载的时候用的就是准确值。代价是要多一次更新操作,但准确性提升明显。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不记得历史 | 注入方式不对 / 预算太小 / 顺序错 | 检查注入参数、调大 max_tokens、确认排序 |
| 工具调用死循环 | tool_call 和结果不成对 | 检查 fix_tool_pairs 逻辑 |
| 服务启动失败 | 端口被占用 | lsof -i :8765查占用进程 |
| 写入报 database is locked | 并发冲突 | 确认 WAL 模式开启、写队列生效 |
| 中文乱码 | 编码不一致 | 统一用 UTF-8,检查文件读写编码 |
| 会话串了 | session_id 冲突 | 检查 .agent-session 文件是否被误改 |
5.5 几个我踩过的坑
坑一:不要用相对路径存数据库。我一开始把 sessions.db 放在项目目录下,结果在不同目录运行agent-run的时候,创建了好几个不同的数据库,会话全乱了。后来改成用~/.agent-relay/sessions.db这种绝对路径,问题解决。
坑二:CLI 退出时的保存要加超时。有些 CLI 退出很快,保存脚本还没跑完进程就没了。我在包装脚本里加了wait和超时保护,确保保存完成再退出。
坑三:定期清理旧会话。用久了数据库会很大,加载变慢。我写了个定时任务,每周清理 30 天没活动的会话。清理前先备份,防止误删。
坑四:适配器要写单元测试。格式转换是最容易出 bug 的地方,而且出错了很难发现。我给每个适配器都写了测试用例,用真实的会话数据做输入,确保转换前后信息不丢失。
6. 会话接力的扩展玩法
6.1 多工具协作的流水线
基础功能跑通之后,我开始尝试更复杂的玩法。比如让 Claude Code 负责需求分析和方案设计,把结果写到会话里,然后自动触发 Codex 去实现,实现完再回到 Claude Code 做代码审查。整个过程通过会话中转层串联,每个工具只做自己擅长的事。
实现方式是在包装脚本里加一个--pipeline参数,读取一个配置文件定义流水线的步骤。每一步执行完检查会话里有没有特定的标记消息,有就触发下一步。这个玩法还在打磨,但已经能跑通简单的场景了。
6.2 会话的导出与归档
有时候我想把某次会话整理成文档,或者分享给同事。中转层天然支持这个需求,加一个导出接口就行:
@app.get("/session/{session_id}/export") def export_session(session_id: str, format: str = "markdown"): messages = load_all_messages(session_id) if format == "markdown": return {"content": to_markdown(messages)} elif format == "json": return {"content": messages}导出成 Markdown 的时候,我会把工具调用渲染成代码块,把用户和助手的对话用引用块区分,读起来很清晰。这个功能我用得挺多,尤其是整理技术方案的时候。
6.3 会话的搜索与检索
消息都存在 SQLite 里,加一个全文搜索很简单。SQLite 自带 FTS5 扩展,建一个虚拟表就能实现:
CREATE VIRTUAL TABLE messages_fts USING fts5( content, content='messages', content_rowid='id' );然后就能用MATCH语法搜索了。我经常用它来找"上次那个关于数据库索引的讨论在哪",比翻聊天记录快多了。中文搜索需要配置分词器,简单场景用 LIKE 也够用。
6.4 后续可以优化的方向
现在这套方案还有几个可以改进的地方。一是消息去重,有时候同一个工具会重复写入相同的消息,加一个内容哈希校验能避免。二是增量加载,现在每次都是全量加载再截断,如果会话特别长会有点慢,可以改成只加载新增部分。三是跨设备同步,目前数据只在本机,如果想在多台机器之间共享,需要加一层同步机制,但这会引入复杂度,我暂时没做。
我个人在实际操作中的体会是,会话接力这件事的价值不在于技术多复杂,而在于它改变了你使用工具的方式。以前你会因为"切换成本太高"而将就着用一个不那么合适的工具,现在可以随时切换到最顺手的那个。这种自由度带来的效率提升,比任何单点优化都明显。最后再分享一个小技巧:.agent-session文件可以加到.gitignore里,但建议同时把会话数据库定期备份到项目外的位置,毕竟那是你长期积累的思考记录,丢了挺可惜的。