Agent Loop 的运行机制(解析 Claude Code 各阶段职责与工具调用时机)

Agent 循环由”上下文装配→模型决策→工具执行→结果回填→终止判断”五个阶段串成,Claude Code 把它跑成生产级 Agent 框架的骨架;每轮循环都会重新装配上下文、判断是否调用工具,再决定继续推进或收尾。理解这五个阶段各自的职责边界,是调优 prompt、设权限、设计并行工具的前提。

一、循环在做什么

一次 query() 启动后,循环在 while not stopped 框架里反复跑。官方的”五步视角”把循环拆成:接收 prompt、评估响应、执行工具、重复、返回结果。底层伪代码则把每轮进一步细化为 8 步:发送开始事件、控制工具输出体积、压缩历史、追加 system context、调 API、解析流、运行工具、把结果塞回历史。

不论粒度如何拆,循环的”主干语义”始终是装配→决策→执行→回填→终止。下面按阶段说明每个环节在做什么、什么时候调用工具。

把这五阶段用一段伪代码写直白点:

# one turn of the agent loop
while not stopped:
  # (a) assemble -- what the model sees
  context = assemble(
    system_prompt,   # instructions header
    tool_schemas,    # callable tool signatures
    history,         # prior turn messages
    hook_additions,  # pushed in by hooks
  )

  # (b) model -- pick the next action
  action = model(context, tools)
  if action.is_text_only():
    stopped = run_stop_hooks(action)  # may veto
    continue

  # (c) execute -- gate and run the tool call
  if not permitted(action):           # permission layer
    continue
  action  = run_pre_tool_hooks(action)   # block / write
  result  = execute(action)              # tool runs here
  result  = run_post_tool_hooks(result)  # mutate / annotate
  history.append(action, result)

这段代码的价值是把”权限闸门”和”hooks”在循环里出现的位置标出来——前者挤在执行阶段的最前面,后者分别在工具调用前后插入。

二、五个阶段的职责边界

2.1 上下文装配(assemble)

每轮开始时把模型能看到的所有信息拼成一个 prompt:系统提示、工具签名、历史消息、Hook 注入的额外段、CLAUDE.md 记忆文件、Skills 列表、MCP 资源。系统提示由 buildEffectiveSystemPrompt() 拼装,环境信息用 computeSimpleEnvInfo() 计算并缓存。整段 prompt 体量常常破万 token,因此后续要做压缩。

这一步不调用工具,职责只是”把模型该看的东西准备好”。任何想影响模型决策的代码——Hook 注入、Skill 启用、MCP 工具注册——都发生在这一阶段。

2.2 模型决策(model)

把装配好的 context 喂给 Claude,让它选择下一步动作。结果只有两类:

  • 仅文本:模型直接回答,循环进入”是否停止”判断;
  • 含 tool_use:模型决定调用工具,循环进入执行阶段。

文本块直接进 UI 流式显示;思考块被内部消费;工具调用块则被提取出来交给下一阶段。模型决策也不真正执行工具——它只是表达意图,权限闸门和实际执行都在后面。

2.3 工具执行(execute)

拿到 tool_use 后先过四道闸:

  1. 黑名单(reject):如 rm -rf 等敏感命令立即拒绝;
  2. 白名单(allow):如 Read、Glob 走快通道免审;
  3. 分类器:异步启发式判定读/写;
  4. 交互式确认:分类器无法判定时向用户问询。

通过后由 execute(action) 真正运行工具,再依次跑 pre_tool_hooks 和 post_tool_hooks 做拦截、修改或追加上下文。结果会包成 tool_result 块,塞回 user 消息。批量执行时,partitionToolCalls() 会把只读工具并发(默认 10 并发)、写工具串行,避免竞态。

2.4 结果回填(feedback)

工具结果回填到 history 后,循环回到装配阶段进入下一轮。模型在下一次响应里能看见全部历史,包括之前的工具输出、错误信息、Hook 改动。压缩策略在这一阶段触发:剩余上下文窗口低于阈值时,启动 micro-compression(裁剪冗长解释)、对话记忆压缩(早期对话换成摘要)、失败后的反应式压缩(连续三次失败后停止压缩尝试)。

回填的”颗粒”很重要——既要保留工具的原始输出供模型回看,也要避免一次性塞满上下文;这段平衡是 Claude Code 相对裸 Messages API 的一大价值。

2.5 终止判断(stop)

每轮末尾判断”是否继续”:

  • 模型仅文本输出:通过 stop_hooks 跑停止钩子,没被否决就退出;
  • 达到 max_turns 或 max_budget_usd:硬性停机;
  • 工具拒绝、异常抛错:按错误类型决定是否熔断。

终止后 SDK 发出 final AssistantMessage(仅文本)+ ResultMessage(含 token 用量、成本、session id)。从这里开始,UI 可以做”任务完成”展示,调用方可以做计费统计。

