7大开源Agent源码对比解读——会话管理

7大开源Agent源码对比解读------会话管理

本文是「7大开源Agent源码对比解读」系列第 3 篇。评估对象:codex、gemini-cli、qwen-code、opencode、kimi-code、deepseek-harness(下称 dsh)、oh-my-pi(下称 omp)。所有机制描述附源码引证。

文章 内容速览
总述 拆了七大开源 Agent 的源码,最高分竟然不是 Codex 总体结论、评估方法与评分卡
01 架构对比 四种流派与分化根源、主循环与事件机制、插件化与耦合风险
02 上下文管理 压缩触发阈值、摘要方式、token 计数口径、跨会话记忆
03 会话管理 持久化模型三层次、并发控制、崩溃恢复与 resume / fork
04 工具调用 注册与可见性、并行调度四种语义、审批门控、错误处理
05 重连与容错 重试预算、流中断处理、降级链、副作用安全
06 系统提示词与指令遵循 四种组装范式、动态注入、注入防御三层与共同敞口
07 思维链与工作流编排 思维链接入、plan 模式语义、子代理治理、工作流引擎
08 性能设计 前缀缓存三档分化、启动优化、成本核算
09 可扩展性 MCP 接入、自定义工具、多 provider、SDK 与 API
10 安全与权限控制 沙箱两种语义、审批默认姿态、企业管控、敏感数据保护

会话管理的核心问题是:会话以什么形态落盘,崩溃后怎么恢复,多个进程同时碰同一个会话怎么办。七个项目的持久化模型可以归入三个递进层次。

持久化模型的三个层次

  1. 快照式,代表 gemini-cli。把当前对话状态整体序列化成 JSON 文件,恢复就是读回最近快照。简单,但历史不可分叉、不可重放。
  2. 转录式,代表 codex、qwen-code、kimi-code、omp。会话写成追加式 JSONL 日志,每条记录一个事件,恢复就是从日志重放。已经能 resume 和 fork,但状态投影仍需调用方现场重建。
  3. 事件溯源式,代表 dsh、opencode。日志即唯一真相,所有可读状态(消息、部件、UI)都是日志的投影。天然支持审计、重放、双后端,代价是要维护投影机和事件 schema 版本。

持久化格式与并发控制

项目 持久化格式 并发控制
codex JSONL rollout 按日期分目录 + SQLite 索引 + zstd 冷压缩 进程内单写者任务 + 跨进程文件锁
gemini-cli JSON 记录 + 影子 git 快照(默认关闭) 未发现会话写锁
qwen-code JSONL 转录 + 检查点 session-writer-lease 文件锁租约
opencode SQLite WAL(session/message/part 表) 每 session 一个协调器 + BusyError
kimi-code wire.jsonl + state.json + 自研 minidb 串行 flush + O_APPEND,无跨进程锁
dsh append-only 事件日志,JSONL(zstd)/SQLite 双后端 协调器 LRU + 活会话快照等待
omp JSONL 树(每条带 parentId)+ blob 内容寻址仓 AgentBusyError 禁并发 prompt

恢复与容错设计

项目 resume / fork 崩溃恢复设计
codex resume、fork、revert,fork 记录血缘 revert 保线程 ID 新建不可变文件
gemini-cli --resume + /rewind + checkpoint 磁盘满时降级提示,禁用录制不崩溃
qwen-code --continue、--resume、--fork、/restore 四类恢复计划 + 孤儿 tool_use 修复
opencode --continue、--fork、--replay、share 无状态循环重推导 + 中断工具标记
kimi-code --continue、fork(不继承 goal) 记录重放,只重建内存状态
dsh load、inspect、fork、seed 格式版本拒绝 + crash 补合成 turn/end
omp fork 只移指针 + 导入 Claude/Codex 会话 终端 breadcrumb + continueRecent

逐家看点

codex:JSONL 为体,SQLite 为用

每次会话写成一个 rollout-*.jsonl,按日期分目录存放,文件名本身编码时间戳和线程身份(rollout/src/rollout_file_name.rs:39-47)。JSONL 是规范真相,SQLite 只是查询索引,索引坏了可以由 rollout 重建。

