Skip to content
Chenghao Liao
Go back

从 Raft 到 EvoAgent:Agent Runtime 的并发、副作用与持久化执行

传统 Agent 很容易被理解成:

用户请求
→ LLM 推理
→ 调一次工具
→ 返回答案

在这个模型里,很多系统问题并不明显。

但当 Agent 开始执行几十秒甚至几分钟、调用多个外部工具、修改 GitHub / 数据库 / 文件、与其他 Agent 并发工作,还要等待 CI、Webhook 或 Human Approval 时,事情就变了。

Agent 不再只是一次 LLM 调用,而是一个长期运行、会产生副作用的异步 worker。

这时会出现一组很具体的问题:

这些问题也恰好会出现在我正在做的 EvoAgent PR Reviewer 中。因此,这篇文章前半部分会讨论这些问题需要什么 Runtime 语义,后半部分再把它们映射回真实的 PR Review、AutoFix 和 CI 场景。

1. Agent Runtime 真正需要解决什么

过去的执行过程可以简化成:

Request
→ LLM
→ Tool
→ Response

而一个长期运行的 Agent 更接近:

Event
→ Agent
→ Read State
→ Reason
→ Tool
→ External Effect
→ Wait
→ New Event
→ Resume
→ More Effects
→ Commit

执行模型变化以后,Runtime 需要面对五类问题:

状态会变
→ stale execution

多个 Agent 会抢同一个任务
→ concurrent execution

Tool 会产生真实副作用
→ partial failure / retry

外界事件越来越多
→ routing / attention

任务会跨越很长时间
→ suspend / resume

所以 Agent Runtime 的核心问题,已经开始从“模型怎么推理”转向“执行过程怎么管理”。它需要的不只是 Prompt + Tool Call,而是:

Agent Runtime
= LLM
+ 并发控制
+ Tool Effect 管理
+ 消息路由
+ Durable Execution

恰好,AI-Native 协作产品 Raft 在真实的多人 + Multi-Agent 环境里遇到了类似问题,它的一组机制很适合作为 Runtime 设计样本。

本文受到 Raft 文章 Is Having Agents in the Room Meant to Be Chaotic? 启发。这里的 Raft 不是分布式系统中的 Raft consensus algorithm;CAS、lock、Effect Ledger、durable continuation 等,是我从产品机制出发做的工程类比,并非 Raft 官方术语。

2. Raft 给出的五种 Runtime 语义

基于旧状态执行:Freshness Hold

传统聊天里,人看到一条消息,很快就回复了。

但 Agent 不一样。

Agent 收到消息以后,可能经历:

读取上下文
→ 推理
→ 调工具
→ 查数据
→ 再推理
→ 生成答案
→ 发送

整个过程可能持续几十秒甚至几分钟。

问题在于,这段时间外部世界并不会停止变化。

例如:

t0:
Agent 读取聊天室状态 S0

t1:
Agent 开始处理

t2:
用户发送了一条新消息
聊天室变成 S1

t3:
Agent 还基于 S0 生成答案

如果此时 Agent 直接提交,就属于:

基于旧状态完成了一次写操作。

这和我们熟悉的读写冲突其实是同一个问题。

文章里的 Freshness Hold,本质上可以理解成:

Optimistic Lock / CAS

Agent 开始执行时记录:

version = S0

提交之前检查:

current_version == S0 ?

如果还是 S0:

Commit

如果已经变成 S1:

Hold
→ 把新增上下文给 Agent
→ 重新判断
→ revise / send / silence

所以它解决的是:

我读取数据以后,到提交结果之前,数据有没有发生变化?

和数据库里的:

UPDATE ...
WHERE version = old_version

本质非常接近。

多 Agent 重复执行:Task Claim

Freshness Hold 主要解决旧状态问题。

但 Multi-Agent 还有另一个问题:

Agent A 看到任务 T
Agent B 也看到任务 T

A 开始执行
B 也开始执行

如果只是回答问题,可能只是重复。

但如果任务是:

那就可能产生真正的冲突。

所以文章又设计了 Task Claim。

