# Factory agents

> Pull a ready-made voice agent into your codebase and ship it in minutes.

## 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](https://www.zavu.dev/docs/guides/functions/overview)
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.

> **Info:**
> You'll need an active **sender** in your Zavu project. For voice, the sender
> also needs a **phone number** ([purchase one](https://www.zavu.dev/docs/guides/phone-numbers/purchasing)).

## 1. Install the CLI

```sh
# No install step. Always runs the current version.
npx zavudev@latest --version
```

```sh
npm install -g zavudev
```

```sh
# Download from https://github.com/zavudev/zavu-cli/releases, then:
chmod +x ./zavu-macos-arm64
sudo mv ./zavu-macos-arm64 /usr/local/bin/zavudev
```

```sh
npx zavudev login
npx zavudev whoami        # confirm the project
```

## 2. Browse the catalog

```sh
npx zavudev agents catalog
```

```
id       name                          voice  tools  category
fermi    Fermi — Lead Qualification    yes    1      sales
support  Support — Customer Support    yes    2      support
```

## 3. Find your sender

```sh
npx zavudev senders list
export SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"
```

No sender yet? Create one — a phone number gives you voice and SMS right away:

```sh
npx zavudev phone-numbers search --country US
npx zavudev phone-numbers buy +1XXXXXXXXXX
npx zavudev senders create --name "My agent" --phone +1XXXXXXXXXX --set-default
```

> **Tip:**
> `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:

```sh
# Telegram — paste a bot token from @BotFather
npx zavudev telegram connect --sender "$SENDER_ID" --token <botfather-token>

# Email — add a domain, publish the DNS records it prints, then verify
npx zavudev email-domains add example.com
npx zavudev email-domains verify <domainId>
```

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

```sh
npx zavudev agents pull fermi --sender "$SENDER_ID"
cd fermi
```

This scaffolds an editable `index.ts`:

```ts
import { defineAgent, defineTool } from "@zavudev/functions"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Fermi",
  provider: "zavu",
  model: "openai/gpt-5.6-luna",
  channels: ["voice", "whatsapp"],
  voice: {
    enabled: true,
    model: "openai/gpt-4o",        // co-located voice model — lowest latency
    greeting: "Hi, I'm Fermi. Tell me a bit about what you want to build.",
    language: "en",
    interruptible: true,
    maxCallDurationMinutes: 10,
  },
  prompt: `# Personality
You are Fermi, a sharp, friendly lead-qualification agent…`,
})

defineTool({
  name: "qualify_lead",
  description: "Record a qualified lead once you know the use case, channels, and volume.",
  parameters: { type: "object", properties: { /* … */ }, required: ["use_case", "monthly_volume", "score"] },
  handler: async (args) => {
    // TODO: push this lead to your CRM / Slack / webhook.
    return { ok: true }
  },
})
```

> **Tip:**
> `--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>`.

> **Note:**
> 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

```sh
npx zavudev deploy
```

```
✓ Deployed in 12s
  Agents synced:
    + Fermi
  Tools synced:
    + qualify_lead
```

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:

```sh
npx zavudev calls create --to "+15551234567" --sender "$SENDER_ID"
```

## 7. Watch it run

```sh
npx zavudev fn logs --tail                          # live tool calls (your handler activity)
npx zavudev agents executions --sender "$SENDER_ID" # tools used, tokens, cost, latency
npx zavudev calls list --limit 10                   # every call in & out
```

## 8. Iterate

Edit `index.ts` — tweak the prompt, add a `defineTool`, change the greeting —
and redeploy:

```sh
npx zavudev deploy
```

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

  
**The agent doesn't answer calls**

    Confirm the sender has a phone number assigned.
    Then check the deploy printed `Agents synced: + Fermi` and that the `voice`
    block has `enabled: true`.
  
  
**`voice config ignored` warning on deploy**

    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.
  
  
**A skill never gets called**

    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](https://www.zavu.dev/docs/guides/functions/defining-agents): Providers, models, prompts, and the voice block.
  - [defineTool in depth](https://www.zavu.dev/docs/guides/functions/defining-tools): Schemas, handlers, returning structured data.
  - [Voice configuration](https://www.zavu.dev/docs/guides/voice-agents/configuration): Greetings, languages, voicemail, and transfer.
  - [Secrets](https://www.zavu.dev/docs/guides/functions/secrets): Store SENDER_ID, API keys, and other env vars.