冷数据治理做得很细。后台 worker 把 7 天未修改的 rollout 压成 zstd:先写临时文件、回读校验、再确认原文件未变才落地(compression.rs:632-701)。压缩前还要查 RolloutReferenceIndex,被 fork 引用的 rollout 一律跳过,避免压缩切断 fork 链。

写入走 mpsc 通道,唯一的后台任务串行落盘。后台任务一旦以 IO 错误退出,错误被存住,之后所有写操作原样复现同一个失败(terminal_failure),不做无谓重试。

revert 的语义是「保留前缀、另起新 rollout」:线程 ID 不变,新建一个不可变文件引用保留的前缀,唯一可变的切换点是 SQLite 里的路径指针(thread-store/src/local/revert_thread.rs:15-18)。旧 rollout 原封不动。

gemini-cli:影子 git 三位一体回滚

gemini-cli 独有的「文件 + 对话 + 工具调用」三位一体回滚:每次可回滚的工具调用前,在影子 git 仓库做一次文件快照,连同对话历史和工具调用参数一起存成 checkpoint(checkpointUtils.ts:130-139)。/rewindgit checkout 回该 commit 并重放对话。

影子仓库完全脱离用户真实 git 环境:重置 GIT_CONFIG_GLOBALGIT_DIR 等全部环境变量,专用 identity,禁用 gpg(gitService.ts:102-142)。

默认关闭(checkpointing.enabled default false)。建仓库加每工具一次 commit 确实有成本,但对编辑类工具来说,这是七者中唯一能同时回滚文件和对话的方案,默认关掉有点可惜。

gemini-cli 没有会话级写锁,多开同一会话靠目录隔离,不防并发写同一转录文件。

qwen-code:唯一的多进程写租约

daemon 和 TUI 可能同时持有同一会话,qwen 用一个租约文件保证同一时刻只有一个权威写者。这是七个项目里唯一一套面向「多进程写同一会话转录」的工程级方案,细节密度很高:

  • 锁记录带进程启动身份。 仅靠 isProcessAlive(pid) 判断锁主是否活着会被 PID 复用骗,锁里存 process_start_identity(Linux 读 /proc/<pid>/stat 启动 ticks + boot_id,macOS 用 ps -o lstart=,Windows 用 PowerShell 取 StartTime.Ticks),身份不一致即判过期可回收(session-writer-lease.ts:270-312)。
  • 防符号链接替换。 打开转录文件强制带 O_NOFOLLOW,打开前 lstat 确认是普通文件,防止攻击者把转录路径换成指向别处的符号链接。
  • 转录哈希校验。 获取租约时把整条转录按 1MB 块算 SHA-256,此后每次追加前重新核对,租约之外被改动就抛错。
  • 获取锁用硬链接的 create-new 语义。 写临时文件再 link 到锁路径,EEXIST 即被抢占。

恢复侧是 buildSessionRecoveryPlan:resume 时按转录尾部形态分成四类修复计划(session-recovery.ts:22-26)。

计划类型 触发条件 处理
clean 无中断 直接返回
interrupted_prompt 尾部是用户输入,模型未回完 重发用户输入,可自动续
interrupted_turn 有 functionCall 但无对应响应 合成失败工具结果补全,需确认
degraded_history 检测到父链缺失 禁止自动续,提示历史不完整

「孤儿 tool_use」是公开痛点:转录里有 functionCall 却没有配对的 functionResponse,直接喂给 provider 会报错。qwen 为每个悬空调用合成一个错误响应,把历史闭合成合法续接点(session-recovery.ts:206-209)。

opencode:一切进 SQLite,循环无状态

会话状态全进一个 SQLite 库,开 journal_mode = WAL,消息拆成 message 与 part 两层,part 是流式部件(文本块、工具调用块)。WAL 让多进程读写同一库成为可能。ID 是带前缀的 ULID(ses_msg_prt_),按字典序就是时间序,还能从 ID 反推创建时间。

主循环本身无状态:状态全在库里,每轮从库重读重推导。恢复就是从库重放事件投影出当前状态,不需要单独的 replay 模式。

并发控制是「每个 session 一个协调器」:同一 key 已有在跑的执行就加入它,不同 session 可并发(run-coordinator.ts:5)。已有活跃 turn 时新 prompt 被拒为 PromptConflictError,客户端落入队列重试。

