Skip to main content

Factory agents

Factory agents are ready-made agents you pull straight into your project with one command, then deploy. Under the hood each is a Zavu Function that declares an agent with defineAgent and its skills with defineTool — so what you pull is real, editable code you own, not a black box. npx zavudev agents pull fermi scaffolds the agent, registers it, and leaves you a directory to npx zavudev deploy. Voice agents answer phone calls; text agents run on WhatsApp, SMS, and more.
You’ll need an active sender in your Zavu project. For voice, the sender also needs a phone number (purchase one).

1. Install the CLI

2. Browse the catalog

3. Find your sender

No sender yet? Create one — a phone number gives you voice and SMS right away:
npx zavudev agents init does all of this in one guided command: it creates the sender (buying a number if you want), pulls a factory agent, and sets SENDER_ID — so you can skip straight to npx zavudev deploy.

Connect more channels (from the CLI)

Voice and SMS work as soon as the sender has a number. The rich channels each connect with one command:
The agent answers on the channels its own channels list names, and only those. Connecting a channel to the sender is necessary and not sufficient: a channel the sender receives on but the agent does not list is dropped before the agent sees it, with no error and no reply. It is recorded: the drop lands in GET /v1/senders/{senderId}/agent/executions with status: "filtered" and a line saying which channel arrived and which the agent listens on, and the dashboard’s Agents → Logs → Filtered view shows the same. The factory agents ship channels: ["*"] — every channel their sender carries — so this only bites once you narrow it. Check the channels line in the file you pulled before you go looking for a bug in Telegram.

4. Pull the agent

This scaffolds an editable index.ts:
--sender sets the SENDER_ID secret for you. If you leave it off, set it before deploying with npx zavudev fn secrets set SENDER_ID <senderId>.
The voice block is what makes this agent answer calls. It runs the LLM co-located in the voice network for the lowest latency, independent of the model used for text. Remove the voice block and redeploy to turn the agent back into a text-only agent.

5. Deploy

Your agent is live. For a voice agent, the sender’s number now answers calls with Fermi; for a text agent, every inbound message hands off to it.

6. Call it

Dial the sender’s phone number from your phone. Fermi greets you, qualifies the lead, and calls qualify_lead near the end of the conversation. Prefer to test outbound? Place a call from code:

7. Watch it run

8. Iterate

Edit index.ts — tweak the prompt, add a defineTool, change the greeting — and redeploy:
The summary shows only what changed. There’s no separate agent config to keep in sync: the code is the source of truth.

Common pitfalls

Confirm the sender has a phone number assigned. Then check the deploy printed Agents synced: + Fermi and that the voice block has enabled: true.
A field in your voice block is out of range (e.g. maxCallDurationMinutes above the cap) or misspelled. The deploy warns and ships the agent as text-only rather than failing. Fix the value and redeploy.
The LLM decides when to call a tool from its description. Rewrite each description as the answer to “when should the model call this?” — vague descriptions don’t trigger.

Next steps

defineAgent in depth

Providers, models, prompts, and the voice block.

defineTool in depth

Schemas, handlers, returning structured data.

Voice configuration

Greetings, languages, voicemail, and transfer.

Secrets

Store SENDER_ID, API keys, and other env vars.