Ship / Deploy & hosting
How an agent actually runs
Two ways to put an agent on a phone line. One needs no infrastructure from you; the other runs a process you own. Knowing which you are on explains every credential question.
The short answer. Building in the flow builder means you never touch LiveKit or provider keys — the platform runs it. Writing an agent with the SDK means you run the worker process, so that process needs credentials.
The two paths
Path A — flow builder no infrastructure
The platform already runs a pool of workers that are connected to telephony and to the model providers. Deploying a flow points that pool at your published version — there is nothing to host and no keys to manage.
- 1
Build the flow
Compose it on the canvas and press Save. Nothing runs yet.
- 2
Publish
Snapshots an immutable version. Validation errors block publish; warnings ride along.
- 3
Go live
Save + publish + point the shared runtime at this version, in one step. The agent now appears in Agents.
- 4
Attach a number
Numbers → assign the number to the agent. Inbound calls to that number now run this flow.
Need custom logic — a lookup in your database, creating a ticket? You do not need the SDK for that. Add a Call an API step (or register an HTTP tool under Tools) that points at an endpoint you host. The platform calls it server-side, with the URL and credentials never leaving the backend.
Path B — SDK worker you host
.start() boots a worker process that connects to LiveKit and waits for calls dispatched to its agent name. That process is yours to run — locally while you develop, or as a long-running deployment in production.
# Only needed for the SDK-worker path.
VOICELAYER_API_KEY=vl_... # who you are
VOICELAYER_API_URL=https://api.vlayers.ai
LIVEKIT_URL=wss://<project>.livekit.cloud
LIVEKIT_API_KEY=API...
LIVEKIT_API_SECRET=...
OPENAI_API_KEY=sk-... # provider keys the pipeline uses
DEEPGRAM_API_KEY=...
AGENT_ENVIRONMENT=prd # MUST match where the agent is registeredThree things trip people up on this path, all of them silent:
.env is not read for you
.start() reads process.env and nothing else — in production the environment is injected. Locally, import a loader yourself or the worker exits with a missing-credentials error.
AGENT_ENVIRONMENT must match
An agent is identified by name + environment. If the worker boots as dev (the default) but the agent is registered in prd, you get two separate rows and calls go to neither.
Stopping the worker removes the agent
On SIGINT/SIGTERM the SDK deregisters so the dashboard reflects reality immediately. Pass { deregisterOnSignal: false } to keep the row.
import 'dotenv/config'; // <- .start() does NOT read .env for you
import { defineAgent } from '@voicelayer/sdk';
export default await defineAgent({ name: 'support-desk', prompt: '…' })
.start(import.meta.url);Registered vs running
These are different things, and the difference explains most “why is my agent offline?” questions.
Registered — it exists
One authenticated call creates the agent record. It shows in Agents with its config. No worker, no LiveKit, no call-handling. The row persists until you delete it.
Running — it can answer
A live process heartbeats every ~30s. Miss the heartbeats for ~90s and the agent reads Offline — the record stays, it just can’t take a call. Flow agents mirror the shared pool’s health, so they are online whenever the platform is.
import { createClient } from '@voicelayer/sdk';
const client = createClient({ apiKey: process.env.VOICELAYER_API_KEY });
await client.agents.register(
{ name: 'support-desk', version: '1.0.0', environment: 'prd' },
{ attachSignalHandlers: false }
);Which should you choose? Start with the flow builder — it covers most agents and has nothing to operate. Reach for the SDK when you need logic the canvas can’t express, and remember you can mix: a flow can call your API for the hard parts.