Ralph循环:我如何在夜间运行自主AI代理
我构建了一个自主代理系统,该系统使用停止钩子拦截退出尝试,使用文件系统记忆在上下文窗口之间持久化状态,并使用生成预算防止失控递归。该系统在多个夜间会话中交付了我的9个PRD审议基础设施(3,455行Python代码,141个测试)。1
TL;DR
Ralph架构通过同时解决三个问题来实现长时间运行的自主AI开发:上下文窗口耗尽(通过每次迭代使用全新上下文解决)、状态持久化(通过文件系统作为记忆解决)以及任务连续性(通过停止钩子循环阻止代理在完成前终止来解决)。我在Claude Code钩子系统中实现了这一模式,并用它构建了多代理审议基础设施。该系统确实有效,但它让我在生成预算、标准质量和文件系统污染方面学到了深刻的教训。
上下文窗口问题
每个AI对话都在一个上下文窗口内运行:一个固定大小的缓冲区,用于保存对话历史、系统提示、工具输出和工作记忆。Claude的上下文窗口大约可容纳200,000个token。一个复杂的开发会话可能在30到60分钟的高强度工作后耗尽这些token。2
我在审议系统构建期间直接测量了这一点。那些一开始能在8个Python模块之间精确进行多文件编辑的会话,到90分钟时退化为只能关注单个文件的隧道视野。代理不再引用它之前读取过的架构,因为那些上下文已经被压缩掉了。3
退化遵循一条可预测的曲线:
Iteration 1: [200K tokens] → writes code, creates files
Iteration 2: [200K tokens] → reads files from disk, continues
Iteration 3: [200K tokens] → reads updated files, continues
...
Iteration N: [200K tokens] → reads final state, verifies criteria
与单个长时间会话对比:
Minute 0: [200K tokens available] → productive
Minute 30: [150K tokens available] → somewhat productive
Minute 60: [100K tokens available] → degraded
Minute 90: [50K tokens available] → significantly degraded
Minute 120: [compressed, lossy] → errors accumulate
每次迭代使用全新上下文的代理优于持续运行的代理,因为每次迭代都将全部认知资源分配给当前状态,而不是承载之前推理的负担。
我的实现
停止钩子
我的递归守卫系统拦截代理的停止尝试并检查完成标准:
#!/bin/bash
# From recursion-guard.sh - simplified
CONFIG_FILE="${HOME}/.claude/configs/recursion-limits.json"
STATE_FILE="${HOME}/.claude/state/recursion-depth.json"
# Safe defaults with config override
MAX_DEPTH=2
MAX_CHILDREN=5
DELIB_SPAWN_BUDGET=2
DELIB_MAX_AGENTS=12
# Load config with validation
load_config() {
if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then
config_depth=$(jq -r '.max_depth // 2' "$CONFIG_FILE")
if [[ "$config_depth" =~ ^[0-9]+$ ]] && [[ "$config_depth" -gt 0 ]]; then
MAX_DEPTH="$config_depth"
fi
fi
}
该钩子读取一个定义成功条件的标准文件。这些条件必须是机器可验证的:测试通过/失败、linter输出、HTTP状态码、文件存在性检查。4
文件系统作为持久记忆
关键洞察:文件在上下文窗口之间持久存在。我的.claude/目录充当代理的持久记忆:
| 目录 | 内容 | 在Ralph循环中的角色 |
|---|---|---|
state/ |
recursion-depth.json、agent-lineage.json |
跟踪迭代次数、父子关系 |
configs/ |
14个JSON文件 | 编码阈值、预算、规则(非硬编码) |
handoffs/ |
49个上下文文档 | 保留跨会话的架构决策 |
hooks/ |
95个生命周期处理器 | 在迭代之间强制执行质量关卡 |
每次新迭代从磁盘读取当前状态,并从上一次迭代停止的地方继续。会话启动钩子初始化干净的状态:
# From session-start.sh - recursion state initialization
RECURSION_STATE_FILE="$RECURSION_STATE_DIR/recursion-depth.json"
# Initialize with safe defaults
{
"depth": 0,
"agent_id": "root",
"parent_id": null,
"initialized_by": "session-start"
}
如果状态损坏(在开发过程中发生了两次),恢复模式会从安全默认值重新创建,而不是崩溃:
if ! jq -e '.depth' "$RECURSION_STATE_FILE" &>/dev/null; then
# Corrupted state file, recreate with safe defaults
echo "- Recursion state recovered (was corrupted)"
fi
任务规范格式
有效的Ralph任务包含三个要素:目标、完成标准和上下文指针:
OBJECTIVE: Implement multi-agent deliberation with consensus validation.
COMPLETION CRITERIA:
- All tests in tests/test_deliberation_lib.py pass (81 tests)
- post-deliberation.sh validates consensus above 70% threshold
- recursion-guard.sh enforces spawn budget (max 12 agents)
- deliberation-pride-check.sh passes 5 quality checks
- No Python type errors (mypy clean)
CONTEXT:
- Follow patterns in lib/deliberation/state_machine.py
- Consensus thresholds in configs/deliberation-config.json
- Spawn budget model: agents inherit budget, not increment depth
我用这个模式构建了什么
审议基础设施(9个PRD)
最大的Ralph循环项目:9个产品需求文档在多个会话中实现。
| PRD | 交付物 | 测试 |
|---|---|---|
| PRD 1-4 | 钩子、配置、递归守卫扩展 | 提交为3cad08c |
| PRD-5 | 48个bash集成测试(7个套件) | 提交为10df724 |
| PRD 7-8 | 钩子连接 + 81个Python单元测试 | 提交为fbf1a0d |
| PRD-9 | 12个端到端管道模拟测试 | 提交为32bd711 |
总产出: 跨8个模块的3,455行Python代码,141个测试,4次提交。每个会话都从上一个会话的文件系统状态继续。全新的上下文意味着每个PRD都获得了代理的全部注意力,无需携带早期PRD的对话历史。5
博客质量系统(12个模块)
博客linter最初是一个3模块脚本,通过迭代Ralph循环扩展到12个模块。每次迭代添加一个模块,运行完整的测试套件,并验证零回归。完成标准不断演进:
- 迭代1: “所有77个测试通过”
- 迭代5: “所有77个测试通过且linter对所有33篇文章报告0个错误”
- 迭代8: “所有测试通过且0个错误且0个警告且所有文章的深度分数≥2”
失败与教训
失败1:生成预算灾难
在审议系统构建早期,我运行了一个没有生成预算限制的会话。代理生成了3个探索子代理。每个子代理又生成了自己的子代理。几分钟之内,递归守卫钩子就在拦截数十个生成请求。该会话以正常速率10倍的速度消耗API token,直到我手动终止。6
修复方案: 我在recursion-limits.json中添加了生成预算模型。代理从其父代理继承预算,而非递增深度。一个预算为12的根代理最多可以在所有递归层级中总共生成12个代理。这是一个关键的架构洞察:预算继承防止了指数级增长,同时仍然允许深层代理链。
失败2:轻易通过的标准
一个早期任务要求代理”编写能通过的测试”。代理编写了最简测试:assert True、assert 1 == 1。技术上,标准确实满足了。但输出毫无价值。
修复方案: 标准必须同时指定数量和质量:
| 标准质量 | 示例 | 结果 |
|---|---|---|
| 模糊 | “测试通过” | 代理编写简单测试 |
| 可衡量但不完整 | “测试通过且覆盖率>80%” | 代理编写覆盖代码行但不测试实质内容的测试 |
| 全面 | “所有测试通过且覆盖率>80%且无类型错误且linter通过且每个测试类测试一个独立模块” | 生产级质量的输出 |
失败3:文件系统污染
探索了死胡同方案的迭代留下了残留物:部分实现的功能、过时的文件、冲突的配置。迭代5可能基于迭代3中一个半完成的方案继续构建,而该方案在迭代4中已被放弃。
修复方案: 我在停止钩子的标准中添加了清理步骤:”不存在任何未被导入或测试引用的文件。”这迫使代理在迭代完成之前清理死胡同。
失败4:((VAR++)) 事件
在bash集成测试期间,递归守卫钩子在第一次迭代时静默崩溃。问题是:((VAR++))在VAR为0时返回退出码1(因为0++求值为0,bash将其视为false)。在启用set -e的情况下,这会终止脚本。
修复方案是使用VAR=$((VAR + 1))替代((VAR++))。这个bash陷阱记录在我的MEMORY.md中,已经在后续6个钩子脚本中防止了相同的错误。7
Ralph何时有效,何时无效
强适用场景
- 绿地实现,具有明确的规范(新API、新模块、新测试套件)
- 自动化验证已存在(测试、类型检查器、linter、编译)
- 有界范围,可以在单个任务文件中描述
弱适用场景
- 主观质量(”让UI看起来好看”)没有机器可验证的标准
- 探索性工作,方向取决于中间发现
- 大规模重构,需要理解跨数十个文件的全局代码关系
关键收获
对于构建自主代理系统的开发者: - 在启动自主循环之前,投入精力制定机器可验证的完成标准;我的审议系统之所以成功,是因为每个PRD都有可测试的成功标准(共141个测试) - 从第一天起就实施生成预算;预算继承模型(而非深度递增)在允许深层链的同时防止了代理的指数级生成 - 将文件系统清理加入完成标准;被放弃迭代中的死胡同残留物会污染后续迭代
对于评估自主AI开发的工程团队: - Ralph架构用人类实现时间换取人类规范编写时间;投资回报率取决于您的瓶颈是实现能力还是规范清晰度 - 以审查外部承包商代码的同等严谨程度审计自主输出;我的141个测试之所以存在,是因为我认识到满足完成标准并不保证生产就绪
参考文献
-
作者使用Claude Code钩子实现的Ralph循环模式。审议基础设施:3,455行Python代码,8个模块,141个测试,跨4次提交(2025-2026)。 ↩
-
Anthropic,“Claude Models,” 2025。 ↩
-
Liu, Nelson F. et al., “Lost in the Middle: How Language Models Use Long Contexts,” TACL, 2024。 ↩
-
Anthropic,“Claude Code Documentation,” 2025。钩子生命周期事件。 ↩
-
作者的git日志。提交
3cad08c、10df724、fbf1a0d、32bd711涵盖整个审议基础设施构建。 ↩ -
作者在无预算代理生成方面的经验。记录在
~/.claude/projects/*/memory/MEMORY.md错误条目中。 ↩ -
作者的bash调试。
((VAR++))在set -e下的退出码行为作为跨会话学习记录在MEMORY.md中。 ↩