三、循环的内部小步:8 步伪代码

把单轮放大看,代码侧真实的步骤是这八步,按顺序串成:

  1. emit stream_request_start 事件;
  2. 裁剪本轮工具输出体积,避免单条结果撑爆窗口;
  3. 必要时压缩上一条响应;
  4. 追加 system context(环境、预算、策略等);
  5. 调 Claude API 并流式接收;
  6. 解析流,分出 text / thinking / tool_use 块;
  7. 对 tool_use 跑权限闸门 + hooks + 实际执行;
  8. 把工具结果回填到消息列表,准备进入下一轮。

这八步与上面五阶段是”粗粒度 vs 细粒度”的关系:装配=1+3+4,决策=5+6,执行=7,回填=8,终止判断横跨 6 和 7 的尾段。理解粗粒度便于优化策略,理解细粒度便于定位问题。

四、阶段与工具调用时机的对应

工具被调用的时刻永远在”模型决策”阶段之后、”结果回填”阶段之前。把它写成时间轴就是:装配 → 决策(含 tool_use)→ 执行(含 hook)→ 回填 → 终止判断 → 回到装配。

阶段 触发工具 不触发工具 关键动作
装配 否 否 拼 prompt、加载 CLAUDE.md、注册 tools
决策 否 否 调 API、解析流
执行 是 否 权限闸门、hooks、execute()
回填 否 是 写 history、压缩
终止判断 否 是 跑 stop_hooks、产出 result

注意:装配与决策阶段虽不直接执行工具,但决定了工具是否被使用——装配阶段通过 tool schema 限制可选范围,决策阶段通过 prompt 上下文影响模型偏好。优化循环时,要从这两段入手而不是改 execute()。

五、典型流程演示

用 prompt “修一下 auth.ts 里的失败测试” 演示循环完整跑一遍:

  1. 装配:把 CLAUDE.md、可用工具、当前目录、失败测试列表拼好;
  2. 决策 turn 1:模型给出 assistant 块,含 Bash 工具调用 npm test;
  3. 执行:权限通过 → 跑 npm test → 输出三个失败;
  4. 回填:把失败输出塞回 history;
  5. 决策 turn 2:模型决定读 auth.ts 与 auth.test.ts;
  6. 决策 turn 3:模型调 Edit 改文件,再 Bash 跑测试;
  7. 回填:测试通过;
  8. 决策 final:模型给纯文本回复”已修复,全部通过”;
  9. 终止:跑 stop_hooks,产出 ResultMessage,计费统计落账。

整个过程是 4 轮(3 轮带工具 + 1 轮纯文本)。maxTurns=2 会把循环卡在”已修代码但还没跑回归”那一刻——这是配置项最直观的副作用。

六、循环里的并发与权限

并行工具只在执行阶段被决定,并发上限默认 10 个。三个并发规则:

  • 只读工具(Read、Glob、Grep)并发跑,按提交顺序返回结果;
  • 写工具(Edit、Write)必须串行,避免顺序错乱;
  • 一次 assistant 响应里若同时有读和写,SDK 会先批处理读、写一次只提交一个。

权限层级的”四道闸门”在 execute() 入口处运行。bypassPermissions 模式会跳过白名单与分类器,但 reject 仍然生效——这意味着无论权限怎么开,危险命令都拦得住。

七、循环的可调旋钮

调优循环时真正能动的参数集中在 query 入口和 system context:

  • maxTurns / max_turns:限制带工具的轮数;
  • maxBudgetUsd / max_budget_usd:按花费熔断;
  • permissionMode:plan / acceptEdits / bypassPermissions / default;
  • canUseTool 自定义回调:把分类器升级为业务级决策;
  • includePartialMessages:开 stream_event 增量;
  • systemPrompt / appendSystemPrompt:装配阶段可控注入。

调任何一个旋钮都等价于在循环某一阶段插入策略——比如改 canUseTool 就是在执行阶段换闸门。

到这里,循环从”装配到终止”的五个阶段、每阶段的工具调用时机、可调的旋钮都串完了。设计 Agent 时,把策略放在对的阶段,比把策略塞进 system prompt 更可控。

常见问题(FAQ)

Q1:maxTurns 限制的是带工具的轮次还是所有轮次?

只统计带 tool_use 的轮次,纯文本回复那一轮不计入预算;纯文本轮次会自然结束循环。

Q2:执行阶段的权限闸门能完全关掉吗?

黑名单不能关,白名单与分类器可在 bypassPermissions 下跳过;想接管决策应自定义 canUseTool 而不是改设置。

Q3:循环压缩会丢历史吗?

micro-compression 只裁冗长解释,工具输入输出保留;对话记忆压缩会把早期对话换成摘要,原文不再可见;失败连续三次后停止自动压缩。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
其AI的头像其AI普通用户

相关推荐

返回顶部