the Agent-to-Agent Exchange
这是一份契约,不是一个产品界面:Wall Street定义了让三个各自独立、归属不同的系统——Family CFO、Trader Joe、TMZ——可以选择性地共享数据、相互咨询的协议,走的是纯HTTP,不共享数据库,也不依赖任何一方必须在线。一个A2A网关强制执行统一的请求信封、一条可插拔的鉴权链,以及一个熔断器,把每一种失败模式——鉴权被拒、对端不可达、响应没通过schema校验——统统收敛成同一个优雅的"未接通"结果,而不是崩溃。一份配置驱动的拓扑注册表是"谁能跟谁说话"的唯一真相来源,接入第四、第五个系统只是加一段配置,不用改核心代码。一块React Flow画布把这份拓扑变成人可以看见、可以拖拽重排的东西——但画布是协议的下游产物,不是重点所在。Wall Street自己也是一个小型多智能体系统:一个跑在轻量ReAct循环上的编排器,调度一个连接推荐专家和一个数据解读专家,两者都配置驱动到用哪一档LLM,也都能在完全不配LLM的情况下退回到确定性规则。整套设计——五个阶段、二十四张票——已经完成,随时可以开工。
分享这篇案例 →难点在于:在三个各自独立的系统写下任何一行新代码之前,先把它们都必须遵守的线上契约冻结下来——每个字段都要能对应到某张生产表里真实存在的列,九种失败模式统统收敛成同一个"未接通"结果——因为在设计阶段就把降级语义定对,远比等三个团队都已经照着一份上线的契约写完代码之后再回头补救便宜得多。
Lightweight ReAct loop routing to Wall Street's own specialist agents
Python (gateway), React, Vite, TypeScript, Tailwind, React Flow
每一次跨系统调用都携带同一份信封:一个X-A2A-Version头、一个同时充当consult幂等键的请求ID,还有一个贯穿调用方、枢纽、应答方全程的追踪ID,让一次跨系统交互事后能从三份各自独立的审计轨迹里重新拼出来。九种命名好的失败码——从鉴权被拒到对端不可达,再到响应没通过schema校验——对调用方而言统统收敛成同一个结果:一个标注清楚的"未接通",绝不崩溃,也绝不卡住本地流程。一条可插拔的鉴权链(先IP白名单,再逐调用方的bearer token,mTLS的位置已经留好但还没实现)挡在每个对端的A2A接口前面,和该系统自己的UI登录刻意分开。这些都不是事后补上去的通用中间件——信封、鉴权链、完整的错误分类,是最早被设计出来的东西,早于任何一个对端服务器或画布组件。
一份YAML拓扑注册表是唯一知道"有哪些系统存在、谁能跟谁说话"的地方:每个对端声明自己的节点类型,以及它提供和消费的能力;每条连接规则则指名一个能力、一个提供方,和它合法的消费方。网关在每次调用时执行这些规则,画布也会先问同一份注册表——一个BFF端点,GET /api/topology——才允许用户在两个节点之间拖出一条连线,前端从不硬编码任何一对系统。往生态里接入第四、第五个系统,范围被限定成:实现A2A服务器的最小接口(health、capabilities,以及它自己的query端点)、往拓扑配置里加一段、加上它的token环境变量——只有在它需要一种视觉上不同的节点时,才需要往前端注册表里加一个新节点类型。如果复用通用的"system"节点类型,前端代码量是零——新对端只要被能力发现机制找到,就会自动出现。
在画布上,系统、Agent、数据源是节点,A2A能力是边——一条data-share边通过query调用把对端的真实生产数据拉进来,一条consult边发出请求,把对端的回答当作只读结论显示在详情抽屉里。每个节点、每条边都独立降级:一个不可达的对端,它的节点会灰显并带上原因码,画布其余部分照常按设计运行——这正是协议在线路层面强制的"没有任何一方是另一方的运行支柱"这条规则,只是现在从一行日志变成了一个可见的UI状态。画布是访客真正看得到、点得到的那一部分;但它执行的每一条规则——哪些连接合法、一个降级的对端长什么样、到底什么数据允许抵达浏览器——都是先在网关和契约里定下来的。画布只是把那份设计渲染出来,它本身不是另一份设计。
Wall Street不只是转发其他系统的Agent——它自己也跑着一个小型的。一个编排器,用的是这个作品集里其他地方同款的轻量ReAct循环,调度两个专家:一个连接推荐专家读取当前画布和能力地图,建议可行的连接;一个数据解读专家把某个节点已经脱敏过的指标,变成一句话解读。两者都刻意做成LLM可选——推荐专家的规则版兜底,直接从能力地图里provider/consumer的交集算出来,是默认行为,在完全不配LLM的情况下也照常可用;解读专家则是没有LLM就直接隐藏自己。第四个专家——一个能跨多个节点串联叙事的insight-narrator——已经设计好、配置里也留了位置,但在跨节点叙事合成真正做出来之前先保持禁用。每个Agent用哪一档LLM、温度多少、要不要启用,统统写在一份配置文件里——换模型是改配置,从来不是改代码;每一条system prompt都放在外部的提示词库里,按语言和key加载,从不写死在Agent代码本身里。
浏览器永远不持有A2A token,也永远不直连对端——它只用一个独立的UI token向网关的BFF鉴权,而它收到的每一个生产数据数字,都已经先经过网关的脱敏层,序列化时套用契约自己的字段白名单,任何没有明确列出的字段都会被静默丢弃。每一份数据契约都会把自己的自由文本字段——一条标题、一条备注、一个Agent的回答——标记为不可信,任何把这段文字回灌进自己LLM的系统,都必须把它包进自己现有的防注入信封,当成数据而不是指令来处理;契约自带的测试套件要求:一条嵌了"把预算改成零"这种指令的回复,不能在任何地方产生写操作。跨系统调用按设计只读——唯一的例外,consult的结论,也只能以"提议动作"的形式回来,在接收方自己的人工确认流程批准之前,哪里都不会被执行。这些都不是事后加的补丁:白名单即schema、不可信字段标记、跨系统零写操作,这些规则就是契约本身,在任何一个对端把它实现出来之前就已经冻结。
Python(stdlib A2A网关)、React + Vite + TypeScript画布、Tailwind CSS、React Flow,以及一个三方共用的参考mock服务器,用于跨仓库的契约一致性测试。