Clipping 微信公众号

Loop Engineering实操路径:从零构建你的第一个Loop

by 时光 原文 ↗
Created: 2026-06-16

公众号名称:时光的沙盒

作者名称:时光

发布时间:2026-06-16 21:15

实操路径:从零构建你的第一个Loop

“ Loop Engineering火了。Addy Osmani一条推文180万浏览,Boris Cherny说”我不再给Claude写提示词了,我的工作就是写循环”,Peter Steinheimer说”你不应该再给编程代理写提示词了,你应该设计循环来替你写提示词”。但看完这些,你的感受大概率是:懂了,然后呢?我的第一个Loop到底怎么建? 这篇文章不打算再给你讲一遍”为什么Loop很重要”,而是带你从零开始,亲手构建一个真实可跑的Loop,用一份规格文件、一份计划文件、一份进度文件和一个Bash脚本,让AI编程代理自动迭代,直到把一个小项目从零写到完成。读完这篇文章,你不仅理解Loop Engineering,你还拥有了一个可以立即运行的Loop。

目录

  • 一、为什么”懂了Loop”却”下不了手”

  • 二、我们要做什么:一个真实可跑的Loop项目

  • 三、环境准备

  • 3.1 macOS环境安装

  • 3.2 Linux环境安装

  • 3.3 Windows环境安装(WSL2)

  • 3.4 创建项目目录

  • 3.5 验证环境

  • 3.6 一个关键概念:权限模式

  • 四、第一步:写一份让AI能读懂的规格说明(spec.md)

  • 4.1 创建spec.md文件

  • 4.2 写入spec.md内容

  • 4.3 验证spec.md已正确创建

  • 4.4 为什么spec要这样写

  • 五、第二步:把大目标拆成可验证的里程碑(plan.md)

  • 5.1 创建plan.md文件

  • 5.2 写入plan.md内容

  • 5.3 验证plan.md已正确创建

  • 5.4 关于外层四反引号围栏

  • 5.5 为什么Milestone 1就要包含持久化

  • 5.6 plan.md的拆分原则

  • 六、第三步:创建进度追踪文件(progress.md)

  • 6.1 创建progress.md文件

  • 6.2 写入progress.md内容

  • 6.3 验证progress.md已正确创建

  • 6.4 progress.md的更新规则

  • 七、第四步:手动跑一轮,理解代理到底在做什么

  • 7.1 执行手动迭代命令

  • 7.2 逐段理解命令参数

  • 7.3 观察代理行为的5个关键指标

  • 7.4 检查手动迭代结果

  • 7.5 为什么要先手动跑

  • 八、第五步:写你的第一个Loop脚本

  • 8.1 创建loop.sh文件

  • 8.2 写入loop.sh内容

  • 8.3 给脚本添加执行权限

  • 8.4 验证loop.sh已正确创建

  • 8.5 脚本逐段解析

  • 九、第六步:跑起来,观察每一轮迭代

  • 9.1 启动前检查清单

  • 9.2 启动Loop

  • 9.3 预期输出示例

  • 9.4 每一轮迭代发生了什么

  • 9.5 如果某轮迭代卡住了怎么办

  • 9.6 停止Loop

  • 十、第七步:给Loop装上刹车

  • 10.1 刹车一:迭代上限(已有)

  • 10.2 刹车二:停滞检测

  • 10.3 刹车三:对齐检查点

  • 10.4 刹车四:验收测试

  • 10.5 终极版loop.sh:集成全部四刹车

  • 10.6 Loop完成后做什么

  • 十一、复盘:我的第一个Loop教会了我什么

  • 十二、从这里出发:更高级的Loop形态

  • 附录A:重置并重新开始

  • 附录B:常见问题

  • 附录C:快速参考卡

一、为什么”懂了Loop”却”下不了手”

过去两周,你的信息流里大概出现了这些内容:Addy Osmani的博文《Loop Engineering》、Boris Cherny在Lenny’s Newsletter的深度访谈、The New Stack的报道”构建Claude Code的Anthropic负责人说他已经放弃了提示词”。你读完了,点了收藏,甚至转发到了技术群。然后呢?

然后你打开Claude Code,输入了一句提示词,等它返回代码,修改提示词,再提交。和昨天一模一样。

这不是你的问题。几乎所有关于Loop Engineering的讨论都停留在”是什么”和”为什么”的层面,很少有人告诉你”怎么做”。Addy的原文在”实操路径”那一节给了五个步骤,但每个步骤只有两三段话:“写一个spec文件”、“写一个plan文件”、“写一个bash脚本”。然后呢?文件里写什么?脚本怎么写?跑起来是什么样?出了问题怎么办?这些关键细节全部缺失。

这就像有人告诉你”游泳就是手脚配合划水”,然后把你扔进深水区。理论上完全正确,实操上毫无帮助。

这篇文章要做的,就是补上这段从”懂了”到”做了”之间的空白。我不会再花5000字解释Loop Engineering是什么(如果你还不了解,Addy的原文是最佳入门),而是直接带你动手。下面这七个步骤,每一步都有完整的文件内容、逐行解析、以及你可能踩的坑。跟着做完,你就有了自己的第一个可运行的Loop。

二、我们要做什么:一个真实可跑的Loop项目

在写任何文件之前,先明确我们要Loop的东西是什么。

项目:一个Python命令行Todo工具。

为什么选它?三个原因。第一,足够简单,4个里程碑就能完成,不会让你在Loop还没跑通之前就陷入复杂度。第二,足够真实,有明确的输入输出和验收标准,代理可以自己验证。第三,足够通用,每个读者都能理解需求,不需要领域知识。

Loop的角色分工:

  • :写spec.md(规格说明)、plan.md(实施计划)、progress.md(进度追踪),然后启动Loop脚本

  • AI代理(Claude Code):每一轮读取这三个文件,完成当前里程碑,更新progress.md,等待下一轮

  • Loop脚本(loop.sh):自动调用代理、检测进度变化、在全部完成时停止

最终交付物: 一个todo.py文件,支持add、list、done、delete、clear五个命令,数据持久化到~/.todo.json

三、环境准备

你需要两样东西:一个终端和一个Claude Code CLI。下面按操作系统分别给出完整的安装步骤。

3.1 macOS环境安装

第一步:安装Node.js(如果还没有)

Claude Code需要Node.js 18+。检查你是否已安装:

node --version

如果输出类似v20.x.x或更高版本,跳过安装。如果提示”command not found”,需要安装Node.js。推荐使用nvm(Node Version Manager)安装:

# 安装nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# 重新加载shell配置(让nvm命令生效)
source ~/.zshrc

# 安装Node.js LTS版本
nvm install --lts

# 验证安装
node --version
npm --version

