← 核心插件深度解析索引 / 整体架构解析
@deepseek-ai/dsh-agent-loop · packages/core/agent-loop

dsh-agent-loop — 唯一的具体循环实现

harness 里唯一包含具体循环逻辑的包:ReactLoopAgent 实现 Agent 接口,驱动 session/turn/step 生命周期。 新行为一律进插件,不进这里。

ctx key: ctx.agentLoop 6 个 src 文件 · 1777 行 AgentLoop 服务类(实现 AgentFactory)+ agent-loop-invariant 接口见 dsh-agent

01包定位与入口

内容
ServiceAgentLoop extends Service implements AgentFactory;static inject = ['agents','sessions','llm','tools','systemPrompt'];构造时 ctx.agents.setFactory(this) 自注册
ConfigmaxParallelToolCalls(默认 10,1 = 串行)+ agents[](id / sessionId / cwd / resumeSessionId / provider / model / maxTokens)
Settingsagent-loop namespace 只有 maxParallelToolCalls;写入校验非正整数拒绝,运行中保留最后好的 cap
事件agent-loop/config-start-failed { sessionId, error } —— 配置 agent 启动失败时
额外 ctxctx.configuredAgentIdentities:launcher 在 Loader 挂载前固定配置 agent 的会话身份
◆ 可替换性 — 本包只是 AgentFactory 的一个实现。注册表把 create/resume 委托给当前唯一注册的 factory —— 加载另一个实现 Agent + AgentFactory 的插件即可整体换掉循环,UI/hook/工具插件无感知。

02核心接口

驱动接口(ctx.agentLoop)

create(id, options?, meta?): Agent      // 同步无 setup 创建,调用 fiber 拥有(config-path)
config: ResolvedConfig                 // maxParallelToolCalls getter 每次读,经 settings source
// AgentFactory:
createAgent(ownerCtx, options): Promise<AgentHandle>
resume(ownerCtx, options): Promise<AgentHandle>

内部驱动类 ReactLoopAgent(agent.ts:64)

包内可见,没有 /src/* 逃生通道导出。构造(agent.ts:80-97):建 fused dispatcher、Inbox、找最后一个 turn/start 设 lastTurn、 createScope(loopCtx, this)ctx = scope.ctx.extend({ agent: this })

03关键机制深度解析

turn() 循环(agent.ts:246-330)

turn/start {turn}
preStep()
inbox.claim(target, {turn,step}) → systemPrompt.assemble → runtimeContext.project → agent/pre-step 瀑布
▼ reject?→ turn/end {blocked},零步关闭(claimed 消息既不丢弃也不重发)
step === 0 且 messages 为空?
→ turn/end {completed},无模型调用
step/start → user/message ×N(surfaceOp: append)→ this.step()
▼ finally 保证
step/end
agent/turn-stopping(serial)
仅在 turnEnds 非空 且 inbox.nextStep 为空时;监听器可能已 steer → 复查
▼ 数据决定:nextStep 有内容 → 继续下一步;无 → break
turn/end {turn, reason}
inbox.hasPending? → 新 AbortController、step=0、turn 继续 : kick 结束

step():流式消费与 finish 判定(agent.ts:332-401)

  1. buildRequest(turn, step, assembly.tools, renderPrompt(assembly), session.deriveMessages(), signal) —— 每步重建
  2. 每个 chunk 先 append('assistant/chunk', ...) 收 seq,再 assembler.push(BlockAssembler 增量组装)
  3. finish 判定:error/aborted → agent/request-error 瀑布(retry 则 continue 重建请求);正常 → 每个成功 finish 恰好一个 assistant/message(surfaceOp: append,sourceEventSeqs 精确列出 chunk seqs);max-tokens → 返回 kind;有 tool calls → executeToolCalls
  4. 工具结果的 additionalContexts splice 进 next-step inbox;concludesTurn 置 concluded → step 以 completed 收尾

buildRequest:一次请求的完整装配(agent.ts:407-495)

  1. session.requestHeader() 持久化 header;route = agent options 的 provider/model
  2. seedConfig 提案:首次请求用 route;此后从 header 折叠 —— 剥掉 adapterDefaults 标记的字段(reasoningEffort/maxTokens),让当前精确路由重新物化自己的默认;未标记的显式设置跨 step/跨路由保留
  3. agent/request 瀑布 → 检查 provider/model 非空 → llm.prepareCall(物化 adapter 默认;NO_ADAPTER 时保留提案 config 允许 llm/stream 监听器接管)
  4. request/header 记录:首次 {reason:initial|resume},之后仅当 !headerEquals 时 {reason:change}
  5. request/context 记录:provider/model/contextWindow 变化才追加
  6. 组装 markAgentLoopRequest(deepFreeze({...config, messages, system?, tools?, sessionId, signal})) —— 打进程内标记供 invariant 识别

tool-calls.ts:并行调度与模型序提交

invariant 断言(invariant.ts:19-55)

llm/stream 全局 + prepend(防短路 replay 监听器屏蔽检查),只对 isAgentLoopRequest 的 loop 构建请求: 必须 frozen;sessionId 必须存在且 live;日志必须有 step/start 且能折叠出 request/header; deriveMessages() 重建边界与 options.messages JSON 逐字节一致(log-reconstruction desync);model/system/temperature/maxTokens/stop/tools 与折叠 header 匹配。

04自定制插件指南

◆ 首要规则 — 不要改本包实现来加行为。每个目标都有文档化扩展点(扩展点总表): 拦截请求用 agent/pre-step / agent/request;限流/超时用 guard / tools/execute wrapper; turn 预算在 agent/turn-stopping 挂 cancel。真的需要不同循环语义时才整体替换(见 dsh-agent 定制指南 (a))。

为配置的 agent 指定启动方式

观察 / 干预循环的行为

05关键陷阱与约定