例如:

Agent A:
claim(task)
→ success

Agent B:
claim(task)
→ already owned by A

这更接近:

lock / lease / ownership

所以这里其实有两类并发控制:

Freshness Hold
→ CAS / optimistic concurrency
→ 防止基于旧状态提交

Task Claim
→ lock / ownership
→ 防止多个 Agent 同时执行同一任务

这两个不能完全混为一谈。

Tool 部分失败:Partial Result

我觉得这是文章里非常有工程价值的一点。

普通 Tool API 很容易设计成:

success
failed

但 Agent 调工具时,这远远不够。

因为一个工具调用内部可能已经产生了多个副作用。

例如:

send_message()

内部:

  1. 正文发送成功
  2. @mention 失败

如果 Tool 最后只告诉 Agent:

FAILED

Agent 很自然就会:

retry

结果就变成:

正文发送两次

这就是典型的:

Partial Failure
+
Retry
=
Duplicate Side Effect

所以 Agent 真正需要的不是:

这个函数成功还是失败?

而是:

这个操作已经对外部世界造成了哪些影响?

因此 Tool Result 应该更像:

status: PARTIAL_SUCCESS

effects:
  - message_created: success
  - mention_created: failed

这样 Agent 才知道:

这其实是 Effect Semantics

我会把它理解成:

Tool Call Result
从 Function Result
升级成 Effect Report

例如一个 AutoFix 流程:

create branch   success
commit          success
push            success
create PR       failed

正确恢复方式应该是:

从 create PR 继续

而不是:

把整个 AutoFix 再执行一遍

所以一个成熟 Agent Runtime 最好明确记录:

Intent
→ Effect 1
→ Effect 2
→ Effect 3

甚至保存成 Effect Ledger。

这实际上对应很多传统系统里的概念:

世界事件如何进入 Agent:Inbox

这一部分和我之前提到的“路由投递中心”非常像。

一个聊天室里可能每分钟都有大量消息,但对于某一个 Agent 来说,绝大部分消息其实都不重要。

如果系统设计成:

聊天室新消息
→ 自动塞进 Agent Context

那么很快就会遇到两个问题:

所以文章设计了 Inbox。

我更倾向于把它理解成:

Event Routing Center

整体结构:

所有事件
    │
    ▼
Routing / Inbox
    │
    ├── Agent A
    ├── Agent B
    └── Agent C

只有和某个 Agent 真正相关的事件,才投递给它。

例如:

对于 Security Agent 来说,可能只需要:

其他信息根本不用进入 Context。

Routing 和 Attention 还可以再区分。

更严格一点,可以拆成:

Routing
→ 这个事件属于哪个 Agent?

Attention
→ 这个事件要不要现在进入 Agent Context?

于是变成:

World Events
     ↓
Event Bus
     ↓
Router
     ↓
Agent Inbox
     ↓
Attention Policy
     ↓
Context Window

这个结构比:

所有消息 → messages[]

要合理很多。

因为:

世界状态和 Agent 当前注意到的信息,本来就不应该是同一个东西。

长时间等待:Reminder

这一点我一开始觉得最容易被误解。

文章所谓 Reminder,本质上不是:

Agent sleep 一会

而是:

当前执行先结束,把“未来还要继续做什么”持久化,之后再重新唤醒。

例如:

Agent push code
↓
CI running

这时候 CI 要跑 20 分钟。

最笨的做法是:

sleep 20 min

或者:

while CI running:
    poll()

这样 Agent 一直保持运行状态。

但实际上这 20 分钟 Agent 什么都做不了。

更合理的是:

保存 checkpoint
↓
结束当前 Agent run

checkpoint 里保存:

task = PR #123
completed = push commit
waiting_for = CI
next_step = CI finished 后检查结果

然后当前执行直接结束。

后续再唤醒。

未来由:

触发:

Wakeup
↓
读取 checkpoint
↓
读取最新世界状态
↓
继续执行

这里非常关键的一点是:

它不是恢复 20 分钟前完整的程序运行现场。

而是恢复:

logical state / continuation

也就是:

