跳转至

Agent Loop 状态机与驱动机制

如果说 Cordis 是 DSH 的骨架,那么 Agent Loop 就是 DSH 跳动的心脏。

Agent Loop 负责调度 LLM 请求、工具调用执行、多轮对话流转以及异常中断恢复。DSH 对 Agent 的生命周期做出了业界极为清晰严格的数学级划分:Turn(轮次)Step(步骤)


核心文件速查

路径 职责 重要度
packages/core/agent-loop/src/agent.ts ReactLoopAgent 类:核心循环驱动状态机 ⭐⭐⭐
packages/core/agent/src/inbox.ts 优先级消息邮箱与事件唤醒机制 ⭐⭐⭐
packages/core/agent/src/runtime-types.ts Agent 接口定义与生命周期状态类型 ⭐⭐⭐
packages/core/agent/src/dispatch.ts Waterfall 事件发射与拦截派发器 ⭐⭐⭐

核心概念:Turn vs Step

在很多初学者甚至部分 Agent 库中,"一次用户发问"与"一次大模型调用"经常被混为一谈。在 DSH 中,两者有极其严格的界定:

  • Step(步骤)单次模型请求 + 伴随的工具调用执行
  • Turn(轮次)从用户输入到达开始,到所有派生工作(包括连续多步工具执行、多轮思考)全部平息(Settled)为止的完整业务闭环。一个 Turn 包含 0 个、1 个或多个 Step。
User Input Arrival
       │
       ▼
 ┌───────────┐
 │turn/start │
 └─────┬─────┘
       │
       ├──────────────────────────────────────────────┐
       │                                              │
       ▼ (Step Loop)                                  ▼
 ┌───────────┐                                ┌───────────────┐
 │step/start │                                │ (No work left)│
 └─────┬─────┘                                └───────┬───────┘
       │                                              │
       ▼                                              │
 ┌──────────────────────────────────────────┐         │
 │ 1. 组装 Prompt 与工具 Schema               │         │
 │ 2. agent/pre-step (Waterfall 拦截与改写) │         │
 │ 3. agent/request -> llm/stream           │         │
 │ 4. 接收 assistant/chunk* 流式输出         │         │
 │ 5. assistant/message (落盘)              │         │
 │ 6. 解析 tool/call* 并执行工具流水线       │         │
 │ 7. tools/pre-execute -> execute -> post  │         │
 │ 8. 记录 tool/result* (落盘)               │         │
 └─────┬────────────────────────────────────┘         │
       │                                              │
       ▼                                              │
 ┌───────────┐                                        │
 │ step/end  │                                        │
 └─────┬─────┘                                        │
       │                                              │
       ├────► [工具产生结果,需要模型继续分析] ────────┘
       │
       ▼ (所有工具执行完毕,模型无新增调用,邮箱无待办)
 ┌───────────┐
 │ turn/end  │
 └───────────┘

逐行精读:ReactLoopAgent.runLoop()

让我们打开 packages/core/agent-loop/src/agent.ts,阅读核心执行循环:

// packages/core/agent-loop/src/agent.ts
private async runTurn(): Promise<void> {
  // 1. 发射持久化会话事件 turn/start
  this.session.append({ type: 'turn/start', turnId })

  try {
    while (!this.teardown.signal.aborted) {
      // 2. 从 Inbox 领取下一批待处理输入(包括用户消息、系统注入提示)
      const inputs = this.inbox.claim()
      if (!inputs && !this.toolsOweWork) {
        break // 无剩余工作,结束当前 Turn
      }

      // 3. 组装当前 Step 的系统提示词与工具 Schema
      const prompt = await this.systemPrompt.assemble(this.ctx)
      const schemas = this.tools.getSchemas()

      // 4. Waterfall 事件:agent/pre-step
      // 允许插件(如安全守卫、上下文注入器)拦截、改写或直接拒绝本次 Step 输入
      const preStepResult = await emitAgentEvent(this.ctx, 'agent/pre-step', {
        agent: this,
        inputs,
        prompt,
      })

      if (preStepResult.status === 'reject') {
        // 被守卫拦截,记录拒绝事件,结束该 Step
        break
      }

      // 5. 开始 Step
      this.session.append({ type: 'step/start', stepId })

      // 6. 派生 LLM 历史消息并请求模型
      const history = deriveMessages(this.session.events)
      const stream = await this.llm.stream({
        messages: history,
        system: prompt,
        tools: schemas,
      })

      // 7. 处理流式输出并记录 assistant/chunk / assistant/message
      const assistantMessage = await this.consumeLlmStream(stream)

      // 8. 如果模型发起了 Tool Calls,并发/串行进入工具执行流水线
      if (assistantMessage.toolCalls?.length) {
        await this.executeTools(assistantMessage.toolCalls)
        this.toolsOweWork = true // 标记工具已产生结果,需要下一次 Step 喂给模型
      } else {
        this.toolsOweWork = false
      }

      this.session.append({ type: 'step/end', stepId })
    }
  } finally {
    // 9. 发射 turn/end,完成本轮业务
    this.session.append({ type: 'turn/end', turnId })
  }
}

优先级消息邮箱:Inbox 机制

DSH 设计了一个非阻塞、带优先级的 Inbox 机制(packages/core/agent/src/inbox.ts): - 普通用户输入(User Message):进入常规队列,立刻唤醒正在休眠的 Agent Loop; - 后台子任务通知(Subagent Settlement):子智能体执行完毕后以系统通知形式投递到 Inbox; - 静默注入上下文(Silent Context Injection):一些扩展插件可以向 Inbox 塞入上下文片段,但不主动唤醒 Agent,直到用户下一次发问时一并打包带给模型。


本章思考与自测

  1. 思考题:在 agent/pre-step 中,如果守卫插件直接将本次输入改写为空(Empty),DSH 为什么仍然要求落盘一个不包含 Step 的 turn/startturn/end
  2. 自测题:如果在工具执行期间,用户又向当前 Agent 发送了一条新的消息,DSH 是立刻打断正在执行的工具,还是暂存到 Inbox 中?为什么这样设计?