中断处理学 qwen 的思路:工具执行中被打断时,failUnsettledTools 把 pending/running 的工具标为失败并附「Tool execution interrupted」,保证历史里 tool_use 永远有配对结果(runner/llm.ts:306-316)。

kimi-code:自研 KV 与重放契约

会话事件流写进 wire.jsonl,会话级状态快照另存 state.json。有意思的是自研存储引擎 minidb:buffered、append-only、group-commit 的 WAL(单写者,类 SQLite WAL),快照走临时文件加原子 rename 加 WAL 轮转(minidb/src/wal.ts:3-14)。

全文索引的分词器针对中文做了处理:CJK 连续段切成单字加相邻二字两类 token(如「会话」切成 会话),无须词典和分词器(tokenize.ts:3-13)。这是中文检索的经典做法,查询时同样分词后 AND 命中。

恢复契约是「只重建内存状态」:读 wire.jsonl 逐行重放,在内存重建上下文、工具、goal,不回写日志(agent/replay/)。fork 时显式丢弃 goal 文件(FORKED_SESSION_DROPPED_FILES = ['upcoming-goals.json']),新分支从零目标开始。

短板是 wire.jsonl 没有跨进程写锁,并发模型隐含「单一进程持有该会话」假设,多会话场景靠进程边界隔离。对照 qwen 的租约方案,这是可以补的一课。

dsh:不变量、双后端、宁可拒绝

dsh 的会话真相是一条只追加的事件日志,事件带单调 seq。在此之上有一条 repo 级运行时不变量:任何会到达模型请求的内容,必须先作为事件入日志,从而可从日志重建(packages/client/AGENTS.md:55)。纯展示数据不进日志,由 host 每帧重算。
#mermaid-svg-jXoCl5NCcbEd59JS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jXoCl5NCcbEd59JS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jXoCl5NCcbEd59JS .error-icon{fill:#552222;}#mermaid-svg-jXoCl5NCcbEd59JS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jXoCl5NCcbEd59JS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jXoCl5NCcbEd59JS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jXoCl5NCcbEd59JS .marker.cross{stroke:#333333;}#mermaid-svg-jXoCl5NCcbEd59JS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jXoCl5NCcbEd59JS p{margin:0;}#mermaid-svg-jXoCl5NCcbEd59JS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jXoCl5NCcbEd59JS .cluster-label text{fill:#333;}#mermaid-svg-jXoCl5NCcbEd59JS .cluster-label span{color:#333;}#mermaid-svg-jXoCl5NCcbEd59JS .cluster-label span p{background-color:transparent;}#mermaid-svg-jXoCl5NCcbEd59JS .label text,#mermaid-svg-jXoCl5NCcbEd59JS span{fill:#333;color:#333;}#mermaid-svg-jXoCl5NCcbEd59JS .node rect,#mermaid-svg-jXoCl5NCcbEd59JS .node circle,#mermaid-svg-jXoCl5NCcbEd59JS .node ellipse,#mermaid-svg-jXoCl5NCcbEd59JS .node polygon,#mermaid-svg-jXoCl5NCcbEd59JS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jXoCl5NCcbEd59JS .rough-node .label text,#mermaid-svg-jXoCl5NCcbEd59JS .node .label text,#mermaid-svg-jXoCl5NCcbEd59JS .image-shape .label,#mermaid-svg-jXoCl5NCcbEd59JS .icon-shape .label{text-anchor:middle;}#mermaid-svg-jXoCl5NCcbEd59JS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jXoCl5NCcbEd59JS .rough-node .label,#mermaid-svg-jXoCl5NCcbEd59JS .node .label,#mermaid-svg-jXoCl5NCcbEd59JS .image-shape .label,#mermaid-svg-jXoCl5NCcbEd59JS .icon-shape .label{text-align:center;}#mermaid-svg-jXoCl5NCcbEd59JS .node.clickable{cursor:pointer;}#mermaid-svg-jXoCl5NCcbEd59JS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jXoCl5NCcbEd59JS .arrowheadPath{fill:#333333;}#mermaid-svg-jXoCl5NCcbEd59JS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jXoCl5NCcbEd59JS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jXoCl5NCcbEd59JS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jXoCl5NCcbEd59JS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jXoCl5NCcbEd59JS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jXoCl5NCcbEd59JS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jXoCl5NCcbEd59JS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jXoCl5NCcbEd59JS .cluster text{fill:#333;}#mermaid-svg-jXoCl5NCcbEd59JS .cluster span{color:#333;}#mermaid-svg-jXoCl5NCcbEd59JS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jXoCl5NCcbEd59JS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jXoCl5NCcbEd59JS rect.text{fill:none;stroke-width:0;}#mermaid-svg-jXoCl5NCcbEd59JS .icon-shape,#mermaid-svg-jXoCl5NCcbEd59JS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jXoCl5NCcbEd59JS .icon-shape p,#mermaid-svg-jXoCl5NCcbEd59JS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jXoCl5NCcbEd59JS .icon-shape .label rect,#mermaid-svg-jXoCl5NCcbEd59JS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jXoCl5NCcbEd59JS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jXoCl5NCcbEd59JS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jXoCl5NCcbEd59JS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 折叠投影
append-only 事件日志(seq 单调 · JSONL(zstd) / SQLite 双后端)
turn/start
user 消息
assistant 消息
tool 结果
turn/end