“ ⚠️ 如果source ~/.zshrc后nvm仍不可用,尝试关闭终端重新打开,或手动执行export NVM_DIR="$HOME/.nvm"[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

第二步:安装Claude Code

npm install -g @anthropic-ai/claude-code

安装完成后,验证:

claude --version

如果输出版本号,说明安装成功。

第三步:配置Anthropic API Key

Claude Code需要Anthropic API Key/国产模型API Key才能工作。有两种配置方式:

方式A:交互式登录(推荐)

直接运行claude,按提示在浏览器中完成Anthropic账号认证:

claude

首次运行会弹出浏览器让你登录。登录成功后,回到终端,按Ctrl+C退出交互模式即可。Key会自动保存到本地。

方式B:手动设置环境变量

如果你已有API Key,可以直接设置为环境变量。打开你的shell配置文件:

# 如果用zsh(macOS默认)
nano ~/.zshrc

在文件末尾添加一行:

export ANTHROPIC_API_KEY="sk-ant-xxxxx你的Key"

保存退出(nano中按Ctrl+O保存,Ctrl+X退出),然后重新加载:

source ~/.zshrc

“ ⚠️ API Key是敏感信息,不要提交到Git仓库。确保.gitignore中包含.env文件。

第四步:确认Python 3可用

macOS通常自带Python 3。验证:

python3 --version

如果输出Python 3.8.x或更高版本,可以继续。如果提示”command not found”,通过Homebrew安装:

# 先安装Homebrew(如果还没有)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 安装Python 3
brew install python3

3.2 Linux环境安装

第一步:安装Node.js

# 安装nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# 重新加载shell配置
source ~/.bashrc

# 安装Node.js LTS版本
nvm install --lts

# 验证
node --version

第二步:安装Claude Code

npm install -g @anthropic-ai/claude-code
claude --version

第三步:配置API Key

与macOS相同,参见3.1第三步。Linux默认shell配置文件是~/.bashrc

第四步:安装Python 3

# Ubuntu/Debian
sudo apt update && sudo apt install -y python3

# CentOS/RHEL
sudo yum install -y python3

# 验证
python3 --version

3.3 Windows环境安装(WSL2)

Windows用户需要通过WSL2(Windows Subsystem for Linux 2)来运行Claude Code,因为Claude Code CLI目前原生支持macOS和Linux。

第一步:安装WSL2

以管理员身份打开PowerShell(右键”开始”菜单 → “终端(管理员)”),运行:

wsl --install

这会自动安装WSL2和Ubuntu。安装完成后需要重启电脑。

重启后,会自动弹出Ubuntu终端窗口,按提示设置Linux用户名和密码。

“ ⚠️ 如果wsl --install失败,可能需要手动启用:

  1. 打开”控制面板” → “程序” → “启用或关闭Windows功能”

  2. 勾选”适用于Linux的Windows子系统”和”虚拟机平台”

  3. 重启电脑

  4. 在Microsoft Store搜索”Ubuntu”并安装

第二步:在WSL2中安装Node.js

打开Ubuntu终端(开始菜单搜索”Ubuntu”),执行:

# 安装nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# 重新加载
source ~/.bashrc

# 安装Node.js
nvm install --lts

# 验证
node --version

第三步:安装Claude Code并配置API Key

npm install -g @anthropic-ai/claude-code
claude --version

API Key配置同3.1第三步。在WSL2中编辑~/.bashrc

echo 'export ANTHROPIC_API_KEY="sk-ant-xxxxx你的Key"' >> ~/.bashrc
source ~/.bashrc

第四步:安装Python 3

WSL2的Ubuntu通常自带Python 3,验证:

python3 --version

如果没有:

sudo apt update && sudo apt install -y python3

“ 💡 Windows用户提示:后续所有命令都在WSL2的Ubuntu终端中执行,不是在PowerShell或CMD中。文件路径也使用Linux格式(如/home/你的用户名/my-first-loop),而不是Windows格式(如C:\Users\...)。你可以通过\\wsl$\Ubuntu\home\你的用户名\my-first-loop在Windows资源管理器中访问WSL文件。

3.4 创建项目目录

所有操作系统通用。在终端中执行:

mkdir my-first-loop
cd my-first-loop

这个目录就是Loop的工作区。接下来我们要在里面创建三个核心文件和一个脚本。

“ 💡 建议同时初始化Git仓库,方便后续追踪变更和回滚:

git init

这不是必须的,但强烈推荐。Loop运行过程中代理会修改文件,有了Git你可以随时查看diff和回滚。

3.5 验证环境

在项目目录下运行以下命令,逐条确认:

# 确认 claude 命令可用
claude --version

# 确认 Python 3 可用
python3 --version

# 确认当前在项目目录中
pwd

预期输出类似:

Claude Code v1.x.x
Python 3.x.x
/home/你的用户名/my-first-loop

三条命令都有正常输出,就可以继续了。如果任何一条报错,回到对应安装步骤排查。

3.6 一个关键概念:权限模式

这一步是大部分Loop教程回避的话题,但如果不讲清楚,你的Loop根本跑不起来。

Claude Code默认运行在沙箱权限模式下:代理可以读当前目录的文件,但写文件、执行shell命令、访问当前目录之外的路径(比如~/.todo.json),都需要用户授权。在交互式终端里,这表现为”Allow / Deny”弹窗。但在我们后面要用的-p(print)非交互模式下,没有人能点Allow,于是代理只能放弃写文件。

我实测过:不加权限标志直接跑loop,代理会卡在”我需要写todo.py的权限”这一步,永远做不出任何代码。

解决方案是给代理加上--dangerously-skip-permissions标志,让它跳过所有权限检查。这个标志只能在你完全信任的项目目录里用,因为它意味着代理可以无提示地修改任何文件、执行任何命令。我们这个Todo项目是安全的(代理只会在my-first-loop/目录和~/.todo.json上读写),但生产环境里请改用更细粒度的--permission-mode acceptEdits加上--add-dir显式列出允许访问的目录。

后面写loop.sh时会用到这个标志,先在心里有个数。

四、第一步:写一份让AI能读懂的规格说明(spec.md)

spec.md是Loop的”宪法”。它定义了项目要做什么、不做什么、以及最终验收标准。代理在每一轮迭代开始时都会读它,所以它必须精确、无歧义、自包含。

4.1 创建spec.md文件

有三种方式创建文件,选择你最顺手的一种:

方式A:终端heredoc命令(最快,推荐)

确保你在my-first-loop目录下,直接在终端中粘贴以下整段命令并回车:

cat > spec.md << 'SPEC_EOF'
(下方4.2节的内容粘贴在这里)
SPEC_EOF

“ ⚠️ 注意'SPEC_EOF'两边的单引号不能省略!省略后shell会尝试执行内容中的反引号命令、展开$变量等特殊字符,导致写入的文件内容被篡改。

方式B:使用nano编辑器

nano spec.md

这会打开nano编辑器。把4.2节的内容完整粘贴进去,然后按Ctrl+O保存(按回车确认文件名),按Ctrl+X退出。

方式C:使用VS Code

如果你安装了VS Code并且配置了code命令:

code spec.md

这会在VS Code中打开(或创建)spec.md文件。把4.2节的内容粘贴进去,按Ctrl+S保存。

“ 💡 如果code命令不可用,也可以先在VS Code中打开整个项目目录:code .,然后在侧边栏中右键新建文件spec.md

4.2 写入spec.md内容

将以下完整内容写入spec.md:

# Todo CLI — Project Specification

## Overview
A minimal command-line todo manager written in Python 3.
No external dependencies — only the Python standard library.

## Commands

### `python3 todo.py add "task text"`
- Adds a new task to the list.
- Prints: `Added task #N: task text` (N is the task number).
- Task numbers start at 1 and increment. Deleted task IDs are NOT reused.

### `python3 todo.py list`
- Lists all tasks, one per line.
- Format: `#N [ ] task text` for pending, `#N [x] task text` for done.
- If no tasks exist, prints: `No tasks yet. Add one with: python3 todo.py add "task text"`

### `python3 todo.py done `
- Marks task #id as completed.
- Prints: `Completed task #N: task text`
- If task is already done: `Task #N is already completed.`
- If ID not found: `Error: Task #N not found.`

### `python3 todo.py delete `
- Removes task #id from the list.
- Prints: `Deleted task #N: task text`
- Remaining tasks keep their original IDs (do NOT renumber).
- If ID not found: `Error: Task #N not found.`

### `python3 todo.py clear`
- Deletes all tasks.
- Prints: `Cleared N tasks.`

## Data Storage
- Tasks are persisted in `~/.todo.json`.
- JSON format: `{"next_id": N, "tasks": [task1, task2, ...]}`
  where `next_id` is the ID to assign to the next new task (only increments, never resets).
  Each task is `{"id": number, "text": string, "done": boolean}`.
- On startup, load from this file if it exists.
- After every modification, save to this file.
- If the file is missing or contains invalid JSON, start with
`{"next_id": 1, "tasks": []}`.
- Write atomically: write to a temp file first, then rename.

## Constraints
- Python 3.8+ only, no pip install needed.
- Single file: `todo.py`.
- Use `argparse` for CLI parsing.
- Use `os.path.expanduser` to resolve `~`.

如果你用方式A(heredoc),完整命令是这样的:

cat > spec.md << 'SPEC_EOF'
# Todo CLI — Project Specification

## Overview
A minimal command-line todo manager written in Python 3.
No external dependencies — only the Python standard library.

## Commands

### `python3 todo.py add "task text"`
- Adds a new task to the list.
- Prints: `Added task #N: task text` (N is the task number).
- Task numbers start at 1 and increment. Deleted task IDs are NOT reused.

### `python3 todo.py list`
- Lists all tasks, one per line.
- Format: `#N [ ] task text` for pending, `#N [x] task text` for done.
- If no tasks exist, prints: `No tasks yet. Add one with: python3 todo.py add "task text"`

### `python3 todo.py done `
- Marks task #id as completed.
- Prints: `Completed task #N: task text`
- If task is already done: `Task #N is already completed.`
- If ID not found: `Error: Task #N not found.`

### `python3 todo.py delete `
- Removes task #id from the list.
- Prints: `Deleted task #N: task text`
- Remaining tasks keep their original IDs (do NOT renumber).
- If ID not found: `Error: Task #N not found.`

### `python3 todo.py clear`
- Deletes all tasks.
- Prints: `Cleared N tasks.`

## Data Storage
- Tasks are persisted in `~/.todo.json`.
- JSON format: `{"next_id": N, "tasks": [task1, task2, ...]}`
where `next_id` is the ID to assign to the next new task (only increments, never resets).
  Each task is `{"id": number, "text": string, "done": boolean}`.
- On startup, load from this file if it exists.
- After every modification, save to this file.
- If the file is missing or contains invalid JSON, start with
  `{"next_id": 1, "tasks": []}`.
- Write atomically: write to a temp file first, then rename.

## Constraints
- Python 3.8+ only, no pip install needed.
- Single file: `todo.py`.
- Use `argparse` for CLI parsing.
- Use `os.path.expanduser` to resolve `~`.
SPEC_EOF

4.3 验证spec.md已正确创建

# 确认文件存在
ls -la spec.md

# 查看文件开头几行,确认内容正确
head -5 spec.md

预期输出:

# 第一条命令输出类似:
-rw-r--r-- 1 user staff 1234 Jun 16 10:00 spec.md

# 第二条命令输出:
# Todo CLI — Project Specification

## Overview
A minimal command-line todo manager written in Python 3.
No external dependencies — only the Python standard library.

如果ls -la显示文件大小为0,说明内容没有写入成功,回到4.1重新操作。

4.4 为什么spec要这样写

这份spec有几个关键设计决策,值得你理解清楚,因为它们直接影响Loop能不能跑通。

第一,每个命令的输出格式都精确指定了。 “Added task #N: task text”而不是”添加成功”。这不是吹毛求疵,而是给代理一个明确的验收标准。如果spec只说”添加一个任务”,代理可能输出”Task added successfully”,也可能输出”✅ Done”,你无法自动验证。精确的输出格式让代理可以自己运行命令、比对结果、判断是否完成。

第二,边界条件全部列出了。 “Task #N is already completed”、“Error: Task #N not found.”、空列表时的提示语。这些是代理最容易遗漏的地方,也是手动测试时最容易被忽略的地方。把它们写进spec,代理就没有借口跳过。

第三,数据存储的原子写入要求。 “write to a temp file first, then rename”这条约束看起来过度设计,但它是一个很好的教学点:spec不仅定义功能,还定义质量标准。如果你不写,代理可能直接json.dump覆盖文件,一旦中途崩溃,数据就丢了。

第四,约束部分明确排除了模糊空间。 “Python 3.8+ only”、“Single file: todo.py”、“Use argparse”。这些约束防止代理自作主张地引入额外依赖、拆分文件、或用sys.argv手动解析参数。约束越明确,代理的输出越可预测。

一个常见的错误是把spec写成人读的PRD,比如”用户应该能够管理待办事项”。这种写法对AI代理毫无用处,因为它无法从中推导出具体的实现和验收标准。spec应该像一份合同:精确、可验证、无歧义。

五、第二步:把大目标拆成可验证的里程碑(plan.md)

spec定义了”做什么”,plan定义”按什么顺序做”。plan的核心思想是:把一个大目标拆成一系列小里程碑,每个里程碑都有明确的验证命令。代理完成一个里程碑后,运行验证命令确认,然后推进到下一个。

5.1 创建plan.md文件

与spec.md相同,有三种方式:

方式A:终端heredoc命令

cat > plan.md << 'PLAN_EOF'
(下方5.2节的内容粘贴在这里)
PLAN_EOF

“ ⚠️ plan.md内容中包含反引号(`code` 标记)和路径等shell敏感字符。如果用不带引号的分隔符(<< PLAN_EOF),shell会尝试执行反引号里的命令、转换路径,导致写入的文件内容被篡改。所以分隔符必须加单引号'PLAN_EOF',让heredoc内容按字面值写入,shell完全不做解析。

方式B:使用nano编辑器

nano plan.md

粘贴5.2节内容,Ctrl+O保存,Ctrl+X退出。

方式C:使用VS Code

code plan.md

粘贴内容,Ctrl+S保存。

5.2 写入plan.md内容

将以下完整内容写入plan.md:

# Implementation Plan

## Milestone 1: Basic CLI with Add, List, and Simple Persistence

Create the minimum viable CLI that can add and list tasks, with basic
JSON file persistence so tasks survive between commands:

- Create `todo.py` with `argparse` setup for `add` and `list` subcommands
- `python3 todo.py add "task text"` adds a task and saves to `~/.todo.json`
- `python3 todo.py list` loads from `~/.todo.json` and prints all tasks
  in `#N [ ] task text` format
- JSON format: `{"next_id": N, "tasks": [...]}` where `next_id` tracks
  the next ID to assign (only increments, never resets — this ensures
  deleted IDs are never reused)
- On startup, load tasks from `~/.todo.json` if it exists;
  if missing or invalid JSON, start with `{"next_id": 1, "tasks": []}`
- After every add, save tasks to `~/.todo.json`
- Use `os.path.expanduser` to resolve `~`

Note: Do NOT implement done/delete/clear yet. Do NOT implement atomic
writes yet. Simple open/write/close is fine for this milestone.

**Verification:**
```bash
rm -f ~/.todo.json
python3 todo.py add "First task"
python3 todo.py add "Second task"
python3 todo.py list
# Expected output:
# #1 [ ] First task
# #2 [ ] Second task
python3 todo.py
# Expected: help message is printed
```

## Milestone 2: Robust Persistence

Make the file persistence production-ready:

- Write atomically: write to a temp file first, then use `os.replace()`
  to rename (prevents data corruption on crash)
- Handle corrupt/invalid JSON gracefully: if `~/.todo.json` contains
  invalid JSON, treat it as an empty list and log a warning
- Handle file permission errors gracefully

**Verification:**
```bash
# Test normal persistence
rm -f ~/.todo.json
python3 todo.py add "Persistent task"
python3 todo.py list
# Expected: "Persistent task" is listed

# Test corrupt file handling
echo "not valid json" > ~/.todo.json
python3 todo.py list
# Expected: treats as empty list, no crash
# (may print a warning about invalid JSON)

# Test that valid data still works after corrupt file was encountered
python3 todo.py add "New task after corrupt"
python3 todo.py list
# Expected: "New task after corrupt" is listed
```

## Milestone 3: Done and Delete Commands

Implement the `done` and `delete` subcommands:

- `python3 todo.py done ` marks a task as done, changes `[ ]` to `[x]`
  in list output, prints "Completed task #N: text"
- `python3 todo.py delete ` removes a task from the list,
  prints "Deleted task #N: text"
- If the ID does not exist, print "Error: Task #N not found."
- After delete, remaining tasks keep their original IDs (do not renumber)

**Verification:**
```bash
rm -f ~/.todo.json
python3 todo.py add "Task A"
python3 todo.py add "Task B"
python3 todo.py add "Task C"
python3 todo.py done 2
python3 todo.py list
# Expected: Task B shows [x], others show [ ]
python3 todo.py delete 1
python3 todo.py list
# Expected: Task A is gone, Task B and C remain with IDs #2 and #3
python3 todo.py done 999
# Expected: "Error: Task #999 not found."
```

## Milestone 4: Polish and Edge Cases

Handle all remaining edge cases from the spec:

- `python3 todo.py list` on empty list prints:
`No tasks yet. Add one with: python3 todo.py add "task text"`
- `python3 todo.py done ` on an already-done task prints:
`Task #N is already completed.`
- Add a `python3 todo.py clear` command that deletes all tasks
  and prints "Cleared N tasks."

**Verification:**
```bash
rm -f ~/.todo.json
python3 todo.py list
# Expected: "No tasks yet. Add one with: python3 todo.py add \"task text\""
python3 todo.py add "Test"
python3 todo.py done 1
python3 todo.py done 1
# Expected: "Task #1 is already completed."
python3 todo.py add "Another"
python3 todo.py clear
# Expected: "Cleared 2 tasks."
python3 todo.py list
# Expected: "No tasks yet. Add one with: python3 todo.py add \"task text\""
```

## Completion

When all four milestones are verified, the project is complete.
Update progress.md to set status to "ALL DONE".

如果你用方式A(heredoc),完整命令如下。注意这里heredoc分隔符用'PLAN_EOF'(带单引号),防止shell展开内容中的特殊字符:

cat > plan.md << 'PLAN_EOF'
(把上面从 # Implementation Plan 到最后的 ```` 全部粘贴在这里)
PLAN_EOF

5.3 验证plan.md已正确创建

# 确认文件存在且非空
ls -la plan.md

# 查看文件开头
head -3 plan.md

# 确认包含4个里程碑
grep "^## Milestone" plan.md

预期输出:

# ls -la 输出类似:
-rw-r--r-- 1 user staff 2345 Jun 16 10:05 plan.md

# head -3 输出:
# Implementation Plan

## Milestone 1: Basic CLI with Add, List, and Simple Persistence

# grep 输出4行:
## Milestone 1: Basic CLI with Add, List, and Simple Persistence
## Milestone 2: Robust Persistence
## Milestone 3: Done and Delete Commands
## Milestone 4: Polish and Edge Cases

如果grep只输出3行或更少,说明文件内容不完整,重新创建。

5.4 为什么Milestone 1就要包含持久化

你可能会问:为什么不在Milestone 1只做纯内存版本,把持久化留到Milestone 2?

因为CLI工具的每个命令都是独立进程。python3 todo.py add "First task"执行完毕后进程就退出了,内存中的数据随之消失。接下来执行python3 todo.py list时,是一个全新的进程,内存是空的。如果Milestone 1不包含持久化,验证命令根本跑不通:add添加的任务,list看不到。

这是设计里程碑时最容易犯的错误:把”增量开发”理解成了”做一半功能”。正确的做法是,每个里程碑都要产出一个可独立运行和验证的增量。Milestone 1的增量是”add + list + 基本持久化”,Milestone 2的增量是”把持久化做健壮”,Milestone 3的增量是”done + delete”,Milestone 4的增量是”边界情况补全”。

5.5 plan.md的拆分原则

拆分里程碑时,遵循三个原则:

  1. 每个里程碑可独立验证。 有明确的验证命令,代理运行后能判断成功还是失败。如果一个里程碑的验证依赖于下一个里程碑的功能,说明拆分有问题。

  2. 每个里程碑只做一件事。 Milestone 1只做add/list,Milestone 3只做done/delete。不要把多个不相关的功能塞进同一个里程碑,否则代理容易顾此失彼。

  3. 后面的里程碑不破坏前面的。 Milestone 3添加done/delete时,add/list的验证命令必须仍然通过。如果某个里程碑的改动导致前面的验证失败,代理应该自己发现并修复,而不是等你手动干预。

六、第三步:创建进度追踪文件(progress.md)

progress.md是Loop的”记分牌”。它记录当前做到哪个里程碑、哪些已完成、遇到了什么问题。Loop脚本通过读取这个文件来判断是否继续迭代。

6.1 创建progress.md文件

同样三种方式:

方式A:终端heredoc命令

cat > progress.md << 'PROGRESS_EOF'
(下方6.2节的内容粘贴在这里)
PROGRESS_EOF

方式B:使用nano编辑器

nano progress.md

粘贴6.2节内容,Ctrl+O保存,Ctrl+X退出。

方式C:使用VS Code

code progress.md

粘贴内容,Ctrl+S保存。

6.2 写入progress.md内容

将以下完整内容写入progress.md:

# Progress

## Current Status
Starting — no milestones completed yet.

## Completed Milestones
(none)

## Current Milestone: Milestone 1: Basic CLI with Add, List, and Simple Persistence

## Issues
(none)

## Notes
Starting fresh. Read spec.md and plan.md before beginning.

如果你用方式A(heredoc),完整命令:

cat > progress.md << 'PROGRESS_EOF'
# Progress

## Current Status
Starting — no milestones completed yet.

## Completed Milestones
(none)

## Current Milestone: Milestone 1: Basic CLI with Add, List, and Simple Persistence

## Issues
(none)

## Notes
Starting fresh. Read spec.md and plan.md before beginning.
PROGRESS_EOF

“ ⚠️ 关键提醒## Current Milestone: 这一行必须和里程碑名称写在同一行,不能换行!因为loop.sh脚本通过grep "^## Current Milestone:"来提取当前里程碑名称。如果你写成两行(标题一行、名称另一行),脚本将无法解析,Loop会失效。这个格式在后续代理更新progress.md时也必须保持。

6.3 验证progress.md已正确创建

# 确认文件存在
ls -la progress.md

# 查看完整内容(文件很短,可以直接看全部)
cat progress.md

# 关键:确认 Current Milestone 行格式正确
grep "^## Current Milestone:" progress.md

预期输出:

# grep 输出:
## Current Milestone: Milestone 1: Basic CLI with Add, List, and Simple Persistence

如果grep没有输出,说明格式不对(可能是## Current Milestone:和名称分成了两行),需要手动修正。

6.4 progress.md的更新规则

这个文件会在Loop的每一轮迭代中被代理更新。更新规则必须清晰:

  • Current Milestone:始终指向下一个要做的里程碑,且必须和## Current Milestone:写在同一行(例如## Current Milestone: Milestone 2: Robust Persistence)。这是loop.sh脚本解析进度的依赖,脚本通过grep匹配## Current Milestone:来提取当前里程碑名称。代理完成当前里程碑后,把这一行替换为下一个里程碑。

  • Completed Milestones:按顺序列出已完成的里程碑。代理每完成一个,就在这里追加一行。

  • Current Status:一句话描述当前状态。当所有里程碑完成时,设为ALL DONE,这是Loop脚本判断是否终止的信号。

  • Issues:记录遇到的问题。如果代理连续在同一里程碑失败,这里的问题描述就是你诊断原因的线索。

这三个文件,spec.md、plan.md、progress.md,构成了Loop的”状态三角”。spec定义目标(不变),plan定义路径(基本不变),progress记录进度(每轮更新)。代理在每一轮迭代开始时读取这三个文件,就能完整地理解”我要做什么”、“按什么顺序做”、“现在做到哪了”,不需要依赖任何上下文窗口中的记忆。

七、第四步:手动跑一轮,理解代理到底在做什么

在写自动化脚本之前,先手动跑一轮。这一步很重要:你需要亲眼看到代理如何读取文件、执行任务、更新进度,才能理解自动化脚本在做什么。

7.1 执行手动迭代命令

确保你在项目目录下(my-first-loop),然后运行以下命令。注意这是一整条命令,从claude到最后的引号闭合,全部复制粘贴到终端中回车执行

“ 💡 为什么把--dangerously-skip-permissions放在-p前面,而不是放在多行提示词之后? 实测发现:当权限标志紧跟在多行字符串的闭合引号"后面时,复制粘贴过程中某些终端会吃掉换行或把标志误解析为字符串的一部分,常见报错是claude: --dangerously-skip-permissions: command not foundunknown option。把全局标志写在-p之前,可以让它和claude位于同一物理行,避开长字符串结尾的解析陷阱。这也是Claude Code CLI的推荐写法:claude [options] -p "prompt"

claude --dangerously-skip-permissions -p "You are continuing a software project. Start by reading these three files in the current directory:

1. spec.md — The project specification (what to build and why)
2. plan.md — The implementation plan with milestones
3. progress.md — Current progress and status

Your task: Complete the current milestone described in progress.md.

Rules:
- Read ALL three files before writing any code.
- Implement ONLY the current milestone described in progress.md.
  Do NOT skip ahead to future milestones.
- After completing the milestone, run the verification commands
  from plan.md to confirm your work is correct.
- If verification fails, fix the issues and re-verify.
- After successful verification, update progress.md:
  - Add the completed milestone under '## Completed Milestones'
  - Change '## Current Milestone: ...' to the next milestone
    (keep it on the SAME line as the heading, e.g.
     '## Current Milestone: Milestone 2: Robust Persistence')
  - Update '## Current Status' to reflect progress
- If ALL milestones are complete, set ## Current Status to 'ALL DONE'
- CRITICAL — STOP AFTER ONE MILESTONE:
  Once you finish the current milestone and update progress.md, STOP.
  Do NOT begin the next milestone even if you have tool budget left.
  The loop will call you again for the next milestone. Stopping early
  after exactly one milestone is the correct behavior, not a failure.

Start by reading the three files now."

“ ⚠️ 如果终端提示”command not found: claude”,说明Claude Code没有安装或不在PATH中,回到3.1安装步骤。如果提示认证错误,说明API Key没有配置,回到3.1第三步。如果提示unknown option: --dangerously-skip-permissions,检查你的Claude Code版本(claude --version),老版本可能不支持该标志,运行npm update -g @anthropic-ai/claude-code升级。

7.2 逐段理解命令参数

这条命令很长,逐段拆解:

参数含义
claude调用Claude Code CLI
--dangerously-skip-permissions跳过权限检查。没有这个标志,代理在-p模式下无法写文件、无法执行shell命令(因为没有人能点”Allow”)。这是Loop能跑通的关键。放在-p前面,避免和长提示词的引号纠缠(详见7.1开头的解释)。
-p "..."print模式(非交互模式)。代理接收提示词,执行完毕后输出结果文本,不需要用户交互。这是Loop自动化的基础——没有这个标志,代理会进入交互式对话模式,需要你手动输入。
"You are continuing..."发送给代理的提示词。告诉代理:读取三个文件、完成当前这一个里程碑、验证、更新进度,然后停止(不能顺手做下一个)。这段提示词就是后续loop.sh中每轮迭代都会发送的指令。

7.3 观察代理行为的5个关键指标

代理开始工作后,注意观察以下5个方面:

  1. 代理是否读取了三个文件? 正常情况下,代理的第一步应该是读取spec.md、plan.md、progress.md。如果代理跳过了读取直接写代码,说明提示词约束不够强。

  2. 代理是否只做了当前里程碑? 当前是Milestone 1,代理应该只实现add和list,不应该提前做done/delete/clear。我们在7.1的提示词中加了”CRITICAL — STOP AFTER ONE MILESTONE”硬性约束,专门防止代理”热情过度”一口气把4个里程碑都做完。如果你试跑时代理仍然越界,依次检查:(a) 你复制的是不是本节给出的最新版提示词(早期版本约束较弱,容易一口气跑完);(b) plan.md中Milestone 1末尾的”Do NOT implement done/delete/clear yet”是否还在;(c) 把loop.sh里的MAX_TURNS_PER_ITERATION从25降到18-20,强制代理只能完成一个里程碑的代码量。

  3. 代理是否运行了验证命令? 代理写完代码后,应该执行plan.md中Milestone 1的验证命令(add两个任务、list确认输出)。如果代理跳过验证直接更新progress.md,这就是”幻觉性完成”。

  4. 代理是否正确更新了progress.md? 完成后,progress.md的## Current Milestone:应该变为Milestone 2: Robust Persistence## Completed Milestones下应该有Milestone 1。

  5. 代理的执行过程花了多长时间? 正常情况下,Milestone 1需要1-3分钟。如果超过5分钟还在跑,可能是代理卡住了。

7.4 检查手动迭代结果

代理执行完毕后,逐项检查:

# 1. 检查todo.py是否已创建
ls -la todo.py

# 2. 手动运行验证命令
rm -f ~/.todo.json
python3 todo.py add "First task"
python3 todo.py add "Second task"
python3 todo.py list

# 3. 检查progress.md是否已更新
cat progress.md

如果代理正确完成了Milestone 1,progress.md应该变成类似这样(具体措辞可能略有不同,但结构应该一致):

# Progress

## Current Status
Milestone 1 completed. Moving to Milestone 2.

## Completed Milestones
- Milestone 1: Basic CLI with Add, List, and Simple Persistence

## Current Milestone: Milestone 2: Robust Persistence

## Issues
(none)

## Notes
Milestone 1 verified successfully. Basic add/list with JSON persistence works.

如果代理没有正确更新progress.md(比如把Current Milestone写成了两行,或者忘了更新Completed Milestones),这就是你需要手动修正的地方。用nano或VS Code打开progress.md,按照6.4节的格式规则手动修正。修正后,再手动跑第二轮,看代理能否正确接续。

7.5 为什么要先手动跑

因为自动化脚本只是把你的手动操作重复执行。如果你不知道手动操作时会发生什么,自动化出了问题你就完全无法诊断。先手动跑一轮,理解代理的行为模式,再写脚本,这是Loop Engineering的”先走后跑”原则。

八、第五步:写你的第一个Loop脚本

现在你已经理解了代理的行为模式,可以写自动化脚本了。Loop脚本的核心逻辑很简单:循环调用代理,检查进度,决定是否继续。

8.1 创建loop.sh文件

同样三种方式:

方式A:终端heredoc命令

cat > loop.sh << 'LOOP_EOF'
(下方8.2节的内容粘贴在这里)
LOOP_EOF

“ ⚠️ loop.sh内容中包含大量$变量(如$AGENT_CMD$ITERATION等),heredoc分隔符必须加单引号'LOOP_EOF',否则shell会尝试展开这些变量,导致脚本内容被破坏。

方式B:使用nano编辑器

nano loop.sh

粘贴8.2节内容,Ctrl+O保存,Ctrl+X退出。

方式C:使用VS Code

code loop.sh

粘贴内容,Ctrl+S保存。

8.2 写入loop.sh内容

将以下完整内容写入loop.sh:

#!/usr/bin/env bash
set -euo pipefail

# ── Configuration ──────────────────────────────────────────────
MAX_ITERATIONS=8            # Safety cap: stop after this many iterations
AGENT_CMD="claude"          # The coding agent CLI to invoke
MAX_TURNS_PER_ITERATION=25  # Max agent turns per iteration
SPEC_FILE="spec.md"
PLAN_FILE="plan.md"
PROGRESS_FILE="progress.md"

# ── Colors (auto-disable when piped) ──────────────────────────
if [ -t 1 ]; then
    BOLD='\033[1m'  DIM='\033[2m'  GREEN='\033[32m'
    YELLOW='\033[33m'  RED='\033[31m'  RESET='\033[0m'
else
    BOLD=''  DIM=''  GREEN=''  YELLOW=''  RED=''  RESET=''
fi

# ── Pre-flight checks ─────────────────────────────────────────
echo -e "${BOLD}Pre-flight checks...${RESET}"

if ! command -v "$AGENT_CMD" &> /dev/null; then
    echo -e "${RED}Error: '$AGENT_CMD' not found.${RESET}"
    echo"Install it with: npm install -g @anthropic-ai/claude-code"
    exit 1
fi

for file in"$SPEC_FILE""$PLAN_FILE""$PROGRESS_FILE"; do
    if [ ! -f "$file" ]; then
        echo -e "${RED}Error: $file not found in $(pwd)${RESET}"
        exit 1
    fi
done

echo -e "${GREEN}All checks passed.${RESET} Starting loop..."
echo""

# ── Main loop ──────────────────────────────────────────────────
ITERATION=0

while [ $ITERATION -lt $MAX_ITERATIONS ]; do
    ITERATION=$((ITERATION + 1))

    # Read current milestone from progress file
    CURRENT_MILESTONE=$(grep "^## Current Milestone:""$PROGRESS_FILE" \
                        | head -1 \
                        | sed 's/^## Current Milestone: //' \
                        | sed 's/^[[:space:]]*//')

    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${BOLD}Iteration $ITERATION / $MAX_ITERATIONS${RESET}"
    echo -e "  Current: ${YELLOW}${CURRENT_MILESTONE}${RESET}"
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo""

    # The prompt sent to the agent every iteration
    PROMPT="You are continuing a software project. Start by reading these three files in the current directory:

1. ${SPEC_FILE} — The project specification (what to build and why)
2. ${PLAN_FILE} — The implementation plan with milestones
3. ${PROGRESS_FILE} — Current progress and status

Your task: Complete the current milestone described in ${PROGRESS_FILE}.

Rules:
- Read ALL three files before writing any code.
- Implement ONLY the current milestone described in ${PROGRESS_FILE}.
  Do NOT skip ahead to future milestones.
- After completing the milestone, run the verification commands
  from ${PLAN_FILE} to confirm your work is correct.
- If verification fails, fix the issues and re-verify.
- After successful verification, update ${PROGRESS_FILE}:
  - Add the completed milestone under '## Completed Milestones'
  - Change '## Current Milestone: ...' to the next milestone
    (keep it on the SAME line as the heading, e.g.
     '## Current Milestone: Milestone 2: Robust Persistence')
  - Update '## Current Status' to reflect progress
- If ALL milestones are complete, set ## Current Status to 'ALL DONE'
- CRITICAL — STOP AFTER ONE MILESTONE:
  Once you finish the current milestone and update progress.md, STOP.
  Do NOT begin the next milestone even if you have tool budget left.
  The loop will call you again for the next milestone. Stopping early
  after exactly one milestone is the correct behavior, not a failure.

Start by reading the three files now."

    echo -e "${DIM}Launching agent...${RESET}"
    echo""

    # Launch the agent.
    # --dangerously-skip-permissions is REQUIRED here: without it, the agent
    # cannot write todo.py or run the verification commands (which touch
    # ~/.todo.json outside the project dir) in non-interactive -p mode.
    # Only use this flag in a project directory you trust.
    $AGENT_CMD -p "$PROMPT" \
        --max-turns "$MAX_TURNS_PER_ITERATION" \
        --dangerously-skip-permissions \
        < /dev/null || true

    echo""
    echo -e "${DIM}Agent finished. Checking progress...${RESET}"

    # Check if all milestones are done
    if grep -q "ALL DONE""$PROGRESS_FILE"; then
        echo""
        echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${GREEN}${BOLD}All milestones completed!${RESET}"
        echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo""
        echo -e "Completed milestones:"
        grep -A 10 "^## Completed Milestones""$PROGRESS_FILE" | tail -n +2
        exit 0
    fi

    # Check if milestone changed
    NEW_MILESTONE=$(grep "^## Current Milestone:""$PROGRESS_FILE" \
                    | head -1 \
                    | sed 's/^## Current Milestone: //' \
                    | sed 's/^[[:space:]]*//')

    if [ "$NEW_MILESTONE" != "$CURRENT_MILESTONE" ]; then
        echo -e "${GREEN}Milestone completed: ${CURRENT_MILESTONE}${RESET}"
        echo -e "Next up: ${YELLOW}${NEW_MILESTONE}${RESET}"
    else
        echo -e "${YELLOW}Warning: Milestone did not change. The agent may have stalled.${RESET}"
        echo -e "${YELLOW}Check progress.md for issues. The loop will continue.${RESET}"
    fi

    echo""
done

# ── Safety cap reached ─────────────────────────────────────────
echo""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${RED}${BOLD}Reached maximum iterations ($MAX_ITERATIONS)${RESET}"
echo -e "Manual review needed. Check ${PROGRESS_FILE} for current status."
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
exit 1

8.3 给脚本添加执行权限

这是必须的步骤!新创建的.sh文件默认没有执行权限,直接运行会报”Permission denied”:

chmod +x loop.sh

验证权限已设置:

ls -la loop.sh

输出中应该包含x(执行权限),类似:-rwxr-xr-x

“ ⚠️ 如果跳过这一步,运行./loop.sh时会报错:bash: ./loop.sh: Permission denied。解决方法就是执行上面的chmod +x loop.sh

8.4 验证loop.sh已正确创建

# 确认文件存在且有执行权限
ls -la loop.sh

# 检查脚本开头是否正确
head -3 loop.sh

# 检查脚本是否包含关键部分
grep "MAX_ITERATIONS" loop.sh
grep "ALL DONE" loop.sh
grep "dangerously-skip-permissions" loop.sh

预期输出:

# head -3 输出:
#!/usr/bin/env bash
set -euo pipefail

# grep MAX_ITERATIONS 输出:
MAX_ITERATIONS=8            # Safety cap: stop after this many iterations

# grep ALL DONE 输出:
if grep -q "ALL DONE" "$PROGRESS_FILE"; then

# grep dangerously 输出:
        --dangerously-skip-permissions \

如果任何一条grep没有输出,说明脚本内容不完整,重新创建。

8.5 脚本逐段解析

配置段(前10行):所有可调参数集中在顶部,方便你根据项目调整。MAX_ITERATIONS=8是安全上限,4个里程碑最多8轮迭代,留了容错空间。MAX_TURNS_PER_ITERATION=25限制代理每轮最多25次工具调用,防止无限循环(每个里程碑的实际消耗大约在10-15次之间:读3个文件、写todo.py、跑5-10条验证命令、更新progress.md)。

颜色段:终端输出着色,但只在交互式终端中启用([ -t 1 ]检测stdout是不是TTY)。如果你把输出重定向到文件,颜色代码会自动关闭,不会污染日志。

预检段:在启动Loop之前,检查代理命令是否存在、三个核心文件是否都在。这是”快速失败”原则:与其在第三轮迭代时才发现spec.md不存在,不如一开始就报错。

主循环段:这是脚本的核心。每一轮迭代做四件事:读取当前里程碑、构造提示词、调用代理、检查进度变化。

几个容易忽略的细节:

  • $AGENT_CMD ... || true:确保即使代理返回非零退出码,脚本也不会终止。代理可能因为各种原因返回非零(比如上下文超限、网络抖动),但这不一定意味着Loop应该停止。

  • < /dev/null:把stdin接到/dev/null。Claude Code的-p模式如果检测不到stdin输入,会发出警告”no stdin data received in 3s”。这条重定向让代理立刻知道没有stdin数据,跳过3秒等待,每一轮迭代省下3秒。

  • --dangerously-skip-permissions这一行是Loop能不能跑通的关键。没有它,代理在-p模式下写不了todo.py、跑不了会写~/.todo.json的验证命令。请只在完全信任的项目目录里使用这个标志。

进度检测段:代理执行完毕后,脚本重新读取progress.md,先检查ALL DONE是否出现,再比较当前里程碑是否变化。如果里程碑变了,说明代理完成了;如果没变,说明代理可能卡住了,脚本会输出警告但继续下一轮。

九、第六步:跑起来,观察每一轮迭代

9.1 启动前检查清单

在启动Loop之前,确认以下5项全部就绪:

# 1. 确认在项目目录中
pwd
# 应输出:/home/你的用户名/my-first-loop

# 2. 确认三个核心文件都存在
ls -la spec.md plan.md progress.md

# 3. 确认loop.sh有执行权限
ls -la loop.sh | grep x

# 4. 确认claude命令可用
claude --version

# 5. 确认progress.md指向Milestone 1
grep "Current Milestone" progress.md

5项全部通过,就可以启动了。

9.2 启动Loop

cd my-first-loop
./loop.sh

“ 💡 如果你想把输出同时保存到日志文件(方便后续回看),可以用tee命令:

./loop.sh 2>&1 | tee loop.log

这样终端会实时显示输出,同时所有内容也会保存到loop.log文件中。

9.3 预期输出示例

你会看到类似这样的输出:

Pre-flight checks...
All checks passed. Starting loop...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 1 / 8
  Current: Milestone 1: Basic CLI with Add, List, and Simple Persistence
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Launching agent...

[代理开始工作:读取文件、创建todo.py、运行验证命令...]

Agent finished. Checking progress...
Milestone completed: Milestone 1: Basic CLI with Add, List, and Simple Persistence
Next up: Milestone 2: Robust Persistence

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 2 / 8
  Current: Milestone 2: Robust Persistence
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[代理继续工作...]

“ 关于[代理开始工作...]这块:在Claude Code的-p模式下,默认只能看到代理最后输出的文本摘要,看不到中间的Read/Write/Bash等工具调用。如果你想看代理每一步在干什么,给$AGENT_CMD调用加上--verbose标志,或改用--output-format stream-json输出原始事件流。教学场景下保持默认就行,等到Loop出问题再开verbose。

9.4 每一轮迭代发生了什么

Iteration 1:代理读取三个文件,理解当前任务是Milestone 1。它创建todo.py,实现add和list命令,加上基本的JSON文件读写。JSON格式为{"next_id": N, "tasks": [...]},其中next_id只增不减,确保删除的ID不会被重用。运行验证命令(add两个任务,list确认输出),通过后更新progress.md,把Current Milestone推进到Milestone 2。

Iteration 2:代理重新读取三个文件,理解当前任务是Milestone 2。它修改todo.py,把简单的文件写入替换为原子写入(tempfile + os.replace),添加对损坏JSON的容错处理。运行验证命令(包括故意写一个损坏的JSON文件),通过后推进到Milestone 3。

Iteration 3:代理添加done和delete命令。done命令标记任务为完成,delete命令删除任务。由于JSON中next_id只增不减,删除任务后新增的任务ID不会与已删除的ID冲突。运行验证命令(done、delete、错误ID测试),通过后推进到Milestone 4。

Iteration 4:代理处理边界情况:空列表提示、重复完成提示、clear命令。运行验证命令,全部通过后,将progress.md的Current Status设为ALL DONE。Loop脚本检测到ALL DONE,输出完成信息并退出。

9.5 如果某轮迭代卡住了怎么办

最常见的情况是代理没有正确更新progress.md。比如它完成了代码但忘了更新Current Milestone,或者把Current Milestone写成了两行导致脚本无法解析。这时Loop脚本会输出警告:

Warning: Milestone did not change. The agent may have stalled.
Check progress.md for issues. The loop will continue.

你不需要立即干预。下一轮迭代时,代理会重新读取progress.md,发现Current Milestone还是自己刚完成的那个,通常会意识到需要更新。如果连续两轮都没更新,手动编辑progress.md,把Current Milestone推进到下一个,然后让Loop继续。

另一个常见问题是代理跳过了验证步骤,直接更新了progress.md。Claude Code这类代理在--max-turns限制下偶尔会”幻觉性完成”——声称验证通过但其实没跑。如果你怀疑代理跳过了验证,手动运行plan.md中对应里程碑的验证命令,确认代码确实能跑通。下一节”装上刹车”会专门讲怎么用停滞检测把这个问题挡住。

还有一种相反的异常:代理一轮做了多个里程碑。 有时代理”热情过度”,在一轮迭代里把Milestone 1和Milestone 2(甚至全部4个)都做完,progress.md的Current Milestone直接跳到了Milestone 3或4。这听起来像好事,实际上破坏了Loop的核心节奏:每个里程碑本该是独立可验证、可回滚的增量。一轮做多个会让问题定位变难——如果Milestone 3有bug,你不知道是Milestone 1/2/3中哪一步引入的;代理也很难在跨里程碑的上下文里保持质量。

我们的提示词中已经加了”CRITICAL — STOP AFTER ONE MILESTONE”硬性约束来挡住这种情况。如果你试跑时代理仍然越界(比如progress.md从Milestone 1直接跳到Milestone 4),按下面的顺序排查:

  1. 确认你用的是本节给出的最新版提示词。 早期版本只写了”Do not skip ahead”这种弱约束,对Claude/Sonnet这类高主动性模型不够强。

  2. 降低MAX_TURNS_PER_ITERATION 从25降到18-20,让代理单轮的token预算只能覆盖一个里程碑(读3文件+写代码+跑验证+更新进度,正常10-15个turn)。预算一紧,代理就没”闲暇”顺手做下一个了。

  3. 检查plan.md中每个里程碑的Note是否清晰。 Milestone 1末尾必须保留”Do NOT implement done/delete/clear yet”这种显式禁止语。这是除了提示词之外的第二道防线。

  4. 如果以上都不管用,回退到手动模式(第七步)跑一轮。 手动模式下你能立即看到代理的越界行为,并在progress.md里手动把它推回正确的里程碑。确认提示词生效后再切回自动Loop。

9.6 停止Loop

如果Loop正在运行中,你想手动停止它:

  • **按Ctrl+C**:发送中断信号,Loop脚本会立即停止。代理当前正在执行的操作也会被终止。

  • 这不会影响已经完成的工作。已创建的todo.py和已更新的progress.md都会保留。

停止后,如果你想从断点继续,直接再次运行./loop.sh即可。脚本会读取progress.md中的当前里程碑,从那里继续。

十、第七步:给Loop装上刹车

上面的loop.sh能跑,但缺少安全机制。如果代理陷入死循环、持续失败、或者跑偏了方向,脚本会一直跑到MAX_ITERATIONS才停下来。这在玩具项目上无所谓,但在真实项目中,每一次迭代都在烧钱。

下面是四个关键刹车,建议你逐步加上。

10.1 刹车一:迭代上限(已有)

我们的脚本已经设置了MAX_ITERATIONS=8。这是最基础的刹车:无论发生什么,Loop不会超过8轮。对于4个里程碑的项目,8轮给了100%的容错空间(每个里程碑允许失败一次)。

调整建议:里程碑数量乘以2,就是合理的MAX_ITERATIONS。3个里程碑设6,5个里程碑设10。

除了MAX_ITERATIONS,Claude Code本身还支持--max-budget-usd标志,给单次调用设硬性美元上限。如果你的Loop跑在一个无人值守的服务器上,强烈建议在$AGENT_CMD调用里加上比如--max-budget-usd 5,这样即使逻辑出bug跑飞了,单个迭代最多烧5美元就强制停止。

10.2 刹车二:停滞检测

如果代理连续两轮都没能推进里程碑,很可能卡住了。这时应该停下来让你检查,而不是继续烧token。

在主循环外初始化计数器,并在else分支里递增和判断:

STALLED_COUNT=0  # 在 while 循环之前初始化

# 在迭代结束时的判断逻辑中:
if [ "$NEW_MILESTONE" != "$CURRENT_MILESTONE" ]; then
    echo -e "${GREEN}Milestone completed: ${CURRENT_MILESTONE}${RESET}"
    echo -e "Next up: ${YELLOW}${NEW_MILESTONE}${RESET}"
    STALLED_COUNT=0
else
    STALLED_COUNT=$((STALLED_COUNT + 1))
    echo -e "${YELLOW}Warning: Milestone did not change (stall count: ${STALLED_COUNT})${RESET}"
    if [ $STALLED_COUNT -ge 2 ]; then
        echo -e "${RED}Loop stalled for 2 consecutive iterations. Stopping for manual review.${RESET}"
        exit 1
    fi
fi

10.3 刹车三:对齐检查点

每完成两个里程碑,暂停Loop让你做一次快速审查。这不是因为不信任代理,而是因为代理的”认知投降”问题:当代理在早期里程碑犯了小错(比如输出格式略有偏差),它会在后续里程碑中基于这个错误继续构建,错误会像滚雪球一样越滚越大。

# 在迭代结束时加入:
if [ $((ITERATION % 2)) -eq 0 ]; then
    echo -e "${YELLOW}Alignment checkpoint after iteration ${ITERATION}.${RESET}"
    echo -e "${YELLOW}Press Enter to continue, or Ctrl+C to stop and review.${RESET}"
    read -r
fi

10.4 刹车四:验收测试

Loop完成后,运行一次完整的端到端验收测试,确认所有功能都按spec工作。这可以作为一个独立的脚本acceptance.sh

创建acceptance.sh文件:

cat > acceptance.sh << 'ACCEPTANCE_EOF'
#!/usr/bin/env bash
set -euo pipefail

echo"Running acceptance tests..."
PASS=0
FAIL=0

rm -f ~/.todo.json

# Test 1: Add tasks
if python3 todo.py add "Buy milk" | grep -q "Added task #1: Buy milk"; then
    echo"PASS: add task"; PASS=$((PASS+1))
else
    echo"FAIL: add task"; FAIL=$((FAIL+1))
fi
if python3 todo.py add "Read book" | grep -q "Added task #2: Read book"; then
    echo"PASS: add second task"; PASS=$((PASS+1))
else
    echo"FAIL: add second task"; FAIL=$((FAIL+1))
fi

# Test 2: List tasks
if python3 todo.py list | grep -q "#1 \[ \] Buy milk"; then
    echo"PASS: list pending task"; PASS=$((PASS+1))
else
    echo"FAIL: list pending task"; FAIL=$((FAIL+1))
fi
if python3 todo.py list | grep -q "#2 \[ \] Read book"; then
    echo"PASS: list second task"; PASS=$((PASS+1))
else
    echo"FAIL: list second task"; FAIL=$((FAIL+1))
fi

# Test 3: Complete a task
if python3 todo.py done 1 | grep -q "Completed task #1: Buy milk"; then
    echo"PASS: done task"; PASS=$((PASS+1))
else
    echo"FAIL: done task"; FAIL=$((FAIL+1))
fi
if python3 todo.py list | grep -q "#1 \[x\] Buy milk"; then
    echo"PASS: list done task"; PASS=$((PASS+1))
else
    echo"FAIL: list done task"; FAIL=$((FAIL+1))
fi

# Test 4: Already completed
if python3 todo.py done 1 | grep -q "already completed"; then
    echo"PASS: already completed"; PASS=$((PASS+1))
else
    echo"FAIL: already completed"; FAIL=$((FAIL+1))
fi

# Test 5: Delete a task
if python3 todo.py delete 2 | grep -q "Deleted task #2: Read book"; then
    echo"PASS: delete task"; PASS=$((PASS+1))
else
    echo"FAIL: delete task"; FAIL=$((FAIL+1))
fi

# Test 6: Task not found
if python3 todo.py done 999 | grep -q "Error: Task #999 not found"; then
    echo"PASS: not found error"; PASS=$((PASS+1))
else
    echo"FAIL: not found error"; FAIL=$((FAIL+1))
fi

# Test 7: Add after delete (ID must be #3, not reused #2)
if python3 todo.py add "Extra" | grep -q "Added task #3"; then
    echo"PASS: add after delete (ID not reused)"; PASS=$((PASS+1))
else
    echo"FAIL: add after delete (ID reused!)"; FAIL=$((FAIL+1))
fi
if python3 todo.py clear | grep -q "Cleared 2 tasks"; then
    echo"PASS: clear tasks"; PASS=$((PASS+1))
else
    echo"FAIL: clear tasks"; FAIL=$((FAIL+1))
fi

# Test 8: Empty list message
if python3 todo.py list | grep -q "No tasks yet"; then
    echo"PASS: empty list message"; PASS=$((PASS+1))
else
    echo"FAIL: empty list message"; FAIL=$((FAIL+1))
fi

# Test 9: Persistence
python3 todo.py add "Persist test"
if python3 todo.py list | grep -q "Persist test"; then
    echo"PASS: persistence"; PASS=$((PASS+1))
else
    echo"FAIL: persistence"; FAIL=$((FAIL+1))
fi

# Test 10: Corrupt file handling
echo"bad json" > ~/.todo.json
if python3 todo.py list 2>/dev/null; then
    echo"PASS: corrupt file handled"; PASS=$((PASS+1))
else
    echo"FAIL: corrupt file crash"; FAIL=$((FAIL+1))
fi

# Test 11: Deleted IDs are not reused
rm -f ~/.todo.json
python3 todo.py add "First"
python3 todo.py add "Second"
python3 todo.py delete 2
python3 todo.py add "Third"
if python3 todo.py list | grep -q "#3 \[ \] Third"; then
    echo"PASS: deleted ID not reused"; PASS=$((PASS+1))
else
    echo"FAIL: deleted ID was reused"; FAIL=$((FAIL+1))
fi

rm -f ~/.todo.json

echo""
echo"Results: $PASS passed, $FAIL failed"
if [ $FAIL -eq 0 ]; then
    echo"All acceptance tests passed!"
else
    echo"Some tests failed. Review the output above."
fi
ACCEPTANCE_EOF

给脚本执行权限并运行:

chmod +x acceptance.sh
./acceptance.sh

注意acceptance.sh是你(用户)跑的,不是Loop里的代理跑的。所以它不需要任何特殊权限标志——直接python3 todo.py ...就行。这也是设计上的一个好处:Loop里代理的自我验证可能被幻觉污染,但人手动跑的acceptance.sh不会。

10.5 终极版loop.sh:集成全部四刹车

前面的刹车代码是分散给出的,需要你自己拼装。这里提供完整集成版,可以直接替换你之前的loop.sh:

cat > loop.sh << 'LOOP_EOF'
#!/usr/bin/env bash
set -euo pipefail

# ── Configuration ──────────────────────────────────────────────
MAX_ITERATIONS=8            # Safety cap: stop after this many iterations
AGENT_CMD="claude"          # The coding agent CLI to invoke
MAX_TURNS_PER_ITERATION=25  # Max agent turns per iteration
MAX_BUDGET_USD=""           # Set to e.g. "5" for $5 per-iteration cap (empty = no cap)
SPEC_FILE="spec.md"
PLAN_FILE="plan.md"
PROGRESS_FILE="progress.md"
STALLED_LIMIT=2             # Stop after this many consecutive stalls

# ── Colors (auto-disable when piped) ──────────────────────────
if [ -t 1 ]; then
    BOLD='\033[1m'  DIM='\033[2m'  GREEN='\033[32m'
    YELLOW='\033[33m'  RED='\033[31m'  RESET='\033[0m'
else
    BOLD=''  DIM=''  GREEN=''  YELLOW=''  RED=''  RESET=''
fi

# ── Pre-flight checks ─────────────────────────────────────────
echo -e "${BOLD}Pre-flight checks...${RESET}"

if ! command -v "$AGENT_CMD" &> /dev/null; then
    echo -e "${RED}Error: '$AGENT_CMD' not found.${RESET}"
    echo"Install it with: npm install -g @anthropic-ai/claude-code"
    exit 1
fi

for file in"$SPEC_FILE""$PLAN_FILE""$PROGRESS_FILE"; do
    if [ ! -f "$file" ]; then
        echo -e "${RED}Error: $file not found in $(pwd)${RESET}"
        exit 1
    fi
done

echo -e "${GREEN}All checks passed.${RESET} Starting loop..."
echo""

# ── Main loop ──────────────────────────────────────────────────
ITERATION=0
STALLED_COUNT=0

while [ $ITERATION -lt $MAX_ITERATIONS ]; do
    ITERATION=$((ITERATION + 1))

    # Read current milestone from progress file
    CURRENT_MILESTONE=$(grep "^## Current Milestone:""$PROGRESS_FILE" \
                        | head -1 \
                        | sed 's/^## Current Milestone: //' \
                        | sed 's/^[[:space:]]*//')

    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${BOLD}Iteration $ITERATION / $MAX_ITERATIONS${RESET}"
    echo -e "  Current: ${YELLOW}${CURRENT_MILESTONE}${RESET}"
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo""

    # The prompt sent to the agent every iteration
    PROMPT="You are continuing a software project. Start by reading these three files in the current directory:

1. ${SPEC_FILE} — The project specification (what to build and why)
2. ${PLAN_FILE} — The implementation plan with milestones
3. ${PROGRESS_FILE} — Current progress and status

Your task: Complete the current milestone described in ${PROGRESS_FILE}.

Rules:
- Read ALL three files before writing any code.
- Implement ONLY the current milestone described in ${PROGRESS_FILE}.
  Do NOT skip ahead to future milestones.
- After completing the milestone, run the verification commands
  from ${PLAN_FILE} to confirm your work is correct.
- If verification fails, fix the issues and re-verify.
- After successful verification, update ${PROGRESS_FILE}:
  - Add the completed milestone under '## Completed Milestones'
  - Change '## Current Milestone: ...' to the next milestone
    (keep it on the SAME line as the heading, e.g.
     '## Current Milestone: Milestone 2: Robust Persistence')
  - Update '## Current Status' to reflect progress
- If ALL milestones are complete, set ## Current Status to 'ALL DONE'
- CRITICAL — STOP AFTER ONE MILESTONE:
  Once you finish the current milestone and update progress.md, STOP.
  Do NOT begin the next milestone even if you have tool budget left.
  The loop will call you again for the next milestone. Stopping early
  after exactly one milestone is the correct behavior, not a failure.

Start by reading the three files now."

    echo -e "${DIM}Launching agent...${RESET}"
    echo""

    # Build the agent command with optional budget flag
    AGENT_ARGS=(-p "$PROMPT" --max-turns "$MAX_TURNS_PER_ITERATION" --dangerously-skip-permissions)
    if [ -n "$MAX_BUDGET_USD" ]; then
        AGENT_ARGS+=(--max-budget-usd "$MAX_BUDGET_USD")
    fi

    # Launch the agent.
    $AGENT_CMD"${AGENT_ARGS[@]}" < /dev/null || true

    echo""
    echo -e "${DIM}Agent finished. Checking progress...${RESET}"

    # Check if all milestones are done
    if grep -q "ALL DONE""$PROGRESS_FILE"; then
        echo""
        echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${GREEN}${BOLD}All milestones completed!${RESET}"
        echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo""
        echo -e "Completed milestones:"
        grep -A 10 "^## Completed Milestones""$PROGRESS_FILE" | tail -n +2
        exit 0
    fi

    # Check if milestone changed
    NEW_MILESTONE=$(grep "^## Current Milestone:""$PROGRESS_FILE" \
                    | head -1 \
                    | sed 's/^## Current Milestone: //' \
                    | sed 's/^[[:space:]]*//')

    if [ "$NEW_MILESTONE" != "$CURRENT_MILESTONE" ]; then
        echo -e "${GREEN}Milestone completed: ${CURRENT_MILESTONE}${RESET}"
        echo -e "Next up: ${YELLOW}${NEW_MILESTONE}${RESET}"
        STALLED_COUNT=0
    else
        STALLED_COUNT=$((STALLED_COUNT + 1))
        echo -e "${YELLOW}Warning: Milestone did not change (stall count: ${STALLED_COUNT}/${STALLED_LIMIT})${RESET}"
        if [ $STALLED_COUNT -ge $STALLED_LIMIT ]; then
            echo""
            echo -e "${RED}${BOLD}Loop stalled for ${STALLED_LIMIT} consecutive iterations.${RESET}"
            echo -e "${RED}Stopping for manual review. Check ${PROGRESS_FILE} for issues.${RESET}"
            exit 1
        fi
    fi

    # Alignment checkpoint every 2 iterations
    if [ $((ITERATION % 2)) -eq 0 ]; then
        echo""
        echo -e "${YELLOW}━━━ Alignment checkpoint after iteration ${ITERATION} ━━━${RESET}"
        echo -e "${YELLOW}Review the current state. Press Enter to continue, or Ctrl+C to stop.${RESET}"
        read -r
    fi

    echo""
done

# ── Safety cap reached ─────────────────────────────────────────
echo""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${RED}${BOLD}Reached maximum iterations ($MAX_ITERATIONS)${RESET}"
echo -e "Manual review needed. Check ${PROGRESS_FILE} for current status."
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
exit 1
LOOP_EOF

然后重新设置执行权限:

chmod +x loop.sh

“ 💡 终极版新增了三个改进:1)STALLED_LIMIT=2停滞检测,连续2轮没推进就自动停止;2)每2轮迭代暂停一次对齐检查点;3)MAX_BUDGET_USD配置项,设为"5"即可启用每迭代5美元硬性上限(留空则不限制)。

