DeepSeek Harness Agent Loop 模块深度架构分析
代码仓库 :
github.com/deepseek-harness分析范围 :
packages/core/agent-loop/+packages/core/agent/(Agent 接口与驱动器)分析维度 :设计实现 · 循环机制 · Turn/Step/Round 三层嵌套 · 事件分发 · 工具调度 · 数据流
文档版本 :v1.0
分析日期:2025-09-03
目录
- [1. 概述与定位](#1. 概述与定位)
- [2. 包结构与源码索引](#2. 包结构与源码索引)
- [3. Agent 接口设计(core/agent)](#3. Agent 接口设计(core/agent))
- [3.1 Agent 接口定义](#3.1 Agent 接口定义)
- [3.2 三种输入投递方式](#3.2 三种输入投递方式)
- [3.3 Inbox 消息队列系统](#3.3 Inbox 消息队列系统)
- [3.4 Agent 事件体系](#3.4 Agent 事件体系)
- [3.5 Dispatch 融合分发器](#3.5 Dispatch 融合分发器)
- [3.6 ConsumedWork 工作审计](#3.6 ConsumedWork 工作审计)
- [4. ReactLoopAgent 驱动器实现(core/agent-loop/agent.ts)](#4. ReactLoopAgent 驱动器实现(core/agent-loop/agent.ts))
- [4.1 类结构与状态机](#4.1 类结构与状态机)
- [4.2 Phase 三态状态机](#4.2 Phase 三态状态机)
- [4.3 wakeDriver 驱动唤醒](#4.3 wakeDriver 驱动唤醒)
- [4.4 kick 驱动主循环](#4.4 kick 驱动主循环)
- [4.5 turn 方法------Turn 级循环](#4.5 turn 方法——Turn 级循环)
- [4.6 preStep 方法------Step 前置处理](#4.6 preStep 方法——Step 前置处理)
- [4.7 step 方法------Step 级行执行](#4.7 step 方法——Step 级行执行)
- [4.8 buildRequest 方法------请求构建](#4.8 buildRequest 方法——请求构建)
- [4.9 RuntimeContextProjection 运行时上下文投影](#4.9 RuntimeContextProjection 运行时上下文投影)
- [5. 工具调用调度(tool-calls.ts)](#5. 工具调用调度(tool-calls.ts))
- [5.1 调度模型:独占屏障 + 并行池](#5.1 调度模型:独占屏障 + 并行池)
- [5.2 executeToolCalls 主入口](#5.2 executeToolCalls 主入口)
- [5.3 runGroup 组调度核心](#5.3 runGroup 组调度核心)
- [5.4 有序提交与模型顺序保证](#5.4 有序提交与模型顺序保证)
- [5.5 中断与失败处理](#5.5 中断与失败处理)
- [6. AgentLoop 工厂服务(index.ts)](#6. AgentLoop 工厂服务(index.ts))
- [6.1 AgentLoop 服务类](#6.1 AgentLoop 服务类)
- [6.2 FactoryOwnership 工厂级生命周期管理](#6.2 FactoryOwnership 工厂级生命周期管理)
- [6.3 prepare 方法------Agent 准备](#6.3 prepare 方法——Agent 准备)
- [6.4 create / createAgent / resume 三种创建路径](#6.4 create / createAgent / resume 三种创建路径)
- [6.5 配置体系与声明式 Agent](#6.5 配置体系与声明式 Agent)
- [7. Invariant 请求重建不变量](#7. Invariant 请求重建不变量)
- [8. 循环机制全景](#8. 循环机制全景)
- [8.1 三层嵌套循环模型](#8.1 三层嵌套循环模型)
- [8.2 Turn 完整流程时序图](#8.2 Turn 完整流程时序图)
- [8.3 Step 完整流程时序图](#8.3 Step 完整流程时序图)
- [8.4 Turn 结束决策逻辑](#8.4 Turn 结束决策逻辑)
- [8.5 连续 Turn 驱动机制](#8.5 连续 Turn 驱动机制)
- [9. 数据流分析](#9. 数据流分析)
- [10. 模块间调用关系](#10. 模块间调用关系)
- [11. 避坑指南](#11. 避坑指南)
1. 概述与定位
1.1 什么是 Agent Loop
Agent Loop 是 DeepSeek Harness 的运行时心脏------它驱动 Agent 与 LLM 的完整交互循环。每一次用户输入、每一次模型请求、每一次工具调用,都由 Agent Loop 编排。
Agent Loop 实现了三层嵌套循环模型:
| 层级 | 名称 | 含义 | 持久事件 |
|---|---|---|---|
| Turn | 一次输入排空 | 从 claim 首个输入到模型和工具停止 | turn/start、turn/end |
| Step | 一次模型请求 + 工具调用 | Turn 内的一次完整 LLM 交互 | step/start、step/end、user/message、assistant/*、tool/* |
| Round | 外层策略迭代 | 如 goal round(跨 Turn 的自动续行) | 由 goal-round-driver 管理,非 Loop 内置 |
1.2 核心设计原则
#mermaid-svg-5yQlcVX3g4s58oTn{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-5yQlcVX3g4s58oTn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5yQlcVX3g4s58oTn .error-icon{fill:#552222;}#mermaid-svg-5yQlcVX3g4s58oTn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5yQlcVX3g4s58oTn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5yQlcVX3g4s58oTn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5yQlcVX3g4s58oTn .marker.cross{stroke:#333333;}#mermaid-svg-5yQlcVX3g4s58oTn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5yQlcVX3g4s58oTn p{margin:0;}#mermaid-svg-5yQlcVX3g4s58oTn .edge{stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .section--1 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section--1 path,#mermaid-svg-5yQlcVX3g4s58oTn .section--1 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section--1 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section--1 text{fill:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth--1{stroke-width:17;}#mermaid-svg-5yQlcVX3g4s58oTn .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-0 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-0 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-0 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-0 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-0 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-0{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-0{stroke-width:14;}#mermaid-svg-5yQlcVX3g4s58oTn .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-1 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-1 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-1 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-1 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-1 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-1{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-1{stroke-width:11;}#mermaid-svg-5yQlcVX3g4s58oTn .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-2 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-2 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-2 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-2 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-2 text{fill:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-2{stroke-width:8;}#mermaid-svg-5yQlcVX3g4s58oTn .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-3 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-3 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-3 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-3 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-3 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-3{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-3{stroke-width:5;}#mermaid-svg-5yQlcVX3g4s58oTn .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-4 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-4 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-4 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-4 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-4 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-4{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-4{stroke-width:2;}#mermaid-svg-5yQlcVX3g4s58oTn .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-5 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-5 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-5 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-5 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-5 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-5{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-5{stroke-width:-1;}#mermaid-svg-5yQlcVX3g4s58oTn .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-6 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-6 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-6 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-6 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-6 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-6{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-6{stroke-width:-4;}#mermaid-svg-5yQlcVX3g4s58oTn .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-7 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-7 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-7 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-7 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-7 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-7{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-7{stroke-width:-7;}#mermaid-svg-5yQlcVX3g4s58oTn .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-8 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-8 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-8 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-8 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-8 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-8{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-8{stroke-width:-10;}#mermaid-svg-5yQlcVX3g4s58oTn .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-9 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-9 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-9 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-9 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-9 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-9{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-9{stroke-width:-13;}#mermaid-svg-5yQlcVX3g4s58oTn .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-10 rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-10 path,#mermaid-svg-5yQlcVX3g4s58oTn .section-10 circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-10 polygon,#mermaid-svg-5yQlcVX3g4s58oTn .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-10 text{fill:black;}#mermaid-svg-5yQlcVX3g4s58oTn .node-icon-10{font-size:40px;color:black;}#mermaid-svg-5yQlcVX3g4s58oTn .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .edge-depth-10{stroke-width:-16;}#mermaid-svg-5yQlcVX3g4s58oTn .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled,#mermaid-svg-5yQlcVX3g4s58oTn .disabled circle,#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:lightgray;}#mermaid-svg-5yQlcVX3g4s58oTn .disabled text{fill:#efefef;}#mermaid-svg-5yQlcVX3g4s58oTn .section-root rect,#mermaid-svg-5yQlcVX3g4s58oTn .section-root path,#mermaid-svg-5yQlcVX3g4s58oTn .section-root circle,#mermaid-svg-5yQlcVX3g4s58oTn .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-5yQlcVX3g4s58oTn .section-root text{fill:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .section-root span{color:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .section-2 span{color:#ffffff;}#mermaid-svg-5yQlcVX3g4s58oTn .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-5yQlcVX3g4s58oTn .edge{fill:none;}#mermaid-svg-5yQlcVX3g4s58oTn .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-5yQlcVX3g4s58oTn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent Loop
设计原则
日志即真相
每个请求从 Session 日志派生
deriveMessages 投射模型历史
模型可见即已记录
运行时不变量断言
事件驱动扩展
agent/pre-step Waterfall
agent/request Waterfall
agent/request-error Waterfall
agent/turn-stopping Serial
agent/status emit
单一 Inbox
next-turn 等待新 Turn
next-step 等待下一个 Step
claim 时合并两个队列
去重验证
可取消性
每个活动有 AbortController
cancel 清空 inbox + abort
disposed 原因特殊处理
中断后保留已产出内容
有序提交
工具结果按模型顺序提交
独占调用形成屏障
并行调用有界滚动池
额外上下文 FIFO 注入
可重建性
请求 frozen + sessionId
request/header 持久化
llm/stream 不变量校验
派生消息 vs 实际消息比对
1.3 两个包的关系
#mermaid-svg-ulei9Yca4Sy5E81z{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-ulei9Yca4Sy5E81z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ulei9Yca4Sy5E81z .error-icon{fill:#552222;}#mermaid-svg-ulei9Yca4Sy5E81z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ulei9Yca4Sy5E81z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ulei9Yca4Sy5E81z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ulei9Yca4Sy5E81z .marker.cross{stroke:#333333;}#mermaid-svg-ulei9Yca4Sy5E81z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ulei9Yca4Sy5E81z p{margin:0;}#mermaid-svg-ulei9Yca4Sy5E81z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ulei9Yca4Sy5E81z .cluster-label text{fill:#333;}#mermaid-svg-ulei9Yca4Sy5E81z .cluster-label span{color:#333;}#mermaid-svg-ulei9Yca4Sy5E81z .cluster-label span p{background-color:transparent;}#mermaid-svg-ulei9Yca4Sy5E81z .label text,#mermaid-svg-ulei9Yca4Sy5E81z span{fill:#333;color:#333;}#mermaid-svg-ulei9Yca4Sy5E81z .node rect,#mermaid-svg-ulei9Yca4Sy5E81z .node circle,#mermaid-svg-ulei9Yca4Sy5E81z .node ellipse,#mermaid-svg-ulei9Yca4Sy5E81z .node polygon,#mermaid-svg-ulei9Yca4Sy5E81z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ulei9Yca4Sy5E81z .rough-node .label text,#mermaid-svg-ulei9Yca4Sy5E81z .node .label text,#mermaid-svg-ulei9Yca4Sy5E81z .image-shape .label,#mermaid-svg-ulei9Yca4Sy5E81z .icon-shape .label{text-anchor:middle;}#mermaid-svg-ulei9Yca4Sy5E81z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ulei9Yca4Sy5E81z .rough-node .label,#mermaid-svg-ulei9Yca4Sy5E81z .node .label,#mermaid-svg-ulei9Yca4Sy5E81z .image-shape .label,#mermaid-svg-ulei9Yca4Sy5E81z .icon-shape .label{text-align:center;}#mermaid-svg-ulei9Yca4Sy5E81z .node.clickable{cursor:pointer;}#mermaid-svg-ulei9Yca4Sy5E81z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ulei9Yca4Sy5E81z .arrowheadPath{fill:#333333;}#mermaid-svg-ulei9Yca4Sy5E81z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ulei9Yca4Sy5E81z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ulei9Yca4Sy5E81z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ulei9Yca4Sy5E81z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ulei9Yca4Sy5E81z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ulei9Yca4Sy5E81z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ulei9Yca4Sy5E81z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ulei9Yca4Sy5E81z .cluster text{fill:#333;}#mermaid-svg-ulei9Yca4Sy5E81z .cluster span{color:#333;}#mermaid-svg-ulei9Yca4Sy5E81z 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-ulei9Yca4Sy5E81z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ulei9Yca4Sy5E81z rect.text{fill:none;stroke-width:0;}#mermaid-svg-ulei9Yca4Sy5E81z .icon-shape,#mermaid-svg-ulei9Yca4Sy5E81z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ulei9Yca4Sy5E81z .icon-shape p,#mermaid-svg-ulei9Yca4Sy5E81z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ulei9Yca4Sy5E81z .icon-shape .label rect,#mermaid-svg-ulei9Yca4Sy5E81z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ulei9Yca4Sy5E81z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ulei9Yca4Sy5E81z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ulei9Yca4Sy5E81z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} packages/core/agent-loop/ --- 驱动器实现层
packages/core/agent/ --- Agent 接口层
被实现
setFactory
被使用
被构建
被分发
Agent 接口
followup / steer / inject / cancel
AgentRegistry
Agent 注册表与工厂委托
Inbox
消息队列 + 持久 splice
AgentEventDispatch
融合事件分发器
Agent 事件定义
agent/* 全部事件类型
ConsumedWork
工作审计折叠
ModelSelection
模型切换
ReactLoopAgent
默认 Agent 驱动器
AgentLoop
工厂服务 + 生命周期
tool-calls.ts
工具调用调度器
RuntimeContextProjection
运行时上下文投影
invariant.ts
请求重建不变量
constants.ts
默认值
core/agent:定义 Agent 接口、事件类型、Inbox 队列和分发器------是抽象层core/agent-loop:实现 Agent 驱动器ReactLoopAgent、工厂服务AgentLoop和工具调度------是具体层
2. 包结构与源码索引
2.1 core/agent 包
packages/core/agent/src/
├── index.ts (706 行) AgentRegistry 注册表 + Agent 接口导出 + Context 扩展
├── runtime-types.ts (292 行) Agent 接口完整定义 + 全部 agent/* 事件声明
├── inbox.ts (220 行) Inbox 消息队列 + 持久 splice + 去重验证
├── dispatch.ts (176 行) AgentEventDispatch 融合分发器 + scope carrier
├── consumed-work.ts (108 行) foldConsumedWork 工作审计折叠
├── model-selection.ts ( 75 行) 模型选择 + prompt/request 联动
├── types.ts ( 27 行) InboxTarget 类型 + AgentCancelCause 重导出
└── invariant.ts ( 32 行) 空不变量注册(伴生插件占位)
2.2 core/agent-loop 包
packages/core/agent-loop/src/
├── index.ts (713 行) AgentLoop 工厂服务 + FactoryOwnership + create/resume
├── agent.ts (515 行) ReactLoopAgent 驱动器 + turn/step/buildRequest
├── tool-calls.ts (289 行) executeToolCalls + runGroup + 有序提交 + 中断处理
├── runtime-context.ts ( 76 行) RuntimeContextProjection 动态上下文投影
├── invariant.ts ( 63 行) 请求重建不变量(llm/stream 拦截校验)
└── constants.ts ( 6 行) DEFAULT_MAX_PARALLEL_TOOL_CALLS = 10
2.3 依赖关系
#mermaid-svg-d78vL2CvLe8o4Kum{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-d78vL2CvLe8o4Kum .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d78vL2CvLe8o4Kum .error-icon{fill:#552222;}#mermaid-svg-d78vL2CvLe8o4Kum .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d78vL2CvLe8o4Kum .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d78vL2CvLe8o4Kum .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d78vL2CvLe8o4Kum .marker.cross{stroke:#333333;}#mermaid-svg-d78vL2CvLe8o4Kum svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d78vL2CvLe8o4Kum p{margin:0;}#mermaid-svg-d78vL2CvLe8o4Kum .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d78vL2CvLe8o4Kum .cluster-label text{fill:#333;}#mermaid-svg-d78vL2CvLe8o4Kum .cluster-label span{color:#333;}#mermaid-svg-d78vL2CvLe8o4Kum .cluster-label span p{background-color:transparent;}#mermaid-svg-d78vL2CvLe8o4Kum .label text,#mermaid-svg-d78vL2CvLe8o4Kum span{fill:#333;color:#333;}#mermaid-svg-d78vL2CvLe8o4Kum .node rect,#mermaid-svg-d78vL2CvLe8o4Kum .node circle,#mermaid-svg-d78vL2CvLe8o4Kum .node ellipse,#mermaid-svg-d78vL2CvLe8o4Kum .node polygon,#mermaid-svg-d78vL2CvLe8o4Kum .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d78vL2CvLe8o4Kum .rough-node .label text,#mermaid-svg-d78vL2CvLe8o4Kum .node .label text,#mermaid-svg-d78vL2CvLe8o4Kum .image-shape .label,#mermaid-svg-d78vL2CvLe8o4Kum .icon-shape .label{text-anchor:middle;}#mermaid-svg-d78vL2CvLe8o4Kum .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d78vL2CvLe8o4Kum .rough-node .label,#mermaid-svg-d78vL2CvLe8o4Kum .node .label,#mermaid-svg-d78vL2CvLe8o4Kum .image-shape .label,#mermaid-svg-d78vL2CvLe8o4Kum .icon-shape .label{text-align:center;}#mermaid-svg-d78vL2CvLe8o4Kum .node.clickable{cursor:pointer;}#mermaid-svg-d78vL2CvLe8o4Kum .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d78vL2CvLe8o4Kum .arrowheadPath{fill:#333333;}#mermaid-svg-d78vL2CvLe8o4Kum .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d78vL2CvLe8o4Kum .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d78vL2CvLe8o4Kum .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d78vL2CvLe8o4Kum .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d78vL2CvLe8o4Kum .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d78vL2CvLe8o4Kum .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d78vL2CvLe8o4Kum .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d78vL2CvLe8o4Kum .cluster text{fill:#333;}#mermaid-svg-d78vL2CvLe8o4Kum .cluster span{color:#333;}#mermaid-svg-d78vL2CvLe8o4Kum 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-d78vL2CvLe8o4Kum .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d78vL2CvLe8o4Kum rect.text{fill:none;stroke-width:0;}#mermaid-svg-d78vL2CvLe8o4Kum .icon-shape,#mermaid-svg-d78vL2CvLe8o4Kum .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d78vL2CvLe8o4Kum .icon-shape p,#mermaid-svg-d78vL2CvLe8o4Kum .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d78vL2CvLe8o4Kum .icon-shape .label rect,#mermaid-svg-d78vL2CvLe8o4Kum .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d78vL2CvLe8o4Kum .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d78vL2CvLe8o4Kum .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d78vL2CvLe8o4Kum :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} dsh-scope
作用域原语
dsh-llm
LLM 适配器 + 消息类型
dsh-session
Session 日志 + 事件类型
dsh-system-prompt
提示组装
dsh-tools
工具注册表 + 执行管道
dsh-settings
设置系统
dsh-invariants
不变量注册
dsh-commands/brand
CommandId
core/agent
Agent 接口层
core/agent-loop
驱动器实现层
3. Agent 接口设计(core/agent)
3.1 Agent 接口定义
Agent 接口定义在 runtime-types.ts 中,是 Agent Loop 的核心抽象:
#mermaid-svg-li44pcflMGgnkFGw{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-li44pcflMGgnkFGw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-li44pcflMGgnkFGw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-li44pcflMGgnkFGw .error-icon{fill:#552222;}#mermaid-svg-li44pcflMGgnkFGw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-li44pcflMGgnkFGw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-li44pcflMGgnkFGw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-li44pcflMGgnkFGw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-li44pcflMGgnkFGw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-li44pcflMGgnkFGw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-li44pcflMGgnkFGw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-li44pcflMGgnkFGw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-li44pcflMGgnkFGw .marker.cross{stroke:#333333;}#mermaid-svg-li44pcflMGgnkFGw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-li44pcflMGgnkFGw p{margin:0;}#mermaid-svg-li44pcflMGgnkFGw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-li44pcflMGgnkFGw .cluster-label text{fill:#333;}#mermaid-svg-li44pcflMGgnkFGw .cluster-label span{color:#333;}#mermaid-svg-li44pcflMGgnkFGw .cluster-label span p{background-color:transparent;}#mermaid-svg-li44pcflMGgnkFGw .label text,#mermaid-svg-li44pcflMGgnkFGw span{fill:#333;color:#333;}#mermaid-svg-li44pcflMGgnkFGw .node rect,#mermaid-svg-li44pcflMGgnkFGw .node circle,#mermaid-svg-li44pcflMGgnkFGw .node ellipse,#mermaid-svg-li44pcflMGgnkFGw .node polygon,#mermaid-svg-li44pcflMGgnkFGw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-li44pcflMGgnkFGw .rough-node .label text,#mermaid-svg-li44pcflMGgnkFGw .node .label text,#mermaid-svg-li44pcflMGgnkFGw .image-shape .label,#mermaid-svg-li44pcflMGgnkFGw .icon-shape .label{text-anchor:middle;}#mermaid-svg-li44pcflMGgnkFGw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-li44pcflMGgnkFGw .rough-node .label,#mermaid-svg-li44pcflMGgnkFGw .node .label,#mermaid-svg-li44pcflMGgnkFGw .image-shape .label,#mermaid-svg-li44pcflMGgnkFGw .icon-shape .label{text-align:center;}#mermaid-svg-li44pcflMGgnkFGw .node.clickable{cursor:pointer;}#mermaid-svg-li44pcflMGgnkFGw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-li44pcflMGgnkFGw .arrowheadPath{fill:#333333;}#mermaid-svg-li44pcflMGgnkFGw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-li44pcflMGgnkFGw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-li44pcflMGgnkFGw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-li44pcflMGgnkFGw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-li44pcflMGgnkFGw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-li44pcflMGgnkFGw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-li44pcflMGgnkFGw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-li44pcflMGgnkFGw .cluster text{fill:#333;}#mermaid-svg-li44pcflMGgnkFGw .cluster span{color:#333;}#mermaid-svg-li44pcflMGgnkFGw 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-li44pcflMGgnkFGw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-li44pcflMGgnkFGw rect.text{fill:none;stroke-width:0;}#mermaid-svg-li44pcflMGgnkFGw .icon-shape,#mermaid-svg-li44pcflMGgnkFGw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-li44pcflMGgnkFGw .icon-shape p,#mermaid-svg-li44pcflMGgnkFGw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-li44pcflMGgnkFGw .icon-shape .label rect,#mermaid-svg-li44pcflMGgnkFGw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-li44pcflMGgnkFGw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-li44pcflMGgnkFGw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-li44pcflMGgnkFGw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent 接口 (runtime-types.ts)
方法
send(message, target, wakeup)
底层投递:指定目标 + 是否唤醒
followup(message)
排队新 Turn + 唤醒驱动器
steer(message)
下一个 Step 边界 + 唤醒
inject(message)
下一个 Step 边界 + 不唤醒
cancel(cause, options?)
取消活跃工作 + 清空 inbox
whenIdle()
等待当前活动静止
runMaintenance(task)
空闲时运行非 Turn 维护任务
id: SessionId
与 session 共享的唯一标识
options: AgentOptions
· provider?: string
· model?: string
· maxTokens?: number
session: Session
活跃会话,日志是持久真相源
inbox: Inbox
Agent 拥有的待处理工作投影
status: AgentStatus
'idle' | 'running'
ctx: Context
Agent 作用域上下文
注册 agent-local,卸载时回退
3.2 三种输入投递方式
Agent 有三种语义不同的输入投递方式,都通过底层 send() 实现:
#mermaid-svg-fMclTYG8d226r68G{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-fMclTYG8d226r68G .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fMclTYG8d226r68G .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fMclTYG8d226r68G .error-icon{fill:#552222;}#mermaid-svg-fMclTYG8d226r68G .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fMclTYG8d226r68G .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fMclTYG8d226r68G .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fMclTYG8d226r68G .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fMclTYG8d226r68G .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fMclTYG8d226r68G .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fMclTYG8d226r68G .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fMclTYG8d226r68G .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fMclTYG8d226r68G .marker.cross{stroke:#333333;}#mermaid-svg-fMclTYG8d226r68G svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fMclTYG8d226r68G p{margin:0;}#mermaid-svg-fMclTYG8d226r68G .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fMclTYG8d226r68G .cluster-label text{fill:#333;}#mermaid-svg-fMclTYG8d226r68G .cluster-label span{color:#333;}#mermaid-svg-fMclTYG8d226r68G .cluster-label span p{background-color:transparent;}#mermaid-svg-fMclTYG8d226r68G .label text,#mermaid-svg-fMclTYG8d226r68G span{fill:#333;color:#333;}#mermaid-svg-fMclTYG8d226r68G .node rect,#mermaid-svg-fMclTYG8d226r68G .node circle,#mermaid-svg-fMclTYG8d226r68G .node ellipse,#mermaid-svg-fMclTYG8d226r68G .node polygon,#mermaid-svg-fMclTYG8d226r68G .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fMclTYG8d226r68G .rough-node .label text,#mermaid-svg-fMclTYG8d226r68G .node .label text,#mermaid-svg-fMclTYG8d226r68G .image-shape .label,#mermaid-svg-fMclTYG8d226r68G .icon-shape .label{text-anchor:middle;}#mermaid-svg-fMclTYG8d226r68G .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fMclTYG8d226r68G .rough-node .label,#mermaid-svg-fMclTYG8d226r68G .node .label,#mermaid-svg-fMclTYG8d226r68G .image-shape .label,#mermaid-svg-fMclTYG8d226r68G .icon-shape .label{text-align:center;}#mermaid-svg-fMclTYG8d226r68G .node.clickable{cursor:pointer;}#mermaid-svg-fMclTYG8d226r68G .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fMclTYG8d226r68G .arrowheadPath{fill:#333333;}#mermaid-svg-fMclTYG8d226r68G .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fMclTYG8d226r68G .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fMclTYG8d226r68G .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fMclTYG8d226r68G .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fMclTYG8d226r68G .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fMclTYG8d226r68G .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fMclTYG8d226r68G .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fMclTYG8d226r68G .cluster text{fill:#333;}#mermaid-svg-fMclTYG8d226r68G .cluster span{color:#333;}#mermaid-svg-fMclTYG8d226r68G 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-fMclTYG8d226r68G .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fMclTYG8d226r68G rect.text{fill:none;stroke-width:0;}#mermaid-svg-fMclTYG8d226r68G .icon-shape,#mermaid-svg-fMclTYG8d226r68G .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fMclTYG8d226r68G .icon-shape p,#mermaid-svg-fMclTYG8d226r68G .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fMclTYG8d226r68G .icon-shape .label rect,#mermaid-svg-fMclTYG8d226r68G .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fMclTYG8d226r68G .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fMclTYG8d226r68G .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fMclTYG8d226r68G :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} send
send
send
inject(message)
target: 'next-step'
wakeup: false
运行中驱动器在最近
Step 边界 claim
idle 驱动器不唤醒
可能错过已 claim 的 Step
steer(message)
target: 'next-step'
wakeup: true
idle 驱动器启动 Turn
running 驱动器在下一个
Step 边界消费
followup(message)
target: 'next-turn'
wakeup: true
成为自己 Turn 的
唯一普通消息
send(message, target, wakeup)
是
否
wakeup &&
phase 非 idle &&
signal 已 abort?
resolvedTarget =
wakingAfterAbort ? 'next-turn' : target
inbox.splice(resolvedTarget, ∞, 0, message)
wakeup?
wakeDriver(wakingAfterAbort)
不唤醒,等待其他消息唤醒
关键设计:wakingAfterAbort 分类
当唤醒输入在活跃工作被 abort 后到达时,它被重定向到 next-turn(而不是原始 target),确保唤醒输入不能加入已中止的活动。这个分类在 splice 之前捕获,防止重入 cancel 重分类它。
3.3 Inbox 消息队列系统
Inbox 是 Agent 的待处理消息队列,维护两个列表并通过持久 agent/inbox/spliced 事件记录所有变更。
#mermaid-svg-rQOWJux9bKthAsyj{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-rQOWJux9bKthAsyj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rQOWJux9bKthAsyj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rQOWJux9bKthAsyj .error-icon{fill:#552222;}#mermaid-svg-rQOWJux9bKthAsyj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rQOWJux9bKthAsyj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rQOWJux9bKthAsyj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rQOWJux9bKthAsyj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rQOWJux9bKthAsyj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rQOWJux9bKthAsyj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rQOWJux9bKthAsyj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rQOWJux9bKthAsyj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rQOWJux9bKthAsyj .marker.cross{stroke:#333333;}#mermaid-svg-rQOWJux9bKthAsyj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rQOWJux9bKthAsyj p{margin:0;}#mermaid-svg-rQOWJux9bKthAsyj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rQOWJux9bKthAsyj .cluster-label text{fill:#333;}#mermaid-svg-rQOWJux9bKthAsyj .cluster-label span{color:#333;}#mermaid-svg-rQOWJux9bKthAsyj .cluster-label span p{background-color:transparent;}#mermaid-svg-rQOWJux9bKthAsyj .label text,#mermaid-svg-rQOWJux9bKthAsyj span{fill:#333;color:#333;}#mermaid-svg-rQOWJux9bKthAsyj .node rect,#mermaid-svg-rQOWJux9bKthAsyj .node circle,#mermaid-svg-rQOWJux9bKthAsyj .node ellipse,#mermaid-svg-rQOWJux9bKthAsyj .node polygon,#mermaid-svg-rQOWJux9bKthAsyj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rQOWJux9bKthAsyj .rough-node .label text,#mermaid-svg-rQOWJux9bKthAsyj .node .label text,#mermaid-svg-rQOWJux9bKthAsyj .image-shape .label,#mermaid-svg-rQOWJux9bKthAsyj .icon-shape .label{text-anchor:middle;}#mermaid-svg-rQOWJux9bKthAsyj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rQOWJux9bKthAsyj .rough-node .label,#mermaid-svg-rQOWJux9bKthAsyj .node .label,#mermaid-svg-rQOWJux9bKthAsyj .image-shape .label,#mermaid-svg-rQOWJux9bKthAsyj .icon-shape .label{text-align:center;}#mermaid-svg-rQOWJux9bKthAsyj .node.clickable{cursor:pointer;}#mermaid-svg-rQOWJux9bKthAsyj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rQOWJux9bKthAsyj .arrowheadPath{fill:#333333;}#mermaid-svg-rQOWJux9bKthAsyj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rQOWJux9bKthAsyj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rQOWJux9bKthAsyj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rQOWJux9bKthAsyj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rQOWJux9bKthAsyj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rQOWJux9bKthAsyj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rQOWJux9bKthAsyj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rQOWJux9bKthAsyj .cluster text{fill:#333;}#mermaid-svg-rQOWJux9bKthAsyj .cluster span{color:#333;}#mermaid-svg-rQOWJux9bKthAsyj 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-rQOWJux9bKthAsyj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rQOWJux9bKthAsyj rect.text{fill:none;stroke-width:0;}#mermaid-svg-rQOWJux9bKthAsyj .icon-shape,#mermaid-svg-rQOWJux9bKthAsyj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rQOWJux9bKthAsyj .icon-shape p,#mermaid-svg-rQOWJux9bKthAsyj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rQOWJux9bKthAsyj .icon-shape .label rect,#mermaid-svg-rQOWJux9bKthAsyj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rQOWJux9bKthAsyj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rQOWJux9bKthAsyj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rQOWJux9bKthAsyj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 属性
hasPending
nextTurn.length > 0 ||
nextStep.length > 0
nextTurn
只读 next-turn 列表
nextStep
只读 next-step 列表
Inbox 类 (inbox.ts)
通知回调
inserted(message)
消息进入 inbox
discarded(message)
消息被删除(cancel 时)
claimed(message, turn)
消息被 claim 进 Turn
持久化
agent/inbox/spliced 事件
· target: 'next-turn' | 'next-step'
· start: number
· removedCount?: number
· inserted: UserMessage\[\]
· outcome?: 'canceled'
重放恢复
构造时遍历 seed 之后
的所有 splice 事件
操作方法
claim(target, turn)
取出完整批次
-
清空 next-step
-
如果 target=next-turn,
取出 next-turn0
- 发布 claimed 通知
append(target, message)
追加到尾部
持久 splice
prepend(target, message)
插入到头部
持久 splice
replace(messageId, newMessage)
原地替换
发布 discarded + inserted
remove(messageId)
删除单个消息
持久 splice
splice(target, start, deleteCount, inserted)
标准 splice 语义
持久记录 + 去重验证
clear()
清空两个队列
先清 next-step 再清 next-turn
内部状态
next-turn: UserMessage\[\]
等待各自 Turn 的消息
next-step: UserMessage\[\]
等待下一个 Step 边界的消息
关键实现细节:
-
持久化优先 :
mutate()先session.append('agent/inbox/spliced', splice)写入日志,然后 才修改内存状态。同步session/event观察者看到 splice 前的列表,可从规范化坐标重建被删除的消息。 -
去重验证 :
validate()检查 splice 后两个队列中不存在重复的message.id。验证时将候选队列与另一个队列合并检查。 -
outcome 标记 :当
discardRemoved=true && actualDeleteCount > 0时,splice 事件携带outcome: 'canceled'。ConsumedWork折叠器使用此标记区分"被取消的工作"和"被替换的工作"。 -
重放恢复 :构造函数遍历
session.events.slice(seedLength)中的所有agent/inbox/spliced事件,通过apply()重建内存状态。
3.4 Agent 事件体系
Agent 事件分为三类,全部在 runtime-types.ts 中声明:
#mermaid-svg-8lJ4o2vw5GIJd53V{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-8lJ4o2vw5GIJd53V .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8lJ4o2vw5GIJd53V .error-icon{fill:#552222;}#mermaid-svg-8lJ4o2vw5GIJd53V .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8lJ4o2vw5GIJd53V .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8lJ4o2vw5GIJd53V .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8lJ4o2vw5GIJd53V .marker.cross{stroke:#333333;}#mermaid-svg-8lJ4o2vw5GIJd53V svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8lJ4o2vw5GIJd53V p{margin:0;}#mermaid-svg-8lJ4o2vw5GIJd53V .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster-label text{fill:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster-label span{color:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster-label span p{background-color:transparent;}#mermaid-svg-8lJ4o2vw5GIJd53V .label text,#mermaid-svg-8lJ4o2vw5GIJd53V span{fill:#333;color:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V .node rect,#mermaid-svg-8lJ4o2vw5GIJd53V .node circle,#mermaid-svg-8lJ4o2vw5GIJd53V .node ellipse,#mermaid-svg-8lJ4o2vw5GIJd53V .node polygon,#mermaid-svg-8lJ4o2vw5GIJd53V .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8lJ4o2vw5GIJd53V .rough-node .label text,#mermaid-svg-8lJ4o2vw5GIJd53V .node .label text,#mermaid-svg-8lJ4o2vw5GIJd53V .image-shape .label,#mermaid-svg-8lJ4o2vw5GIJd53V .icon-shape .label{text-anchor:middle;}#mermaid-svg-8lJ4o2vw5GIJd53V .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8lJ4o2vw5GIJd53V .rough-node .label,#mermaid-svg-8lJ4o2vw5GIJd53V .node .label,#mermaid-svg-8lJ4o2vw5GIJd53V .image-shape .label,#mermaid-svg-8lJ4o2vw5GIJd53V .icon-shape .label{text-align:center;}#mermaid-svg-8lJ4o2vw5GIJd53V .node.clickable{cursor:pointer;}#mermaid-svg-8lJ4o2vw5GIJd53V .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8lJ4o2vw5GIJd53V .arrowheadPath{fill:#333333;}#mermaid-svg-8lJ4o2vw5GIJd53V .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8lJ4o2vw5GIJd53V .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8lJ4o2vw5GIJd53V .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8lJ4o2vw5GIJd53V .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8lJ4o2vw5GIJd53V .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8lJ4o2vw5GIJd53V .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster text{fill:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V .cluster span{color:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V 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-8lJ4o2vw5GIJd53V .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8lJ4o2vw5GIJd53V rect.text{fill:none;stroke-width:0;}#mermaid-svg-8lJ4o2vw5GIJd53V .icon-shape,#mermaid-svg-8lJ4o2vw5GIJd53V .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8lJ4o2vw5GIJd53V .icon-shape p,#mermaid-svg-8lJ4o2vw5GIJd53V .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8lJ4o2vw5GIJd53V .icon-shape .label rect,#mermaid-svg-8lJ4o2vw5GIJd53V .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8lJ4o2vw5GIJd53V .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8lJ4o2vw5GIJd53V .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8lJ4o2vw5GIJd53V :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分发模式
Agent 事件体系 (runtime-types.ts)
机器扩展点
Inbox 事件 (emit)
生命周期事件 (emit)
agent/created
Agent + Session 发布完成
同步 throw 否决发布
agent/disposed
Agent 离开注册表
驱动器静止 + 注册卸载后
agent/status
idle ⇄ running
每次状态转换触发
agent/session-start
Session 生命周期开始
首次 Turn 之前
source: startup|resume|clear|compact
agent/inbox/inserted
消息进入 inbox
agent/inbox/claimed
消息被 claim 进 Turn
携带 turn 号
agent/inbox/discarded
消息从 inbox 删除
agent/pre-step
(Waterfall)
拒绝或重写 Step 消息
必须调 next()
agent/request
(Waterfall)
替换冻结的调用配置
不能修改 messages
agent/request-error
(Waterfall)
处理失败的模型请求
返回 {kind:'retry'} 或 undefined
agent/turn-stopping
(Serial, 无 next)
Turn 即将关闭
监听器可 steer 延续
agent/error
(emit)
Step 或 Turn 出错
报告 failure
emit: 广播
每个监听器独立调用
失败不影响其他
waterfall: 链式
监听器必须调 next()
可短路返回
serial: 串行
有序调用
无 next() 委托
3.5 Dispatch 融合分发器
dispatch.ts 实现了融合分发器 (Fused Dispatcher),将 Agent 主体与 scope carrier 绑定,确保 scope key 和 payload 中的 agent 字段不会分歧。
#mermaid-svg-6MqSbSqiVWV3TjJV{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-6MqSbSqiVWV3TjJV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6MqSbSqiVWV3TjJV .error-icon{fill:#552222;}#mermaid-svg-6MqSbSqiVWV3TjJV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6MqSbSqiVWV3TjJV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6MqSbSqiVWV3TjJV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6MqSbSqiVWV3TjJV .marker.cross{stroke:#333333;}#mermaid-svg-6MqSbSqiVWV3TjJV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6MqSbSqiVWV3TjJV p{margin:0;}#mermaid-svg-6MqSbSqiVWV3TjJV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster-label text{fill:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster-label span{color:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster-label span p{background-color:transparent;}#mermaid-svg-6MqSbSqiVWV3TjJV .label text,#mermaid-svg-6MqSbSqiVWV3TjJV span{fill:#333;color:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV .node rect,#mermaid-svg-6MqSbSqiVWV3TjJV .node circle,#mermaid-svg-6MqSbSqiVWV3TjJV .node ellipse,#mermaid-svg-6MqSbSqiVWV3TjJV .node polygon,#mermaid-svg-6MqSbSqiVWV3TjJV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6MqSbSqiVWV3TjJV .rough-node .label text,#mermaid-svg-6MqSbSqiVWV3TjJV .node .label text,#mermaid-svg-6MqSbSqiVWV3TjJV .image-shape .label,#mermaid-svg-6MqSbSqiVWV3TjJV .icon-shape .label{text-anchor:middle;}#mermaid-svg-6MqSbSqiVWV3TjJV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6MqSbSqiVWV3TjJV .rough-node .label,#mermaid-svg-6MqSbSqiVWV3TjJV .node .label,#mermaid-svg-6MqSbSqiVWV3TjJV .image-shape .label,#mermaid-svg-6MqSbSqiVWV3TjJV .icon-shape .label{text-align:center;}#mermaid-svg-6MqSbSqiVWV3TjJV .node.clickable{cursor:pointer;}#mermaid-svg-6MqSbSqiVWV3TjJV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6MqSbSqiVWV3TjJV .arrowheadPath{fill:#333333;}#mermaid-svg-6MqSbSqiVWV3TjJV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6MqSbSqiVWV3TjJV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6MqSbSqiVWV3TjJV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6MqSbSqiVWV3TjJV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6MqSbSqiVWV3TjJV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6MqSbSqiVWV3TjJV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster text{fill:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV .cluster span{color:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV 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-6MqSbSqiVWV3TjJV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6MqSbSqiVWV3TjJV rect.text{fill:none;stroke-width:0;}#mermaid-svg-6MqSbSqiVWV3TjJV .icon-shape,#mermaid-svg-6MqSbSqiVWV3TjJV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6MqSbSqiVWV3TjJV .icon-shape p,#mermaid-svg-6MqSbSqiVWV3TjJV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6MqSbSqiVWV3TjJV .icon-shape .label rect,#mermaid-svg-6MqSbSqiVWV3TjJV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6MqSbSqiVWV3TjJV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6MqSbSqiVWV3TjJV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6MqSbSqiVWV3TjJV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AgentEventDispatch 融合分发器
三种分发方法
Scope 过滤
scopeTarget(agent, agent)
构建 Scoped<Agent>
· 过滤未标记监听器
· 加 agent 自己的
· 祖先 scope 可收到后代事件
agentCarrier(agent)
scopeTarget(agent, agent)
构建 scope carrier
stateless routing object
agentEvents(ctx, agent, carrier?)
构建融合分发器
carrier 构造一次,热路径复用
emit(name, payload)
· 注入 agent 到 payload
· 通过 carrier 分发
· 每个 callback 独立 try/catch
· 返回 promise rejection 被 warn
· 不否决生命周期
serial(name, payload)
· 注入 agent 到 payload
· ctx.serial(carrier, name, payload)
· 有序串行调用
· 返回第一个 bail 值
waterfall(name, payload, ...rest)
· 注入 agent 到 payload
· ctx.waterfall(carrier, name, payload, ...rest)
· rest = next() 回调
· 返回组合结果
fused(payload)
({ ...payload, agent })
· spread 在前,agent 注入在后
· 防止 payload 中的 agent 覆盖
· 类型安全
关键设计:
-
构造一次,复用多次 :
ReactLoopAgent构造函数中调用agentEvents(loopCtx, this)构建分发器并保存,热路径分发零分配。 -
emit 独立容错 :Cordis 的
emit通过Array.map调用 callback------一个同步 throw 会饿死后续监听器,返回的 promise 被丢弃。Agent 通知是非否决的,所以融合分发器自己解析 callback 集合并独立容错两种失败模式。 -
assembleContextFor :
assembleContextFor(agent, signal?)构建 prompt 组装上下文,将 agent 和 scope 一起设置,确保 agent-scoped 的 prompt 和 tool 贡献不会被静默遗漏。
3.6 ConsumedWork 工作审计
consumed-work.ts 实现了工作审计折叠,解决"Turn 和 Step 词汇无法回答 Agent 消费了什么工作"的问题。
#mermaid-svg-ayMRWAYG2HqioAuu{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-ayMRWAYG2HqioAuu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ayMRWAYG2HqioAuu .error-icon{fill:#552222;}#mermaid-svg-ayMRWAYG2HqioAuu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ayMRWAYG2HqioAuu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ayMRWAYG2HqioAuu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ayMRWAYG2HqioAuu .marker.cross{stroke:#333333;}#mermaid-svg-ayMRWAYG2HqioAuu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ayMRWAYG2HqioAuu p{margin:0;}#mermaid-svg-ayMRWAYG2HqioAuu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ayMRWAYG2HqioAuu .cluster-label text{fill:#333;}#mermaid-svg-ayMRWAYG2HqioAuu .cluster-label span{color:#333;}#mermaid-svg-ayMRWAYG2HqioAuu .cluster-label span p{background-color:transparent;}#mermaid-svg-ayMRWAYG2HqioAuu .label text,#mermaid-svg-ayMRWAYG2HqioAuu span{fill:#333;color:#333;}#mermaid-svg-ayMRWAYG2HqioAuu .node rect,#mermaid-svg-ayMRWAYG2HqioAuu .node circle,#mermaid-svg-ayMRWAYG2HqioAuu .node ellipse,#mermaid-svg-ayMRWAYG2HqioAuu .node polygon,#mermaid-svg-ayMRWAYG2HqioAuu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ayMRWAYG2HqioAuu .rough-node .label text,#mermaid-svg-ayMRWAYG2HqioAuu .node .label text,#mermaid-svg-ayMRWAYG2HqioAuu .image-shape .label,#mermaid-svg-ayMRWAYG2HqioAuu .icon-shape .label{text-anchor:middle;}#mermaid-svg-ayMRWAYG2HqioAuu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ayMRWAYG2HqioAuu .rough-node .label,#mermaid-svg-ayMRWAYG2HqioAuu .node .label,#mermaid-svg-ayMRWAYG2HqioAuu .image-shape .label,#mermaid-svg-ayMRWAYG2HqioAuu .icon-shape .label{text-align:center;}#mermaid-svg-ayMRWAYG2HqioAuu .node.clickable{cursor:pointer;}#mermaid-svg-ayMRWAYG2HqioAuu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ayMRWAYG2HqioAuu .arrowheadPath{fill:#333333;}#mermaid-svg-ayMRWAYG2HqioAuu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ayMRWAYG2HqioAuu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ayMRWAYG2HqioAuu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ayMRWAYG2HqioAuu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ayMRWAYG2HqioAuu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ayMRWAYG2HqioAuu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ayMRWAYG2HqioAuu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ayMRWAYG2HqioAuu .cluster text{fill:#333;}#mermaid-svg-ayMRWAYG2HqioAuu .cluster span{color:#333;}#mermaid-svg-ayMRWAYG2HqioAuu 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-ayMRWAYG2HqioAuu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ayMRWAYG2HqioAuu rect.text{fill:none;stroke-width:0;}#mermaid-svg-ayMRWAYG2HqioAuu .icon-shape,#mermaid-svg-ayMRWAYG2HqioAuu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ayMRWAYG2HqioAuu .icon-shape p,#mermaid-svg-ayMRWAYG2HqioAuu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ayMRWAYG2HqioAuu .icon-shape .label rect,#mermaid-svg-ayMRWAYG2HqioAuu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ayMRWAYG2HqioAuu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ayMRWAYG2HqioAuu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ayMRWAYG2HqioAuu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} foldConsumedWork 解决方案
折叠逻辑
stepped: Set<turn>
记录有 step 的 Turn
accountsForClaim(reason)
· completed → false (没有剩余工作)
· blocked → true (pre-step 拒绝)
· aborted → true (中断)
· interrupted → true (打断)
· error → true (错误)
· default → true (未知也要算)
turn/end
· stepped.delete(turn) ||
(claimed.delete(turn) &&
accountsForClaim(reason))
→ end = event, droppedUnrun = false
输入: SessionEvent\[\]
输出: ConsumedWork
· end?: turn/end 事件
· droppedUnrun: boolean
claimed: Set<turn>
记录 claim 了输入的 Turn
open: turn | undefined
当前开放的 Turn
turn/start
open = turn
step/start
stepped.add(turn)
agent/inbox/spliced
· outcome='canceled' && inserted=空
→ droppedUnrun = true
· 否则 open !== undefined
→ claimed.add(open)
问题背景
一个在首步前停止的 Turn
留下一个 turn/end
形状与 no-op Turn 完全一样
缺少的事实是 inbox 自己的记录
核心洞察 :一次取消从 inbox 取走但从未被任何 Turn 处理的输入------没有 turn/end 描述它。droppedUnrun 是这种"在 Turn 开启前被取消的工作"的唯一账目。
4. ReactLoopAgent 驱动器实现(core/agent-loop/agent.ts)
4.1 类结构与状态机
ReactLoopAgent 是 DSH 的默认 Agent 驱动器,实现了完整的 Turn/Step 循环。
#mermaid-svg-RDQXtBSEz7MzWvyW{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-RDQXtBSEz7MzWvyW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RDQXtBSEz7MzWvyW .error-icon{fill:#552222;}#mermaid-svg-RDQXtBSEz7MzWvyW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RDQXtBSEz7MzWvyW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RDQXtBSEz7MzWvyW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RDQXtBSEz7MzWvyW .marker.cross{stroke:#333333;}#mermaid-svg-RDQXtBSEz7MzWvyW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RDQXtBSEz7MzWvyW p{margin:0;}#mermaid-svg-RDQXtBSEz7MzWvyW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster-label text{fill:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster-label span{color:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster-label span p{background-color:transparent;}#mermaid-svg-RDQXtBSEz7MzWvyW .label text,#mermaid-svg-RDQXtBSEz7MzWvyW span{fill:#333;color:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW .node rect,#mermaid-svg-RDQXtBSEz7MzWvyW .node circle,#mermaid-svg-RDQXtBSEz7MzWvyW .node ellipse,#mermaid-svg-RDQXtBSEz7MzWvyW .node polygon,#mermaid-svg-RDQXtBSEz7MzWvyW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RDQXtBSEz7MzWvyW .rough-node .label text,#mermaid-svg-RDQXtBSEz7MzWvyW .node .label text,#mermaid-svg-RDQXtBSEz7MzWvyW .image-shape .label,#mermaid-svg-RDQXtBSEz7MzWvyW .icon-shape .label{text-anchor:middle;}#mermaid-svg-RDQXtBSEz7MzWvyW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RDQXtBSEz7MzWvyW .rough-node .label,#mermaid-svg-RDQXtBSEz7MzWvyW .node .label,#mermaid-svg-RDQXtBSEz7MzWvyW .image-shape .label,#mermaid-svg-RDQXtBSEz7MzWvyW .icon-shape .label{text-align:center;}#mermaid-svg-RDQXtBSEz7MzWvyW .node.clickable{cursor:pointer;}#mermaid-svg-RDQXtBSEz7MzWvyW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RDQXtBSEz7MzWvyW .arrowheadPath{fill:#333333;}#mermaid-svg-RDQXtBSEz7MzWvyW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RDQXtBSEz7MzWvyW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RDQXtBSEz7MzWvyW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDQXtBSEz7MzWvyW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RDQXtBSEz7MzWvyW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDQXtBSEz7MzWvyW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster text{fill:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW .cluster span{color:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW 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-RDQXtBSEz7MzWvyW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RDQXtBSEz7MzWvyW rect.text{fill:none;stroke-width:0;}#mermaid-svg-RDQXtBSEz7MzWvyW .icon-shape,#mermaid-svg-RDQXtBSEz7MzWvyW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDQXtBSEz7MzWvyW .icon-shape p,#mermaid-svg-RDQXtBSEz7MzWvyW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RDQXtBSEz7MzWvyW .icon-shape .label rect,#mermaid-svg-RDQXtBSEz7MzWvyW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDQXtBSEz7MzWvyW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RDQXtBSEz7MzWvyW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RDQXtBSEz7MzWvyW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ReactLoopAgent 类 (agent.ts)
私有方法
wakeDriver(wakeAfterAbort?)
唤醒驱动器或锁存唤醒
kick()
驱动主循环
while (await turn()) {}
turn()
Turn 级循环
返回是否继续下一个 Turn
preStep(target, position)
Step 前置处理
claim + assemble + waterfall
step(assembly)
Step 级行执行
buildRequest + stream + tools
buildRequest(...)
构建冻结请求
- request/header 持久化
throwError(error)
报告失败 + 重新抛出
公开方法
get status()
idle|running
setPhase(next)
提交 phase + 发布状态转换
send(message, target, wakeup)
followup(input)
steer(input)
inject(input)
cancel(cause, options?)
runMaintenance(job)
whenIdle()
属性
inbox: Inbox
消息队列
phase: Phase
三态状态机
activityDone: Promise
当前活动完成信号
scope: Scope
Agent 作用域注册边界
ctx: Context
Agent-scoped 上下文
dispatch: AgentEventDispatch
融合分发器(构造时构建)
requestHeaderLogged: boolean
是否已记录初始/resume 请求锚点
runtimeContext: RuntimeContextProjection
运行时上下文投影
4.2 Phase 三态状态机
#mermaid-svg-G7HHPVdpcj6QoAV7{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-G7HHPVdpcj6QoAV7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G7HHPVdpcj6QoAV7 .error-icon{fill:#552222;}#mermaid-svg-G7HHPVdpcj6QoAV7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G7HHPVdpcj6QoAV7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .marker.cross{stroke:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G7HHPVdpcj6QoAV7 p{margin:0;}#mermaid-svg-G7HHPVdpcj6QoAV7 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-G7HHPVdpcj6QoAV7 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-G7HHPVdpcj6QoAV7 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-G7HHPVdpcj6QoAV7 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-G7HHPVdpcj6QoAV7 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-G7HHPVdpcj6QoAV7 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-G7HHPVdpcj6QoAV7 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-G7HHPVdpcj6QoAV7 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G7HHPVdpcj6QoAV7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G7HHPVdpcj6QoAV7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G7HHPVdpcj6QoAV7 .edgeLabel .label text{fill:#333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .label div .edgeLabel{color:#333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-G7HHPVdpcj6QoAV7 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-G7HHPVdpcj6QoAV7 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-G7HHPVdpcj6QoAV7 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .cluster-label,#mermaid-svg-G7HHPVdpcj6QoAV7 .nodeLabel{color:#131300;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .note-edge{stroke-dasharray:5;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-note text{fill:black;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagram .edgeLabel{color:red;}#mermaid-svg-G7HHPVdpcj6QoAV7 #dependencyStart,#mermaid-svg-G7HHPVdpcj6QoAV7 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-G7HHPVdpcj6QoAV7 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G7HHPVdpcj6QoAV7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造函数
lastTurn = 最后一个 turn/start
wakeDriver()
setPhase({kind:'running',
turn: lastTurn, step: 0})
runMaintenance(job)
setPhase({kind:'maintenance',
abort: new AC})
kick() finally
setPhase({kind:'idle',
lastTurn: turn})
runMaintenance() finally
setPhase({kind:'idle',
lastTurn: lastTurn})
idle
running
maintenance
idle:
· status = 'idle'
· 可接受 waking 输入
· 可接受 runMaintenance
· wakeDriver 直接启动 Turn
running:
· status = 'running'
· abort: AbortController
· turn: 当前 Turn 号
· step: 当前 Step 号
· wakeRequested: 锁存的唤醒
· kick() 驱动 while(turn())
maintenance:
· status = 'idle' (对外仍是 idle)
· abort: AbortController
· wakeRequested: 锁存的唤醒
· runMaintenance 完成后
如果 wakeRequested && hasPending
则 wakeDriver()
三种 Phase 的关键区别:
| Phase | status | 可接受操作 | abort 信号 | 驱动器 |
|---|---|---|---|---|
idle |
'idle' |
waking 输入、runMaintenance | 无 | 无活跃驱动器 |
running |
'running' |
waking 输入(锁存)、cancel | AbortController | kick() 驱动中 |
maintenance |
'idle' |
waking 输入(锁存)、cancel | AbortController | 维护任务运行中 |
4.3 wakeDriver 驱动唤醒
wakeDriver 是驱动器的入口点,处理唤醒输入在三种 Phase 下的不同行为:
#mermaid-svg-x2kOc8MVdUqWXotD{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-x2kOc8MVdUqWXotD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-x2kOc8MVdUqWXotD .error-icon{fill:#552222;}#mermaid-svg-x2kOc8MVdUqWXotD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-x2kOc8MVdUqWXotD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-x2kOc8MVdUqWXotD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-x2kOc8MVdUqWXotD .marker.cross{stroke:#333333;}#mermaid-svg-x2kOc8MVdUqWXotD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-x2kOc8MVdUqWXotD p{margin:0;}#mermaid-svg-x2kOc8MVdUqWXotD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-x2kOc8MVdUqWXotD .cluster-label text{fill:#333;}#mermaid-svg-x2kOc8MVdUqWXotD .cluster-label span{color:#333;}#mermaid-svg-x2kOc8MVdUqWXotD .cluster-label span p{background-color:transparent;}#mermaid-svg-x2kOc8MVdUqWXotD .label text,#mermaid-svg-x2kOc8MVdUqWXotD span{fill:#333;color:#333;}#mermaid-svg-x2kOc8MVdUqWXotD .node rect,#mermaid-svg-x2kOc8MVdUqWXotD .node circle,#mermaid-svg-x2kOc8MVdUqWXotD .node ellipse,#mermaid-svg-x2kOc8MVdUqWXotD .node polygon,#mermaid-svg-x2kOc8MVdUqWXotD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-x2kOc8MVdUqWXotD .rough-node .label text,#mermaid-svg-x2kOc8MVdUqWXotD .node .label text,#mermaid-svg-x2kOc8MVdUqWXotD .image-shape .label,#mermaid-svg-x2kOc8MVdUqWXotD .icon-shape .label{text-anchor:middle;}#mermaid-svg-x2kOc8MVdUqWXotD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-x2kOc8MVdUqWXotD .rough-node .label,#mermaid-svg-x2kOc8MVdUqWXotD .node .label,#mermaid-svg-x2kOc8MVdUqWXotD .image-shape .label,#mermaid-svg-x2kOc8MVdUqWXotD .icon-shape .label{text-align:center;}#mermaid-svg-x2kOc8MVdUqWXotD .node.clickable{cursor:pointer;}#mermaid-svg-x2kOc8MVdUqWXotD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-x2kOc8MVdUqWXotD .arrowheadPath{fill:#333333;}#mermaid-svg-x2kOc8MVdUqWXotD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-x2kOc8MVdUqWXotD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-x2kOc8MVdUqWXotD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x2kOc8MVdUqWXotD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-x2kOc8MVdUqWXotD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x2kOc8MVdUqWXotD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-x2kOc8MVdUqWXotD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-x2kOc8MVdUqWXotD .cluster text{fill:#333;}#mermaid-svg-x2kOc8MVdUqWXotD .cluster span{color:#333;}#mermaid-svg-x2kOc8MVdUqWXotD 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-x2kOc8MVdUqWXotD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-x2kOc8MVdUqWXotD rect.text{fill:none;stroke-width:0;}#mermaid-svg-x2kOc8MVdUqWXotD .icon-shape,#mermaid-svg-x2kOc8MVdUqWXotD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x2kOc8MVdUqWXotD .icon-shape p,#mermaid-svg-x2kOc8MVdUqWXotD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-x2kOc8MVdUqWXotD .icon-shape .label rect,#mermaid-svg-x2kOc8MVdUqWXotD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x2kOc8MVdUqWXotD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-x2kOc8MVdUqWXotD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-x2kOc8MVdUqWXotD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} idle: 启动驱动器
否
是
非 idle: 锁存唤醒
是
否
是
否
abort.reason.kind
=== 'disposed'?
不锁存
(disposed 不锁存)
(活跃驱动器自己 claim)
phase.kind === 'maintenance'
|| wakeAfterAbort?
wakeRequested = true
在收敛时重放
wakeDriver(wakeAfterAbort = false)
phase.kind === 'idle'?
driver = Promise.withResolvers()
activityDone = driver.promise
setPhase({
kind: 'running',
abort: new AbortController(),
turn: lastTurn,
step: 0,
wakeRequested: false
})
loopCtx.agents.withInitiator(this,
() => this.kick())
.then(driver.resolve, driver.reject)
关键设计:
- disposed 不锁存:销毁原因的 abort 不锁存唤醒,确保 teardown 不等待任何 model turn。
- 活跃驱动器自己 claim:running 状态的驱动器自己 claim 排队工作,不需要锁存。
- maintenance 和 wakeAfterAbort 锁存:维护任务完成后或 abort 后的唤醒需要锁存,在收敛到 idle 时重放。
- withInitiator :kick 在
agents.withInitiator上下文中运行,使工具调度器能通过ctx.agents.requireInitiator()获取发起 Agent。
4.4 kick 驱动主循环
kick() 是驱动器的主循环入口:
渲染错误: Mermaid 渲染失败: Parse error on line 14: ... while_loop -->|turn() 返回 true| while_lo -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
核心 :while (await this.turn()) {}------turn() 返回 true 时继续下一个 Turn,返回 false 时停止。turn() 的返回值取决于 inbox 是否还有 pending 消息。
4.5 turn 方法------Turn 级循环
turn() 是 Turn 级循环的核心方法,打开一个 Turn 边界,在内部循环执行 Step:
#mermaid-svg-vzkN3yb2N95dKIeV{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-vzkN3yb2N95dKIeV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vzkN3yb2N95dKIeV .error-icon{fill:#552222;}#mermaid-svg-vzkN3yb2N95dKIeV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vzkN3yb2N95dKIeV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vzkN3yb2N95dKIeV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vzkN3yb2N95dKIeV .marker.cross{stroke:#333333;}#mermaid-svg-vzkN3yb2N95dKIeV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vzkN3yb2N95dKIeV p{margin:0;}#mermaid-svg-vzkN3yb2N95dKIeV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vzkN3yb2N95dKIeV .cluster-label text{fill:#333;}#mermaid-svg-vzkN3yb2N95dKIeV .cluster-label span{color:#333;}#mermaid-svg-vzkN3yb2N95dKIeV .cluster-label span p{background-color:transparent;}#mermaid-svg-vzkN3yb2N95dKIeV .label text,#mermaid-svg-vzkN3yb2N95dKIeV span{fill:#333;color:#333;}#mermaid-svg-vzkN3yb2N95dKIeV .node rect,#mermaid-svg-vzkN3yb2N95dKIeV .node circle,#mermaid-svg-vzkN3yb2N95dKIeV .node ellipse,#mermaid-svg-vzkN3yb2N95dKIeV .node polygon,#mermaid-svg-vzkN3yb2N95dKIeV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vzkN3yb2N95dKIeV .rough-node .label text,#mermaid-svg-vzkN3yb2N95dKIeV .node .label text,#mermaid-svg-vzkN3yb2N95dKIeV .image-shape .label,#mermaid-svg-vzkN3yb2N95dKIeV .icon-shape .label{text-anchor:middle;}#mermaid-svg-vzkN3yb2N95dKIeV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vzkN3yb2N95dKIeV .rough-node .label,#mermaid-svg-vzkN3yb2N95dKIeV .node .label,#mermaid-svg-vzkN3yb2N95dKIeV .image-shape .label,#mermaid-svg-vzkN3yb2N95dKIeV .icon-shape .label{text-align:center;}#mermaid-svg-vzkN3yb2N95dKIeV .node.clickable{cursor:pointer;}#mermaid-svg-vzkN3yb2N95dKIeV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vzkN3yb2N95dKIeV .arrowheadPath{fill:#333333;}#mermaid-svg-vzkN3yb2N95dKIeV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vzkN3yb2N95dKIeV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vzkN3yb2N95dKIeV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzkN3yb2N95dKIeV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vzkN3yb2N95dKIeV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzkN3yb2N95dKIeV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vzkN3yb2N95dKIeV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vzkN3yb2N95dKIeV .cluster text{fill:#333;}#mermaid-svg-vzkN3yb2N95dKIeV .cluster span{color:#333;}#mermaid-svg-vzkN3yb2N95dKIeV 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-vzkN3yb2N95dKIeV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vzkN3yb2N95dKIeV rect.text{fill:none;stroke-width:0;}#mermaid-svg-vzkN3yb2N95dKIeV .icon-shape,#mermaid-svg-vzkN3yb2N95dKIeV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzkN3yb2N95dKIeV .icon-shape p,#mermaid-svg-vzkN3yb2N95dKIeV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vzkN3yb2N95dKIeV .icon-shape .label rect,#mermaid-svg-vzkN3yb2N95dKIeV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzkN3yb2N95dKIeV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vzkN3yb2N95dKIeV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vzkN3yb2N95dKIeV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 连续 Turn 检查
Step 循环 (while true)
否
是
是
否
是
否
是
否
是
是
否
否
是
否
异常
关闭 Turn
是
否
signal.aborted?
turnEnds = {kind: 'aborted', reason}
throw error
turnEnds = {kind: 'error', error}
throwError(error)
session.append('turn/end',
{turn, reason: turnEnds})
执行 Step
session.append('step/start', {turn, step})
phase.step = step
for message of decision.messages:
session.append('user/message', message,
{surfaceOp: 'append'})
stepEnd = await this.step(decision.assembly)
if turnEnds === null ||
turnEnds.kind !== 'max-tokens':
turnEnds = stepEnd
session.append('step/end', {turn, step})
打开 Turn 边界
session.append('turn/start', {turn})
phase.turn = turn
turn()
phase.kind === 'running'?
throwError
无驱动器预留
signal = phase.abort.signal
signal.throwIfAborted()
turn = phase.turn + 1
turnEnds = null
target = 'next-turn'
signal.throwIfAborted()
step = phase.step + 1
preStep(target, {turn, step})
decision.kind === 'reject'?
turnEnds = {kind: 'blocked'}
return false
turnEnds &&
decision.messages.length === 0?
break
(Turn 自然结束)
phase.step === 0 &&
decision.messages.length === 0?
turnEnds = {kind: 'completed'}
return false
(首步空 Turn)
signal.throwIfAborted()
signal.throwIfAborted()
turnEnds &&
inbox.nextStep.length === 0?
dispatch.serial('agent/turn-stopping',
{turn, signal})
signal.throwIfAborted()
turnEnds &&
inbox.nextStep.length === 0?
break
(Turn 停止)
target = 'next-step'
(后续 Step 从 next-step claim)
!inbox.hasPending?
return false
phase.abort = new AbortController()
phase.wakeRequested = false
phase.step = 0
return true
(继续下一个 Turn)
关键设计要点:
-
max-tokens 粘性 :一旦任何 Step 命中 max-tokens 上限,后续正常完成的 Step 不能降级 Turn 结果。
if (turnEnds === null || turnEnds.kind !== 'max-tokens') turnEnds = stepEnd确保粘性。 -
首步空 Turn :如果第一个 Step 的
decision.messages.length === 0(消息被 pre-step 重写为空或被 cancel 清除),Turn 仍然拥有边界但不花费 model call,以completed结束。 -
turn-stopping 是 Serial :
agent/turn-stopping是 serial 事件,无next()。监听器不能委托,只能观察并 steer 延续。Turn 是否停止由数据决定(inbox 是否有 pending),不是监听器顺序决定。 -
Turn 间重置 :如果 inbox 有 pending,Turn 返回
true,重置abort(新 Controller 使旧锁存失效)、wakeRequested = false、step = 0,继续下一个 Turn。 -
finally 总是记录 turn/end :无论 Turn 如何结束(正常、blocked、aborted、error),finally 块总是
session.append('turn/end', {turn, reason: turnEnds})。
4.6 preStep 方法------Step 前置处理
preStep() 在每个 Step 开始前执行,负责 claim 输入、组装提示和运行 agent/pre-step waterfall:
#mermaid-svg-r499WuYdnJuTKmyi{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-r499WuYdnJuTKmyi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r499WuYdnJuTKmyi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r499WuYdnJuTKmyi .error-icon{fill:#552222;}#mermaid-svg-r499WuYdnJuTKmyi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r499WuYdnJuTKmyi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r499WuYdnJuTKmyi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r499WuYdnJuTKmyi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r499WuYdnJuTKmyi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r499WuYdnJuTKmyi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r499WuYdnJuTKmyi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r499WuYdnJuTKmyi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r499WuYdnJuTKmyi .marker.cross{stroke:#333333;}#mermaid-svg-r499WuYdnJuTKmyi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r499WuYdnJuTKmyi p{margin:0;}#mermaid-svg-r499WuYdnJuTKmyi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-r499WuYdnJuTKmyi .cluster-label text{fill:#333;}#mermaid-svg-r499WuYdnJuTKmyi .cluster-label span{color:#333;}#mermaid-svg-r499WuYdnJuTKmyi .cluster-label span p{background-color:transparent;}#mermaid-svg-r499WuYdnJuTKmyi .label text,#mermaid-svg-r499WuYdnJuTKmyi span{fill:#333;color:#333;}#mermaid-svg-r499WuYdnJuTKmyi .node rect,#mermaid-svg-r499WuYdnJuTKmyi .node circle,#mermaid-svg-r499WuYdnJuTKmyi .node ellipse,#mermaid-svg-r499WuYdnJuTKmyi .node polygon,#mermaid-svg-r499WuYdnJuTKmyi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r499WuYdnJuTKmyi .rough-node .label text,#mermaid-svg-r499WuYdnJuTKmyi .node .label text,#mermaid-svg-r499WuYdnJuTKmyi .image-shape .label,#mermaid-svg-r499WuYdnJuTKmyi .icon-shape .label{text-anchor:middle;}#mermaid-svg-r499WuYdnJuTKmyi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-r499WuYdnJuTKmyi .rough-node .label,#mermaid-svg-r499WuYdnJuTKmyi .node .label,#mermaid-svg-r499WuYdnJuTKmyi .image-shape .label,#mermaid-svg-r499WuYdnJuTKmyi .icon-shape .label{text-align:center;}#mermaid-svg-r499WuYdnJuTKmyi .node.clickable{cursor:pointer;}#mermaid-svg-r499WuYdnJuTKmyi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-r499WuYdnJuTKmyi .arrowheadPath{fill:#333333;}#mermaid-svg-r499WuYdnJuTKmyi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-r499WuYdnJuTKmyi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-r499WuYdnJuTKmyi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r499WuYdnJuTKmyi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-r499WuYdnJuTKmyi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r499WuYdnJuTKmyi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-r499WuYdnJuTKmyi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-r499WuYdnJuTKmyi .cluster text{fill:#333;}#mermaid-svg-r499WuYdnJuTKmyi .cluster span{color:#333;}#mermaid-svg-r499WuYdnJuTKmyi 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-r499WuYdnJuTKmyi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-r499WuYdnJuTKmyi rect.text{fill:none;stroke-width:0;}#mermaid-svg-r499WuYdnJuTKmyi .icon-shape,#mermaid-svg-r499WuYdnJuTKmyi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r499WuYdnJuTKmyi .icon-shape p,#mermaid-svg-r499WuYdnJuTKmyi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-r499WuYdnJuTKmyi .icon-shape .label rect,#mermaid-svg-r499WuYdnJuTKmyi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r499WuYdnJuTKmyi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-r499WuYdnJuTKmyi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-r499WuYdnJuTKmyi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 返回阶段
decision.kind === 'reject'?
return {kind: 'reject'}
return {kind: 'enter',
messages, assembly}
agent/pre-step Waterfall
dispatch.waterfall(
'agent/pre-step',
{messages: claimed, ...position, signal},
() => ({kind: 'enter',
messages: context === undefined
? claimed
: ...claimed, context}))
signal.throwIfAborted()
组装阶段
loopCtx.systemPrompt.assemble(
assembleContextFor(this, signal))
组装提示段落 + 工具 Schema
signal.throwIfAborted()
renderContextSections(assembly)
渲染上下文段落
runtimeContext.project(
joinContextSections(sections), sections)
投影运行时上下文
Claim 阶段
inbox.claim(target, position.turn)
-
清空 next-step → claimed
-
如果 target=next-turn:
取出 next-turn0 → claimed
- 发布 claimed 通知
preStep(target, position)
phase.kind === 'running'
(否则 throw)
signal = phase.abort.signal
关键流程:
-
Claim :
inbox.claim(target, turn)取出完整批次------先清空next-step,如果target === 'next-turn'还取出next-turn[0]。第一个 Step 的 target 是next-turn,后续 Step 是next-step。 -
Assemble :
systemPrompt.assemble()组装完整提示(sections + tools + variables),这是一个 waterfall 事件。 -
Runtime Context 投影 :
runtimeContext.project()检查动态上下文是否变化,如果变化则生成一条 user message 作为上下文快照。 -
Waterfall :
agent/pre-stepwaterfall 允许监听器重写消息或拒绝 Step。默认行为是enter,消息为claimed + context(如果有运行时上下文)。
4.7 step 方法------Step 级行执行
step() 是 Step 级行执行的核心,包含模型请求、流式响应和工具调用:
#mermaid-svg-m7RKKZ4ELZJXFE2K{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-m7RKKZ4ELZJXFE2K .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-m7RKKZ4ELZJXFE2K .error-icon{fill:#552222;}#mermaid-svg-m7RKKZ4ELZJXFE2K .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-m7RKKZ4ELZJXFE2K .marker{fill:#333333;stroke:#333333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .marker.cross{stroke:#333333;}#mermaid-svg-m7RKKZ4ELZJXFE2K svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-m7RKKZ4ELZJXFE2K p{margin:0;}#mermaid-svg-m7RKKZ4ELZJXFE2K .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster-label text{fill:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster-label span{color:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster-label span p{background-color:transparent;}#mermaid-svg-m7RKKZ4ELZJXFE2K .label text,#mermaid-svg-m7RKKZ4ELZJXFE2K span{fill:#333;color:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .node rect,#mermaid-svg-m7RKKZ4ELZJXFE2K .node circle,#mermaid-svg-m7RKKZ4ELZJXFE2K .node ellipse,#mermaid-svg-m7RKKZ4ELZJXFE2K .node polygon,#mermaid-svg-m7RKKZ4ELZJXFE2K .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .rough-node .label text,#mermaid-svg-m7RKKZ4ELZJXFE2K .node .label text,#mermaid-svg-m7RKKZ4ELZJXFE2K .image-shape .label,#mermaid-svg-m7RKKZ4ELZJXFE2K .icon-shape .label{text-anchor:middle;}#mermaid-svg-m7RKKZ4ELZJXFE2K .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .rough-node .label,#mermaid-svg-m7RKKZ4ELZJXFE2K .node .label,#mermaid-svg-m7RKKZ4ELZJXFE2K .image-shape .label,#mermaid-svg-m7RKKZ4ELZJXFE2K .icon-shape .label{text-align:center;}#mermaid-svg-m7RKKZ4ELZJXFE2K .node.clickable{cursor:pointer;}#mermaid-svg-m7RKKZ4ELZJXFE2K .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .arrowheadPath{fill:#333333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m7RKKZ4ELZJXFE2K .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-m7RKKZ4ELZJXFE2K .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m7RKKZ4ELZJXFE2K .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster text{fill:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K .cluster span{color:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K 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-m7RKKZ4ELZJXFE2K .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-m7RKKZ4ELZJXFE2K rect.text{fill:none;stroke-width:0;}#mermaid-svg-m7RKKZ4ELZJXFE2K .icon-shape,#mermaid-svg-m7RKKZ4ELZJXFE2K .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m7RKKZ4ELZJXFE2K .icon-shape p,#mermaid-svg-m7RKKZ4ELZJXFE2K .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-m7RKKZ4ELZJXFE2K .icon-shape .label rect,#mermaid-svg-m7RKKZ4ELZJXFE2K .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m7RKKZ4ELZJXFE2K .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-m7RKKZ4ELZJXFE2K .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-m7RKKZ4ELZJXFE2K :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求循环 (while true)
完成检查
是
否
是
否
是
否
无错误
finish.kind
buildRequest(turn, step, assembly.tools,
system, session.deriveMessages(), signal)
assembler = new BlockAssembler()
chunkSeqs = \[\]
error | aborted
dispatch.waterfall(
'agent/request-error',
{turn, step, provider, failure,
retryPolicy, signal},
() => undefined)
signal.throwIfAborted()
action?.kind === 'retry'?
continue
(重试请求)
throw new LlmError(...)
max-tokens
return {kind: 'max-tokens'}
normal (stop)
message = createAssistantMessage({
content: assembler.blocks(),
source: {provider, model, replayState?}
})
session.append('assistant/message',
{turn, step, message, usage?},
{surfaceOp: 'append', sourceEventSeqs: chunkSeqs})
toolCalls = message.content
.filter(block => block.type === 'tool-call')
toolCalls.length === 0?
return {kind: 'completed'}
executeToolCalls(loopCtx, turn, step,
toolCalls, signal, acceptContext)
concluded?
return {kind: 'completed'}
return null
(Turn 未结束)
流式响应阶段
逐块处理 (for await)
signal.throwIfAborted()
session.append('assistant/chunk',
{turn, step, chunk})
assembler.push(chunk)
stream = preparedCall?.stream(request)
?? loopCtx.llm.stream(request)
signal.throwIfAborted()
signal.throwIfAborted()
流式错误处理
signal.aborted?
content = assembler.interruptedBlocks()
content.length > 0?
session.append('assistant/message',
{turn, step, message: createAssistantMessage({content, source}),
interrupted: true, usage?},
{surfaceOp: 'append', sourceEventSeqs: chunkSeqs})
throw error
step(assembly)
phase.kind === 'running'
(否则 throw)
{turn, step, signal} = phase
system = renderPrompt(assembly)
关键设计要点:
-
中断内容保留 :当流式响应被 abort 时,已经接收的内容通过
assembler.interruptedBlocks()提取,以interrupted: true的assistant/message事件持久化。这让中断前的部分输出在重放时可见。 -
请求错误恢复 :
agent/request-errorwaterfall 允许监听器返回{kind: 'retry'}来重试请求(如 compaction-basic 的溢出恢复)。如果监听器不调next()而返回 retry,则拥有恢复;调next()委托则默认undefined(失败是终止的)。 -
工具调用后继续 :如果工具执行后
concluded为 false(模型可能需要更多工具调用),返回null,Step 循环继续------buildRequest重新派生消息(现在包含工具结果),发起新的模型请求。 -
消息派生 :每次请求的
messages通过session.deriveMessages()从日志派生,确保请求内容是日志的纯函数。
4.8 buildRequest 方法------请求构建
buildRequest() 构建冻结的模型请求并绑定到适配器注册:
#mermaid-svg-tLXLA3PYZilNzcwy{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-tLXLA3PYZilNzcwy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tLXLA3PYZilNzcwy .error-icon{fill:#552222;}#mermaid-svg-tLXLA3PYZilNzcwy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tLXLA3PYZilNzcwy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tLXLA3PYZilNzcwy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tLXLA3PYZilNzcwy .marker.cross{stroke:#333333;}#mermaid-svg-tLXLA3PYZilNzcwy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tLXLA3PYZilNzcwy p{margin:0;}#mermaid-svg-tLXLA3PYZilNzcwy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tLXLA3PYZilNzcwy .cluster-label text{fill:#333;}#mermaid-svg-tLXLA3PYZilNzcwy .cluster-label span{color:#333;}#mermaid-svg-tLXLA3PYZilNzcwy .cluster-label span p{background-color:transparent;}#mermaid-svg-tLXLA3PYZilNzcwy .label text,#mermaid-svg-tLXLA3PYZilNzcwy span{fill:#333;color:#333;}#mermaid-svg-tLXLA3PYZilNzcwy .node rect,#mermaid-svg-tLXLA3PYZilNzcwy .node circle,#mermaid-svg-tLXLA3PYZilNzcwy .node ellipse,#mermaid-svg-tLXLA3PYZilNzcwy .node polygon,#mermaid-svg-tLXLA3PYZilNzcwy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tLXLA3PYZilNzcwy .rough-node .label text,#mermaid-svg-tLXLA3PYZilNzcwy .node .label text,#mermaid-svg-tLXLA3PYZilNzcwy .image-shape .label,#mermaid-svg-tLXLA3PYZilNzcwy .icon-shape .label{text-anchor:middle;}#mermaid-svg-tLXLA3PYZilNzcwy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tLXLA3PYZilNzcwy .rough-node .label,#mermaid-svg-tLXLA3PYZilNzcwy .node .label,#mermaid-svg-tLXLA3PYZilNzcwy .image-shape .label,#mermaid-svg-tLXLA3PYZilNzcwy .icon-shape .label{text-align:center;}#mermaid-svg-tLXLA3PYZilNzcwy .node.clickable{cursor:pointer;}#mermaid-svg-tLXLA3PYZilNzcwy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tLXLA3PYZilNzcwy .arrowheadPath{fill:#333333;}#mermaid-svg-tLXLA3PYZilNzcwy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tLXLA3PYZilNzcwy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tLXLA3PYZilNzcwy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tLXLA3PYZilNzcwy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tLXLA3PYZilNzcwy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tLXLA3PYZilNzcwy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tLXLA3PYZilNzcwy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tLXLA3PYZilNzcwy .cluster text{fill:#333;}#mermaid-svg-tLXLA3PYZilNzcwy .cluster span{color:#333;}#mermaid-svg-tLXLA3PYZilNzcwy 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-tLXLA3PYZilNzcwy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tLXLA3PYZilNzcwy rect.text{fill:none;stroke-width:0;}#mermaid-svg-tLXLA3PYZilNzcwy .icon-shape,#mermaid-svg-tLXLA3PYZilNzcwy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tLXLA3PYZilNzcwy .icon-shape p,#mermaid-svg-tLXLA3PYZilNzcwy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tLXLA3PYZilNzcwy .icon-shape .label rect,#mermaid-svg-tLXLA3PYZilNzcwy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tLXLA3PYZilNzcwy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tLXLA3PYZilNzcwy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tLXLA3PYZilNzcwy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 最终构建阶段
request = markAgentLoopRequest(deepFreeze({
...header.config,
messages: boundaryMessages,
system?, tools?,
sessionId: session.id,
signal
}))
return {request, preparedCall?}
请求上下文阶段
requestContext = {provider, model, contextWindow?}
previousContext 不匹配?
→ append('request/context', requestContext)
请求头持久化阶段
header = canonicalHeader({config, adapterDefaults?, system, tools})
!requestHeaderLogged?
→ append('request/header', {header, reason: 'initial'|'resume'})
→ requestHeaderLogged = true
baseline === undefined ||
!headerEquals(baseline, header)?
→ append('request/header', {header, reason: 'change'})
适配器解析阶段
loopCtx.llm.prepareCall(proposedConfig, signal)
catch NO_ADAPTER:
config = proposedConfig (中间件可能服务)
config = preparedCall.config
配置提议阶段
dispatch.waterfall('agent/request',
{turn, step, signal}, () => seedConfig)
signal.throwIfAborted()
!proposedConfig.provider || !proposedConfig.model
→ throw 'no provider/model'
配置解析阶段
persistedHeader = session.requestHeader()
route = {provider: options.provider ?? '',
model: options.model ?? ''}
如果 persistedConfig 匹配 route
且 adapterDefaults.reasoningEffort !== true:
reasoningEffort = persistedConfig.reasoningEffort
seedConfig = deepFreeze(
requestHeaderLogged ? requestProposal(persistedHeader)
: {route, reasoningEffort?, maxTokens?})
buildRequest(turn, step, tools, system,
boundaryMessages, signal)
关键设计:
-
requestProposal :移除 adapter 派生的值(
reasoningEffort和maxTokens如果adapterDefaults标记为true),让插件在下一步重新提议。 -
首次 vs 后续 :首次请求使用
reason: 'initial'或'resume',后续只在 header 变化时记录reason: 'change'。 -
markAgentLoopRequest :标记请求为 Loop 构建,携带
sessionId。到达时被deepFreeze(修改抛错),invariant 校验其内容是日志的纯函数。 -
request/context:记录 provider/model/contextWindow 变化,供 compaction 等模块读取路由目标。
4.9 RuntimeContextProjection 运行时上下文投影
RuntimeContextProjection 跟踪动态运行时上下文的最后快照,避免重复注入:
#mermaid-svg-IePfxnj1py2kBOfU{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-IePfxnj1py2kBOfU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IePfxnj1py2kBOfU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IePfxnj1py2kBOfU .error-icon{fill:#552222;}#mermaid-svg-IePfxnj1py2kBOfU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IePfxnj1py2kBOfU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IePfxnj1py2kBOfU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IePfxnj1py2kBOfU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IePfxnj1py2kBOfU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IePfxnj1py2kBOfU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IePfxnj1py2kBOfU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IePfxnj1py2kBOfU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IePfxnj1py2kBOfU .marker.cross{stroke:#333333;}#mermaid-svg-IePfxnj1py2kBOfU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IePfxnj1py2kBOfU p{margin:0;}#mermaid-svg-IePfxnj1py2kBOfU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IePfxnj1py2kBOfU .cluster-label text{fill:#333;}#mermaid-svg-IePfxnj1py2kBOfU .cluster-label span{color:#333;}#mermaid-svg-IePfxnj1py2kBOfU .cluster-label span p{background-color:transparent;}#mermaid-svg-IePfxnj1py2kBOfU .label text,#mermaid-svg-IePfxnj1py2kBOfU span{fill:#333;color:#333;}#mermaid-svg-IePfxnj1py2kBOfU .node rect,#mermaid-svg-IePfxnj1py2kBOfU .node circle,#mermaid-svg-IePfxnj1py2kBOfU .node ellipse,#mermaid-svg-IePfxnj1py2kBOfU .node polygon,#mermaid-svg-IePfxnj1py2kBOfU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IePfxnj1py2kBOfU .rough-node .label text,#mermaid-svg-IePfxnj1py2kBOfU .node .label text,#mermaid-svg-IePfxnj1py2kBOfU .image-shape .label,#mermaid-svg-IePfxnj1py2kBOfU .icon-shape .label{text-anchor:middle;}#mermaid-svg-IePfxnj1py2kBOfU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IePfxnj1py2kBOfU .rough-node .label,#mermaid-svg-IePfxnj1py2kBOfU .node .label,#mermaid-svg-IePfxnj1py2kBOfU .image-shape .label,#mermaid-svg-IePfxnj1py2kBOfU .icon-shape .label{text-align:center;}#mermaid-svg-IePfxnj1py2kBOfU .node.clickable{cursor:pointer;}#mermaid-svg-IePfxnj1py2kBOfU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IePfxnj1py2kBOfU .arrowheadPath{fill:#333333;}#mermaid-svg-IePfxnj1py2kBOfU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IePfxnj1py2kBOfU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IePfxnj1py2kBOfU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IePfxnj1py2kBOfU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IePfxnj1py2kBOfU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IePfxnj1py2kBOfU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IePfxnj1py2kBOfU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IePfxnj1py2kBOfU .cluster text{fill:#333;}#mermaid-svg-IePfxnj1py2kBOfU .cluster span{color:#333;}#mermaid-svg-IePfxnj1py2kBOfU 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-IePfxnj1py2kBOfU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IePfxnj1py2kBOfU rect.text{fill:none;stroke-width:0;}#mermaid-svg-IePfxnj1py2kBOfU .icon-shape,#mermaid-svg-IePfxnj1py2kBOfU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IePfxnj1py2kBOfU .icon-shape p,#mermaid-svg-IePfxnj1py2kBOfU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IePfxnj1py2kBOfU .icon-shape .label rect,#mermaid-svg-IePfxnj1py2kBOfU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IePfxnj1py2kBOfU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IePfxnj1py2kBOfU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IePfxnj1py2kBOfU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RuntimeContextProjection
project(current, sections)
retained === undefined &&
current.length === 0?
→ return undefined (无快照需要)
snapshot = current 为空 ? CLEARED : current
retained.text === snapshot?
→ return undefined (无需更新)
createUserMessage({
content: {type:'text', text: snapshot},
source: {kind:'plugin', plugin: SOURCE,
form: 'snapshot', sections}})
事件跟踪
session/event: user/message
- isOwned?
→ retained = {seq, text}
session/event: replacement
- sourceEventSeqs 包含 retained.seq?
→ retained = null
构造(恢复)
从后向前扫描 session.events
找到 source.plugin === 'dsh-system-prompt'
的 user/message
在当前 surface 中?
→ retained = {seq, text}
不在 surface
→ retained = null
从未存在
→ retained = undefined
核心逻辑 :project() 只在保留值与当前值不同时生成新快照消息。被 compaction 替换的快照自动变为 null(通过 sourceEventSeqs 检测)。