代理注册表
AI代理的协议无关注册表服务。AgentRegistry允许代理在不预先知道它们如何通信或在哪里运行的情况下发现彼此。
它做什么
现代人工智能系统越来越多地涉及多个代理的协作——一个与搜索代理对话的总结器,一个委托给专业工具的工作流编排器。这些代理可能使用不同的协议(A2A、MCP、ACP),在不同的传输方式(HTTP、RabbitMQ、Azure Service Bus)上运行,并按需临时启动,而不是像往常一样在服务上运行。
Agent注册表是结缔组织。代理在启动时注册自己,声明它可以做什么以及如何访问它,并在运行时更新其注册。消费者通过功能、协议或传输查询注册表以查找代理,并且只看到当前可访问的代理。
设计要点
- 协议无关 --代理根据每个端点声明其协议(A2A、MCP、ACP或自定义)。注册表按协议存储和过滤,但不说任何协议。
- 运输无关 --HTTP端点和基于队列的端点(AMQP、Azure Service Bus)是一流的。发现队列支持的代理时不需要运行;队列地址是端点。
- 短命原住民 --两种活性模型共存。 *短暂的* 代理(Azure Functions、KEDA扩展的workers)向TTL注册,并在每次调用时续订。 *持久* 代理(长寿命Pod、服务)定期发送心跳。两者都作为TTL密钥统一存储在Redis中。在注册表重新启动时,临时端点会自动从Postgres重新播种,因此扩展到零的代理仍然可以被发现,而无需重新注册。
- 发现是公开的;管理层已通过身份验证 —
GET /discover/agents不需要凭据。注册、心跳和密钥管理需要API密钥或JWT。
建筑
┌─────────────────────────────────────────────────────────────────┐
│ AgentRegistry.Api │
│ ASP.NET Core 10 · Minimal APIs · Scalar UI · OpenTelemetry │
│ Auth: API key (Admin/Agent scopes) + JWT Bearer │
│ Protocol adapters: A2A · MCP · ACP · Queued A2A │
└────────────┬───────────────────────┬────────────────────────────┘
│ │
┌────────────▼───────┐ ┌───────────▼────────────────────────────┐
│ AgentRegistry. │ │ AgentRegistry.Infrastructure │
│ Application │ │ SqlAgentRepository (EF Core + Npgsql) │
│ AgentService │ │ RedisLivenessStore │
│ IApiKeyService │ │ SqlApiKeyService │
│ IAgentRepository │ │ PostgreSQL · Redis │
│ ILivenessStore │ └────────────────────────────────────────-┘
└────────────┬───────┘
│
┌────────────▼───────┐
│ AgentRegistry. │
│ Domain │
│ Agent · Endpoint │
│ Capability │
│ ApiKey · Scope │
└────────────────────┘存储
| 关注 | 商店 |
|---|---|
| 代理身份、功能、端点元数据 | PostgreSQL(EF Core) |
| 端点活性(基于TTL) | Redis |
| API密钥(哈希,从不明文) | PostgreSQL |
可观测性 --OpenTetry跟踪和指标;在以下情况下,使用OTLP导出将Serilog结构化日志记录到控制台 Otel:Endpoint 已配置。
协议支持
每个适配器的详细设计原理见 /docs:
A2A(代理人对代理人)
目标为 A2A v1.0 RC规范注册表提供A2A代理卡并接受A2A本地注册。
GET /.well-known/agent.json--注册中心自己的A2A代理卡GET /a2a/agents/{id}--任何注册A2A代理商的代理商卡(公共)POST /a2a/agents--通过直接提交A2A代理卡进行注册(代理或管理员)
代理能力与A2A技能相对应。协议特定字段(流媒体功能、安全方案、提供商、图标URL等)往返 Endpoint.ProtocolMetadata 所以没有什么损失。
MCP(模型上下文协议)
目标为 MCP规范2025-11-25, 仅支持流式HTTP传输 --不支持已弃用的HTTP+SSE传输(2024-11-05)和stdio。
注册表本身就是一个MCP服务器。 任何支持MCP的型号或代理都可以连接到 POST /mcp 并使用五个内置工具来发现代理: discover_agents, get_agent, get_a2a_card, get_mcp_server_card,以及 get_acp_manifest这意味着人工智能模型可以要求注册表“给我找一个会说A2A的实时摘要代理”,并在没有任何人参与的情况下得到可用的答案。
注册表还充当其他MCP服务器的发现服务——存储它们的服务器卡并公开它们以供查找:
POST /mcp--注册表自己的MCP服务器端点(流式HTTP,公共)GET /mcp/servers/{id}--注册服务器的MCP服务器卡(公共)GET /mcp/servers--MCP服务器卡的筛选列表(公共)POST /mcp/servers--通过直接提交MCP服务器卡进行注册(代理或管理员)
工具、资源和提示描述符(包括JSON模式)往返通过 Endpoint.ProtocolMetadataThe isLive 返回卡上的字段反映了Redis的实时活跃度。
ACP(代理通信协议)
目标 ACP规范0.2.0 (IBM研究/BeeAI)。ACP于2025年8月在Linux基金会的治理下被吸收到A2A中,但仍被广泛部署。同时支持这两种协议。
GET /acp/agents/{id}--注册代理人的ACP代理人清单(公开)GET /acp/agents--已筛选的ACP清单列表,可选domain过滤器(公共)POST /acp/agents--通过提交ACP清单+端点URL进行注册(代理或管理员)
清单包含MIME类型的内容类型、用于输入/输出/配置/线程状态的JSON模式、性能状态指标和丰富的元数据(框架、自然语言、许可证、作者)。所有字段往返通过 Endpoint.ProtocolMetadata。在清单生成时,代理名称被标准化为RFC 1123 DNS标签格式。
排队A2A(异步消息代理上的A2A)
通过以下方式进行通信的代理 RabbitMQ 的 或 Azure服务总线 而不是HTTP。A2A有线协议(任务请求/状态更新/结果消息形状)不变,只是传输不同。调用者将A2A任务消息发布到代理上的指定主题,并异步接收响应,从而启用KEDA规模的工作器、长时间运行的任务和解耦的代理管道。
GET /a2a/async/agents/{id}--已排队的A2A代理卡,包含完整的代理连接详细信息(公开)GET /a2a/async/agents--已筛选的排队代理列表(公共)POST /a2a/async/agents--通过提交排队的代理卡(代理或管理员)进行注册
这 queueEndpoint 每张卡片上的物品 technology ("rabbitmq" 或 "azure-service-bus"), host, port, virtualHost, exchange, taskTopic (呼叫者发布的地方),以及 responseTopic (呼叫者收听回复)。对于Azure服务总线, namespace 和 entityPath 替换AMQP特定字段。
排队代理也出现在 GET /discover/agents?protocol=A2A 与HTTP A2A代理一起使用,因为它们共享相同的协议类型。看 排队A2A适配器设计 以了解完整的设计原理。
通用(协议无关)
所有协议也可以通过通用的API注册和发现,该API返回注册中心自己的域模型,而不是协议名称的卡格式。
POST /agents--以显式方式注册protocol和transport领域GET /discover/agents?protocol=MCP&transport=Http--按任意组合过滤
先决条件
- .NET 10 SDK
- PostgreSQL 14+
- Redis 7+
dotnet-ef工具:dotnet tool install --global dotnet-ef
入门
1.克隆和恢复
git clone https://github.com/MarimerLLC/agentregistry
cd AgentRegistry
dotnet restore2.设置连接字符串
连接字符串通过以下方式管理 .NET用户机密 使凭据不受源代码控制。
dotnet user-secrets set "ConnectionStrings:Postgres" \
"Host=;Port=5432;Database=agentregistry;Username=agentregistry;Password=
" \
--project src/AgentRegistry.Api
dotnet user-secrets set "ConnectionStrings:Redis" \
":6379,password=
" \
--project src/AgentRegistry.Api3.创建数据库并运行迁移
# Create the database (once)
psql -U postgres -c "CREATE DATABASE agentregistry;"
psql -U postgres -d agentregistry -c "CREATE USER agentregistry WITH PASSWORD '
';"
psql -U postgres -d agentregistry -c "GRANT ALL ON SCHEMA public TO agentregistry;"
# Apply migrations
dotnet ef database update -p src/AgentRegistry.Infrastructure -s src/AgentRegistry.Api如果你不能直接访问PostgreSQL,请通过pod申请:
dotnet ef migrations script --idempotent \
-p src/AgentRegistry.Infrastructure -s src/AgentRegistry.Api \
-o /tmp/migration.sql
kubectl cp /tmp/migration.sql /
:/tmp/migration.sql
kubectl exec -n
-- psql -U postgres -d agentregistry -f /tmp/migration.sql4.跑步
dotnet run --project src/AgentRegistry.ApiAPI可在 http://localhost:5000.标量API资源管理器: http://localhost:5000/scalar/v1.
5.获取您的第一个管理员密钥
首次运行时,不存在API键。使用预共享令牌启动一个:
步骤1。 集 Bootstrap:Token --您选择的秘密字符串——在用户秘密(本地)或Kubernetes秘密(生产)中:
dotnet user-secrets set "Bootstrap:Token" "your-one-time-bootstrap-token" \
--project src/AgentRegistry.Api步骤2。 调用引导端点:
curl -X POST http://localhost:5000/api-keys/bootstrap \
-H "X-Bootstrap-Token: your-one-time-bootstrap-token" \
-H "Content-Type: application/json" \
-d '{"ownerId": "platform-team", "description": "Initial admin key"}'响应包含原始密钥-- 现在复制它,它永远不会再显示:
{
"id": "...",
"ownerId": "platform-team",
"scope": "Admin",
"keyPrefix": "ar_Abc123",
"rawKey": "ar_Abc123DefGhi...",
"createdAt": "..."
}步骤3。 移除 Bootstrap:Token 从配置。端点返回 404 当令牌不存在时,使其永久变暗。
6.发布代理密钥
使用Admin密钥,为代理和系统发布范围密钥:
# Issue an Agent-scoped key (can register/heartbeat, cannot manage keys)
curl -X POST http://localhost:5000/api-keys \
-H "X-Api-Key: ar_Abc123DefGhi..." \
-H "Content-Type: application/json" \
-d '{"description": "summarizer-agent", "scope": "Agent"}'授权
范围
| 范围 | 谁拥有它 | 它能做什么 |
|---|---|---|
Admin | 平台运营商 | 发布/列出/撤销密钥、注册代理、完全发现 |
Agent | 个人代理和服务 | 注册代理、心跳/续订、发现 |
认证方案
注册表接受两种身份验证方法,由标头选择:
- API密钥 —
X-Api-Key: ar_...头球作用域来自数据库中的关键记录。 - JWT持有者 --标准
Authorization: Bearer头球令牌必须包含以下之一:
- A. registry_scope 有价值的索赔 Admin 或 Agent - A. roles 有价值的索赔 registry.admin 或 registry.agent
发现、MCP服务器端点和协议卡端点(/discover/agents, /mcp, /a2a/agents/*, /a2a/async/agents/*, /mcp/servers/*, /acp/agents/*)始终是公开的,不需要身份验证。
API概述
通用代理管理
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
POST | /agents | 代理或管理员 | 注册代理 |
GET | /agents/{id} | 代理或管理员 | 获取具有活动状态的代理 |
PUT | /agents/{id} | 代理或管理员(所有者) | 更新代理元数据 |
DELETE | /agents/{id} | 代理人或管理员(所有者) | 注销代理人 |
POST | /agents/{id}/endpoints | 代理或管理员(所有者) | 添加终结点 |
DELETE | /agents/{id}/endpoints/{eid} | 代理或管理员(所有者) | 删除终结点 |
POST | /agents/{id}/endpoints/{eid}/heartbeat | 代理或管理员(所有者) | 持久活性重置 |
POST | /agents/{id}/endpoints/{eid}/renew | 代理人或管理员(所有者) | 临时TTL续期 |
GET | /discover/agents | 公开 | 发现实时代理 |
A2A协议
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
GET | /.well-known/agent.json | 公共 | 注册机构自己的A2A代理卡 |
GET | /a2a/agents/{id} | 公共 | A2A注册代理人的代理人卡 |
POST | /a2a/agents | 代理人或管理员 | 提交A2A代理人卡进行注册 |
MCP协议
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
POST/GET | /mcp | 公共 | 注册表自己的MCP服务器(流式HTTP) |
GET | /mcp/servers/{id} | 已注册服务器的公共 | MCP服务器卡 |
GET | /mcp/servers | 公共 | MCP服务器卡的筛选列表 |
POST | /mcp/servers | 代理或管理员 | 提交MCP服务器卡进行注册 |
ACP协议
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
GET | /acp/agents/{id} | 注册代理的公共 | ACP代理清单 |
GET | /acp/agents | 公开 | ACP代理清单的筛选列表 |
POST | /acp/agents | 代理或管理员 | 通过提交ACP代理清单进行注册 |
排队A2A协议
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
GET | /a2a/async/agents/{id} | 公共 | 排队的A2A卡,带有代理连接详细信息 |
GET | /a2a/async/agents | 公共 | 已筛选的排队A2A代理卡列表 |
POST | /a2a/async/agents | 代理或管理员 | 使用队列终结点详细信息注册代理 |
密钥管理和系统
| 方法 | 路径 | 身份验证 | 描述 |
|---|---|---|---|
POST | /api-keys | 管理员 | 发布新的API密钥 |
GET | /api-keys | 管理员 | 列出您的API密钥 |
DELETE | /api-keys/{id} | 管理员 | 吊销API密钥 |
POST | /api-keys/bootstrap | Bootstrap令牌 | 发出第一个管理员密钥 |
GET | /healthz | 公开 | 活体调查 |
GET | /readyz | 公共 | 就绪性探测(检查Postgres+Redis) |
GET | /scalar/v1 | 公共 | 交互式API资源管理器 |
GET | /openapi/v1.json | 公开 | OpenAPI规范 |
完整的交互式文档可在 /scalar/v1 当服务正在运行时。
活体模型
代理在注册时为每个端点选择其活性模型。
短暂的 --适用于无服务器/KEDA扩展的工作负载,每个作业都会启动:
{
"livenessModel": "Ephemeral",
"ttlSeconds": 300
}注册在以下时间到期 ttlSeconds。代理人打电话来 POST /agents/{id}/endpoints/{eid}/renew 如果代理停止运行,注册将自动过期,无需清理。
持久 --适用于长寿命Pod或服务:
{
"livenessModel": "Persistent",
"heartbeatIntervalSeconds": 30
}代理人打电话来 POST /agents/{id}/endpoints/{eid}/heartbeat 每 heartbeatIntervalSeconds。注册表在标记终结点过时之前授予2.5倍的宽限期。
这两种模型都作为TTL密钥统一存储在Redis中。Discovery在单个批处理Redis调用中查询SQL并过滤实时端点。
注册表重新启动恢复
Redis是短暂的——当注册表重新启动时,liveness键会丢失。两个互补的托管服务在启动时自动恢复活性,因此代理不需要重新注册。
EphemeralReseedService (始终打开)--在启动时,查询Postgres以查找每个临时端点 last_alive_at 时间戳在过去48小时内,并将这些端点重新发送到Redis中。这包括自行注册并一直在致电的代理商 /renew 通常。注册表重新启动时缩放为零的代理在注册表恢复后仍然可以被发现。
AgentSeedService (配置驱动)——对于在缩放到零事件后永远无法自我注册的知名或系统代理,您可以在配置中声明它们。如果每个配置的代理不存在,则在Postgres中创建,并且在每次注册表启动时无条件地重新播种其临时端点,而不管48小时的窗口如何。
"AgentSeeds": {
"Agents": [
{
"Name": "invoice-processor",
"OwnerId": "system",
"Description": "Always-available invoice processing agent",
"Labels": { "team": "finance" },
"Capabilities": [
{ "Name": "process-invoice", "Description": "Processes invoices", "Tags": ["finance"] }
],
"Endpoints": [
{
"Name": "primary",
"Transport": "Http",
"Protocol": "A2A",
"Address": "https://invoice-processor.internal/",
"LivenessModel": "Ephemeral",
"TtlSeconds": 300
}
]
}
]
}这两个服务是不冲突的——如果配置定义的代理也自我注册和调用 /renew,这两个服务都只是调用 SetAliveAsync 对于相同的Redis密钥,它是幂等的。
队列支持的代理
使用AMQP或Azure服务总线的代理在被发现时不需要运行。注册表将队列地址存储为端点。注册队列支持的代理有两种方法:
排队A2A(建议A2A代理使用,而不是代理)
使用 POST /a2a/async/agents 与完整 queueEndpoint 连接详细信息:
{
"name": "ResearchAgent",
"description": "On-demand research agent",
"version": "1.0",
"skills": [{ "id": "research", "name": "Research", "description": "Researches a topic", "tags": ["search"] }],
"defaultInputModes": ["application/json"],
"defaultOutputModes": ["application/json"],
"queueEndpoint": {
"technology": "rabbitmq",
"host": "rabbitmq.example.com",
"port": 5672,
"virtualHost": "/",
"exchange": "rockbot",
"taskTopic": "agent.task.ResearchAgent",
"responseTopic": "agent.response.{callerName}"
}
}客户通过以下方式发现代理 GET /a2a/async/agents 并直接接收发布A2A任务消息所需的完整代理连接详细信息。
通用注册
使用 POST /agents 明确 transport 和 protocol 领域。这适用于任何协议/传输组合,但返回注册表的内部模型,而不是协议本机卡:
{
"name": "async-processor",
"endpoints": [{
"name": "queue",
"transport": "AzureServiceBus",
"protocol": "A2A",
"address": "agents/summarizer/requests",
"livenessModel": "Ephemeral",
"ttlSeconds": 60
}]
}在这两种情况下,KEDA扩展的worker在启动时注册,处理作业,当扩展组空闲时,TTL自然过期。消费者将工作路由到队列地址——工人当前是否正在运行是KEDA关心的问题。看 排队A2A适配器设计 对于包括活力和往返在内的完整模式。
配置参考
| 关键字 | 描述 | 默认值 |
|---|---|---|
ConnectionStrings:Postgres | Npgsql连接字符串 | 生产中必需 |
ConnectionStrings:Redis | StackExchange。Redis连接字符串 | 生产中需要 |
Database:AutoMigrate | 启动时应用挂起的迁移 | false |
Bootstrap:Token | 启用 POST /api-keys/bootstrap 何时设置 | 取消设置(端点为404) |
Jwt:Authority | OIDC JWT承载验证机构 | 可选 |
Jwt:Audience | 预计JWT观众 | agentregistry |
Otel:Endpoint | 用于跟踪和度量的OTLP gRPC端点 | 可选 |
AgentSeeds:Agents | 每次启动时要创建和重新发送的代理列表(请参阅 注册表重新启动恢复) | [] |
Kubernetes部署
清单在 k8s/:
# Apply config (non-sensitive)
kubectl apply -f k8s/agentregistry/configmap.yaml
# Create the secret (do not commit real values)
kubectl create secret generic agentregistry-secrets \
--from-literal=ConnectionStrings__Postgres="Host=...;Database=agentregistry;Username=agentregistry;Password=..." \
--from-literal=ConnectionStrings__Redis="...:6379,password=..." \
--from-literal=Bootstrap__Token="your-one-time-bootstrap-token"
# Deploy
kubectl apply -f k8s/agentregistry/deployment.yaml
kubectl apply -f k8s/agentregistry/service.yaml该服务通过Tailscale(注释 tailscale.com/expose: "true").移除 Bootstrap__Token 在发布第一个管理员密钥后,从密钥中删除。
发展
# Run all tests
dotnet test
# Add a migration after model changes
dotnet ef migrations add -p src/AgentRegistry.Infrastructure -s src/AgentRegistry.Api
# Apply migrations (uses AGENTREGISTRY_DB env var or falls back to user secrets)
export AGENTREGISTRY_DB="Host=...;Database=agentregistry;..."
dotnet ef database update -p src/AgentRegistry.Infrastructure -s src/AgentRegistry.Api
# Build Docker image
docker build -t agentregistry:latest .项目结构
src/
AgentRegistry.Domain/ Pure domain model — no external dependencies
AgentRegistry.Application/ Use cases, interfaces, service logic
AgentRegistry.Infrastructure/ EF Core (PostgreSQL), Redis, SQL API key service
AgentRegistry.Api/ ASP.NET Core 10 minimal API, auth, Scalar
Protocols/
A2A/ A2A v1.0 RC agent card adapter (HTTP)
MCP/ MCP 2025-11-25 server card adapter (Streamable HTTP)
ACP/ ACP 0.2.0 agent manifest adapter
QueuedA2A/ A2A over async message brokers (RabbitMQ, Azure Service Bus)
tests/
AgentRegistry.Domain.Tests/ Domain unit tests
AgentRegistry.Application.Tests/ Service tests using Rocks source-gen mocks
AgentRegistry.Api.Tests/ Integration tests via WebApplicationFactory
Protocols/
A2A/ A2A endpoint tests
MCP/ MCP endpoint tests
ACP/ ACP endpoint tests
QueuedA2A/ Queued A2A endpoint tests
k8s/
redis.yaml Redis StatefulSet + Service
agentregistry/
configmap.yaml Non-sensitive configuration
secret.example.yaml Secret template — create imperatively, do not commit
deployment.yaml Deployment with liveness/readiness probes
service.yaml LoadBalancer with Tailscale annotation