Skip to content

feat: native multi-agent support via MultiAgentRegistry - #1031

Open
malladinagarjuna2 wants to merge 8 commits into
a2aproject:mainfrom
malladinagarjuna2:feat-multi-agent-support
Open

feat: native multi-agent support via MultiAgentRegistry#1031
malladinagarjuna2 wants to merge 8 commits into
a2aproject:mainfrom
malladinagarjuna2:feat-multi-agent-support

Conversation

@malladinagarjuna2

Copy link
Copy Markdown
Contributor

Overview

This PR introduces native support for deploying multiple Server Agents within a single Quarkus application instance for the reference-jsonrpc extension, resolving the operational overhead of 1:1 Kubernetes pod mappings.

Fixes #898

Changes

  • MultiAgentRegistry Interface: Introduced a registry interface that users can implement as a CDI bean to provide a map of agentId to JSONRPCHandler.
  • Dynamic Routing in A2AServerRoutes: The server router now checks for the presence of a MultiAgentRegistry. If found, it iterates over all registered agents and dynamically creates Vert.x routes for /{agentId} and /{agentId}/.well-known/agent-card.json.
  • Backward Compatibility: If no registry is provided, the router falls back to the default single-agent singleton behavior (/ and /.well-known/agent-card.json).

This allows production deployments to scale infinitely to hundreds of agents in a single JVM with zero custom routing code.

This adds native support for multiple agents in the reference-jsonrpc Quarkus extension.
If a CDI bean implements MultiAgentRegistry, the A2AServerRoutes will automatically
dynamically register endpoints for each agent, mapping '/{agentId}' to its JSONRPCHandler
and '/{agentId}/.well-known/agent-card.json'.

@kabir kabir left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR :-) The MultiAgentRegistry approach is interesting.

A few things to address before this can be merged:

  • Support for all transports (REST, gRPC), not just JSON-RPC — as noted inline
  • The tenant extraction / agent ID path conflict
  • Compile issues in the tests

It would also be interesting to see if this could be enhanced with @mescaja's database-driven approach from #898 (comment) — either in this PR or as a follow-up.

Instance<JSONRPCHandler> jsonRpcHandler;

@Inject
Instance<org.a2aproject.sdk.server.apps.quarkus.registry.MultiAgentRegistry> multiAgentRegistry;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an import

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, now it imports multiagentresgistry instead of using the fully-qualified name

