Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

状态模型

一个可持续工作的 Agent 必须记得自己正在做什么,但“记得”不能全部交给模型上下文。

Claude Code 把状态分在多个作用域中,每个作用域有不同的所有者、生命周期和共享边界。这是理解主 Agent 与 Subagent 隔离的基础。

六个状态作用域

作用域典型内容主要生命周期
进程与全局状态功能开关、客户端、插件与 MCP 注册、全局配置进程
会话状态session ID、历史、当前模型、权限模式、基础工作目录会话
Agent 实例 / 对话线程状态agentId、专属 transcript、有效 CWD 覆盖、已调用 Skill、读取与内容替换状态、专属 MCP、取消资源Agent 实例或线程
用户回合状态当前消息、压缩跟踪、转移原因、备用模型、继续次数一个 agentic turn
工具执行状态tool use ID、进度、当前输入的权限决策与取消结果工具或一批工具
持久状态transcript、配置、Memory、任务、邮箱、文件和 Git跨回合、跨会话或跨进程

状态分层的核心原则是:只在确实需要的范围共享可变状态。如果把“当前 Agent”、“当前 CWD”和“当前取消器”都写成单一全局变量,多个后台 Agent 并发时会立即串号。

Agent 类型与 Agent 实例必须分开

Claude Code 中有三个容易被混淆的身份:

  • agentType:逻辑角色,用于选择指令、工具和模型策略;同一类型可创建很多实例。
  • agentId:一次真实运行实例的标识,用于 transcript、Skill 状态、通知路由和清理。
  • 团队身份:需要跨进程寻址和重连时,使用可预测的队友名和团队名。

逻辑角色可复用,运行实例必须隔离,团队身份又必须可寻址。用一个字段表达三者,会同时伤害重用、隔离和重连。

会话状态是主线容器,不是唯一真相

主会话需要展示消息、权限对话框、任务、通知和模型状态。因此它有一份面向 UI 和主循环的可变状态。

但不是所有事实都应该只存在这里:

  • 真正的团队邮箱和任务列表需要跨进程保存;
  • 子 Agent 的完整历史应放在独立 sidechain transcript;
  • 工具进度属于运行中事件,不需要全部发给模型;
  • 文件和 Git 才是工作产物的持久真相。

因此主会话状态更像运行时观察与控制容器,而不是全系统唯一数据库。

查询状态是一个显式状态机

一个用户回合中,主查询循环需要带着下列状态继续迭代:

  • 当前内部消息序列;
  • 工具执行上下文;
  • 是否已压缩、是否尝试响应式恢复;
  • 最大输出恢复次数;
  • 当前回合计数与任务 Token 预算;
  • 上一次为什么继续,下一次要替换哪部分状态。

使用显式状态机而不是递归调用,可以更清楚地表达“工具结果后继续”、“压缩后重试”、“模型降级后继续”和“Hook 阻止后给模型再一次机会”等不同转移。

读取缓存与内容替换状态

上下文管理不能每轮对同一段旧内容作出不同处理,否则 Prompt 前缀会不断漂移。因此运行时需要记住:

  • 某文件在什么时候被读过;
  • 读取后文件是否变化;
  • 某个旧工具结果是否已被清理或摘要;
  • 一次内容替换决策是否应在后续回合保持。

这些不是一次工具调用结束就丢弃的临时状态,而属于会话对话线程或 Agent 实例,会跨工具回合保持。普通 Subagent 通常获得自己的读取缓存和内容替换状态。Fork 可以复制父线程的决策快照,但后续不与父线程共用同一可变容器。

并发身份和 CWD 不应使用单一全局变量

多个后台 Agent 可在同一进程并发等待 API、文件和工具。主会话持有基础 CWD,但具体 Agent 链可以用异步上下文覆盖自己的有效 CWD。在这种架构下,“当前 Agent ID”、“当前队友身份”和“当前有效工作目录”必须跟随各自的异步执行链,而不能被所有任务改写同一份全局状态。

这个设计让同进程 Agent 能共享 API 配置、注册表和任务系统,同时保持 telemetry、路径、Skill 状态和消息路由不串线。

任务状态与前后台状态是正交的

一个 Agent 任务可以是 pendingrunningcompletedfailedkilled。但“前台/后台”不是这条状态链中的终态:

  • running + 前台 表示当前用户回合正在等它;
  • running + 后台 表示它仍在运行,但主 Agent 可继续其他工作;
  • completed / failed / killed 才表示生命周期终止。

前台转后台因此是调度所有权的改变,不应被误解为 Agent 类型改变或任务已完成。

持久化是状态生命周期的最后一步

需要恢复的状态不能只存在内存:

  • 主会话与 Subagent sidechain 保存消息历史;
  • Agent metadata 保存类型、工作树路径和描述等恢复信息;
  • 团队任务和邮箱使用跨进程文件与锁;
  • Memory 和会话笔记保存被选中的长期或长会话事实;
  • 文件系统和 Git 保存工作产物。

恢复时不会盲目复制最后的内存。运行时会清理未完结工具、孤立思考块和空响应,再使用当前仍存在的 Agent 定义重建能力。这使恢复是“从持久事实重建合法运行状态”,而不是内存镜像回放。

状态分层的精妙与代价

精妙之处

  • 主 Agent 可查看任务,又不需共享子 Agent 的完整可变上下文;
  • 后台 Agent 可独立取消,不会因主回合 ESC 而自动全部消失;
  • 条件启用且入口允许时,Fork 可复用请求前缀,又在分叉后独立推进状态;
  • Worktree 可只隔离文件工作副本,不必复制整个进程和配置。

付出的代价

  • 同一个事实可能在 UI 镜像、运行任务和持久文件中有不同表示;
  • 生命周期结束时必须对称清理 Agent 专属 MCP、Hook、Skill、缓存和 transcript 资源;
  • 正确性依赖清晰的状态所有权,不能随意在模块级全局状态中加字段。

源码定位

  • 主会话与应用状态:src/state/src/bootstrap/state.ts
  • 查询回合状态:src/query.ts
  • Agent 并发身份:src/utils/agentContext.tssrc/utils/teammateContext.ts
  • CWD 作用域:src/utils/cwd.ts
  • 任务状态:src/Task.tssrc/tasks/
  • Subagent 持久与恢复:src/tools/AgentTool/runAgent.tssrc/tools/AgentTool/resumeAgent.ts