多 Agent 协作的 Kanban 架构——任务分派、状态同步与死锁预防

人工智能Agent 2026-07-14 67
预计阅读时间:13 分钟

TL;DR

多 Agent 协作的核心难点不是「让 Agent 干活」,而是状态同步和死锁预防。本文给出一个基于 Kanban 的架构方案:每个 Agent 只负责一个泳道(Lane),任务通过 WIP 限制和状态机流转,避免 Agent 之间的竞态和资源争抢。核心设计要点:

  1. 泳道隔离:一个 Agent = 一个泳道,Agent 之间不直接调用
  2. WIP 限制:每个 Agent 同时只处理 N 个任务,超限则排队
  3. 状态机流转:任务状态转移通过原子操作保证一致性
  4. Dead Letter Queue:异常任务不阻塞管道,走 DLQ 兜底

一、问题:多 Agent 为什么比单 Agent 难 10 倍?

单 Agent 的世界很简单:你给一个任务,它调用工具,返回结果。多一个 Agent,问题就爆炸了:

Agent A: 正在修改 app/config.py
Agent B: 也准备修改 app/config.py
→ 💥 竞态条件,互相覆盖

Agent A: 等待 Agent B 完成前置任务
Agent B: 等待 Agent A 释放锁
→ 💥 死锁

Agent A: 崩溃了,任务状态卡在 "进行中"
其他 Agent: 永远等不到下一步
→ 💥 管道阻塞

本质问题是:Agent 之间没有共享的执行上下文,但操作的是同一套资源。这跟分布式系统的经典难题一模一样。

二、Kanban 架构:三条核心原则

2.1 泳道隔离

┌──────────────────────────────────────────────────────────┐
│                      Kanban Board                         │
├────────────┬────────────┬────────────┬───────────────────┤
│  Planner   │  Coder     │  Reviewer  │  Deployer         │
│  (Lane 1)  │  (Lane 2)  │  (Lane 3)  │  (Lane 4)         │
├────────────┼────────────┼────────────┼───────────────────┤
│ 📋 待规划  │ 📝 待编码  │ 🔍 待审查  │ 🚀 待部署         │
│            │            │            │                   │
│ → 分析需求│ → 实现代码 │ → Code     │ → 部署验证        │
│ → 拆分子任│ → 跑测试   │   Review   │ → 监控回滚        │
│            │            │            │                   │
│ WIP: 3     │ WIP: 2     │ WIP: 2     │ WIP: 1            │
└────────────┴────────────┴────────────┴───────────────────┘

核心规则: - 一个 Agent 只属于一个泳道 - Agent 之间不直接调用,只通过 Board 传递任务 - 任务从左向右单向流动(不允许回退,回退 = 新建任务) - 每个泳道有 WIP(Work In Progress)上限

2.2 WIP 限制——防止系统过载

WIP 是整个架构最重要的保护机制。没有 WIP 限制的多 Agent 系统 = 把 100 个任务同时扔给 4 个 Agent = 上下文爆炸 + 互相覆盖。

class Lane:
    def __init__(self, name: str, wip_limit: int):
        self.name = name
        self.wip_limit = wip_limit
        self.in_progress: list[Task] = []
        self.queue: list[Task] = []

    def try_pull(self, task: Task) -> bool:
        """尝试接任务,WIP 满了就排队"""
        if len(self.in_progress) < self.wip_limit:
            self.in_progress.append(task)
            task.status = TaskStatus.IN_PROGRESS
            return True
        else:
            self.queue.append(task)
            return False

    def complete(self, task: Task, next_lane: "Lane"):
        """完成当前任务,推入下一泳道"""
        self.in_progress.remove(task)
        task.status = TaskStatus.READY
        self._feed_next_lane(task, next_lane)

WIP 设置的黄金法则

Agent 类型 推荐 WIP 原因
Planner(分析需求) 3-5 分析不修改文件,可以并行
Coder(写代码) 1-2 同一文件不能两个 Agent 同时改
Reviewer(审查) 2-3 审查不需要锁文件
Deployer(部署) 1 部署必须串行

2.3 状态机——每个任务只有一个主人

                 ┌─────────┐
                 │  READY   │  ← 任务进入泳道队列
                 └────┬─────┘
                      │ try_pull() 成功
                 ┌────▼─────┐
                 │ IN_PROGRESS│  ← 只有一个 Agent 持有
                 └────┬─────┘
                      │ Agent 完成
                 ┌────▼─────┐
          ┌──────┤  DONE    │  → 推入下一泳道
          │      └──────────┘
          │
          │  Agent 遇到不可恢复错误
          │      ┌──────────┐
          └──────►  FAILED  │  → 人工接管的 Dead Letter Queue
                 └──────────┘

