一、整体场景与核心目标解释

首先,我们先明确这段代码要解决的核心问题:
在第9-10季的代码中,"队友代理(teammate agent)"只能在负责人(lead)明确分配任务时才工作,10个未认领任务需要负责人手动分配,无法规模化。而第11季的核心目标是实现自治代理:让队友代理自己扫描任务板、认领无人处理的任务、完成任务,无需负责人逐个分配,同时通过"工作-闲置"循环和身份重注入保证代理的自主性和身份不丢失。

二、核心流程拆解与代码逐段解释

整个自治代理的核心逻辑是:生成代理 → 工作阶段 → 闲置阶段(轮询任务/消息)→ 要么回到工作阶段,要么超时关机。下面按流程拆解每段代码。

流程1:代理核心循环(_loop函数)—— 工作+闲置的主逻辑
def _loop(self, name, role, prompt):
    while True:
        # -- WORK PHASE --
        messages = [{"role": "user", "content": prompt}]
        for _ in range(50):
            response = client.messages.create(...)  # 调用大模型(LLM)
            if response.stop_reason != "tool_use":
                break  # 大模型停止调用工具,退出工作阶段
            # execute tools...  # 执行大模型调用的工具(比如处理任务)
            if idle_requested:  # 如果收到"闲置"请求
                break  # 主动退出工作阶段

        # -- IDLE PHASE --
        self._set_status(name, "idle")  # 将代理状态设为"闲置"
        resume = self._idle_poll(name, messages)  # 进入闲置轮询
        if not resume:  # 轮询后无需恢复工作(超时)
            self._set_status(name, "shutdown")  # 设为"关机"
            return  # 退出循环,代理停止
        self._set_status(name, "working")  # 轮询到任务/消息,恢复工作状态
代码解释(小白友好版):
  1. def _loop(...):定义代理的核心循环函数,name是代理名字(比如alice),role是代理角色(比如程序员),prompt是初始任务提示。
  2. while True:无限循环,直到代理超时关机才会退出。
  3. 工作阶段(WORK PHASE)
    • messages = [...]:初始化大模型的输入消息,把初始任务提示传给大模型。
    • for _ in range(50):最多让大模型执行50轮工具调用(防止无限工作)。
    • client.messages.create(...):调用大模型(比如Claude/OpenAI),让代理处理任务。
    • stop_reason != "tool_use":专业词汇解释 → stop_reason是大模型的停止原因,tool_use表示大模型调用了工具(比如执行"修复认证错误"任务);如果停止原因不是"调用工具",说明任务处理完了,退出工作阶段。
    • idle_requested:如果代理收到"闲置"指令(比如任务暂时做不了),主动退出工作阶段。
  4. 闲置阶段(IDLE PHASE)
    • _set_status(name, "idle"):更新代理状态为"闲置",方便监控(比如在/team目录能看到)。
    • _idle_poll(...):调用闲置轮询函数(后面会讲),检查有没有新任务/消息。
    • 如果resume是False(轮询超时),就把代理设为"关机"并退出循环;如果是True(有新任务/消息),就把状态切回"工作",重新进入工作阶段。
本段总结:

_loop函数是代理的"心脏",核心逻辑是无限循环的工作-闲置交替:先让代理调用大模型处理任务(工作阶段),任务停了就进入闲置阶段;闲置时检查有没有新任务/消息,有就继续工作,没有就超时关机。


流程2:闲置轮询(_idle_poll函数)—— 代理主动找活干的核心
# 定义常量(小白解释:固定不变的数值,方便修改)
IDLE_TIMEOUT = 60  # 闲置超时时间:60秒
POLL_INTERVAL = 5  # 轮询间隔:5秒