平衡边界
模型可见上下文
UI 与存储
resume / fork / 审计 / 回放
运行时不变量:到达模型的内容

必须先入日志,且可从日志重建

双后端:同一份事件流可落地 JSONL(zstd) 或 SQLite。物理差异很实在,SQLite 能按 seq 定位只读后缀,顺序介质的 JSONL 只能整文件解析后跳到目标位置(session-persistence/src/index.ts:212-214)。

几条恢复语义值得抄:

  • 格式版本拒绝先于解释。 后端遇到不认识的格式版本直接拒绝加载,宁可不可用也不冒险误读(session-persistence-jsonl/src/format.ts:234-243)。
  • crash 补合成 turn/end。 冷加载时若最后一轮是被打断的完整 turn,持久地闭合它:补上缺失的工具错误和开着的边界,让日志结尾落在平衡的 turn/end。只读接口 inspect 则只在内存加合成闭合,物理撕裂尾不动。
  • 活会话不做 crash 修复。 仍绑定活 Session 的身份,要么给出当前不可变快照,要么拒绝或等待,绝不与活写者争抢。

omp:树形会话,分支零成本

omp 的会话是一个 JSONL 文件,首行 header,其后每条 entry 都带 parentId,整体是一棵树。会话状态由一个可变的 leaf 指针选择当前活跃路径(session-manager.ts:434-439)。

fork 就是把 leaf 挪回某个祖先再 append:新 entry 从该祖先分叉出去,原分支一字不动地留在文件里。分支零成本、零拷贝、历史永不重写,这是七者中分支成本最低的设计。
#mermaid-svg-HJM7bfWPUIiz3Q5Z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .error-icon{fill:#552222;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .marker.cross{stroke:#333333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z p{margin:0;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster-label text{fill:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster-label span{color:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster-label span p{background-color:transparent;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .label text,#mermaid-svg-HJM7bfWPUIiz3Q5Z span{fill:#333;color:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .node rect,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node circle,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node ellipse,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node polygon,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .rough-node .label text,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node .label text,#mermaid-svg-HJM7bfWPUIiz3Q5Z .image-shape .label,#mermaid-svg-HJM7bfWPUIiz3Q5Z .icon-shape .label{text-anchor:middle;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .rough-node .label,#mermaid-svg-HJM7bfWPUIiz3Q5Z .node .label,#mermaid-svg-HJM7bfWPUIiz3Q5Z .image-shape .label,#mermaid-svg-HJM7bfWPUIiz3Q5Z .icon-shape .label{text-align:center;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .node.clickable{cursor:pointer;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .arrowheadPath{fill:#333333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HJM7bfWPUIiz3Q5Z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HJM7bfWPUIiz3Q5Z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster text{fill:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .cluster span{color:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HJM7bfWPUIiz3Q5Z rect.text{fill:none;stroke-width:0;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .icon-shape,#mermaid-svg-HJM7bfWPUIiz3Q5Z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .icon-shape p,#mermaid-svg-HJM7bfWPUIiz3Q5Z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .icon-shape .label rect,#mermaid-svg-HJM7bfWPUIiz3Q5Z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HJM7bfWPUIiz3Q5Z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HJM7bfWPUIiz3Q5Z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HJM7bfWPUIiz3Q5Z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 沿路径构造上下文
setLeaf(B) 再 append D,

