800行代码实现OpenClaw的Tool、消息总线、子Agent管理架构

2026年4月24日

75

525

800行代码实现OpenClaw的Tool、消息总线、子Agent管理架构

在Agent系统开发中,如何平衡抽象层次与工程确定性是一个核心命题。当业界普遍依赖LangChain等框架时,一个有趣的设计思路是采用薄抽象、显式控制流的方式,直接面向API构建系统。本文将详细介绍一个基于Anthropic Claude API的轻量级Agent框架实现,展示如何在800行代码内构建Tool系统、消息总线和子Agent管理三大核心模块。

概述

MessageBus处理入站方向的消息流,实现从子系统或外部到主Agent的消息传递。其核心是订阅-发布模式,InboundMessage包含channel(消息通道)、senderId(发送者标识)、chatId(关联会话)和content(消息内容)四个字段。 MessageBus提供了两种消费模式:subscribe注册实时回调,适合常驻服务场景;drain从队列中取出并清空消息,适合轮询式的同步消费场景。路由规则清晰:有订阅者走回调,无订阅者入队列。消息只走一条路径,不会同时触发回调和入队。这种设计避免了消息处理的歧义性。 需要明确的是,MessageTool负责出站(Agent→外部),MessageBus负责入站(外部→Agent),两者方向相反,没有直接的代码耦合。这种解耦设计使得消息流清晰可追踪。

消息总线:入站消息的统一枢纽

整个系统存在两条数据流主线: 同步路径:用户输入→REPL→agent.run()→工具调用→结果回传模型→最终回复→stdout。这是标准的ReAct循环。 异步路径:SpawnTool/CronService→bus.publish(system)→handler入队→tryDrainPending()→agent.run()处理系统通知→stdout。异步结果通过MessageBus汇入主循环,由互斥锁保证不与同步路径冲突。 这种设计使得同步和异步操作能够有序共存,不会产生状态混乱。切换接入层(从REPL到Bot)时,只需替换sendCallback和输入源,工具系统和Agent逻辑无需任何改动。

中间层越薄,调试越容易,对API行为的控制越精确。

“设计原则”

模块协作与数据流

REPL(Read-Eval-Print Loop)是整个Agent的终端交互入口。核心挑战在于:用户输入和子Agent回传结果都可能触发agent.run(),但history数组是共享的,不能并发修改。 解决方案是布尔互斥锁配合暂存队列。processing标志充当互斥锁,确保同一时刻只有一个agent.run()执行。子Agent结果到达时先push到pendingSubagentResults数组,tryDrainPending只在非processing状态下进入,避免并发写入history。用户交互完成后,在释放锁之前调用drainPendingResults处理积攒的子Agent结果,保证结果不会无限滞后。 启动流程按以下顺序初始化:创建Anthropic客户端和MessageBus实例→注册工具到ToolRegistry(区分主Agent和子Agent工具集)→初始化CronService→创建SubagentManager→构建ContextBuilder→创建AgentLoop并启动交互循环。

设计哲学与局限

这个框架的核心设计哲学可以概括为四个要点:薄抽象层带来的调试优势、显式控制流带来的可追踪性、零框架依赖带来的完全控制力、以及贴近SDK设计带来的类型安全。 当然,设计也存在局限:子Agent无持久记忆,不适合需要跨任务积累上下文的场景;CronService的cron表达式是近似实现,不支持精确时间点语义;MessageBus无持久化,进程重启后消息丢失;ExecTool的正则黑名单只是最低防线,生产环境应该使用容器沙箱;REPL的布尔锁在多用户场景下需要更完整的队列机制。 这些取舍反映了「先跑起来,再逐步完善」的工程思路。框架的可扩展性已经预留好了空间:扩展新工具只需继承Tool并注册,切换接入层只需替换回调,输入源的变化不会影响核心逻辑。

如有侵权,请联系删除。

Related Articles

联系我们 获取方案
小墨 AI