【DeepThink Research】长程任务 Agent 的工程实现机制:中断续跑、记忆分层、长上下文不丢失与分布式优雅升级

长程任务 Agent 的工程实现机制:中断续跑、记忆分层、长上下文不丢失与分布式优雅升级

------ 基于 Claude Code / Codex / opencode / OpenClaw 源码与公开资料的深度研究

DeepThink Research · 2026-07-19


DeepThink 是你的私有AI 操作系统 (AI Agent Platform),在安全隔离的沙箱环境中,自主执行代码、管理文件、完成超复杂长程任务。自托管的多用户本地 AI Agent Loop Engineering 系统 (支持桌面端+浏览器+移动端) ------ 让 DeepThink 成为你的全能数字助手。 ------ Powered By AI Genius Institute & 光剑AI

DeepThink 项目开源代码: Gitcode: gitcode.com/AIGeniusIns... Github: github.com/AIGeniusIns...


摘要

长程任务 Agent(Long-Horizon Task Agent)是指那些需要执行数百乃至数千个步骤、跨越数小时乃至数天、期间经历多次中断/恢复/升级仍能保持目标连贯性的自主智能体。以 Claude Code、OpenAI Codex CLI、opencode、OpenClaw 为代表的新一代 AI Coding Agent,已经从"单轮对话工具"演化为"带状态、可恢复、可演化、可集群化"的常驻工程系统。本文基于对四个项目源码的逐文件分析(~/claude-coder~/codex~/opencode~/openclaw),结合 Letta/MemGPT 等学术成果与 Anthropic 工程博客,系统拆解其四个核心工程机制:① 任务状态中断续跑② 记忆分层管理③ 长上下文不丢失④ 分布式集群部署与优雅升级,并提炼出可复用的设计范式与对自研 Agent 平台(如 DeepThink)的启示。

关键词:Long-Horizon Agent、Context Engineering、Session Persistence、Compaction、Subagent Isolation、Stateful Agent、Graceful Upgrade


1. 引言:为什么长程任务 Agent 是一道工程难题

一个能跑 5000 步的 Agent,与一个只能跑 50 步的 Agent,中间隔的不是"更大的上下文窗口",而是一整套状态管理基础设施。核心矛盾有四个:

  1. 中断不可避免:进程崩溃、网络中断、用户主动暂停、配额耗尽、CI 超时------任何一种都会让"内存里的对话"瞬间蒸发。如果不能续跑,前面 4900 步全部白费。
  2. 上下文窗口物理有限:即便 200K/1M token 窗口,几千步的工具调用结果(读文件、grep、测试输出)也会迅速撑爆。无限堆窗口是死路。
  3. 记忆不能扁平化:所有信息一锅塞进 system prompt,既不可扩展也难以维护。记忆必须有层级、有生命周期、有写入策略。
  4. 生产化要能升级:一个跑在用户机器/集群上的常驻进程,必须能在不丢会话状态的前提下完成版本升级、配置变更、横向扩容。

本文围绕这四个矛盾,逐一拆解四个主流实现的真实源码机制。所有引用均给出 文件:行号 或公开链接,确保可复现。


2. 维度一:任务状态中断续跑

2.1 核心思想:把"对话"当作"事务日志"

四个项目不约而同地采用了同一范式------会话状态以 append-only 日志形式落盘,恢复即回放。这与数据库的 WAL(Write-Ahead Log)思想同源:每一条消息/事件在产生时即持久化,进程崩溃后从日志重放重建内存状态。

2.2 Claude Code:JSONL 事务日志式会话

Claude Code 将会话以 JSONL 文件 逐条消息落盘,存储于 ~/.claude/projects/<project-hash>/。每条消息(user / assistant / tool_call / tool_result)独立成行,构成天然的事务日志式 checkpoint。

  • claude --continue / claude -c:自动恢复当前目录最近一次会话,重放 JSONL 回填上下文。
  • claude --resume / claude -r:交互式列表选择历史会话恢复。
  • 恢复时若上下文已膨胀,会先走 compaction 再续跑,避免溢出。

~/claude-coder 的 Rust 实现中,session.rs 给出了双格式持久化与增量写入的具体机制:

  • 双格式 :JSON 全量快照(to_json/from_json,原子写并 rotate)与 JSONL 流式 append(save_to_path/load_from_path 自动嗅探格式)------ session.rs:231-277session.rs:360-411session.rs:485-591
  • 增量写push_message 同时调用 append_persisted_message 追加到 JSONL,失败则回滚 ------ session.rs:279-293。这保证了"每条消息要么完整落盘,要么不存在",无中间态。
  • 回放即 resumeload_from_pathsession_meta / message / compaction / prompt_history 记录重建为内存 Session,下一轮 run_turn 直接把 messages clone 进请求 ------ session.rs:515-591
  • 压缩历史记录SessionCompaction { count, removed_message_count, summary } 让多次压缩不丢元信息 ------ session.rs:62-70session.rs:328-336
  • Forkfork_session / Session::fork 克隆 messages 与 compaction、生成新 session_id 与 SessionFork { parent_session_id, branch_name } ------ conversation.rs:562-564session.rs:339-358。这是"从某点分叉出新会话、不污染原会话"的基础。

2.3 Codex CLI:rollout + SQLite 双写 + 反向扫描重建

Codex(~/codex/codex-rs/)把这一范式做到了工业级:

持久化层

  • rollout/src/lib.rs:24-25 --- 会话文件存放于 ~/.codex/sessions/rollout-<timestamp>-<UUID>.jsonl,归档于 archived_sessions/
  • rollout/src/recorder.rs:84 --- RolloutRecorder 是 JSONL 追加写入器,内部 spawn RolloutWriterTask(:132)异步处理写入队列,失败可重试不丢条目。RolloutRecorderParams::resume(path)(:258)从已有 rollout 文件续写。
  • rollout/src/metadata.rs:153-220 --- backfill_sessions 扫描 sessionsarchived_sessions,回填进 state_dbSQLite),支持分页/过滤/排序。这是"日志 + 索引库"双写:JSONL 是事实源,SQLite 是查询加速层。

