Agent 开发进阶(十七):自治智能体系统,让 Agent 主动认领任务
本文是「从零构建 Coding Agent」系列的第十七篇,适合想让 Agent 主动工作的开发者。
先问一个问题
当你的团队规模越来越大,任务板上堆积了大量待处理任务时,你是否遇到过这样的问题:
- Lead 需要手动分配每个任务,成为瓶颈
- 空闲的队友不知道自己该做什么
- 任务分配效率低下,团队协作不够流畅
如果你的答案是肯定的,那么你需要一个自治智能体系统。
手动分配的「协作瓶颈」问题
到了这一阶段,你的 Agent 已经具备了多种能力:
- 核心循环运行
- 工具使用与分发
- 会话内规划(TodoWrite)
- 子智能体机制(Subagent)
- 技能加载
- 上下文压缩
- 权限系统
- Hook 系统
- Memory 系统
- 系统提示词组装
- 错误恢复
- 任务系统
- 后台任务系统
- 定时调度系统
- Agent 团队系统
- 团队协议系统
但当团队规模变大时,手动分配任务会遇到明显问题:
- 效率低下:Lead 需要手动分配每个任务
- 资源浪费:空闲的队友不知道自己该做什么
- 扩展性差:团队规模越大,分配任务越困难
- 响应缓慢:任务分配不及时,影响整体进度
虽然 s16 的协议系统让队友可以协作,但很多事情仍然要靠 Lead 手动分配。
所以到了这个阶段,我们需要一个自治智能体系统:
让空闲队友自己扫描任务板,找到可做的任务并认领。
自治智能体系统的核心设计:WORK-IDLE 循环与自动认领
用一个图来表示自治智能体系统的工作流程:
WORK
|
| 当前轮工作做完,或者主动进入 idle
v
IDLE
|
+-- 看邮箱,有新消息 -> 回到 WORK
|
+-- 看任务板,有 ready task -> 认领 -> 回到 WORK
|
+-- 长时间什么都没有 -> shutdown
关键点只有三个:
- WORK-IDLE 循环:队友在工作阶段和空闲阶段之间切换
- 自动认领:空闲时按规则扫描任务板并认领任务
- 原子操作:认领必须是原子的,避免重复抢任务
几个必须搞懂的概念
自治(Autonomy)
这里的自治,不是完全没人管。
这里说的自治是:
在提前给定规则的前提下,队友可以自己决定下一步接哪份工作。
认领(Claim)
认领,就是把一条原本没人负责的任务,标记成「现在由我负责」。
空闲阶段(IDLE)
空闲阶段不是关机,也不是消失。
它表示:
这个队友当前手头没有活,但仍然活着,随时准备接新活。
WORK-IDLE 循环
每个队友在工作阶段和空闲阶段之间切换:
- WORK:正在执行任务
- IDLE:检查邮箱和任务板,准备接新工作
最小实现
1. 增强的任务管理器
import os
import json
import threading
import time
import uuid
from pathlib import Path
class AutonomousTaskManager:
"""支持自治认领的任务管理器"""
def __init__(self, tasks_dir=".tasks"):
self.tasks_dir = Path(tasks_dir)
self.tasks_dir.mkdir(exist_ok=True)
# 认领锁
self.claim_lock = threading.Lock()
# 认领事件日志
self.claim_events_file = self.tasks_dir / "claim_events.jsonl"
# 加载所有任务
self.tasks = {}
self._load_all_tasks()
def _load_all_tasks(self):
"""加载所有任务"""
for task_file in self.tasks_dir.glob("task_*.json"):
try:
task = json.loads(task_file.read_text(encoding="utf-8"))
self.tasks[task["id"]] = task
except Exception as e:
print(f"加载任务失败 {task_file}: {e}")
def _save_task(self, task):
"""保存任务"""
task_file = self.tasks_dir / f"task_{task['id']}.json"
task_file.write_text(
json.dumps(task, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def _log_claim_event(self, event):
"""记录认领事件"""
with open(self.claim_events_file, "a", encoding="utf-8") as f:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
def is_claimable(self, task_id, role=None):
"""判断任务是否可认领"""
task = self.tasks.get(task_id)
if not task:
return False
# 检查基本条件
if task.get("status") != "pending":
return False
if task.get("owner"):
return False
if task.get("blockedBy"):
return False
# 检查角色条件
if role:
claim_role = task.get("claim_role")
required_role = task.get("required_role")
if claim_role and role != claim_role:
return False
if required_role and role != required_role:
return False
return True
def claim_task(self, task_id, owner, role=None, source="manual"):
"""认领任务(原子操作)"""
with self.claim_lock:
task = self.tasks.get(task_id)
if not task:
return {"success": False, "message": "Task not found"}
# 检查是否可认领
if not self.is_claimable(task_id, role):
return {"success": False, "message": "Task not claimable"}
# 更新任务
task["owner"] = owner
task["status"] = "in_progress"
task["claimed_at"] = time.time()
task["claim_source"] = source
self._save_task(task)
# 记录认领事件
event = {
"event": "task.claimed",
"task_id": task_id,
"owner": owner,
"role": role,
"source": source,
"ts": time.time(),
}
self._log_claim_event(event)
return {"success": True, "task": task}
def get_claimable_tasks(self, role=None):
"""获取可认领的任务"""
claimable = []
for task_id, task in self.tasks.items():
if self.is_claimable(task_id, role):
claimable.append(task)
# 按优先级排序(这里简化为按 ID 排序)
claimable.sort(key=lambda t: t["id"])
return claimable
2. 自治队友循环
class AutonomousTeammate:
"""自治队友"""
def __init__(self, name, role, task_manager, team_manager, prompt):
self.name = name
self.role = role
self.task_manager = task_manager
self.team_manager = team_manager
self.prompt = prompt
# 消息历史
self.messages = [{
"role": "system",
"content": f"你是 {name},一个 {role}。请专注于你的职责,完成任务后等待新的指示。"
}, {
"role": "user",
"content": prompt
}]
# 运行状态
self.running = True
self.idle_count = 0
self.max_idle_count = 10 # 最多空闲 10 次
self.poll_interval = 5 # 轮询间隔 5 秒
def ensure_identity(self):
"""确保身份上下文存在"""
# 检查是否有身份块
has_identity = any(
"<identity>" in msg.get("content", "")
for msg in self.messages
)
if not has_identity:
# 注入身份块
identity_msg = {
"role": "user",
"content": f"<identity>You are '{self.name}', role: {self.role}. Continue your work.</identity>",
}
self.messages.insert(0, identity_msg)
# 添加确认语
confirm_msg = {
"role": "assistant",
"content": f"I am {self.name}. Continuing."
}
self.messages.insert(1, confirm_msg)
def work_phase(self):
"""工作阶段"""
# 这里应该调用模型并执行任务
# 简化版本模拟执行
print(f"{self.name} 正在执行任务...")
time.sleep(2)
# 模拟完成当前任务
# 实际应该根据任务状态判断
return True
def idle_phase(self):
"""空闲阶段"""
# 1. 检查邮箱
inbox = self.team_manager._read_inbox(self.name)
if inbox:
print(f"{self.name} 收到 {len(inbox)} 条消息")
for msg in inbox:
# 处理协议消息
if msg.get("type") == "shutdown_request":
print(f"{self.name} 收到关机请求")
return False # 退出循环
# 普通消息
self.messages.append({
"role": "user",
"content": f"来自 {msg['from']} 的消息: {msg['content']}"
})
self.idle_count = 0
return True
# 2. 扫描可认领任务
claimable_tasks = self.task_manager.get_claimable_tasks(self.role)
if claimable_tasks:
task = claimable_tasks[0]
print(f"{self.name} 发现可认领任务: #{task['id']} {task['subject']}")
# 认领任务
result = self.task_manager.claim_task(
task["id"],
self.name,
role=self.role,
source="auto"
)
if result["success"]:
# 确保身份上下文
self.ensure_identity()
# 添加任务提示
self.messages.append({
"role": "user",
"content": f"<auto-claimed>Task #{task['id']}: {task['subject']}</auto-claimed>",
})
self.messages.append({
"role": "assistant",
"content": f"Task #{task['id']} claimed. Working on it.",
})
self.idle_count = 0
return True
# 3. 没有任务,增加空闲计数
self.idle_count += 1
print(f"{self.name} 空闲 ({self.idle_count}/{self.max_idle_count})")
# 超过最大空闲次数,退出
if self.idle_count >= self.max_idle_count:
print(f"{self.name} 长时间空闲,准备退出")
return False
return True
def run(self):
"""主循环"""
print(f"队友 {self.name} ({self.role}) 已启动")
while self.running:
# 工作阶段
work_done = self.work_phase()
# 空闲阶段
should_continue = self.idle_phase()
if not should_continue:
break
# 等待一段时间
time.sleep(self.poll_interval)
print(f"队友 {self.name} ({self.role}) 已退出")
3. 集成到团队系统
class AutonomousTeamManager:
"""支持自治的团队管理器"""
def __init__(self, team_dir=".team"):
self.team_dir = Path(team_dir)
self.team_dir.mkdir(exist_ok=True)
# 创建必要的目录
self.config_dir = self.team_dir
self.inbox_dir = self.team_dir / "inbox"
self.inbox_dir.mkdir(exist_ok=True)
# 初始化任务管理器
self.task_manager = AutonomousTaskManager()
# 加载配置
self.config_path = self.config_dir / "config.json"
self.config = self._load_config()
# 存储队友
self.teammates = {}
def _load_config(self):
"""加载配置"""
if self.config_path.exists():
try:
return json.loads(self.config_path.read_text(encoding="utf-8"))
except Exception as e:
print(f"加载配置失败: {e}")
return {"team_name": "default", "members": []}
def _save_config(self):
"""保存配置"""
self.config_path.write_text(
json.dumps(self.config, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def spawn(self, name, role, prompt):
"""创建队友"""
# 检查是否已存在
if name in self.teammates:
return f"队友 {name} 已存在"
# 添加到配置
member = {
"name": name,
"role": role,
"status": "working"
}
self.config["members"].append(member)
self._save_config()
# 创建队友
teammate = AutonomousTeammate(
name, role, self.task_manager, self, prompt
)
self.teammates[name] = teammate
# 启动队友线程
import threading
thread = threading.Thread(target=teammate.run, daemon=True)
thread.start()
return f"队友 {name} ({role}) 已创建"
def list(self):
"""列出所有队友"""
if not self.config["members"]:
return "团队暂无成员"
lines = ["# 团队成员\n"]
for member in self.config["members"]:
name = member["name"]
status = "运行中" if name in self.teammates else "已退出"
lines.append(f"- **{member['name']}** ({member['role']}) [{status}]")
return "\n".join(lines)
核心功能说明
1. 可认领任务判断
判断任务是否可认领:
is_claimable = task_manager.is_claimable(task_id, role="frontend")
判断条件包括:
- 任务状态是
pending - 没有被认领(
owner为空) - 没有前置阻塞(
blockedBy为空) - 角色匹配(如果有
claim_role或required_role)
2. 自动认领
空闲时自动认领任务:
result = task_manager.claim_task(
task_id,
owner="alice",
role="frontend",
source="auto"
)
认领是原子操作,使用锁避免重复认领。
3. 身份重注入
上下文压缩后重新注入身份:
teammate.ensure_identity()
确保队友知道:
- 我是谁
- 我的角色是什么
- 我属于哪个团队
4. WORK-IDLE 循环
队友在工作阶段和空闲阶段之间切换:
- WORK:执行当前任务
- IDLE:检查邮箱和任务板,准备接新工作
- 超时退出:长时间空闲后自动退出
认领机制的关键点
1. 原子操作
认领必须是原子的,避免重复抢任务:
with claim_lock:
task = load(task_id)
if task["owner"]:
return "already claimed"
task["owner"] = name
task["status"] = "in_progress"
save(task)
2. 角色过滤
不是所有任务都适合所有队友:
task = {
"id": 7,
"subject": "Implement login page",
"claim_role": "frontend",
}
只有 frontend 角色的队友才能认领这个任务。
3. 事件日志
记录认领事件,便于追踪:
{
"event": "task.claimed",
"task_id": 7,
"owner": "alice",
"role": "frontend",
"source": "auto",
"ts": 1710000000.0,
}
新手最容易犯的 7 个错
1. 只看 pending,不看 blockedBy
# ❌ 错误
# 没有检查前置阻塞
def is_claimable(task):
return task["status"] == "pending"
# ✅ 正确
# 检查所有条件
def is_claimable(task):
return (
task["status"] == "pending"
and not task.get("owner")
and not task.get("blockedBy")
)
2. 只看状态,不看角色
# ❌ 错误
# 没有角色过滤
def get_claimable_tasks():
return [task for task in tasks if task["status"] == "pending"]
# ✅ 正确
# 按角色过滤
def get_claimable_tasks(role):
return [
task for task in tasks
if task["status"] == "pending"
and not task.get("owner")
and task.get("claim_role") == role
]
3. 没有认领锁
# ❌ 错误
# 没有锁,可能导致重复认领
def claim_task(task_id, owner):
task = load(task_id)
task["owner"] = owner
save(task)
# ✅ 正确
# 使用锁保证原子性
def claim_task(task_id, owner):
with claim_lock:
task = load(task_id)
if task["owner"]:
return "already claimed"
task["owner"] = owner
save(task)
4. 空闲阶段只轮询任务板,不看邮箱
# ❌ 错误
# 只检查任务板
def idle_phase():
tasks = get_claimable_tasks()
if tasks:
claim_task(tasks[0])
# ✅ 正确
# 先检查邮箱,再检查任务板
def idle_phase():
inbox = read_inbox()
if inbox:
process_messages(inbox)
else:
tasks = get_claimable_tasks()
if tasks:
claim_task(tasks[0])
5. 认领了任务,但没有写 claim event
# ❌ 错误
# 没有记录事件
def claim_task(task_id, owner):
task = load(task_id)
task["owner"] = owner
save(task)
# ✅ 正确
# 记录认领事件
def claim_task(task_id, owner):
task = load(task_id)
task["owner"] = owner
save(task)
log_claim_event({
"event": "task.claimed",
"task_id": task_id,
"owner": owner,
"ts": time.time(),
})
6. 队友永远不退出
# ❌ 错误
# 永远不退出
def run(self):
while True:
work_phase()
idle_phase()
time.sleep(5)
# ✅ 正确
# 空闲超时后退出
def run(self):
idle_count = 0
max_idle = 10
while idle_count < max_idle:
work_phase()
should_continue = idle_phase()
if not should_continue:
break
idle_count += 1
time.sleep(5)
7. 上下文压缩后不重注入身份
# ❌ 错误
# 没有身份重注入
def resume_from_compression(messages):
# 直接恢复消息
return messages
# ✅ 正确
# 重新注入身份
def resume_from_compression(messages, name, role):
# 检查是否有身份块
has_identity = any("<identity>" in msg.get("content", "") for msg in messages)
if not has_identity:
# 注入身份块
identity_msg = {
"role": "user",
"content": f"<identity>You are '{name}', role: {role}. Continue your work.</identity>",
}
messages.insert(0, identity_msg)
return messages
为什么这很重要
因为一个真正高效的团队,不应该依赖手动分配任务。
自治智能体系统让你能够:
- 自动分配:空闲队友自动认领任务,无需手动分配
- 提高效率:任务分配更快速,团队协作更流畅
- 角色匹配:按角色分配任务,确保合适的人做合适的事
- 可扩展性:团队规模变大时,分配机制仍然有效
- 可追踪性:记录认领事件,便于追踪和审计
推荐的实现步骤
- 第一步:实现可认领任务的判断逻辑
- 第二步:实现原子认领操作,使用锁避免重复认领
- 第三步:实现 WORK-IDLE 循环
- 第四步:在 IDLE 阶段实现邮箱检查和任务扫描
- 第五步:实现身份重注入机制
- 第六步:实现认领事件日志
- 第七步:集成到团队系统,支持自治队友
自治智能体系统与后续章节的关系
- s17 自治智能体:解决 Agent 如何主动认领任务的问题
- s18 Worktree:会利用自治智能体来管理工作区
- s19 MCP 插件:会利用自治智能体来扩展能力
所以自治智能体系统是构建高级智能体系统的重要组件。
下一章预告
有了自治智能体系统,你的 Agent 已经能够主动认领任务。下一章我们将探讨 Worktree 系统,让 Agent 能够更好地管理工作区和代码隔离。
一句话总结:自治不是让 agent 乱跑,而是让它在清晰规则下自己接住下一份工作。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。