Agent tools
Tools and agents are part of the flow's definition, not of a call. That way they exist before the first call, and they are versioned with your code.
Declare a tool
tool({ description, input, handler, timeout?, afterSpeech? }):
descriptionandinput(a zod schema, sent as JSON Schema) are what the agent sees, and they are part of the flow version.handler(args, call, ctx)gets the parsed args, the call, andctx.toolCallId,ctx.eventIdandctx.agent({ id, alias }).- Return JSON for the agent, or
handoff(name, data)to end the session.c.connectthen returns withhandoffanddata, and the flow moves the call. afterSpeech: truewaits for the agent to finish speaking before the tool runs, on platforms that support it. Use it for a tool that returnshandoff, so a transfer doesn't cut the agent off.
A tool that returns JSON never runs again, because the agent is waiting on that result. A retried tool call reuses ctx.eventId and gets the first result without running again, on platforms that send a tool call id. ElevenLabs doesn't send one, so a retry there runs the handler again: use something from the arguments to make side effects idempotent.
Give it to an agent
agents: { alias: agent(id, { tools, variables?, provider? }) }, where tools holds the agent's tools by name:
const lookup_order = tool({ description: 'Look up an order.', input, handler });
// Declare the agent once, and use it in any flow.
export const assistant = agent(env('SUPPORT_AGENT_ID'), { tools: { lookup_order } });
export const support = defineCallFlow('support', { agents: { assistant } }, function* (c) {
yield* c.connect('assistant');
});c.connect('alias')andc.transfer('alias')connect that agent with its tools.agent(id)with no tools still works anywhere.- A flow has no tools of its own. Every tool belongs to an agent, so there's nothing to keep in step and no tool that no agent can call.
- Two agents share a tool by holding the same
tool(). Two different tools under one name are an error when the flow is defined. - One agent in several flows is the same constant in each
agents. Its tools are the same everywhere, so a sync from one flow never takes tools off another. - Unknown aliases are type errors, and
defineCallFlowthrows on them. - The code after
c.connecthandles the end of the session.
What a deploy does
PUT /v1/flows (hcc deploy, hcc.serve's report or hcc.flows.deploy) checks each bound agent:
- an unknown agent is a 422;
- a binding to an undeclared tool is a 400;
- a disabled agent comes back in
warnings.
Platforms that take tools per call need no sync: the bound tools ride with each conversation.
Platforms that keep tools on the agent (ElevenLabs): when a version becomes active, we sync its bound tools onto the agent.
- We create, update and remove only tools we created. Tools you made yourself are never touched, and a sync fails rather than shadow one of yours with the same name.
- The Agents page shows the status, with "Retry sync" on error.
- The agent holds one tool set, so if live and test bind the same agent, the latest activation wins, with a warning. Use separate agents per environment on these platforms.