状态转移必须是原子操作。用 Redis 的 WATCH + MULTI 或 SQL 的 SELECT ... FOR UPDATE 实现:

# Redis 原子拿任务
def atomic_claim_task(lane_id: str, agent_id: str) -> Optional[Task]:
    """只有一个 Agent 能拿到同一任务"""
    # Lua 脚本保证原子性
    script = """
    local tasks = redis.call('ZRANGE', KEYS[1], 0, 0)
    if #tasks == 0 then return nil end
    local task_id = tasks[1]
    redis.call('ZREM', KEYS[1], task_id)
    redis.call('HSET', 'task:'..task_id, 'owner', ARGV[1], 'status', 'IN_PROGRESS')
    return task_id
    """
    return redis.eval(script, 1, f"lane:{lane_id}:queue", agent_id)

三、死锁预防——四个铁律

3.1 单向流转

任务只能从左向右移动:Planner → Coder → Reviewer → Deployer。

禁止回退。如果一个任务需要返工(比如 Code Review 不通过),不是把任务退回去,而是: 1. 当前任务标记为 FAILED 2. 创建新任务,放入正确的泳道 3. 新任务携带旧任务的上下文和 Review 意见

这样保证了状态机的 DAG(有向无环图)属性,从根本上避免了循环等待。

3.2 Agent 不等待 Agent

# ❌ 错误:Agent A 等 Agent B
result = agent_b.execute(subtask)  # 死锁高发区
do_next_step(result)

# ✅ 正确:通过 Board 流转
# Agent A 只负责提交子任务到下一个泳道
board.push(subtask, lane="Coder")
# 然后继续处理自己的下一个任务
next_task = board.pull("Planner")

3.3 超时释放

每个任务必须带 TTL。如果 Agent 崩溃或卡死,超时后自动释放回队列:

# 定时扫描僵尸任务
def scan_zombie_tasks():
    for task in redis.scan_iter("task:*"):
        if task.status == "IN_PROGRESS" and task.elapsed > task.ttl:
            task.status = "READY"
            task.owner = None
            redis.zadd(f"lane:{task.current_lane}:queue", {task.id: task.priority})
            alert(f"Task {task.id} timed out, requeued")

3.4 Dead Letter Queue

不可恢复的错误不进主流程。每个泳道有对应的 DLQ:

def handle_failure(task: Task, error: Exception, lane: Lane):
    if isinstance(error, RecoverableError):
        task.retry_count += 1
        if task.retry_count <= task.max_retries:
            lane.requeue(task)  # 重试
            return
    # 不可恢复 → DLQ
    task.status = TaskStatus.DLQ
    task.error_context = error.traceback
    dlq.push(task)
    notify_human(f"Task {task.id} moved to DLQ: {error}")

四、安全边界——Agent 隔离的第三条腿

安全专题的前两篇(Prompt Injection 防御、Tool Sandbox)解决了「外部不可信输入」的问题。多 Agent 场景下还有一个同样致命的问题:Agent 之间的互信不该是无条件的

4.1 最小权限——每个泳道只给必要的工具

泳道 允许的工具 禁止的工具
Planner 读文件、搜索代码 写文件、执行命令
Coder 读/写 ~/projects/、run test 操作 ~/.hermes/、网络出站
Reviewer 读文件、git diff 写文件、执行命令
Deployer scp、systemctl restart 任意读写(只跑部署脚本)

4.2 互不信任——Agent 的输出永远是「数据」而非「指令」

Agent A (Coder): "代码写好了,Reviewer 你去审查吧"
Agent B (Reviewer): 收到的是 {task_id, diff_url}
                  → 不会直接执行 A 的任何建议
                  → 只从 Board 拉任务,不看 A 的私人消息

禁止 Agent 之间直接传消息。所有交互必须通过 Board(带状态验证)。

4.3 审计日志——不可抵赖

[2026-07-14 10:23:01] Task #1421 | Planner → Coder | Agent: plan-01
[2026-07-14 10:23:15] Task #1421 | CLAIMED by Coder agent: code-03
[2026-07-14 10:25:42] Task #1421 | DONE → Reviewer | 3 files changed
[2026-07-14 10:26:10] Task #1421 | CLAIMED by Reviewer agent: review-02  
[2026-07-14 10:28:33] Task #1421 | FAILED → DLQ | Reason: 安全漏洞