String pathPrefix = "/" + agentId;
registerAgentRoutes(router, pathPrefix, entry.getValue());
}
} else if (!jsonRpcHandler.isUnsatisfied()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use isResolvable()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced both !isUnsatisfied() checks (this one and the jsonRpcHandler one right below it) with isResolvable(), since isUnsatisfied() doesn't catch the ambiguous-resolution case (.get() would throw AmbiguousResolutionException).

/**
* @return a map of agent ID (path segment) to their JSONRPCHandler
*/
Map<String, JSONRPCHandler> getAgents();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also include the other transports

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extended the same MultiAgentRegistry pattern to REST (routes registered under //...) and gRPC (dispatched via a new X-A2A-Agent-Id metadata header, since gRPC has no per-path routing). Also verified live locally ,JSON-RPC calls to two different registered agents each get routed to and served by their own handler correctly.

}

private void registerAgentRoutes(Router router, String pathPrefix, JSONRPCHandler handler) {
String rpcPath = pathPrefix.isEmpty() ? "/" : pathPrefix;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tenant extraction will conflict with multi-agent routing. extractTenant() reads the normalized path, so a request to POST /myagent would return "myagent" as the tenant. The agent ID prefix needs to be stripped before tenant extraction — registerAgentRoutes should pass the pathPrefix length so extractTenant can skip it, or use a Vert.x path parameter (e.g. /:agentId/*) instead of a fixed path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

went with your suggestion: registerAgentRoutes now stores the reistered path prefix on the routing context, and extractTenant() strips it before computing the tenant from the remaining path. So POST/myagent now correctly resolves to an empty tentant instead of treating "myagent" as the tenant.

- Import MultiAgentRegistry instead of using its fully-qualified name
- Use Instance.isResolvable() instead of !isUnsatisfied(), which also
  matches on ambiguous (multi-bean) resolution and would throw on get()
- Strip the registered agent path prefix before computing the tenant,
  so a request to POST /myagent no longer treats "myagent" as the tenant
- Fix pre-existing test compile errors (isUnsatisfied()/get() stubbed on
  the wrong mock) and the resulting compile break in
  MultiVersionJSONRPCRoutes, which still called the old 2-arg
  invokeJSONRPCHandler
Mirrors the JSON-RPC MultiAgentRegistry pattern across REST and gRPC:

- REST: MultiAgentRegistry (Map<String, RestHandler>), routes each
  agent under /<agentId>/ with the ID as a literal regex prefix ahead
  of the tenant capture group.
- gRPC: MultiAgentRegistry (Map<String, GrpcAgent>), dispatches by a
  new X-A2A-Agent-Id metadata header, falling back to the default
  single-agent beans when absent/unknown.

Also fixes the compile break this causes in MultiVersionRestRoutes.
Signed-off-by: malladi nagarjuna <zombmalladinags69@gmail.com>
@malladinagarjuna2

Copy link
Copy Markdown
Contributor Author

@kabir thanks for the review. All four points are addressed, details below, plus a note on the database-driven follow-up.

Support for all transports

MultiAgentRegistry now exists for all three:

  • JSON-RPC — Map<String, JSONRPCHandler>, routes registered under /<agentId>/ and /<agentId>/.well-known/agent-card.json
  • REST — Map<String, RestHandler>, same path scheme
  • gRPC — Map<String, GrpcAgent>, dispatched on the X-A2A-Agent-Id metadata header, since gRPC has no per-path routing. GrpcAgent bundles the card, optional extended card, and request handler. Calls with no header, or naming an unregistered agent, fall back to the single-agent beans if those are configured.

In every transport the registry bean is optional: if no MultiAgentRegistry is resolvable, the server behaves exactly as before.

Tenant extraction / agent ID path conflict

Fixed as you suggested. registerAgentRoutes stores the registered path prefix on the RoutingContext (a2aAgentPathPrefix), and extractTenant() strips it before computing the tenant from the remaining path. So POST /myagent now resolves to an empty tenant instead of treating myagent as the tenant, and POST /myagent/tenant1 resolves to tenant tenant1.

isResolvable()

Both !isUnsatisfied() checks are now isResolvable(). isUnsatisfied() doesn't catch the ambiguous-resolution case, where the subsequent .get() would throw AmbiguousResolutionException.

Test compile issues

Resolved. A2AServerRoutesTest (JSON-RPC and REST) and the new QuarkusGrpcHandlerTest cover multi-agent registration, per-agent routing, and single-agent fallback.


On @mescaja's database-driven approach, and @omatheusmesmo's capabilities question

One thing worth making explicit, since it came up in #898: the capabilities problem doesn't apply to this design. @omatheusmesmo noted that the handlers gate operations on a single resolved card:

if (!resolveAgentCard().capabilities().streaming()) { ... }

In registry mode each registered agent has its own handler instance carrying its own AgentCard, and those checks read that instance's card. So streaming, pushNotifications, extendedAgentCard, and the protocol-version validation are all per-agent — N agents in one app do not share capabilities. The gRPC path resolves the card per-call from the header for the same reason.

On the database-driven card store: I'd prefer to keep it out of this PR and do it as a follow-up. It's a different concern , this PR is about routing requests to the right handler, whereas @mescaja's approach and @ehsavoie's suggestion of an extras module are both about storing and serving cards, which the spec covers under Registries/Catalogs (§8.2). The two compose rather than compete: an application can already back its MultiAgentRegistry implementation with a database today, since it's just a CDI bean returning a map. A card-store extras module would slot in underneath without changing anything here.

@mescaja

mescaja commented Aug 15, 2026

Copy link
Copy Markdown

thanks a lot @malladinagarjuna2. It's a nice feature that you put together that will simplify the handling of multiple AgentCard. For more complex enterprise agentic platform that requires deploy agent at scale both corporate and remote one i think other options would be preferred in my view.

Sorry to ask you for a favor but it'd appreciate if you could have a look at this ticket i raised yesterday which i believe it's a critical one and have your view on it. Not expecting to pick it up straightaway if it's a bug, just quick little look and let me know your thoughts.
#1068

@malladinagarjuna2

Copy link
Copy Markdown
Contributor Author

well @mescaja thanks a lot

sure will have a look on #1068.

@malladinagarjuna2

Copy link
Copy Markdown
Contributor Author

@kabir could you please review it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to scale a A2A production deployment to 100 A2A Server Agents each exposing a different AgentCard?

3 participants