10.6 Loop完成后做什么

Loop跑完、验收测试通过,不代表项目结束了。还有三件事要做:

  1. 代码审查。 代理写的代码能跑,不代表写得好。花10分钟通读todo.py,检查变量命名、错误处理、代码结构。你大概率会发现一些可以改进的地方,比如硬编码的文件路径、缺少docstring、或者异常处理不够细致。这些是代理的”质量税”,需要人类来补。

  2. Git提交。 把Loop的产出物提交到版本控制。建议分两次提交:一次提交代码(todo.py),一次提交Loop基础设施(spec.md、plan.md、progress.md、loop.sh)。这样如果后续需要回滚代码,不会丢失Loop的配置。

git init
git add todo.py
git commit -m "feat: todo CLI — generated by loop engineering"
git add spec.md plan.md progress.md loop.sh acceptance.sh
git commit -m "chore: add loop engineering infrastructure"
  1. 复盘Loop本身。 记录这次Loop跑了多少轮、花了多少token、哪些里程碑卡住了、卡住的原因是什么。这些数据是你优化下一个Loop的依据。一个简单的做法是在progress.md的Notes部分追加复盘信息。

十一、复盘:我的第一个Loop教会了我什么

如果你跟着做完了上面的步骤,你已经有了自己的第一个Loop。现在退后一步,看看这个过程中有哪些值得提炼的认知。

