← Back to honi.dev

Multi-Agent Orchestration

Route messages between agents, call tools on remote agents via MCP, and compose complex pipelines from independent Durable Object agents.

How it works

Each Honi agent is a Durable Object — addressable by binding name and thread ID. The multi-agent helpers let you communicate between agents directly over DO stubs, with no HTTP round-trips leaving your Cloudflare account.

Routing Messages

Use routeToAgent to send a chat message to another agent and get back its full response:

TypeScript
1import { routeToAgent } from 'honidev'
2
3const result = await routeToAgent(
4 env,
5 { binding: 'RESEARCHER_AGENT', threadId: 'thread-1' },
6 'Summarise the latest earnings report for AAPL'
7)
8
9console.log(result.response) // final assistant reply

Calling Tools on Remote Agents

Use callAgentTool to invoke a specific tool on another agent via its MCP endpoint — useful for composing agent capabilities without full chat:

TypeScript
1import { callAgentTool, listAgentTools } from 'honidev'
2
3// See what tools another agent exposes
4const tools = await listAgentTools(env, { binding: 'DATA_AGENT' })
5
6// Call a specific tool directly
7const data = await callAgentTool(
8 env,
9 { binding: 'DATA_AGENT' },
10 'fetch_metrics',
11 { metricId: 'revenue-q4' }
12)

wrangler.toml Setup

Each agent needs its own Durable Object binding. Agents that call each other need both bindings declared:

TOML — wrangler.toml
1# Orchestrator agent
2[[durable_objects.bindings]]
3name = "ORCHESTRATOR"
4class_name = "OrchestratorDO"
5
6# Sub-agents
7[[durable_objects.bindings]]
8name = "RESEARCHER_AGENT"
9class_name = "ResearcherDO"
10
11[[durable_objects.bindings]]
12name = "DATA_AGENT"
13class_name = "DataDO"

Full Example — Orchestrator Pattern

TypeScript — src/index.ts
1import { createAgent, tool, routeToAgent, z } from 'honidev'
2
3// Sub-agent: handles research tasks
4const researcher = createAgent({
5 name: 'researcher',
6 model: 'claude-sonnet-4-5',
7 binding: 'RESEARCHER_AGENT',
8 system: 'You are a research specialist.',
9})
10
11// Orchestrator: routes tasks to sub-agents
12const delegateResearch = tool({
13 name: 'delegate_research',
14 description: 'Send a research task to the researcher agent',
15 input: z.object({ task: z.string() }),
16 handler: async ({ task }, { env }) => {
17 const res = await routeToAgent(env, { binding: 'RESEARCHER_AGENT' }, task)
18 return res.response
19 }
20})
21
22const orchestrator = createAgent({
23 name: 'orchestrator',
24 model: 'claude-sonnet-4-5',
25 binding: 'ORCHESTRATOR',
26 tools: [delegateResearch],
27 system: 'You coordinate tasks across specialist agents.'
28})
29
30export default { fetch: orchestrator.fetch }
31export const OrchestratorDO = orchestrator.DurableObject
32export const ResearcherDO = researcher.DurableObject

API Reference

FunctionDescription
routeToAgent(env, agent, message)Send a chat message to another agent. Returns { response, messages }
callAgentTool(env, agent, toolName, args)Call a specific tool on another agent via MCP
listAgentTools(env, agent)List tools exposed by another agent's MCP endpoint
getAgentHistory(env, agent)Fetch conversation history from another agent
clearAgentHistory(env, agent)Clear another agent's conversation history

AgentReference

FieldTypeDescription
bindingstringDurable Object namespace binding name from wrangler.toml
threadIdstring?Thread/instance ID (defaults to 'default')