s11_自主代理设计:为什么 Agent 空闲时不该只是等下一条指令
2026/4/6 18:13:34 网站建设 项目流程
自主代理设计为什么 Agent 空闲时不该只是等下一条指令很多人第一次做多智能体系统时默认采用的都是“派工制”。也就是说lead 负责看全局、拆任务、发消息每个 teammate 只在被明确点名时才开始动。这个模式能跑起来但有一个很快就会暴露的问题只要所有工作都得等 lead 逐个分配团队规模一变大lead 自己就会变成瓶颈。agents/s11_autonomous_agents.py往前推进的一步正是把这个问题拆开处理。它没有一上来做复杂调度器也没有引入数据库或消息队列而是用一种很克制的方式让 teammate 在暂时没活的时候自己看 inbox 有没有新消息自己扫.tasks里有没有没人接手的任务自己认领能做的下一项工作如果长时间什么都没有再自己退出我会把这一节理解成一句话前面的章节让队友“能协作”这一节让队友开始“会自驱”。链接 s11_autonomous_agents.py先说结论如果只看表面s11像是多了两个工具idleclaim_task但真正重要的变化并不在工具数量上而在运行方式上。这节代码把 teammate 的生命过程从“工作完一轮就等着别人再叫我”推进成了“工作完一轮后先自己去找下一份工作”。这件事听起来像一个小优化实际上很关键。因为它改变的不是某个函数而是团队的组织方式lead 不必再负责每一次微观派工teammate 不再只是被动执行者.tasks不再只是记事本而开始变成外部工作源为什么光有协作还不够如果把前几节连起来看会更容易理解s11的位置s09解决的是“队友能长期存在并且能互相发消息”s10解决的是“队友之间的关键协作要有协议和回执”s11解决的是“队友没被点名时也知道下一步怎么找事做”这三步其实对应了团队成长的三个阶段先能联系上彼此再让协作变得有规矩最后才是减少对单一调度者的依赖从工程角度看自治不等于“没人管理”而是把低价值、重复性的派工动作从 lead 身上卸下来。lead 仍然存在只是它不必一直盯着“谁现在空着、下一单派给谁”这种细碎调度。和上一节相比真正新增了什么如果把s10和s11放在一起看变化会更清楚维度s10s11队友拿任务的方式主要靠 lead 明确派活可以在空闲时自己找活空闲后的行为更多是等待下一次唤醒进入idle轮询工作来源以 inbox 为主inbox .tasks任务板长期运行稳定性主要靠现有上下文维持增加身份重注入新增能力重点协议与握手自治与自我调度所以s11不是把上一节推倒重来而是在已有协作能力上补了一层“没有人安排时我自己怎么办”。整体架构图多了一个外部任务板也多了一种空闲后的行为用户Lead 主循环TOOL_HANDLERSTeammateManagerMessageBus.tasks 任务板.team/config.jsonTeammate ATeammate B这张图里最值得注意的不是又多了一个目录而是多了一条工作来源过去主要靠 inbox 接收别人分派的工作现在还可以靠.tasks主动发现没人接手的工作这意味着 teammate 的“下一步”不再只来自别人发来的消息也可能来自它自己对外部状态的扫描。teammate 生命周期从单阶段执行变成双阶段循环这一节最核心的设计其实藏在TeammateManager._loop()里。它把 teammate 的运行分成了两个阶段WORK正常调用模型、执行工具、推进任务IDLE暂时无事可做时进入轮询等待消息或自动认领任务用状态图来看会很清楚spawn / resume持续 tool_use无工具可调或调用 idleinbox 有新消息自动认领到新任务超时无新工作WorkingIdleShutdown这张图里有一个很容易被低估的点idle不是“什么都不做”而是一个显式的调度信号。也就是说模型不是简单地停下来而是在告诉宿主程序我这一轮先做到这里请把我切换到一个更适合等待和找活的状态。在这份实现里idle 阶段的策略也写得很明确每隔 5 秒检查一次最多等待 60 秒只要收到了新消息或认领到新任务就立刻恢复工作如果整个窗口内都没有新工作就自动进入shutdown关键逻辑可以概括成下面这样whileTrue:# 工作阶段for_inrange(50):responseclient.messages.create(...)ifresponse.stop_reason!tool_use:breakifidle_requested:break# 空闲阶段self._set_status(name,idle)...ifnotresume:self._set_status(name,shutdown)returnself._set_status(name,working)这段结构最妙的地方在于它没有把“空闲”理解成结束而是理解成一种可恢复的中间状态。这比“没工具可调就直接退出”更接近真实团队的工作方式。自动认领任务让.tasks从记账处变成找活入口这节代码里我最喜欢的一点是它没有额外做一套很重的调度中心而是直接复用了.tasks/task_*.json。只要满足下面三个条件任务就会被认为是可以认领的status pending没有owner没有blockedBy对应逻辑很直接defscan_unclaimed_tasks()-list:unclaimed[]forfinsorted(TASKS_DIR.glob(task_*.json)):taskjson.loads(f.read_text())if(task.get(status)pendingandnottask.get(owner)andnottask.get(blockedBy)):unclaimed.append(task)returnunclaimed这个设计有两个很实用的优点。第一它把“还有哪些活没做”放到了对话上下文之外。这意味着就算某个 teammate 的历史消息被压缩了任务板本身仍然在系统依然知道有哪些活存在。第二它让任务发现这件事变得统一。不管是 lead 还是 teammate只要读同一份.tasks看到的待办世界就是一致的。我会把这一步理解成任务板不再只是给人看的清单而是开始成为 agent 可以直接消费的工作接口。自动认领的完整时序比“会不会扫描目录”更重要单看scan_unclaimed_tasks()你可能会觉得这不过就是扫一下文件夹。但真正完整的一次自动认领其实包含 4 个动作模型上下文.tasksMessageBusTeammate模型上下文.tasksMessageBusTeammateread_inbox(name)暂无新消息scan_unclaimed_tasks()返回可认领任务claim_task(task_id, name)注入 auto-claimed 任务内容继续调用工具推进任务这里真正关键的是最后两步。teammate 不是只把任务文件改一下就算完而是会把新认领到的任务重新塞回自己的messages里让模型把它当成当前上下文中的“下一件要做的事”。这才是“自动认领”真正闭环的地方。如果只有认领没有重新注入上下文那任务所有权虽然变了模型却不一定知道自己现在该干什么。claim_task()的意义不只是改状态还有把任务所有权显式化认领动作在代码里很短defclaim_task(task_id:int,owner:str)-str:with_claim_lock:pathTASKS_DIR/ftask_{task_id}.jsontaskjson.loads(path.read_text())task[owner]owner task[status]in_progresspath.write_text(json.dumps(task,indent2))returnfClaimed task #{task_id}for{owner}但它带来的效果非常实在外部能看见任务当前归谁负责其他 teammate 再扫任务板时可以跳过已被占用的任务/tasks这样的命令可以直接把任务状态展示出来也就是说自动认领不是一种“心里知道我接了这个活”而是一次落盘的、对全团队可见的状态变更。不过这段实现也留了一个值得继续收紧的地方。虽然代码用了_claim_lock来串行化写入但claim_task()里面并没有再次确认当前任务是否仍然满足“未认领”条件。这意味着如果两个 teammate 几乎同时扫到了同一个任务理论上仍然可能发生“后写覆盖先写”。所以这里的锁更准确地说是把冲突窗口缩小了但还没有把竞争彻底消掉。这也是很有学习价值的地方自治系统一旦开始共享任务源竞争条件就会自然浮现。身份重注入让 agent 长跑时别忘了自己是谁这一节除了“自己找任务”之外另一个很值得注意的设计是身份重注入。原因很现实。一旦系统支持长期运行、上下文压缩、多轮恢复模型就可能逐渐丢失一些最基础但又非常关键的信息我是谁我现在扮演什么角色我属于哪个团队所以代码在某些恢复工作的重要时刻会补一段身份说明iflen(messages)3:messages.insert(0,make_identity_block(name,role,team_name))messages.insert(1,{role:assistant,content:fI am{name}. Continuing.})而make_identity_block()本身做的事情也很简单defmake_identity_block(name:str,role:str,team_name:str)-dict:return{role:user,content:(fidentityYou are {name}, role:{role}, fteam:{team_name}. Continue your work./identity),}这个机制我觉得特别像在长跑中时不时提醒一句你是后端你还在这个团队里你现在不是重新开始而是在接着上一次往下做。它不复杂但特别重要。因为很多时候真正让长期运行系统失稳的不是“大问题”而是这种角色感慢慢漂移的小问题。当然len(messages) 3本身只是一个启发式判断不是严格检测。它表达的意思更像是“如果上下文已经短到不像一次正常持续工作会话了那就把身份再讲一遍。”idle工具看起来很小实际上是自治的开关很多人第一次看到idle可能会觉得它只是一个过渡工具。但在我看来它是这一节最有代表性的设计之一。因为它把“我现在没有明确下一步”从一种含糊状态变成了一个清晰可处理的系统事件。代码里对应的判断非常直白ifblock.nameidle:idle_requestedTrueoutputEntering idle phase. Will poll for new tasks.一旦模型调用了idle宿主程序就知道接下来不该继续把它留在普通工作循环里而是应该把它送到空闲轮询逻辑。这一步很像现实团队里的“报空闲”。队友不是失联了也不是下线了而是在说我手头这一单做完了接下来如果有新活请按你定义好的机制把我接回去。所以idle的价值不在于“暂停”而在于让系统知道该怎么暂停以及暂停后如何恢复。lead 并没有消失只是从派工者变成了管理者看到“自主代理”这几个字很多人会误以为 lead 的作用被削弱了。其实不是。lead 在这节里依然负责很多关键事情拉起 teammate读取团队 inbox处理关机和计划审批协议必要时直接认领任务变化只是lead 不再需要事无巨细地做每一次微观派工。从组织形态上看它更像从“调度所有细节的人”变成了“定方向、看例外、处理关键决策的人”。我觉得这正是自治设计最健康的样子。不是把 lead 去掉而是让 lead 从低价值重复劳动里解放出来。这节最值得带走的 6 个判断1. 自治不是让 agent 一直忙而是让它在没活时也有明确行为如果空闲时没有规则系统就只能在“原地傻等”和“直接退出”之间二选一。s11选择的是第三条路进入可恢复的 idle 状态。2..tasks的真正价值不只是存任务而是给 agent 提供统一的外部工作源只要任务状态不被锁死在聊天记录里agent 才有机会在压缩、恢复、重启之后继续推进工作。3.idle是调度信号不是摆设工具它把“暂时没有下一步”变成了明确状态迁移让宿主程序知道该切换运行模式。4. 自动认领真正难的不是扫描目录而是保证状态闭环要扫描、要落盘、要回写上下文这三步少一步自治都会变得不完整。5. 长期运行系统迟早会遇到身份漂移问题身份重注入看起来像一个小补丁但它解决的是长期会话里很容易被忽视的稳定性问题。6. 一旦多个 agent 共享任务源就必须正视竞争条件_claim_lock已经是很好的开始但更严格的二次校验、原子更新甚至事务化认领都会是后续自然会走到的方向。这份实现还有哪些边界从示例的角度看这份代码已经把“自治”最小闭环讲清楚了但它也保留了几个很值得记住的边界。1. 现在是轮询不是事件驱动idle 阶段每 5 秒检查一次 inbox 和任务板简单直观但不算特别实时。它的好处是容易理解、容易实现。代价是有固定轮询延迟空闲时仍然会有周期性检查开销2. 任务认领还不是严格原子操作前面提到过当前实现把写入串行化了但还没有在锁内再次确认任务仍未被认领。如果继续往生产方向收紧这里会是一个很自然的增强点。3. 身份重注入使用的是启发式判断len(messages) 3很实用但并不等于“系统准确知道自己刚发生了上下文压缩”。它更像是一种低成本补救。4. 协议追踪表仍然主要是进程内状态关机请求和计划审批的 tracker 继承了上一节的做法主要还在内存里。这意味着这套自治系统已经能跑但还没有完全进化成跨重启也稳态可追踪的完整调度系统。最后总结agents/s11_autonomous_agents.py最值得学的不是“又多了两个工具”也不是“让 teammate 闲着时扫一下文件夹”这么简单。我觉得它真正讲明白的是一个团队如果想从“会协作”走向“更像真实团队”就不能让所有下一步都依赖上级逐个派工。这节代码用很轻的方式把这件事拆成了三个可落地的动作用idle把空闲显式化用.tasks给队友提供外部工作源用身份重注入保证长期运行时角色不漂这三步都不重但一旦连起来系统气质就变了。它不再只是“有人问就答、有人叫就干”的 agent 集合而开始有一点“自己能接上下一棒”的味道了。致谢学习主线受益于shareAI-lab/learn-claude-code

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询