很多人使用 AI Agent 时,第一反应仍然是“这次 Prompt 怎么写”。这种思路适合一次性问答,却很难支撑复杂任务:代码要改、测试要跑、错误要分析、文档要更新、结果还要验收。只靠人工一轮轮补充指令,本质上还是人在手动推动流程。
Agent Loop 要解决的是另一类问题:让 AI Agent 在明确目标、工具权限和护栏约束下,自己完成“观察状态 → 制定动作 → 调用工具 → 接收反馈 → 修正下一步”的循环。
它的关键不在于把 Prompt 写得更长,而在于把工作设计成一个可运行、可验证、可收敛的系统。
flowchart TD
A[目标 Objective] --> B[组装上下文 Context]
B --> C[模型推理 Reasoning]
C --> D[选择动作 Action]
D --> E[调用工具 Tools]
E --> F[观察结果 Observation]
F --> G{是否满足停止条件}
G -- 否 --> B
G -- 是 --> H[输出结果 Result]
G -- 超预算/无进展 --> I[安全停止 Guardrail Stop]
这套循环把 AI 从“回答问题的聊天窗口”推进到“能执行任务的自动化单元”。不过,循环一旦跑起来,也会带来新的工程问题:什么时候该停,失败信息怎么复用,多个 Agent 如何分工,记忆给多大会失焦,怎样防止一本正经地编造事实,成本如何控制。
这些问题都不能只靠“写好 Prompt”解决。
Agent Loop 解决的是什么问题
一次性 Prompt 的典型流程是:
sequenceDiagram
participant 人
participant AI
人->>AI: 说明任务
AI-->>人: 给出结果
人->>AI: 补充错误、日志、要求
AI-->>人: 修改结果
人->>AI: 再补充约束
AI-->>人: 再修改
这种模式的问题很明显:上下文靠人复制,错误靠人转述,任务是否完成靠人判断。只要任务链条稍长,协作成本就会迅速上升。
Agent Loop 把这些人工动作工程化:
| 环节 | 一次性 Prompt | Agent Loop |
|---|---|---|
| 目标表达 | 人写一段自然语言 | 目标 + 可验证完成条件 |
| 上下文 | 人手动粘贴 | 根据当前状态动态组装 |
| 工具调用 | AI 建议,人执行 | Agent 按权限调用工具 |
| 失败处理 | 人重新描述错误 | 错误日志进入下一轮输入 |
| 验收方式 | 人主观判断 | 测试、规则、检查器共同判断 |
| 停止方式 | 人觉得差不多 | 满足条件、超预算或无进展停止 |
所以,Agent Loop 的工程核心可以概括成一句话:
不要只设计“AI 应该说什么”,要设计“AI 如何持续获得状态、采取动作、利用反馈,并在正确的时间停下来”。
Agent Loop 的基本组成
一个可落地的 Agent Loop 通常包含七类组件。
| 组件 | 作用 | 典型实现 |
|---|---|---|
| Automations | 自动触发循环 | 定时任务、Webhook、CI 事件、队列消息 |
| Worktrees | 隔离执行环境 | Git worktree、临时目录、容器、沙箱 |
| Skills | 可复用操作手册 | Markdown 规程、命令模板、代码生成规范 |
| Plugins / Connectors | 外部工具连接 | GitHub、Slack、MCP(模型上下文协议)、数据库、浏览器 |
| Sub-agents | 分工或验收 | 生成器、审查器、事实检查器、测试修复器 |
| Memory | 外挂记忆 | Markdown、向量库、关系数据库、事件日志 |
| Guardrails | 资源与行为护栏 | 最大轮数、预算上限、权限控制、无进展检测 |
这些组件不是越多越好。一个小型代码修复 Agent,可能只需要目标、文件读写工具、测试命令、最大轮数和停止条件;一个长期运行的研发助手,才需要完整的记忆、技能沉淀、多 Agent 验收和成本控制。
停止条件:先定义“什么叫完成”
Agent Loop 最容易失控的地方,是目标说得太软。
“把代码改好”不是一个好的停止条件,因为模型可能把“看起来合理”误认为“已经完成”。更稳的写法是:
目标:
修复用户登录接口在空密码输入时返回 500 的问题。
停止条件:
1. 新增一个能复现该问题的测试;
2. 修复后该测试通过;
3. 原有登录相关测试全部通过;
4. 修改范围不包含无关模块。
停止条件要尽量具备三个特征:
| 特征 | 说明 | 示例 |
|---|---|---|
| 可观察 | Agent 能拿到结果 | 测试输出、Lint 结果、HTTP 状态码 |
| 可判断 | 不依赖模糊感受 | exit code 为 0、断言通过 |
| 可收敛 | 不会无限追求更好 | 最多修改 N 个文件、最多运行 M 轮 |
代码、数据处理、构建发布这类任务,通常都能写出硬停止条件。麻烦的是软目标,例如“写得更清楚”“方案更稳妥”“周报让管理者满意”。这种目标很难完全自动验收,可以用“分级完成”降低误判风险。
| 目标类型 | 停止条件写法 | 适合做法 |
|---|---|---|
| 硬目标 | 测试、命令、断言通过 | 自动停止 |
| 半软目标 | 检查清单全部满足 | 自动初筛 + 人工确认 |
| 软目标 | 明确不可自动验证的部分 | 输出置信等级和待确认项 |
例如,让 Agent 做方案评审时,不要让它简单说“方案可行”,而是要求它输出:
结论等级:
- 可验证事实:哪些信息来自代码、日志、数据或文档;
- 推理判断:哪些结论是基于工程经验推断;
- 待确认问题:哪些点需要人或外部系统确认。
这样不能把软目标变成硬目标,但能防止 Agent 假装自己已经完成了无法验证的判断。
Context 不是写出来的,而是组装出来的
传统 Prompt 是一段固定文字。Agent Loop 中的 Context(上下文)更像一个动态构建结果:每一轮都根据当前状态重新生成。
一个代码修复循环的上下文可能包含:
- 用户目标
- 当前任务状态
- 最近一次失败日志
- 本轮允许调用的工具
- 相关文件摘要
- 已尝试过的方案
- 停止条件
- 安全约束
动态上下文的价值在于,Agent 不需要靠“记住所有对话”工作,而是每一轮都拿到当前最相关的信息。
flowchart LR
A[任务目标] --> F[Context Builder]
B[文件树摘要] --> F
C[失败日志] --> F
D[历史尝试] --> F
E[相关记忆] --> F
F --> G[下一轮 Prompt]
G --> H[Agent 动作]
H --> I[新状态]
I --> F
Context Builder(上下文构造器)是 Agent Loop 的核心部件之一。它决定了模型看到什么、不看到什么,以及以什么顺序看到。
一个常见原则是:稳定规则放前面,动态状态放中间,最新错误放后面。因为最新错误通常最影响下一步动作,而稳定规则负责约束行为边界。
[角色与边界]
你是代码修复 Agent,只能修改与当前 bug 相关的文件。
[任务目标]
修复登录接口空密码输入返回 500 的问题。
[停止条件]
新增复现测试;修复后所有登录测试通过。
[当前状态]
已新增测试 test_empty_password_returns_400,目前失败。
[最近失败日志]
AssertionError: expected 400, got 500
[允许动作]
读取文件、修改文件、运行指定测试。
Context 不是越大越好。塞入太多历史记录会让模型失焦,甚至被无关信息带偏。工程上更可靠的做法,是按相关性、时间和层级筛选。
失败是下一轮输入,不是流程终点
在普通程序里,错误通常意味着流程停止。在 Agent Loop 里,错误更像新的观测数据。
比如测试失败后,不应该只告诉 Agent“继续修”,而应该把结构化失败信息放回下一轮:
{
"last_action": "run_tests",
"command": "pytest tests/test_login.py",
"exit_code": 1,
"error_summary": "空密码输入时接口返回 500,而不是 400",
"stack_trace": "...",
"changed_files": [
"app/auth.py",
"tests/test_login.py"
]
}
这样做有两个好处:
- Agent 能基于真实反馈修正,而不是凭想象继续改。
- 循环系统可以判断是否“有进展”,避免反复尝试同一种错误方案。
无进展检测可以非常简单。例如连续三轮测试失败原因相同、修改文件相同、错误栈相同,就触发停止,把问题交给人处理。
flowchart TD
A[执行动作] --> B[得到反馈]
B --> C{反馈是否变化}
C -- 有变化 --> D[继续循环]
C -- 无变化 --> E[记录重复失败]
E --> F{达到重复阈值}
F -- 否 --> D
F -- 是 --> G[停止并请求人工介入]
Agent Loop 不应该追求永远自动解决问题。稳定的系统要能识别“继续烧 token 也没意义”的时刻。
多 Agent 编排的六种拓扑
多 Agent 不是简单地多开几个模型实例。真正重要的是拓扑,也就是它们如何分工、通信和合并结果。
| 拓扑 | 工作方式 | 适合场景 | 主要风险 |
|---|---|---|---|
| 顺序流水线 | A 的输出交给 B,B 交给 C | 文档生成、数据清洗、发布流程 | 前一步错误会传染到后面 |
| 协调者-工作者 | 一个协调者拆任务,多个工作者执行 | 大任务拆解、批量修改 | 协调者判断错会整体跑偏 |
| 扇出合并 | 多个 Agent 并行处理,再合并 | 方案征集、代码审查、候选生成 | 合并器可能偏向表达更自信的一方 |
| 生成-验证 | 一个生成,一个检查 | 代码生成、事实核查、格式审查 | 同模型盲区会重合 |
| 共享状态 | 多个 Agent 读写同一任务状态 | 长期项目、复杂协作 | 状态污染、覆盖、冲突 |
| 辩论对抗 | 正反双方互相挑战 | 风险分析、架构取舍 | 可能一起走向错误共识 |
用图表示,常见的“生成-验证”拓扑是这样:
flowchart LR
A[任务目标] --> B[Maker 生成方案]
B --> C[Checker 检查方案]
C --> D{是否通过}
D -- 通过 --> E[输出]
D -- 不通过 --> F[反馈问题]
F --> B
Maker-Checker(生成者-检查者)模式听起来很稳,但有一个隐蔽问题:如果两个 Agent 使用同一个底层模型,它们可能共享同样的盲区。生成者看不出的 off-by-one 错误,检查者也可能看不出来。
更稳的验收方式应该尽量引入异质信号:
| 检查对象 | 更可靠的 Checker |
|---|---|
| 代码正确性 | 单元测试、属性测试、静态分析 |
| 链接真实性 | HTTP 请求、搜索接口、白名单源 |
| 数据结论 | SQL 查询、日志、指标平台 |
| 安全边界 | 规则引擎、权限系统、人工审批 |
| 文案质量 | 多 Agent 评审 + 人工终审 |
LLM 适合做语义审查、完整性检查和风险枚举,但不应该独自承担所有验收职责。只要能用程序验证,就优先交给程序。
记忆:分层,而不是全塞进去
Memory(记忆)用于突破上下文窗口限制,让 Agent 能利用长期信息。问题是,记忆太小没用,记忆太大会让 Agent 被历史噪声干扰。
一种实用设计是三层记忆:
| 层级 | 内容 | 用途 |
|---|---|---|
| L0 | 一句话摘要 | 快速判断是否相关 |
| L1 | 几条关键结论 | 放入常规上下文 |
| L2 | 完整记录 | 只有高相关时展开 |
例如一次事故复盘可以这样存:
# memory/login-empty-password-500.md
## L0
登录接口遇到空密码曾因未校验输入触发 500,正确行为是返回 400。
## L1
- 问题出在 auth service 调用 hash 前没有检查空字符串。
- 测试用例应覆盖空密码、缺失密码、正常密码三类输入。
- 修复时不要改动 token 生成逻辑。
## L2
完整日志、提交 diff、测试输出、复盘记录……
检索时先看 L0,相关再展开 L1,只有需要证据时才读取 L2。
flowchart TD
A[当前任务] --> B[检索 L0 摘要]
B --> C{是否相关}
C -- 否 --> D[忽略]
C -- 是 --> E[读取 L1 结论]
E --> F{是否需要证据}
F -- 否 --> G[放入上下文]
F -- 是 --> H[展开 L2 全文]
H --> G
记忆还需要治理,否则会变成污染源。错误结论、过时方案、一次性临时信息如果长期保留,会让 Agent 在未来任务中反复踩坑。
记忆系统至少要记录四类元数据:
| 元数据 | 作用 |
|---|---|
| 来源 | 这条记忆来自日志、代码、人工输入还是模型推断 |
| 时间 | 判断是否可能过期 |
| 置信等级 | 区分事实、推断和待确认信息 |
| 适用范围 | 防止跨项目、跨版本误用 |
Guardrails:资源护栏和认知护栏要分开
Guardrails(护栏)不只是不让 Agent 做危险操作,也包括防止它无限循环、烧光预算、污染记忆、编造事实。
可以把护栏分成两类:
| 类型 | 解决的问题 | 适合放在哪里 |
|---|---|---|
| 资源类护栏 | 成本、轮数、权限、超时 | Loop 框架底层 |
| 认知类护栏 | 胡说、失忆、错误记忆、虚假引用 | 可插拔治理层 |
资源类护栏应该尽量写死,因为它们是系统安全边界。例如最大循环次数、最大 token 预算、禁止访问的目录、禁止执行的命令,都不应该依赖 Agent 自觉遵守。
认知类护栏变化更快,适合做成可迭代的规则层。例如发布前检查清单、事实核查策略、记忆写入审批、失败转技能规则,都可能随着业务变化调整。
一个最小可用的护栏配置可以长这样:
loop:
max_iterations: 8
max_tokens: 80000
timeout_minutes: 20
permissions:
allow:
- read_files
- edit_files
- run_tests
deny:
- delete_repository
- access_secrets
- deploy_production
review:
require_tests: true
require_source_check_for_external_claims: true
require_human_approval_for_memory_write: true
这里的重点不是 YAML 格式,而是把“Agent 可以做什么、不能做什么、什么时候必须停”变成系统配置,而不是藏在一段自然语言里。
治理层:让 Loop 跑得稳一点
Agent Loop 解决的是“让 Agent 自己转起来”。但循环跑起来之后,还需要一层治理机制处理三个问题:
- 不胡说:外部事实、链接、数据结论必须可核查。
- 不失忆:关键经验能沉淀下来,并在需要时被检索。
- 不重复踩坑:失败要转成可复用规则,而不是只存在一次对话里。
可以把这层称为轻量治理层。它不替代 Agent 框架,也不替代工具系统,只是在循环外面包一层白盒约束。
flowchart TD
A[Agent Loop] --> B[执行任务]
B --> C[生成结果]
C --> D[治理层检查]
D --> E{是否通过}
E -- 是 --> F[发布/提交]
E -- 否 --> G[返回问题清单]
G --> A
D --> H[事实核查]
D --> I[记忆检索]
D --> J[失败转技能]
轻量治理层可以包含三个模块。
| 模块 | 作用 | 典型规则 |
|---|---|---|
| 白盒记忆治理 | 管理长期经验 | 记忆分层、来源标注、写入审批 |
| 发布前自检 | 防止错误输出 | 链接检查、数据出处、未验证声明标记 |
| 失败转技能 | 减少重复错误 | 每次失败沉淀一条可复用规则 |
例如,发布前自检可以要求 Agent 在输出前生成一份检查结果:
## 发布前检查
- 外部链接:已逐个访问验证 / 不涉及
- 数字和指标:已标注来源 / 不涉及
- 事实性结论:有证据支持 / 已标为推断
- 未验证内容:已列入待确认项
- 记忆写入:需要人工确认 / 不写入
这类检查不能保证完全正确,但能显著降低“模型把推断包装成事实”的概率。
软目标的处理:不要把评分器当真理
很多团队会尝试让另一个大语言模型充当 Judge(评判器),给结果打分,超过阈值就停止。这个方法能用,但不能过度信任。
LLM Judge 的问题在于评分可能漂移。同一份输出,在不同时间、不同采样参数、不同上下文下,分数可能变化很大。它更适合做排序、挑刺和辅助判断,不适合作为唯一停止条件。
更稳的做法是把软目标拆成检查清单:
任务:改写一份技术方案,让评审更容易理解。
可检查条件:
1. 是否说明背景问题;
2. 是否列出核心方案;
3. 是否给出架构图;
4. 是否说明风险和取舍;
5. 是否标出待确认事项;
6. 是否避免无来源的性能承诺。
然后让 Judge 只判断这些具体项,而不是判断“好不好”。
| 不稳定写法 | 更稳写法 |
|---|---|
| 给这份方案打 0 到 1 分,超过 0.8 就通过 | 检查是否包含背景、方案、风险、证据、待确认项 |
| 判断这段文字是否吸引人 | 判断标题是否具体、段落是否有信息增量、是否存在空话 |
| 判断方案是否值得做 | 列出收益证据、成本假设、风险、不可验证部分 |
软目标不能完全自动化时,就诚实地保留人工确认点。自动化系统最危险的状态,不是“不知道”,而是“不知道自己不知道”。
防止 AI 一本正经地胡说
Agent Loop 自动化程度越高,事实错误的危害越大。一次聊天中的幻觉可能只是一句错话;一个自动循环中的幻觉可能进入文档、代码注释、周报、配置甚至记忆库。
事实核查要尽量外部化,不要只让模型自查。
flowchart LR
A[Agent 生成结论] --> B{是否包含外部事实}
B -- 否 --> C[进入普通审查]
B -- 是 --> D[检索或访问来源]
D --> E{来源是否存在}
E -- 否 --> F[标记为不可用]
E -- 是 --> G{来源是否支持结论}
G -- 是 --> H[保留并附来源]
G -- 否 --> I[降级为推断或删除]
对外部事实可以使用三种策略:
| 策略 | 做法 | 适合场景 |
|---|---|---|
| 强制来源 | 每个事实性结论必须有来源 | 技术文档、研究报告、对外材料 |
| 白名单来源 | 只允许引用可信域名或内部系统 | 合规要求高的场景 |
| 结构化输出 | 把事实、推断、建议分字段输出 | 方案评审、事故分析、周报 |
例如输出可以强制分成:
{
"verified_facts": [
{
"claim": "登录接口在空密码输入时返回 500",
"source": "tests/test_login.py::test_empty_password"
}
],
"inferences": [
"问题可能来自输入校验缺失"
],
"unknowns": [
"生产环境是否存在相同错误还需要查看日志"
]
}
这样即使模型仍然可能出错,错误也更容易被发现和拦截。
成本控制:多 Agent 和长循环必须精打细算
Agent Loop 的成本主要来自三件事:循环轮数、上下文长度、模型数量。多 Agent 并行后,成本会按倍数上升;如果每个 Agent 都带着长上下文反复推理,token 消耗会非常快。
成本优化可以从四层入手。
| 层级 | 优化方式 | 示例 |
|---|---|---|
| 模型分级 | 简单任务用便宜模型,关键判断用强模型 | 路由、摘要、格式检查用小模型 |
| 上下文压缩 | 只传必要信息 | 传错误摘要而不是完整日志 |
| 中间结果缓存 | 不把所有中间产物塞回 Prompt | 文件、变量、数据库存储中间结果 |
| 循环限额 | 控制最大轮数和无进展重试 | 连续三轮无变化即停止 |
一个常见模型分级结构如下:
flowchart TD
A[任务进入] --> B[便宜模型路由]
B --> C{任务类型}
C -- 简单格式处理 --> D[小模型执行]
C -- 代码修改 --> E[强模型执行]
C -- 风险判断 --> F[强模型 + 规则检查]
D --> G[轻量检查]
E --> H[测试验证]
F --> I[人工确认]
不要把所有东西都放进 Prompt。中间产物如果能用文件、数据库、变量保存,就不要每一轮都重复发送。Prompt 里只放当前决策需要的信息。
例如:
不要传:
- 完整构建日志 20000 行
- 所有历史 diff
- 全部项目文件树
改成传:
- 失败命令
- 失败测试名
- 最相关的 30 行错误栈
- 最近一次 diff 摘要
- 可按需读取的文件路径
成本控制不是最后才做的优化项,而是 Loop 设计的一部分。没有预算边界的 Agent Loop,很容易从“自动化助手”变成“自动烧钱脚本”。
一个最小可用的 Agent Loop 模板
把前面的设计合起来,一个最小可用的代码修复 Loop 可以这样组织:
objective: 修复登录接口空密码返回 500 的问题
stopping_conditions:
- 新增失败测试复现该问题
- 修复后该测试通过
- 登录相关测试全部通过
- 未修改无关模块
context_builder:
include:
- task_objective
- stopping_conditions
- current_diff_summary
- latest_test_failure
- relevant_memory_l1
exclude:
- unrelated_history
- full_logs_by_default
tools:
- read_file
- edit_file
- run_tests
guardrails:
max_iterations: 8
max_tokens: 80000
stop_if_same_failure_repeats: 3
review:
checker:
- unit_tests
- static_rules
- llm_review
require_human_approval:
- memory_write
- production_deploy
对应的执行流程:
flowchart TD
A[接收任务] --> B[生成复现测试]
B --> C[运行测试确认失败]
C --> D{是否复现}
D -- 否 --> E[重新分析问题]
D -- 是 --> F[修改实现]
F --> G[运行相关测试]
G --> H{测试是否通过}
H -- 否 --> I[把失败日志写入下一轮上下文]
I --> F
H -- 是 --> J[运行审查清单]
J --> K{是否通过}
K -- 否 --> L[返回问题清单继续修正]
L --> F
K -- 是 --> M[输出结果]
这个模板不复杂,但已经覆盖了 Agent Loop 最关键的工程点:目标明确、上下文动态、失败反馈、工具执行、测试验收、护栏停止。
实际落地时最容易踩的坑
| 坑 | 表现 | 处理方式 |
|---|---|---|
| 停止条件模糊 | Agent 自称完成,但结果不可验证 | 把目标改成测试、命令、清单或人工确认点 |
| Checker 同源盲区 | 两个 Agent 一起误判 | 引入测试、规则、外部检索或不同模型 |
| 记忆过载 | Agent 被无关历史带偏 | L0/L1/L2 分层检索,按相关性展开 |
| 护栏只写在 Prompt | Agent 忘记遵守或绕开 | 资源护栏下沉到框架配置 |
| 事实未核查 | 生成不存在的链接、数据、引用 | 外部检索、白名单来源、结构化事实字段 |
| 循环太长 | 成本上升但结果无进展 | 最大轮数、重复失败检测、人工介入 |
| 拓扑过度复杂 | 多 Agent 比单 Agent 更混乱 | 从单 Loop 开始,只在瓶颈明确时拆分 |
| 中间结果全进上下文 | token 快速膨胀 | 文件化、缓存化,只传摘要和索引 |
什么时候不适合用 Agent Loop
Agent Loop 不是所有任务的默认答案。以下场景直接用一次性 Prompt 或普通脚本更合适:
| 场景 | 更合适的方式 |
|---|---|
| 一次性小问题 | 直接问答 |
| 规则完全确定 | 普通脚本或工作流引擎 |
| 风险极高且难以回滚 | 人工执行,AI 只做辅助分析 |
| 缺少可观察反馈 | 先补日志、测试、指标 |
| 无法设置权限边界 | 不要让 Agent 自动执行 |
Agent Loop 适合的是“目标明确、过程可反馈、结果可验证、失败可修正”的任务。它不是让 AI 获得无限自由,而是把自由限制在一个可控循环里。
总结
Agent Loop 的核心能力,不是让 Prompt 更聪明,而是让 AI Agent 在工程约束下持续行动。一个稳定的 Loop 至少要回答六个问题:
- 目标是什么,什么叫完成;
- 每一轮上下文如何组装;
- Agent 可以调用哪些工具;
- 失败信息如何进入下一轮;
- 谁来验收结果;
- 超预算、无进展或高风险时如何停止。
只要这些问题没有设计清楚,多 Agent、长期记忆、自动化工具接得越多,系统越容易失控。反过来,如果停止条件、上下文、反馈、护栏和治理层都设计得足够清楚,Agent Loop 就能从“会聊天的模型”变成一个能稳定处理任务的工程系统。