← 返回首页

Agent Loop:一次轮次里到底发生了什么

2026-09-24 · 8 分钟阅读

dsh-agent 和 dsh-agent-loop 这两个包名字很像,职责其实分得很清楚。搞混它们是最常见的配置错误来源。

两个包,两件事

包负责什么
dsh-agent创建/恢复 agent,提供 ctx.agents 与 Agent 句柄;不产生模型请求
dsh-agent-loop驱动「调用模型 → 执行工具 → 重复」,以及会话历史持久化

关键点:在没有驱动器注册工厂之前,dsh-agent 的服务保持惰性。所以最小的可用组合必须两个一起挂。如果你只挂了 agent 包然后发现什么都没发生,原因就在这。

三种输入,区别在「唤不唤醒」

句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。三个方法长得很像,语义差别不小:

handle.agent.followup({...})  // 排队一条普通的下一个轮次提示词,唤醒驱动器
handle.agent.steer({...})     // 提交下一步输入并唤醒它
handle.agent.inject({...})    // 添加面向模型的上下文,但不唤醒驱动器

inject 是最容易用错的:它不唤醒驱动器,所以消息会安静地待着,直到下一个被接纳的步骤才落进去。想立刻让 agent 动起来,用它是不行的。

另外 cancel(cause) 中止当前活动,未设置 keepInbox 时会清掉待处理工作;whenIdle() 等整个 agent 完全停稳。

声明式 agent 怎么配

配置里声明的 agent 会在插件加载时自动启动,每个条目需要一个 id 标签;要发模型请求就得同时给 provider 和 model:

- name: '@deepseek-ai/dsh-agent-loop'
  config:
    maxParallelToolCalls: 10
    agents:
      - id: 'main'
        provider: deepseek
        model: deepseek-chat
        reasoningEffort: high
        cwd: /workspace

并发工具的边界

maxParallelToolCalls 默认 10,约束每个步骤同时在途的并行安全调用数;设 1 就是全串行。

但有个重要例外:独占调用保留顺序。这意味着并发上限不是简单地把所有工具塞进一个池子——标记为独占的调用会按原顺序执行。写有副作用的工具时,这个机制是你唯一的顺序保证,别指望靠并发上限来控序。

这个值是在下一个工具组开始时读取的,所以运行时改配置对已经在途的组不生效。

关于取消

取消 agent 时,已经流式交付给用户的文本会保留,不会回滚。这个行为是刻意的——用户已经看到的内容突然消失比留着更糟。如果你的上层逻辑依赖「取消后状态完全干净」,得自己处理这部分残留。

什么时候别用默认循环

标准组合应该挂 dsh-agent-loop。只有当你需要的行为超出了「调用模型、运行工具、重复」这个生命周期时,才考虑自定义 Agent 实现。大多数看起来「需要自定义」的需求,实际上用 agent/* 事件就能解决——比如 agent/pre-step 可以拒绝拟进入的步骤或替换进入它的消息,agent/request-error 让监听器重试失败的模型请求。这些事件让插件不必依赖循环包就能作用于实时工作。

Agent Loop工具调用并发