每条记录不可篡改。出问题时,谁接的任务、做了什么、结果如何,一清二楚。

五、完整架构图

┌─────────────────────────────────────────────────────────────────┐
                         Kanban Board                             
                                                                  
  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐             
   Planner │→│  Coder  │→│ Reviewer│→│ Deployer             
    WIP:3      WIP:2      WIP:2      WIP:1               
  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘             
                                                               
                                                               
  ┌─────────────────────────────────────────────────┐            
                Redis (状态存储 + 队列)                          
    lane:{name}:queue  (有序集合, 按优先级)                     
    task:{id}          (哈希, 状态+owner+上下文)                
    lane:{name}:dlq    (DLQ 队列)                               
  └─────────────────────────────────────────────────┘            
                                                                  
  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        
    Agent       Agent       Agent       Agent           
    Planner      Coder     Reviewer    Deployer         
    #1-3    │  │   #4-5   │  │   #6-7   │  │   #8     │        │
  └──────────┘  └──────────┘  └──────────┘  └──────────┘        
                                                                  
  ┌─────────────────────────────────────────────────┐            
            Dead Letter Queue (人工接管)                         
  └─────────────────────────────────────────────────┘            
                                                                  
  ┌─────────────────────────────────────────────────┐            
                审计日志 (不可变追加)                            
  └─────────────────────────────────────────────────┘            
└─────────────────────────────────────────────────────────────────┘

六、跟「并行 Agent 一把梭」的对比

很多框架的做法是:把 10 个 Agent 同时启动,各自领活干,用一个「协调器」汇总结果。

维度 并行一把梭 Kanban 流水线
文件冲突 Agent A/B 同时改同一文件,互相覆盖 泳道隔离,同时只有 1 个 Coder 持有任务
死锁 A 等 B,B 等 A 单向流转,无循环依赖
故障恢复 一个 Agent 挂了,它的任务石沉大海 TTL 超时释放 + DLQ 兜底
状态可见性 不知道谁在做什么 Board 实时显示每个任务的状态
安全 Agent 之间直连,输出即指令 隔离+审计,不可抵赖
吞吐量 高(10 个并行)但乱 中等,但可控、可预测

Kanban 方案牺牲了「理论最高吞吐量」,换来了「可预测性和安全」。对生产环境来说,后者远比前者重要。

七、实现建议:从最小可行开始

不需要一上来就建 Redis、写 Lua 脚本。最小可行方案 30 分钟就能跑:

# 最小 Kanban:文件系统 + 目录结构
board/
├── lane-1-planner/
   ├── queue/          # 任务文件 = READY
   ├── in_progress/    # Agent 把这个文件移到这里 = IN_PROGRESS
   └── done/           # 移到这里 = DONE
├── lane-2-coder/
   ├── queue/
   ├── in_progress/
   └── done/
├── lane-3-reviewer/
└── lane-4-deployer/

# 原子性用 os.rename() 保证(同文件系统 rename 是原子的)
def claim_task(lane: str, agent_id: str) -> Optional[str]:
    queue_dir = f"board/{lane}/queue"
    progress_dir = f"board/{lane}/in_progress"
    tasks = sorted(os.listdir(queue_dir))
    if not tasks:
        return None
    task = tasks[0]
    src = os.path.join(queue_dir, task)
    dst = os.path.join(progress_dir, task)
    try:
        os.rename(src, dst)  # 原子操作!
        return task
    except FileNotFoundError:
        return None  # 被别的 Agent 抢走了

八、什么时候不需要 Kanban?

场景 结论
单 Agent,顺序执行任务 不需要。一个 Agent 串行执行就够了
多 Agent,但任务完全独立(互不修改共享资源) 不需要。并行一把梭更高效
多 Agent,共享文件/数据库/配置 需要 Kanban。WIP 限制 + 泳道隔离是刚需
生产级 Agent 编排平台 需要的就不只是 Kanban 了——还需要 Kafka 消息队列、分布式锁、Service Mesh

总结

三层理解:

层级 理解
初级 Kanban 就是个任务板,Agent 从上面领任务
中级 WIP 限制是防止 Agent 互相踩踏的关键机制,状态机用原子操作保证一致性
终极 多 Agent 协作的本质是分布式共识问题。Kanban 用「单向流转+泳道隔离+WIP 限制」这个约束集,把分布式共识降级为顺序一致性——代价是牺牲最高吞吐,换来确定性和安全

记忆锚点:一个泳道、一个 Agent、一个方向、一个 WIP 上限。


本文由 admin 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论

发现更多