认知一:spec是Loop最重要的文件

不是脚本,不是代理,是spec。一份模糊的spec会让代理在错误的方向上狂奔,而且每一轮迭代都在加固错误。反过来,一份精确的spec让代理几乎不可能跑偏。你在spec上花的时间,会在Loop运行时以10倍的速度回报。

这个原则有一个正式的名字:意图债务(Intent Debt)。Addy Osmani在同名文章中定义了它:当你给代理的指令不够精确时,你欠下了一笔”意图债务”。代理会用自己的判断填补模糊空间,而这些判断可能和你的真实意图不同。每一轮迭代,这个偏差都可能被放大。解决意图债务的方法不是更好的代理,而是更精确的spec。

认知二:里程碑的粒度决定Loop的成败

太大,代理会在一轮迭代中做太多事,出错概率急剧上升。太小,你会花更多时间写plan.md和progress.md的更新逻辑,而不是让代理干活。一个实用的经验:每个里程碑应该能在代理的一次迭代(20-30次工具调用)内完成,并且有明确的验证命令。

我们的Milestone 1最初设计为”无持久化”,但验证命令需要跨进程读取数据,根本跑不通。这暴露了一个重要原则:每个里程碑完成后,项目必须处于可验证的状态。如果验证命令本身无法执行,代理就无法自我检查,Loop就失去了自动化的基础。我们把持久化提前到Milestone 1,就是为了确保每个里程碑的验证命令都能实际运行。

