Agent loop 主循环 · 一轮对话是怎么转起来的
grok 的主循环不在「Agent 对象」里,而在 shell 的 SessionActor。这一章讲清「Agent 对象」与「会话 actor」的分工,以及 process_conversation_turn 那个核心 loop:一次用户输入如何驱动模型与工具来回多轮,直到收敛。
作者 David更新于 第 7 篇(共 60 篇)
Agent loop 主循环 · 一轮对话是怎么转起来的
消息生命周期那张图的第 2 步——「开一轮 turn」——是整个 agent 的心脏。这一章把它拆开。
先分清两个东西:Agent 对象 vs 会话 actor
读源码时最容易迷路的一点:主循环不在 xai-grok-agent 里。
xai-grok-agent造的是一个Agent对象(agent.rs):definition + 渲染好的 system prompt +Arc<ToolBridge>+ 各种策略(压缩、提醒)+ 后端托管工具清单。它构造完基本不可变,是「这个会话配置成什么样」的载体,本身不驱动任何东西。- 真正的循环在
xai-grok-shell的SessionActor(src/session/acp_session.rs,实现拆在同级的acp_session_impl/目录里)。它是一个单线程 actor(!Send,跑在 tokioLocalSet上),一个run_session命令循环(run_loop.rs)收SessionCommand,把 turn 派出去跑。
记住这个分工:Agent 对象是「配置」,SessionActor 是「引擎」。
一次输入到一个 turn
你发一句话,走这条链(每一跳在源码里是独立文件,职责清晰):
SessionCommand::Prompt→queue_input(prompt_queue.rs)把输入压进pending_inputs队列;maybe_start_running_task(notification_drain.rs):若当前没有 running task,就把队首提升为运行中(顺带处理合并排队消息、hook 拦停、编辑锁等情况),由AgentTask::new_prompt用spawn_local起一个 turn 任务;handle_prompt→process_conversation_turn_with_recovery→process_conversation_turn(都在turn.rs,核心loop在它包着的process_conversation_turn_inner里);- 跑完,结果经 completion 通道回到
run_session,handle_turn_end收尾,再看队列里有没有下一条。
一个「turn」不等于「一次模型调用」。 一个 turn 内部会和模型来回很多次——模型每要求调一次工具,就得把结果喂回去再问一遍。turn 结束的定义是:模型不再要求工具、给出最终答复。
核心 loop:process_conversation_turn
它返回 TurnOutcome(Completed / Cancelled / MaxTurnsReached / StationarityEnded)。循环体每一圈大致是:
图表加载中…
几个关键点:
- 采样前先查压缩:
check_auto_compact_needed()(session/compaction.rs)在圈顶跑,估算用量达到上下文窗口的 85%(默认阈值)就先压缩再采样(详见上下文压缩)。 - 调模型:
run_turn_via_sampler(sampler_turn.rs)最终调的是下一章那个采样器 actor 的submit_and_collect_with_metadata——submit_and_collect的加强版,多带回 doom-loop 信号。它还会等一个「流已排空」的屏障(STREAM_DRAIN_TIMEOUT,5 秒),确保流式增量都落到 UI 后再处理工具调用。 - 分支是循环的灵魂:
- 模型没再要工具 → 跑 TodoGate(这道闸默认关闭,需经远端设置
todo_gate_enabled打开;开着时若还有未完成 todo 且没超次数上限——默认每个 prompt 2 次——就注入<system-reminder>逼它continue)→ 再排空一次打断 → 否则finalize收尾、返回Completed。 - 模型要工具 →
execute_tool_calls执行(详见工具系统),结果写回chat_state,continue再来一圈。
- 模型没再要工具 → 跑 TodoGate(这道闸默认关闭,需经远端设置
- 安全网:
MaxTurnsReached(--max-turns上限)、工具后check_preflight_overflow()再查一次溢出;- 鉴权失败自动刷新重试(
RefreshAuthAndResubmit)、上下文超长自动压缩重投(CompactAndResubmit); - 瞬时故障(流停滞、传输错误、可重试的 5xx)在采样器自己的重试用尽后,turn 层再按 2 / 10 / 30 秒退避重投(
RetryTransient,只对交互式主会话开启:每步最多 3 次、每个 prompt 最多 10 次、一次故障期最长 10 分钟); - 「动作停滞」检测:模型连续发出逐字相同的工具调用,先注入提醒,再超过硬阈值就以
StationarityEnded静默收尾(阈值见打断与防失控); - 输出撞上
max_tokens被截断时,还有一套可配置的 length salvage,注入「接着写」的提醒让模型续上。
和《从 LLM 到 Coding Agent》对照
那本的 agent-loop 讲的最小实现,本质是一个 while 循环:问模型 → 有工具就执行 → 喂回去 → 再问。grok 的循环骨架一模一样,但外面裹了一圈生产级的东西:自动压缩、TodoGate、打断排空、鉴权重试、瞬时重投、溢出兜底、动作停滞检测、turn 上限。教学实现让你看清心跳;生产实现让你看清心跳周围要挂多少监护仪。
上一篇:一条消息的生命周期 · 下一篇:采样器 · actor 模型