Agent-Reach 这个名字乍一看挺像营销工具的,但它实际上是我折腾了大半年的一个 AI Agent 互联协议项目。做 AI 应用的同学应该都有这种体会:单机跑一个 agent 不难,难的是当你手里有十几个来自不同框架、不同团队、不同运行环境里的 agent,想让它们自动互相找到、按需调用能力的时候,整个系统就迅速变成一团乱麻。Agent-Reach 解决的正是这个问题——它给分散部署的 agent 提供一套发现、路由、鉴权的通信底座,让 agent 之间能像微服务一样互相寻址、建立会话、协同完成任务。项目适合正在做多 Agent 系统、 Agent 平台或智能工作流编排的团队参考,也适合想搞懂 Agent 互联原理的开发者当成一份入门材料来读。
1. 项目动机:Agent 之间的连接层,为什么值得单独做
先聊点背景。过去一年圈子里涌现了大量单 Agent 应用:写代码的、做数据分析的、管日程的、生成图文的,单拎出来个个都能干活。可真到生产环境里,你会发现这些 agent 其实是“孤岛”。每个 agent 内部把规划、工具调用、记忆都做得很完整,但对外基本没有标准接口,A agent 想请 B agent 帮个忙,要么人肉搬运结果,要么硬写一套私有 HTTP 调用,然后陷入接口维护的泥潭。
我把这个阶段称为“多 Agent 的蛮荒期”。大家拼的不是单点能力,而是怎么把一堆能力组织成一个有机整体。市面上有 agent 编排框架、有工作流引擎,它们解决的是“流程怎么走”的问题,但始终有一个基础问题没人好好管:agent 之间到底怎么发现彼此、怎么声明自己能干什么、怎么安全地把请求送到正确的实例上?
Agent-Reach 的定位非常明确:它不做规划、不抢框架的活,只做“连接层”。你可以把它理解成 Agent 世界的 DNS 加路由网关——每个 agent 启动后把自己的身份和能力注册上来,其他 agent 只需要知道一个逻辑名字,就能找到目标并发起调用。至于对方内部是用 LangChain、AutoGPT 还是自研框架,完全不用关心。
这个设计取舍我斟酌了很久。最初差点把任务调度、状态机、多轮协商都塞进来,后来发现这是贪多嚼不烂。把连接层做薄、做稳,让上层框架有足够的自由发挥空间,反而更符合生态规律。一个协议如果什么都管,最后一定什么都管不好。Agent-Reach 只承诺三件事:能发现、能路由、能被信任,其余全部留给调用方。
2. 核心架构拆解:四个关键模块怎么分工
整个系统的核心可以拆成四块,彼此独立、通过一套明确的消息协议协作。没有分布式大佬那种精巧到炫技的架构,每一步都是被真实部署逼出来的。
2.1 注册与发现(Registry)
Registry 是所有 agent 的中枢登记处,负责记录当前有哪些 agent 在线、在哪个地址、提供什么能力。每个 agent 启动后必须向 Registry 发送注册请求,并携带自己的 agent_id、连接地址、能力清单。为了及时清理死掉的实例,我设计了心跳机制:agent 每 15 秒发一次心跳,超过 45 秒没收到心跳,Registry 就把该实例标记为不可用,并从发现结果里摘掉。
状态存储这块我采用了分层方案:在线状态这类短时效数据放 Redis,用 TTL 自然过期;能力清单和 agent 元数据放 SQLite 或 PostgreSQL,便于做版本回溯和审计。为什么不全放 Redis?因为能力清单是相对稳定的配置数据,一旦注册中心重启就全丢会引发灾难,数据库落盘才能保证重启后快速恢复。
2.2 能力声明(Capability Manifest)
这是 Agent-Reach 最核心的数据模型。每个 agent 必须用一个 YAML 文件描述自己的能力,格式固定,包含能力名、描述、输入输出 JSON Schema、超时时间。JSON Schema 是灵魂——有了它,调用方才能在做参数校验和结果解析时不再靠猜。
我当时特意让 manifest 跟随代码仓库走,而不是由注册中心动态改。能力是代码的公开契约,既然是契约就应该走代码评审、版本管理,不能让人在后台悄悄改一个字段就把线上行为变了。每次发布新版本,agent 启动时带上 manifest 的版本号,Registry 会记录当前生效版本,并保留历史版本用于回溯。
2.3 消息路由与会话管理
注册只是第一步,真正的难点在路由。Agent-Reach 采用了两层路由模型:第一层按 agent_id 定位目标实例,第二层按能力名定位具体动作。消息在协议层被表达成 Reach 地址,格式类似reach://data-fetcher/query_metrics,语义就是“找到>docker run -d --name reach-registry \ -p 8866:8866 \ -e REACH_STORE_DSN=sqlite:///data/reach.db \ -v reach-data:/data \ reach-registry:1.4.2
Registry 对外暴露两个端口:8866 是 WebSocket 接入端口,agent SDK 连这个;8867 是管理端口,用于查看在线状态和健康检查。首次启动后打开http://localhost:8867/health,能看到服务状态和当前注册的 agent 数量,就说明注册中心起来了。
3.2 让 agent 注册并声明能力
给 report-bot 写一份 manifest.yaml:
agent_id: report-bot display_name: 报表生成助手 version: 1.2.0 description: 生成周报月报的数据摘要 capabilities: - name: generate_report description: 根据周期生成报表摘要 input_schema: type: object properties: period: type: string description: 周期,如 2025-03 required: - period output_schema: type: object properties: summary: type: string timeout_ms: 30000然后写接入代码。SDK 提供了 Python 版本,核心就四行:
from agent_reach import AgentReachClient client = AgentReachClient( registry_url="ws://registry.internal:8866", agent_id="report-bot", private_key_path="./keys/report-bot.pem", ) client.register_from_file("manifest.yaml") client.start()注册成功后,在管理端页面能看到 report-bot 出现在在线列表里,点进去还能看到它声明的能力。这里有个小细节我反复提醒自己:manifest 里的能力名一经发布就不要随便改名,否则所有调用方全部断链。真要大改,宁可新起一个能力名、把旧的标记为 deprecated。
3.3 跨 Agent 调用的最小样例
现在 report-bot 要调用>result = client.call( target="data-fetcher", capability="query_metrics", payload={"period": "2025-03"}, timeout=30, ) print(result.summary)
SDK 内部替你完成了寻址、鉴权、建连、WebSocket 通信、超时控制这一整套流程。实际体验下来,最容易被坑的是超时设置。如果被调能力内部还要再调其他 agent,链路一深,超时就要分层设置:最外层给 30 秒,内层每个环节只给 20 秒,保证外层超时时内层早就死透了,不会出现资源悬挂。
3.4 跨网段部署的网关模式
单机房部署没问题,但真实场景里 agent 往往分散在不同 VPC、不同业务区,网络层面未必能直连。Agent-Reach 额外提供了一个 reach-gateway 组件,扮演协议层中继的角色。SDK 不直连 Registry,而是先连就近的 gateway,由 gateway 转发注册信息和消息。
网关模式不是让你去绕网络限制,而是解决业务网络里常见的“不能直接点对点”的问题。它只做协议转发,不改变鉴权逻辑、不做内容解密,所以安全边界还是保持原样。部署时建议每个网络区域各放一个,避免单点故障影响全局。
4. 实测踩坑:常见问题与排查思路
运行这半年,我把最有代表性的几个坑整理在下面,都是真实环境里会反复遇到的。
4.1 注册了几秒就掉线
现象:agent 在管理端能正常注册,但几秒后状态就变成离线,再注册再掉,无限循环。
排查过程:先看心跳日志,发现 SDK 在发送心跳时抛出了 WebSocket 连接已关闭的异常。再看 Registry 的接入日志,发现连接被服务端主动断开。最后定位到是 Nginx 代理层设置了空闲超时 60 秒,而 agent 心跳周期是 15 秒,看起来没问题,但问题出在连接建立后 SDK 先注册再启动心跳,中间有个空窗期超过了代理的判定阈值。解决方案是让 SDK 在注册前先发一条 ping 占位,确保代理不会把连接当成空闲连接回收。
4.2 消息乱序导致任务状态错乱
长任务走异步模式后,有段时间上层业务反馈报表内容对不上。查了半天才发现问题不在 Agent-Reach,而在上层框架用了多个线程处理同一会话的事件,处理顺序无法保证。Agent-Reach 在协议层保证了单连接内的消息有序,但一旦业务侧开了多线程消费,顺序就不可控了。解法是在事件消息里增加 session_seq 序号,消费端按会话做内存重排。
4.3 权限 scope 配得太宽
测试环境出现过一次越权:一个只该读数据的 agent,由于 token 里写的是call:*:*,竟然能触发另外一个 agent 的数据删除能力。这类问题在测试环境几乎发现不了,因为权限越小越容易踩到缺失权限的报错,大家为了跑通流程就顺手放开了。我的教训是:上线前必须做一个权限最小化走查,用脚本扫出所有通配 scope,逐个确认存在的必要性。
4.4 问题排查速查表
| 现象 | 可能原因 | 优先排查手段 |
|---|---|---|
| 注册后立即掉线 | 中间代理空闲超时 / 心跳启动晚 | 抓接入日志,确认断开方向 |
| 调用超时但目标 agent 正常 | 链路层次多、超时配置冲突 | 检查调用链各层 timeout_ms |
| 消息重复执行 | 客户端重试机制叠加服务端重放 | 检查消息去重字段 session_seq |
| 注册失败 401 | 证书签发时间与服务器时区偏差 | 校验本机时间,重新签发 token |
| 管理端看不到新注册能力 | manifest 版本号未变化导致缓存未刷新 | 强制递增 manifest 版本 |
这种速查表,我建议每个团队按自己的实际故障维护一份,比看十篇文档都管用。
5. 扩展方向:和 MCP 生态的融合
最后聊聊 Agent-Reach 未来可能的走向。最近 MCP(Model Context Protocol)热度很高,它解决的是“agent 怎么调外部工具”的问题,Agent-Reach 解决的是“agent 之间怎么互相调用”的问题,两者天然互补。
我最近在尝试做一层适配器,把 Agent-Reach 注册的每个 agent 能力转换成 MCP 工具描述格式,让任何兼容 MCP 的客户端都能像调用工具一样调用远端 agent。相当于给 Agent-Reach 套了一个 MCP 外壳,既保留了 agent 互联的寻址能力,又能接入庞大的 MCP 工具生态。目前原型已经能跑通,效果比我预期好。
另一个方向是多组织联邦注册。当两个公司或两个部门各自维护一套 Registry,又要相互调用时,联邦模式比全局统一注册更现实。每个 Registry 可以配置对等信任关系,只同步对方公开的能力摘要,具体调用请求仍然由各自内部路由处理。这样既隔离了管理边界,又打通了业务协作。
我在实际维护过程中最深的感触是:互联协议这种东西,前三个月拼的是功能,后半年拼的全是细节。心跳、重试、超时、权限、审计,每一项单看都不难,但合在一起,稳定性就是从这些枯燥的细节里长出来的。Agent-Reach 当前版本我还在持续迭代,代码里那些注释和排查日志,基本都是深夜真实事故留下的痕迹。如果你也在做多 Agent 系统,建议先把连接层想清楚,再谈上层智能,否则业务跑起来之后再回头补底座的课,代价会大得多。