Skip to main content

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.

Quickstart

We’ll build a restaurant booking agent that lives on a WhatsApp sender. By the end, customers can text the number and the agent will show the menu, check availability, and confirm reservations.
You’ll need an active WhatsApp sender in your Zavu project. If you don’t have one yet, connect WhatsApp first.
Skip writing boilerplate: install Zavu’s Coding Agent Skills in Claude Code, Cursor, Copilot, or any of 40+ supported AI coding agents. Your agent will then know defineAgent, defineTool, zavu deploy, and everything in this guide — just describe what you want and it generates the code for you.
npx skills add zavudev/zavu-skills

1. Install the CLI

brew install zavudev/tools/zavu
Already installed? Upgrade with brew upgrade zavu. Verify:
zavu --version

2. Log in

zavu login
This opens your browser, you sign in, pick the project this agent will live in, and click Authorize. The CLI saves the API key to ~/.zavu/credentials.json (chmod 0600). Confirm you’re on the right project:
zavu whoami
Project:  Acme Restaurants
Project ID: jh72w2dnzytttrxjqjtaq267nn7wcw6y
Team:     Acme
Mode:     live
API URL:  https://api.zavu.dev
Key ending: …1bmmn

3. Find your sender

zavu senders list
Copy the ID of the WhatsApp sender you’ll attach the agent to:
id                                name           phone           whatsapp
jn76vnxet8g5nq661by3v06y1581bmmn  Pizzeria Main  +15076323077    yes
export SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"

4. Scaffold the function

zavu fn init --template restaurant-booking -y
cd reservations
You’ll get an index.ts like this (truncated):
import { defineAgent, defineTool } from "@zavu/functions"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Bella",
  provider: "zavu",
  model: "openai/gpt-4o-mini",
  channels: ["whatsapp"],
  prompt: `Eres Bella, anfitriona de Bella Pizzeria…`,
})

defineTool({
  name: "view_menu",
  description: "Get the restaurant menu.",
  parameters: { type: "object", properties: { filter: { type: "string" } } },
  handler: async (args) => ({ menu: [/* … */] }),
})
// + check_availability, create_reservation, view_reservation
The template uses provider: "zavu" — our managed AI gateway. No BYOK required; LLM costs are billed from your Zavu balance.

5. Set the sender ID as a secret

zavu fn secrets set SENDER_ID "$SENDER_ID"
Output:
✓ Created SENDER_ID (…1bmmn)
  Environment updates on the next `zavu deploy`. Run it now to apply.

6. Deploy

zavu deploy
Watch the output:
› Deploying Restaurant reservations agent (reservations)…
  deployment id: nx7ztwe6cwg1dfp14bk6v7nb2s86hpn6
› status: bundling
› status: uploading
› status: publishing
› status: active
✓ Deployed in 14s
  Agents synced:
    + Bella
  Tools synced:
    + view_menu
    + check_availability
    + create_reservation
    + view_reservation
Your agent is live. The WhatsApp sender will now hand off every inbound to it. You didn’t run zavu fn triggers add anywhere — and yet, the sender knows to forward every inbound to your function. Here’s the wiring:
  • defineAgent({ senderId, ... }) registers your agent on that sender. When zavu deploy syncs the manifest, Zavu writes a row that says “sender $SENDER_ID has an active agent backed by function reservations.”
  • Every inbound message to that sender automatically hands off to the agent, which runs your tools (view_menu, check_availability, etc.) inside the function.
  • You can verify the link any time:
    zavu agents get --sender "$SENDER_ID"
    # → enabled: true, managedByFunctionId: fn_…, model: openai/gpt-4o-mini
    
No defineAgent? You need triggers. If you want a function to react to sender events without an LLM agent — say, a webhook that logs every delivery, or a custom non-LLM responder — declare it with defineFunction and bind it explicitly:
zavu fn triggers add --events message.inbound --senders "$SENDER_ID"
See the Triggers guide for the full event list and cartesian-binding patterns (multiple events × multiple senders).

7. Try it on WhatsApp

From your phone (not the sender’s number), send the sender:
hola, qué tienen vegano?
Expected flow:
👤 hola, qué tienen vegano?
🤖 [calls view_menu(filter="vegan")]
🤖 Tenemos Vegan Buddha Bowl ($11). ¿Te lo reservo?

👤 sí, mañana viernes a las 9 para 2
🤖 [calls check_availability(date="friday", partySize=2)]
🤖 Tengo 19:30 y 21:00. ¿Cuál preferís?

👤 21
🤖 [calls create_reservation(date="friday", time="21:00", partySize=2, customerName="…")]
🤖 ¡Listo! Reserva RES-LXXY confirmada para 2 personas el viernes a las 21:00.

8. Watch it run

In three terminals:
# Live tool calls (your function's console.log + handler activity)
zavu fn logs --tail
# Agent executions: which tools, tokens used, cost, latency
zavu agents executions --sender "$SENDER_ID"
# Every message in & out
zavu messages list --limit 10

9. Iterate

Edit index.ts — say, add a cancel_reservation tool — and redeploy:
zavu deploy
The summary shows what changed:
✓ Deployed in 11s
  Tools synced:
    + cancel_reservation
The new tool is immediately available to the agent on the next user message. You don’t need to update prompts — the LLM reads the tool’s description and parameters schema directly.

Common pitfalls

Run zavu agents get --sender "$SENDER_ID" and confirm enabled: true. If false, check that defineAgent is being called (deploy must show Agents synced: + Bella).
Check that the tool description is specific enough. The LLM uses the description to decide when to call the tool, so vague descriptions ("do stuff") don’t trigger. Rewrite each description as the answer to “when should the model call this?”.Also confirm zavu agents tools list --sender "$SENDER_ID" shows the 4 tools with enabled: true.
Watch zavu fn logs --tail while you trigger the tool. The error stack appears live. Common causes: missing env var (run zavu fn secrets list to confirm what’s set), JSON parse errors on response, unhandled async exceptions.
Function names cap at 64 chars internally, and we prefix yours with zavu-fn-<projectId>- (41 chars used). Slugs over 23 chars get rejected server-side with a clear message. Pick something short — bella, not restaurant-reservations-agent-v2.
Pure defineFunction handlers don’t get traffic automatically — they need an explicit trigger. Run:
zavu fn triggers list                                  # see what's bound
zavu fn triggers add --events message.inbound \
  --senders "$SENDER_ID"                               # bind to a sender
defineAgent is the only declarative shortcut that auto-binds — every other event flow goes through triggers. See Triggers.

Next steps

Define agents in depth

Providers, models, prompts, triggers.

Define tools in depth

Schemas, handlers, returning structured data.

Customer support example

Knowledge base lookup + ticket creation.

Ecommerce example

Order status + smart recommendations.