Functions

Your agent is not a form.It is a file in your repo.

Declare the agent and the tools it can call in TypeScript. Deploy the directory. Zavu builds it, holds the secrets, runs it on your events or on a schedule, and reconciles the live agent with what the file says.

Runtime
nodejs24
Pinned at the first deploy and kept, so a later deploy cannot move it under you.
Timeout
1–180 s
Default 30. Which ceiling actually binds depends on the trigger.
Memory
128 · 256 · 512 · 1024
MB per invocation. Most tool handlers fit in the smallest.
Source
200 files · 900,000 bytes
npm packages are declared, not uploaded.

01 — Write it

A directory, an entrypoint, and whatever it imports.

Deploy starts at the entrypoint and follows relative imports. What it reaches ships; what nothing imports stays on your machine.

What actually gets uploaded

Pick a file. The panel says whether it travels with the deploy, and why.

order-bot/
Files uploaded4 / 200
Bytes uploaded1,355 / 900,000
index.tsEntrypoint
import { defineAgent, defineTool } from "@zavudev/functions"
import { formatOrder, lookupOrder } from "./lib/orders"
import { HOST_PROMPT } from "./prompts/host"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Bella",
  provider: "zavu",
  model: "openai/gpt-4o-mini",
  prompt: HOST_PROMPT,
})

defineTool({
  name: "lookup_order",
  description: "Get current status of an order. Use when the customer asks about one.",
  parameters: {
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"],
  },
  handler: async ({ orderId }, ctx) => {
    ctx.log("lookup", orderId)
    return formatOrder(await lookupOrder(orderId))
  },
})

The entrypoint. Deploy reads it first and walks its relative imports from here. It defaults to index.ts — name a different one with entrypoint over REST, or --source on the CLI.

Refused paths

../shared/x.tsnode_modules/package.json

The CLI and the API are the same surface.

Use whichever fits the way you ship. Nothing here is available through only one of them.

index.ts
npx zavudev fn init --name order-bot --template blank
import { defineAgent, defineTool } from "@zavudev/functions"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Bella",
  provider: "zavu",
  model: "openai/gpt-4o-mini",
  prompt: "You are Bella, host at the restaurant. Be brief.",
})

defineTool({
  name: "check_availability",
  description: "Get free reservation slots for a date.",
  parameters: {
    type: "object",
    properties: { date: { type: "string" }, partySize: { type: "number" } },
    required: ["date", "partySize"],
  },
  handler: async ({ date, partySize }) => {
    return { available: true, slots: ["19:00", "21:00"] }
  },
})

02 — Ship it

A deploy is a record you can watch, and go back to.

Deploying returns immediately with an id. You poll it. Every version is kept, with the build output that explains it.

Deployment lifecycle

Step through it, or let it run. Break an import to take the other path.

  1. supersededwhat a version becomes once a newer one takes over. It stays readable, and rolling back to it is one call.

DeploymentGET /v1/functions/deployments/{deploymentId}
{
  "id": "fnd_8c21ab5f",
  "functionId": "fn_4kq2m9x",
  "version": 7,
  "status": "pending",
  "sourceCodeBytes": 18432,
  "bundleBytes": null,
  "errorMessage": null,
  "deployedAt": null,
  "createdAt": "2026-03-14T09:41:12.000Z"
}

Poll until status is active or failed. Both are terminal.

Rollback

Roll back by naming a deployment id. Its source, dependencies and runtime pin are copied onto the draft and deployed again as a new version, so history stays append-only. Secrets are not rolled back — they are current, not versioned.

$ npx zavudev fn rollback 4

Deploy from GitHub

Link a repository and a push to the branch deploys it. The server decides how the link authenticates, not you: with the Zavu GitHub App installed, private repositories work and there is nothing to add to the repository. Without it you get a manual link, and its webhook secret is printed exactly once. Linking does not check the repository against GitHub — an owner/repo the installation cannot see is accepted and fails on the first deploy.

connection
app or manual — the server picks
branch
only pushes here deploy
rootDir
the subdirectory, for monorepos
autoDeploy
false keeps the link and ignores pushes
lastStatus
deploying, deployed or failed
lastError
why the last push did not land

03 — Run it

On your events, or on the clock.

A trigger subscribes the function to an event type, for one sender or for every one of them. The special cron type runs it on a schedule instead.

Trigger builder

Pick event types and senders. The request and the triggers it creates appear on the right, and a cron expression is resolved to its next fire times in UTC.

Event types

Senders

Add the cron event type above to schedule the function instead of subscribing it to a message. It takes a five-field UTC expression, and a function can hold several with different schedules.

RequestPOST /v1/functions/{functionId}/triggers
{
  "eventTypes": [
    "message.inbound"
  ],
  "senderIds": [
    null
  ]
}
Response201
{
  "added": 1,
  "skipped": 0,
  "triggers": [
    {
      "id": "fnt_0001",
      "eventType": "message.inbound",
      "senderId": null,
      "active": true
    }
  ]
}

One trigger per event type × sender. A cron trigger ignores the sender axis, so it counts once however many senders you picked. Duplicates come back as skipped, not created twice.

Worth knowing

The timeout you set is not always the ceiling that binds.

  • Event and cron runs

    Asynchronous. Nobody is waiting on the response, so a long timeout only bounds what a stuck run costs you.

  • A tool called mid-conversation

    Synchronous. The customer's reply waits on your handler, so keep these well under the limit rather than at it.

  • A function exposed over HTTP

    Additionally bound by the platform's HTTP response limit. Raising timeoutSec does not raise that one.

04 — Operate it

Secrets, logs, and an endpoint when you want one.

01

Secrets go in and do not come back

Listing them returns each key and the last four characters of its value, never the value. Keys are uppercase env-var style, and the AWS_ and LAMBDA_ prefixes are reserved. Setting one marks the function out of sync rather than restarting it underneath you; the next deploy applies it.

02

An API key you did not have to wire

Creating a function provisions a scoped Zavu key and injects it as ZAVU_API_KEY, so a handler can call the API back without you managing a credential. For broader scope, create a key yourself and set it as a secret.

03

Logs, filtered and paged

Fetch invocation logs bounded by a time window or narrowed by a filter pattern, and page through them with nextToken. The same output tails from the CLI while you work.

04

An HTTPS endpoint, on a switch

Turning the public URL on applies to the already-deployed function — no redeploy. Turned off, the stored URL stops serving and is no longer returned, so publicUrl reads null rather than stale.

Write it, deploy it, watch the logs.

Start free, no credit card. The CLI talks to the same API your code will.

Functions | Agents and tools defined in TypeScript — Zavu | Zavu