defineAgent
defineAgent declares the AI agent that will run on a specific WhatsApp / SMS
sender. Calling it inside your function source binds the agent to that sender
on every npx zavudev deploy.
npx zavudev deploy:
- The agent is created (or updated) under that sender.
- Tagged as managed by this function — the dashboard disables manual edits to prevent drift.
- Marked
enabled: trueautomatically.
Required fields
Optional fields
Voice
Add avoice block to make the agent answer calls. The LLM runs co-located
in the voice network for the lowest latency, independent of the model used
for text. npx zavudev deploy provisions everything; removing the block turns the
agent back into text-only.
Voice requires the Voice Agents feature enabled for your team and a phone
number assigned to the sender. The fastest way to start is to pull a ready-made
voice agent: see Factory agents.
Knowledge base
Documents the agent answers from, declared with the agent. Retrieval runs before every reply.- a document you added is created and embedded
- a document whose
contentchanged is re-embedded - a document you removed is deleted
- a document that did not change is left alone, so a redeploy costs nothing
Titles are identity
Documents are matched bytitle. Renaming one replaces it: the old document is
deleted and a new one embedded. That is usually what you want, but it is a
re-embed rather than an edit.
Writing documents that retrieve
Retrieval is similarity over chunks, not keyword search.- Headings and short paragraphs chunk cleanly. A wall of text produces chunks that each hold half an answer.
- Use the customer’s words. A document that says “return merchandise authorization” does not retrieve for “can I send it back”.
- State facts. “14 days” retrieves and answers. “within a reasonable period” retrieves and says nothing.
- One base per subject. Mixing the refund policy with the API reference retrieves from both and dilutes the context.
Check that it is actually being used
knowledge chunk(s) used. Zero on an agent that has
documents means the answer was not grounded in them, which is otherwise
indistinguishable from a correct answer.
The size limit is your source file
Documents live inline, and the deploy caps source at about 900KB. A knowledge base larger than that belongs on the API:Flows
defineFlow declares a deterministic conversation: the steps you wrote, in the
order you wrote them. Reach for it where the model must not improvise, and leave
the rest of the conversation to the agent.
A new flow arrives disabled
A flow intercepts real conversations, so writing one and turning it on are separate decisions. Passenabled: true to have the first deploy enable it, or
turn it on afterwards:
enabled. Pausing a flow from the dashboard
during an incident has to survive a redeploy.
The tool a step calls must exist on the agent
Declaring it withdefineTool in the same file is enough, because tools
reconcile before flows. A step naming a tool the agent does not have is reported
and that flow is skipped; the rest of the deploy still lands.
The declaration is the source of truth, with two exceptions
A flow you stop declaring is deleted on the next deploy. Two things are left alone:- A flow this function did not create. One built in the dashboard with the same name is not adopted and not overwritten. The deploy says so.
- A flow someone is standing in. If a contact is mid-conversation inside it, it is kept and removed on a later deploy, rather than stranding them.
Providers
zavu — managed gateway (recommended for getting started)
No API key needed. LLM costs are billed directly from your Zavu balance at
pass-through rates.
Bring your own key (BYOK)
Passprovider matching the vendor and apiKey (or pre-create a secret in
the dashboard and rely on it being already stored).
gpt-4o-mini, claude-3-5-sonnet-20241022,
gemini-1.5-flash, etc) — no provider/ prefix when using BYOK.
Prompts
Theprompt field is the system message every conversation starts with. It’s
where you set the persona, rules, and guardrails.
Patterns that work
Anti-patterns
Triggers (which messages reach the agent)
By default the agent fires on every inbound message to its sender. Restrict withchannels and messageTypes:
message.inbound), use explicit triggers
in addition.
Several files
Split the agent across as many files as it needs. Imports between them are resolved when you deploy:npx zavudev deploy starts at your entrypoint (index.ts by default) and
uploads every file it reaches through relative imports. Files nothing imports
stay on your machine, so tests and scratch work are not deployed.
npm packages are not part of that tree: declare them under dependencies in
package.json and they are installed and bundled.
../shared/x.ts),
node_modules/, and names starting with __zavu. A project can hold up to 200
files totalling 900,000 bytes. If you import something from outside the project
directory, either move it inside or publish it as an npm package.
The dashboard editor works the same way — add files in the sidebar and import
between them.
To deploy straight from a repository instead, link it:
One agent per file
defineAgent is allowed multiple times only when each call has a different
(senderId, name) pair, in which case each tool must explicitly bind to one:
SENDER_ID secret.
Updates and ownership
Everynpx zavudev deploy reconciles the live agent to match what’s in the code.
When you remove
defineAgent from your code and redeploy, the managed agent
is deleted. Manual agents are never touched.
Disabling without deleting
To pause an agent without removing the code:Common patterns
Multi-language agent
Multi-language agent
Don’t hard-code a language. Tell the prompt to match the user:The LLM handles language detection per turn — no need for routing logic.
Escalation to human
Escalation to human
Add a tool that signals escalation (e.g., creates a ticket in your CRM
or notifies an operator on Slack). Tell the prompt when to use it:
Different agent per sender (multi-tenant)
Different agent per sender (multi-tenant)
Same code, different
SENDER_ID secret in each function. The prompt can
even include process.env.BRAND_NAME to customize per tenant:Reading contact metadata
Reading contact metadata
With
includeContactMetadata: true (default), the LLM sees:- Contact’s display name (if known).
- Custom metadata fields on the contact.
- Channel they wrote from.
client.contacts.update(contactId, { metadata: {...} }).
Useful for “Hello $name” style personalization without a tool call.Next
defineTool
Give the agent actions to execute.
Secrets
Store SENDER_ID, API keys, and other env vars.
