Autonomous Agents
Multi-Agent PlatformSelf-Claim, Self-Resume|603 LOC|14 tools
Autonomy is a bounded mechanism -- idle, scan, claim, resume -- not magic.
s00 > s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > [ s17 ] > s18 > s19
一个团队真正开始“自己运转”,不是因为 agent 数量变多,而是因为空闲的队友会自己去找下一份工作。
这一章要解决什么问题
到了 s16,团队已经有:
- 持久队友
- 邮箱
- 协议
- 任务板
但还有一个明显瓶颈:
很多事情仍然要靠 lead 手动分配。
例如任务板上已经有 10 条可做任务,如果还要 lead 一个个点名:
- Alice 做 1
- Bob 做 2
- Charlie 做 3
那团队规模一大,lead 就会变成瓶颈。
所以这一章要解决的核心问题是:
让空闲队友自己扫描任务板,找到可做的任务并认领。
建议联读
- 如果你开始把 teammate、task、runtime slot 三层一起讲糊,先回
team-task-lane-model.md。 - 如果你读到“auto-claim”时开始疑惑“活着的执行槽位”到底放在哪,继续看
s13a-runtime-task-model.md。 - 如果你开始忘记“长期队友”和“一次性 subagent”最根本的区别,回看
entity-map.md。
先解释几个名词
什么叫自治
这里的自治,不是完全没人管。
这里说的自治是:
在提前给定规则的前提下,队友可以自己决定下一步接哪份工作。
什么叫认领
认领,就是把一条原本没人负责的任务,标记成“现在由我负责”。
什么叫空闲阶段
空闲阶段不是关机,也不是消失。
它表示:
这个队友当前手头没有活,但仍然活着,随时准备接新活。
最小心智模型
最清楚的理解方式,是把每个队友想成在两个阶段之间切换:
WORK
|
| 当前轮工作做完,或者主动进入 idle
v
IDLE
|
+-- 看邮箱,有新消息 -> 回到 WORK
|
+-- 看任务板,有 ready task -> 认领 -> 回到 WORK
|
+-- 长时间什么都没有 -> shutdown
这里的关键不是“让它永远不停想”,而是:
空闲时,按规则检查两类新输入:邮箱和任务板。
关键数据结构
1. Claimable Predicate
和 s12 一样,这里最重要的是:
什么任务算“当前这个队友可以安全认领”的任务。
在当前教学代码里,判定已经不是单纯看 pending,而是:
def is_claimable_task(task: dict, role: str | None = None) -> bool:
return (
task.get("status") == "pending"
and not task.get("owner")
and not task.get("blockedBy")
and _task_allows_role(task, role)
)
这 4 个条件缺一不可:
- 任务还没开始
- 还没人认领
- 没有前置阻塞
- 当前队友角色满足认领策略
最后一条很关键。
因为现在任务可以带:
claim_rolerequired_role
例如:
task = {
"id": 7,
"subject": "Implement login page",
"status": "pending",
"owner": "",
"blockedBy": [],
"claim_role": "frontend",
}
这表示:
这条任务不是“谁空着谁就拿”,而是要先过角色条件。
2. 认领后的任务记录
一旦认领成功,任务记录至少会发生这些变化:
{
"id": 7,
"owner": "alice",
"status": "in_progress",
"claimed_at": 1710000000.0,
"claim_source": "auto",
}
这里新增的两个字段很值得单独记住:
claimed_at:什么时候被认领claim_source:这次认领是auto还是manual
因为到这一步,系统开始不只是知道“任务现在有人做了”,还开始知道:
- 这是谁拿走的
- 是主动扫描拿走,还是手动点名拿走
3. Claim Event Log
除了回写任务文件,这章还会把认领动作追加到:
.tasks/claim_events.jsonl
每条事件大致长这样:
{
"event": "task.claimed",
"task_id": 7,
"owner": "alice",
"role": "frontend",
"source": "auto",
"ts": 1710000000.0,
}
为什么这层日志重要?
因为它回答的是“自治系统刚刚做了什么”。
只看最终任务文件,你知道的是:
- 现在是谁 owner
而看事件日志,你才能知道:
- 它是什么时候被拿走的
- 是谁拿走的
- 是空闲时自动拿走,还是人工调用
claim_task
4. Durable Request Record
这章虽然重点是自治,但它不能从 s16 退回到“协议请求只放内存里”。
所以当前代码里仍然保留了持久化请求记录:
.team/requests/{request_id}.json
它保存的是:
- shutdown request
- plan approval request
- 对应的状态更新
这层边界很重要,因为自治队友并不是在“脱离协议系统另起炉灶”,而是:
在已有团队协议之上,额外获得“空闲时自己找活”的能力。
5. 身份块
当上下文被压缩后,队友有时会“忘记自己是谁”。
最小补法是重新注入一段身份提示:
identity = {
"role": "user",
"content": "<identity>You are 'alice', role: frontend, team: default. Continue your work.</identity>",
}
当前实现里还会同时补一条很短的确认语:
{"role": "assistant", "content": "I am alice. Continuing."}
这样做的目的不是好看,而是为了让恢复后的下一轮继续知道:
- 我是谁
- 我的角色是什么
- 我属于哪个团队
最小实现
第一步:让队友拥有 WORK -> IDLE 的循环
while True:
run_work_phase(...)
should_resume = run_idle_phase(...)
if not should_resume:
break
第二步:在 IDLE 里先看邮箱
def idle_phase(name: str, messages: list) -> bool:
inbox = bus.read_inbox(name)
if inbox:
messages.append({
"role": "user",
"content": json.dumps(inbox),
})
return True
这一步的意思是:
如果有人明确找我,那我优先处理“明确发给我的工作”。
第三步:如果邮箱没消息,再按“当前角色”扫描可认领任务
unclaimed = scan_unclaimed_tasks(role)
if unclaimed:
task = unclaimed[0]
claim_result = claim_task(
task["id"],
name,
role=role,
source="auto",
)
这里当前代码有两个很关键的升级:
scan_unclaimed_tasks(role)不是无差别扫任务,而是带着角色过滤claim_task(..., source="auto")会把“这次是自治认领”显式写进任务与事件日志
也就是说,自治不是“空闲了就乱抢一条”,而是:
按当前队友的角色、任务状态和阻塞关系,挑出一条真正允许它接手的工作。
第四步:认领后先补身份,再把任务提示塞回主循环
ensure_identity_context(messages, name, role, team_name)
messages.append({
"role": "user",
"content": f"<auto-claimed>Task #{task['id']}: {task['subject']}</auto-claimed>",
})
messages.append({
"role": "assistant",
"content": f"{claim_result}. Working on it.",
})
return True
这一步非常关键。
因为“认领成功”本身还不等于“队友真的能顺利继续”。
还必须把两件事接回上下文里:
- 身份上下文
- 新任务提示
只有这样,下一轮 WORK 才不是无头苍蝇,而是:
带着明确身份和明确任务恢复工作。
第五步:长时间没事就退出
time.sleep(POLL_INTERVAL)
...
return False
为什么需要这个退出路径?
因为空闲队友不一定要永远占着资源。
教学版先做“空闲一段时间后关闭”就够了。
为什么认领必须是原子动作
“原子”这个词第一次看到可能不熟。
这里它的意思是:
认领这一步要么完整成功,要么不发生,不能一半成功一半失败。
为什么?
因为两个队友可能同时扫描到同一个可做任务。
如果没有锁,就可能发生:
- Alice 看见任务 3 没主人
- Bob 也看见任务 3 没主人
- 两人都把自己写成 owner
所以最小教学版也应该加一个认领锁:
with claim_lock:
task = load(task_id)
if task["owner"]:
return "already claimed"
task["owner"] = name
task["status"] = "in_progress"
save(task)
身份重注入为什么重要
这是这章里一个很容易被忽视,但很关键的点。
当上下文压缩发生以后,队友可能丢掉这些关键信息:
- 我是谁
- 我的角色是什么
- 我属于哪个团队
如果没有这些信息,队友后续行为很容易漂。
所以一个很实用的做法是:
如果发现 messages 的开头已经没有身份块,就把身份块重新插回去。
这里你可以把它理解成一条恢复规则:
任何一次从 idle 恢复、或任何一次压缩后恢复,只要身份上下文可能变薄,就先补身份,再继续工作。
为什么 s17 不能从 s16 退回“内存协议”
这是一个很容易被漏讲,但其实非常重要的点。
很多人一看到“自治”,就容易只盯:
- idle
- auto-claim
- 轮询
然后忘了 s16 已经建立过的另一条主线:
- 请求必须可追踪
- 协议状态必须可恢复
所以现在教学代码里,像:
- shutdown request
- plan approval
仍然会写进:
.team/requests/{request_id}.json
也就是说,s17 不是推翻 s16,而是在 s16 上继续加一条新能力:
协议系统继续存在
+
自治扫描与认领开始存在
这两条线一起存在,团队才会像一个真正的平台,而不是一堆各自乱跑的 worker。
如何接到前面几章里
这一章其实是前面几章第一次真正“串起来”的地方:
s12提供任务板s15提供持久队友s16提供结构化协议s17则让队友在没有明确点名时,也能自己找活
所以你可以把 s17 理解成:
从“被动协作”升级到“主动协作”。
自治的是“长期队友”,不是“一次性 subagent”
这层边界如果不讲清,读者很容易把 s04 和 s17 混掉。
s17 里的自治执行者,仍然是 s15 那种长期队友:
- 有名字
- 有角色
- 有邮箱
- 有 idle 阶段
- 可以反复接活
它不是那种:
- 接一条子任务
- 做完返回摘要
- 然后立刻消失
的一次性 subagent。
同样地,这里认领的也是:
s12里的工作图任务
而不是:
s13里的后台执行槽位
所以这章其实是在两条已存在的主线上再往前推一步:
- 长期队友
- 工作图任务
再把它们用“自治认领”连接起来。
如果你开始把下面这些词混在一起:
- teammate
- protocol request
- task
- runtime task
建议回看:
初学者最容易犯的错
1. 只看 pending,不看 blockedBy
如果一个任务虽然是 pending,但前置任务还没完成,它就不应该被认领。
2. 只看状态,不看 claim_role / required_role
这会让错误的队友接走错误的任务。
教学版虽然简单,但从这一章开始,已经应该明确告诉读者:
- 并不是所有 ready task 都适合所有队友
- 角色条件本身也是 claim policy 的一部分
3. 没有认领锁
这会直接导致重复抢同一条任务。
4. 空闲阶段只轮询任务板,不看邮箱
这样队友会错过别人明确发给它的消息。
5. 认领了任务,但没有写 claim event
这样最后你只能看到“任务现在被谁做”,却看不到:
- 它是什么时候被拿走的
- 是自动认领还是手动认领
6. 队友永远不退出
教学版里,长时间无事可做时退出是合理的。
否则读者会更难理解资源何时释放。
7. 上下文压缩后不重注入身份
这很容易让队友后面的行为越来越不像“它本来的角色”。
教学边界
这一章先只把自治主线讲清楚:
空闲检查 -> 安全认领 -> 恢复工作。
只要这条链路稳了,读者就已经真正理解了“自治”是什么。
更细的 claim policy、公平调度、事件驱动唤醒、长期保活,都应该建立在这条最小自治链之后,而不是抢在前面。
试一试
cd learn-claude-code
python agents/s17_autonomous_agents.py
可以试试这些任务:
- 先建几条 ready task,再生成两个队友,观察它们是否会自动分工。
- 建几条被阻塞的任务,确认队友不会错误认领。
- 让某个队友进入 idle,再发一条消息给它,观察它是否会重新被唤醒。
这一章要建立的核心心智是:
自治不是让 agent 乱跑,而是让它在清晰规则下自己接住下一份工作。