Discovery
AgentEnsemble v3.0.0 introduces dynamic capability discovery on the ensemble network. Ensembles advertise their shared tasks and tools with optional tags, and other ensembles can discover providers at runtime without hardcoding names or URLs.
Quick Start
Section titled “Quick Start”// Kitchen shares a tool with tagsEnsemble kitchen = Ensemble.builder() .chatLanguageModel(model) .task(Task.of("Manage kitchen operations")) .shareTool("check-inventory", inventoryTool, "food", "stock") .shareTask("prepare-meal", mealTask, "food", "cooking") .webDashboard(WebDashboard.builder().port(7329).build()) .build();
kitchen.start(7329);
// Room service discovers tools dynamicallyNetworkTool inventoryCheck = NetworkTool.discover("check-inventory", registry);Tag Support
Section titled “Tag Support”Tags classify shared capabilities for filtered discovery. Pass tags as additional
arguments to shareTask() and shareTool():
Ensemble kitchen = Ensemble.builder() .chatLanguageModel(model) .task(Task.of("Manage kitchen operations")) .shareTask("prepare-meal", mealTask, "food", "cooking") .shareTool("check-inventory", inventoryTool, "food", "stock") .shareTool("dietary-check", allergyTool, "food", "safety") .build();Tags are included in the SharedCapabilityInfo sent during the HelloMessage handshake
and indexed by the CapabilityRegistry for fast lookup.
NetworkTool.discover()
Section titled “NetworkTool.discover()”Find any tool provider on the network by capability name:
// Discover a tool -- no need to know which ensemble provides itNetworkTool tool = NetworkTool.discover("check-inventory", clientRegistry);discover() queries the CapabilityRegistry for the first ensemble that provides
the named tool and returns a NetworkTool bound to that provider. Throws
IllegalStateException if no provider is found.
NetworkToolCatalog
Section titled “NetworkToolCatalog”NetworkToolCatalog is a DynamicToolProvider that resolves network tools at task
execution time. Place it into Task.builder().tools() alongside regular tools.
All tools
Section titled “All tools”// Make every tool on the network available to the agentTask task = Task.builder() .description("Handle guest request") .tools(NetworkToolCatalog.all(clientRegistry)) .build();Filtered by tag
Section titled “Filtered by tag”// Only food-related toolsTask task = Task.builder() .description("Handle room service order") .tools(NetworkToolCatalog.tagged("food", clientRegistry)) .build();How it works
Section titled “How it works”On each task execution, the catalog’s resolve() method queries the CapabilityRegistry
for all TOOL-type capabilities (optionally filtered by tag) and returns a fresh list
of NetworkTool instances. New ensembles that come online between executions are
immediately discoverable.
DynamicToolProvider
Section titled “DynamicToolProvider”NetworkToolCatalog implements the DynamicToolProvider interface from the core module:
public interface DynamicToolProvider { List<AgentTool> resolve();}The framework’s ToolResolver expands DynamicToolProvider instances at execution time
(not at build time). This means the set of available tools can change between executions
as the network topology evolves.
You can implement DynamicToolProvider for custom discovery strategies:
public class MyCustomCatalog implements DynamicToolProvider { @Override public List<AgentTool> resolve() { // Return tools based on custom logic }}CapabilityRegistry
Section titled “CapabilityRegistry”The CapabilityRegistry maintains a thread-safe index of all shared capabilities
discovered across the network. It is populated automatically during the WebSocket
connection handshake.
Automatic registration
Section titled “Automatic registration”When an ensemble connects to another ensemble, the server sends a HelloMessage
containing its sharedCapabilities. The client-side registry processes this list and
builds inverted indices for fast lookup by name and tag.
Lookup methods
Section titled “Lookup methods”CapabilityRegistry registry = clientRegistry.getCapabilityRegistry();
// Find any provider of a capabilityOptional<String> provider = registry.findProvider("check-inventory");
// Find all providersList<String> providers = registry.findAllProviders("check-inventory");
// Find capabilities by tagList<SharedCapabilityInfo> foodCaps = registry.findByTag("food");
// List all capabilities on the networkList<SharedCapabilityInfo> all = registry.all();
// Total capability countint total = registry.size();Unregistration
Section titled “Unregistration”When an ensemble disconnects, its capabilities are removed from the registry:
registry.unregister("kitchen");Wire Protocol
Section titled “Wire Protocol”Discovery uses two message types on the wire:
capability_query
Section titled “capability_query”Sent by a client to discover providers of a named capability:
{ "type": "capability_query", "capabilityName": "check-inventory", "tag": "food"}capability_response
Section titled “capability_response”Returned by the server with matching providers:
{ "type": "capability_response", "capabilities": [ { "name": "check-inventory", "description": "Check current inventory levels", "type": "TOOL", "tags": ["food", "stock"] } ]}The HelloMessage handshake provides the initial capability set. The query/response
protocol enables on-demand discovery after connection.