
Routing
- 13 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
Routing is a Claude Code skill for the clodds trading bot that routes chat messages to specialized agents and enforces per-agent tool policies.
About
Routing is a clodds bot component that dispatches incoming chat messages to specialized agents based on channel bindings, pattern matching, and keyword detection. Developers use it to assign different Claude models, system prompts, and tool-access policies to agents like trading, research, and alerts. It matters for multi-agent chat bots that need per-channel behavior and tool gating.
- Routes chat messages to specialized agents (main, trading, research, alerts) by pattern, keyword, and channel binding
- Per-agent model, system prompt, and allowed-tool policies with admin allow/deny controls
- Channel bindings and optional sandboxed per-agent workspaces
Routing by the numbers
- 13 all-time installs (skills.sh)
- Ranked #11,396 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
routing capabilities & compatibility
- Capabilities
- agent routing · tool policy · channel binding
- Use cases
- orchestration
What routing says it does
Route messages to specialized agents, configure channel bindings, and manage tool access policies.
Trading terms → trading agent
npx skills add https://github.com/alsk1992/cloddsbot --skill routingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Route a Telegram or chat message to the right specialized agent and enforce which tools that agent may call.
Who is it for?
Chat bots that need to dispatch messages to different specialized agents with distinct models and tool permissions.
Skip if: Single-agent bots or apps that do not need per-channel routing or tool gating.
When should I use this skill?
You need to bind a channel to a specific agent or configure which tools an agent may use.
What you get
Messages are routed to the correct specialized agent with model, prompt, and tool-policy applied.
- agent definitions
- channel bindings
- per-agent tool policies
By the numbers
- 4 built-in agents (main, trading, research, alerts)
- 4-tier routing rule order (binding, pattern, keyword, default)
Files
Routing - Complete API Reference
Route messages to specialized agents, configure channel bindings, and manage tool access policies.
---
Chat Commands
Agent Management
/agents List available agents
/agent trading Switch to trading agent
/agent research Switch to research agent
/agent main Switch to main agent
/agent status Current agent infoChannel Bindings
/bind trading Bind channel to trading agent
/bind research Bind channel to research agent
/unbind Remove channel binding
/bindings List all bindingsTool Policies (Admin)
/tools List available tools
/tools allow <agent> <tool> Allow tool for agent
/tools deny <agent> <tool> Deny tool for agent
/tools policy <agent> View agent's tool policy---
TypeScript API Reference
Create Routing Service
import { createRoutingService } from 'clodds/routing';
const routing = createRoutingService({
// Default agent
defaultAgent: 'main',
// Agent definitions
agents: {
main: {
name: 'Main',
description: 'General assistant',
model: 'claude-3-sonnet',
systemPrompt: 'You are a helpful trading assistant...',
allowedTools: ['*'], // All tools
},
trading: {
name: 'Trading',
description: 'Order execution specialist',
model: 'claude-3-haiku',
systemPrompt: 'You execute trades efficiently...',
allowedTools: ['execute', 'portfolio', 'markets', 'feeds'],
},
research: {
name: 'Research',
description: 'Market analysis expert',
model: 'claude-3-opus',
systemPrompt: 'You provide deep market analysis...',
allowedTools: ['web-search', 'web-fetch', 'markets', 'news'],
},
alerts: {
name: 'Alerts',
description: 'Notification handler',
model: 'claude-3-haiku',
systemPrompt: 'You manage price alerts...',
allowedTools: ['alerts', 'feeds'],
},
},
// Storage
storage: 'sqlite',
dbPath: './routing.db',
});Route Message
// Route determines best agent
const route = await routing.route({
message: 'Buy 100 shares of Trump YES',
channelId: 'telegram-123',
userId: 'user-456',
});
console.log(`Routed to: ${route.agent}`);
console.log(`Confidence: ${route.confidence}`);
console.log(`Reason: ${route.reason}`);Get Available Agents
const agents = routing.getAgents();
for (const [id, agent] of Object.entries(agents)) {
console.log(`${id}: ${agent.name}`);
console.log(` ${agent.description}`);
console.log(` Model: ${agent.model}`);
console.log(` Tools: ${agent.allowedTools.join(', ')}`);
}Add Custom Agent
routing.addAgent({
id: 'defi',
name: 'DeFi Specialist',
description: 'Solana and EVM DeFi expert',
model: 'claude-3-sonnet',
systemPrompt: 'You are an expert in DeFi protocols...',
allowedTools: ['solana', 'evm', 'bridge', 'portfolio'],
patterns: [
/swap|dex|liquidity|pool/i,
/solana|jupiter|raydium/i,
/uniswap|1inch|bridge/i,
],
});Update Agent
routing.updateAgent('trading', {
model: 'claude-3-sonnet', // Upgrade model
allowedTools: [...currentTools, 'futures'],
});Channel Bindings
// Bind channel to specific agent
await routing.addBinding({
channelId: 'telegram-trading-group',
agentId: 'trading',
});
// Get binding for channel
const binding = await routing.getBinding('telegram-trading-group');
console.log(`Channel bound to: ${binding?.agentId || 'default'}`);
// List all bindings
const bindings = await routing.getBindings();
for (const b of bindings) {
console.log(`${b.channelId} → ${b.agentId}`);
}
// Remove binding
await routing.removeBinding('telegram-trading-group');Tool Policies
// Check if tool is allowed for agent
const allowed = routing.isToolAllowed('trading', 'web-search');
console.log(`web-search allowed for trading: ${allowed}`);
// Get allowed tools for agent
const tools = routing.getAllowedTools('trading');
console.log(`Trading agent tools: ${tools.join(', ')}`);
// Update tool policy
routing.setToolPolicy('trading', {
allow: ['execute', 'portfolio', 'futures'],
deny: ['web-search', 'browser'],
});---
Built-in Agents
| Agent | Model | Purpose | Tools |
|---|---|---|---|
| main | Sonnet | General assistant | All |
| trading | Haiku | Fast order execution | Execute, Portfolio |
| research | Opus | Deep analysis | Search, Fetch, News |
| alerts | Haiku | Notifications | Alerts, Feeds |
---
Routing Rules
Messages are routed based on:
1. Channel binding — If channel is bound, use that agent 2. Pattern matching — Match against agent patterns 3. Keyword detection — Trading terms → trading agent 4. Default fallback — Use main agent
Pattern Examples
{
trading: [/buy|sell|order|position|close/i],
research: [/analyze|research|explain|why/i],
alerts: [/alert|notify|when|watch/i],
}---
Tool Categories
| Category | Tools |
|---|---|
| Execution | execute, portfolio, markets |
| Data | feeds, news, web-search, web-fetch |
| Crypto | solana, evm, bridge |
| System | files, browser, docker |
---
Workspace Isolation
Each agent can have isolated workspace:
routing.addAgent({
id: 'research',
workspace: {
directory: '/tmp/research',
allowedPaths: ['/tmp/research/**'],
sandboxed: true,
},
});---
Best Practices
1. Use fast models for trading — Haiku for time-sensitive operations 2. Restrict tools appropriately — Trading agent doesn't need browser 3. Channel bindings — Dedicated channels for specific workflows 4. Custom agents — Create specialized agents for your use case 5. Monitor routing — Check logs to see routing decisions
/**
* Routing CLI Skill
*
* Commands:
* /agents - List available agents
* /agent <id> - Switch to or view an agent
* /agent status - Current agent info
* /bind <agent> - Bind channel to agent
* /unbind - Remove channel binding
* /bindings - List all bindings
* /tools - List available tools
* /tools allow <agent> <tool> - Allow tool for agent
* /tools deny <agent> <tool> - Deny tool for agent
* /tools policy <agent> - View agent's tool policy
*/
import { createRoutingService, type RoutingService } from '../../../routing/index';
let service: RoutingService | null = null;
function getService(): RoutingService {
if (!service) {
service = createRoutingService();
}
return service;
}
function handleAgents(): string {
const svc = getService();
const agents = svc.getAgents();
if (agents.length === 0) {
return 'No agents configured.';
}
let output = `**Available Agents** (${agents.length})\n\n`;
for (const agent of agents) {
const status = agent.enabled ? 'enabled' : 'disabled';
output += `**${agent.name}** (\`${agent.id}\`) - ${status}\n`;
output += ` ${agent.description}\n`;
if (agent.model) {
output += ` Model: ${agent.model}\n`;
}
output += '\n';
}
return output;
}
function handleAgent(agentId: string): string {
const svc = getService();
const agent = svc.getAgent(agentId);
if (!agent) {
return `Agent \`${agentId}\` not found.\n\nUse \`/agents\` to list available agents.`;
}
let output = `**${agent.name}** (\`${agent.id}\`)\n\n`;
output += `Description: ${agent.description}\n`;
output += `Enabled: ${agent.enabled}\n`;
if (agent.model) {
output += `Model: ${agent.model}\n`;
}
if (agent.systemPrompt) {
output += `\nSystem Prompt:\n\`\`\`\n${agent.systemPrompt.slice(0, 200)}${agent.systemPrompt.length > 200 ? '...' : ''}\n\`\`\`\n`;
}
const tools = svc.getAllowedTools(agent.id);
if (tools) {
output += `\nAllowed Tools: ${tools.join(', ')}\n`;
} else {
output += `\nAllowed Tools: All\n`;
}
return output;
}
function handleAgentStatus(): string {
const svc = getService();
const defaultAgent = svc.getDefaultAgent();
return `**Current Agent:** ${defaultAgent.name} (\`${defaultAgent.id}\`)\n\n${defaultAgent.description}`;
}
function handleBindings(): string {
const svc = getService();
const bindings = svc.getBindings();
if (bindings.length === 0) {
return 'No bindings configured.';
}
let output = `**Routing Bindings** (${bindings.length})\n\n`;
for (const binding of bindings) {
const status = binding.enabled ? 'active' : 'inactive';
output += `\`${binding.id}\` [${binding.type}] -> **${binding.agentId}** (priority: ${binding.priority}, ${status})\n`;
output += ` Pattern: \`${binding.pattern}\`\n`;
if (binding.channel) {
output += ` Channel: ${binding.channel}\n`;
}
}
return output;
}
function handleBind(agentId: string): string {
const svc = getService();
const agent = svc.getAgent(agentId);
if (!agent) {
return `Agent \`${agentId}\` not found.\n\nUse \`/agents\` to list available agents.`;
}
const bindingId = `channel-bind-${Date.now()}`;
svc.addBinding({
id: bindingId,
type: 'channel',
pattern: 'current',
agentId,
priority: 90,
enabled: true,
});
return `Channel bound to **${agent.name}** agent.`;
}
function handleUnbind(): string {
const svc = getService();
const bindings = svc.getBindings().filter(b => b.type === 'channel' && b.id.startsWith('channel-bind-'));
if (bindings.length === 0) {
return 'No channel bindings to remove.';
}
for (const binding of bindings) {
svc.removeBinding(binding.id);
}
return `Removed ${bindings.length} channel binding(s).`;
}
function handleTools(): string {
const svc = getService();
const agents = svc.getAgents();
let output = '**Tool Policies by Agent**\n\n';
for (const agent of agents) {
const tools = svc.getAllowedTools(agent.id);
output += `**${agent.name}** (\`${agent.id}\`):\n`;
if (tools) {
output += ` Allowed: ${tools.join(', ')}\n`;
} else {
output += ` Allowed: All tools\n`;
}
output += '\n';
}
return output;
}
function handleToolAllow(agentId: string, tool: string): string {
const svc = getService();
const agent = svc.getAgent(agentId);
if (!agent) {
return `Agent \`${agentId}\` not found.`;
}
const policy = agent.toolPolicy || { allow: [], deny: [] };
if (!policy.allow) policy.allow = [];
policy.allow.push(tool);
// Remove from deny list if present
if (policy.deny) {
policy.deny = policy.deny.filter(t => t !== tool);
}
svc.updateAgent(agentId, { toolPolicy: policy });
return `Tool \`${tool}\` is now **allowed** for agent \`${agentId}\`.`;
}
function handleToolDeny(agentId: string, tool: string): string {
const svc = getService();
const agent = svc.getAgent(agentId);
if (!agent) {
return `Agent \`${agentId}\` not found.`;
}
const policy = agent.toolPolicy || { allow: [], deny: [] };
if (!policy.deny) policy.deny = [];
policy.deny.push(tool);
// Remove from allow list if present
if (policy.allow) {
policy.allow = policy.allow.filter(t => t !== tool);
}
svc.updateAgent(agentId, { toolPolicy: policy });
return `Tool \`${tool}\` is now **denied** for agent \`${agentId}\`.`;
}
function handleToolPolicy(agentId: string): string {
const svc = getService();
const agent = svc.getAgent(agentId);
if (!agent) {
return `Agent \`${agentId}\` not found.`;
}
const policy = agent.toolPolicy;
if (!policy) {
return `Agent \`${agentId}\` has no tool policy (all tools allowed).`;
}
let output = `**Tool Policy for ${agent.name}**\n\n`;
if (policy.allow?.length) {
output += `Allowed: ${policy.allow.join(', ')}\n`;
}
if (policy.deny?.length) {
output += `Denied: ${policy.deny.join(', ')}\n`;
}
if (policy.allowGroups?.length) {
output += `Allowed Groups: ${policy.allowGroups.join(', ')}\n`;
}
if (policy.denyGroups?.length) {
output += `Denied Groups: ${policy.denyGroups.join(', ')}\n`;
}
output += `Confirm Dangerous: ${policy.confirmDangerous ? 'Yes' : 'No'}\n`;
return output;
}
export async function execute(args: string): Promise<string> {
const parts = args.trim().split(/\s+/);
const command = parts[0]?.toLowerCase() || 'help';
const rest = parts.slice(1);
switch (command) {
case 'agents':
return handleAgents();
case 'agent':
if (!rest[0] || rest[0] === 'status') return handleAgentStatus();
return handleAgent(rest[0]);
case 'bind':
if (!rest[0]) return 'Usage: /routing bind <agentId>';
return handleBind(rest[0]);
case 'unbind':
return handleUnbind();
case 'bindings':
return handleBindings();
case 'tools':
if (!rest[0]) return handleTools();
if (rest[0] === 'allow' && rest[1] && rest[2]) return handleToolAllow(rest[1], rest[2]);
if (rest[0] === 'deny' && rest[1] && rest[2]) return handleToolDeny(rest[1], rest[2]);
if (rest[0] === 'policy' && rest[1]) return handleToolPolicy(rest[1]);
return 'Usage: /routing tools [allow|deny|policy] [agent] [tool]';
case 'help':
default:
return `**Routing Commands**
**Agents:**
/routing agents List available agents
/routing agent <id> View agent details
/routing agent status Current agent info
**Bindings:**
/routing bind <agent> Bind channel to agent
/routing unbind Remove channel binding
/routing bindings List all bindings
**Tool Policies:**
/routing tools List all tool policies
/routing tools allow <agent> <tool> Allow tool for agent
/routing tools deny <agent> <tool> Deny tool for agent
/routing tools policy <agent> View agent's tool policy
**Examples:**
/routing agent trading
/routing bind research
/routing tools allow trading futures`;
}
}
export default {
name: 'routing',
description: 'Multi-agent routing, channel bindings, and tool policies',
commands: ['/routing', '/agents', '/agent', '/bind', '/unbind', '/bindings'],
handle: execute,
};
Related skills
FAQ
How does routing decide which agent handles a message?
It checks channel binding first, then pattern matching, then keyword detection, and falls back to the default main agent.
Can each agent use a different model?
Yes, each agent definition sets its own model, system prompt, and allowedTools.