AI AgentsClaude CodeCLITutorial

Build AI Agents Using Your AI Agents

You create the account and log in. Your coding agent does the rest: scaffolds the agent, writes the tools, deploys, tests it, and fixes what it finds.

Written by: Victor VillalobosReviewed by: Jennifer VillalobosJuly 26, 202610 min read

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:

PieceWhat it gives your coding agentHow you add it
SkillsKnowledge: endpoints, channel rules, error codes, conventionsnpx skills add zavudev/zavu-skills
CLIHands: scaffold, deploy, test, read logs, run a tool locallynpx zavudev
Skills tell it what to do. The CLI lets it do it, and puts the result in production.

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.

YouYour coding agent
Create the Zavu accountFinds or creates the sender
Run zavu login and authorize in the browserScaffolds the function
Approve buying a phone number, if it needs oneWrites defineAgent and every defineTool
Connect WhatsApp Business through Meta's signupSets the secrets
Read the diff before it shipsDeploys, tests, tails logs, fixes, redeploys
Everything in the right column is a command, which is exactly why an agent can do it.

The only part you type

bash
npx 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

SkillWhat it teaches
zavu-rulesAlways loaded. SDK ecosystem, auth, conventions, business rules
functionsdefineAgent, defineTool, deploy, debugging
ai-agentAgent config, providers, flows, tools, knowledge bases
voice-agentVoice agents, greetings, transfers, call limits
send-messageChannel decision tree and every message type
webhook-setupSignature verification, retry policy, event handling
whatsapp-templatesTemplate creation, Meta approval, OTP buttons
broadcast-campaignCreate, review, send, monitor
contacts-managementMulti-channel contacts, merge, introspection
phone-numbersSearch, purchase, regulatory requirements

Then you describe what you want

text
Build 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.

bash
npx 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:

bash
npx zavudev agents catalog
text
id 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.

ts
import { 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

text
Same agent, but it should answer the phone too. Spanish greeting, transfer to +14155551234 when someone asks for a human, hard stop at 12 minutes.
ts
defineAgent({ 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.

text
The 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:

bash
npx 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.

Need help? Contact us or join our Discord community for support.

Follow us on social media

Ready to get started?

Start building for free, or schedule a call to discuss your specific use case.

Build AI Agents With Claude Code, Cursor or Copilot | Zavu Blog