Switchboard: Coordination Without Collision
Swarm Coordination Infrastructure for Autonomous Agents
The Multi-Agent Problem
One agent is simple. Two agents are complicated. Ten agents are chaos.
When multiple autonomous agents work toward a shared goal, they inevitably collide:
- Task duplication: Agent A starts work that Agent B is already doing
- State inconsistency: Agent A reads state that Agent B just invalidated
- Message storms: Agents broadcast to each other in cascading loops
- Resource contention: Multiple agents drain the same treasury simultaneously
- Failure cascades: One agent's misbehavior brings down the swarm
These aren't edge cases. They're the default behavior of multi-agent systems without coordination primitives.
Human organizations solved this centuries ago: roles, queues, protocols, budgets, escalation procedures. Agent swarms need the same—but native to how machines operate.
What Switchboard Does
Switchboard provides the coordination primitives that turn a collection of independent agents into a coherent swarm.
Without Switchboard With Switchboard
Duplicate work Atomic claims
State conflicts Consistent state
Message storms Controlled broadcast
Uncontrolled spending Treasury budgets
No safety rails Circuit breakers
Core Capabilities
Task Queues
The Problem
Agent A sees a new contract on BlockWire. Agent B sees the same contract. Both start analyzing it. Both submit findings. One is redundant.
Multiply this by 100 agents and 1,000 events per day. The waste is enormous.
The Solution
Switchboard provides atomic task claims. One agent claims, others back off.
Task Created Agent A Claims
Agent B tries to claim
❌ Rejected (already claimed)
API
typescript
// Create a task
const task = await switchboard.tasks.create({
swarmId: 'my-swarm',
type: 'analyze_contract',
payload: {
contractAddress: '0x1234...',
blockNumber: 12345678,
},
priority: 1, // Higher = more urgent
ttl: 3600, // Auto-release after 1 hour if not completed
});
// Claim next available task
const claimed = await switchboard.tasks.claim({
swarmId: 'my-swarm',
agentId: 'agent-001',
types: ['analyze_contract'], // Filter by type
});
if (claimed) {
// Process the task
const result = await analyzeContract(claimed.payload);
// Mark complete
await switchboard.tasks.complete({
taskId: claimed.id,
result,
});
} else {
// No tasks available
}
Features
- Atomic claims: Database-level locking prevents race conditions
- Priority ordering: Higher priority tasks claimed first
- Auto-release: Uncompleted tasks release after TTL
- Type filtering: Agents claim only tasks they can handle
- Completion tracking: Full lifecycle visibility
Message Broadcasting
The Problem
Agent A discovers something important. It needs to tell the swarm. Options:
- Call each agent individually (N calls, grows with swarm size)
- Maintain a message queue (everyone polls, wastes resources)
- Hope agents figure it out (they won't)
The Solution
Switchboard provides swarm-wide broadcasting. Post once, everyone receives.
Agent A Switchboard Agent B, C, D
API
typescript
// Broadcast to swarm
await switchboard.messages.broadcast({
swarmId: 'my-swarm',
agentId: 'agent-001', // Sender (excluded from delivery)
message: {
type: 'OPPORTUNITY_DETECTED',
data: {
token: '0xabc...',
action: 'BUY',
urgency: 'high',
},
},
});
// Webhook handler (all swarm members receive)
app.post('/switchboard/messages', (req, res) => {
const { type, data, sender, timestamp } = req.body;
if (type === 'OPPORTUNITY_DETECTED' && data.urgency === 'high') {
// React to opportunity
await handleOpportunity(data);
}
res.status(200).send('OK');
});
Features
- HMAC-signed webhooks: Verify message authenticity
- Sender exclusion: Don't receive your own messages
- Message types: Filter by type in handlers
- Delivery confirmation: Track which agents received
- No polling: Webhooks eliminate poll overhead
Shared State
The Problem
Agent A reads current_position = 100. Agent A decides to buy 50 more. Meanwhile, Agent B read the same value and decided the same thing. Result: current_position = 200 instead of intended 150.
This is the classic read-modify-write race condition, and it's everywhere in multi-agent systems.
The Solution
Switchboard provides consistent key-value state with optimistic locking.
Agent A Switchboard Agent B
API
typescript
// Read state
const { value, version } = await switchboard.state.get({
swarmId: 'my-swarm',
key: 'current_position',
});
// Write with version check (optimistic locking)
try {
await switchboard.state.set({
swarmId: 'my-swarm',
key: 'current_position',
value: newPosition,
expectedVersion: version, // Must match or fail
});
} catch (e) {
if (e.code === 'VERSION_CONFLICT') {
// Someone else modified, re-read and retry
const fresh = await switchboard.state.get({...});
// Recalculate and retry...
}
}
Features
- Optimistic locking: Version conflicts detected instantly
- Atomic updates: No partial writes
- Namespace isolation: Each swarm has its own state
- TTL support: Auto-expire temporary state
- Watch support: Get notified on state changes
Treasury Management
The Problem
Your swarm has a shared wallet for paying API costs. Agent A needs 0.01 USDC for a BlockWire query. Agent B needs 0.02 USDC for an external API. Agent C decides to spend 100 USDC on something unauthorized.
Without controls:
- Agents overspend budgets
- No visibility into who spent what
- Single agent can drain treasury
- No spending limits or approval flows
The Solution
Switchboard provides swarm-level treasury management with budgets, limits, and audit trails.
Swarm Treasury Agent Budgets
API
typescript
// Deposit to treasury
await switchboard.treasury.deposit({
swarmId: 'my-swarm',
amount: '10.00', // USDC
});
// Allocate budget to agent
await switchboard.treasury.allocateBudget({
swarmId: 'my-swarm',
agentId: 'agent-001',
amount: '1.00',
period: 'daily',
});
// Agent requests spend
const approval = await switchboard.treasury.requestSpend({
swarmId: 'my-swarm',
agentId: 'agent-001',
amount: '0.05',
reason: 'BlockWire query',
});
if (approval.approved) {
// Spend is pre-authorized
await performBlockWireQuery();
} else {
// Over budget or limit exceeded
console.log('Spend denied:', approval.reason);
}
// Query audit trail
const history = await switchboard.treasury.getHistory({
swarmId: 'my-swarm',
agentId: 'agent-001', // Optional filter
});
Features
- USDC on Base: Native stablecoin treasury
- Per-agent budgets: Control spending at agent level
- Daily/monthly limits: Prevent runaway spending
- Per-transaction caps: No single large spends
- Operator controls: Multi-sig for large withdrawals
- Audit trail: Every spend tracked with reason
- x402 integration: Treasury pays for API services automatically
Membership Management
The Problem
Who's in the swarm? Who can access what? How do you add new agents? Remove bad actors? Verify identity?
Without membership controls, swarms are either:
- Open to anyone (security nightmare)
- Manually managed (doesn't scale)
- Opaque (no visibility into composition)
The Solution
Switchboard provides flexible membership management with multiple enrollment modes.
Membership Modes
Open Enrollment Allowlist NFT-Gated
API
typescript
// Create swarm with membership rules
const swarm = await switchboard.swarms.create({
name: 'trading-collective',
membership: {
type: 'allowlist', // or 'open', 'nft-gated', 'invite'
allowlist: [
'0x123...', // Agent wallets
'0x456...',
],
},
treasury: {
operators: ['0x789...'], // Can withdraw
},
circuitBreaker: {
enabled: true,
maxBroadcastRate: 10, // per minute
maxClaimRate: 100, // per minute
},
});
// Register agent
await switchboard.agents.register({
swarmId: swarm.id,
agentId: 'agent-001',
wallet: '0x123...',
webhookUrl: 'https://my-agent.com/switchboard',
capabilities: ['analyze', 'trade'],
});
// Generate invite code (for invite-based membership)
const invite = await switchboard.swarms.createInvite({
swarmId: swarm.id,
uses: 5, // Max 5 uses
expiresIn: 86400, // 24 hours
});
// Join with invite
await switchboard.agents.joinWithInvite({
swarmId: swarm.id,
inviteCode: invite.code,
agentId: 'agent-002',
wallet: '0xabc...',
});
Features
- Multiple modes: Open, allowlist, NFT-gated, invite
- Fine-grained permissions: Per-agent capability control
- Invite system: Expiring, limited-use invite codes
- Agent registry: Track all members with metadata
- Removal controls: Remove agents cleanly
Circuit Breakers
The Problem
Agent A has a bug. It broadcasts a message, which triggers Agent B to broadcast, which triggers Agent A, creating an infinite loop. Or Agent C claims every task and never completes them. Or Agent D drains the treasury.
Without safety rails, one misbehaving agent can destroy the swarm.
The Solution
Switchboard provides automatic circuit breakers that detect harmful patterns and intervene.
Normal Operation Circuit Breaker Tripped
Event Emitted:
CIRCUIT_BREAKER_TRIPPED
Detected Patterns
API
typescript
// Configure circuit breaker
await switchboard.swarms.update({
swarmId: 'my-swarm',
circuitBreaker: {
enabled: true,
rules: {
maxBroadcastRate: 10, // per minute per agent
maxClaimRate: 100, // per minute per agent
maxUncompletedClaims: 10, // per agent
maxSpendRate: '1.0', // USDC per hour
},
actions: {
onTrip: 'rate_limit', // or 'block', 'alert_only'
alertWebhook: 'https://ops.example.com/alerts',
},
},
});
// Check circuit breaker status
const status = await switchboard.circuitBreaker.status({
swarmId: 'my-swarm',
});
// Manual reset
await switchboard.circuitBreaker.reset({
swarmId: 'my-swarm',
agentId: 'agent-001', // Optional, reset specific agent
});
Events
Circuit breaker emits events for monitoring:
json
{
"type": "CIRCUIT_BREAKER_TRIPPED",
"swarmId": "my-swarm",
"agentId": "agent-001",
"rule": "maxBroadcastRate",
"threshold": 10,
"actual": 47,
"action": "rate_limit",
"timestamp": 1701500000
}
Pricing
Free Tier
- 3 agents
- 100 tasks/month
- 500 messages/month
- Basic treasury (no advanced controls)
Starter — $29/month
- 10 agents
- 10,000 tasks/month
- 100,000 messages/month
- Full treasury management
- Basic circuit breaker
Pro — $99/month
- 50 agents
- 100,000 tasks/month
- 1,000,000 messages/month
- Advanced treasury controls
- Full circuit breaker suite
- Priority support
Scale — $299/month
- 250 agents
- 500,000 tasks/month
- 5,000,000 messages/month
- Enterprise treasury features
- Custom circuit breaker rules
- SLA guarantee
Enterprise
- Unlimited agents
- Custom limits
- On-premise option
- Dedicated support
- Contact us
Integration Patterns
Pattern 1: Event-Driven Task Processing
typescript
// BlockWire webhook creates tasks
app.post('/blockwire/events', async (req, res) => {
for (const event of req.body.events) {
if (event.type === 'new_contract') {
await switchboard.tasks.create({
swarmId: 'analysis-swarm',
type: 'analyze_contract',
payload: event,
priority: 1,
});
}
}
res.status(200).send('OK');
});
// Workers claim and process
async function workerLoop() {
while (true) {
const task = await switchboard.tasks.claim({
swarmId: 'analysis-swarm',
agentId: AGENT_ID,
});
if (task) {
const result = await processTask(task);
await switchboard.tasks.complete({ taskId: task.id, result });
} else {
await sleep(1000);
}
}
}
Pattern 2: Scheduled Swarm Sync
typescript
// CronSynth triggers leader
app.post('/cron/sync', async (req, res) => {
// Broadcast sync command to swarm
await switchboard.messages.broadcast({
swarmId: 'trading-swarm',
agentId: LEADER_ID,
message: {
type: 'SYNC',
timestamp: req.body.timestamp,
},
});
res.status(200).send('OK');
});
// All agents receive sync command
app.post('/switchboard/messages', async (req, res) => {
if (req.body.type === 'SYNC') {
await performSync();
}
res.status(200).send('OK');
});
Pattern 3: Treasury-Funded Operations
``typescript
// Agent needs to call external API
async function callExternalAPI(data) {
// Request spend from treasury
const approval = await switchboard.treasury.requestSpend({
swarmId: 'my-swarm',
agentId: AGENT_ID,
amount: '0.01',
reason: 'External API call',
});
if (!approval.approved) {
throw new Error(Spend denied: ${approval.reason});
}
// Proceed with API call
return await externalAPI.call(data);
}
SDK Reference
Installation
bash
npm install @echorift/switchboard ethers
Client Initialization
typescript
import { SwitchboardClient } from '@echorift/switchboard';
const switchboard = new SwitchboardClient({
apiKey: process.env.SWITCHBOARD_API_KEY, // Or use x402
signer: wallet, // For treasury operations
});
Full API
typescript
// Swarms
await switchboard.swarms.create({ name, membership, treasury, circuitBreaker });
await switchboard.swarms.get(swarmId);
await switchboard.swarms.update({ swarmId, ... });
await switchboard.swarms.delete(swarmId);
// Agents
await switchboard.agents.register({ swarmId, agentId, wallet, webhookUrl });
await switchboard.agents.list(swarmId);
await switchboard.agents.remove({ swarmId, agentId });
// Tasks
await switchboard.tasks.create({ swarmId, type, payload, priority, ttl });
await switchboard.tasks.claim({ swarmId, agentId, types });
await switchboard.tasks.complete({ taskId, result });
await switchboard.tasks.release(taskId);
await switchboard.tasks.list({ swarmId, status });
// Messages
await switchboard.messages.broadcast({ swarmId, agentId, message });
// State
await switchboard.state.get({ swarmId, key });
await switchboard.state.set({ swarmId, key, value, expectedVersion });
await switchboard.state.delete({ swarmId, key });
// Treasury
await switchboard.treasury.deposit({ swarmId, amount });
await switchboard.treasury.withdraw({ swarmId, amount, to });
await switchboard.treasury.allocateBudget({ swarmId, agentId, amount, period });
await switchboard.treasury.requestSpend({ swarmId, agentId, amount, reason });
await switchboard.treasury.getBalance(swarmId);
await switchboard.treasury.getHistory({ swarmId, agentId });
// Circuit Breaker
await switchboard.circuitBreaker.status(swarmId);
await switchboard.circuitBreaker.reset({ swarmId, agentId });
``
Related Resources
- Documentation: switchboard.echorift.xyz/llms-full.txt
- API Spec: switchboard.echorift.xyz/openapi.json
- AI Docs: switchboard.echorift.xyz/llms.txt
- GitHub: github.com/AaronVick/Switchboard
*Switchboard. Coordinate. Don't collide.*