# 02. Agent Loop 执行机制 ![Agent Loop 执行机制](assets/02-agent-loop.png) ## 1. Agent Loop 的基本形态 Agent 每轮不是只调用一次模型,而是一个循环: ```text 用户输入 -> 构造 session 和 prompt -> 调模型 -> 模型返回 assistant text 或 tool_calls -> 如果没有 tool_calls:输出最终回复,结束 -> 如果有 tool_calls:执行工具 -> 工具结果写回 session -> 下一轮模型继续读取工具结果 -> 直到最终回复、预算超限、取消、max_turns 或 review 停止 ``` 这个循环允许 Agent 做“观察-执行-再观察”的任务,例如: - 先读文件,再决定是否需要抽取。 - 先生成 plan,等待用户 review。 - 先运行脚本,再根据校验结果修复。 - 先查询线上数据,再抽样展示。 ## 2. 新会话和恢复会话 新会话入口: ```text src/agent_runtime.py:413 run(...) ``` 关键动作: ```text 1. 清理当前 managed_agent_id 和 resume_source_session_id。 2. 创建 session_id。 3. 创建 scratchpad_directory。 4. 绑定 plan_runtime/task_runtime 到 scratchpad。 5. 调 _run_prompt(...) 6. 累计 usage。 ``` 恢复会话入口: ```text src/agent_runtime.py:441 resume(...) ``` 关键动作: ```text 1. 从 StoredAgentSession 恢复 AgentSessionState。 2. 回放 file_history 和 compaction 信息。 3. 设置 active_session_id 和 last_session_path。 4. 恢复 plugin state。 5. 复用已有 scratchpad_directory。 6. 调 _run_prompt(...) ``` 这解释了为什么刷新页面、回到旧会话后,Agent 理论上可以继续同一个 session 的上下文,而不是新建一个任务。 ## 3. `_run_prompt` 主链路 核心位置: ```text src/agent_runtime.py:520 _run_prompt 主体 ``` 主链路关键步骤: ```text 1. slash command 预处理。 2. hook policy / plugin hook 修改 prompt。 3. agent_manager.start_agent(...) 记录运行。 4. 新建或复用 AgentSessionState。 5. runtime_context prepend 到模型可见的用户消息。 6. session.append_user(...)。 7. tool_context 注入 scratchpad、plan_runtime、task_runtime。 8. 生成 tool_specs。 9. 初始化 usage、cost、tool_calls、events。 10. 进入 turn loop。 ``` 对应代码点: ```text src/agent_runtime.py:523 agent_manager.start_agent(...) src/agent_runtime.py:531 session = base_session or build_session(...) src/agent_runtime.py:539 _prepend_runtime_context(...) src/agent_runtime.py:543 session.append_user(...) src/agent_runtime.py:546 replace(self.tool_context, scratchpad_directory=...) src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...] src/agent_runtime.py:582 stream_events = _RuntimeEventBuffer(event_sink) src/agent_runtime.py:619 for turn_index in range(...) ``` ## 4. 模型调用和 tool_calls 判断 模型调用入口: ```text src/agent_runtime.py:1490 _query_model(...) ``` 非流式路径中: ```text turn = self.client.complete( session.to_openai_messages(), tool_specs, output_schema=... ) ``` 模型能否返回 `tool_calls` 取决于: - 当前 `messages`。 - 系统提示词。 - Skill 列表。 - 工具描述和参数 schema。 - 模型自身 tool calling 能力。 基座不会强制某个工具被调用。基座只把工具能力暴露给模型,并在模型返回 `tool_calls` 后负责执行。 ## 5. 没有 tool_calls 时 如果模型本轮没有工具调用: ```text src/agent_runtime.py:763 if not turn.tool_calls ``` 后续可能有三种情况: 1. 直接输出最终回复。 2. 如果模型输出被截断,自动追加 continuation prompt。 3. 如果达到 continuation 限制,则追加提示并结束。 最终会: ```text session.append_assistant(...) _append_final_text_stream_events(...) AgentRunResult(...) _persist_session(...) ``` ## 6. 有 tool_calls 时 如果模型返回工具调用: ```text src/agent_runtime.py:1008 for tool_call in turn.tool_calls ``` 每个工具调用会: ```text 1. tool_calls 计数 +1。 2. 检查预算。 3. session.start_tool(...) 写入工具开始消息。 4. stream_events 追加 tool_start。 5. 根据工具名执行 handler。 6. 工具结果写回 session。 7. stream_events 追加工具结果。 8. 下一轮模型读取工具结果继续判断。 ``` 关键代码点: ```text src/agent_runtime.py:1054 session.start_tool(...) src/agent_runtime.py:1060 stream_events.append(type='tool_start') src/agent_tools.py:60 execute_tool(...) src/agent_tools.py:76 execute_tool_streaming(...) ``` ## 7. 为什么 review 可以暂停流程 review 门禁不是特殊 UI 魔法,而是 Agent loop 的自然结果: 1. Skill 要求模型在某一步调用 review 工具。 2. 工具创建 pending state,并返回需要展示的信息。 3. Agent 生成回复,告诉用户需要确认。 4. 当前 run 结束。 5. 用户下一轮回复“确认”。 6. Agent resume 旧 session,调用 confirm 工具。 7. 后续流程继续。 例如 `product-data`: ```text data_agent_prepare_generation_goal -> pending goal -> 停止等待用户确认 data_agent_confirm_generation_goal -> confirmed_goal_id -> data_agent_prepare_generation_plan -> pending plan -> 停止等待用户确认 data_agent_confirm_generation_plan -> confirmed_plan_id -> 允许生成 draft 和转换 records ``` ## 8. 预算、取消和 max_turns 基座会在模型调用前后、工具请求前检查预算: ```text src/agent_runtime.py:592 initial_budget = self._check_budget(...) src/agent_runtime.py:732 budget_after_model = self._check_budget(...) src/agent_runtime.py:1014 budget_after_tool_request = self._check_budget(...) ``` 如果达到最大轮次: ```text src/agent_runtime.py:1440 _build_max_turns_output(...) ``` 输出会包含最后一次阶段说明,提醒用户可以继续补充指令。 取消由后端 run manager 和 tool process registry 处理。Web 点停止后,后端将 run 标记取消,并让工具执行上下文感知 cancel_event。 ## 9. 事件流和前端活动区 Agent loop 内会不断追加 `stream_events`,后端用 `event_sink` 把事件推给前端。 后端关键代码: ```text backend/api/server.py:1751 emit_agent_event(event) backend/api/server.py:1756 state.run_manager.record_event(...) backend/api/server.py:1758 state.run_state_store.record_event(...) backend/api/server.py:2034 StreamingResponse(..., media_type='application/x-ndjson') ``` 前端活动区展示的阶段说明、工具开始、工具完成、最终文本,本质上都来自这些 runtime events 或 session replay。