原分支 C 原样保留
会话 header(id、parentSession)
entry A

parentId=null
entry B

parentId=A
entry C

parentId=B(原 leaf)
entry D

parentId=B(新分支)
fork 前的 leaf 指针
fork 后的 leaf 指针

大块二进制(图像)不塞进 JSONL,外置到内容寻址的 blob 仓,按 sha256 存,天然幂等去重(blob-store.ts:26-32)。

omp 还能导入别家会话:把 Claude、Codex 的会话转成自己的树形 JSONL 继续(foreign-session-import 系列文件)。崩溃恢复靠终端 breadcrumb:创建会话时立即写一条面包屑,进程随后崩溃也能靠 continueRecent 找回。

给 Agent 开发者的借鉴清单

  1. 孤儿 tool_use 必须有标准处理。 崩溃恢复后,没有配对结果的工具调用会让 provider 拒绝整个请求。qwen 的合成错误响应和 opencode 的 interrupted 标记是两种成熟做法。
  2. 多进程会碰同一会话就上写租约。 qwen 的 lease 是完整样本:硬链接创建、进程启动身份防 PID 复用、O_NOFOLLOW 防符号链接、追加前哈希复核。
  3. 树形日志 + leaf 指针让分支免费。 比复制会话文件省得多,历史还不可变(omp)。
  4. 事件溯源让恢复语义变简单。 resume、fork、审计、回放都是同一份日志的投影(dsh、opencode)。
  5. 格式版本不兼容时宁可拒绝。 误读旧日志造成的数据损坏比「暂时打不开」严重得多(dsh)。
  6. 回滚要同时管文件和对话。 只回滚对话不回滚文件,用户看到的还是脏工作区(gemini-cli 影子 git 是唯一完整方案)。
  7. 冷数据压缩前检查引用。 被 fork 引用的历史不能压,否则分支断链(codex 的 RolloutReferenceIndex)。

文档地图

文章 内容速览
总述 拆了七大开源 Agent 的源码,最高分竟然不是 Codex 总体结论、评估方法与评分卡
01 架构对比 四种流派与分化根源、主循环与事件机制、插件化与耦合风险
02 上下文管理 压缩触发阈值、摘要方式、token 计数口径、跨会话记忆
03 会话管理 持久化模型三层次、并发控制、崩溃恢复与 resume / fork
04 工具调用 注册与可见性、并行调度四种语义、审批门控、错误处理
05 重连与容错 重试预算、流中断处理、降级链、副作用安全
06 系统提示词与指令遵循 四种组装范式、动态注入、注入防御三层与共同敞口
07 思维链与工作流编排 思维链接入、plan 模式语义、子代理治理、工作流引擎
08 性能设计 前缀缓存三档分化、启动优化、成本核算
09 可扩展性 MCP 接入、自定义工具、多 provider、SDK 与 API
10 安全与权限控制 沙箱两种语义、审批默认姿态、企业管控、敏感数据保护
相关推荐
Csvn27 分钟前
第 9 章 记忆系统
人工智能·aigc·agent
m4Rk_37 分钟前
【论文阅读】Agent 记忆机制(54):MINJA——普通用户如何仅通过查询污染 Agent 的长期记忆
论文阅读·人工智能·学习·开源·github
weixin_4402132940 分钟前
多Agent项目落地技术选型全维度指南(含闭环评估体系·工程实战
agent·闭环评估
木圭的AI时代指南44 分钟前
商汤日日新TokenPlan大调整与接入全解
人工智能·ai·语言模型
leeyi44 分钟前
ACP 协议 + devops 可视化:Agent 与编辑器的两条桥(第99篇-E85)
aigc·agent·devops
一克拉的眼泪很珍贵1 小时前
23 - 综合实战(上):需求分析与架构设计!从零到一构建生产级智能客服Agent
人工智能·ai·性能优化·架构·需求分析
严同学正在努力1 小时前
Oracle Data Guard 容灾实战:从搭建到切换,一篇讲透
数据库·ai·oracle
javgo.cn1 小时前
Markweave:开源 Markdown-first WYSIWYG 编辑器
开源·编辑器
七77.2 小时前
SceneAssistant: A Visual Feedback Agent for Open-Vocabulary 3D Scene Generation
3d·agent·世界模型