HuanCode Docs

第11课:自治Agent——自组织任务认领

队友自己看看板,有活就认领。WORK-IDLE双阶段循环+身份重注入,让多Agent团队从手动指派走向自组织协作。

系列导读

这是**《12课拆解Claude Code架构》系列的第 11 课,也是阶段四(多 Agent 协作)的最后一课**。

前十课我们从一个 while 循环出发,一路叠加工具注册、规划系统、Subagent、技能加载、上下文压缩、任务持久化、后台执行、多 Agent 团队、消息传递。到第 10 课,队友之间已经能通过收件箱互发消息了。

但有一个问题越来越刺眼——队友只在被明确指派时才干活。

第 11 课的格言:

"队友自己看看板,有活就认领"

这一课,我们让 Agent 从"等领导分配"变成"自己找活干"。


手动指派的瓶颈

第 9-10 课的团队模式下,领导(orchestrator)是唯一的任务分配者:

领导看到10个待办任务

给coder写prompt → 给tester写prompt → 给reviewer写prompt

还剩7个任务没人认领,等队友做完再一个一个分配……
瓶颈表现
领导成为单点所有任务必须经过领导分配
指派延迟队友做完一个任务后要等领导轮到自己
扩展不了10 个队友就要写 10 次 prompt
空闲浪费队友做完活只能干等,不会自己找下一个

现实世界里,好的团队不是这样运作的。你不会每写一张便利贴就走到某个人工位上贴到他脸上。 你贴在看板上,谁有空谁拿。

手动指派:领导成为瓶颈

WORK-IDLE 双阶段循环

核心思路:每个队友的循环不再只有 WORK,增加一个 IDLE 阶段——做完活就自动去看板找下一个。

                  ┌──────────────────────────┐
                  │       Agent 启动          │
                  └────────────┬─────────────┘

                  ┌──────────────────────────┐
              ┌──→│        IDLE 阶段          │
              │   │  轮询收件箱 + 扫描看板    │
              │   │  (每5s,最多60s)          │
              │   └────────┬────────┬────────┘
              │            │        │
              │      找到任务    60s超时
              │            ▼        ▼
              │   ┌──────────┐  ┌──────────┐
              │   │  claim    │  │ SHUTDOWN │
              │   │  认领任务  │  └──────────┘
              │   └─────┬────┘
              │         ▼
              │   ┌──────────────────────────┐
              │   │       WORK 阶段           │
              │   │  正常 Agent Loop 执行任务  │
              │   └────────────┬─────────────┘
              │           任务完成
              └────────────────┘
  • WORK:正常的 Agent Loop,和前面课程完全一致
  • IDLE:轮询收件箱 + 扫描看板,每 5 秒一次,最多 60 秒

找到活 → claim → 进入 WORK。60 秒没活 → 自动 SHUTDOWN。

核心拆解:四个关键机制

机制一:idle_poll——轮询等待

def idle_poll(agent_id: str, task_board: TaskBoard,
              inbox: Inbox, timeout: int = 60) -> Optional[str]:
    deadline = time.time() + timeout
    while time.time() < deadline:
        # 先检查收件箱——有人直接指派给我的优先
        msg = inbox.poll(agent_id)
        if msg and msg.get("task_id"):
            return msg["task_id"]
        # 再扫描看板——找无主任务
        task_id = scan_unclaimed_tasks(agent_id, task_board)
        if task_id:
            return task_id
        time.sleep(5)
    return None  # 超时,准备关机

优先级:直接指派 > 自由认领 > 等待 > 超时关机

机制二:scan_unclaimed_tasks——扫描看板

def scan_unclaimed_tasks(agent_id: str,
                         task_board: TaskBoard) -> Optional[str]:
    for task in task_board.list_tasks():
        if (task["status"] == "pending"
                and task.get("owner") is None
                and not is_blocked(task, task_board)):
            return task["id"]
    return None

def is_blocked(task: dict, task_board: TaskBoard) -> bool:
    for dep_id in task.get("blocked_by", []):
        dep_task = task_board.get_task(dep_id)
        if dep_task and dep_task["status"] != "completed":
            return True
    return False

三个过滤条件全部满足才能认领:pending + 无 owner + 未被阻塞blocked_by 是任务间的依赖——"写测试"依赖"写代码"完成,代码没写完就不能拿测试任务。

机制三:claim——原子认领