认知三:spec里的数据格式细节不是小事

我们的spec.md明确规定了JSON格式为{"next_id": N, "tasks": [...]},而不是简单的任务列表。这个设计选择直接决定了”删除的ID不被重用”这个需求能否正确实现。如果spec只写”用JSON存储任务”而不规定格式,代理可能选择最简单的[task1, task2]结构,用max(id) + 1计算下一个ID,导致删除最大ID后ID被重用,违反spec约束。

这个教训可以推广:spec中凡是影响正确性的数据结构决策,都必须显式规定,不能留给代理自行判断。 代理会优先选择最简单的实现,而最简单的实现未必满足所有约束。

认知四:progress.md是Loop的”上下文窗口外挂”

AI代理的上下文窗口是有限的。当项目变大、迭代变多时,早期轮次的对话历史会被截断。progress.md的作用就是充当”外部记忆”:代理不需要记住之前做了什么,只需要读progress.md就知道。这就是为什么progress.md的格式必须机器可解析,因为loop.sh脚本也要读它。

这个思路可以推广:任何需要跨迭代持久化的信息,都应该写进文件,而不是依赖上下文窗口。spec.md持久化目标,plan.md持久化路径,progress.md持久化进度。三个文件,三个维度,完整覆盖。

认知五:先手动跑,再自动化

