API 会崩。它们不打招呼就改响应格式,把你限流到崩溃,凌晨三点直接下线。每个开发者都写过周一还正常、周五就挂掉的代码——因为某个第三方端点决定把 temperature 字段改成嵌套到 weather.temp_celsius 里。
通常的修复流程:人类发现问题 → 更新解析器 → 部署 → 祈祷不要再出别的问题。
如果 Agent 能自己修复呢?我们做了一个实验来验证这个想法。
实验设计:API Apocalypse
我们创建了一个可控混沌环境——三个模拟天气 API 数据源在 30 秒内渐进式降级:
| 时间 | Source A (JSON) | Source B (XML) | Source C (CSV) |
|---|---|---|---|
| 0–6s | 正常 | 正常 | 正常 |
| 6s | 格式变更 | 正常 | 正常 |
| 12s | 已变更 | 格式变更 | 正常 |
| 18s | 已变更 | 已变更 | 格式变更 |
| 24s | 限流 (429) | 已变更 | 已变更 |
| 28s | 限流中 | 完全下线 (503) | 已变更 |
两个 Agent 在同一环境中竞争:
- Rotifer Agent:6 个 Gene(每个 API 源 2 个),基于运行时 Fitness 自动 failover
- Baseline Agent:3 个固定解析器,无适应能力
两个 Agent 每 2 秒从三个源聚合天气数据。当某个源崩溃时,Baseline Agent 对该源永久失败;Rotifer Agent 尝试替代 Gene。
核心创新:Fitness 排序 Failover
Rotifer Agent 为每个 domain 维护一个 Gene 池。每个 Gene 有一个运行时 Fitness 分数——成功时增加,失败时降低。当活跃 Gene 失败时,Agent 执行:
- 惩罚失败 Gene 的 Fitness
- 按 Fitness 降序排列同 domain 的剩余 Gene
- 逐个尝试,直到某个候选 Gene 成功
- 将成功的候选 Gene 提升为活跃 Gene
// 主 Gene 失败时,按 Fitness 排名尝试替代方案const candidates = this.getGenesByDomain(domain) .filter(g => g.name !== activeName);
for (const candidate of candidates) { try { const result = await candidate.express(input); this.updateFitness(candidate, true); this.activeGenes.set(domain, candidate.name); recovered = true; break; } catch { this.updateFitness(candidate, false); }}这不是重试逻辑。这是选择压力。失败的 Gene 不会被盲目重试——它们的 Fitness 排名下降,被能处理新现实的替代方案取代。
实验结果
t │ ROTIFER(自动切换) │ BASELINE(固定代码) ─────┼─────────────────────────────-┼────────────────────── 0s │ ✓ ✓ ✓ 23.5°C │ ✓ ✓ ✓ 23.5°C 6s │ ✓ ✓ ✓ 23.4°C │ ✓ ✓ ✓ 23.4°C 8s │ ✓⟳ ✓ ✓ 23.3°C │ ✗ ✓ ✓ 23.2°C 14s │ ✓ ✓⟳ ✓ 23.1°C │ ✗ ✗ ✓ 23.0°C 20s │ ✓ ✓ ✓⟳ 22.9°C │ ✗ ✗ ✗ NO DATA 28s │ ✗ ✓ ✓ 23.0°C │ ✗ ✗ ✗ NO DATA⟳ 标记展示了自动 Gene 切换——Rotifer Agent 检测到解析失败并在同一个 tick 内完成切换。
正面对决
| 指标 | Rotifer | Baseline |
|---|---|---|
| 数据源可用性 | 83.3% | 33.3% |
| 数据连续性 | 100%(始终至少有 1 个源可用) | 50%(t=90s 后丢失所有源) |
| 自动 Gene 切换 | 3 次 | N/A |
| 人工干预 | 0 次 | 需要手动修复 |
Rotifer Agent 维持了 2.5 倍的数据源可用性,同时零人工干预。当 Source A 在 t=24s 被限流时,两个 Agent 都丢失了它——网络层故障不是解析 Gene 能修复的。但到那个时候,Baseline Agent 已经因无法处理格式变更而丢失了全部三个源。
这跟重试逻辑有什么本质区别
重试循环是重复同一个操作,期待不同的结果。Rotifer Agent 做的事情有本质不同:
它切换的是实现方式,不是尝试次数。
当 json-v1(期待 { temperature: 23.5 })遇到 { weather: { temp_celsius: 23.5 } } 时,重试 json-v1 永远不会成功。但 json-v2 恰好是为这种格式设计的。Agent 不知道 API 为什么变了——它只观察到 json-v1 的 Fitness 下降而 json-v2 成功,于是提升了 json-v2。
这就是生物学类比:生物体不调试它们的环境。它们携带遗传变异,选择压力提升有效的那些。
Gene Fitness 动态
实验结束后,Fitness 分数清晰地讲述了整个故事:
weather-source-a-v1 █████████████████░░░ 0.850 (15✓ 1✗) weather-source-a-v2 ████████████████████ 1.000 (45✓ 0✗) weather-source-b-v1 █████████████████░░░ 0.850 (30✓ 1✗) weather-source-b-v2 ████████████████████ 1.000 (45✓ 0✗) weather-source-c-v1 █████████████████░░░ 0.850 (45✓ 1✗) weather-source-c-v2 ████████████████████ 1.000 (45✓ 0✗)每个 v1 Gene 作为初始活跃解析器,在稳定期表现良好。当 API 格式变更时,v1 Gene 失败一次,Fitness 受到惩罚,v2 Gene 被提升。v2 Gene 随后无故障运行直到实验结束或源完全下线。
没有配置文件被更新。没有人类被呼叫。Agent 自行适应了。
自己试试
完整实验是自包含的,可以复现:
git clone https://github.com/rotifer-protocol/rotifer-playground.gitcd rotifer-playgroundnpm installnpx tsx experiments/api-apocalypse/run.ts30 秒演示版(为录屏优化):
npx tsx experiments/api-apocalypse/demo.ts一切都在本地运行——模拟服务器、Gene、Agent、指标采集。无需外部依赖、API 密钥或云服务。
这证明了什么(以及没证明什么)
本实验证明了基于 Fitness 的 Gene 选择 + 自动 failover 机制可以在 API 中断期间维持服务连续性,无需人工干预,实现比固定代码方案 2.5 倍的数据源可用性提升。
本实验没有声称进化式方法普遍优于传统工程。场景是刻意限定的——单 Agent、本地执行、已知故障模式 + 预构建的替代 Gene。
下一步的诚实问题是:如果 Agent 必须在运行时发现或生成新 Gene(而不仅仅是从预注册池中选择),会发生什么?那是我们为 v0.9 规划的多 Agent HLT(水平逻辑迁移)实验——当 P2P 网络使跨 Agent Gene 共享成为可能的时候。
为什么这对 Rotifer 之外也有意义
每个依赖第三方 API 的生产系统都面临同样的问题。当前业界的答案是防御性工程:超时、重试、断路器、降级值。这些必要但被动——它们管理故障,不进化越过故障。
本实验展示的替代方案是携带遗传变异——为同一能力维护多种实现策略,让运行时性能决定哪个领先。这是一个在任何规模下都有效的模式,从单个 API 解析器到自主 Agent 舰队。
代码开源。运行它,打破它,扩展它。
Rotifer Protocol — Code as Gene, Evolution as Runtime