def claim_task(agent_id: str, task_id: str,
               task_board: TaskBoard) -> bool:
    with task_board.lock:
        task = task_board.get_task(task_id)
        if (task and task["status"] == "pending"
                and task.get("owner") is None):
            task["status"] = "in_progress"
            task["owner"] = agent_id
            task_board.update_task(task_id, task)
            return True
    return False

加锁。 两个 Agent 可能同时看到同一个无主任务,锁保证只有一个能认领成功。认领失败的会在下一轮继续扫描。这就是去中心化调度的核心:不需要中央分配器,冲突通过锁 + 重试解决。

机制四:身份重注入

第 6 课的 Context Compact 会压缩消息历史,Agent 可能丢失关键信息——包括自己是谁。

压缩前: [0] system: "你是coder" → [1-39] 大量工具调用
压缩后: [0] assistant: "之前讨论了..." ← system提示词被压缩掉了
Agent忘了自己是coder,开始做tester的活

解决方案:消息列表过短时,在开头插入身份块。

IDENTITY_THRESHOLD = 3

def maybe_reinject_identity(agent_id: str, role: str,
                            messages: list,
                            identity_prompt: str) -> list:
    if len(messages) >= IDENTITY_THRESHOLD:
        return messages
    identity_block = {
        "role": "user",
        "content": (f"<identity>\n你是 {agent_id},角色是 {role}\n"
                    f"{identity_prompt}\n</identity>"),
    }
    return [identity_block] + messages

触发条件是消息列表长度——Compact 后消息数量骤降,阈值检查能准确捕捉到"刚被压缩过"的时刻。

组装:自治 Agent 循环

四个机制拼进 Agent Loop,外层 while True 是 IDLE-WORK 切换,内层 while True 是原有的 Agent Loop:

def autonomous_agent_loop(agent_id, role, identity_prompt,
                          task_board, inbox, tools, bg_manager):
    messages = []
    while True:
        # ——— IDLE 阶段 ———
        task_id = idle_poll(agent_id, task_board, inbox)
        if task_id is None:
            print(f"[{agent_id}] 60秒无任务,自动关机")
            break
        if not claim_task(agent_id, task_id, task_board):
            continue  # 被别人抢了,继续找

        task = task_board.get_task(task_id)
        messages.append({"role": "user",
                         "content": f"任务: {task['title']}\n{task['description']}"})

        # ——— WORK 阶段(原有 Agent Loop,一行不改) ———
        while True:
            messages = maybe_reinject_identity(
                agent_id, role, messages, identity_prompt)
            response = client.messages.create(
                model=MODEL, system=identity_prompt,
                messages=messages, tools=tools, max_tokens=8000)
            messages.append({"role": "assistant", "content": response.content})
            if response.stop_reason != "tool_use":
                break
            results = []
            for block in response.content:
                if block.type == "tool_use":
                    output = dispatch_tool(block.name, block.input)
                    results.append({"type": "tool_result",
                                    "tool_use_id": block.id, "content": output})
            messages.append({"role": "user", "content": results})

        task_board.update_task(task_id, {"status": "completed", "owner": agent_id})
        # 回到外层 while True → 继续 IDLE

WORK 阶段的 Agent Loop 从第 1 课到第 11 课,一行不改。 自治逻辑全部在循环之外。

实际运行:三个 Agent 自组织协作

看板上 5 个任务,3 个 Agent 同时启动:

看板: T1(pending) T2(pending,blocked_by=T1) T3(pending,blocked_by=T2)
      T4(pending) T5(pending)

[coder]  IDLE → T1无阻塞 → claim → WORK
[tester] IDLE → T2被阻塞 → T4无阻塞 → claim → WORK
[devops] IDLE → T5无阻塞 → claim → WORK

[devops] 完成T5 → IDLE → 无活(T2/T3被阻塞)
[tester] 完成T4 → IDLE → 无活
[coder]  完成T1 → IDLE → T2解除阻塞 → claim → WORK

[coder]  完成T2 → IDLE → T3解除阻塞 → claim → WORK
[devops] IDLE超时 → SHUTDOWN
[tester] IDLE超时 → SHUTDOWN

[coder]  完成T3 → IDLE → 无活 → IDLE超时 → SHUTDOWN

没有任何中央调度。 依赖自动通过 blocked_by 解决,空闲 Agent 自动退出。

自组织协作:三个Agent自动认领任务

两个设计洞见

洞见一:去中心化调度的代价

