> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zavu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](/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](/guides/phone-numbers/purchasing))
  and the **Voice Agents** feature enabled for your team.
</Info>

## 1. Install the CLI

<CodeGroup>
  ```sh Homebrew (macOS / Linux) theme={null}
  brew install zavudev/tools/zavu
  ```

  ```sh Standalone binary theme={null}
  # Download from https://github.com/zavudev/zavu-cli/releases, then:
  chmod +x ./zavu-macos-arm64
  sudo mv ./zavu-macos-arm64 /usr/local/bin/zavu
  ```
</CodeGroup>

```sh theme={null}
npx zavudev login
npx zavudev whoami        # confirm the project
```

## 2. Browse the catalog

```sh theme={null}
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 theme={null}
npx zavudev senders list
export SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"
```

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

```sh theme={null}
zavu phone-numbers search --country US
zavu 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`.
</Tip>

### 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 theme={null}
# 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 whichever of its `channels` are connected to the sender.

## 4. Pull the agent

```sh theme={null}
npx zavudev agents pull fermi --sender "$SENDER_ID"
cd fermi
```

This scaffolds an editable `index.ts`:

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

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Fermi",
  provider: "zavu",
  model: "openai/gpt-4o-mini",
  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>`.
</Tip>

<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.
</Note>

## 5. Deploy

```sh theme={null}
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 theme={null}
zavu calls create --to "+15551234567" --sender "$SENDER_ID"
```

## 7. Watch it run

```sh theme={null}
npx zavudev fn logs --tail                          # live tool calls (your handler activity)
npx zavudev agents executions --sender "$SENDER_ID" # tools used, tokens, cost, latency
zavu 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 theme={null}
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

<AccordionGroup>
  <Accordion title="The agent doesn't answer calls">
    Confirm the sender has a phone number assigned and the **Voice Agents**
    feature is enabled for your team (voice endpoints return `403` otherwise).
    Then check the deploy printed `Agents synced: + Fermi` and that the `voice`
    block has `enabled: true`.
  </Accordion>

  <Accordion title="`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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="defineAgent in depth" icon="brain" href="/guides/functions/defining-agents">
    Providers, models, prompts, and the voice block.
  </Card>

  <Card title="defineTool in depth" icon="wrench" href="/guides/functions/defining-tools">
    Schemas, handlers, returning structured data.
  </Card>

  <Card title="Voice configuration" icon="phone" href="/guides/voice-agents/configuration">
    Greetings, languages, voicemail, and transfer.
  </Card>

  <Card title="Secrets" icon="key" href="/guides/functions/secrets">
    Store SENDER\_ID, API keys, and other env vars.
  </Card>
</CardGroup>
