harness 里唯一包含具体循环逻辑的包:ReactLoopAgent 实现 Agent 接口,驱动 session/turn/step 生命周期。
新行为一律进插件,不进这里。
ctx.agentLoop
6 个 src 文件 · 1777 行
AgentLoop 服务类(实现 AgentFactory)+ agent-loop-invariant
接口见 dsh-agent
| 项 | 内容 |
|---|---|
| Service | AgentLoop extends Service implements AgentFactory;static inject = ['agents','sessions','llm','tools','systemPrompt'];构造时 ctx.agents.setFactory(this) 自注册 |
| Config | maxParallelToolCalls(默认 10,1 = 串行)+ agents[](id / sessionId / cwd / resumeSessionId / provider / model / maxTokens) |
| Settings | agent-loop namespace 只有 maxParallelToolCalls;写入校验非正整数拒绝,运行中保留最后好的 cap |
| 事件 | agent-loop/config-start-failed { sessionId, error } —— 配置 agent 启动失败时 |
| 额外 ctx | ctx.configuredAgentIdentities:launcher 在 Loader 挂载前固定配置 agent 的会话身份 |
AgentFactory 的一个实现。注册表把 create/resume 委托给当前唯一注册的 factory ——
加载另一个实现 Agent + AgentFactory 的插件即可整体换掉循环,UI/hook/工具插件无感知。
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>
包内可见,没有 /src/* 逃生通道导出。构造(agent.ts:80-97):建 fused dispatcher、Inbox、找最后一个 turn/start 设 lastTurn、
createScope(loopCtx, this)、ctx = scope.ctx.extend({ agent: this })。
buildRequest(turn, step, assembly.tools, renderPrompt(assembly), session.deriveMessages(), signal) —— 每步重建append('assistant/chunk', ...) 收 seq,再 assembler.push(BlockAssembler 增量组装)agent/request-error 瀑布(retry 则 continue 重建请求);正常 → 每个成功 finish 恰好一个 assistant/message(surfaceOp: append,sourceEventSeqs 精确列出 chunk seqs);max-tokens → 返回 kind;有 tool calls → executeToolCallsadditionalContexts splice 进 next-step inbox;concludesTurn 置 concluded → step 以 completed 收尾session.requestHeader() 持久化 header;route = agent options 的 provider/modelagent/request 瀑布 → 检查 provider/model 非空 → llm.prepareCall(物化 adapter 默认;NO_ADAPTER 时保留提案 config 允许 llm/stream 监听器接管)request/header 记录:首次 {reason:initial|resume},之后仅当 !headerEquals 时 {reason:change}request/context 记录:provider/model/contextWindow 变化才追加markAgentLoopRequest(deepFreeze({...config, messages, system?, tools?, sessionId, signal})) —— 打进程内标记供 invariant 识别executionMode(first.exec) 为 parallel 取剩余全部、exclusive 取第一个(barrier);组边界处重新分类commitReady 只推进连续模型序槽位;needsPost 走 finalize、否则 finish;只有 dispatch/body 重叠,policy/结果/上下文保持模型序appendToolResult(结果+错误身份+meta,sourceEventSeqs: [callSeq] 引用 call)TOOL_ABORTED_BEFORE_DISPATCH);scheduler 内部错误:等已启动的 settle,抛第一个失败,不伪造工具结果maxParallelToolCalls 每轮从 ctx.agentLoop.config 解构读取 —— settings 变更影响下一组而非在飞组
挂 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 匹配。
agent/pre-step / agent/request;限流/超时用 guard / tools/execute wrapper;
turn 预算在 agent/turn-stopping 挂 cancel。真的需要不同循环语义时才整体替换(见 dsh-agent 定制指南 (a))。
restoreOrCreateConfigured;resume 走 resumeWithsessionId 配置项让 remount 恢复已物化的历史;fresh 配置 agent 默认 ${label}-session-<uuid>agent-loop/config-start-failed 上报,不会静默吞掉agent/status 的 running 是"驱动级 drain 区间"而非"turn 仍开"的证据dsh-session-checkpoint-policy(每模型请求前、顶层工具执行前、每步前 await flush,失败 fail-closed)