每个 Agent 每 5 秒扫一次看板,3 个 Agent 是 3 次/5s,100 个 Agent 是 100 次/5s。当前规模下可忽略(内存字典读操作不花时间),但原则上:去中心化用轮询换掉了中央推送。 规模上去后可优化为事件驱动,但不改变架构。

洞见二:身份重注入是 Agent 的"名牌"

Context Compact 会无差别压缩——包括身份信息。身份丢失的后果不只是"忘了自己是谁",更是角色越权:coder 开始审查代码,tester 开始写业务逻辑。重注入把角色边界重新划清。

<identity> 标签而不是修改 system prompt——因为 system prompt 在 API 层面只设一次,身份重注入需要在对话过程中动态触发。

五分钟跑起来

# 进入项目目录
cd learn-claude-code

# 启动第十一课
python agents/s11_autonomous_agents.py

任务一:自动认领 + 依赖解锁 + 冲突拒绝

创建 3 个任务,spawn 两个队友,观察他们自组织:

s11 >> Create 3 tasks on the board, then spawn alice and bob.
       Watch them auto-claim.

实际执行记录(精简):

> bash: ✅ Tasks 8, 9, 10 created!
  #8  Add farewell() to greet.py          blockedBy: []
  #9  Create CHANGELOG.md                 blockedBy: []
  #10 Write test for farewell             blockedBy: [8]   ← 等 #8 完成

> spawn_teammate: Spawned 'alice' (role: developer)
> spawn_teammate: Spawned 'bob' (role: developer)

两个 Agent 同时扫描看板,自主认领任务:

  [bob]   claim_task: Claimed task #8 for bob       ← bob 先抢到 #8
  [alice] write_file: CHANGELOG.md                  ← alice 拿了 #9

  [bob]   claim_task: Error: Task 9 has already been claimed by alice
                                                    ← bob 想抢 #9,被拒绝!

  [alice] edit_file: greet.py → 添加 farewell()
  [alice] bash: Goodbye, Alice!                     ← 验证 farewell 函数

alice 完成 #8 后,_clear_dependency 触发,#10 解锁:

  → Task 10 unblocked!                              ← 依赖自动解锁

  [bob]   claim_task: Claimed task #10 for bob       ← bob 抢到解锁的 #10
  [bob]   bash: pytest → 3 tests, all passing ✅

两个 Agent 完成所有任务后自动进入空闲:

  [alice] idle: Entering idle phase. Will poll for new tasks.
  [bob]   send_message: Sent message to lead         ← 汇报完成

关键行为验证:

  • 自动认领:无人指派,alice 和 bob 自己扫看板抢任务
  • 冲突拒绝:bob 尝试认领 alice 已拿的 #9,被 claim_task 拒绝
  • 依赖解锁:#10 等 #8 完成才可认领,_clear_dependency 精确生效
  • 自动空闲:任务做完,进入 idle 轮询

任务二:完全自治——零指令执行全部任务

只 spawn 一个 coder,什么都不告诉它,让它自己找活:

s11 >> Spawn a coder teammate and let it find work from the task board itself

实际执行记录(时间线):

> spawn_teammate: Spawned 'coder' (role: developer)

  [coder] 自主扫描 .tasks/ 目录...
  [coder] claim_task: Claimed task #2 for coder     ← 自动找到 pending 任务
  [coder] write_file: src/calculator.py              ← 写代码
  [coder] claim_task: Claimed task #3 for coder     ← 完成后立即找下一个
  [coder] write_file: tests/test_calculator.py       ← 写测试
  [coder] bash: pytest → 25 tests passed ✅

  [coder] claim_task: Claimed task #4 for coder     ← Parse(#5/#6 的前置)
  [coder] write_file: src/refactor/parse.py
  [coder] claim_task: Claimed task #5 for coder     ← Transform(#4完成后解锁)
  [coder] write_file: src/refactor/transform.py
  [coder] claim_task: Claimed task #6 for coder     ← Emit(也是 #4 完成后解锁)
  [coder] write_file: src/refactor/emit.py
  [coder] claim_task: Claimed task #7 for coder     ← Test(#5 和 #6 都完成后解锁)
  [coder] write_file: tests/test_refactor.py
  [coder] bash: pytest → 29 tests passed ✅

  [coder] send_message: "All available tasks have been completed."
  [coder] idle: Entering idle phase. Will poll for new tasks.

