# 25. Agent Loop 全景知识图谱与端到端复习
本章不新增一套与前文平行的概念,而是以一次完整 Agent Loop 为主轴,把第 1—20 章重新组织成多张互相对照的架构图。阅读时始终追踪同一个业务事实:用户目标如何成为模型输入,模型提议如何成为受控副作用,结果如何回到 Context,系统又如何停止、压缩、恢复或分发给子 Agent。
本章是第二遍学习使用的“完整地图”:第一次阅读先看 0.6 节,建立主链和章节位置;学完各专题后再回到本章,把正常执行、异常恢复、Context、Memory、安全、Multi-Agent 和可观测体系串在一起。这样既避免一开始被细节淹没,也能在复习时检查知识之间是否真正连通。
# 25.1 图谱阅读方法:一条主链,八个观察视角
| 观察视角 | 核心问题 | 主要章节 |
|---|---|---|
| Harness 全景 | 哪些组件共同把概率模型变成可治理系统 | 1、2、17、18 |
| 端到端时序 | 一次 Turn 中谁先调用谁,事实何时持久化 | 2、4、13、14、17 |
| Loop 与停止 | 为什么继续下一 Step,什么时候才允许完成 | 4、16、23 |
| 状态与恢复 | 崩溃、取消、断线后从哪条事实继续 | 2、9、17、18 |
| Context–Memory | 模型本轮看到什么,什么会跨会话保留 | 5、7—12、15 |
| 安全执行 | 模型提议如何经过授权与 OS 强制边界 | 6、14、19、20 |
| Multi-Agent | 任务、消息、工作区与权限如何分别隔离 | 3、16、19、20 |
| 观测与评测 | 如何解释行为、定位失败并形成优化闭环 | 13、18、23、24 |
八个视角共享同一组稳定标识:thread/session_id → turn_id → step_id → call_id/task_id → artifact/event_id。其中 artifact_id 表示执行产物 ID。图中如果两个模块不能用这些 ID 或明确的父子关系关联,就意味着它们之间仍存在恢复、审计或一致性缺口。
# 25.2 Harness 全景功能架构图
这张图体现 Harness 的核心原理:模型不直接接触操作系统,也不拥有最终完成权。模型只在一个固定 Step Context 上产生文本或动作提议;Runtime 把提议交给 Tool、安全和状态平面;Verifier 再依据外部证据判断是否完成。Context、权限、Rollout 和 Trace 必须引用同一 Step/Call 快照,否则模型看到的动作、用户批准的动作与实际执行动作可能不是同一个事实。
Claude Code 的 query → StreamingToolExecutor → executeToolCall → Session Storage 与 Codex 的 RegularTask → run_turn → ToolRouter/Handler → Rollout 都能映射到这张图:query.ts:280、toolExecution.ts:614、regular.rs:38、turn.rs:1288。
# 25.3 一次 Turn 的端到端时序图
时序中的两个“先持久化”非常关键:模型响应中的 ToolCall 要先写入持久化事件记录,ToolResult 也要先保存最终状态,然后才能推送 UI 或进入下一次采样。这样断线只会丢失临时展示,不会让 Runtime 忘记已执行的副作用。Codex 的 History/Rollout 写入与 Claude Code 的 JSONL/有序写队列分别体现了这一原则:session/mod.rs:1389、sessionStorage.ts:549。
# 25.3.1 同一次 Turn 中的控制流、数据流与状态流
三条流不能混为一谈:控制流可以重新运行,数据流可以裁剪或压缩,但已经确认的副作用状态不能被重算覆盖。恢复时先用状态流重建“已经发生什么”,再恢复控制流决定“下一步做什么”,最后重新构建数据流需要的 Context;顺序反过来就可能重复执行工具或使用过期权限。
# 25.4 Agent Loop 控制流与停止条件图
Loop 的“继续”必须带结构化原因:工具结果需要观察、验证失败、用户追加输入、Stop Hook 阻塞、上下文需要压缩、计划需要修订或预算允许重试。while true 只表达重复,无法决定下一轮应改变 Context、权限、输出预算还是任务图。Claude Code query.ts 中不同 transition reason 与 Codex 外层 RegularTask、内层 Turn Loop 的分层,都是把继续原因显式化:query.ts:1095、regular.rs:49。
必须停止的条件包括用户取消、墙钟/Token/费用/Step 预算耗尽、不可恢复安全错误、连续无进展和外部系统明确终止。模型输出自然语言“完成”只是一项完成提议;只有验收通过且系统中没有未完成 ToolCall、Task 或审批时,Runtime 才能写入 Turn 终态。
# 25.5 什么场景会触发上下文压缩
触发判断不能只写成“当前 Token 超过 80%”。生产系统至少有四类触发:
- Pre-sampling 预测触发:当前尚未超限,但加上下一批 World State、用户输入或工具 Schema 后将接近阈值。
- Turn 执行中触发(mid-turn):工具结果加入 Context 后仍需继续循环,而下一次请求的有效输入已经达到自动压缩阈值。
- Reactive 触发:Provider 明确返回 Prompt Too Long,Runtime 先清理异常大项,再重建请求。
- 显式触发:用户手动压缩,或 Session Memory 已有可靠摘要边界,可主动替换旧历史。
压缩通常不应只由 Session 累计消耗的 Token 触发;累计成本预算可以要求停止或提醒,但窗口压缩真正依据的是下一次请求会发送给模型的有效输入。相反,单个 50K Token 工具输出即使总窗口尚有空间,也应先受“单项内容硬上限”约束,避免一项内容挤掉全部计划和约束。
Codex 将 Turn 开始前(pre-turn)与 Turn 执行中(mid-turn)的初始上下文插入策略分开,并在替换后重算 Token;Claude Code 会调整切分位置以保持 ToolUse/ToolResult 配对,再恢复 Plan、Skill、Hook 和后台 Agent 状态:compact.rs:57、compact.rs:368、sessionMemoryCompact.ts:317、compact.ts:1471。
# 25.6 在线状态机与崩溃恢复流程
# 25.6.1 Turn 在线状态机
状态机中的 Interrupted 不等于 Failed。采样中断通常可以从已持久化边界重建;工具运行中断则必须区分“确认未执行”“已有终态”和“可能产生副作用但结果未知”。只有前两类能自动继续或复用,第三类需要下游幂等查询或人工确认。
# 25.6.2 进程重启后的恢复流程
恢复必须重放事实再修复边界,不能反序列化一个旧内存对象后继续运行。权限、cwd、MCP 连接和文件系统可能已经变化,因此旧 StepContext 只能用于审计,恢复后的下一 Step 必须重新捕获世界状态。Claude Code 沿 parentUuid 选择主链并恢复 Plan/Skill,Codex 的 Resume/Fork 分支则恢复历史、有效配置和持久化前缀:conversationRecovery.ts:409、session/mod.rs:1317。
# 25.6.3 出现异常时如何判断:先确认副作用,再决定恢复动作
判断异常时使用下面的固定顺序:
- 先查持久化事件,不先猜错误文本:确认 ToolCall、审批、执行开始和 ToolResult 分别记录到了哪一步。
- 再查外部事实:文件是否已修改、远端任务是否已创建、支付或发布是否已经发生。
- 再判断幂等性:重复执行是否安全,是否存在业务幂等键,是否只能查询而不能重放。
- 再判断 Context 是否过期:权限、工作目录、工具 Schema、文件版本或计划假设变化后,必须创建新 Step 快照。
- 最后选择恢复动作:复用、重试、重新规划、等待审批、人工确认或停止。
| 异常场景 | 关键判断证据 | 正确动作 | 禁止动作 |
|---|---|---|---|
| 模型或 MCP 暂时不可用 | 没有进入工具副作用,重试预算仍有余量 | 退避并限次重试,必要时熔断 | 无限快速重试 |
| Prompt 超出窗口 | Provider 错误、预测 Token、单项大小 | 先处理超大项,再压缩并重建 Context | 删除系统规则或未完成任务 |
| 参数或 Schema 校验失败 | 工具尚未执行,字段或类型不合法 | 把结构化错误交给模型修正 | 用原参数重复调用 |
| 权限拒绝或审批过期 | Policy/Approval 有明确最终状态 | 返回拒绝观察,换方案或重新申请 | 绕过审批或关闭沙箱 |
| 工具超时但结果未知 | 只有开始事件,没有可靠 ToolResult | 查询外部事实;无法确认则人工处理 | 把 unknown 当作 failed 自动重试 |
| 沙箱违规 | 内核事件、退出码、规范化动作和 Profile | 停止并计算可解释的最小权限差异 | 任意非零退出码都改成无沙箱重试 |
| 验收失败 | 测试、编译、Diff 或业务检查未通过 | 把证据加入 Context,局部重新规划 | 仅凭模型声称完成而结束 Turn |
| 子 Agent 失联 | 租约、心跳、Task owner、执行产物状态 | 使旧租约失效,再重新分配未完成任务 | 两个 Worker 同时提交同一任务 |
# 25.7 Context–Memory–Prompt 数据流图
这张图要记住三个边界:
- Memory 不等于 Context:Memory 是可持久化候选,Context 是一次 Step 实际选中的有界视图。
- Rollout 不等于 Prompt History:Rollout 是用于恢复的原始事件记录,Prompt History 是根据模型能力、预算和协议约束生成的模型可见视图。
- 压缩不等于删除事实:压缩只替换模型可见历史,原始事件、执行产物和来源记录仍用于审计、恢复,以及按原始记录重新构建状态。
Codex History::for_prompt 在发送前补齐调用结果、移除孤立输出、适配多模态并截断 FunctionCall 输出;World State 又只渲染相对基线的变化,说明 Context 是一个有类型的编译过程,而非字符串拼接:history.rs:141、history.rs:325、environment.rs:115。
# 25.8 安全工具执行与信任边界图
安全链中每层解决的问题不同:Schema 防止结构错误,Semantic Validator 理解工具语义,Hook 是扩展治理点,Authorization 判断主体是否可请求,Approval 是本次动作的交互授权,Sandbox 则由 OS 强制“即使进程恶意也不能越界”。审批通过不能自动关闭沙箱,Feature Flag 为真也不能跳过权限链。
参数规范化必须只做一次并形成 hash,审批、Policy、Sandbox 和 Audit 都引用同一动作对象。Claude Code 的执行顺序与 Codex requested/granted permission 交集、SandboxManager 后端编译共同体现了这一不变量:toolExecution.ts:682、policy_transforms.rs:126、manager.rs:272。
# 25.9 Multi-Agent 协作、隔离与汇总图
Multi-Agent 的四个一致性域必须分别管理:Spawn 决定执行实体是否存在,Mailbox 决定信息是否送达,Task Store 决定责任和依赖状态,Worktree/Patch 决定文件修改如何隔离。消息发送成功不代表任务 owner 已转移,Worktree 创建成功也不代表 Agent 获得额外权限。最终 Join 必须同时检查 Task 终态、证据、Diff 与 Agent 生命周期。
根 Agent 不应复制所有子 Agent 的历史,只保留任务摘要、状态、证据和执行产物引用;否则子任务越多,协调者的 Context 越容易膨胀。Claude Code 的 AgentTool、Task Store、Worktree 与 Codex 的 AgentControl、父子 Thread 和通信事件都体现了这些独立边界:runAgent.ts:412、TaskUpdateTool.ts:276、spawn.rs:452、agent_communication.rs:6。
# 25.10 Rollout、Trace、Metric 与 Eval 的质量闭环
四种数据必须从同一领域事件派生,但不能保存相同内容:Rollout 可以在受控权限下保留回放所需原文;Trace 保存因果、时间和脱敏属性;Metric 只保存取值种类较少的聚合指标;Eval 读取 Rollout 与执行产物并产出质量标签。它们共享 turn_id/step_id/call_id,才能从线上告警定位到单次执行轨迹,再把失败转成可复现的 Eval 用例。
成功指标不能只有 HTTP 200。一次 Turn 可能请求成功却修改错文件,因此需要同时观察任务验收率、验证通过率、用户纠正率、重复工具调用、压缩后事实丢失、权限拒绝与沙箱违规。Codex TurnTimingState 对采样、压缩、工具等待分别计时,Claude Code Session Trace 对 model/tool/approval/hook 建立 Span,正是为了把“慢”和“错”分解到可行动环节:turn_timing.rs:43、sessionTracing.ts:49。
# 25.11 二十个知识维度在主链中的位置
| 知识维度 | 在 Agent Loop 中的位置 | 必须守住的核心原理 |
|---|---|---|
| 1. Harness | 包住整个端到端闭环 | 模型、执行、上下文、状态、安全、治理共享稳定事实 ID |
| 2. Runtime | Turn/Step/ToolCall 生命周期 | 状态所有权清晰,取消向下传播,终态只写一次 |
| 3. Multi-Agent | Plan 后的任务分发与 Join | Spawn、Message、Task、Workspace 是四条独立协议 |
| 4. Agent Loop | 每次采样与工具观察之间 | 继续与停止原因结构化,完成权属于 Runtime/Verifier |
| 5. Skill | Context 候选发现与按需注入 | 渐进披露;Skill 声明需求但不授予权限 |
| 6. MCP | Tool Registry 的外部能力来源 | 连接版本与工具快照一致,远端能力服从统一受控执行流程 |
| 7. 长期记忆 | 从原始事件记录后台抽取,并在下一会话召回 | 后台生成、保留来源、合并验证和使用反馈 |
| 8. 短期记忆 | 当前 Turn 的 Working State | 事实、结构化投影和滚动摘要分层 |
| 9. 上下文压缩 | 模型采样前或 Turn 执行中的历史替换 | 不破坏调用配对的切分位置、一次完成的检查点写入、重新注入关键内容和可评测性 |
| 10. 上下文窗口 | Context 编译的物理预算 | 输入、输出、单项、分类和累计成本多层预算 |
| 11. Prompt Engineering | Context Items 到 Provider 请求 | 按权威层级编译,稳定前缀与动态后缀分离 |
| 12. Context Engineering | 每个 Step 的模型可见投影 | 确定、有界、可归因、可失效 |
| 13. Agent UX | Runtime Event 到用户界面 | UI 是状态机只读投影,命令经 Runtime 确认后才生效 |
| 14. Tool Use | 模型提议到结构化结果 | 校验、Hook、权限、沙箱、持久化不可绕过 |
| 15. Memory | 跨 Working/Episodic/Semantic/Procedural/Prospective | 类型决定生命周期、写入权、失效与召回方式 |
| 16. Planning | Goal 到 Plan Artifact 与 Task DAG | 方案、调度、审批是三份不同契约 |
| 17. Session | 贯穿全部 Step 的顶层状态容器 | 关键记录先持久化再可见,恢复时根据事件重建状态 |
| 18. 可观测 | 根据所有状态变化生成观测数据 | Rollout、Trace、Metric、Eval 使用同一来源关联,并按敏感级别脱敏 |
| 19. 权限 | Tool 执行前的控制面 | 规范化动作、规则来源、最小委托和 Deny 优先 |
| 20. 沙箱 | Tool 副作用发生的执行面 | 统一 Profile 编译到 OS Enforcement,不能静默降级 |
# 25.12 一次完整复习的推荐顺序
第一遍只沿第 25.3 节时序图口述正常路径,确保能在 5 分钟内讲清 UserInput → Step Context → Model → Tool → Result → Next Step → Verify。第二遍在每个节点加入异常:模型超长、工具拒绝、审批等待、沙箱违规、进程崩溃、MCP 断线、子 Agent 冲突。第三遍用第 25.11 节表格逐项检查 20 个维度是否都能落到主链位置和一个明确不变量。
面试回答时推荐采用固定结构:先画第 25.2 节六平面全景,再用第 25.3 节走一次时序;面试官追问长任务就展开压缩、Memory 与恢复,追问生产安全就展开权限—沙箱链,追问复杂任务就展开 Plan—Task DAG—Multi-Agent,追问线上质量就展开 Rollout—Trace—Eval 闭环。这样所有知识点都围绕一条 Agent Loop 主线展开,不会变成互不相干的术语清单。
# 25.13 判断架构是否正确的关键不变量
组件名称会变化,但下面这些规则不能被破坏。复习源码或设计新系统时,应优先检查不变量,而不是检查是否存在某个同名类。
| 范围 | 必须始终成立的规则 | 被破坏后的典型现象 | 关联章节 |
|---|---|---|---|
| 身份关联 | Thread、Turn、Step、ToolCall、Task、Event 使用稳定 ID 串联 | Trace、UI、恢复记录无法对应同一次动作 | 2、17、18 |
| Step 一致性 | 模型看到的 Context、Tool Schema 和随后解析调用的 Registry 来自同一快照 | 模型按旧工具定义生成参数,Runtime 却用新定义执行 | 2、6、12 |
| 写入顺序 | 关键事实先持久化,再通知 UI 或进入下一 Step | UI 显示成功,但进程重启后系统忘记副作用 | 14、17 |
| 调用配对 | 每个 ToolCall 都有唯一、可对应的 ToolResult 或明确的未知状态 | 压缩后出现孤立调用,模型重复调用工具 | 9、14 |
| 状态单向性 | 已完成、失败、取消等最终状态不能退回运行中 | 迟到进度把已完成任务重新显示为执行中 | 13、17 |
| 完成判定 | 模型只能提出完成;验收通过且没有未完成工作时 Runtime 才能结束 Turn | 模型说“完成”但测试失败、审批仍等待或子任务未结束 | 4、16、23 |
| 权限绑定 | 审批、策略、沙箱和审计引用同一个规范化动作及哈希 | 用户批准的命令与真正执行的对象不同 | 19、20 |
| 最小权限 | 子 Agent、MCP 和远程 Runtime 获得的是父权限与任务所需范围的交集 | 权限在委托过程中不断扩大 | 3、6、19 |
| 沙箱强制 | 权限允许只是控制决策,副作用仍必须由 OS 或远程隔离环境强制限制 | 应用层校验被绕过后可直接访问宿主资源 | 20 |
| Context 有界 | 每类内容和单项内容都有上限,并为模型输出保留空间 | 单个日志挤掉目标、计划和安全规则 | 8—12 |
| Memory 可追溯 | 长期记忆保留来源、作用域、时间和删除状态 | 旧摘要覆盖新事实,删除后仍能从索引召回 | 7、15 |
| 任务唯一归属 | 同一 Task 在同一时刻只有一个有效执行者,过期 Worker 不能提交 | 多个 Agent 覆盖同一文件或重复产生外部副作用 | 3、16 |
| 恢复先查事实 | 重启后先重放事件并确认外部副作用,再决定是否重试 | 把结果未知的旧调用当成失败并重复执行 | 17、25.6 |
| 观测同源 | Rollout、Trace、Metric 和 Eval 从同一领域事件派生 | 指标告警无法定位到具体轨迹,评测与线上行为不一致 | 18 |
# 25.14 一页复习卡:从用户目标到可验证结果
# 25.14.1 六十秒口述模板
一个生产级 Agent 不是“模型加几个工具”,而是由 Harness 管理的可恢复状态机。用户输入先进入 Session 并创建 Turn;Runtime 在每个 Step 固定世界状态、权限和工具目录,再由 Context Builder 按预算组合规则、历史、计划、Skill、Memory 与证据。模型只能生成文本或 ToolCall,动作必须经过参数校验、权限、必要审批和沙箱。ToolResult 先以稳定 call_id 持久化,再进入下一 Step。系统根据验证结果、未完成任务、预算和安全状态决定继续或停止。长任务通过压缩、事件恢复和 Multi-Agent 扩展;Rollout、Trace、Metric 与 Eval 使用同一组 ID 形成质量闭环。
# 25.14.2 十个自测问题
- 能否解释 Session、Turn、Step、ToolCall、Task 和 Agent 的区别?
- 为什么模型看到的 Tool Schema 必须与执行时的 Registry 属于同一 Step 快照?
- ToolCall 和 ToolResult 为什么必须先持久化再进入下一 Step?
- 哪四类情况会触发上下文压缩,压缩后必须重新注入什么?
- Memory、Context、Prompt History 和 Rollout 分别是什么?
- Approval 与 Sandbox 分别解决什么问题,为什么不能互相替代?
- 工具超时且结果未知时,为什么不能直接按失败重试?
- Multi-Agent 中 Spawn、Message、Task 和 Worktree 为什么是四条独立协议?
- Runtime 在什么条件下才能把 Turn 标记为完成?
- 如何用相同的
turn_id/step_id/call_id从告警定位到轨迹,再生成 Eval 用例?
如果这十个问题都能结合一条源码入口、一个异常分支和一个设计取舍回答,说明已经形成完整知识体系,而不是只记住了术语。