恢复层 :Codex 的 resume 不是简单重放整条日志,而是从最新 checkpoint 反向扫描、按需重建

  • core/src/session/rollout_reconstruction.rs:113 --- Session::reconstruct_history_from_rollout 是核心重放函数。算法从最新向最旧扫描(rollout_items.iter().enumerate().rev(),:154),逐轮累积 ActiveReplaySegment,直到同时拿到"存活的 replacement-history checkpoint"与"resume 元数据"即提前停止(:286-294)------不必读完整条日志,O(尾部) 而非 O(全长)。
  • 它提取的重建要素包括:最新存活的 Compacted.replacement_history(作为基线,:181-186)、窗口 ID、最近 TurnContext(模型/comp_hash)、reference_context_itemworld_state_replay(按时间序应用全量快照与 merge-patch,:389-422)、回滚标记(ThreadRolledBack,:188-191)。
  • 之后正向物化:把 rollout_suffix(checkpoint 之后的条目)灌入新的 ContextManager,遇到 Compactedhistory.replace,遇到回滚标记调 history.drop_last_n_user_turns ------ :325-373
  • 调用方:session/mod.rs:1263 record_initial_history 处理 InitialHistory::Resumed:1303 处理 InitialHistory::Forkedhandlers.rs:536 处理 live fork/resume。

命令层

  • cli/src/main.rs:180,192 --- codex resume / codex fork 子命令;--last 选最近一条。
  • tui/src/resume_picker.rs:99,387 --- SessionPickerAction::Resume/Fork,通过 app-server 查询 thread-store 获取可恢复会话列表。
  • core/src/agent/control/spawn.rs:720 --- resume_single_agent_from_rollout:读 history.items,封装为 InitialHistory::Resumed(ResumedHistory{...})(:746,含 rollout_path),再走 resume_thread_with_history_with_source 把历史重放进新会话。
  • core/src/codex_thread.rs:80 --- forked_from_thread_id 记录 fork 来源;:223 emit_thread_resume_lifecycle 触发 on_thread_resume 扩展事件。Fork 本质:复制历史快照到新 thread_id 新 rollout,不污染原会话。

resume 与 fork 的严格分离 (Rust port 的关键设计点):resume 复用同一 conversation_id、通过 RolloutRecorderParams::Resume { path } 重开原 rollout 文件以 append 模式续写(recorder.rs:1802-1819 open_rollout_for_appendensure_rollout_is_newline_terminated 修复缺失尾换行,ordinal_state_for_rollout 扫描算下一个 ordinal);fork 则总是走 RolloutRecorderParams::Create { forked_from_id, ... } 生成全新 ThreadId + 全新 rollout 文件,源历史以 InitialHistory::Forked 拷贝(thread_manager.rs:1017-1092 fork_thread_with_initial_historyfork_history_from_snapshot 注入 InterruptedTurnHistoryMarker 标分支点)。甚至 JSON-RPC thread/resume 带 inline history 字段时也走 fork 式路径(resume_thread_from_history 返回 InitialHistory::Forkedthread_processor.rs:3439-3445),只有按 id/path resume 才返回 InitialHistory::Resumed 保留同 conversation_id。rollout 文件布局为 ~/.codex/sessions/YYYY/MM/DD/rollout-<date>-<conversation_id>.jsonlrecorder.rs:1505-1535),冷文件由后台 worker 压缩为 .jsonl.zstcompression.rs:29 spawn_rollout_compression_worker),append 前透明解压回 .jsonl

持久化策略(什么写什么不写)rollout/src/policy.rs:9 is_persisted_rollout_item 决定耐久性。所有 ResponseItem 持久化(除 AdditionalTools/CompactionTrigger/Other);Compacted/TurnContext/WorldState/SessionMeta 永远持久化;事件按 should_persist_event_msg 过滤------TurnStarted/TurnComplete/ThreadRolledBack/TokenCount 永久,UserMessage/AgentMessage 仅在 Legacy 模式,瞬态 delta/error 不写。

2.4 opencode:V2Session + Effect 框架 + 三段式 revert

opencode(~/opencode,TypeScript/Bun)用 Effect 函数式框架重新实现了同样的语义,并把"会话操作"显式建模为一个操作集合。其持久化是 SQLite + 事件溯源双轨

  • packages/core/src/database/database.ts:43-57 --- DB 解析为 ~/.local/share/opencode/opencode-{channel}.db(WAL 模式,busy_timeout=5000foreign_keys=ONsynchronous=NORMAL),通道(latest/beta/prod)隔离 DB 文件可并存。
  • 表结构(packages/core/src/session/sql.ts:32-130):SessionTable(会话元数据)、MessageTable/PartTable(V1 消息/part,JSON blob)、SessionMessageTable(V2 事件流,seq 单调递增 + (session_id,seq) 唯一索引)、SessionInputTable(待消费用户输入队列)、SessionContextEpochTable(系统上下文快照基线)。
  • V2 是事件溯源 架构:每次变更都是 SessionMessageTable 中一行(type 区分 user/assistant/tool/compaction/system/synthetic/shell)。SessionHistory.loadpackages/core/src/session/history.ts:54-101)从最新 compaction event seq 起回放,跳过已被压缩的历史------这就是 resume 原语,与 Codex 反向扫描同构。

packages/core/src/session.ts 定义 V2Session 服务,暴露 create/get/list/messages/message/context/history/prompt/shell/skill/switchAgent/switchModel/compact/wait/resume/interrupt/revert 等操作(:208-449)。其中:

  • resume(sessionID)(:426)--- yield* execution.resume(sessionID),唤醒挂起的执行。
  • prompt(:360)--- if (input.resume !== false) yield* execution.wake(admitted.sessionID)(:382),即提交 prompt 时可带 resume 语义续跑。
  • interrupt(sessionID)(:430)--- 中断运行中的会话(保留状态)。
  • revert.stage/clear/commit(:434-449)--- 三段式回滚stage 暂存一个可回退点,clear 丢弃暂存,commit 提交。这是比单纯 fork 更细粒度的 checkpoint 原语,等价于"事务保存点"。
  • compact(:417)--- 显式压缩操作。

