第十篇:自主 Agent —— 让 Agent 自己决定做什么
自主 Agent —— 让 Agent 自己决定做什么
最优秀的下属不是"你说一步他做一步",而是"他看到有什么活该干,自己就去干了"。
从"要我干"到"我要干"
回顾之前的演进:
- s01-s06:Agent 被动响应。你说什么,它做什么。你不说,它不动。
- s07-s08:Agent 开始有计划。它能把大任务拆成小任务,但仍然是"你给了任务它才做"。
- s09-s10:Agent 之间能协作,但 Leader 还是"下指令"的模式:spawn → 分配任务 → 等待完成。
都是"要我干"。
s11 把最后一层依赖也去掉了。 Agent 可以:
- 完成工作后进入 IDLE 状态
- 在 IDLE 状态下,定时扫描任务板
- 发现未认领的任务 → 自动认领
- 认领后自动恢复 WORKING 状态
这是一个从"被动执行者"到"主动工作者"的跃迁。
三阶段生命周期
s11 的 Agent 生命周期变成了三个阶段:
+-------+
| SPAWN | ← 被 Lead spawn
+---+---+
|
v
+-------+ tool_use +-------+
| WORK | ←─────────── | LLM | 工作阶段:正常 agent loop
+---+---+ +-------+
|
| stop_reason != tool_use
v
+--------+
| IDLE | 轮询最多 60 秒,每 5 秒一次
+---+----+
|
├──→ 检查 inbox → 有消息?→ 恢复 WORK
|
├──→ 扫描 .tasks/ → 有未认领任务?→ 认领 → 恢复 WORK
|
└──→ 60 秒超时 → SHUTDOWN
代码实现比描述更直白:
class TeammateManager:
def _loop(self, name: str, role: str, prompt: str):
while True:
# ── WORK PHASE ──
for _ in range(50): # 最多 50 轮工具调用
inbox = BUS.read_inbox(name)
for msg in inbox:
if msg.get("type") == "shutdown_request":
self._set_status(name, "shutdown")
return # 直接退出
messages.append(...)
response = client.messages.create(...)
messages.append(response)
if response.stop_reason != "tool_use":
break # 进入 IDLE
# 执行工具...
if block.name == "idle":
idle_requested = True # 模型主动要求闲置
# ── IDLE PHASE ──
self._set_status(name, "idle")
for _ in range(polls): # polls = IDLE_TIMEOUT / POLL_INTERVAL
time.sleep(POLL_INTERVAL) # 每 5 秒醒一次
inbox = BUS.read_inbox(name)
if inbox:
resume = True
break
unclaimed = scan_unclaimed_tasks()
if unclaimed:
task = unclaimed[0]
result = claim_task(task["id"], name)
if not result.startswith("Error:"):
resume = True
break
if not resume:
self._set_status(name, "shutdown") # 60 秒无动静 → 关机
return
self._set_status(name, "working") # 找到活 → 恢复工作
“idle” 工具:模型可以主动说"我没事干了"
一个特别有意思的细节是那个 idle 工具:
{"name": "idle", "description": "Signal that you have no more work. Enters idle polling phase.",
"input_schema": {"type": "object", "properties": {}}}
这不是强迫 Agent 闲置的机制——是让 Agent 主动说"我干完了"的工具。
当 Agent 完成当前任务,没有更多 TODO 可以做,它会主动调用 idle:
Agent: 完成了支付模块重构,提交了 PR。进入 idle。
→ 调用 idle 工具
→ 循环进入 IDLE PHASE
→ 每 5 秒扫描一次任务板
→ 发现 task #42 "优化数据库查询" 无人认领
→ 自动认领,注入消息 "Claimed task #42"
→ 恢复 WORKING
这就像你团队里最积极的同事。他干完手里的活,不会干坐着等——他会自己去 backlog 里找下一件该做的事。
模型自己决定什么时候"干完了",而不是 Harness 替它判断。
安全认领:Claim 锁
多 Agent 同时扫任务板时,可能两个人同时看中了同一个任务。s11 用了一个简单的线程级锁来防止重复认领:
_claim_lock = threading.Lock()
def claim_task(task_id: int, owner: str) -> str:
with _claim_lock: # ← 只有一个线程能进入
path = TASKS_DIR / f"task_{task_id}.json"
if not path.exists():
return f"Error: Task {task_id} not found"
task = json.loads(path.read_text())
if task.get("owner"):
return f"Error: Task {task_id} already claimed by {task['owner']}"
if task.get("status") != "pending":
return f"Error: Task {task_id} status is '{task['status']}'"
if task.get("blockedBy"):
return f"Error: Task {task_id} is blocked by other task(s)"
task["owner"] = owner
task["status"] = "in_progress"
path.write_text(json.dumps(task, indent=2))
return f"Claimed task #{task_id} for {owner}"
三重检查 + 文件写入:检查 ownership → 检查状态 → 检查依赖 → 原子写入。
注意:这里的"原子性"是 Python 的 GIL + 文件系统提供的,不是真正的分布式锁。但在单进程多线程的场景下够用了。如果多进程,需要用文件锁(fcntl/msvct),但那是 s12 的事了。
身份重建:压缩后别失忆
s11 还有一个重要的细节——identity re-injection。
上下文压缩后,Agent 的 messages 被替换成了一个摘要。但如果摘要里没有包含身份信息,Agent 醒来后会失忆:
[Conversation compressed]
完成了支付模块重构。当前正在等待 code review。
→ "我是谁?我是什么角色?我属于哪个团队?"
→ 茫然中
s11 的解法是:在压缩后插入身份块:
def make_identity_block(name: str, role: str, team_name: str) -> dict:
return {
"role": "user",
"content": f"<identity>You are '{name}', role: {role}, team: {team_name}. Continue your work.</identity>",
}
当 Agent 从 IDLE 恢复并认领了新任务时:
if len(messages) <= 3: # 说明刚被压缩过
messages.insert(0, make_identity_block(name, role, team_name))
messages.insert(1, {"role": "assistant", "content": f"I am {name}. Continuing."})
这个检查 len(messages) <= 3 的意思是:如果消息列表很短(刚被压缩),插入身份信息。如果消息列表很长(还没压缩过),不需要。
这个设计很小,但很关键。 没有它,自主 Agent 会在第一次压缩后变成一个"没有记忆的打工机器"——它会知道自己要干活,但不知道自己是谁、有什么专长、在团队里扮演什么角色。
自主性带来了什么?
给 Agent 自主性,不只是"更酷了"。它带来了实际的工程价值:
1. 利用率提升
以前 Agent 干完活需要人来派新活。现在它会自己去 task board 上找。人只需要往 board 上扔任务,Agent 群体会自动消化。
2. 弹性伸缩
任务多的时候,更多 Agent 会处于 WORKING 状态。任务少的时候,Agent 做完活自动 IDLE 再 SHUTDOWN。不需要手动管理"扩缩容"。
3. 故障自愈
如果一个 Agent 在处理任务时崩溃了(比如 API 超时),任务会保持在 in_progress 状态。其他 Agent 不会碰它。但如果任务加了超时机制,超时后任务回到 pending,其他 Agent 可以重新认领。
4. 人只需要定义目标
不需要告诉 Agent"第二步做什么、第三步做什么"。只需要把任务扔到 board 上,合格的 Agent 自然会认领和处理。
但自主性有个坑
自主性的反面是不可预测性。
当你的 Agent 开始自己决定做什么的时候,你可能会遇到:
- Agent A 和 B 同时看上了一个任务,争抢(锁机制解决了这个)
- Agent 认领了一个它其实不擅长的任务(没有匹配机制)
- Agent 在 IDLE 扫描时疯狂轮询 API(s11 用 5 秒间隔限制了这个)
- 所有 Agent 都去抢简单任务,复杂任务没人动(没有优先级机制)
s11 的解决方案是最简的——它只解决了"能不能自主"的问题,没解决"自主得好不好"的问题。那些是留给生产系统的优化空间。
下集预告
s11 让 Agent 可以自主干活,但所有 Agent 共享同一个工作目录。有两个 Agent 同时改同一个文件怎么办?
s12 用 git worktree 解决了这个问题——每个任务有自己独立的目录,通过 worktree 隔离,永不碰撞。
下一篇:Worktree 任务隔离 —— 目录即边界
更多推荐
所有评论(0)