6.4 KiB
s16: Autonomous Agents — 自己看板,自己认领
s01 → ... → s14 → s15 → s16 → s17 → s18 → s19 → s20 → s21
"空闲时不只等消息,也主动寻找可以开始的工作。" — 共享任务板、自动发现与原子认领。
Harness 层:自治 — Lead 管目标,队友从任务状态中发现下一步。
问题
s15 的队友会在完成一项工作后进入 IDLE,等待 Lead 继续派发。如果任务板上已经有十个待办任务,Lead 仍然要逐个选择队友、发送消息,再等待结果。
当任务已经被拆分,并且依赖关系也写进了任务板,谁来执行下一项工作不一定需要 Lead 再做一次模型决策。空闲队友可以直接读取共享状态,找到已经满足条件的任务并认领它。
解决方案
s16 不改变 s15 的团队生命周期,只扩展 IDLE 状态:
s15: WORK → result → IDLE → 等待消息
s16: WORK → result → IDLE → 等待消息
└→ 扫描任务板 → 认领 → WORK
新增两个函数:
scan_unclaimed_tasks():找出当前可以开始的任务。claim_next_task(name):尝试原子认领其中一个任务。
队友工具集同时增加 list_tasks、claim_task 和 complete_task,让认领后的工作能在同一个循环中闭合。
工作原理
1. 发现任务和认领任务是两步
扫描只读取状态,不修改任务:
def scan_unclaimed_tasks() -> list[Task]:
return [
task for task in list_tasks()
if (
task.status == "pending"
and task.owner is None
and can_start(task.id)
)
]
一个任务必须同时满足三个条件:
- 状态是
pending; - 还没有
owner; blockedBy中的任务都已经完成。
扫描得到的只是候选列表。另一个队友可能在下一瞬间认领同一任务,因此不能把“扫描到”当成“已经拥有”。
2. claim 在锁内完成读、检查和写入
claim_task() 使用同一把 task_lock 包住完整的读改写过程:
def claim_task(task_id: str, owner: str) -> str:
with task_lock:
task = load_task(task_id)
if task.status != "pending" or task.owner:
return "Task is no longer available"
if not can_start(task_id):
return "Task is blocked"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task.id}"
claim_next_task() 依次尝试候选任务。某次认领失败时,它会继续尝试下一个,而不是把失败误当成成功:
def claim_next_task(name: str) -> Task | None:
for task in scan_unclaimed_tasks():
result = claim_task(task.id, owner=name)
if result.startswith("Claimed "):
return load_task(task.id)
return None
扫描负责发现,claim 负责所有权。把两者分开后,多个队友可以同时观察任务板,但每个任务只能有一个最终 owner。
3. 消息优先,任务扫描其次
队友进入 IDLE 后,先等待一小段时间的收件箱事件:
while True:
inbox = BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL)
if inbox:
handle_messages(inbox)
break
task = claim_next_task(name)
if task:
messages.append({
"role": "user",
"content": (
f"[Auto-claimed task {task.id}] "
f"{task.subject}\n{task.description}"
),
})
break
这样安排有两个原因:
- 关机、计划审批和 Lead 的直接消息应该尽快响应;
- 没有消息时,空闲时间才用于寻找共享任务。
如果既没有消息也没有可认领任务,队友继续保持 IDLE,不会因为一次扫描为空就退出。
4. 自动认领后复用同一个 WORK 循环
认领成功后,运行时把任务 ID、标题和描述写入队友 messages。对模型来说,它只是收到了一项新工作;文件、Shell、计划闸门、结果上报都继续使用 s15 的机制。
任务板出现 ready task
→ 空闲队友扫描到候选
→ claim_task 写入 owner 和 in_progress
→ 任务进入队友 messages
→ WORK
→ complete_task
→ result + idle_notification
→ 再次扫描
自治是在既有 Agent Loop 上增加一个由共享状态触发的入口。
为什么这样设计
为什么不是 Lead 每次分配?
任务依赖已经编码在 status、owner 和 blockedBy 中。让 Lead 反复解释同一状态,只会增加协调轮次。
为什么不是扫描时直接改 owner?
扫描可能并发发生。把认领集中到带锁的函数中,所有调用方共享同一个所有权规则。
为什么不在没有任务时关闭队友?
暂时没有 ready task 可能只是因为依赖尚未完成。保持 IDLE 后,前置任务完成时队友可以自动接上后续工作。
相对 s15 的变化
| 组件 | s15 | s16 |
|---|---|---|
| IDLE 行为 | 等待团队消息 | 先等消息,再扫描任务板 |
| 任务分配 | Lead 明确派发 | 队友可自动认领 |
| 任务所有权 | 调用方发起 claim | task_lock 保证认领原子性 |
| 队友工具 | 文件、Shell、消息、计划 | 增加 list / claim / complete task |
| 结果与关机 | result、idle_notification、shutdown 协议 |
保持不变 |
试一下
cd learn-claude-code
python s16_autonomous_agents/code.py
输入一个自然需求:
请把后端改造拆到共享任务板,按依赖关系并行完成配置、认证和测试,
保持现有接口兼容,并在最后汇总结果。
Lead 提出团队方案后回复:
开始吧
观察 .tasks/ 中任务如何从 pending 进入 in_progress 和 completed,以及两个空闲队友是否会认领不同任务。带 blockedBy 的任务应该只在前置任务完成后出现为候选。
接下来
队友已经能自己找到任务,但仍然在同一个工作目录里修改文件。下一章把任务所有权和工作目录绑定起来,让并行工作彼此隔离。