并发 drainSessionRunCoordinatorpackages/core/src/session/run-coordinator.ts:24-104)是 per-key 串行执行器------run(key) 加入在飞 drain 或启动新的;wake(key) 合并后续唤醒;interrupt(key) 取消 owner fiber;settle(:51)处理 pendingWake 接力避免丢失续跑请求。接线在 session/execution/local.ts:16-37,注释明确写着 "Replace local ownership with durable multi-node ownership when clustered"------集群化是 roadmap,目前单进程 。实际 drain 循环在 SessionRunner.runrunner/llm.ts:358-410)先调 failInterruptedTools(:118-138)把上次崩溃遗留的 pending/running tool 调用标为 failed,再循环 runTurn。续跑即再次调用 resume(sessionID),内存 runner 重建,状态全在 SQLite。

snapshot/checkpointpackages/opencode/src/snapshot/index.ts:71 用一个独立的 bare git 仓库 ~/.local/share/opencode/snapshot/{projectID}/{hash(worktree)},与用户工作树解耦。track() 写 commit、patch(hash) 出 diff、restore checkout、revert(patches) 反向。SessionRevert.revertsession/revert.ts:38-88)遍历 message/part 收集 patch,调 snap.revert,把 revert state 存到 session row 的 revert 字段------用户可见的"回滚到某步"。

会话状态在 server 端持久,client 崩溃不丢任务;详见第 5 节。

2.5 OpenClaw:复用 codex app-server 协议 + active-memory session 层

OpenClaw(~/openclaw)的策略是站在 codex 的肩膀上scripts/sync-codex-app-server-protocol.ts 会从 codex 生成 app-server 的 JSON-RPC 协议 schema,落到 extensions/codex/src/app-server/protocol-generated/。这意味着 OpenClaw 的会话/恢复语义直接复用 codex 的 rollout + thread-store 体系,而非另起炉灶。

在此之上,OpenClaw 加了一层 active-memory 扩展(extensions/active-memory/):

  • session.ts --- resolveCanonicalSessionKeyFromSessionId(:12)把外部 sessionId 规约到 canonical sessionKey,跨 IM 渠道(iMessage/WhatsApp/Telegram)绑定同一逻辑会话;persistPluginStatusLines(:271)通过 patchSessionEntry 把 active-memory 的状态行持久化进 session entry(:293-328),崩溃后可恢复。
  • session-policy.ts --- api.runtime.state.openKeyedStore(:20)提供按 session 粒度的键值存储,承载会话级策略。

换言之,OpenClaw = codex 会话内核 + 多渠道会话绑定层 + 持久化插件状态。这是"协议复用 + 扩展点"的典型工程取舍。

2.6 小结与范式提炼

中断续跑的可复用范式:

要素 实现
事实源 append-only 日志(JSONL rollout)
加速层 索引库(SQLite state_db)
恢复算法 从最新 checkpoint 反向扫描 + 正向物化,O(尾部)
细粒度原语 fork(分支不污染)/ revert stage-clear-commit(保存点)
持久化策略 区分耐久/瞬态事件,瞬态 delta 不落盘
崩溃一致性 增量 append 失败回滚,无中间态
flowchart A[Agent Loop 每步] --> B{push_message} B -->|同步 append| C[(JSONL Rollout 日志)] C -->|异步 backfill| D[(SQLite 索引库)] E[崩溃/中断] --> F[resume 命令] F --> G[反向扫描找最新 Compacted checkpoint] G --> H[正向物化 checkpoint 之后条目] H --> I[重建 ContextManager 内存态] I --> A

3. 维度二:记忆分层管理

3.1 为什么不能"一锅端"

如果把所有记忆(项目规范、用户偏好、历史决策、工具结果)都塞进 system prompt,会导致三个问题:① token 预算爆炸;② 记忆无生命周期(该忘的忘不掉);③ 修改记忆要重写整段。四个项目的共识是------记忆分层 + 按需注入 + 差分更新

3.2 Claude Code:CLAUDE.md 三级金字塔 + auto-memory

Claude Code 的记忆金字塔(由高到低):

  1. CLAUDE.md(项目级 + 用户级 + 全局级),随上下文每次注入顶部。
  2. ~/.claude/CLAUDE.md 用户私有偏好。
  3. 子目录 CLAUDE.md 按需加载(递归向上读取)。
  4. Auto-memory:近期版本自动将"用户偏好/项目事实"追加写入 CLAUDE.md,形成自进化记忆。

来源:Claude Code memorysettings

3.3 Codex:AGENTS.md 层级发现 + world-state 差分注入

Codex(~/codex/codex-rs/)把记忆分层做成了严格的"发现 + 注入 + 差分"三段式:

发现(core/src/agents_md.rs

  • 算法(模块文档 :1-16):从 cwd 向上找 project_root(按 project_root_markers,默认 .git),绝不越过项目根 ;再从 root 向下到 cwd 收集每层 AGENTS.md,按序拼接。
  • DEFAULT_AGENTS_MD_FILENAME = "AGENTS.md"(:38)、LOCAL_AGENTS_MD_FILENAME = "AGENTS.override.md"(:40,本地覆盖优先)。
  • AGENTS_MD_SEPARATOR = "\n\n--- project-doc ---\n\n"(:44)------ 用户/内部指令与项目指令的分隔符。
  • read_agents_md(:89)读取每个发现的文件,强制 project_doc_max_bytes 预算(:95),超限截断(:120-130),附 InstructionProvenance::Project { source_path, environment_id, cwd }(:134-141)溯源。
  • agents_md_paths(:155)合并非 Project 层读 project_root_markers,调 find_nearest_ancestor_with_markers(:180),构建 root→cwd 搜索目录列表(:188-205)。

全局用户层(codex-home crate)

  • CodexHomeUserInstructionsProvidercodex-home/src/instructions/mod.rs:14)实现 UserInstructionsProvider trait(:70)。load_from_codex_home(:24)按序扫描 AGENTS.override.mdAGENTS.md(:9-10),返回 UserInstructions { text, source }。在 cli/src/main.rs:1973 接入。