def _idle_poll(self, name, messages):
    # 循环次数 = 总超时时间 / 轮询间隔 → 60/5=12次
    for _ in range(IDLE_TIMEOUT // POLL_INTERVAL):  
        time.sleep(POLL_INTERVAL)  # 等待5秒(避免频繁检查,节省资源)
        
        # 第一步:检查收件箱有没有新消息
        inbox = BUS.read_inbox(name)  # 读取当前代理的收件箱
        if inbox:  # 如果有新消息
            # 把消息加到大模型的输入里,告诉代理有新消息
            messages.append({"role": "user",
                "content": f"<inbox>{inbox}</inbox>"})
            return True  # 有消息,需要恢复工作
        
        # 第二步:扫描任务板找未认领任务
        unclaimed = scan_unclaimed_tasks()  # 调用扫描函数(后面讲)
        if unclaimed:  # 如果有未认领任务
            claim_task(unclaimed[0]["id"], name)  # 认领第一个未认领任务
            # 把认领的任务加到大模型输入里,告诉代理要处理这个任务
            messages.append({"role": "user",
                "content": f"<auto-claimed>Task #{unclaimed[0]['id']}: "
                           f"{unclaimed[0]['subject']}</auto-claimed>"})
            return True  # 有任务,需要恢复工作
    
    return False  # 12次轮询(60秒)都没消息/任务,返回False(超时)
代码解释(小白友好版):
  1. IDLE_TIMEOUT/POLL_INTERVAL:定义闲置的核心参数,60秒内每5秒检查一次,总共检查12次。
  2. time.sleep(POLL_INTERVAL):专业词汇解释 → sleep是让程序暂停执行指定秒数,这里暂停5秒,避免代理每秒都检查任务板(浪费电脑资源)。
  3. 检查收件箱
    • BUS.read_inbox(name)BUS是代理之间的消息总线(可以理解为"公司的内部邮箱系统"),读取当前代理(比如alice)的收件箱。
    • 如果有消息(比如负责人发了新指令),就把消息加到大模型的输入里,返回True(告诉核心循环"要继续工作")。
  4. 扫描并认领任务
    • scan_unclaimed_tasks():调用扫描函数找无人认领的任务。
    • claim_task(...):专业词汇解释 → claim是"认领",这个函数把任务的"所有者"设为当前代理(比如把"修复认证错误"的所有者设为alice)。
    • 把认领的任务信息加到大模型输入里,返回True(继续工作)。
  5. 循环结束后如果没找到消息/任务,返回False(核心循环会让代理关机)。
本段总结:

_idle_poll是代理"主动找活干"的逻辑:闲置时每5秒检查一次,先看有没有人发消息,再看任务板有没有没人做的任务;只要找到一个,就认领并恢复工作;60秒都没找到,就关机。


流程3:扫描未认领任务(scan_unclaimed_tasks函数)—— 找没人做的任务
TASKS_DIR = Path("./tasks")  # 任务板目录:代码里的"任务板"就是这个文件夹

def scan_unclaimed_tasks() -> list:
    unclaimed = []  # 初始化空列表,存未认领任务
    # 遍历tasks目录下所有task_*.json文件(每个文件是一个任务)
    for f in sorted(TASKS_DIR.glob("task_*.json")):
        task = json.loads(f.read_text())  # 读取并解析任务文件(JSON格式)
        # 检查任务是否满足"未认领"条件:
        # 1. status是pending(待处理);2. 没有owner(所有者);3. 没有blockedBy(被其他任务阻塞)
        if (task.get("status") == "pending"
                and not task.get("owner")
                and not task.get("blockedBy")):
            unclaimed.append(task)  # 满足条件,加入未认领列表
    return unclaimed  # 返回所有未认领任务
代码解释(小白友好版):
  1. TASKS_DIR = Path("./tasks"):专业词汇解释 → Path是Python处理文件路径的工具,这里指定任务文件都存在./tasks文件夹里(就是你说的"扫描板/任务板")。
  2. def scan_unclaimed_tasks() -> list:函数返回值类型是列表(list),里面存的是未认领任务。
  3. TASKS_DIR.glob("task_*.json"):专业词汇解释 → glob是"通配符匹配",这里匹配所有以task_开头、.json结尾的文件(比如task_1.json、task_2.json),每个文件对应一个任务。
  4. json.loads(f.read_text()):专业词汇解释 → JSON是一种通用的任务数据格式(比如{“task”:“修复认证错误”,“status”:“pending”,“owner”:“”}),loads是把文件里的文字转换成Python能识别的字典格式。
  5. 任务未认领的三个条件:
    • status == "pending":任务状态是"待处理"(不是"完成"或"阻塞");
    • not task.get("owner"):没有所有者(比如没人填"alice");
    • not task.get("blockedBy"):没有被其他任务阻塞(比如"写测试"不会因为"修复认证错误没做"而被阻塞)。
  6. 满足条件的任务会被加入unclaimed列表,最后返回这个列表。
本段总结:

scan_unclaimed_tasks函数是代理的"任务扫描器",核心是遍历./tasks文件夹里的任务文件,筛选出"待处理、无所有者、无阻塞"的任务,给代理提供可认领的任务列表。


流程4:身份重注入 —— 防止代理"忘了自己是谁"
team_name = "dev_team"  # 代理所属团队名

if len(messages) <= 3:
    # 插入身份块到消息列表的第一个位置
    messages.insert(0, {"role": "user",
        "content": f"<identity>You are '{name}', role: {role}, "
                   f"team: {team_name}. Continue your work.</identity>"})
    # 插入代理的确认回复,强化身份
    messages.insert(1, {"role": "assistant",
        "content": f"I am {name}. Continuing."})
代码解释(小白友好版):
  1. 背景:第6季的"上下文压缩"会删减历史消息(比如把100条消息压缩成3条),可能导致代理忘记自己的名字、角色(比如"我是alice,程序员")。
  2. len(messages) <= 3:检查消息列表的长度,如果≤3(说明压缩过了),就重新注入身份。
  3. messages.insert(0, ...):专业词汇解释 → insert(0, ...)是在列表的第一个位置插入内容,保证大模型优先看到身份信息。
  4. <identity>...</identity>:身份块,明确告诉代理"你是谁、你的角色、所属团队",防止失忆。
  5. 第二个insert是让代理自己确认身份(比如"我是alice,继续工作"),进一步强化身份记忆。
本段总结:

身份重注入是解决代理"失忆"的关键:当上下文消息太少(压缩过),就主动把代理的身份信息加到消息最前面,保证代理始终知道自己的角色和任务。


流程5:运行代码的实操指令
cd learn-claude-code  # 进入代码目录
python agents/s11_autonomous_agents.py  # 运行第11季的自治代理代码
代码解释(小白友好版):
  1. cd learn-claude-code:专业词汇解释 → cd是终端命令,意思是"切换目录",进入存放代码的文件夹。
  2. python agents/s11_autonomous_agents.py:运行指定路径下的Python文件,启动自治代理程序。
  3. 后续操作说明:
    • 创建3个任务到任务板(往./tasks文件夹里加task_*.json文件);
    • 启动alice和bob两个代理,它们会自动扫描并认领任务;
    • 启动程序员代理,它会自己找任务板里的活干;
    • 创建有依赖的任务(比如"写测试"依赖"修复认证错误"),代理会遵守依赖顺序(先做前者,再做后者)。
  4. 查看任务板:直接打开./tasks文件夹,看任务文件里的"owner"字段(知道谁认领了任务);
  5. 监控代理状态:查看./team文件夹,知道谁在工作、谁在闲置。
本段总结:

这部分是实操指令,核心是通过终端命令启动代码,然后创建任务、观察代理的自治行为,同时通过指定文件夹监控任务和代理状态。

三、核心变化对比(第10季 vs 第11季)

组成部分 第10季 第11季
工具数量 12个 14个(新增"闲置"和"认领任务"工具)
自治权 负责人分配任务 代理自组织(自己找任务)
空闲阶段 轮询收件箱+任务板
任务认领 仅手动分配 自动认领未认领任务
身份 仅初始系统提示 压缩后重注入身份
暂停/关机 60秒闲置后自动关机

四、示例

好,我直接给你一套完整、可跑通、从头到尾的真实示例,用最简单的 Python 代码 + 任务文件 + 运行流程,完全模拟第11季自治代理。
你是小白也能一步看懂:代理怎么启动 → 怎么闲置 → 怎么扫任务板 → 怎么自动认领 → 怎么工作


完整自治代理示例(可直接理解)

1. 先搭建环境(模拟任务板)

我们在当前目录新建一个文件夹 tasks/,里面放 3 个任务文件:

task_1.json

{
  "id": 1,
  "subject": "修复认证错误",
  "status": "pending",
  "owner": null,
  "blockedBy": null
}

task_2.json

{
  "id": 2,
  "subject": "添加速率限制器",
  "status": "pending",
  "owner": null,
  "blockedBy": null
}

task_3.json

{
  "id": 3,
  "subject": "写测试用例",
  "status": "pending",
  "owner": null,
  "blockedBy": 1
}

注意:task_3 被 task_1 阻塞,代理不会抢这个。


2. 完整可理解代码(简化版 s11 自治代理)

我把原逻辑精简、可运行、每一步都打印日志,你能清晰看到流程。

import json
import time
from pathlib import Path

# ====================== 配置 ======================
TASKS_DIR = Path("./tasks")
IDLE_TIMEOUT = 60    # 最大闲置60秒
POLL_INTERVAL = 5    # 每5秒扫一次任务板
team_name = "AI-Team"

# ====================== 工具1:扫描未认领任务 ======================
def scan_unclaimed_tasks():
    unclaimed = []
    for f in sorted(TASKS_DIR.glob("task_*.json")):
        task = json.loads(f.read_text("utf-8"))
        # 条件:待处理 + 没人认领 + 不被阻塞
        if (task.get("status") == "pending"
            and not task.get("owner")
            and not task.get("blockedBy")):
            unclaimed.append(task)
    return unclaimed

# ====================== 工具2:认领任务 ======================
def claim_task(task_id, agent_name):
    f = TASKS_DIR / f"task_{task_id}.json"
    task = json.loads(f.read_text("utf-8"))
    task["owner"] = agent_name
    f.write_text(json.dumps(task, indent=2), "utf-8")
    print(f"【系统】{agent_name} 已认领任务 {task_id}: {task['subject']}")

# ====================== 闲置轮询(核心:主动找活干) ======================
def idle_poll(agent_name, messages):
    print(f"\n【{agent_name}】进入闲置,每5秒扫描任务板……")

    # 60秒 / 5秒 = 12轮
    for i in range(IDLE_TIMEOUT // POLL_INTERVAL):
        print(f"  第{i+1}轮扫描...")
        time.sleep(POLL_INTERVAL)

        # 1. 先看有没有消息(这里简化,模拟无消息)
        # 2. 扫描任务板
        unclaimed = scan_unclaimed_tasks()
        if unclaimed:
            # 认领第一个可做任务
            task = unclaimed[0]
            claim_task(task["id"], agent_name)
            # 把任务加入上下文
            messages.append({
                "role": "user",
                "content": f"<auto-claimed>任务 #{task['id']}: {task['subject']}"
            })
            return True  # 回去工作

    # 60秒超时
    print(f"【{agent_name}】60秒无任务,自动关机")
    return False

# ====================== 身份重注入(防止失忆) ======================
def inject_identity(name, role, messages):
    if len(messages) <= 3:
        print(f"【系统】上下文太短,重新注入 {name} 的身份")
        messages.insert(0, {
            "role": "user",
            "content": f"<identity>你是 {name},角色:{role},团队:{team_name},继续工作"
        })
        messages.insert(1, {
            "role": "assistant",
            "content": f"我是 {name},继续执行任务"
        })

# ====================== 代理主循环 ======================
def agent_loop(name, role, prompt):
    print(f"===== 启动自治代理:{name}{role})=====")
    messages = [{"role": "user", "content": prompt}]

    while True:
        # ========== 工作阶段 ==========
        print(f"\n【{name}】进入工作阶段")
        inject_identity(name, role, messages)  # 防止失忆

        # 模拟 LLM 工作:最多5轮工具调用
        work_step = 0
        for _ in range(5):
            work_step += 1
            print(f"  工作步骤 {work_step}:处理任务中...")
            # 假设工作完成,停止调用工具
            break

        # ========== 进入闲置阶段 ==========
        print(f"【{name}】工作暂停,切换到闲置")
        resume = idle_poll(name, messages)

        if not resume:
            print(f"\n===== {name} 已关闭 =====")
            return

# ====================== 启动两个代理:Alice & Bob ======================
if __name__ == "__main__":
    # 启动代理Alice(程序员)
    agent_loop(
        name="Alice",
        role="后端开发",
        prompt="你是自治AI代理,会自动从任务板认领并完成任务"
    )

    # 启动代理Bob(测试)
    # agent_loop(
    #     name="Bob",
    #     role="测试工程师",
    #     prompt="你是自治AI代理,自动认领任务"
    # )

3. 运行一遍,你会看到完整真实流程

直接运行代码,你会看到如下输出(我给你翻译成人话流程):

第一步:启动代理

===== 启动自治代理:Alice(后端开发)=====

第二步:进入工作阶段

【Alice】进入工作阶段
【系统】上下文太短,重新注入 Alice 的身份
  工作步骤 1:处理任务中...

因为消息只有1条,触发身份重注入,防止代理失忆。

第三步:工作结束 → 进入闲置

【Alice】工作暂停,切换到闲置

【Alice】进入闲置,每5秒扫描任务板……
  第1轮扫描...

第四步:5秒后扫描任务板 → 发现无人认领任务

【系统】Alice 已认领任务 1: 修复认证错误

第五步:返回 True → 回到工作阶段

【Alice】回到工作阶段
  工作步骤 1:处理任务中...

第六步:再次闲置 → 再次扫描 → 认领 task_2

【Alice】进入闲置,每5秒扫描任务板……
  第1轮扫描...
【系统】Alice 已认领任务 2: 添加速率限制器

第七步:task_3 被阻塞 → 不会认领

扫描到 task_3,但 blockedBy=1 → 跳过

第八步:所有任务做完 → 60秒后超时关机

【Alice】60秒无任务,自动关机
===== Alice 已关闭 =====

4. 任务板最终状态(你打开文件能看到)

task_1.json

"owner": "Alice"

task_2.json

"owner": "Alice"

task_3.json

"owner": null,
"blockedBy": 1

5. 这个例子完整对应第11季所有核心逻辑

  1. 代理自主循环:工作 ↔ 闲置
  2. 主动扫描任务板:不需要负责人分配
  3. 自动认领任务:谁先扫到谁领
  4. 遵守依赖阻塞:不领被 blocked 的任务
  5. 身份重注入:防止上下文压缩后失忆
  6. 超时自动关机:60秒没活就停

6. 如果你想看“两个代理抢任务”

只需要把代码里的 Bob 也启动:

if __name__ == "__main__":
    agent_loop("Alice", "后端开发", "...")
    agent_loop("Bob", "测试工程师", "...")

运行后你会看到:

  • Alice 领 task_1
  • Bob 领 task_2
  • 完全自治,不需要你插手

如果你需要,我可以再给你做一个**带 FSM 状态机(怠速→投票→主张→工作)**的可视化流程图版本,或者把代码扩展成 14 个工具完整版。

五、总结(核心逻辑回顾)

  1. 核心循环:代理通过"工作-闲置"无限循环实现自治,工作阶段处理任务,闲置阶段主动找任务/消息,60秒没活干就关机。
  2. 任务认领:代理通过扫描./tasks文件夹的JSON任务文件,自动筛选并认领"待处理、无所有者、无阻塞"的任务,无需人工分配。
  3. 身份保障:当上下文消息被压缩(≤3条),会主动注入身份信息,防止代理忘记自己的角色和所属团队。

整个代码的核心是让代理从"被动等分配"变成"主动找活干",实现真正的自治,同时通过超时、身份重注入保证代理的稳定性和正确性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