主题
第 2 章:理解 Loop Engineering 的五个构建模块
本章解锁:你能识别 Loop Engineering 的五个构建模块和一个关键支撑(外部记忆),并评估自己的场景需要哪些模块。
预计时间:25 分钟
起点与环境
前置章节:第 1 章(五层演进全景图)
你已经知道 Loop Engineering 在五层中的位置:它在 Harness 之上,解决的是"一个 Agent 如何在没有人类持续干预的情况下,持续执行、自我纠错、跨时间段工作"。
现在的问题是:具体怎么做?Loop 不是一个抽象概念,它由一组可拆解的工程构建模块组成。
为什么现在做
你大概有过这样的经历:你让 agent 修一个 bug,它修好了。第二天你让它修另一个类似的 bug,它又从头开始——不记得昨天用了什么方法,不知道项目有什么约定,甚至不知道上次踩过什么坑。
或者这样的场景:你想让 agent 每天自动检查仓库里的新 issue,做初步分类,给简单问题打标签。这个任务本身不难,难的是让它持续、稳定、自主地跑起来。
这些场景暴露的是同一个问题:你需要的不是一个更好的 prompt,而是一个完整的执行循环系统。 Addy Osmani 把这个系统拆成了五个构建模块和一个关键支撑。让我们逐个看。
动手实践
2.1 五个构建模块 + 一个关键支撑
先看全貌:
构建模块 1:Automations(自动触发)
解决的问题:谁来启动 agent?什么时候启动?
在没有 Automation 的世界里,每次都是你手动打开终端、输入命令、等待结果。Automation 让 agent 按照时间表或事件自动启动。
具体例子:
- 每天早上 9 点自动扫描仓库中新增的 issue,做分类和优先级标记
- 每次 PR 被创建时自动运行代码审查
- 每周日自动检查所有依赖的安全漏洞
本质:Automation 是 Loop 的"启动器"。没有它,Loop 就需要人来按按钮,那就不是真正的循环了。
把上面第一个例子(每天扫描 issue)写成 GitHub Actions,大概就是这样:
yaml
# .github/workflows/issue-triage.yml — Automation 示例
name: Daily Issue Triage
on:
schedule:
- cron: "0 1 * * *" # 每天 UTC 01:00 触发
issues:
types: [opened] # 新 issue 也立即触发
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run triage agent
run: npx catpaw --prompt "扫描新 issue,按 bug/feature/question 分类并打标签"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}十几行 YAML 就把"人来按按钮"变成了"系统自动按按钮"。schedule 解决定时触发,issues.opened 解决事件触发——这就是 Automation 的两种基本模式。
构建模块 2:Worktrees(并行隔离)
解决的问题:多个 agent 任务同时跑,怎么不互相踩踏?
想象你有三个 agent 同时在修三个不同的 bug,它们都需要修改同一个仓库的代码。如果在同一个工作目录里操作,必然冲突。
Git worktree 解决了这个问题:同一个仓库可以同时签出多个工作树,每个 agent 在自己独立的工作树里操作,互不干扰。
具体例子:
repo/
├── .git/ # 共享的 git 数据
├── main-worktree/ # 主工作目录
├── agent-fix-auth/ # Agent 1 修认证 bug
├── agent-fix-pagination/ # Agent 2 修分页 bug
└── agent-update-deps/ # Agent 3 更新依赖本质:Worktree 让 Loop 支持并行。没有它,agent 只能串行处理任务。
实际操作只需要几行命令:
bash
# worktree 操作示例 — 为 agent 创建隔离的工作目录
git worktree add ../agent-fix-auth -b fix/auth-bug main
git worktree add ../agent-fix-paging -b fix/pagination main
git worktree add ../agent-update-deps -b chore/deps main
# 每个目录都是完整的工作树,agent 可以独立修改、提交,互不干扰
# 任务完成后清理:git worktree remove ../agent-fix-auth三条 git worktree add 就创建了三个并行工作空间。每个 agent 进入各自的目录后,就像在自己独享的仓库里工作一样。
构建模块 3:Skills(意图固化)
解决的问题:agent 每次冷启动都在猜你想要什么。
你第一次让 agent 写单元测试,它可能用了错误的测试框架、错误的目录结构、错误的命名约定。你纠正了。第二次它又犯同样的错。
Skill 把你的意图固化成一份文档(通常是 SKILL.md 或类似格式),agent 每次启动时读取它,就知道"这个项目用 Vitest 不用 Jest""测试文件放在 __tests__/ 目录""用 describe-it 风格"。
具体例子:
markdown
# 单元测试 Skill
## 框架
- 使用 Vitest,不要使用 Jest
- 配置文件在 vitest.config.ts
## 约定
- 测试文件放在同目录的 __tests__/ 子目录下
- 文件名格式:{被测模块名}.test.ts
- 使用 describe-it 风格
- Mock 使用 vi.mock(),不要使用手动 mock
## 运行
- 单文件:npx vitest run {文件路径}
- 全量:npx vitest run本质:Skill 是 Loop 的"长期记忆中的一种"——把需要反复告诉 agent 的事情固化下来,让它不再每次冷启动猜测。
构建模块 4:Plugins/Connectors(外部连接)
解决的问题:agent 只能操作本地文件和终端,无法触达外部系统。
一个有用的 agent loop 通常需要:读取 issue tracker 里的任务、查询数据库验证结果、调用 staging 环境的 API 测试、向 Slack 发送通知。这些都需要连接外部系统。
MCP(Model Context Protocol) 是当前连接外部系统的主流协议。通过 MCP server,agent 可以像调用本地工具一样访问外部服务。
具体例子:
json
{
"mcpServers": {
"github": {
"command": "mcp-server-github",
"env": { "GITHUB_TOKEN": "..." }
},
"database": {
"command": "mcp-server-postgres",
"env": { "DATABASE_URL": "..." }
},
"slack": {
"command": "mcp-server-slack",
"env": { "SLACK_BOT_TOKEN": "..." }
}
}
}本质:Plugin/Connector 是 Loop 的"手和眼"。没有它,agent 只能在自己的小沙箱里转圈。
构建模块 5:Sub-agents(角色分工)
解决的问题:一个 agent 又要写代码又要审查自己的代码,就像一个人既当运动员又当裁判。
Sub-agent 让不同角色分开:一个负责探索(explore),一个负责实现(implement),一个负责验证(verify)。
具体例子:
主 Agent(协调者)
├── explore-agent # 搜索相关代码、理解上下文
├── implement-agent # 根据探索结果编写代码
└── verify-agent # 运行测试、检查代码质量关键区别:sub-agent 和第 4-5 章要讲的 Graph 不一样。Sub-agent 是一个 loop 内部的角色分工,由主 agent 调度;Graph 是多个独立 agent 之间的协作结构。可以这样理解:sub-agent 是一个部门内的分工,Graph 是跨部门的协作。
关键支撑:External Memory(外部记忆)
解决的问题:模型在 run 之间会忘干净。
这是 Loop Engineering 中最容易被忽视、但最致命的一块。
LLM 没有持久记忆。每次 session 结束,它在这次 run 中学到的一切——代码结构的理解、已经做过的决策、踩过的坑——全部消失。如果 loop 需要跨多次 run 工作(几乎所有实际 loop 都需要),就必须有外部记忆。
记忆的形式:
- Markdown 文件:最简单,agent 自己读写。比如一个
MEMORY.md记录已完成的任务、关键决策、已知约束 - 结构化存储:JSON/YAML 文件、SQLite 数据库
- 项目管理工具:Linear board、GitHub issues 作为任务状态的外部记忆
- 专用记忆系统:一些 Agent 框架提供内置的记忆存储
具体例子——一个代码审查 loop 的记忆文件:
markdown
# Code Review Memory
## 已审查的 PR
- PR #142: 修复登录超时 - 已通过 (2026-07-18)
- PR #143: 添加搜索功能 - 打回,原因:缺少测试 (2026-07-18)
## 已知项目约定
- 所有 API 端点必须有集成测试
- 错误码使用枚举,不用魔数
- 日志格式:[时间] [级别] [模块] 消息
## 上次 Run 遇到的问题
- TypeScript 5.x 的 decorator 语法变了,不要用旧语法
- CI 环境没有 Redis,测试需要 mock模型在 run 之间会忘干净,记忆必须落盘。
— Addy Osmani, Loop Engineering 要点
落盘的最简实现不过二十来行 Python。下面的 read_memory / append_memory 就是一个可直接用的记忆读写模块:
python
# memory.py — External Memory 的最简实现
import json, os
from datetime import datetime
MEMORY_FILE = os.path.expanduser("~/agent-memory/memory.json")
def read_memory() -> list[dict]:
"""读取全部记忆条目,文件不存在则返回空列表。"""
if not os.path.exists(MEMORY_FILE):
return []
with open(MEMORY_FILE, "r") as f:
return json.load(f)
def append_memory(entry: str, tags: list[str] | None = None) -> None:
"""追加一条记忆,自动带上时间戳。"""
memories = read_memory()
memories.append({
"timestamp": datetime.now().isoformat(),
"content": entry,
"tags": tags or [],
})
os.makedirs(os.path.dirname(MEMORY_FILE), exist_ok=True)
with open(MEMORY_FILE, "w") as f:
json.dump(memories, f, ensure_ascii=False, indent=2)Agent 在每次 run 开始时调用 read_memory() 加载历史,结束时调用 append_memory() 写入新发现。文件格式是纯 JSON,人类也能直接打开查看——这正是"记忆落盘"最朴素的形态。
2.2 五个模块的协作关系
这五个模块不是彼此独立的清单,它们之间有明确的协作关系:
2.3 你的场景评估
你的实践任务:想一个你希望 agent 自动化的真实任务。然后填写下面的评估表。
| 模块 | 你的任务需要吗? | 具体怎么用? |
|---|---|---|
| Automations | 需要/不需要 | (描述触发方式) |
| Worktrees | 需要/不需要 | (描述是否需要并行) |
| Skills | 需要/不需要 | (描述需要固化的约定) |
| Plugins/Connectors | 需要/不需要 | (描述需要连接的外部系统) |
| Sub-agents | 需要/不需要 | (描述需要的角色分工) |
| External Memory | 需要/不需要 | (描述需要跨 run 记住什么) |
评估提示:
- 如果你的任务只跑一次,可能不需要 Automation 和 Memory
- 如果你的任务不涉及代码仓库,可能不需要 Worktrees
- 几乎所有任务都需要 Skills(否则 agent 每次都在猜)
- 如果你的任务需要读取外部数据,就需要 Plugins
2.4 Boris Cherny 的范式转换
回到 Boris Cherny 那句话:
"I don't prompt Claude anymore. I have loops running that prompt Claude."
这句话值得仔细品味。注意他的用词:loops running that prompt Claude。不是他在 prompt Claude,而是 loop 在 prompt Claude。他退到了"设计 loop"的位置,loop 负责"prompt 模型"。
这就是 Loop Engineering 的核心范式转换:你不再是模型的用户,你是系统的设计者。 系统使用模型,你设计系统。
把五个构建模块连起来看,Boris Cherny 的 loop 大概是这样的:
- Automation 按时间表触发 agent
- Agent 启动后从 Memory 读取上次状态
- 根据 Skill 知道该做什么、怎么做
- 通过 Plugin 获取外部信息
- 分派 Sub-agent 执行具体任务
- 把结果写回 Memory
- 等待下次触发
他按一下按钮的动作变成了:设计好这七步,让系统自己转。
理解检查
- 区分题:Skill 和 External Memory 都是"让 agent 知道一些事情"的机制。它们的关键区别是什么?
- 设计题:你要设计一个 agent loop,每天自动检查 npm 依赖是否有安全漏洞,有的话自动创建 PR 修复。列出你需要哪些构建模块,每个模块的具体配置。
- 判断题:一个 loop 使用了 Automation、Skill 和 Plugin,但没有 External Memory。它可能会遇到什么问题?
参考答案:
Skill 是静态的、预先定义的,记录的是"做事的约定"(用什么框架、文件放哪里),通常由人类编写和维护。Memory 是动态的、运行时产生的,记录的是"做过什么、发现了什么",由 agent 在每次 run 中读写。Skill 告诉 agent "怎么做",Memory 告诉 agent "做到哪了"。
需要的模块:
- Automation:每天定时触发(如每天早上 8 点),或在依赖更新事件时触发
- Skill:记录项目的依赖管理约定(用 npm 还是 pnpm、修复策略是 minor 自动升级还是都要人审、PR 模板格式)
- Plugin:GitHub MCP(创建 PR)、npm audit API(检查漏洞)
- Sub-agent:一个检查漏洞,一个尝试修复,一个运行测试验证修复有效
- Memory:记录已知漏洞和处理状态,避免重复创建 PR
- Worktree:如果有多个漏洞需要同时修复,每个在独立 worktree 中操作
没有 Memory,agent 每次 run 都是"失忆"状态。可能重复处理已经处理过的任务,可能重复犯已经纠正过的错误,可能不知道上次 run 中断在哪里。对于周期性运行的 loop,没有 Memory 几乎不可用。
发生了什么
这一章你拆解了 Loop Engineering 的内部结构。
核心概念回顾:
- Automations:让 loop 自动启动的触发器(定时/事件)
- Worktrees:让 loop 支持并行的隔离机制
- Skills:把执行约定固化,避免冷启动猜测
- Plugins/Connectors:通过 MCP 等协议连接外部系统
- Sub-agents:loop 内部的角色分工
- External Memory:跨 run 的状态持久化,是 loop 的生命线
- 范式转换:从"prompt 模型"到"设计 prompt 模型的系统"
常见误区
| 误区 | 纠正 |
|---|---|
| "有了 Automation 就是 Loop 了" | Automation 只是触发器。没有 Memory 和验证机制的 Automation,只是定时跑脚本,不是 loop。 |
| "Memory 用 LLM 的对话历史就行" | LLM 的对话历史在 session 结束后消失,而且有上下文窗口限制。Memory 必须是外部持久化的。 |
| "Sub-agent 越多越好" | 每增加一个 sub-agent 就增加一次模型调用和一次上下文传递。如果任务简单到一个 agent 能搞定,拆分只会增加延迟和出错机会。 |
| "Skill 写一次就够了" | 项目约定会变。Skill 需要和代码一样维护,否则会变成误导 agent 的过时文档。 |
| "这五个模块都是必须的" | 不是。具体需要哪些取决于你的场景。一个简单的一次性任务可能只需要 Skill。 |
本章检查点
完成以下清单后,你可以进入下一章:
- [ ] 我能列出五个构建模块和一个关键支撑的名称和作用
- [ ] 我能解释 Skill 和 Memory 的区别
- [ ] 我已经用评估表分析了一个真实场景
- [ ] 我理解为什么 Boris Cherny 说"I don't prompt Claude anymore"
下一章预告:你知道了 Loop 由哪些模块组成,但还没有真正设计过一个。下一章我们动手,从零设计一个 Agent Loop 的完整配置——包括骨架、触发方式、停止条件和 Maker-Checker 模式。