注入(关键设计:AGENTS.md 不进 base_instructions) : Codex 有两条独立注入通道------base_instructions(作为 Responses API instructions 参数一次性发送)与一组 contextual user/developer 消息(world-state 片段,AGENTS.md 走这条)。

  • core/src/session/mod.rs:609-613 --- base_instructions 优先级:CLI/文件 config.base_instructions → resumed-thread BaseInstructionsModelInfo.get_model_instructions(personality)(模型默认,protocol/src/openai_models.rs:476,模板 BASE_INSTRUCTIONS_DEFAULT = protocol/src/models.rs:1255include_str!("prompts/base_instructions/default.md"))。
  • core/src/session/mod.rs:3399 --- for fragment in world_state.render_full()role() 分派:"developer" 打包成 developer 消息,"user" 打成 contextual user 消息。AGENTS.md 内容在此作为 user-role 消息 (包裹在 # AGENTS.md instructions ... </INSTRUCTIONS>)注入,而非塞进 system prompt。

差分更新(避免每轮重发全部记忆)

  • core/src/context/world_state/mod.rs --- render_full(:282,线程启动/压缩后全量渲染)、render_diff(:287,与持久化快照 diff)、render_history_diff(:298,稳态路径,只重发"变更或从历史中消失"的 section)。
  • core/src/session/mod.rs:2801 record_step_world_state_if_changed --- 每步渲染 render_diff,记录结果项,持久化 WorldState merge-patch rollout 项,存新基线。AGENTS.md 中途变更以 in-band user 消息送达REPLACEMENT_NOTICE/REMOVAL_NOTICEworld_state/agents_md.rs:9-11),而非重写 base_instructions。
  • agents_md_manager.rs:11 --- AgentsMdManagerTurnEnvironmentSelection 缓存 LoadedAgentsMdrefresh 选择未变时短路(:32)。

Config 分层(config/src/config_layer_source.rsprecedence()(:31)定义 Mdm(0) → System(10) → EnterpriseManaged(15) → User(20/21) → Project(25) → SessionFlags(30) → Legacy(40/50)。loader/mod.rs:116 按磁盘层序加载(admin→system→cloud→user→profile→cwd→tree→repo→runtime),不可信的 project/repo/cwd 层加载但禁用PROJECT_LOCAL_CONFIG_DENYLIST(:62)阻止项目级配置设凭据/model_provider/notify 等敏感键。merge.rs:7 merge_toml_values 递归合并含键别名归一化。

3.4 Letta:学术级的显式分层

Letta(前身 MemGPT,arxiv 2310.08560 / 2502.10428)把分层做到了理论清晰:

  • Core memory(persona block + human block):每次 LLM 调用强制注入顶部,容量小而精。
  • Episodic memory:近期事件日志,运行时加载、周期性截断压缩。
  • Archival memory :长期存储(向量库),仅 agent 主动调 archival_memory_search 时按需检索。
  • Memory stream:后台异步进程,按 importance score 给新事件打分路由到不同层。
  • Self-editing memory :agent 用 core_memory_replace/core_memory_append 工具自编辑记忆。
  • Sleep-time computation:空闲期归档旧记忆、压缩 episodic、更新 core------"睡眠整理记忆"。

来源:arxiv 2502.10428Letta memory hierarchysleep-time

3.5 范式提炼:记忆的三轴分层

flowchart TB subgraph 按生命周期 A1[Core/永久] --> A2[Episodic/近期] --> A3[Archival/长期归档] end subgraph 按来源 B1[全局用户级 ~/.codex/AGENTS.md] B2[项目根→cwd 层级 AGENTS.md] B3[Config 分层 Mdm→System→User→Project→SessionFlags] end subgraph 按注入时机 C1[启动/压缩后 全量渲染] C2[稳态 每步差分] C3[按需 主动检索] end

四个项目的共识:记忆不进 system prompt 主体,而以 contextual 消息注入 + 差分更新记忆有生命周期 (永久/近期/归档);记忆可被 agent 自编辑


4. 维度三:长上下文管理------几千步不丢失

这是最硬核的一节。"几千步骤上下文仍然能够不丢"靠的不是窗口大小,而是多层级的上下文精炼 + subagent 隔离 + 可回放压缩

4.1 压缩的三条路由(Codex compact.rs 家族)

Codex 把"压缩"实现了三种可路由的策略,选择发生在 core/src/session/turn.rs:980 run_auto_compact

  1. Token-budget 变体compact_token_budget.rs:49)------ 跳过模型摘要 ,直接 start_new_context_window 安装全新窗口。最快、最省 token,代价是丢弃对话连续性,适合"窗口已满、开新篇"。
  2. 本地 Responses 摘要compact.rs:92/:124/:221)------ 用 SUMMARIZATION_PROMPT 流式让模型生成结构化 summary(scope/tools/recent user requests/pending work/key files/key timeline),再 build_compacted_history 组装替换历史。
  3. 远端摘要 v1/v2compact_remote.rs/compact_remote_v2.rs)------ 服务端 summarization;v2 保留 user/developer/system 角色消息,受 RETAINED_MESSAGE_TOKEN_BUDGET = 64_000compact_remote_v2.rs:54)预算约束。

触发core/src/session/context_window.rs:23 context_window_token_status 是中央预算检查。token_limit_reached(:75-79)当 auto_compact_scope_tokens >= buffered_auto_compact_limit(scope limit + fallback buffer)或 active_context_tokens >= full_context_window_limit(模型硬上限)时为真。触发时机分三阶段:PreTurn(:836,采样前)、MidTurn(:368,采样中遇 ContextWindowExceeded)、StandaloneTurn(手动)。