这篇文章让你先手动跑一轮(第七步),再写自动化脚本(第八步)。这不是教学上的冗余,而是工程上的必要。自动化脚本只是把你的手动操作重复执行,如果你不知道手动操作时会发生什么,自动化出了问题你就完全无法诊断。

Addy把这个原则总结为”先走后跑”:先用最简单的方式(手动提示词)验证你的spec和plan是否合理,再升级为半自动(脚本+手动检查点),最后才是全自动(无人值守Loop)。每个阶段都应该稳定运行后再升级。

认知六:权限是Loop的隐形地基

这一篇里我们花了相当多篇幅讲--dangerously-skip-permissions,因为它是最容易被教程略过、却最容易让Loop”看起来在跑但实际没出活”的细节。默认权限模式下,代理在-p非交互模式里没法写文件、没法跑会触碰工作目录外文件的命令。你看到的可能是代理每轮都”热情地开始工作”,但progress.md一直不动,todo.py永远没生成。

把这个教训记在心里:任何让代理自动化跑的脚本,都必须先想清楚权限边界。 是用--dangerously-skip-permissions全开(简单但有风险),还是用--permission-mode acceptEdits--add-dir显式列目录(安全但要维护目录列表),或者在.claude/settings.json里配置permissions.allow规则(最生产级),这三种方案各有取舍,但不能不选

