状态模型
一个可持续工作的 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 任务可以是 pending、running、completed、failed 或 killed。但“前台/后台”不是这条状态链中的终态:
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.ts、src/utils/teammateContext.ts - CWD 作用域:
src/utils/cwd.ts - 任务状态:
src/Task.ts、src/tasks/ - Subagent 持久与恢复:
src/tools/AgentTool/runAgent.ts、src/tools/AgentTool/resumeAgent.ts