There are two kinds of AI agent in your work right now, and they do different jobs.
One lives in your editor. Claude Code, Cursor, Copilot, Codex. It reads your repo and writes code.
The other answers your customers. It picks up the phone, replies on WhatsApp, checks an order, books a meeting.
This is about pointing the first one at the second one. Not you following a tutorial with your agent watching. You create a Zavu account and log in, and from there your coding agent scaffolds the agent, writes the tools, deploys it, tests it, reads the logs, and fixes what it finds.
Why your coding agent cannot do this today
Ask any coding agent to "add WhatsApp to my app" and you get code that looks right. Looking right is the problem.
It invents endpoint names. It ignores the WhatsApp 24-hour window, so the first template send fails in production and not in dev. It writes a webhook handler with no signature verification. It picks SMS for a message with an image attached.
None of this is the model being weak. It has simply never seen this API, and it will not say so.
Two pieces fix that, and each does a different job:
| Piece | What it gives your coding agent | How you add it |
|---|---|---|
| Skills | Knowledge: endpoints, channel rules, error codes, conventions | npx skills add zavudev/zavu-skills |
| CLI | Hands: scaffold, deploy, test, read logs, run a tool locally | npx zavudev |
Who does what
The split is not "you do the easy parts". It is: you do the things that legally, financially, or physically need a person.
| You | Your coding agent |
|---|---|
| Create the Zavu account | Finds or creates the sender |
Run zavu login and authorize in the browser | Scaffolds the function |
| Approve buying a phone number, if it needs one | Writes defineAgent and every defineTool |
| Connect WhatsApp Business through Meta's signup | Sets the secrets |
| Read the diff before it ships | Deploys, tests, tails logs, fixes, redeploys |
The only part you type
bashnpx skills add zavudev/zavu-skills npx zavudev login
The installer asks which coding agents to install the skills to. They are plain markdown files that load themselves when the task matches, so nothing runs until it is relevant. Forty-plus agents are supported, including Claude Code, Cursor, Copilot, Codex, Cline, Gemini CLI, Amp, and Warp.
login opens a browser, you sign in, you pick the project, you click Authorize. The key lands in ~/.zavu/credentials.json with mode 0600 and your agent uses it from then on. On a machine with no browser, set ZAVUDEV_API_KEY instead and skip the step.
That is the setup. From here you talk.
What the skills put in the model's head
| Skill | What it teaches |
|---|---|
zavu-rules | Always loaded. SDK ecosystem, auth, conventions, business rules |
functions | defineAgent, defineTool, deploy, debugging |
ai-agent | Agent config, providers, flows, tools, knowledge bases |
voice-agent | Voice agents, greetings, transfers, call limits |
send-message | Channel decision tree and every message type |
webhook-setup | Signature verification, retry policy, event handling |
whatsapp-templates | Template creation, Meta approval, OTP buttons |
broadcast-campaign | Create, review, send, monitor |
contacts-management | Multi-channel contacts, merge, introspection |
phone-numbers | Search, purchase, regulatory requirements |
Then you describe what you want
textBuild a Zavu agent for my pizzeria on WhatsApp. It should show the menu, check table availability, and take reservations. Reservations live in my Postgres at DATABASE_URL. Use my existing sender, deploy it, and test it before you tell me it works.
You are not expected to know the commands below. They are here so you can recognise what scrolls past, and so you can tell when your agent skipped a step.
bashnpx zavudev whoami # which project am I about to change npx zavudev senders list # find the sender to attach to npx zavudev fn init --template restaurant-booking -y npx zavudev fn secrets set SENDER_ID "jn76vnxet8g5nq661by3v06y1581bmmn" npx zavudev fn secrets set DATABASE_URL "postgres://..." npx zavudev deploy
whoami is the one to look for. Deploying an agent into the wrong project is the kind of mistake that stays quiet for a day.
If you would rather it start from something proven than from a blank file, there is a catalog:
bashnpx zavudev agents catalog
textid name voice tools category fermi Fermi, Lead Qualification yes 5 sales curie Curie, Customer Support yes 5 support kepler Kepler, Video Call Booking yes 2 frontDesk hopper Hopper, Lead Capture and Booking yes 5 sales aria Aria, Multi-channel concierge yes 3 frontDesk atlas Atlas, Product Expert yes 0 support
Say "start from kepler" and it runs agents pull kepler, which scaffolds real, editable TypeScript you own, not a black box. Atlas has no tools on purpose: it answers from a knowledge base, and an agent that only needs to read your documentation should not be handed the ability to write anywhere.
Read what it wrote
This is the part worth your attention, because it is the part you will live with.
tsimport { defineAgent, defineTool } from "@zavudev/functions" defineAgent({ senderId: process.env.SENDER_ID!, name: "Bella", provider: "zavu", model: "openai/gpt-4o-mini", channels: ["whatsapp", "sms"], prompt: "You are the order desk for Tony's Pizza. Keep answers to one or two sentences.", }) defineTool({ name: "check_order_status", description: "Look up an order. Call this before answering about one.", parameters: { type: "object", properties: { orderId: { type: "string" } }, required: ["orderId"], }, handler: async ({ orderId }) => { const res = await fetch("https://api.example.com/orders/" + orderId) if (!res.ok) return { ok: false, reason: "not_found" } return { ok: true, ...(await res.json()) } }, })
Two things to check every time.
provider: "zavu" is the managed gateway: no API key of your own, no separate billing relationship, LLM cost comes off your Zavu balance. If your agent wired in a raw OpenAI key instead, that was a choice nobody asked for.
The code is the source of truth. Delete the defineAgent call and the next deploy deletes the agent. There is no second place where it also exists and quietly drifts.
You never registered a webhook or wired a trigger, and that is not an omission. defineAgent({ senderId }) is the link: that sender now hands every inbound message to this agent.
It ships a voice agent the same way
textSame agent, but it should answer the phone too. Spanish greeting, transfer to +14155551234 when someone asks for a human, hard stop at 12 minutes.
tsdefineAgent({ senderId: process.env.SENDER_ID!, name: "Bella", provider: "zavu", model: "openai/gpt-4o-mini", channels: ["voice", "whatsapp"], voice: { enabled: true, model: "openai/gpt-4o", greeting: "Hi, thanks for calling Tony's. How can I help?", greetings: { es: "Hola, gracias por llamar a Tony's." }, interruptible: true, maxCallDurationMinutes: 12, transferPhoneNumber: "+14155551234", }, prompt: "You are the order desk for Tony's Pizza.", })
interruptible is barge-in: the caller can cut the agent off mid-sentence, which is what makes a call feel like a call. Voice needs a phone number on the sender and the Voice Agents feature enabled for your team.
It debugs itself, and that is the whole point
Writing the agent is the easy half. The half that decides whether it is any good is the loop: run it, read what went wrong, fix it, run it again. Every step of that loop is a command, so your agent runs the loop instead of narrating it to you.
textThe agent quoted the wrong price for ORD-12345. Run agents test against that order, read fn logs and agents executions, find where it went wrong, fix it, and redeploy.
What it reaches for:
bashnpx zavudev agents test --sender "$SENDER_ID" --message "where is order ORD-12345?" npx zavudev fn invoke --tool check_order_status --args '{"orderId":"ORD-1"}' npx zavudev fn logs --tail npx zavudev agents executions --sender "$SENDER_ID" npx zavudev messages list --limit 10
agents test delivers nothing to anyone, charges nothing, records nothing. fn invoke runs one handler on your machine in under a second, against uncommitted code, with the send calls mocked, so it separates "the tool is broken" from "the prompt is wrong". The last three are the evidence: your logs, every agent run with the tools it called and what each cost, and every message in and out.
That is a real loop. The agent has knowledge from the skills, hands from the CLI, and now evidence.
Do you need the MCP server?
There is one, and if your coding agent has a terminal you can skip it.
It wraps the same REST API the CLI already mirrors command for command, so it buys you a second thing to authenticate and covers strictly less. Running a handler locally against uncommitted code is not an API operation, so no MCP can offer it. Reach for the MCP server when the agent has no shell.
What it cannot do without you
Three things, and each stops for a reason.
Buying a phone number spends your money. The CLI gates it behind an explicit confirmation, and it should stay that way. An agent that provisions numbers unattended is a billing incident waiting for a bad loop.
Connecting WhatsApp Business is Meta's signup. A person clicks through it in a browser, agrees to Meta's terms, and picks the business. Nobody can automate consent on your behalf.
Shipping to production is your call. deploy is one command, which is exactly why the diff deserves the thirty seconds. Read what the tools actually do before they run against real customers.
How Zavu bills this
None of the part your coding agent does costs money. The skills, the CLI, the SDKs, deploy, and every redeploy after it are free. You are billed for an agent that runs, not for one that gets built.
The plan covers running it. Free is $0 with 2,000 messages, 3,000 emails and 100,000 function invocation units. Hobby is $25 a month with 100,000 messages, 100,000 emails and 1M units, and Standard and Growth raise those. A unit is one 128 MB invocation, so a 256 MB function spends 2 per call. On the free tier the function quota is a hard cap: requests are rejected rather than billed.
Delivery is per message. Each channel has its own rate, and traffic past your plan's allowance is billed per message: $0.003 on Hobby, $0.002 on Standard, $0.001 on Growth. Email overage is $0.90 per thousand. Paid plans also drop a monthly credit into your balance, which is what the first sends come out of.
The model, only if it is ours. With provider: "zavu" the LLM is metered per token off your Zavu balance, and each charge names the model and the token counts, so you can read the bill line by line. Bring your own key and Zavu bills nothing for inference: you pay OpenAI or Anthropic directly. Either way you need a positive balance, because the reply the agent sends is a message and messages cost money.
Voice is per connected minute. The managed pipeline is $0.0625 a minute plus telephony, billed from answer to hangup. Standard models are covered by that rate; a premium model adds its own token cost. A short estimate is reserved when the call is placed and settled against the real duration when it ends.
The part worth knowing before you start: the build loop is free. agents test delivers nothing, records no execution, and charges nothing. fn invoke runs the handler on your own machine. Your coding agent can run, read, fix, and run again all afternoon without touching the bill. The first thing you pay for is a real customer getting a real answer.
Rates move. The pricing page is the one that is current.
Three rules that keep this honest
A dry run is not proof. agents test exercises the text path and prints warnings about what it cannot prove: an agent that is disabled, tools this path will never call, contact metadata that only exists in a real conversation. If your agent reports a green test as "it works", those warnings are the thing it skipped.
Tools do not run on the plain text path. They run on voice, and inside a flow's tool step. Deploy still prints "Tools synced" for a text-only agent, because the tools are registered. Registered is not invoked. If your WhatsApp agent needs to look something up, that step belongs in a flow.
Never return a fake success. A handler that reports { booked: true } when it booked nothing makes the agent tell a real person their table exists. Return the failure. This is the single most common way an agent that passes every test hurts a customer, and it is worth saying out loud in your prompt.
Where this leaves you
An agent that answers your customers is a file in your repo. It has a prompt, some tools, a deploy command, and a log you can read. Same review, same version control, same rollback as everything else you ship.
You brought the account and the judgment. The agent already in your editor brought the rest.