Files
analysis_claude_code/s16_autonomous_agents/README.zh.md
2026-07-31 03:54:12 +08:00

208 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# s16: Autonomous Agents — 自己看板,自己认领
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → s15 → `s16` → [s17](../s17_worktree_isolation/) → s18 → s19 → s20 → s21
> *"空闲时不只等消息,也主动寻找可以开始的工作。"* — 共享任务板、自动发现与原子认领。
>
> **Harness 层**:自治 — Lead 管目标,队友从任务状态中发现下一步。
---
## 问题
s15 的队友会在完成一项工作后进入 IDLE等待 Lead 继续派发。如果任务板上已经有十个待办任务Lead 仍然要逐个选择队友、发送消息,再等待结果。
当任务已经被拆分,并且依赖关系也写进了任务板,谁来执行下一项工作不一定需要 Lead 再做一次模型决策。空闲队友可以直接读取共享状态,找到已经满足条件的任务并认领它。
---
## 解决方案
![Autonomous Agents Overview](images/autonomous-agents-overview.svg)
s16 不改变 s15 的团队生命周期,只扩展 IDLE 状态:
```text
s15: WORK → result → IDLE → 等待消息
s16: WORK → result → IDLE → 等待消息
└→ 扫描任务板 → 认领 → WORK
```
新增两个函数:
- `scan_unclaimed_tasks()`:找出当前可以开始的任务。
- `claim_next_task(name)`:尝试原子认领其中一个任务。
队友工具集同时增加 `list_tasks``claim_task``complete_task`,让认领后的工作能在同一个循环中闭合。
---
## 工作原理
### 1. 发现任务和认领任务是两步
扫描只读取状态,不修改任务:
```python
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` 包住完整的读改写过程:
```python
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()` 依次尝试候选任务。某次认领失败时,它会继续尝试下一个,而不是把失败误当成成功:
```python
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 后,先等待一小段时间的收件箱事件:
```python
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 的机制。
```text
任务板出现 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 协议 | 保持不变 |
---
## 试一下
```sh
cd learn-claude-code
python s16_autonomous_agents/code.py
```
输入一个自然需求:
```text
请把后端改造拆到共享任务板,按依赖关系并行完成配置、认证和测试,
保持现有接口兼容,并在最后汇总结果。
```
Lead 提出团队方案后回复:
```text
开始吧
```
观察 `.tasks/` 中任务如何从 `pending` 进入 `in_progress``completed`,以及两个空闲队友是否会认领不同任务。带 `blockedBy` 的任务应该只在前置任务完成后出现为候选。
---
## 接下来
队友已经能自己找到任务,但仍然在同一个工作目录里修改文件。下一章把任务所有权和工作目录绑定起来,让并行工作彼此隔离。
下一章:[s17 Worktree Isolation](../s17_worktree_isolation/)。
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->