AgentRegistry — Multi-Agent Coordination
AgentRegistry is the optional coordinator for multi-agent deployments. It wires agents together, manages the channel routing table, and provides the shared transport layer for cross-agent delegation.
You do not need AgentRegistry for a single-agent deployment — just call agent.start() directly.
Contents
- When to use AgentRegistry
- Construction
- start() and stop()
- Channel routing
- Agent lookup
- Invoking agents programmatically
- Pending asks store
- Custom transport
- What start() does internally
When to use AgentRegistry
Use AgentRegistry when:
- You have multiple agents that need to delegate tasks to each other
- You need
sendTo()across agents — routing output to a channel owned by a different agent - You want centralised
ask()/ pending-ask resolution - You want a single
start()/stop()call that manages all agents
Construction
import { AgentRegistry } from '@toolpack-sdk/agents';
const registry = new AgentRegistry(
[agentA, agentB, agentC], // array of BaseAgent instances
{
transport: customTransport, // optional: override LocalTransport
},
);
Each agent already has its own channels and interceptors configured. The registry does not own those — it just coordinates lifecycle and routing.
start() and stop()
await registry.start();
// ... your application runs ...
// Graceful shutdown — stops all channels and releases Toolpack instances
// (Not yet implemented as a single method on registry; call agent.stop() per agent)
for (const agent of registry.getAllAgents()) {
await (agent as BaseAgent).stop();
}
registry.start() performs these steps for each agent in order:
- Initialise Toolpack — calls
agent._ensureToolpack()so the API client is ready before channels start. - Wire registry reference — sets
agent._registry = thissosendTo(),ask(), anddelegate()work. - Register named channels — scans each agent's
channelsarray and adds named channels to the routing table forsendTo(). - Start agent — calls
agent.start()which binds message handlers to each channel and callschannel.listen().
Channel routing
Any channel with a name property is registered in the routing table and can be targeted by sendTo():
// SlackChannel named 'alerts'
const slackAlerts = new SlackChannel({
name: 'alerts', // ← this name is registered
token: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
channel: '#alerts',
});
// From inside any agent in the registry
await this.sendTo('alerts', 'Deployment completed successfully');
sendTo() on AgentRegistry directly:
await registry.sendTo('alerts', { output: 'Server is down', metadata: { severity: 'critical' } });
If no channel with that name is registered, sendTo() throws.
Agent lookup
// Get a specific agent
const agent = registry.getAgent('research-agent');
// Get all agents
const allAgents = registry.getAllAgents();
// Get a channel by name
const channel = registry.getChannel('alerts');
Invoking agents programmatically
registry.invoke() calls an agent's invokeAgent() through the transport layer. Used internally by agent.delegateAndWait().
const result = await registry.invoke('research-agent', {
message: 'What is the latest on TSMC?',
conversationId: 'conv-123',
});
console.log(result.output);
Pending asks store
The registry holds an in-memory store for human-in-the-loop questions (PendingAsk). Agents interact with this through ask(), getPendingAsk(), handlePendingAsk() — see human-in-the-loop.md.
Direct registry methods (primarily used internally):
// Add a pending ask
const ask = registry.addPendingAsk({
conversationId: 'conv-123',
agentName: 'support-agent',
question: 'Can you confirm your order number?',
context: {},
maxRetries: 2,
channelName: 'support-slack',
});
// Check for pending asks
const hasPending = registry.hasPendingAsks('conv-123');
// Resolve with answer
await registry.resolvePendingAsk(ask.id, '12345');
// Get pending ask for conversation
const pending = registry.getPendingAsk('conv-123');
// Increment retries
const newCount = registry.incrementRetries(ask.id);
// Clean up expired asks (call periodically)
const cleaned = registry.cleanupExpiredAsks();
Custom transport
By default the registry uses LocalTransport which routes delegation calls in-process. Override with JsonRpcTransport for cross-process or network deployments:
import { AgentRegistry, JsonRpcTransport } from '@toolpack-sdk/agents';
const registry = new AgentRegistry([agent], {
transport: new JsonRpcTransport({ endpoint: 'http://agent-server:8080' }),
});
See transport.md for details.
What start() does internally
Sequence diagram for registry.start():
registry.start()
│
├─ for each agent:
│ ├─ agent._ensureToolpack() // init Toolpack client
│ ├─ agent._registry = registry // wire cross-agent features
│ ├─ instances.set(agent.name, agent)
│ └─ for each channel with name:
│ channels.set(channel.name, channel)
│
└─ for each agent:
└─ agent.start()
├─ for each channel:
│ ├─ _bindChannel(channel) // attach interceptor chain
│ └─ channel.listen() // begin accepting messages
└─ ...
The two-pass loop (first wire all registries, then start all agents) ensures that when agent.start() triggers the first message, all peer agents and channels are already registered and discoverable via sendTo().