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
```python
# ❌ 错误: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 上限。