模块定位
packages/core/agent-loop 是 DeepSeek Harness 里唯一包含具体 loop driver 的包。它的职责不是实现所有 Agent 能力,而是驱动 Agent 生命周期:领取输入、组装请求、调用模型、执行工具、判断是否继续下一步。
相关源码入口:
packages/core/agent-loop/src/agent.tspackages/core/agent-loop/src/tool-calls.tspackages/core/agent-loop/src/runtime-context.tspackages/core/agent-loop/src/index.tspackages/core/agent/packages/core/session/
项目文档反复强调:新行为优先挂插件,不要改 loop。原因是 loop 是 spine,插件才是行为扩展面。
Turn 与 Step
Turn 是外层工作单元,通常对应用户可感知的一轮交互。Step 是一次模型请求加它触发的工具执行。一个 turn 可以包含多个 step,因为模型可能需要多轮“请求工具 → 看结果 → 继续请求”。
简化流程:
1 | sequenceDiagram |
Inbox 的三个入口
Agent 输入不是直接拼进 messages,而是进入 inbox。核心入口可以理解为三种:
| 方法 | 进入队列 | 是否唤醒 loop | 用途 |
|---|---|---|---|
followup() |
next-turn FIFO | 是 | 普通用户新输入 |
steer() |
next-step inbox | 是 | 立刻影响当前/下一 step |
inject() |
next-step inbox | 否 | 等下一次自然唤醒时附加上下文 |
这种设计让“用户新问题”“运行中引导”“静默上下文注入”有不同语义。Loop 在 turn 边界会领取 pending next-step input 加一个 queued prompt;step 之间只领取 next-step input。
agent/pre-step 是关键关口
Loop 领取输入后不会立刻发给模型。它会先触发 agent/pre-step waterfall。Listener 可以:
- 接受输入。
- 改写进入 step 的消息。
- 拒绝这次 step。
如果第一批输入被拒绝或改写为空,loop 仍然会关闭一个 durable turn,只是没有实际 step。这让“尝试过但没有进入模型”的事实也能被记录。
请求如何形成
每个 step 的模型请求来自:
- system prompt sections。
- 当前 agent 可见 tool schemas。
- session log 派生出的 message history。
- provider/model/maxTokens/reasoning 等 request config。
agent/request waterfall 可以调整请求。然后 loop 调 ctx.llm.prepareCall(),让 adapter 拥有的默认值被具体 materialize,并把 request/header 写入 log,保证后续可重建。
工具调用与并发
模型返回 tool calls 后,loop 会交给 ToolRuntime 执行。工具有并发分类:
parallel:连续安全调用可以进入有上限的 rolling pool。exclusive:作为 barrier,前后调用不能重叠。
注意:并发只覆盖 dispatch/body;策略、持久结果、结果上下文仍保持模型顺序。这样既能提高读类工具吞吐,又不会让 session log 顺序失真。
取消与收敛
取消不是简单 throw。Loop 有一个当前 admission/turn 的 signal。取消时:
- 清理 pending work,除非调用方要求保留 inbox。
- 协作式 abort 当前 signal。
- 已开始工具要 drain,保留 finalized result context。
- 未派发 tool call 会生成 synthetic result。
- durable
turn/end记录 aborted 或 disposed。
一个关键细节是 wake latch:如果取消收敛过程中又来了唤醒输入,loop 会在收敛边界重放唤醒,避免丢输入。
插件应该挂在哪里
不要为了功能直接改 loop。常见扩展点:
| 需求 | 扩展点 |
|---|---|
| 改模型请求 | agent/request |
| 入模前过滤/压缩输入 | agent/pre-step |
| 工具审批 | tools/pre-execute |
| 工具超时/指标 | tools/execute |
| 工具结果附加上下文 | tools/post-execute |
| 回合停止策略 | agent/turn-stopping |
| UI 观察 | session/event + agent/status |
| 持久化 | session/event + session/flush |
常见坑
第一,agent/pre-step 是 waterfall。要委托后续 listener,就必须调用 next()。
第二,插件异常会结束当前 turn,但不一定杀死整个 loop。恢复逻辑要看异常发生在哪个阶段。
第三,模型可见的新信息不能只存在内存。要能从 session log 还原。
第四,工具取消是协作式的。工具 body 必须观察或转发 AbortSignal。
继续读源码
packages/core/agent-loop/README.mdpackages/core/agent-loop/src/agent.tspackages/core/agent-loop/src/tool-calls.tsdocs/agent-lifecycle.mddocs/subsystems/core.md