第 17 章 / 共 21 章
自治代理:从被指派到自己认领
上一章留下的问题:现在的队友是被动的——Lead 派一件,它做一件。任务板上躺着十个待办,Lead 就得发十次指派消息。而且队友做完当前工作就退出了,新任务解锁时它已经不在了。
Lead 变成了瓶颈。它把大部分 token 花在了调度上,而不是判断上。
17.1 三个状态
给队友一个状态机:
| 状态 | 在做什么 | 退出条件 |
|---|---|---|
| WORK | 读收件箱 → 调模型 → 执行工具,循环 | 模型不再调工具(stop_reason != "tool_use") |
| IDLE | 每 5 秒轮询一次:先看收件箱,再扫任务板 | 有消息 / 认领到任务 → 回 WORK;60 秒无事 → SHUTDOWN |
| SHUTDOWN | 汇报总结,退出 | — |
关键改动是:**做完一轮工作后进入 IDLE,而不是直接退出。**这一个改动同时解决了三件事——队友能接住后续解锁的任务、能收到关机请求(第16章的遗留问题)、能响应临时指派。

17.2 空闲轮询
IDLE_POLL_INTERVAL = 5 # 秒
IDLE_TIMEOUT = 60 # 秒
def idle_poll(agent_name: str) -> str:
"""返回 'work' / 'shutdown' / 'timeout'"""
for _ in range(IDLE_TIMEOUT // IDLE_POLL_INTERVAL):
time.sleep(IDLE_POLL_INTERVAL)
inbox = BUS.read_inbox(agent_name) # ① 收件箱优先
if inbox:
for msg in inbox:
if msg.get("type") == "shutdown_request":
return "shutdown"
requeue(agent_name, inbox) # 其余消息留给 WORK 处理
return "work"
unclaimed = scan_unclaimed_tasks() # ② 再扫任务板
if unclaimed:
task = unclaimed[0]
if "Claimed" in claim_task(task["id"], agent_name):
return "work"
return "timeout"
def scan_unclaimed_tasks() -> list[dict]:
out = []
for p in TASKS_DIR.glob("*.json"):
t = json.loads(p.read_text())
if t["status"] == "pending" and not t["owner"] and can_start(t["id"]):
out.append(t)
return out
收件箱优先于任务板,理由很实际:来自 Lead 或同伴的消息通常带有更高的时效性(“停下来”、“先做这个”、“我改了接口”)。如果先扫任务板,队友可能刚认领了一个新任务,才发现 Lead 十秒前让它停下。
scan_unclaimed_tasks 里三个条件缺一不可:状态是 pending(没在做也没做完)、没有 owner(没被别人认领)、can_start 为真(上游依赖都完成了)。少任何一条,你都会得到重复劳动或者提前开工。
17.3 走一遍:一次自治协作
任务板:A(无依赖)、B(依赖 A)、C(依赖 A)、D(依赖 B、C)。队友:alice、bob。
| 时刻 | alice | bob | 任务板 |
|---|---|---|---|
| T0 | 扫到 A,认领,进入 WORK | 扫描:B/C/D 都被阻塞 → IDLE | A 进行中 |
| T1 | 完成 A → IDLE | 仍在 IDLE 轮询 | A 完成,B/C 解锁 |
| T2 | 轮询扫到 B,认领 | 轮询扫到 C,认领 | B、C 并行进行 |
| T3 | 完成 B → IDLE | 完成 C → IDLE | D 解锁 |
| T4 | 先轮询到,认领 D | 扫不到可认领的任务 | D 进行中 |
| T5 | 完成 D → IDLE 60 秒 → 汇报退出 | IDLE 60 秒 → 汇报退出 | 全部完成 |
**Lead 在这段过程里一次指派都没发。**它只需要在开头把任务图建好,之后队友自己按依赖顺序推进。Lead 从”调度员”变成了”规划者”——这才是它该干的事。
17.4 工程现实
自治带来了两个新问题,教学版都没解决:
认领竞态。 alice 和 bob 可能在同一毫秒读到同一个任务的 status == "pending",然后都写入认领。真实系统会对任务文件加锁并配短退避重试。教学版靠”轮询间隔 5 秒 + 队友数量少”降低概率,但没有消除它。判断你的系统需不需要锁,看的是”同时扫描的 Agent 数量”和”任务数量”的比值。
身份丢失。 队友的上下文也会被压缩(第8章)。压缩之后,“我是 alice,负责认证模块”这句话可能被摘要掉,队友开始不知道自己是谁、该认领什么。解法是在每次压缩后重新注入身份——把角色说明当作和系统提示词同级的常驻内容,而不是普通对话历史。
另外两个可以改进的点:
- **固定 60 秒超时太死板。**长跑任务的队友可能需要等更久。可以做成指数增长的轮询间隔。
- **认领策略是”第一个”。**没有优先级概念。真实场景里应该按任务优先级、依赖深度(先做阻塞了最多下游的任务)或者队友专长来挑。