31. agent-loop.ts:一次消息怎样跑成多轮 Agent¶
这个模块干什么¶
agent-loop.ts 是仓库的执行心脏:它把一条或多条 prompt 放入 context,流式请求 LLM,执行 assistant 请求的工具,再把结果喂回下一轮。 【packages/agent/src/agent-loop.ts:95-117】
它不保存全局 Agent 状态,只接收 context、config、abort signal、event sink 与 StreamFn,所以既能被 Agent 使用,也能被 Harness 直接调用。 【packages/agent/src/agent-loop.ts:95-102】 【packages/agent/src/harness/agent-harness.ts:613-622】
循环的难点不在“再请求一次模型”,而在保证流事件、工具结果、队列、停止条件和 session 刷新落在正确的时刻。 【packages/agent/src/agent-loop.ts:169-275】
stream-fn.ts 提供可选默认流,proxy.ts 把后端 SSE 适配回统一事件流,harness/ 则把这一核心接进持久会话和执行环境。 【packages/agent/src/stream-fn.ts:5-20】 【packages/agent/src/proxy.ts:82-99】 【packages/agent/src/harness/agent-harness.ts:442-498】
核心文件速查¶
| 文件 | 一句话职责 | 必读优先级 |
|---|---|---|
src/agent-loop.ts |
驱动 LLM 流、工具批次、转向/后续队列与生命周期事件。 【packages/agent/src/agent-loop.ts:155-792】 | ⭐⭐⭐ |
src/types.ts |
规定 loop 可调用的 hooks、队列模式、工具和事件形状。 【packages/agent/src/types.ts:144-287】 【packages/agent/src/types.ts:379-437】 | ⭐⭐⭐ |
src/agent.ts |
将低层 event sink 变成状态归约加订阅者背压。 【packages/agent/src/agent.ts:398-468】 【packages/agent/src/agent.ts:522-576】 | ⭐⭐⭐ |
src/stream-fn.ts |
注册/读取默认 StreamFn,未配置时明确报错。 【packages/agent/src/stream-fn.ts:3-20】 |
⭐⭐ |
src/proxy.ts |
POST 到 /api/stream,读取 SSE,并在客户端重建 partial assistant message。 【packages/agent/src/proxy.ts:116-232】 |
⭐⭐ |
src/node.ts |
Node 专用入口:NodeExecutionEnv 加全部根 API。 【packages/agent/src/node.ts:1-2】 |
⭐ |
src/harness/agent-harness.ts |
用 session 快照、provider hooks 与专属事件包装 runAgentLoop。 【packages/agent/src/harness/agent-harness.ts:354-498】 |
⭐⭐ |
src/harness/session/session.ts |
将树状 session entries 投影为下一轮的 AgentMessage context。 【packages/agent/src/harness/session/session.ts:59-148】 | ⭐⭐ |
src/harness/messages.ts |
注册 Harness 自定义消息,并实现它们到 LLM Message 的投影。 【packages/agent/src/harness/messages.ts:19-61】 【packages/agent/src/harness/messages.ts:120-164】 | ⭐ |
src/harness/types.ts |
定义执行环境、session tree、Harness hooks 与稳定错误码。 【packages/agent/src/harness/types.ts:146-373】 【packages/agent/src/harness/types.ts:553-817】 | ⭐ |
精读¶
1. agentLoop() 是事件流外壳,runAgentLoop() 才是真执行¶
低层公开 API 分为返回 EventStream 的 agentLoop() / agentLoopContinue(),以及可被宿主 await 的 runAgentLoop() / runAgentLoopContinue()。 【packages/agent/src/agent-loop.ts:31-53】 【packages/agent/src/agent-loop.ts:95-143】
agentLoop() 立即建立 EventStream,后台启动 runAgentLoop(),每个 event 只做 stream.push(event),成功后以返回的新消息数组 stream.end(messages)。 【packages/agent/src/agent-loop.ts:31-53】
因此低层 EventStream 的消费者是观察者;若需要“消息落库后才能开始 tool preflight”的屏障,应通过 Agent 的 awaited event listener 接线。 【packages/agent/src/agent.ts:398-410】 【packages/agent/src/agent.ts:522-576】
agentLoopContinue() 拒绝空 context,也拒绝最后一条是 assistant 的 context。 【packages/agent/src/agent-loop.ts:64-92】
原因是下一次 provider request 的最后一条 LLM 消息必须是 user 或 toolResult;转换器可能改变 role,所以底层只能做 assistant 这一层可检查的防御。 【packages/agent/src/agent-loop.ts:56-63】
runAgentLoop() 先把 prompts 复制到 newMessages,同时创建一个 messages 为“旧 context 加 prompts”的 currentContext。 【packages/agent/src/agent-loop.ts:95-107】
它严格先发 agent_start、turn_start,再为每个 prompt 各发一次 message_start / message_end。 【packages/agent/src/agent-loop.ts:109-116】
runAgentLoopContinue() 不创建 prompt 事件,newMessages 也从空数组开始,因此其最终结果只含这次 continuation 新产出的消息。 【packages/agent/src/agent-loop.ts:120-142】
createAgentStream() 用 agent_end 作为终止事件,并把该事件的 messages 当作 stream result。 【packages/agent/src/agent-loop.ts:145-150】
2. 两层 while:内层继续“本次工作”,外层只等 follow-up¶
runLoop() 维护可替换的 currentContext 与 config,并在真正进入循环前先 poll 一次 steering。 【packages/agent/src/agent-loop.ts:155-168】
外层 while (true) 的职责是:内层没有工作时,再检查 follow-up queue;它不是“每轮请求模型”的循环。 【packages/agent/src/agent-loop.ts:169-174】 【packages/agent/src/agent-loop.ts:262-274】
内层条件是 hasMoreToolCalls || pendingMessages.length > 0。 【packages/agent/src/agent-loop.ts:171-174】
hasMoreToolCalls 初始设为 true,使得没有工具的第一次 prompt 也至少请求一次 LLM。 【packages/agent/src/agent-loop.ts:170-174】
从第二次内层迭代开始才发新的 turn_start;初始那一轮已经由 runAgentLoop() 或 continuation 入口发过。 【packages/agent/src/agent-loop.ts:109-116】 【packages/agent/src/agent-loop.ts:175-179】
下面这段是“先把队列中的用户输入变成正式 transcript,再调模型”的顺序:
while (hasMoreToolCalls || pendingMessages.length > 0) {
if (!firstTurn) {
await emit({ type: "turn_start" });
} else {
firstTurn = false;
}
// Process pending messages (inject before next assistant response)
if (pendingMessages.length > 0) {
来源:packages/agent/src/agent-loop.ts:174-182。
pendingMessages 中每条消息都会先发 start/end,再同时 push 入 currentContext.messages 和 newMessages。 【packages/agent/src/agent-loop.ts:181-190】
这解释了 steering 不是“打断当前工具”:它只会在上一轮 assistant 与工具已走完后的下一个 provider request 前出现。 【packages/agent/src/agent-loop.ts:224-260】
assistant message 的 error 或 aborted stop reason 会立刻得到 turn_end、agent_end 并 return,不会尝试解析工具。 【packages/agent/src/agent-loop.ts:192-200】
正常完成后,loop 从 message.content 里过滤 type === "toolCall"。 【packages/agent/src/agent-loop.ts:202-207】
有工具时,tool result messages 会先补回 context/newMessages,随后才发本轮 turn_end。 【packages/agent/src/agent-loop.ts:205-224】
prepareNextTurn 位于 turn_end 之后,可以替换 context/model/thinking,并影响下一次模型调用。 【packages/agent/src/agent-loop.ts:224-245】
shouldStopAfterTurn 也在此处检查,返回 true 时先发 agent_end,不会再 poll steering 或 follow-up。 【packages/agent/src/agent-loop.ts:247-257】
否则 loop 在每轮末尾 poll steering;若取到消息,内层条件再次成立。 【packages/agent/src/agent-loop.ts:259-260】
只有“无自动工具续轮且无 steering”时才会 poll follow-up;有 follow-up 会回到外层下一次内层处理。 【packages/agent/src/agent-loop.ts:262-271】
3. LLM 边界:转换只影响请求,不覆写完整 transcript¶
streamAssistantResponse() 是唯一把 AgentMessage[] 变为 pi-ai Context 的地方。 【packages/agent/src/agent-loop.ts:277-302】
它先可选地调用 transformContext(context.messages, signal),再调用 convertToLlm(messages)。 【packages/agent/src/agent-loop.ts:288-295】
transformContext 的返回值放在局部变量 messages,没有赋回 context.messages;所以它是本次请求的视图变换,不自动改写长期 transcript。 【packages/agent/src/agent-loop.ts:288-302】
接着 loop 将原始 system prompt、转换后的 LLM messages、原 context 的 tools 组成 llmContext。 【packages/agent/src/agent-loop.ts:297-302】
动态 getApiKey(provider) 优先于 config 内已有 apiKey,适合在多轮工具执行后刷新短期 token。 【packages/agent/src/agent-loop.ts:304-312】
这一段正好定义了模型边界和 abort signal 的去向:
let messages = context.messages;
if (config.transformContext) {
messages = await config.transformContext(messages, signal);
}
const llmMessages = await config.convertToLlm(messages);
const llmContext: Context = {
systemPrompt: context.systemPrompt,
messages: llmMessages,
tools: context.tools,
};
来源:packages/agent/src/agent-loop.ts:288-302。
stream 的 start event 把部分 assistant message push 进 context,并发 message_start 的浅复制。 【packages/agent/src/agent-loop.ts:317-324】
text、thinking、toolcall 的 start/delta/end event 都更新 context 中最后一条 partial message,并发 message_update。 【packages/agent/src/agent-loop.ts:326-344】
provider 发 done 或 error 后,loop 必定调用 response.result() 获得 final assistant message。 【packages/agent/src/agent-loop.ts:346-358】
如果 provider 没发过 start,loop 仍会补 message_start,然后统一发 message_end,因此 event 消费者不必猜测 stream 是否从 start 开始。 【packages/agent/src/agent-loop.ts:346-371】
4. tool-call 解析:先拒绝不安全批次,再逐个 preflight¶
assistant 的 stop reason 为 "length" 时,所有 tool calls 都被拒绝,不会因某个 JSON 恰好能解析就执行。 【packages/agent/src/agent-loop.ts:207-215】
原因是流式 JSON 的 best-effort 解析可能产出看似合法但已截断的 arguments。 【packages/agent/src/agent-loop.ts:374-380】
拒绝路径仍为每一调用发 tool_execution_start、tool_execution_end,再产生一条带明确错误文本的 tool result message。 【packages/agent/src/agent-loop.ts:381-405】
正常路径首先查找同名工具;找不到也不是 throw,而是 immediate error outcome。 【packages/agent/src/agent-loop.ts:600-614】
有 prepareArguments 时,它先处理原始 arguments,再交给 validateToolArguments 做 schema 校验。 【packages/agent/src/agent-loop.ts:586-598】 【packages/agent/src/agent-loop.ts:616-619】
只有校验之后才调用 beforeToolCall,因此权限层看到的是已验证参数而不是半截 JSON。 【packages/agent/src/agent-loop.ts:616-628】
const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
if (!tool) {
return {
kind: "immediate",
result: createErrorToolResult(`Tool ${toolCall.name} not found`),
isError: true,
};
}
try {
const preparedToolCall = prepareToolCallArguments(tool, toolCall);
const validatedArgs = validateToolArguments(tool, preparedToolCall);
来源:packages/agent/src/agent-loop.ts:607-619。
hook 返回 block 会变成 error result;hook 后或返回 prepared 前发现 signal aborted 也会变成 "Operation aborted" 的 immediate result。 【packages/agent/src/agent-loop.ts:620-663】
参数转换、校验和 hook 抛出的异常同样被 catch 并转换为 error result,而不是撕裂整条 Agent 事件序列。 【packages/agent/src/agent-loop.ts:657-663】
5. 并发工具:preflight 串行,执行并行,持久化顺序稳定¶
若 config 是 "sequential",或本批任一目标工具声明 executionMode: "sequential",整批都走 sequential。 【packages/agent/src/agent-loop.ts:411-425】
sequential 路径为每条调用依次发 start、preflight、execute/finalize、发 execution end、发 tool-result message。 【packages/agent/src/agent-loop.ts:433-486】
sequential 在当前调用完成后若 signal 已 aborted 就 break,不再开始后续调用。 【packages/agent/src/agent-loop.ts:472-480】
parallel 路径仍逐条发 tool_execution_start 与 prepareToolCall(),所以 permission/preflight 的顺序稳定而且可观察。 【packages/agent/src/agent-loop.ts:489-520】
preflight 为 immediate error 的调用会立即发 tool_execution_end,无需进入 Promise 批次。 【packages/agent/src/agent-loop.ts:507-519】
准备成功的调用被收集成 thunk;随后 Promise.all 同时启动它们。 【packages/agent/src/agent-loop.ts:522-542】
finalizedCalls.push(async () => {
const executed = await executePreparedToolCall(preparation, signal, emit);
const finalized = await finalizeExecutedToolCall(
currentContext,
assistantMessage,
preparation,
executed,
config,
signal,
);
await emitToolExecutionEnd(finalized, emit);
return finalized;
});
来源:packages/agent/src/agent-loop.ts:522-534。
因此 parallel 的 tool_execution_end 按实际完成时间出现,因为每个 thunk 在自己的结束时 emit。 【packages/agent/src/agent-loop.ts:522-534】
但 Promise.all 返回结果数组保留输入顺序,后面构造并发出 toolResult message 的循环也按该顺序走。 【packages/agent/src/agent-loop.ts:540-548】
这一区分很关键:UI 可以按完成时间刷新工具,写入对话和下一次模型请求则保持 assistant 原始 tool-call 顺序。 【packages/agent/src/agent-loop.ts:540-553】
每个 execute() 收到同一个 abort signal 和 update callback。 【packages/agent/src/agent-loop.ts:666-693】
工具通过 callback 给出的 partial result 会转换成 tool_execution_update event;所有已接收 update 会在工具终态前 await Promise.all(updateEvents)。 【packages/agent/src/agent-loop.ts:671-705】
(partialResult) => {
if (!acceptingUpdates) return;
updateEvents.push(
Promise.resolve(
emit({
type: "tool_execution_update",
toolCallId: prepared.toolCall.id,
toolName: prepared.toolCall.name,
args: prepared.toolCall.arguments,
partialResult,
}),
),
);
},
来源:packages/agent/src/agent-loop.ts:679-692。
工具 throw 会被 executePreparedToolCall() 变成 error result 和 isError: true。 【packages/agent/src/agent-loop.ts:697-703】
afterToolCall 在执行后、tool_execution_end 之前运行,能以字段替换方式修补最终 result 或其错误状态。 【packages/agent/src/agent-loop.ts:709-754】
一批只有在至少一个结果且每个 finalized result 都有 terminate === true 时才停止自动工具续轮。 【packages/agent/src/agent-loop.ts:582-584】
createToolResultMessage() 会把未类型化工具可能给出的空 content 规整为 [],并记录 tool call ID、工具名、details、usage、错误标记和新时间戳。 【packages/agent/src/agent-loop.ts:773-787】
6. abort 与 steering:一个是 signal,一个是排队消息¶
AbortSignal 被原样传进 stream function 的 options。 【packages/agent/src/agent-loop.ts:304-312】
同一 signal 也传进 beforeToolCall、afterToolCall 和工具 execute();实际停止长任务仍取决于这些实现是否尊重 signal。 【packages/agent/src/agent-loop.ts:620-628】 【packages/agent/src/agent-loop.ts:675-679】 【packages/agent/src/agent-loop.ts:720-732】
模型流最终回报 stopReason === "aborted" 时,loop 走与 error 相同的短路结束路径。 【packages/agent/src/agent-loop.ts:196-200】
而 steering 从 getSteeringMessages() 取得,直到一轮的 tool results 写入、turn_end、可选 next-turn hook 之后才注入。 【packages/agent/src/agent-loop.ts:218-260】
所以不要把 steering 当做“取消”API:它改变下一次 LLM 输入,abort 才是取消当前操作的通道。 【packages/agent/src/agent-loop.ts:259-274】 【packages/agent/src/agent.ts:306-314】
7. stream-fn.ts:默认值是宿主选择,不是 core 的 provider 选择¶
模块只维护一个私有 defaultStreamFn 变量。 【packages/agent/src/stream-fn.ts:1-3】
宿主调用 setDefaultStreamFn(streamFn) 可设置或清除该变量。 【packages/agent/src/stream-fn.ts:5-13】
getDefaultStreamFn() 在未配置时 throw,错误信息明确要求显式传入或先调用 setter。 【packages/agent/src/stream-fn.ts:15-20】
Agent 的构造函数和 runAgentLoop() 都可在缺少传入 streamFn 时使用这个 fallback。 【packages/agent/src/agent.ts:210-217】 【packages/agent/src/agent-loop.ts:116-117】
这避免 packages/agent 绑定 provider catalog 或兼容层;真正的模型请求仍由 pi-ai 的 StreamFn 实现承担。 【packages/agent/src/stream-fn.ts:5-10】 【packages/agent/src/types.ts:18-32】
8. proxy.ts:SSE 瘦身后如何复原成标准流¶
ProxyAssistantMessageEvent 刻意去掉每个 delta event 的 partial 字段,以降低服务端到浏览器的带宽。 【packages/agent/src/proxy.ts:33-57】
streamProxy() 先按给定 model 建立一个空的 partial assistant message,再在异步任务中处理网络流。 【packages/agent/src/proxy.ts:116-137】 【packages/agent/src/proxy.ts:119-230】
请求是带 Bearer token 的 POST ${proxyUrl}/api/stream,body 发送 model、context 和经过白名单筛选的 serializable options。 【packages/agent/src/proxy.ts:101-114】 【packages/agent/src/proxy.ts:151-164】
响应读取器把 UTF-8 chunk 累积到 buffer,按换行切分,只处理 data: 开头的 SSE 行。 【packages/agent/src/proxy.ts:179-207】
abort handler 会取消已取得的 reader,fetch 本身也收到同一个 signal。 【packages/agent/src/proxy.ts:139-149】 【packages/agent/src/proxy.ts:151-164】
processProxyEvent() 按 content index 在本地更新 text、thinking 与 tool call block,然后回发带 partial 的统一 AssistantMessageEvent。 【packages/agent/src/proxy.ts:235-365】
工具参数的 delta 会累加临时 partialJson,用 parseStreamingJson 做尽力解析,并用复制 content block 的方式触发响应式观察者。 【packages/agent/src/proxy.ts:320-345】
case "toolcall_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "toolCall") {
(content as any).partialJson += proxyEvent.delta;
content.arguments = parseStreamingJson((content as any).partialJson) || {};
partial.content[proxyEvent.contentIndex] = { ...content };
return {
type: "toolcall_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
来源:packages/agent/src/proxy.ts:320-333。
done 更新 stop reason 与 usage;error 还写入 errorMessage,二者都成为标准终态 event。 【packages/agent/src/proxy.ts:350-359】
网络或解析异常会被 catch 成 error event:signal 已 aborted 时 reason 是 "aborted",否则是 "error"。 【packages/agent/src/proxy.ts:213-229】
9. node.ts 与 harness/:loop 之外的产品化拼装层¶
根入口不含 Node 内建实现;node.ts 专门额外导出实现 ExecutionEnv 的 NodeExecutionEnv。 【packages/agent/src/node.ts:1-2】 【packages/agent/src/harness/env/nodejs.ts:344-354】
ExecutionEnv 是 FileSystem 与 Shell 的组合,所有文件系统操作都以 Result 形式返回预期失败而不是 reject。 【packages/agent/src/harness/types.ts:282-373】
NodeExecutionEnv.exec() 会解析 shell/cwd、接入 timeout 和 abort,并把 stdout/stderr chunk 回调给调用者。 【packages/agent/src/harness/env/nodejs.ts:364-497】
Harness 的内建工具 barrel 公开 createBashTool、createEditTool、createReadTool、createWriteTool 及各自类型。 【packages/agent/src/harness/tools/index.ts:1-23】
harness/messages.ts 通过声明合并注册 bashExecution、custom、branchSummary、compactionSummary 四类 AgentMessage。 【packages/agent/src/harness/messages.ts:19-61】
其 convertToLlm() 会把 bash 运行记录、custom 消息和两类摘要投影为 user 文本,同时透传 LLM 原生三类消息。 【packages/agent/src/harness/messages.ts:120-164】
Session 以有 parentId 的 SessionTreeEntry 存储会话;message、model/thinking/tool 状态变更、压缩、分支摘要等都是 entry 类型。 【packages/agent/src/harness/types.ts:375-464】
buildSessionContext() 先导出沿当前 leaf 的状态,再应用 compaction transform,最后把 entries flatMap 为下一次 Agent context 的 messages。 【packages/agent/src/harness/session/session.ts:39-148】
AgentHarness.createTurnState() 每轮从 session 建 context、解析工具 context、快照资源/stream options,并计算 active tools 和 system prompt。 【packages/agent/src/harness/agent-harness.ts:354-387】
它的 loop config 把 Harness context hook 映射为 transformContext,把 tool_call / tool_result hook 映射为 core 的 before/after tool hooks。 【packages/agent/src/harness/agent-harness.ts:442-484】
它还在 prepareNextTurn 刷新待写 session entries 与 turn state,所以每个后续 provider request 都可看到更新后的会话与模型配置。 【packages/agent/src/harness/agent-harness.ts:485-497】
Harness 处理 core message_end 时 append session message,处理 turn_end 时 flush 并发 save point,处理 agent_end 时转 idle 后发 settled。 【packages/agent/src/harness/agent-harness.ts:538-565】
AgentHarness.executeTurn() 直接调用 runAgentLoop(),不是先构造 Agent,所以它拥有另一套 session-aware 生命周期接线。 【packages/agent/src/harness/agent-harness.ts:581-655】
压缩逻辑用最新 provider usage 加尾部估算 tokens,并在超过 contextWindow - reserveTokens 时建议压缩。 【packages/agent/src/harness/compaction/compaction.ts:231-266】
Skills loader 递归找 SKILL.md、尊重 ignore 文件、返回诊断而非让单个坏 skill 中断加载。 【packages/agent/src/harness/skills.ts:43-75】 【packages/agent/src/harness/skills.ts:103-174】
Prompt-template loader 则只从给定目录读取直接 .md 子项,或接受明确指定的 .md 文件。 【packages/agent/src/harness/prompt-templates.ts:24-61】
formatSkillsForSystemPrompt() 过滤 disableModelInvocation 的 skill,并以 XML 风格的 <available_skills> 块写进 system prompt。 【packages/agent/src/harness/system-prompt.ts:3-25】
数据流¶
下面追踪“用户让模型读取两个文件后解释差异”的完整一趟:
Agent.prompt("比较 a 与 b")
-> normalizePromptInput()
-> runPromptMessages()
-> runAgentLoop(prompts, createContextSnapshot(), createLoopConfig(), processEvents, signal, streamFn)
-> runLoop()
-> streamAssistantResponse()
-> transformContext?() -> convertToLlm()
-> streamFunction(model, llmContext, { signal, ... })
-> message_start / message_update* / message_end(assistant with toolCall×2)
-> executeToolCalls()
-> prepareToolCall() ×2
-> executeToolCallsParallel()
-> tool_execution_start ×2
-> executePreparedToolCall() ×2
-> tool_execution_update* / tool_execution_end ×2
-> message_start/end(toolResult a, then toolResult b)
-> turn_end
-> streamAssistantResponse() // 带两个 toolResult 的下一轮
-> message_end(final assistant)
-> turn_end -> agent_end
-> Agent.processEvents() // 每个 event 先更新 state,再 await listeners
prompt() 的字符串标准化和 runPromptMessages() 到 runAgentLoop() 的接线在 Agent 中完成。 【packages/agent/src/agent.ts:336-347】 【packages/agent/src/agent.ts:379-412】
初始 prompt 事件和第一次进入 runLoop() 的调用顺序由 runAgentLoop() 固定。 【packages/agent/src/agent-loop.ts:95-117】
模型边界是 streamAssistantResponse() 的 transform、convert、streamFunction 三步。 【packages/agent/src/agent-loop.ts:281-312】
assistant 流中的 partial 与 final message 分别触发 update 和 end,因此 UI 能边收边画,session 可以只在 end 时持久化。 【packages/agent/src/agent-loop.ts:317-371】 【packages/agent/src/harness/agent-harness.ts:538-543】
工具批次由 executeToolCalls() 按全局/每工具 execution mode 分派;两个普通可并发工具走 parallel 路径。 【packages/agent/src/agent-loop.ts:411-425】
parallel 的 end event 可以按完成时间交错,但最终两条 toolResult messages 仍按 assistant 的 tool-call 位置进入下一轮 context。 【packages/agent/src/agent-loop.ts:522-553】
没有新的工具调用、steering 或 follow-up 后,loop 发 agent_end;Agent 则在 listener 结算完才完成 prompt Promise。 【packages/agent/src/agent-loop.ts:259-275】 【packages/agent/src/agent.ts:471-493】
自测¶
-
为什么外层
while只处理 follow-up,而第一次 LLM 调用由内层的初始hasMoreToolCalls = true触发? 【packages/agent/src/agent-loop.ts:169-174】 【packages/agent/src/agent-loop.ts:262-274】 -
transformContext()的结果为什么不会自动成为context.messages的新值? 【packages/agent/src/agent-loop.ts:288-302】 -
parallel 模式下,为什么
tool_execution_end和 tool result transcript 的排序可以不同? 【packages/agent/src/agent-loop.ts:522-553】 -
一条带
stopReason: "length"的 assistant message 为什么不能执行其中任何 tool call? 【packages/agent/src/agent-loop.ts:207-215】 【packages/agent/src/agent-loop.ts:374-405】 -
Harness 用哪三个 core event 完成消息持久化、save point 和 settled 通知? 【packages/agent/src/harness/agent-harness.ts:538-565】