# How agents work

> From TypeScript on your machine to an agent answering a phone call

An agent on Zavu is one object that answers on every channel. The same prompt,
model, knowledge base and skills serve a WhatsApp message and a phone call.

There are two ways to get one, and they produce the same thing:

- **In code** — declare it with `defineAgent` in a Zavu Function and deploy.
  The code is the source of truth.
- **In the dashboard or over the API** — create and configure it directly.

This page follows the code-first path end to end, because it is the one with
moving parts you cannot see.

## The pieces

```mermaid
flowchart LR
    subgraph you["Your machine"]
        src["index.ts<br/>defineAgent + defineTool"]
    end
    subgraph cloud["Zavu Cloud"]
        bw["Build Worker<br/>bundles your code"]
        fn["Your Function<br/>an isolated runtime"]
        rec["Reconciler<br/>reads what you declared"]
        agent[("Agent<br/>prompt · model · tools")]
    end
    src -->|npx zavudev deploy| bw
    bw -->|publishes| fn
    rec -->|asks it what it declares| fn
    rec -->|creates / updates| agent
```

Two things live in Zavu Cloud that are easy to conflate:

- **Your Function** is your TypeScript, running in its own isolated runtime. It
  holds your tool handlers — the code that talks to your systems.
- **The Agent** is the configuration a conversation runs against: the prompt,
  the model, which channels trigger it, its knowledge bases, and pointers to
  your tools.

`npx zavudev deploy` publishes the first and derives the second from it.

## What a deploy actually does

```mermaid
sequenceDiagram
    participant Dev as You
    participant CLI as zavu CLI
    participant BW as Build Worker
    participant Fn as Your Function
    participant Rec as Reconciler

    Dev->>CLI: npx zavudev deploy
    CLI->>BW: source + dependencies
    BW->>BW: bundle (your runtime stays external)
    BW->>Fn: publish
    Rec->>Fn: "what do you declare?"
    Fn-->>Rec: manifest — agents + tools
    Rec->>Rec: create, update, delete to match
    Rec-->>CLI: summary + warnings
    CLI-->>Dev: ✓ Deployed
```

The step worth understanding is the **manifest**. After publishing, Zavu asks
your function what it declared, and reconciles reality to match it. That is what
makes the code the source of truth:

- A `defineAgent` you added becomes a new agent.
- A field you changed is updated.
- A `defineAgent` you **deleted** deletes the agent.

> **Warning:**
> Read the lines **above** the ✓. `npx zavudev deploy` prints warnings before the success
> line, and exits non-zero when your declarations were not synced at all. They
> cover the cases where a green deploy did not do what it looks like — tools
> attached to an agent whose channels will never call them, or a second agent
> landing on a sender that already has one.

## A message arrives

```mermaid
sequenceDiagram
    participant User as Customer
    participant Ch as WhatsApp / SMS / …
    participant Zavu as Zavu
    participant Agent as Your agent
    participant KB as Knowledge base

    User->>Ch: "where is order ORD-1?"
    Ch->>Zavu: inbound message
    Zavu->>Zavu: does an agent answer on this sender?
    Zavu->>KB: retrieve relevant chunks
    KB-->>Zavu: context
    Zavu->>Agent: prompt + history + context
    Agent-->>Zavu: reply
    Zavu->>Ch: send
    Ch-->>User: reply
```

## A call arrives

Voice adds speech in and out around the same agent, and one important
difference: **on a call the model is offered your tools.**

```mermaid
sequenceDiagram
    participant Caller
    participant Tel as Telephony
    participant VP as Voice pipeline
    participant Agent as Your agent
    participant Fn as Your Function

    Caller->>Tel: dials your number
    Tel->>VP: call connected
    VP-->>Caller: greeting
    Caller->>VP: speaks
    VP->>VP: speech → text
    VP->>Agent: transcript
    Agent->>Fn: check_availability(…)
    Fn-->>Agent: { ok: true, slots: [...] }
    Agent-->>VP: reply
    VP->>VP: text → speech
    VP-->>Caller: spoken answer
```

The caller can interrupt while the agent is speaking (barge-in) and the agent
stops to listen. It can also transfer to a human, or end the call.

## Where tools run

Everywhere.

| Channel | Are tools offered to the model? |
|---|---|
| **Voice** | Yes |
| A flow's `tool` step | Yes |
| **Text** (WhatsApp, SMS, Telegram, email) | Yes |