保留什么/丢弃什么(本地路径):

  • 保留 :token 预算内最近的真实 user 消息(最多 COMPACT_USER_MESSAGE_MAX_TOKENS = 20_000,newest-biased)、新生成的 summary、重注入的 canonical initial context。system prompt 经 base_instructions 单独提供,不在 replacement_history 内。
  • 丢弃:所有 assistant 消息、reasoning、tool calls、tool outputs、旧 summary------全部压进新 summary。
  • 边界保护 :若首条保留消息是 ToolResult,回退边界以保留配对的 assistant ToolUse,避免 OpenAI-compat 400 ------ compact.rs:124-166
  • 二次压缩merge_compact_summaries 把旧 summary highlights 平铺(非嵌套),防多次压缩膨胀 ------ compact.rs:290-325summary_compression.rscompress_summary(预算 1200 字符/24 行/160 字符每行,按核心细节→段落头→bullet 优先级选行去重)------ :128-156:174-199

关键:压缩是可回放的core/src/session/mod.rs:2983 replace_compacted_history 持久化 RolloutItem::Compacted(含 replacement_history: Some(items))+ 可选 WorldState 全量快照 + TurnContext reference item(:3012-3022)。恢复时 reconstruct_history_from_rollout 直接以最新 Compacted.replacement_history 为基线(§2.3),不必重放整条日志------压缩点即 checkpoint 点

4.2 Claude Code:trident 三阶段精炼 + auto-compact 阈值

~/claude-coder 的 Rust 实现给出了更精细的"压缩前预处理"管线------tridenttrident.rs:105-160trident_compact_session),在标准 compact_session 之前跑三阶段:

  • Stage 1 Supersede (:179-228):对同一文件的旧 Read/Write/Edit,在最后一次 Write/Edit 之后视为过时,零成本删除------工具调用结果的"垃圾回收"。
  • Stage 2 Collapse (:299-379):连续 ≥4 条无 ToolUse/ToolResult 的小消息(<200 字符)合并为 [Collapsed Conversation] system 摘要------压缩对话气泡。
  • Stage 3 Cluster (:426-510,:566-602):用 Jaccard 相似度(0.4 tool + 0.4 file + 0.2 length,阈值默认 0.6)聚类相似消息,每组替换为 [Clustered N messages] 摘要------去重相似工作。

之后再走标准 compact_session(:153)。这套"先回收过时、再合并碎片、再聚类相似、最后摘要"的流水线,是"几千步不丢主线"的关键------它把 token 预算花在刀刃上。

阈值机制

  • token 估算 estimate_session_tokens/estimate_message_tokenslen()/4 + 1 粗估(非真实 tokenizer)------ compact.rs:35-37:458-474
  • 手动压缩 CompactionConfig { preserve_recent_messages: 4, max_estimated_tokens: 10_000 } ------ compact.rs:15-22
  • 自动压缩阈值 DEFAULT_AUTO_COMPACTION_INPUT_TOKENS_THRESHOLD = 100_000 input tokens(基于 provider 真实返回的 usage_tracker 累计,可由 CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS 环境变量覆盖),运行时遇 400 错误可动态下调 ------ conversation.rs:18-19:571-576:706-720:210-212
  • 自动压缩用 max_estimated_tokens: 0 最大化压缩 ------ conversation.rs:578-584

保留策略 :合成一条 system continuation 消息(summary + "Recent messages are preserved verbatim" + 续写指令)+ 最近 N 条原文 ------ compact.rs:71-92:174-183

4.3 Subagent 上下文隔离:把 token 消耗关进笼子

这是"几千步不丢主线"的最核心机制 。主 agent 调用 subagent,subagent 拥有独立、隔离的 context window,中间探索(读几十个文件、失败尝试)的 token 全部消耗在 subagent 内,主 agent 仅收到压缩后的 result。

Claude Code(~/claude-coder 的 Task 工具)

  • tools/src/lib.rs:4091-4093 --- execute_agentexecute_agent_with_spawnspawn_agent_job,用 std::thread::Builder::spawn 起独立线程。
  • lib.rs:4211-4231 --- build_agent_runtime 传入全新 Session::new(),不复用父 session------这是隔离的物理边界。
  • 三件套隔离边界:
    • (a) 独立 system prompt(追加 "You are a background sub-agent... do not ask the user questions")------ lib.rs:4233-4247
    • (b) allowed_tools_for_subagentsubagent_type 白名单裁剪工具集 ------ lib.rs:4257-4336
    • (c) SubagentToolExecutor + PermissionEnforcer + PermissionMode::DangerFullAccess(仍受 tool requirement 约束)------ lib.rs:4219-4223:4338-4343
  • DEFAULT_AGENT_MAX_ITERATIONS = 32 ------ lib.rs:4089:4203
  • 结果回传不走 context,而走磁盘 manifest :写 output_file(.md) 与 manifest_file(.json),终态由 persist_agent_terminal_state 落盘 ------ lib.rs:4107-4138:4157:4355-4379。即父 agent 通过读文件而非膨胀 context 获取结果。

TaskPacket(结构化任务包)task_packet.rs:29-67 定义 TaskPacket(objective/scope/scope_path/repo/worktree/branch_policy/acceptance_criteria/resources/model/provider/permission_profile/commit_policy/reporting/escalation/recovery/verification_plan),validate_packet(:115-191)校验后存入 task_registrycreate_from_packettask_registry.rs:133-144)生成 Task。

TaskRegistry 状态机task_registry.rs):

  • Arc<Mutex<RegistryInner { tasks: HashMap<String, Task>, counter }>> 内存态 ------ :105-114
  • 状态机 Created→Running→Blocked/Completed/Failed/Stopped;stop 拒绝终态 ------ :12-34:249-269
  • 调度视图 lane_board_at 按 active/blocked/finished 分桶,用 LaneHeartbeat.freshness_at 判定 Healthy/Stalled/TransportDead/Unknown(基于 stalled_after_secs)------ :67-78:199-241。这是多 agent 协作的"看板"。
  • 注意:registry 只登记状态,不调度执行;真正执行由 Task 工具的 spawn_agent_job 线程 + worker_boot 状态机驱动。