一个 Agent,零指令,自动清空 6 个任务——扫看板、认领、执行、完成、找下一个,循环直到全部做完。

任务三:钻石依赖图——验证并行分叉和汇聚

最硬核的测试——创建钻石形依赖,两个 Agent 并行执行:

s11 >> Create tasks with dependencies. Watch teammates respect the blocked order.

任务图结构:

         #1 Schema
          /       \
   #2 Backend   #3 Frontend     ← 并行分叉
          \       /
      #4 Integration            ← 汇聚:等 #2 和 #3 都完成
            |
       #5 Deploy                ← 线性:等 #4

实际执行时间线:

~8s   🔄 frank claims #1 (唯一未阻塞的任务)
      → eve 无任务可认领,等待

~23s  ✅ #1 done → #2 和 #3 同时解锁(钻石分叉)
      🔄 eve claims #2 (Backend)
      🔄 eve claims #3 (Frontend)     ← eve 手快,两个都抢到

~43s  ✅ #2 + #3 done → #4 解锁(钻石汇聚)
      🔄 eve claims #4 (Integration)
      → frank 轮询发现 #4 已被 eve 认领

~68s  ✅ #4 done → #5 解锁
      🔄 eve claims #5 (Deploy)

~93s  ✅ #5 done — 全部完成
      [eve]   send_message: "All tasks completed"
      [frank] send_message: "All tasks completed"
      → 两个 Agent 都进入 idle

验证最终状态:

s11 >> /tasks
  [x] #1: Design the data schema @frank
  [x] #2: Build the backend API layer @eve
  [x] #3: Build the frontend formatter layer @eve
  [x] #4: Integration: wire backend + frontend together @eve
  [x] #5: Deploy: generate final report @eve

s11 >> /team
  eve (developer): shutdown
  frank (developer): shutdown

关键行为验证:

  • 钻石分叉:#2 和 #3 在 #1 完成后同时解锁
  • 钻石汇聚:#4 等 #2 和 #3 完成才解锁(不是只等一个)
  • 线性依赖:#5 等 #4 完成
  • 抢占式认领:eve 手快抢了大部分任务,frank 轮询发现没活就继续等
  • 自动退出:全部做完,两个 Agent 自动 shutdown

总结:你刚造了什么

组件之前(s09-s10)之后(s11)
任务分配领导手动指派自组织认领
Agent 空闲时干等领导分配轮询收件箱 + 扫描看板
任务认领仅手动自动 claim(加锁防冲突)
身份保持无机制Context Compact 后重注入
退出机制领导控制60 秒无活自动 SHUTDOWN
新增代码~80 行(idle_poll + claim + identity)

核心变化:从中央调度到自组织。 领导不再是瓶颈,Agent 自己找活、自己认领、自己退出。

变更总结:从手动指派到自治协作

下一课预告

第 11 课让 Agent 团队能自组织协作。但所有 Agent 还共享同一个工作目录——两个 Agent 同时改同一个文件怎么办?

第 12 课:Worktree Isolation —— 用 Git Worktree 给每个 Agent 一个独立的工作空间。

# 预告:s12 的 worktree 隔离
worktree = create_worktree(agent_id, branch="feature/user-auth")
agent_loop(cwd=worktree.path, ...)
merge_worktree(worktree)

并行不冲突。每个 Agent 有自己的沙箱。


这是《12课拆解Claude Code架构:从零掌握Agent Harness工程》系列的第 11 课。关注Claw开发者,不错过后续更新。

完整代码和交互式学习平台:github.com/shareAI-lab/learn-claude-code

如果这篇文章对你有帮助,欢迎转发给你的技术团队。

系列目录

  • 第1课:用20行Python造出你的第一个AI Agent
  • 第2课:给Agent加工具 —— dispatch map模式详解
  • 第3课:TodoWrite —— 让Agent先想后做:规划系统
  • 第4课:Subagent —— 拆解大任务,上下文隔离
  • 第5课:按需加载领域知识——Skill机制
  • 第6课:无限对话——上下文压缩三层策略
  • 第7课:任务持久化——文件级DAG任务图
  • 第8课:后台执行——异步任务与通知队列
  • 第9课:Agent Teams——多Agent协作:团队与邮箱系统
  • 第10课:团队协议——状态机驱动的协商
  • 第11课:自治Agent——自组织任务认领(本文)
  • 第12课:终极隔离——Worktree并行执行

On this page