A text agent asked to look something up calls the tool and answers with what it
returned. The model may chain up to five tool rounds in a single reply, and you
are billed for the tokens the whole loop uses.

[Flows](https://www.zavu.dev/docs/guides/ai-agents/flows) are still worth reaching for, but for a
different reason than they used to be: a flow runs the steps you wrote, in the
order you wrote them. Use one when the sequence has to be deterministic rather
than decided by the model.

Two things this does not cover, both of which `npx zavudev agents test` will
tell you about:

- By default a dry run does not *execute* a tool, because doing so fires real
  webhooks. It reports the tool and arguments the agent chose, so an answer that
  looks like a lookup is never mistaken for one. `--execute-tools` runs them.
- An agent is only reachable on the channels its sender actually has. An agent
  whose sender has none, or none of the ones the agent triggers on, answers
  every dry run and no real message.

## What you can verify, and when

Most of the loop is free and instant. A dry run costs what a reply costs, and
the last step is the real thing.

```mermaid
flowchart TD
    A["Write index.ts"] --> B["tsc — types resolve locally"]
    B --> C["npx zavudev fn invoke --tool<br/>runs one handler, no deploy"]
    C --> D["npx zavudev deploy"]
    D --> E["npx zavudev agents list<br/>confirm what landed"]
    E --> F["npx zavudev agents test<br/>the agent's reply, nothing sent"]
    F --> G["A real message, or a real call"]
    G -.->|costs money| G
```

| Step | Cost | What it proves |
|---|---|---|
| `tsc` | free | Your code is valid against the runtime's types |
| `fn invoke --tool` | free | A handler does what you think, in isolation |
| `agents list` | free | The deploy created what you meant |
| `agents test` | a reply's tokens | The prompt, model and retrieval produce the right answer; with `--execute-tools`, the tools too |
| A real message or call | billed | Everything else, including tool calling on voice |

> **Note:**
> `agents test` runs the **text** path. It exercises the prompt, the model and the
> knowledge base, and it reports what it could not prove — a disabled agent, tools
> its channels will not call, flows it did not evaluate. Treat those warnings as
> part of the result.

## Agents and senders

A **sender** is the identity a conversation happens on: a phone number, a
WhatsApp account, an email address.

```mermaid
flowchart LR
    A["Agent"] --- S1["Sender<br/>+1 415 555 0100"]
    A --- S2["Sender<br/>support@acme.com"]
    A --- S3["Sender<br/>WhatsApp"]
```

An agent can answer on several senders. **A sender answers with at most one
agent** — connecting one that is already in use is rejected, naming the agent
that holds it.

```bash
npx zavudev agents list
npx zavudev agents senders connect --agent <agentId> --sender <senderId>
```

An agent can also exist with no sender at all while you build it. It will not
receive anything until you connect one and enable it.

## Addressing an agent

Everything that hangs off an agent (its tools, flows and knowledge bases) is
reachable two ways:

```
/v1/senders/{senderId}/agent/tools     through the sender that answers with it
/v1/agents/{agentId}/tools             through the agent itself
```

They are the same resource. Use the second when the agent has no sender yet,
which is the state `POST /v1/agents` creates and the one you assemble an agent
in. The first is convenient when you are already holding a sender id.

```sh
# Build an agent before it answers anywhere
AGENT=$(curl -s -X POST https://api.zavu.dev/v1/agents \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -d '{"name":"Ada","provider":"zavu","model":"openai/gpt-5.6-luna","systemPrompt":"..."}' \
  | jq -r .agent.id)

curl -X POST https://api.zavu.dev/v1/agents/$AGENT/tools -d '{...}'
curl -X POST https://api.zavu.dev/v1/agents/$AGENT/knowledge-bases -d '{...}'

# Then connect a sender and enable it
curl -X POST https://api.zavu.dev/v1/agents/$AGENT/senders -d "{\"senderId\":\"$SENDER_ID\"}"
curl -X PATCH https://api.zavu.dev/v1/agents/$AGENT -d '{"enabled":true}'
```

## Where to go next

  - [Functions quickstart](https://www.zavu.dev/docs/guides/functions/quickstart): Write and deploy your first agent in code.
  - [Factory agents](https://www.zavu.dev/docs/guides/functions/factory-agents): Start from a production agent instead of a blank file.
  - [Voice agents](https://www.zavu.dev/docs/guides/voice-agents/overview): Answer and place phone calls.
  - [Agent setup](https://www.zavu.dev/docs/guides/ai-agents/setup): Create and configure an agent without writing code.