所以我会把它叫做:

durable continuation checkpoint

比普通 checkpoint 更准确。

3. 把它们放在一起:Agent Execution Lifecycle

把文章重新整理以后,我觉得核心非常清楚:

问题文章机制后端视角
Agent 执行期间状态变化Freshness HoldCAS / optimistic lock
多 Agent 同时执行同一任务Task Claimlock / lease / ownership
Tool 部分成功Partial Resulteffect log / idempotency
消息太多Inboxevent routing / attention
等待未来事件Remindercheckpoint / scheduler / durable execution

最终其实可以统一成一个 Agent 生命周期:

Event
  ↓
Router
  ↓
Agent
  ↓
Claim / Read State
  ↓
Execute
  ↓
Record Effects
  ↓
Freshness Check
  ↓
┌────────────────────┐
│ 能继续 → Continue  │
│ 要等待 → Checkpoint│
└────────────────────┘
             ↓
            Exit

       future event
             ↓
          Wakeup
             ↓
      Restore State
             ↓
      Refresh World
             ↓
      Continue / Replan

我觉得这才是整篇文章真正有价值的 mental model。

4. 放回 EvoAgent:问题如何出现在真实 Workflow

状态说明:EvoAgent 当前已经实现 Lead、Security、Correctness/Reliability、Critic 的多 Agent 审查编排,并具备节点级 checkpoint/resume 基础。以下 Freshness Guard、finding ownership、effect-aware AutoFix、Event Router、CI wakeup/resume 等内容,属于从原文得到的启发或后续规划;除非明确标注为“已实现”,不代表当前已经落地。

放回当前 EvoAgent PR Reviewer,最值得关注的不是:

我们是不是也要做 Inbox、Reminder、Claim 这几个模块?

而应该问:

EvoAgent 现在有哪些真实问题,本质上属于这些系统问题?

PR Review:Freshness Guard 与 Finding Dedup

PR 在 Review 期间发生变化。

从原文得到的启发(尚未实现):PR Freshness Guard。

这是最直接可以吸收的。

当前可能出现:

Agent 开始 review commit A
↓
运行几十秒 / 几分钟
开发者 push commit B
↓
Agent 最终把基于 A 的 comment 发出去

本质就是 stale state。

可以直接引入:

review_base_sha

开始时:

base_sha = current_pr_head

发布 findings 前:

current_head == base_sha ?

如果不相等:

HOLD

然后再决定:

这就是 EvoAgent 版的 Freshness Hold。

而且这个功能非常适合做实验:

baseline:
不检查 PR freshness

vs

new:
version-aware review

然后统计:

这种东西会非常有 runtime evidence。

多个 Agent 重复发现或修复问题。

当前设计:

EvoAgent 当前设计中的角色包括:

从原文得到的启发(尚未实现):finding dedup / ownership。

很可能不同 Agent 会发现同一个问题。

例如:

Security:
SQL 拼接存在风险

Correctness:
这里直接拼接 SQL 可能出错

如果没有协调层:

这其实就是 ownership / dedup 问题。

但我觉得这里不一定需要真的做分布式锁。

对当前项目来说,轻量版本可能就够:

finding fingerprint
+
semantic dedup
+
owner agent

比如:

finding_id =
file
+ line range
+ category
+ normalized root cause

然后 Lead/Critic 做:

这就已经吸收了文章的思想。

AutoFix:Effect-aware Execution

后续规划:Effect-aware AutoFix,尚未实现。

这一点我觉得对 EvoAgent 特别有价值。

你的系统后面如果支持:

就一定会遇到 partial failure。

例如:

修改代码        success
测试            success
commit          success
push            success
create Draft PR failed

如果现在只是:

AutoFix status = failed

那 Runtime 根本不知道该怎么恢复。

可以改成:

FixExecution

patch_generated = true
patch_applied = true
tests_passed = true
commit_created = true
branch_pushed = true
draft_pr_created = false

然后恢复时:

resume from draft_pr_created

而不是重新跑全部流程。

这一点甚至可以成为 EvoAgent 的一个亮点