Codex 的 subagent forkcore/src/thread_manager.rs:735 spawn_subagent --- flush 父 rollout,读 stored thread,包成 InitialHistory::Forkedfork_history_from_snapshot(ForkSnapshot::Interrupted, ...),:758),再 start_thread_with_options_and_fork_source。每个 subagent 是独立 Session/thread + 独立 ContextManager + 独立 rollout 文件。agent/control/spawn.rs:554 支持 SpawnAgentForkMode::LastNTurns(n)(调 truncate_rollout_to_last_n_fork_turnsthread_rollout_truncation.rs:241)或 FullHistorykeep_forked_rollout_item(:590)剥离 multi-agent-v2 usage-hint 消息。

agent-graph-store cratesrc/store.rs:17):trait AgentGraphStore 提供 upsert_thread_spawn_edge/set_thread_spawn_edge_status/list_thread_spawn_children/list_thread_spawn_descendants------纯拓扑持久化(父→子有向图),父线程可引用子线程结果而不必把全部 token 拉入父上下文

opencode 的 subagentpackages/opencode/src/agent/agent.ts:38 --- mode: Schema.Literals(["subagent", "primary", "all"]),subagent 是一等公民的 agent 模式;subagent-permissions.ts 控制子代理权限;:193/:216 在调用处标记 mode: "subagent":333 防止 default agent 被设为 subagent。

4.4 历史持久化与回放(claude-coder conversation.rs/session.rs

  • 会话循环:run_turn 每轮构造 ApiRequest { system_prompt, messages: self.session.messages.clone() } 整体发送,assistant 与 tool_result 依次 push_message ------ conversation.rs:364-367:403-405:512-514

4.5 范式提炼:长上下文不丢失的四层防线

flowchart TB L1[第1层: per-item 截断<br/>工具输出超长即时截断] --> L2[第2层: trident 精炼<br/>supersede/collapse/cluster 预处理] L2 --> L3[第3层: compaction 三路由<br/>token-budget/local/remote 摘要] L3 --> L4[第4层: subagent 隔离<br/>独立 context window + 磁盘 manifest 回传] L4 -.可回放 checkpoint.-> L3 L3 -.压缩点即 checkpoint.-> R[(Rollout 持久化<br/>崩溃可反向扫描重建)]

核心洞察:"不丢失"不等于"全保留"。真正的机制是------把"重要信息"(决策、改动、pending work、key files)压进 summary 并 checkpoint 化,把"过程垃圾"(过时工具结果、相似探索、失败尝试)回收或隔离到 subagent,主线上下文永远精简。


5. 维度四:分布式集群部署与优雅升级

5.1 从单进程 CLI 到常驻 server 架构

四个项目呈现出清晰的"进程模型演进":Claude Code 单进程 CLI + 内嵌 Task 递归 → Codex 三层(TUI/app-server/core)→ opencode client/server 分离 + daemon → OpenClaw 复用 codex 协议 + 多渠道 + Sparkle 升级。

关于 Claude Code(~/claude-coder)分布式能力的真实状态 :源码审计显示,其"分布式/升级"更多停留在 schema 与状态机设计层 而非落地实现。rust/crates/runtime/src/remote.rs:41-211 定义了 UpstreamProxyBootstrap/UpstreamProxyState,读取 CLAUDE_CODE_REMOTE 等环境变量、计算 http://127.0.0.1:{port} 代理与 wss://.../v1/code/upstreamproxy/ws 远端 WebSocket URL,并能把代理环境变量注入子进程(subprocess_env,:161-182),但全仓无 TcpListener::bind 起这个本地代理 server ------只在 main.rs:14307 测试中出现。worker_boot.rs:8-12 自述"In-memory worker-boot state machine",WorkerRegistry::create(:307-347)只往 HashMap 插记录,靠字符串匹配屏幕文本推断状态,无 thread::spawn/Command::spawnteam_cron_registry.rs:135-230CronRegistryArc<Mutex<HashMap>>,无调度线程、无 cron 表达式解析器。/upgrade slash 命令在 commands/src/lib.rs:303 注册但在 main.rs:8209-8234 落入空 stub。lane_events.rs 定义了 LaneEventName(lane.started/ready/blocked/red/green/...)事件 schema + reconcile_terminal_events 乱序调解,但无并发调度器。ROADMAP.md:474 把"mixed-version claw fleets"列为愿景。换言之,Claude Code 的分布式集群是"设计先行、实现待补",而 Codex/opencode/OpenClaw 已在 daemon 与热升级上落地------这正是本节聚焦后三者的原因。

5.2 Codex 的三层架构与 daemon 热升级

三层codex-rs/tui(终端 UI 客户端)↔ codex-rs/app-server(长驻服务进程)↔ codex-rs/core(引擎)。codex-rs/cli 仅路由分发。

IPC 多 transportapp-server-transport/src/transport/mod.rs:73-148):AppServerTransport 枚举 Stdio / UnixSocket{socket_path} / WebSocket / OffDEFAULT_LISTEN_URL="stdio://"(:112)。unix_socket.rs:30UnixListener::bind,:47 accept 循环。