认知七:Loop不是万能的

Addy在文中坦率地承认:“I’ve found myself in situations where I spent more time engineering the loop than I would have spent just doing the work.” 这是一个真实的陷阱。Loop Engineering最适合的场景是大规模、高重复性、边界清晰的任务,比如重构一个大型代码库、实现一个定义良好的功能模块、修复一批类似的Bug。它最不适合的场景是一次性的小任务,如果你只是要改一个函数的签名,手动提示词永远比构建循环更快。

一个简单的判断标准:如果你不确定是否需要Loop,你大概率不需要。 Loop是为反复出现的痛点设计的,不是为一次性任务设计的。先用手动循环理清你的工作模式,再决定是否值得投入时间构建自动化。

但当你真正遇到那个”值得用Loop”的任务时,你会知道的。因为手动提示词会让你感到痛苦:重复的上下文说明、反复的检查流程、无法并行的审查瓶颈。那时候,回到这篇文章,拿出你的loop.sh,写一份spec.md,开始你的第一个真正的生产级Loop。

十二、从这里出发:更高级的Loop形态

你的第一个Loop是单代理、单脚本、Bash实现的。这已经能解决80%的问题。但当你面对更复杂的项目时,有几个方向值得探索。

Geoffrey Huntley的Ralph Wiggum