因为现在很多 Agent 项目展示的是:

LLM 调工具成功了

但真正工程系统更关心:

失败以后怎么办?

如果你的项目能展示:

partial failure
→ persisted effects
→ restart
→ resume correctly

技术可信度会高很多。

CI Validation:Checkpoint 与 Event-driven Resume

已实现与后续规划的边界: EvoAgent 已有节点级 checkpoint/resume 基础;下面的 CI 事件驱动等待与恢复属于后续规划,尚未实现。

这个也很自然。

比如 AutoFix:

生成 patch
↓
push branch
↓
等待 CI

不要:

Agent 一直轮询 CI

而是:

FixWorkflow checkpoint:

state = WAITING_FOR_CI
commit = abc123
next = evaluate_ci_result

然后:

结束本轮 execution

CI webhook 回来:

CICompleted(commit=abc123)

Router 找到对应 workflow:

wake
↓
load checkpoint
↓
continue

这样就真正形成了:

Durable Agent Workflow

GitHub、CI 与 Human:统一事件路由

后续规划:Event Router,尚未实现。

Inbox 的思想也非常适合 EvoAgent,但不需要真的做“Agent 邮箱”。

可以抽象成:

Event

例如:

所有东西先进入:

Event Router

然后:

PullRequestUpdated
→ Review Workflow

CICompleted
→ Fix Validation Workflow

Security-related finding
→ Security Agent

也就是说:

都只是 Event Source。

这可以把系统从:

GitHub webhook
→ 一堆 if else

逐渐变成:

Event
→ Routing
→ Workflow
→ Agent

架构会干净很多。

5. 实现边界:吸收 Runtime 语义,而不是造基础设施

后续规划:以下是优先级建议,不代表已经实现。

这一点很重要。

我不建议因为看了这篇文章,就直接:

这样很容易把三个月项目做炸。

我会这样分。

当前 MVP 强烈值得吸收:

  1. PR Freshness Guard
  2. Effect-aware execution state
  3. Workflow state persistence
  4. Finding dedup / ownership

这几个都非常贴近 PR Reviewer 的真实问题。

可以做轻量版本:

  1. Event Router
  2. CI wakeup / resume
  3. task ownership

不需要通用化,先服务:

这几条路径即可。

暂时不要做成通用基础设施:

因为这些很容易偏离项目目标。

我们要吸收的是:

系统语义。

而不是:

把 Raft / Temporal 重新实现一遍。

长期的架构转变。

后续规划:以下是目标架构方向,不代表已经实现。

原来的 EvoAgent 很可能更接近:

PR
↓
Lead Prompt
↓
几个 Specialist Prompt
↓
Critic Prompt
↓
输出结果

这种系统虽然叫 Multi-Agent,但本质上更像:

多阶段 LLM Pipeline。

而吸收这篇文章真正重要的部分以后,可以逐渐变成:

GitHub / CI / Human Events
          ↓
      Event Router
          ↓
   Persistent Workflow
          ↓
     Agent Execution
          ↓
  Effect-aware Actions
          ↓
Freshness / Coordination
          ↓
 checkpoint / resume

这时候系统就不只是:

“有多个 Agent”

而是:

“有 Agent Runtime 语义”

这两者技术含量差别其实很大。

6. 总结

我对这篇文章最大的收获可以概括成一句话:

Agent 不能只被当成一个会自动回复消息的人,它更像一个运行在持续变化环境中的异步 worker。

一旦这样看,很多问题就自然变成了熟悉的后端问题:

状态会变化
→ CAS / freshness

多人会抢任务
→ ownership

工具会部分成功
→ effect tracking

消息很多但注意力有限
→ routing

任务需要等待
→ checkpoint + wakeup

而对 EvoAgent 来说,最值得吸收的也不是“重新设计聊天系统”,而是这套思维:

把 Agent workflow 从一次性推理流程,升级成一个有版本、有状态、有副作用、可暂停、可恢复,并与外界持续交互的异步执行实体。


Share this post:

Next Post
ChatGPT Plus 国内支付记录:Google Play + 银联卡