1. 从单体机器人到协作生态:先聊聊这轮演进的背景
先说结论:Clawdbot和Moltbook并不是什么商业产品,而是我自己在一系列模拟项目里逐步迭代出来的两代智能体系统代号。Clawdbot代表的是最早期的、单打独斗的机器人形态,Moltbook则是在它基础上重构出来的、具备社会化协作能力的智能体平台。把这两代系统放在一起看,其实就是一个完整的Agent社会化进程样本——从单点工具到协同网络,从"人指挥机器"到"机器之间互相配合"。
这几年做智能体落地,我最大的感受是:技术圈里都在讨论Agent,但绝大多数讨论停留在"单Agent能力"上。一个人能写个工具调用,能记忆几轮对话,就已经算"Agent"了。但实际上,当你把视线拉长,会发现单智能体的天花板非常低——你没法让它同时处理多个长周期任务,没法让它在不同场景之间共享经验,更没法让多个任务之间形成真正的协作关系。这些瓶颈,只有在真正把Agent放进一个"社会"里,让它和其他Agent产生连接、分工、通信、记忆交换时,才会被打破。
Clawdbot到Moltbook的演进,恰好完整覆盖了这条路。Clawdbot是最典型的单体Agent:一个进程、一套上下文、一条任务队列,所有事都自己扛。而Moltbook把Agent拆成了角色、分组、会话、消息总线、共享记忆库这几个层次,让每个Agent变成了社会网络里的一个"节点"。这个过程听起来很顺理成章,但实际落地时的细节非常多,很多选择和权衡,都是我踩了坑之后才想明白的。
这篇文章想把整个过程完整还原一遍:为什么当初要把单体Agent推倒重来,Moltbook里的几个核心机制到底是怎么设计的,跨Agent通信的协议怎么定,共享记忆怎么避免"记忆污染",以及实际跑起来之后遇到过哪些奇怪的问题。如果你正在做多智能体系统,或者手里有一个单体Agent想往协作方向演进,这篇文章应该能给你一份比较完整的参考。
2. 拆解Clawdbot:单体智能体的设计与它的三个结构性缺陷
2.1 Clawdbot的设计初衷和基础架构
Clawdbot最初是为了解决一个很具体的问题:自动抓取指定站点上的信息,清洗后生成结构化摘要。它本质上是一条"爬虫+分析器+报告生成器"的流水线,外面包了一层对话式Agent外壳。用户用自然语言下达指令,Clawdbot负责拆解任务、调度内置工具、执行结果并汇总。
它的架构在当时的视角下很简单,也很清晰。核心组件只有四个:任务解析器(把用户指令转成可执行步骤)、工具调度器(管理爬取、解析、摘要三个内置工具)、上下文管理器(维护当前会话的对话历史和中间结果)、输出格式化器(把结果整理成Markdown报告)。整个系统跑在一个事件循环里,一个请求进来,解析、调度、执行、返回,串行完成。
代码层面,它的核心逻辑可以简化成这样一个循环:
class Clawdbot: def __init__(self): self.context = ContextManager() self.tools = {"fetch": FetchTool(), "parse": ParseTool(), "summarize": SummarizeTool()} def handle(self, user_input): steps = self.parser.parse(user_input) # 拆解指令为步骤链表 for step in steps: tool = self.tools[step.tool_name] result = tool.run(step.params, self.context) self.context.append(step.tool_name, result) return self.formatter.render(self.context) bot = Clawdbot() print(bot.handle("抓取A站最新公告并总结"))单看这个循环,功能是能跑通的。但用过一段时间后,我逐渐意识到一个问题:这个系统在处理单任务、短任务时表现还行,一旦任务量上来,或者任务之间产生关联,它就明显顶不住了。
2.2 缺陷一:上下文是"独享"的,经验无法沉淀
Clawdbot的上下文管理器,本质上就是一个围绕当前会话的临时存储。一次会话结束后,所有中间结果、工具调用的经验、操作过程中的调整方案,全部清空。这意味着即便是同一个Agent,昨天处理过的一类问题,今天遇到相似的场景,依然要从零开始摸索。
举个例子。我让Clawdbot连续三天抓取同一个站点的同一类页面。第一天,它的解析器需要重新识别页面结构;第二天,依然要重新识别;第三天,照样如此。因为解析结果只是作为中间产物存放在当前上下文里,没有进入任何长期记忆。从用户的视角看,这个Agent看起来很"傻",没有任何积累和成长的痕迹。
这个问题在单任务场景下还能忍,但一旦你把Agent当作一个"长期存在的数字员工"来使用,它就是致命的。长期存在的Agent,必须有能力把过去的经验抽象成可复用的策略,而不是每次都从头跑一遍完整流程。
2.3 缺陷二:任务串行阻塞,并发能力为零
Clawdbot是一个彻底的串行系统。一个请求没处理完,下一个请求就必须排队。这个设计在单用户、低频率的场景下没毛病,但只要同时来了两个任务,其中一个任务因为外部站点响应缓慢而长时间挂在IO上,另一个任务就跟着一起卡死。
我记得有一次跑一个批量采集任务,一共两百个页面,其中大约十个页面响应特别慢,单页耗时达到二十多秒。整个任务因为串行执行,硬生生跑了快二十分钟。如果当时能并发执行,同时跑五六个任务,时间能压缩到三分钟以内。这个体验让我下定决心:Agent不能只是一个"单线程的办事员",它必须能并行处理多个任务,甚至在多个任务之间动态调配资源。
2.4 缺陷三:工具之间没有协作协议,Agent之间更谈不上"通信"
Clawdbot里的工具,是直接调度、直接执行的。工具和工具之间唯一的沟通渠道,就是共享上下文。比如fetch工具跑完,把HTML文档丢进上下文,parse工具从上下文里捞出来处理,处理完再丢回去。这种"通过黑板的间接通信"简单,但没有边界,任何工具都能读写任何数据,也就很难控制数据流的方向和权限。
更关键的一点是,Clawdbot根本没有"Agent之间通信"这个概念。它只有一个Agent实例,所有事情都由这一个大脑处理。而真实世界的任务,往往是需要分工的:一个Agent负责收集原始材料,另一个Agent负责交叉验证材料的可信度,第三个Agent负责组织成最终输出。如果缺了Agent与Agent之间的通信机制,这种分工就无法实现。这也是后来Moltbook把所有重心放在通信和协作上的直接原因。
3. Moltbook的核心设计思路:把Agent当成社会节点来重构
3.1 从"单体大脑"到"多角色社会",Moltbook的角色模型
Moltbook的设计起点,是我偶然把Agent比作了一个社交网络里的人物。在社交网络里,每个人都有自己的角色、关系、圈子,人与人之间的互动遵循一定的规则;而Moltbook想做的,就是把这种"社会性"映射到Agent系统里。
这个映射关系最终被拆成了四个层次:角色(Role)、群体(Group)、会话(Session)、消息(Message)。每一个Agent实例在启动时会被赋予一个角色,比如"采集员"、"分析师"、"审核员"、"汇报员"。角色决定了它拥有哪些工具、能读哪些记忆、能向哪些群体发消息。群体是一些Agent的集合,类似于社交平台上的群组。同一个Agent可以同时加入多个群体,在不同群体里扮演不同身份。会话则对应一次具体的协作任务,会话里的所有消息都会被记录下来,供群体内所有Agent读取。
这么一来,整个系统不再是一个闭环,而是一张网。用户下发一个综合任务时,任务会被拆分成多个子任务,分别投递给不同的Agent。这些Agent通过消息总线互相沟通,通过共享记忆库交换中间成果,最终由一个负责汇总的Agent整合所有信息,返回给用户。
3.2 社会化进程中最重要的一步:从"指令驱动"到"事件驱动"
如果用一句话概括Clawdbot到Moltbook最重要的变化,我会说:Clawdbot是"指令驱动"的,Moltbook是"事件驱动"的。
Clawdbot的运行机制可以理解为"用户喊一句,它动一下"。每一步操作都来源于上一轮的返回结果,全流程由用户的指令链条驱动。而Moltbook里,每个Agent都是一个独立运行的任务循环,它不停地监听自己所属群体里的消息。当一条新消息出现时,它判断消息是否与自己相关,如果相关就执行相应的动作,并把动作结果以事件形式广播出去。整个过程不需要用户介入,Agent与Agent之间通过事件信息形成自动化的协作链条。
事件驱动的优势在于:系统的扩展性极大地提高了。你可以在不修改任何现有Agent代码的前提下,往系统里增加一个新角色,只要它订阅了正确的群体消息,它就能自动参与到协作流程里来。这个特性在Clawdbot时代是根本做不到的,因为一切都是强耦合的。
我画过一张简化的消息处理循环,大概是这样一个骨架:
class MoltAgent: def __init__(self, role, groups, tools, memory): self.role = role self.groups = groups self.tools = tools self.memory = memory def run(self, bus): while True: message = bus.receive(self.groups) if not message: continue if self.can_handle(message): result = self.tools[message.tool_type].run(message.payload) self.memory.commit(role=self.role, data=result) bus.publish(topic=message.reply_topic, payload=result)这段代码故意写得很简单,因为核心思想本来就不复杂:Agent是一个循环,在循环里接收消息、判断是否处理、执行工具、写入记忆、发布新事件。真正的复杂度在消息协议、记忆隔离、路由规则这些"外围"设计上。
3.3 共享记忆库与消息总线的分工:为什么不能只用一个
在Moltbook里,有两个容易混淆的概念:消息总线和共享记忆库。很多第一次接触这套架构的人会问:既然消息总线里已经流动着所有信息,为什么还需要记忆库?直接让Agent之间互相发完整数据不就行了吗?
这个问题的答案,其实藏在实际操作的成本里。消息总线的定位是"瞬时通信",消息发出后,接收者处理完就扔掉了。如果A想让B以后还能随时查到某条处理结果,每次都重新发一遍消息,代价太高,也不现实。于是就需要一个共享记忆库,用来沉淀那些长期有效的信息。
消息总线负责的是"通知"和"唤起",比如"某份原始数据已经采集完毕,请分析师处理"。真正的数据本身并不直接附在消息里,而是存在共享记忆库里,消息里只带有数据在记忆库中的路径。分析师收到消息后,按路径去记忆库里取数据。这个设计和抽象非常像"邮箱"与"磁盘"的分工:邮箱告诉我们有事情发生了,磁盘保存着我们真正需要的资料。
这种分工也带来了一个好处:记忆库可以做版本管理。采集员第一次采集到的数据是一版,修正后的数据是第二版。后续的分析师可以按版本号回溯,查看当时分析依据的到底是哪一版数据。这在多角色协作里极其重要,因为每个Agent的工作成果都建立在其他人的成果之上,没有版本概念,就无法建立可靠的依赖链。
4. 落地实操:Moltbook的通信协议、记忆隔离与Agent调度
4.1 通信协议的设计:消息结构、路由规则与重试机制
Moltbook的消息协议,是我在整个项目里反复迭代时间最长的一部分。原因很简单:Agent之间的通信格式如果设计得不好,后续扩展每个角色都会变得极其痛苦。最初我用的是简单的JSON字符串,字段随意,爱怎么传就怎么传。跑了不到两周就出问题了,两个Agent对同一个字段的理解不一致,一个传给另一个的消息里写的是page_url,对方却读body_url,导致下游链路直接断掉。
后期我把消息结构收敛成了一套固定骨架,分为五个部分:消息ID、来源角色、目标群体、消息类型、数据载荷。消息类型又细分成了task_assign(任务下发)、data_ready(数据就绪)、need_review(需要审核)、review_done(审核完成)、report_ready(报告就绪)等十几种。每个Agent在编写时只需要按照类型约定处理,不用关心其他Agent的个性化字段。
路由规则是另一个容易忽略的点。最初的版本里,消息总线是纯广播模式——每一条消息都会推给群体内的所有Agent。这种模式在Agent数量少时没什么问题,但一旦数量超过二十个,每个Agent都会收到大量无关消息,白白消耗计算资源。后来我为每个角色增加了一个订阅过滤器,只有符合订阅条件(比如特定的消息类型加特定的数据范围)的消息才会被投递。这个改动让系统整体负载下降了将近四成。
重试机制则是最血泪的教训。Agent之间的通信不像程序内部函数调用,失败是非常常见的——目标Agent可能正在忙,也可能因为外部工具临时不可用而无法处理。最初的Moltbook对发送失败的消息一律抛弃,结果经常出现任务做到一半"无声地中断"的情况。后来我照着消息队列的成熟做法,给每条消息增加了最大重试次数和退避策略。第一轮重试等待五秒,第二轮三十秒,超过五轮就回退到死信队列,由人工介入处理。
4.2 记忆隔离与"角色视角":如何防止共享记忆变成一锅粥
共享记忆库是Moltbook里最强大的机制,也是最容易失控的机制。如果所有Agent都能无限制地读写所有记忆数据,那么这条共享链路很快就会因为数据规模膨胀而彻底瘫痪,更严重的是,不同角色的认知视角会被强行拉齐,Agent的专业性就没法保证了。
我采用的方案是"角色视角隔离"。记忆库里每一条数据记录,在写入时都要声明可见角色列表。采集员写入的原始抓取数据,默认只有分析师可见;分析师写入的分析结论,默认只有审核员可见;审核员写入的审核意见,默认只有汇报员可见。跨角色读取不是不可以,但必须显式申请临时授权,且授权记录要写入审计日志。
这个设计的直接驱动力来自一次真实事故。有一段时间,所有Agent共享同一块记忆空间,结果汇报员在生成报告时,把采集员留下的原始调试日志当成了正式结论,直接写进了最终输出。那次的教训让我意识到:让Agent看到所有信息不等于让Agent拥有所有信息,记忆的可见性必须跟随Agent的角色视角。就好比在一个公司里,销售能看到客户线索,但只有财务能看到账目明细,两者不能也不应该互相干扰。
4.3 Agent调度与任务编排:当Agent数量变多后,谁来统筹全局
Moltbook进入多角色阶段后,我遇到的下一个问题是:Agent多了,任务链条长了,到底由谁来决定下一个该执行哪个角色?最初的设想是"完全去中心化",让每个Agent自己判断什么时候动手。实际跑起来发现,这完全是个理想化的设想。如果没有任何统筹机制,会出现两种极端的局面:一是所有Agent同时认为"该我上场了",导致同一个任务被并发执行好几遍;二是所有Agent都觉得"还没轮到我",任务被卡在原地下不去。
最终的方案是引入一个轻量的调度器,但它的职责非常克制。调度器不干预Agent之间的具体交互,只负责两件事:任务拆解和进度检查。用户下发的综合任务会经过调度器拆解成子任务,并标注子任务之间的依赖关系。只有前置依赖全部完成,调度器才允许后续角色接收相关消息。这相当于给Agent社会加了一个温和的交通信号灯,它不决定车的行驶方向,只保证车辆不会在十字路口撞在一起。
调度器的存在也让任务追踪变得容易很多。每个子任务从创建到完成的所有状态流转(pending、running、blocked、completed、failed)都会被记录,我可以在任意时刻查看当前系统里到底有多少任务在跑,卡在哪个环节,哪个Agent成了瓶颈。这在Clawdbot时代是完全做不到的,那时的任务状态和上下文捆绑在一起,一旦会话结束,一切烟消云散。
下面是一段调度器的简化示意代码,用于展示任务依赖是如何维护的:
class Scheduler: def __init__(self): self.tasks = {} # task_id -> Task self.dependencies = {} # task_id -> set of task_ids def add_task(self, task): self.tasks[task.id] = task self.dependencies[task.id] = set(task.depends_on) def ready_tasks(self): ready = [] for task_id, task in self.tasks.items(): if task.status == "pending" and not self.dependencies[task_id]: ready.append(task) return ready def complete(self, task_id): task = self.tasks[task_id] task.status = "completed" for other_id, deps in self.dependencies.items(): deps.discard(task_id)这段代码对应的核心思想是:任务依赖就是一张有向无环图,调度器只需要维护每个任务的依赖集合,当一个任务完成时把它从所有其他任务的依赖集合里移除,那些依赖集为空的待执行任务就自然变成可调度的了。理解了这个逻辑,再去看各种工作流引擎的源码,会发现它们本质上都是同一套思路的变种。
5. 从Clawdbot迁移到Moltbook:实操过程、兼容策略与取舍记录
5.1 渐进式重构还是推倒重来?我选择的迁移路径
当Moltbook的架构设计基本成形后,我面临一个很现实的问题:Clawdbot虽然结构简单,但已经稳定运行了一段时间,直接推倒重来意味着所有存量逻辑都要重写,迁移风险很高。但不重写的话,旧系统那些根深蒂固的串行逻辑又很难在新架构里自然融入。
我最终选择的路径是"渐进式替换"。第一阶段,保留Clawdbot原样运行,不动它的任何逻辑。第二阶段,在Clawdbot外面套一个适配层,让它可以作为Moltbook里的一个特殊角色对外提供能力。也就是说,从Moltbook的视角看,Clawdbot只是一个调用接口,内部怎么实现根本不关心。第三阶段,等新架构里的角色足够丰富、稳定性足够高之后,再把Clawdbot里的核心逻辑拆解、移植到多个新角色里,关闭旧实例。
整个过程前前后后花了三周时间,但好处是系统始终处于可用状态。某个环节出问题了,随时可以回退到旧方案。这也是我后来做任何大型重构都坚持的原则:宁可多花时间做兼容,也不要让系统经历"不可用的真空期"。
5.2 关键迁移示例:把单一抓取任务改造成三角色协作链
为了让你更直观地看到"单体到社会化"到底改了什么,我把Clawdbot最典型的抓取任务,当年是怎么一步步变成Moltbook里三角色协作链的过程完整拆一遍。
在Clawdbot里,一个抓取任务就是一条串行流水线:解析URL、下载页面、清洗正文、归纳摘要、输出报告。整个过程由同一个Agent驱动程序,任何一个环节出错,整个任务就失败。在Moltbook里,我把这个流程拆成了三个独立角色:采集员只负责下载页面和清洗正文,不关心摘要;分析师只负责读取出清洗好的正文并生成摘要,不关心下载过程;汇报员只负责汇总摘要并格式化输出,不关心清洗细节。
每个角色各干各的,而且随时可以由多个同类角色并行处理不同任务。比如需要采集一百个页面时,我可以启动五个采集员角色同时干活,每个采集员分到二十个页面。采集完成后,它们各自把结果提交到共享记忆库,然后发出一条data_ready消息。分析师角色监听到消息后,从记忆库依次取数据做摘要。摘要完成后同样提交并发出review_done消息。最后汇报员汇总全部摘要生成完整报告。
从用户视角看,他感受到的唯一区别是:同样的任务,以前要等二十分钟,现在三分钟就出结果了。但底层已经从单线程变成了一个多角色并行、事件驱动的协作网络。
5.3 迁移过程中需要特别留意的兼容性陷阱
在迁移过程中,我遇到过几个差点让项目"翻车"的细节,值得单独拿出来说一说。
第一个坑是旧系统的状态存储格式。Clawdbot里的中间结果存在本地文件里,格式是自有规定的,没有标准的版本信息和元数据。迁移时,新系统的共享记忆库识别不了这些旧格式的数据,导致有一部分历史信息无法进入新系统的上下文。这个问题本身不可怕,可怕的是如果一个Agent在迁移后试图读取旧格式数据,又无法解析,就会直接把任务抛异常。解决办法也很粗暴,我在记忆库的读取接口加了一道格式检测,遇到旧格式数据直接跳过并记录告警,不让它阻断整个任务链。
第二个坑是消息协议理解不一致。迁移后新旧系统并存的那段时间里,我必须让适配层把Clawdbot的请求转成Moltbook的标准消息格式,但转出来的消息经常会少字段。比如旧系统经常不区分"任务创建时间"和"任务开始执行时间",在新协议里这是两个字段。凡是遇到这种语义不对齐的字段,我优先选择在适配层做字段级别的补齐,而不是去改旧系统的逻辑,因为改旧系统一旦改出问题,影响面是未知的。
第三个坑是并发量放大后的限流问题。多Agent并发执行任务时,外部接口的请求频率会比单体时代高很多。采集场景还好,不涉及太多外部依赖,但有些需要调用第三方接口的任务,突然面临成倍的请求并发,第三方接口直接开始拒绝服务。后来我在调度环节统一做了并发配额管理,每个角色同一时刻最多允许发起的请求数有上限,避免Agent一多就互相抢占资源。
6. 实操中的典型问题与排查方法
6.1 消息风暴:Agent协作时消息量暴增的处理方案
多Agent系统跑起来之后,第一个让我头疼的问题是消息风暴。理论上,事件驱动的架构鼓励Agent之间多通信,但在实际运行中,消息量会以一种近似指数的方式增长。某次测试场景里,我启动了六个角色做一次综合任务,原计划总共只需要二十条消息,结果系统实际产生了将近两百条消息,其中一半以上是重复的通知和确认。
排查下来的根因是:消息总线上没有做"已读去重"。多个Agent收到同一条消息后,会各自向发件人回复确认,而每条确认又被当作新事件广播给其他Agent,其他Agent再回复确认的确认。这种自激震荡在通信架构里是非常典型的。最后的解决方案是在消息总线上增加了消息ID去重机制,每个Agent只对每条消息回应一次,重复的消息不再被投递。处理完这个问题后,整个系统的消息量立刻降回了正常范围。
另一个小技巧是按需订阅。我曾把所有角色的订阅过滤器关掉,测试纯广播模式下的系统表现。结果表明,六个角色里通常只有两个真正关心某类消息,其余四个都是在空转。所以我的实际建议是,除非你的Agent数量极少,否则老老实实给每个角色配置订阅规则,这比事后优化通信效率要省心得多。
6.2 记忆库的"串味"问题:如何追踪和切断错误记忆来源
共享记忆库运行一段时间后,会出现一种很隐蔽的问题:记忆库里的数据越来越多,某些数据的来源链条变得极其模糊,下游Agent分不清一段分析结论到底是基于哪一批原始数据得出的。一旦采集员修正了部分原始数据的错误,分析师却仍然引用了旧版本数据,最终生成的报告就会出问题。
我把这个问题叫作"记忆串味"。解决它靠的是在共享记忆库的所有写入操作里强制带上"数据血缘"信息。每条记录保存时,除了数据本身,还要记录它的来源消息ID、来源角色、依赖的数据版本号。这样一来,任何一段记忆都可以追溯它的完整生成链路。
排查串味问题时,我一般会先打开记忆库的审计日志,按时间范围筛选出可疑的数据记录,然后顺着血缘信息一层层回溯。有一次花了一个多小时才定位到问题,根因是采集员在修正数据时没有覆盖旧记录,而是新写了一条记录。下游分析师依赖的旧版本数据自然还是错的。这个问题的本质不是记忆库的bug,而是写入策略不明确:更新记忆应该用覆盖还是追加,必须在系统设计时明确下来。我的最终选择是默认保留旧版本、写入新版本,所有读取必须显式指定版本号,避免Agent在不知情的情况下读到过期数据。
6.3 调试多Agent系统的三条实用经验
调试多Agent系统比调试单体系统难得多,这是很多转过来的人第一个感受。单体系统里出了问题,断点一打,变量一查,问题基本就定位了。多Agent系统里,每个Agent都在自己的循环里运行,消息并发流动,断点几乎没法打,排查问题时往往需要从消息日志和记忆库入手。
我的第一条经验是:把消息日志当主线,把所有Agent的执行日志当辅助。一条消息从发出到被处理、再到触发后续消息,完整链路应该是清晰可追的。只要消息链路上某个环节断了,一定能在消息日志里发现,顺着这个断点去看对应Agent的执行日志,通常很快能找到根因。
第二条经验是:不要试图同时看所有Agent的日志,看不过来的。我改用了一套优先级策略,先看汇报员(最终输出者)的结果,然后逆推它依赖了哪些数据,再看这些数据是谁产出的,逐步缩小排查范围。这个思路和调试分布式系统时常用的"从响应反向追踪调用链"是同一个逻辑。
第三条经验是:建立一套"最小复现场景"。出了疑难问题时,我会把Agent数量缩到最少,让消息链缩短到只有一个消息传递的过程,在一个完全隔离的环境里复现问题。绝大多数时候,问题在缩到最小场景后会变得异常清晰,因为那些干扰性的噪声消息被过滤掉了。
7. 一些额外值得分享的实践经验
最后聊几个不在主线设计里、但实际工作时让我省了很多力气的细节。
第一点是关于工具注册。在Moltbook里新增一个Agent角色很容易,但新增一个工具需要想清楚。我把所有工具的注册信息收拢到了一个统一的配置里,每个工具都要声明输入参数、输出格式、执行权限和超时时间。这让工具的使用非常规范,新增角色时不需要翻代码找接口,直接在配置里引用工具名就行。
第二点是关于异常处理。单体Agent时代的异常处理是针对"这一个Agent"的,错了就重新跑。多Agent系统里,异常必须是一个跨角色的概念。一个角色的失败不能只影响自己,必须能被下游角色感知,否则下游角色会因为拿不到输入数据而一直阻塞。我的做法是:任何角色在执行任务失败时,都必须广播一条task_failed消息。下游角色收到失败消息后,可以选择等待重试、切换到备用数据源,或者直接跳过当前子任务。
第三点,也是我想特别强调的一点,是Agent系统的可观测性建设。很多人在搭单体Agent时根本不重视日志和监控,但到了多Agent阶段,没有可观测性寸步难行。我后来养成了给每条消息、每次记忆写入、每次任务状态转换都加结构化日志的习惯。这些日志平时看起来不起眼,但真正排查问题时,它们就是唯一能还原现场的资料。如果你想提前预判系统可能出的问题,最简单的手段就是监控消息队列的积压量。积压量一旦快速上涨,说明某个角色变成了瓶颈,系统的负载平衡可能已经出了问题。
我自己在实际操作中的体会是,做Agent系统最忌讳的是一上来就追求"大而全"的架构。Clawdbot简单,但它让我把单体Agent的各种边界摸清楚了,知道瓶颈集中在哪几个地方。Moltbook复杂,但因为是一步步演进过来的,每一步都对应着一个已经被验证过的真实需求。很多人在架构设计阶段容易陷入的理论争论,在真实跑起来之后根本不成立。与其反复推演"最完美方案",不如先把一个最小闭环跑通,然后按真实需求一点点丰富它。这大概是这套演进过程留给我最核心的经验。