Geoffrey Huntley开源的Ralph Wiggum CLI是目前最接近”生产级Loop脚本”的公开实现。它和我们的loop.sh思路一致,但更成熟:支持多代理协作、内置成本追踪、支持从断点恢复。如果你的Bash脚本开始变得复杂,Ralph Wiggum是一个值得参考的升级路径。

Anthropic的Managed Agents

Anthropic在2026年4月推出了Managed Agents(托管代理),把Loop的编排逻辑从客户端脚本移到了服务端。你不再需要自己写loop.sh,而是把spec和plan上传到Anthropic平台,由平台负责迭代调度、进度追踪、成本控制。这降低了入门门槛,但也意味着你对Loop的控制力减弱了。适合标准化的任务,不适合需要深度定制的场景。

Cursor的Planner-Worker-Judge架构

Cursor在2026年5月公开了他们大规模运行自主编码代理的实践,核心是Planner-Worker-Judge架构。当你的Loop变得复杂时,让一个代理同时负责规划、执行和评估是不靠谱的,代理会”自我评分”。解决方案是角色分离:Planner规划、Worker执行、Judge评估。Cursor的实践还揭示了一个反直觉的发现:不同的模型适合不同的角色,模型匹配正在成为循环设计的一部分。

附录A:重置并重新开始

如果你想从头跑一遍Loop(比如代理把代码搞乱了,或者你想验证流程),按以下步骤重置:

# 1. 确保在项目目录中
cd my-first-loop

# 2. 删除代理生成的代码文件
rm -f todo.py

# 3. 删除数据文件
rm -f ~/.todo.json

# 4. 重置progress.md到初始状态
cat > progress.md << 'PROGRESS_EOF'
# Progress

## Current Status
Starting — no milestones completed yet.

## Completed Milestones
(none)

## Current Milestone: Milestone 1: Basic CLI with Add, List, and Simple Persistence

## Issues
(none)

## Notes
Starting fresh. Read spec.md and plan.md before beginning.
PROGRESS_EOF

# 5. 确认spec.md和plan.md没有被代理修改
#    (如果被修改了,用Git回滚:git checkout -- spec.md plan.md)
git diff spec.md plan.md

# 6. 重新启动Loop
./loop.sh

“ ⚠️ 如果你没有用Git初始化项目,且spec.md或plan.md被代理修改了,你需要手动恢复它们。这就是为什么3.4节建议你git init。如果没初始化Git,只能重新按第四步和第五步的内容手动重建spec.md和plan.md。

附录B:常见问题

Q:我没有Claude Code,能用其他代理吗?

可以。loop.sh中的AGENT_CMD变量可以替换为任何支持-p参数的CLI代理,比如Codex CLI(codex)或Gemini CLI(gemini)。你只需要修改脚本中的命令行参数格式(不同代理的”print模式”标志和”跳过权限”标志名字可能不同)。核心思路(spec + plan + progress + 循环调用)与具体代理无关。

Q:我必须用--dangerously-skip-permissions吗?看着很吓人。

对于教程这种”我自己电脑上、我自己写的spec、我自己看着它跑”的场景,是必须的,否则Loop在-p模式下根本写不了文件。但生产环境里有更安全的替代:在项目根目录建.claude/settings.json,用permissions.allow显式列出代理可以用的工具和路径,例如:

{
  "permissions": {
    "allow": [
      "Read", "Edit", "Write",
      "Bash(python3 todo.py *)",
      "Bash(rm -f ~/.todo.json)"
    ]
  }
}

这样代理只能做白名单里的事,超出范围还是会被挡住,比--dangerously-skip-permissions安全得多。

Q:代理总是不按spec的格式输出,怎么办?

这是最常见的”意图债务”问题。两个解决方案:第一,在spec中给出更具体的示例(包括输入和期望输出的完整对照);第二,在plan.md的验证步骤中加入输出格式的精确匹配检查。如果代理仍然不遵守,考虑在prompt中增加”Your output MUST match the exact format specified in spec.md”的强调。

Q:Loop跑到一半代理改了spec.md,怎么办?

这不应该发生。在prompt中明确要求”Read ALL three files before writing any code”和”Implement ONLY the current milestone”。如果代理仍然修改了spec.md,说明你的prompt约束不够强。可以在loop.sh中加入文件保护:在每轮迭代开始前备份spec.md和plan.md,迭代结束后比对,如果被修改则回滚并警告。

Q:一个里程碑太大,代理一轮做不完,怎么办?

拆分它。如果一个里程碑需要代理做超过30次工具调用才能完成,它大概率太大了。把它拆成两个更小的里程碑,每个都有独立的验证命令。比如”实现用户认证”可以拆成”实现注册”和”实现登录”。

Q:Loop的成本怎么控制?

Claude Code按token计费,每一轮迭代都会消耗token。控制成本的三个方法:第一,限制MAX_TURNS_PER_ITERATION(减少每轮的token消耗);第二,设置MAX_ITERATIONS(限制总轮数);第三,使用停滞检测(避免在卡住的里程碑上反复烧token)。对于我们的todo项目,4轮迭代大约消耗$0.5-2的API费用。如果你的Loop无人值守运行,强烈建议在$AGENT_CMD调用里加上—max-budget-usd硬性上限,比如—max-budget-usd 5`,这样单个迭代最多烧5美元就强制终止。

Q:代理幻觉性完成了任务(声称验证通过但其实没跑),怎么办?

这是--max-turns限制下偶发的”伪完成”问题。三个对策:第一,降低幻觉概率——把MAX_TURNS_PER_ITERATION从25调到40,给代理足够的turn预算真正跑完验证;第二,强制独立验收——Loop跑完后用acceptance.sh做端到端测试,这层是人跑的不是代理跑的,不会被代理幻觉污染;第三,引入Judge角色——参考Cursor的Planner-Worker-Judge架构,让另一个代理(甚至另一个模型)专门负责验证Worker的产出,避免”自己评分自己”。

Q:代理一轮迭代里把好几个里程碑都做完了,是好事吗?

不是。这违反了Loop的核心节奏:每个里程碑本该是独立可验证、可回滚的增量。一轮做多个会让bug定位变难,也破坏了”逐步对齐”的设计。我们已经在提示词中加了”CRITICAL — STOP AFTER ONE MILESTONE”硬性约束来挡住这种情况。如果代理仍然越界(progress.md从Milestone 1直接跳到Milestone 3或4),按这个顺序排查:(1) 确认你用的是本文给出的最新版提示词;(2) 把MAX_TURNS_PER_ITERATION从25降到18-20,让代理单轮的预算只能覆盖一个里程碑;(3) 检查plan.md中Milestone 1末尾的”Do NOT implement done/delete/clear yet”是否还在;(4) 回退到第七步手动跑一轮,亲眼确认代理是否越界,再切回自动Loop。

Q:Loop能并行跑多个项目吗?

可以,但需要小心。每个项目用独立的工作目录和独立的progress.md,然后在不同的终端窗口中分别运行loop.sh。注意不要让多个Loop写同一个文件,否则会有竞争条件。如果你需要更复杂的并行编排,考虑使用任务队列(如GitHub Actions的matrix策略)。

Q:代理写的代码质量不行,Loop有什么办法改善吗?

在spec.md的Constraints部分加入代码质量要求,比如”All functions must have docstrings”、“Use type hints for all function parameters”、“Follow PEP 8 style”。代理会尽量遵守这些约束。但更实际的做法是:Loop完成后做一次人工代码审查,把质量问题的修复合并到下一个Loop的spec中,形成持续改进的闭环。

Q:我是Windows用户,在WSL2中运行Loop时遇到问题怎么办?

常见问题及解决方案:

  • claude命令找不到:确保你在WSL2的Ubuntu终端中执行,不是在PowerShell中。确保nvm和Node.js已正确安装(运行node --version验证)。

  • 文件路径问题:Loop中的所有路径都使用Linux格式。~/.todo.json在WSL2中对应/home/你的Linux用户名/.todo.json,不是Windows的路径。

  • 权限问题:如果在WSL2中chmod +x loop.sh后仍无法执行,可能是文件系统挂载选项的问题。确保项目目录在WSL2的本地文件系统中(/home/...),而不是在Windows挂载的/mnt/c/...下。后者不支持Linux文件权限。

附录C:快速参考卡

把所有关键命令汇总在一页,方便你快速查阅:

# ── 环境安装 ──
npm install -g @anthropic-ai/claude-code    # 安装Claude Code
claude                                        # 首次运行,完成认证

# ── 项目初始化 ──
mkdir my-first-loop && cd my-first-loop       # 创建并进入项目目录
git init                                      # 初始化Git(推荐)

# ── 创建核心文件 ──
# (文件内容见第四、五、六步,此处仅列出验证命令)
ls -la spec.md plan.md progress.md            # 确认三个文件存在

# ── 手动跑一轮 ──
claude --dangerously-skip-permissions -p "You are continuing a software project..."

# ── 创建并运行Loop ──
chmod +x loop.sh                              # 给脚本执行权限(只需一次)
./loop.sh                                     # 启动Loop
./loop.sh 2>&1 | tee loop.log                 # 启动Loop并保存日志

# ── 验收测试 ──
chmod +x acceptance.sh                        # 给验收脚本执行权限(只需一次)
./acceptance.sh                               # 运行验收测试

# ── 重置 ──
rm -f todo.py ~/.todo.json                    # 删除代码和数据
# 然后重新创建progress.md(见附录A)

# ── Git提交 ──
git add todo.py && git commit -m "feat: todo CLI"
git add spec.md plan.md progress.md loop.sh acceptance.sh && git commit -m "chore: loop infrastructure"

参考信源:

  1. Addy Osmani, Loop Engineering, addyosmani.com, 2026-06-08

  2. Addy Osmani, Agent Harness Engineering, addyosmani.com, 2026-04-19

  3. Addy Osmani, Long-Running Agents, addyosmani.com, 2026-04-28

  4. Addy Osmani, The Orchestration Tax, addyosmani.com, 2026-05-24

  5. Addy Osmani, The Intent Debt, addyosmani.com, 2026

  6. Peter Steinberger, You shouldn’t be prompting coding agents anymore, 2026

  7. Boris Cherny, Head of Claude Code: What happens after coding is solved, Lenny’s Newsletter, 2026

  8. The New Stack, The Anthropic leader who built Claude Code says he ditched prompting, 2026-06

  9. Cursor, Scaling Long-Running Autonomous Coding, 2026

  10. Anthropic, Building Effective Agents / Managed Agents, 2026

  11. Geoffrey Huntley, Ralph Wiggum CLI, GitHub, 2026


内容效果不满意?点此反馈

输入关键词开始搜索