daemon(app-server-daemon/src/lib.rs:31-35 :用 app-server.pid/app-server-updater.pid/daemon.lock 文件管进程;:78 探测现存 socket 获取版本;:232 start_pairing 走 daemon socket。

优雅升级(update_loop.rs:55-101------这是工业级热升级的范例:

  • update_loop 监听 SIGTERM,循环 update_once:比对 ExecutableIdentity,决定 restart_mode/updater_refresh_mode,热重启 app-server。
  • :144 reexec_managed_updater 用受管 codex 二进制重 exec 替换 updater。
  • :160-191 fetch_standalone_updater 走远端下载 standalone updater 二进制并 spawn。
  • managed_install.rs:38-98 managed_codex_versioncodex --version 校验受管二进制版本;parse_codex_version 解析输出。
  • 升级原子性:daemon 通过 pid 文件 + daemon.lock 保证原子替换,TUI 重连 Unix socket 即可------session 状态在 rollout/SQLite 落盘,进程替换不丢

MCP 集成(rmcp-client/src/rmcp_client.rs:49-100 :三种 transport------DuplexStream(in-process)、StdioServerTransport(子进程 stdio)、StreamableHttpClientTransport(HTTP/SSE + OAuth)。core/src/mcp.rs:30-85 McpRegistry 汇总 config + 插件贡献的工具。codex-mcp crate 把 codex 自身作为 MCP server 对外暴露工具。

多会话并发thread-store/src/lib.rs:15-24 ThreadStore trait + LocalThreadStore(本地 SQLite+rollout)/InMemoryThreadStore(测试);live_thread::LiveThread 跟踪并发活跃线程。agent/control/spawn.rs reserve_spawn_slot 做并发槽位限额(agent_max_threads,:761)。

5.3 opencode 的 daemon + 版本匹配 health check

opencode 的进程模型与升级机制有一处需要澄清:它不是 JSON-RPC,而是 REST/HTTP + SSE ,且无进程内热重载

  • packages/opencode/src/server/server.ts 起一个 Node HTTP server,暴露 Effect HttpApiOpenCodeHttpApi),事件流在 /event + /global/eventpublic.ts:155)。端口 fallback:显式 0 时先试 4096 再 0(server.ts:110-114)。mDNS publish(mdns.ts)在非环回地址广播 opencode-{port}.local,供 LAN 内 IDE/客户端发现。一个 workspace 一个 server 进程,多客户端(TUI/web/IDE)共用。
  • 进程模型:单 Bun/Node 进程/workspace instance;InstanceStore 管理实例生命周期;GlobalLifecycle.disposeAllInstancesAndEmitGlobalDisposedserver/global-lifecycle.ts)优雅下线;WebSocketTracker 推送实时更新。优雅关闭:forceStopserver.closeAllConnections()
  • 升级机制packages/opencode/src/installation/index.ts:265-307 Installation.upgrade(method, target) 分派到 curl|sh(默认安装脚本)、npm/pnpm/yarn/bun install -g opencode-ai@targetbrew upgradechoco upgradescoop install------spawn 子进程跑安装器,再 process.execPath --version 验证,无进程内热重载getReleaseType(semver major/minor/patch)驱动 UX;通道 DB 命名让 beta/latest 共存。
  • 并发SessionRunCoordinator 按 sessionID 串行化 drain(每会话同时仅一个在飞 turn),不同 session 并行;SQLite WAL + busy_timeout=5000 处理并发写。
  • 多设备同步packages/opencode/src/sync/ + handlers/sync.tsreplay 端点接收序列化 EventV2 事件,经 events.replayAll(payload, {ownerID, strictOwner: true}) 重放------这是多设备同步原语(事件日志复制)。但 README 明确 "Syncing with only one writer"------目前是单写者模型,多写者协同未完成

回到 daemon 层(packages/cli/src/services/daemon.ts)::40 server.json 注册文件;:44-48 password 跨重启保持一个私有凭据让被发现的 client 复用(避免重启后 client 失联);:71 health check(Registered server is not healthy);:77 版本匹配校验Registered server version does not match the client,client 与 server 版本不一致时拒绝,强制升级对齐);:110 start spawn(..., "serve", "--register") 拉起 server;:155 stop 先认证再 signal PID(防误杀);:164 register

opencode serve --stream-all-parts 把 LLM 每一个 part 流式广播给所有订阅 client,支持多端协同。来源:opencode serve

5.4 OpenClaw:协议复用 + Sparkle + 容器化多平台

OpenClaw 在部署侧的取舍:

  • 协议复用scripts/sync-codex-app-server-protocol.ts 从 codex 生成 app-server JSON-RPC schema 落到 extensions/codex/src/app-server/protocol-generated/------不发明新协议,直接复用 codex 的稳定协议,降低维护成本与互操作摩擦。
  • 多渠道会话extensions/imessagewhatsapptelegram 等把外部 IM 渠道绑定到 canonical session(§2.5),实现"一个逻辑会话跨多个 IM"。
  • 自动更新(macOS Sparkle)appcast.xmlSparkle RSS feed,含 <sparkle:version>/<sparkle:shortVersionString>/<sparkle:minimumSystemVersion>scripts/sparkle-build.ts:49 sparkleBuildFloorsFromShortVersion 把语义版本转成 YYMMPPPPLL 楼层号(:87 注释保持旧条目字节稳定后切换新格式)。Sparkle 框架自动检查 appcast、下载、原子替换------桌面端优雅升级的标准方案
  • 容器化与多平台Dockerfile/docker-compose.yml/deploy/ + linux/macos/android/ios/macos-mlx-tts 目录------支持容器化集群部署与移动端。

5.5 范式提炼:优雅升级的四个不变量

flowchart S1[不变量1: state 全落外部存储<br/>JSONL/SQLite/rollout] --> S2[不变量2: client/server 解耦<br/>server 滚动重启 client 重连] S2 --> S3[不变量3: session 级幂等<br/>session_id+message_id 续跑] S3 --> S4[不变量4: worker 无状态化<br/>调度器单一写入点 + 并发锁] S4 --> U[优雅升级<br/>不丢会话/不丢任务/不重复执行]

来源共识:

  1. state 全落外部存储(JSONL/DB),进程无状态。
  2. client 与 server 解耦,server 滚动重启时 client 重连 resume(codex Unix socket、opencode daemon、openclaw app-server)。
  3. session 级幂等(基于 session_id + message_id 续跑,防重复执行)。
  4. worker 无状态化 + 调度器单一写入点(防同一文件并发写,OpenClaw/Cowork 类 scheduler→worker pool,worker 优雅 drain)。

6. 综合对比

