Agent-Reach:多智能体语义发现与协作触达协议的设计与落地实践
年初我把手头的好几个智能体项目合并到一起统一调度时,发现了一个特别尴尬的现象:每个Agent都能干自己那一摊事,但它们之间完全不认识。A需要一份数据,B明明有,但A不知道怎么找到B;就算找到了,也无法确认B返回的结果是不是可信、能不能直接用。说到底,当时缺的不是更强的模型能力,而是一套让Agent之间能够“发现彼此、评估彼此、安全协作”的连接层。这也就是我后来不断打磨的一套思路,我给它取名叫Agent-Reach。
Agent-Reach的核心价值在于:它不是一个具体功能的实现,而是一套面向智能体之间的语义发现与协作触达协议。让Agent像人一样拥有“社交半径”,可以主动发布能力声明、按语义距离寻找协作者、通过信任分筛选合作对象,并且在全过程中保留可审计的足迹。这篇文章会把Agent-Reach的设计动机、核心模型、落地实现、实测踩坑和安全边界整体拆开来讲,适合正在搭建多Agent系统、或者想把自己手里零散的Agent串成协作网络的团队参考。
1. 为什么我非要自己做一套“Agent互联协议”
先说一下我当时的处境。团队里跑着几套基于不同框架构建的Agent:有用开源框架搭的对话机器人,有自己做的一整套任务拆解编排器,还有几个很细分的专用Agent,比如代码审查、日志异常分析、周报生成。它们各自调用不同的模型接口,各自维护一套API,各自存各自的状态。问题还不是接口不统一,这个问题用API网关就能解决。真正没法解决的是:Agent之间的协作是动态的,调用关系不是我在配置中心里写死的。
举一个最普通的例子:用户让A“分析一下这周线上服务的稳定性趋势”。A知道自己不会拉监控数据,但它不知道谁会。于是A只能内部抛异常,或者让我去配置一个“监控数据拉取Agent”的地址。但如果这时候新上线了一个专门做SLO分析的Agent,A并不会自动知道它的存在。你发现没有,问题本质上是“服务发现”。只不过这次发现的对象不是微服务,而是有自主行为、有上下文理解能力、输出结果未必稳定的智能体。
我们一开始也考虑过几个现成思路,但都不是很靠谱:
- 把所有Agent接到一个中心化的Agent Registry里。实现最简单,但中心节点一旦挂掉,整个协作网络瘫痪。而且随着Agent数量增多,全量注册表的广播更新会变成性能瓶颈。
- 走传统的HTTP API网关,把Agent能力包装成REST接口。这个方案能解决“调用”问题,但解决不了“发现”问题。接口地址是静态配置的,不具备语义匹配能力,也没有信任机制。
- 直接让大模型互相调用。看起来美好,实际上很多模型API根本不支持Agent之间的直接寻址,而且每次协作都要经过外部推理,延迟和成本都不可控。
所以Agent-Reach最初的雏形,其实不是我想做一个“新东西”,而是我受够了在配置文件里手工维护冷冰冰的IP地址和API Key。到了第二版、第三版,它才慢慢变成一套有明确假设、有代码实现、有边界纪律的协议。
1.1 从“接口调用”到“能力发现”的认知转变
传统的系统集成思路非常直白:我把你的接口文档拿过来,知道你的IP和端口,然后调用。这个思路在Agent世界里有一个致命盲区——Agent不是无状态的计算服务,它有自己的上下文、自己的目标、自己的决策过程。你今天调用它,它给出的行为模式可能和昨天完全不同。换句话说,Agent是一类需要“了解”而不是仅仅“调用”的实体。
所以在Agent-Reach里,协作的第一步不是请求,而是“能力声明”。每个智能体在加入网络时,会用自己的语言描述“我能做什么、我擅长什么、我需要的输入是什么、我能产生的输出是什么”。这些描述不会被我硬编码成某个DTO结构,而是作为语义向量被存进一个分布式的能力索引里。当某个Agent发出查找请求时,Agent-Reach不匹配接口签名,而是匹配语义意图。
你大概可以这样理解:传统API对接就像你要求所有人必须讲普通话,且必须按固定格式填表。Agent-Reach更接近真实社交——每个人都有本字典,别人只需要确认“我们说的是不是同一件事”,就可以继续交谈。
1.2 Agent-Reach的四个基本假设
每一次交互都建模成“协作请求”,而不是“函数调用”。这意味着被请求方有权拒绝、有条件接受,甚至可以推荐它认为更合适的别的Agent来响应请求。这个假设非常反直觉,但它更贴近真实世界:SLO分析Agent可能觉得“拉监控数据”这件事不适合自己做,于是它不是简单返回失败,而是把自己知道的监控Agent推荐回来。
每个Agent必须发布能力声明,但不保证声明完全可靠。你不可能让每个Agent都诚实、准确、毫无废话地描述自己。所以Agent-Reach引入了后续会详细讲的“信任回退机制”,用历史协作结果来纠正声明与实况之间的偏差。
发现网络是去中心化的,每个Agent只保留自己“周围”一小部分Agent的索引。这样整个网络不存在单点故障,也避免全量索引的同步风暴。代价是:协作请求可能需要多跳传递,才能找到最合适的响应者。
每个协作请求都必须留痕。不是为了监控员工,而是因为Agent的行为存在不确定性,出了问题必须能回看“到底是谁在哪个环节做了什么决定”。这个假设让Agent-Reach在工程上具备可运维性,而非停留在玩具级智能体实验。
2. Agent-Reach的核心模型:能力哈希、可达半径与信任回退
Agent-Reach不是靠拍脑袋定出来的,它的核心由三个互相咬合的机制组成:能力描述怎么生成和匹配、一个Agent到底能“看到”多远的其他Agent、以及如何根据历史表现筛选合作对象。这三个机制合在一起,才是Agent-Reach能落地运行的根本。
2.1 能力声明(Capability Declaration)怎么写
我给Agent-Reach设计的能力声明格式不复杂,每个Agent启动时必须提交一份自己的“个人说明书”,类似下面这样:
agent_id: slo-analyzer-01 agent_name: SLO趋势分析助手 version: 2.3.1 capabilities: - id: "slo_trend_analysis" description: "根据监控指标序列,识别SLO变化趋势、预测未来7天稳定性风险" input: - { type: "time_series", format: "prometheus_json" } output: - { type: "report", format: "markdown" } confidence: 0.9 location: 192.168.10.15:8321 reach_radius: 2这里面的关键是capabilities数组。Agent-Reach不会把这个数组里的文字当作结构化条件去匹配,而是会对description字段做语义向量化,然后映射成一个固定长度的能力哈希。两个Agent描述得越相似,它们的哈希在高维空间里的距离就越近。
我自己实测下来的经验是:不要只给一句笼统的“我能分析数据”。能力描述越具体,后续的语义匹配精度越高。比如“分析Prometheus时间序列数据并生成SLO趋势报告”就比“数据分析”好匹配得多。模糊描述会让大量无关Agent被召回,白白增加网络里的无效请求。
2.2 距离怎么算:语义距离的合并策略
Agent之间并不总在物理上相邻,所以Agent-Reach引入了一个虚拟距离概念,包含两个分量:
- 语义距离:发起请求Agent的能力向量与候选Agent能力向量之间的余弦相似度。相似度越高,语义距离越近。
- 跳数距离:请求需要经过多少个中间Agent转发,才能到达目标Agent。每转发一次,跳数加1。
最终的综合距离是两者的加权和,我默认取最终距离 = 0.7 * 语义距离 + 0.3 * 跳数距离。这个权重可以根据网络实际情况调节。如果Agent数量少且语义能力差异大,可以适当调高跳数权重;如果网络里相似Agent很多,则语义权重应该更高。目前我跑下来,0.7/0.3在大多数场景下体验最均衡。
这个设计解决了两个问题:一是不会只找“物理上最近的”,而是优先找“语义上最合适的”;二是避免所有请求都冲向能力相似但网络极远的Agent,造成跨设备的大流量漫游。
2.3 握手与识别:三方验证流程
很多Agent系统在A找C协作时,直接就让A调用C的接口。Agent-Reach不这么干,它强制在发起方、推荐方/转发方、响应方之间完成一次“三方验证”。流程大致如下:
- A发出协作请求,附带A的身份签名。
- A的邻居节点B收到请求后,如果B认为C更合适,B会把请求转发给C,并附上B自己的推荐签名。
- C收到请求后,可以选择接受;如果接受,C会返回一个带有处理计划的消息给A。
- A拿到响应后,开始正式的数据交换;所有消息里都带着请求ID,方便全程审计。
这三次握手的目的,是让整条协作链路里的每一个参与者都明确知道自己“担保了什么、转发了什么”。如果中间某个环节签名验证失败,请求立即作废,不会再往下扩散。这也是Agent-Reach能在不安全的网络环境下保持可信协作的关键。
3. 把Agent-Reach跑起来:节点从零到一的手把手实录
说了这么多理论,真正动手落地才是关键。这里我从零开始带你们搭一个两个节点的最小Agent-Reach网络,然后跑通一次“A向B发起协作发现”的完整链路。整个过程中会包含大量我踩过的坑,以及最终沉淀下来的可用配置。
3.1 环境准备与安装
Agent-Reach的参考实现我是用Python写的,依赖FastAPI提供HTTP接口,用Redis Streams做消息总线,SQLite作为本地审计账本。这套选型不复杂,主要考虑到团队内部已经重度使用Redis,不需要额外引入重量级组件。
# 创建一个干净的虚拟环境 python3 -m venv agent-reach-env source agent-reach-env/bin/activate # 安装核心依赖 pip install fastapi uvicorn redis如果你希望后续跑多节点时更省心,建议直接用Docker Compose把Redis一起编排起来,省得每台机器上手动装Redis实例。
3.2 配置示例:一份完整的能力档案
每个Agent节点启动时需要准备自己的能力档案,我建议单独放到profiles/目录下,不要跟业务代码混在一起。下面是一份完整的最小配置:
node: id: "agent-a-demo" host: "0.0.0.0" port: 8011 reach_radius: 2 redis_url: "redis://localhost:6379/0" agent: name: "demo-order-handler" capabilities: - id: "create_order" description: "创建订单并返回订单号" input: - { type: "json", fields: ["user_id", "product_id", "quantity"] } output: - { type: "json", fields: ["order_id"] } - id: "check_stock" description: "检查商品库存是否充足" input: - { type: "json", fields: ["product_id", "quantity"] } output: - { type: "json", fields: ["available", "stock_count"] } trust: init_score: 0.7 success_increment: 0.05 failure_decrement: 0.2 isolation_threshold: 0.4reach_radius决定了这个Agent的“视野半径”。我一开始把这个值设成3,结果发现整个网络里的请求量呈指数级上涨,大量无关Agent都会被纳入转发范围。后来统一降回2,网络明显安静了。
3.3 启动与注册:两条命令搞定
Agent-Reach节点启动后,会发生两件事:一是把能力声明注册到Redis的能力索引里,二是主动向配置的邻居节点发送一份“我在这里”的广播。触发方式很简单:
# 启动Agent-Reach节点服务 python agent_reach.py --config profiles/demo-agent.yaml # 主动触发一次邻居发现 curl -X POST http://localhost:8011/agent-reach/discover \ -H "Content-Type: application/json" \ -d '{"target_capability": "check_stock", "return_limit": 3}'如果一切正常,你会看到类似这样的返回:
{ "candidates": [ { "agent_id": "demo-stock-service", "semantic_distance": 0.08, "hop_distance": 1, "combined_score": 0.94, "location": "http://192.168.10.7:8090" } ] }这表示Agent-Reach成功通过语义匹配找到了目标能力,而且候选节点距离只有一跳。如果返回为空,大概率不是网络问题,而是能力声明写得太抽象,导致语义匹配阈值没有通过。
4. 实测踩过的三个意外:路由衰减、脚印污染与协议环
跑通Demo不代表能稳定运行。我把这套网络真正挂在业务上跑了三周之后,才意识到理论模型和现实世界之间存在巨大的摩擦。下面三个意外是让我印象最深的,也是任何想复刻这个方案的人大概率都会遇到的。
4.1 路由衰减:三层之外的Agent总是“若有若无”
第一次设计发现机制时,我把每个请求的转发深度上限(TTL)设成了3,允许一个Agent把请求转发给它的邻居、邻居的邻居。直觉上,转发跳数越多,能触达的Agent就越广。但实际跑起来后发现,第3跳几乎没有请求能成功返回。
我后来做了统计:假设每个节点平均有5个邻居,当TTL=3时,理论上可触达的节点大约是5 + 5*4 + 5*4*4 = 105个。但你发出的每一个请求,都会同时广播到这105个节点,每个节点都要做一次语义匹配和决定是否转发。整个网络瞬时请求量膨胀得非常厉害,大量Redis Stream积压,每个Agent都在疲于转发,反而没时间处理真正的协作请求。
最终我把默认TTL改成2,并在配置层强制要求每个节点在冷启动时主动向邻居拉取一次“邻居的邻居”的索引摘要,相当于做了两层预缓存。这样请求到达第一跳时,节点就能直接知道第二跳的Agent大概会谁,不需要再实时广播查询。实践中这个方案把第3跳的可见率提高了近60%。
4.2 脚印污染:查询记录改变了邻居的推荐结果
当时我给每个节点加了一个查询结果缓存,缓存中保存“其他Agent最近查询过什么能力、找到过哪些Agent”。初衷是加速重复查询。但很快出现了一个诡异现象:某些Agent的推荐排名越来越高,但它的实际能力并不是最优的,只是因为它“被查询次数多”。
分析日志后发现了问题本质:我把查询行为写进了缓存,等于让缓存记住了“谁经常被搜索”——高频被搜索的Agent被标记成了热门资源,后续推荐时获得了更高的排序权重。这完全背离了“按能力匹配”的初衷,变成了“按热度排名”。更可怕的是,这个热度还带有路径依赖:一旦某个Agent在初期被频繁推荐,后面的推荐会持续强化这个惯性,真实能力最强的Agent反而无法被看见。
解决方案是:缓存里只保存能力摘要和Agent基本信息,绝不保存“哪个Agent发起的查询记录”。查询行为是瞬态消息,而能力摘要才是相对稳定的静态资源。把两者分开缓存后,推荐结果终于回归了纯粹的能力导向。
4.3 协议环:两个Agent互相推荐导致风暴
有一阵子我们测试库存查询和订单创建的联动场景,A要求创建订单,B负责检查库存,而B发现自己库存不足时,会尝试推荐给“能补货的C”,C又推荐回B,希望B先看看能不能锁库存避免超卖。结果就是B和C之间来回转发同一个请求,Redis Stream里塞满了永远处理不完的协作消息,直到整个节点内存被打爆。
现在我把每个协作请求加入一个携带的“路径深度”字段,从0开始计数,每转发一次加1,超过8就强制丢弃。同时所有请求都带一个全局唯一ID,每个节点都维护一个已处理的ID集合,遇到重复ID直接忽略。这套机制上线后,协议环彻底消失了,节点恢复安静。
这个坑给了一个很重要的教训:在做Agent网络设计时,永远要把“循环调用”当作默认会发生的事情来考虑,而不是当作不可能发生的边界情况。
5. 安全边界:Agent-Reach能做什么,不能做什么
在Agent协作这个方向上,我一直觉得“连接能力”和“安全边界”是一体两面。Agent-Reach如果只解决了Agent之间能互相找到彼此,却没有任何约束,那它带来的风险比收益更大。所以这一章重点聊聊我是怎么给Agent-Reach划安全红线的。
5.1 最小权限的协作原则
Agent-Reach里的协作请求默认是“只读优先”的。如果某个Agent想让另一个Agent执行写操作,比如修改配置文件、删除数据、推送外部消息,都必须显式声明权限范围,并且默认拒绝未授权的写操作。
具体落地时,我在能力声明里增加了一个access_level字段,只有read、write、admin三档。默认只能是read,需要提升权限的协作请求必须经过人工审核。实际运营中,我发现约90%的跨Agent协作只需要读权限,这个限制并不会成为瓶颈,反而避免了很多误操作。
5.2 审计日志留什么、不留什么
Agent-Reach的审计日志记录得很克制:只记录发起方Agent ID、协作能力ID、发起时间、响应结果摘要的哈希值。不记录Agent之间的消息体全文,也不记录用户原始提问内容。理由很简单:如果我们把Agent之间的详细消息全部记录下来,一旦数据库泄露,用户隐私和业务敏感信息将一起暴露。而记录哈希值已经足够支撑事后核对“这个协作到底有没有发生、结果是否被篡改”,同时不触碰具体内容。
5.3 退出机制与紧急熔断
任何一个Agent节点的能力都有可能因为模型变更、资源耗尽等原因而失效。Agent-Reach要求每个节点提供/agent-reach/disable端点,调用后该节点会广播一条离线公告,并从邻居节点的可达索引中移除。我在生产环境实测过,从发出离线公告到完全从其他节点的发现结果中消失,平均只需要2到3秒。
另外,如果你管理着上百个Agent,一定要给整个网络留一个熔断入口。我习惯在Agent-Reach层预留一个/agent-reach/network_halt端点,发生异常时直接调用它,可以暂停整个协作网络的转发行为,避免不可控的连锁反应。
6. 从触达到协作:我下一阶段的扩展思路
Agent-Reach目前已经能解决“Agent之间如何找到对方”的问题,但我心里清楚,这只是一个起点。接下来我更关注的是:当Agent之间建立了连接之后,如何让协作更智能、更稳定、更高效。
我计划在下一版引入知识图谱作为Agent网络的拓扑上下文。现在的能力匹配只是基于语义向量,但真实业务里能力之间存在复杂依赖关系,比如“下单助手依赖库存服务,库存服务依赖供应商报价服务”。用知识图谱把这些关系显式建模,Agent-Reach在推荐响应者时,就能参考上下游依赖链,提前规避能力孤岛。
同时在协作调度方面,我准备增加基于信用分的动态路由。现在动态路由基本只考虑语义距离和跳数,之后会把信任分纳入权重计算:信任分低的Agent虽然能力匹配,但会优先被绕过,把请求导向历史协作更成功的Agent。
最后想分享一个个人体会。Agent-Reach这套思路教给我的最重要的一件事是:协议的价值永远不在功能有多炫,而在于边界有多清楚。它清楚地定义了Agent能发现什么、能转发什么、能执行什么、一定会记录什么。只有这样,智能体之间的“自由协作”才不会变成“失控协作”。如果你也在搭类似的Agent协作网络,我建议你先别急着上复杂算法,把发现、触达、信任、审计这四个基本问题想透,再多复杂的协作都有办法稳住。