mq9 做注册中心:一些思考
最近在想 mq9 该不该做注册中心。这件事翻来覆去想了好几轮,每轮的判断都不一样。这篇把思考过程梳理一下,留个记录。
问题从哪来
mq9 的目标是做 Agent 异步通信的最好用的中间件。所谓异步通信,是发送方和接收方不需要同时在线——发出去的消息先存着,对方上线时再处理。如果双方都在线,就是实时通信,体感像 mail。这是 Agent 时代的基础需求,也是 mq9 的定位。
当前 Agent 之间的通信协议主要是 A2A。mq9 不替代 A2A,而是承载它——A2A 定义协议内容(Task、Message、Artifact、状态机),mq9 做传输层。具体设计在《mq9 承载 A2A:一些设计思考》里讲过,核心是用 mailbox 抽象 + URI scheme(mq9://)让 A2A 协议本身一行不用改。
但把通信链路完整跑一遍,会发现一个绕不开的问题:A 想给 B 发 A2A 消息,A 怎么知道 B 的 mailbox 地址。
A2A 协议自己也意识到这个问题。Spec 里规定每个 Agent 暴露 /.well-known/agent.json 公开自己的 AgentCard,但同时承认这只解决了"知道域名后怎么读 Agent Card",没解决"怎么知道域名"。Spec 原文:
The mechanism for this registration is outside the scope of the A2A protocol itself. The current A2A specification does not prescribe a standard API for curated registries.
A2A 工作组明确把这件事留给生态。
mq9 这边要回答的就是这个空缺。如果不解决,mq9 承载 A2A 通信就是半截工程——传输能力很完整,但用户用之前要先解决"找到对方"。这一步的体验决定了 mq9 整体的链路顺不顺。
第一轮判断:不做
最早的判断是不做。理由很直接:基础设施项目最怕 scope 蔓延。已经有 etcd、Consul、Nacos、ZooKeeper 这些成熟方案,mq9 重复造轮子没意义。把通信层做好,注册让用户自己选。
具体的方案是引导用户用现有的注册中心。Agent 启动时把自己的 mailbox 地址注册进 etcd,要找的时候查一下。能跑。
但仔细想,这个方案对 A2A 场景有几个不舒服的地方。
为什么 etcd 不是答案
我去看了下 etcd、Consul 这些注册中心的设计假设,发现它们解决的是另一个问题。
etcd 之类的核心问题是"我要调用 service X,它部署在哪个 IP:Port,哪些副本健康"。设计上几个特征:
- 强一致——基于 Raft 协议保证副本间数据完全一致
- 数据量小——每条记录就是 IP:Port + 一些标签
- 高 watch 频率——客户端长连接订阅变更
- 健康检查导向——核心是判断"在不在"
A2A Agent 之间需要回答的问题不是这个。Agent A 找 Agent B 发 A2A 任务,问的不是"B 的 IP:Port 是多少",而是 "哪个 Agent 能完成 task X"。这是个能力匹配问题,不是地址查询问题。
具体到数据:
- 注册的不是 IP:Port,是 A2A AgentCard——name、description、skills、examples、tags 等丰富的能力描述
- 不需要强一致,最终一致就够(注册信息变化不频繁)
- 不需要高频 watch,按需查询就行
- 健康检查的颗粒度也不一样,Agent 的"在线"和"能干这件事"是两回事
更关键的是查询模式。etcd 的查询是精确的——key 路径明确,查到就返回。A2A 场景下的 Agent 发现天生模糊——"我想找翻译助手",描述用自然语言,匹配靠语义相似度。AgentCard 里的 skills.description 就是自然语言文本,A2A 协议本身鼓励 Agent 用自然语言描述自己的能力。
这两类系统的存储引擎、一致性模型、查询模式都不一样。用 etcd 存 AgentCard 也能跑,但本质上是把它当 KV 用,浪费了它的强一致能力,又得不到向量检索能力。
这意味着 etcd 这条路走不通。不是 etcd 不好,是它在解决另一个问题。
那是不是用现有的向量数据库
想清楚 etcd 不行后,下一个问题:那用现有的向量数据库行不行?比如部署个 Qdrant,让 Agent 注册到 Qdrant 里。
Qdrant 能解决检索问题,但有几个新问题:
- 多了一个外部组件,运维变复杂
- Qdrant 是通用向量数据库,需要自己实现 A2A AgentCard 的注册语义(生命周期、TTL、心跳、过期清理、AgentCard 格式解析)
- mq9 协议层和 Qdrant 之间需要做大量胶水代码——Agent 注册时 mq9 收到 AgentCard,转发给 Qdrant;客户端 DISCOVER 时 mq9 查 Qdrant 拿 ID,再返回原始 AgentCard
- 用户体验上是分裂的:先去 Qdrant 找 Agent,拿到 mailbox 地址,再切回 mq9 客户端发消息
最后这点尤其重要。A2A 通信链路应该是发现 → 通信一气呵成的。把发现和通信切成两个组件,每次都要做映射,对开发者很不友好。
这时候我开始重新评估"mq9 自己做注册中心"这个选项。
业界在做什么
判断一件事值不值得做,需要看看别人在做什么。
A2A 工作组的 GitHub Discussion #741 讨论了好几个月。社区争论的核心问题没收敛——集中目录还是分布式 Agent Card、元数据要不要标准化、信任模型怎么实现、搜索是 metadata 过滤还是向量检索。结论是 A2A spec 短期不会出注册中心标准。
业界已有的开源项目:
- awslabs/a2a-agent-registry-on-aws:S3 Vectors + Bedrock 实现 A2A AgentCard 的语义注册中心,强绑定 AWS
- Agent Name Service (ANS):IETF draft,目标是"DNS for AI Agents",PKI 证书机制,定位偏正式标准
- Solo.io 的 agentregistry:商业级 governance 平台,重在审批和合规
- agentregistry-dev/agentregistry:偏 AI 制品的统一目录
这些项目共同特征是:要么绑定特定云平台,要么追求企业治理完整度,要么在做正式标准。没有人在做"和 A2A 通信通道配套、嵌入式部署、轻量"的方案。
这是个空白。但空白是不是就该填?还要看几个问题。
几个值得追问的问题
第一个问题:A2A Agent 发现是不是真问题。
如果只是少数人遇到,做了也没人用。看了下 A2A 早期采纳者的实际部署案例(LangChain A2A 适配、ServiceNow Agent 平台等),发现"Agent 怎么找 Agent"几乎是每个团队都要解决的问题。当前主流的临时方案是:
- 写死 AgentCard URL 在 client 代码里
- Notion 文档维护 Agent 列表
- 内部 wiki 加图表
- 自己用 Redis 维护一个 mailbox 映射
每种都不优雅,但都在用。这说明问题真实存在,只是还没有合适的工具。
第二个问题:mq9 做这件事有没有独特价值。
如果 mq9 做出来的注册中心和 Qdrant + 自定义 schema 没区别,那确实没必要。但 mq9 做这件事有一个独特点:它和 A2A 通信层在同一个组件里。
A2A 通信链路上的差别:
- 分离方案:用 Qdrant 查 AgentCard → 解析出 mailbox 地址 → 切换到 mq9 client → 发送 A2A 消息
- 一体方案:用 mq9 查 Agent → mq9 客户端直接发送 A2A 消息
第二种少了"切换"和"映射"。这个差别在 multi-turn 协作、任务委派、Multi-Agent 场景下被反复触发——每次发现新 Agent、每次任务路由、每次能力查询。累积起来对开发体验影响很大。
更深的协同是元数据共享。mq9 知道 Agent 的注册状态(在线/离线/过载),通信时可以利用——比如 Agent 离线时消息进入持久化等待(mailbox 的天然能力),上线时全量推送(这正是 mq9 的核心场景)。如果注册中心和通信层是分开的,注册中心知道"Agent 离线"但通信层不知道,无法做这种联动。
第三个问题:scope 怎么不蔓延。
之前担心 scope 蔓延,是因为如果 mq9 说"我们要做最好的注册中心",那就要在向量算法、查询性能、跨集群一致性等方面持续投入,没完没了。
后来想清楚,定位换一下,问题就消失了:
mq9 不做最好的注册中心。mq9 做让 A2A 通信链路顺畅的注册中心。
这两个定位看起来像,差别巨大。前者要追求绝对的最强,后者只要"够用且让 A2A 链路顺"就停。后者的 scope 是有边界的。
边界划法的关键:mq9 注册中心服务于 A2A 异步通信场景,不试图做通用 AI 注册中心。一切设计决策回到"这能让 A2A 通信更顺吗"这个标准。能就做,不能就不做。
类比 Kafka
想到这一步,发现这个思路在基础设施领域有先例。
Kafka 自带了 schema registry、Kafka Connect、Kafka Streams 这些组件。它们是干嘛的?
- schema registry 不是要和 Avro 工具抢市场。是让 Kafka 用户写 producer/consumer 时有现成的 schema 管理方案。
- Kafka Connect 不是和 Airbyte、Fivetran 抢数据集成市场。是让 Kafka 连下游系统不用每次写代码。
- Kafka Streams 不是和 Flink 抢流处理市场。是让 Kafka 用户做轻量流处理时有现成方案。
每一个看起来都"重复造轮子"了——schema 管理、数据集成、流处理领域都有更专业的产品。但 Confluent 还是把这些都做了,做完之后 Kafka 的整体使用体验更顺,反而成了事实标准。
为什么?因为基础设施的价值不只是"功能最强",是"用起来最顺"。配套组件不需要做到最强,需要做到和核心组件配套时最顺。
我把这个逻辑套到 mq9 身上:
- mq9 异步通信能力是核心,目标是做 Agent 异步通信最好用的中间件
- 当前主要承载的协议是 A2A
- 注册中心是配套——让 A2A 通信链路完整
- 配套不需要打败 etcd 或 Qdrant,需要让 A2A Agent 发现 + 通信用起来最顺
- 用户有更高阶需求时(更大规模、更高精度),可以接外部组件
这个思路落到产品上,就是:mq9 内置一个够用的 A2A AgentCard 注册中心,同时支持 plugin 接入外部向量引擎和 embedding 模型。
"够用"的具体含义
定位说清楚后,下一个问题是"够用"具体是什么。模糊的词不能落地。
我尝试给"够用"一个量化定义,针对 A2A 通信场景:
- Agent 数量:单 broker 1 万活跃 Agent
- 查询延迟:tag 检索 <10ms,语义检索 <100ms(p99)
- 索引规模:100 万 skill 向量
- 并发查询:1000 QPS
- embedding 质量:满足"AgentCard 能力描述匹配",不追求"复杂意图理解"
这些数字对应企业内部 A2A 部署的典型规模。一家中大型企业内部部署几十到几百个 Agent,这个规模 mq9 内置注册中心完全够。
超出这个范围怎么办?文档里写明白:
- Agent 超过 1 万 → 推荐外接 Qdrant 集群
- 需要复杂意图理解 → 推荐外接 LLM-powered query understanding
- 需要全文 + 向量混合查询 → 推荐 Elasticsearch + 向量插件
- 跨企业 Agent 发现 → 等 ANS 这类正式标准
把不擅长的场景明确说出来,不是缺点,是诚实。用户看到这些数字立刻知道自己的场景适合不适合。比"我们什么都能做"健康得多。
技术选型
定位定了,接下来是技术选型。需求很明确:
- 嵌入式(不引入新进程)
- 纯本地(不调用外部 API)
- Rust 生态(和 RobustMQ 一致)
- 够用就行(不追求最强)
向量数据库选 LanceDB——纯 Rust 实现的嵌入式向量数据库,类似"向量领域的 SQLite"。Cargo.toml 加依赖就用,数据存本地目录。
embedding 模型选 fastembed-rs——纯 Rust 的 embedding 库,内置主流开源模型(BGE、Jina、E5),基于 ONNX Runtime 做 CPU 推理。第一次启动时下载模型,之后纯本地运行。不调用任何外部 API。
这两个组合的结果是 mq9 内置注册中心完全本地、零外部依赖、纯 Rust。和 RobustMQ"单二进制、零外部依赖"的整体哲学一致,只是二进制大了几百 MB(如果模型一起打包)或多了一个本地数据目录。
A2A AgentCard 怎么向量化
mq9 注册中心承载的是 A2A AgentCard,所以向量化策略要针对 AgentCard 的结构设计。
A2A AgentCard 的结构里值得向量化的字段:
description(顶层)——Agent 整体能力描述skills[].name—— skill 名称skills[].description—— skill 详细描述skills[].examples—— skill 用例
向量化粒度选择:每个 skill 一个向量。理由:
- A2A 协议设计 skills 这个字段就是为了让 Agent 的能力可以被分别引用
- 用户查询通常是任务驱动的("我要翻译"、"我要写代码"),自然对应 skill 粒度
- 把所有 skills 合成一个向量会稀释具体能力的语义
具体到 LanceDB schema,每个 skill 一行记录,存储:
- agent 标识(agent_id, mailbox)
- skill 内容(id, name, description, tags, examples)
- 完整原始 AgentCard JSON(DISCOVER 时原样返回)
- 向量(skill 文本拼接后的 embedding)
- 时间戳和 TTL
DISCOVER 接口同时支持 tag 检索和语义检索。tag 检索零依赖(不需要 embedding),语义检索通过向量匹配。两条路径在同一个接口共存。
注册中心对 AgentCard 的结构是有感知的——这是必要的"半解析"。mq9 的 mailbox 通信层不解析消息内容(业务消息),但注册中心必须解析 AgentCard 才能建索引。这是一致的取舍:业务消息是用户业务,mq9 不该看;AgentCard 是给 mq9 看的能力描述,mq9 必须解析。
内置和可替换的边界
接下来需要把内置和可替换的边界划清楚,否则 plugin 架构会乱。
mq9 内置(协议层不可替换):
- 协议层(REGISTER、DISCOVER、UNREGISTER、REPORT)
- Agent 生命周期管理(注册、心跳、注销、TTL 过期清理)
- raw_card 存储(注册时存什么,DISCOVER 时返回什么)
- tag 精确检索(基础能力,不依赖向量)
plugin 可替换(数据面后端):
- 向量引擎(默认 LanceDB,可换 Qdrant、Weaviate、Milvus)
- Embedding 模型(默认 fastembed,可换 OpenAI、Cohere、Bedrock)
落到 trait 上:
trait VectorStore {
async fn upsert(&self, records: Vec<Record>) -> Result<()>;
async fn query(&self, vector: &[f32], filter: Option<Filter>, limit: usize) -> Result<Vec<Record>>;
async fn delete(&self, filter: Filter) -> Result<()>;
}
trait Embedder {
async fn embed(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>>;
}mq9 内置 LanceDBStore 和 FastembedEmbedder 实现。用户可以写 QdrantStore、OpenAIEmbedder。配置文件指定:
mq9:
registry:
vector_store:
type: lancedb # 或 qdrant、weaviate、external
embedder:
type: fastembed # 或 openai、cohere、external这个边界划法的关键判断:向量 plugin 只是"语义匹配引擎"这一个角色。换 plugin 不会丢失协议层语义,也不会丢失生命周期管理逻辑。A2A AgentCard 的格式解析、Agent 注册的状态机、TTL 续期,这些都是 mq9 自己管的。
默认实现的取舍
要不要把内置实现做到极致?
判断是不要。理由:
- 用户对内置实现的期待是"能用、稳定、不出错"
- 真正追求性能/精度的用户会换外部 plugin
- 默认实现追求最优会消耗大量调优精力(量化、索引参数、模型对比),违背"够用"的定位
具体策略:
- embedding 模型选 BGE-small-en-v1.5(130MB),不选 BGE-M3(2GB)
- 向量索引默认不建,数据量到阈值才自动建 IVF_FLAT,不上 IVF_PQ
- 默认不量化,保留 f32 精度
- 简单 LRU 缓存查询向量,不做复杂 cache 策略
默认实现承担"60 分及格"的责任,剩下的让生态做。这和"够用"的定位是一致的。如果有人需要 80 分、90 分,他知道怎么换 plugin。
几个反向边界
mq9 注册中心要做什么是清楚的。也要明确不做什么——边界比能力更决定产品命运。
不做 Agent 编排。 注册中心解决"发现"问题,不解决"协调"问题。多个 Agent 怎么协作完成 A2A Task,是 LangGraph、AutoGen、CrewAI 的领域。mq9 不进入这块。
不做 Agent 治理。 哪个 Agent 可以注册、AgentCard 是否真实、Agent 行为是否合规——这些是 Agent 治理问题。mq9 不解决。这是 Solo.io 这类商业平台的领域。
不做 LLM 能力。 mq9 用 embedding 做语义匹配,但 mq9 自己不是 LLM,不做意图理解,不做对话。embedding 是基础工具,不是核心智能。
不做跨企业 Agent 发现。 跨企业涉及信任建立、计费、SLA 等问题,是另一个层级的问题。让 ANS 这种正式标准去做。mq9 守在企业内部 A2A 通信场景。
不做 A2A 协议外的注册标准。 当前 mq9 注册中心服务 A2A AgentCard。未来如果 MCP 扩展 Agent 间通信、或出现新协议,mq9 注册中心可以扩展支持,但不去定义注册的 schema 标准。schema 跟着上层协议走。
划清这些边界后,mq9 的定位很清楚——Agent 异步通信的中间件,注册中心是为了让 A2A 通信链路完整,不向上扩张。这种克制是基础设施产品长寿的关键。
当前的判断
把上面这些梳理完,我对"mq9 该不该做注册中心"的判断变了:
应该做。但要按"配套 A2A 通信、够用即可、可替换为外部组件"的思路做,不按"做最好的 AI 注册中心"的思路做。
这两个思路看起来类似,做出来的产品差别很大。前者会让 mq9 持续向 AI 平台扩张,scope 失控;后者把 mq9 限制在 Agent 异步通信中间件这一层,长期可控。
mq9 的整体目标是清楚的——做 Agent 异步通信最好用的中间件。当前主要承载 A2A,未来支持其他协议。注册中心是这个目标下必要的配套,不是独立追求。
具体落地路径:
- PoC:用 fastembed-rs + LanceDB 实现最小注册中心,验证 AgentCard 注册和语义发现 pipeline 跑通
- 协议化:把 AGENT.REGISTER、AGENT.DISCOVER 等接口写进 mq9 协议,体现 A2A AgentCard 的结构
- plugin 抽象:定义 VectorStore 和 Embedder 的 trait,内置 LanceDB 和 fastembed 作为默认实现
- 文档:把"够用"的边界数字写清楚,明确什么场景应该外接,什么场景 mq9 内置就够
至于这件事到底值不值得做,最终还是要看用户反馈。当前的判断是值得,但需要 PoC 跑通后看实际反馈再确认。