维度 Claude Code (~/claude-coder) Codex CLI (~/codex) opencode (~/opencode) OpenClaw (~/openclaw)
中断续跑 JSONL 事务日志 + --continue/--resumeSession::fork rollout JSONL + SQLite state_db;reconstruct_history_from_rollout 反向扫描;codex resume/fork V2Session resume/interruptrevert.stage/clear/commit 三段式 复用 codex 协议 + active-memory persistPluginStatusLines
记忆分层 CLAUDE.md 三级 + auto-memory AGENTS.md root→cwd 发现 + global ~/.codex/AGENTS.md;world-state diff 注入;Config 8 层 precedence AGENTS.md/CONTEXT.md + Effect 服务化 active-memory 扩展 + keyedStore 会话策略
长上下文 trident 三阶段精炼 + auto-compact 100k 阈值;Task subagent 独立 Session + 磁盘 manifest 回传 compact 三路由(token-budget/local/remote);subagent fork LastNTurns/FullHistory;agent-graph-store 拓扑 subagent 一等公民 mode;compact 操作 复用 codex 内核
分布式/升级 单进程 CLI + 内嵌 Task 递归;claude update TUI/app-server/core 三层;daemon update_loop + reexec_managed_updater + pid 原子替换;MCP 三 transport daemon + 版本匹配 health check + stream-all-parts 多端 协议复用 + Sparkle appcast + Docker/多平台
语言 Rust Rust (~90 crate) TypeScript/Bun (Effect) TypeScript (复用 codex)

7. 设计原则提炼(对 DeepThink 的启示)

综合四个实现,长程任务 Agent 的可复用设计原则:

  1. 日志即状态(Log as State):会话状态以 append-only JSONL 为事实源,DB 仅索引加速。恢复从最新 checkpoint 反向扫描,O(尾部)。
  2. 记忆不进主 prompt:以 contextual 消息注入 + 差分更新(render_diff),有生命周期分层(core/episodic/archival),可自编辑。
  3. 压缩点即 checkpoint 点 :compaction 结果持久化为 Compacted.replacement_history,恢复时直接以此为基线------压缩与持久化合二为一。
  4. 先精炼后摘要:trident 式预处理(回收过时→合并碎片→聚类相似)再走 LLM 摘要,把 token 预算花在刀刃上。
  5. subagent 隔离 + 磁盘回传:独立 context window + 工具白名单 + 结果走 manifest 文件而非膨胀父 context------这是上千步不丢主线的物理保障。
  6. 进程无状态 + 协议有状态:worker 无状态化,session 状态全落盘;client/server 解耦,滚动升级时 client 重连。
  7. 升级靠原子替换 + 版本协商:pid 文件 + daemon.lock 原子替换(codex)/ Sparkle appcast(openclaw)/ 版本匹配 health check(opencode),核心是"状态在外、二进制可换"。
  8. 并发靠单一写入点 + 槽位限额reserve_spawn_slot 限并发,scheduler 持全局队列与并发锁防冲突。

对应到 DeepThink 的 Loop Engineering 范式(需求→PRD→技术方案→编码→测试→提交闭环),这套机制直接支撑"一个 Agent 独立完成软件研发全生命周期、持续学习、自我改进"的目标------会话状态可中断续跑保证长任务不丢失,记忆分层保证自进化,subagent 隔离保证多会话并发,优雅升级保证生产常驻


8. 结论

长程任务 Agent 的工程实现,本质上是在有限的上下文窗口无限的任务长度之间建立一套可持久化、可压缩、可隔离、可升级的状态管理基础设施。Claude Code、Codex、opencode、OpenClaw 四个实现虽语言与侧重各异,却收敛到同一组范式:

  • 中断续跑 = append-only 日志 + 反向扫描重建 + fork/保存点原语;
  • 记忆分层 = contextual 注入 + 差分更新 + 生命周期分层;
  • 长上下文不丢失 = 多层精炼 + 可回放压缩 + subagent 隔离;
  • 分布式优雅升级 = 进程无状态化 + client/server 解耦 + 原子替换 + 版本协商。

这些不是某一家的"魔法",而是工程方法论的共识------也是 DeepThink 这类企业级自托管 Agent 平台走向"超级智能体孵化器"的必经之路。


参考文献

源码(本地)

  • ~/claude-coder(Rust 实现):session.rsconversation.rscompact.rstrident.rssummary_compression.rstools/src/lib.rstask_packet.rstask_registry.rsworker_boot.rs
  • ~/codex/codex-rs/(~90 crate):rollout/core/src/agents_md.rscore/src/compact*.rscore/src/session/core/src/context_manager/core/src/context/world_state/core/src/agent/control/spawn.rscore/src/thread_manager.rscore/src/thread_rollout_truncation.rsapp-server-daemon/app-server-transport/agent-graph-store/thread-store/config/codex-home/
  • ~/opencode/packages/core/src/session.tscli/src/services/daemon.tsopencode/src/agent/
  • ~/openclaw/scripts/sync-codex-app-server-protocol.tsextensions/active-memory/session.tsextensions/active-memory/session-policy.tsappcast.xmlscripts/sparkle-build.ts

公开资料


本文由 DeepThink 基于本地源码逐文件分析与公开资料检索综合产出,所有源码引用均给出 文件:行号,可在本地仓库复现。

相关推荐
糖果店的幽灵19 小时前
【langgraph 从入门到精通graphApi 篇】Command 与动态流程控制
android·java·数据库·人工智能·langgraph
深蓝AI19 小时前
KTransformers 实战:消费级显卡本地跑满血 DeepSeek,CPU-GPU 异构推理全攻略
人工智能
魏祖潇19 小时前
AI幻觉不是用更大模型解决——RAG增强+引用溯源+置信度标注让AI开口必带出处
人工智能·ai编程
QN1幻化引擎19 小时前
认知架构调度与语言模型辅助:DalinX V8 Track 1 实验报告
人工智能·语言模型·架构
大模型丫丫19 小时前
Agent开发的难点是什么呢?
大数据·人工智能·学习
kp0000019 小时前
NER(Named Entity Recognition)命名实体识别
人工智能·网络安全·信息安全·ai安全
hqyjzsb19 小时前
非计算机专业学生怎么进入AI相关方向
人工智能·职场和发展·金融·数据挖掘·数据分析·aigc·业界资讯
龙虾PRO19 小时前
金融 AI 分析系统:人工智能驱动的金融行情分析系统落地方案
人工智能·金融
卷无止境19 小时前
KTransformers:让巨型模型跑在你家电脑上的黑科技
人工智能·后端