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,跑在 tokio LocalSet 上),一个 run_session 命令循环(run_loop.rs)收 SessionCommand,把 turn 派出去跑。

记住这个分工:Agent 对象是「配置」,SessionActor 是「引擎」。

一次输入到一个 turn

你发一句话,走这条链(每一跳在源码里是独立文件,职责清晰):

  1. SessionCommand::Prompt → queue_input(prompt_queue.rs)把输入压进 pending_inputs 队列;
  2. maybe_start_running_task(notification_drain.rs):若当前没有 running task,就把队首提升为运行中(顺带处理合并排队消息、hook 拦停、编辑锁等情况),由 AgentTask::new_prompt 用 spawn_local 起一个 turn 任务;
  3. handle_prompt → process_conversation_turn_with_recovery → process_conversation_turn(都在 turn.rs,核心 loop 在它包着的 process_conversation_turn_inner 里);
  4. 跑完,结果经 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 再来一圈。
  • 安全网:
    • 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 模型

本页目录