# npx zavudev agents

> Create, test, connect and inspect agents from the terminal

An agent is a standalone object with its own id. It can answer on several
senders, and it can exist with none while you build it.

```bash
npx zavudev agents list
```

```
id                                name        kind   enabled  senders  model
qd75detym58c6has4sfrye3vws8b6ygt  Atlas       voice  yes      1        openai/gpt-5.6-luna
qd7ck28evcskdc3xx4wzevtmeh8b6bcm  Pizza Desk  text   no       0        gpt-4o-mini
```

`kind` reflects what the agent actually is — an agent named for voice whose
`voice` block is missing reads as `text`, which is usually the thing you wanted
to know.

## Test an agent

The command you will use most. It runs the real prompt, model and knowledge
base and prints what the agent **would** reply. Nothing is delivered and no
execution is logged. The run is **billed like a live reply**: on the `zavu`
provider its tokens are charged to your balance (the cost is in the output), on
your own provider key it costs your provider.

```bash
npx zavudev agents test --agent <agentId> --message "where is order ORD-001?"
```

```
Your order ORD-001 shipped on Tuesday and arrives Thursday.

  1418→40 tokens · 5249ms · 1 knowledge chunk(s)
```

| Flag | |
|---|---|
| `--agent <agentId>` | The agent. Get it from `agents list`. |
| `--sender <senderId>` | Address it by sender instead (resolves to that sender's one agent) |
| `--message <text>` | What to say |
| `--turn <text...>` | Prior turns, alternating user/assistant, oldest first. Repeatable. |
| `--no-knowledge` | Skip retrieval, to isolate prompt behaviour from the knowledge base |
| `--execute-tools` | Run the tools the agent calls, for real, and reply with their results. Without it the run stops at the tool the agent chose. |
| `--json` | Raw response |

Multi-turn:

```bash
npx zavudev agents test --agent <agentId> \
  --turn "I need to change my booking" --turn "Sure — which one?" \
  --message "the one on Friday"
```

> **Warning:**
> **Read the warnings.** A dry run cannot prove everything, and it tells you what
> it could not: an agent that is still disabled, tools its channels will never
> call, flows it did not evaluate, contact metadata that exists live but not here.
> Treat them as part of the result — a good answer here is not proof the agent
> works on a real channel.

## Start from a factory agent

```bash
npx zavudev agents catalog                      # what is available
npx zavudev agents pull kepler --dir kepler     # scaffold it locally
cd kepler && npm install
npx zavudev deploy
```

`pull` writes a real TypeScript project: `index.ts` with the agent and its
skills, a `tsconfig.json`, and `@zavudev/functions` as a dependency so your
editor and `tsc` resolve it. Edit it freely — the code is the source of truth
and `npx zavudev deploy` reconciles whatever it declares.

| Flag | |
|---|---|
| `--dir <path>` | Where to scaffold. Defaults to `./<id>`. |
| `--sender <senderId>` | Sets the `SENDER_ID` secret for you |
| `--calendar <provider>` | For booking agents: `webhook` (default) or `calcom` |

### Booking agents

Kepler, Hopper and Fermi book meetings. By default they scaffold against a
generic webhook — an HTTPS endpoint **you** build and host that returns open
slots and books them. Until it exists the agent cannot book, and says so rather
than inventing a time.

Point it at Cal.com instead and it works with an API key:

```bash
npx zavudev agents pull kepler --calendar calcom
```

`pull` then lists every secret the agent needs, with a hint for each:

```
npx zavudev fn secrets set CALCOM_API_KEY <value>
  ↳ Cal.com API key (Settings → Developer → API keys).
npx zavudev fn secrets set CALCOM_EVENT_TYPE_ID <value>
  ↳ Numeric id of the Cal.com event type to book.
```

The Cal.com client only reports a booking when Cal.com accepted it. An event
type that requires confirmation comes back pending, and telling a caller they
are booked at that point is the worst outcome on that line.

`npx zavudev agents init` walks the same path interactively: pick or create a sender,
pick an agent, get ready to deploy.

## Create one from scratch

```bash
npx zavudev agents create --sender <senderId> \
  --name "Order desk" --provider zavu --model openai/gpt-5.6-luna \
  --channels whatsapp,sms \
  --prompt "You are the order desk for Tony's Pizza. Answer in one or two sentences."
```

Use `--prompt-file` for anything longer than a line — terminal paste mangles
newlines. `--data` / `--file` take a full JSON body for advanced fields.

> **Note:**
> An agent is created **disabled** and answers nobody until you turn it on. Pass
> `--enabled true` on create, or `npx zavudev agents update --sender <id> --enabled true`
> later. The CLI says so when it happens.

## Connect senders

A sender is the identity a conversation happens on. An agent can answer on
several; **a sender answers with at most one agent.**

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

Connecting a sender that is already in use is rejected, naming the agent that
holds it — otherwise you would have an agent that looks connected and never
receives a message.

## Tools

```bash
npx zavudev agents tools list --sender <senderId>
npx zavudev agents tools test --sender <senderId> <toolId> --params '{"orderId":"ORD-1"}'
```

> **Note:**
> Tools are offered to the model on **every channel**: plain text, voice, and
> inside a flow's `tool` step, with up to 5 tool rounds per reply. By default
> `agents test` does not run them: it reports the tool and arguments the agent
> chose and stops. Add `--execute-tools` to run them and see the full reply, with
> whatever side effects your handlers have. See [how agents work](https://www.zavu.dev/docs/concepts/agent-architecture).

Tools declared in code with `defineTool` are managed by their function: they
appear here read-only and are changed by redeploying.

To run a handler in isolation, without deploying:

```bash
npx zavudev fn invoke --tool get_order --args '{"orderId":"ORD-1"}'
```

## Knowledge bases

```bash
npx zavudev agents knowledge-bases create --sender <senderId> --name "Product docs"
npx zavudev agents knowledge-bases documents add --sender <senderId> --kb <kbId> \
  --title "Pricing" --content-file ./pricing.md
npx zavudev agents knowledge-bases documents list --sender <senderId> --kb <kbId>
```

Processing takes a few seconds. Verify retrieval actually fires with `agents
test` — it reports how many chunks the answer used.

> **Warning:**
> A prompt that says *"only state what the documentation returns"* with no
> documents attached does not refuse. It invents. Attach the documents before
> relying on that instruction.

## Flows

```bash
npx zavudev agents flows list   --sender <senderId>
npx zavudev agents flows create --sender <senderId> --file flow.json
```

A flow is a deterministic sequence with a trigger. Steps take a `config`
object; a `tool` step names its tool through `config.toolName`, or the older
`config.toolId`. Either field is accepted, and either one may hold the tool's
name or its id. A flow naming a tool the agent does not have is rejected at
creation, listing the ones it does have.

Flows are **not** evaluated by `agents test` — they run on a real message, and
`agents test` says so when the agent has any.

## Inspect what happened

```bash
npx zavudev agents stats            --sender <senderId>
npx zavudev agents executions list  --sender <senderId>
npx zavudev agents executions get   --sender <senderId> <executionId>
npx zavudev agents reset-thread     --sender <senderId>
```

`executions get` carries `errorMessage` and `responseText` — the place to look
when a live reply went wrong. `reset-thread` closes the conversation and any
flow session for one contact so the next inbound starts fresh.

Dry runs do not appear in `stats` or `executions`; they are not executions.

## Calls

```bash
npx zavudev calls list --status completed
npx zavudev calls get <callId>       # includes the transcript, turn by turn
npx zavudev calls hangup <callId>
```

`calls get` prints the conversation including tool calls. It is the only record
of what the agent actually said, and the first place to look after a call went
wrong.

```bash
npx zavudev calls create --to +14155551234
```

> **Warning:**
> `calls create` places a real outbound call, billed per connected minute plus
> telephony. `--greeting`, `--language` and `--max-minutes` override the agent's
> configuration for that call only.
