# Moving from 0.x to 1.0

> Move a self-hosted 0.x voice worker to the 1.0 SDK, with the 0.8 release telling you exactly what to change.

Source: https://docs.vlayers.ai/developers/sdks/migrating

Kind: How-to guide

## What you'll build

Your 0.x voice agent, running on the 1.0 SDK. In 0.x your process was the whole call: it held a LiveKit connection, ran the speech pipeline and called your functions. In 1.0, VoiceLayer runs every call and your worker answers the functions it needs over one outbound connection. You gain the Playground, VoiceLayer numbers and every guard for code agents. You give up the raw audio, and a handler can no longer keep call state in memory between invocations.

> **Note:** 0.8 is the bridge release: it prints what to change while your agent keeps running as before. 0.x gets no releases after it, security fixes included, and keeps working until 2027-03-15. Move inside that window.

## Prerequisites

- Your 0.x agent, upgraded to `@voicelayer/sdk` 0.8.
- Node.js 22 or later.
- Access to your workspace's dashboard, to add provider keys and create a worker key.

## Step 1: Read what the bridge prints

Upgrade to 0.8 and start your worker. Each time it starts, it prints what 1.0 would refuse (`error`), what behaves differently (`change`) and what is worth knowing (`note`):

```text
[voicelayer] agent "venue-escalation" needs changes before @voicelayer/sdk 1.0
  error   on_call_unsupported (onCall): onCall can't run on the VoiceLayer pool — move its opening line to `greeting` …
  error   models_vad_unsupported (models.vad): models.vad can't be set: VoiceLayer always runs its own VAD
  change  tool_real_values (tools): code tool lookup_resident: in 0.x a code tool is handed the caller's real values …
  change  tool_bindings_implicit (toolBindings): in 0.x an agent attaches every enabled dashboard tool …
  note    start_runs_the_runner (start): `.start(import.meta.url)` boots a LiveKit worker in 0.x; in 1.0 …
```

Run the same check without starting anything:

```ts
import { checkMigration, formatMigrationReport } from '@voicelayer/sdk';

console.log(formatMigrationReport(checkMigration(myAgentConfig)));
```

## Step 2: Move provider keys out of your process

Remove the plugin packages and the `LIVEKIT_*`, OpenAI and Deepgram variables from the worker. Add those provider accounts in the dashboard under **Settings → Provider accounts** instead. Two keys replace them:

| Variable | Used by | Key |
|---|---|---|
| `VOICELAYER_API_KEY` | The command line (laptop, CI) | A secret API key |
| `VOICELAYER_WORKER_KEY` | The running worker | A worker key (`vlw_…`) for one environment |

## Step 3: Switch the import

```ts
// before (0.x)
import { defineAgent } from '@voicelayer/sdk';
export default await defineAgent({ name: 'support', prompt: '…' }).start(import.meta.url);

// after (1.0; from 0.8 on the /v1 subpath)
import { defineAgent } from '@voicelayer/sdk/v1';
export default defineAgent({ name: 'support', prompt: '…' });
```

Then check the manifest offline: `npx voicelayer manifest ./agent.ts --check`.

## Step 4: Update models

| 0.x | 1.0 |
|---|---|
| `deepgram.stt({ model })`, `openai.llm({ model })`, … | Unchanged |
| `models.llm: 'gpt-4o'` (every string ran on OpenAI) | `'gpt-4o'` is OpenAI's; `'anthropic/…'` and `'google/…'` run on that provider |
| `wrap(...)`, a plugin instance, a custom provider | Not supported: use a helper or a `provider/model` string |
| `models.vad: silero.vad()` | Delete it: VoiceLayer runs its own |
| `models.turnDetector: livekitTurn.english()` | `models.turnDetection: 'model'` (or `'vad'`) |
| `apiKey` / `baseURL` in a helper | Refused: add the key under Provider accounts |
| `connector.llm({ onQuery })` | `onQuery` on the agent (see [Brains in code](https://docs.vlayers.ai/developers/sdks/brains)) |
| `connector.llm({ url, apiKey })` | A [brain connector](https://docs.vlayers.ai/build/brain-connectors), bound to the agent |

## Step 5: Update ctx and onCall

`say`, `respond`, `ask`, `handoff`, `endCall`, `sendDtmf`, `recordEvent` and `memory` keep their signatures. Each is now a request to VoiceLayer, checked against the `system.*` capability you declare in `capabilities`.

| 0.x | 1.0 |
|---|---|
| `ctx.call`, `ctx.process`, `ctx.channel` | Snapshots, refreshed for each invocation |
| Call state in module variables | `ctx.metadata` (at most 64 KiB), visible to the next handler on any process |
| `ctx.onDtmf(handler)` | A top-level `onDtmf(event, ctx)` (works in 0.8) |
| `ctx.room` | Not available yet |
| `ctx.livekit`, `ctx.adapter`, `ctx.audio.*` | Removed |
| `onCall` | Refused: put the opening line in `greeting` (spoken as written) or in the prompt |

## Step 6: Respect hook deadlines

In 0.x every hook ran in your process with no limit. In 1.0 a hook the call waits on has a deadline and a safe default, so a slow worker never hangs a call:

| Hook | Deadline | If it is late |
|---|---|---|
| A tool's `run` | 10 s | The model gets a failed result |
| `trigger.on` / `trigger.run` | 1 s / 2 s | No match / the turn continues |
| `process.completeWhen` / `onField` / `onComplete` | 2 s / 2 s / 5 s | Not complete / the field stays / it stays complete |
| `handoff.briefing` (a function) | 2 s | VoiceLayer's own briefing |
| `participants.attributes` / `endCall.onEnd` | 1 s / 2 s | No attributes / the call ends |
| `onUtterance`, `onFieldCaptured`, `onCallEnd`, `onDtmf` | None | Notifications, delivered at least once and never waited on |

## Step 7: Opt tools in to real values

In 0.x a code tool got the caller's real values. In 1.0 it gets placeholders unless it opts in, and each release is recorded on the call:

```ts
...defineTool({
  name: 'lookup_resident',
  description: 'Find the resident calling by their phone number',
  input: { phone: 'string' },
  sendRealValues: true, // this tool gets the real number; every other one gets <PII_PHONE_1>
  run: async ({ phone }) => residents.byPhone(phone)
})
```

## Step 8: Private networks, tool bindings and transfers

- VoiceLayer never reaches a private address, so `allowPrivateNetwork` is gone. Call private services from a `defineTool` handler, which runs on your network.
- `toolBindings` is explicit: an empty list means no dashboard tools, and leaving it out is refused.
- `ctx.handoff(to)` only dials destinations the manifest declares or the workspace's transfer policy allows. Ask your workspace owner to allow the numbers your code looks up. See [Transfers](https://docs.vlayers.ai/build/transfers).

## Step 9: Deploy

Create a worker key per environment, then deploy in the fixed order: upload, start the new workers beside the old, publish, let the old drain. See [Voice agents in code](https://docs.vlayers.ai/developers/sdks/voice-agents#step-5-deploy-to-production).

```bash
npx voicelayer deploy ./agent.ts --env prd --no-publish   # upload; writes voicelayer.lock.json
# roll out your workers with the new lock file
npx voicelayer publish --env prd --min-workers 2          # publish once 2 new workers are connected
```

Roll back with `voicelayer publish --manifest <older hash>` while the old workers are still running.

## Test it

```ts
import { assertAgent, invoke, testContext } from '@voicelayer/sdk/testing';
import agent from './agent.js';

assertAgent(agent); // the manifest builds and passes the upload's checks
const ctx = testContext({ call: { callerId: '+15550100' } });
console.log(await invoke(agent, 'tool.lookup_resident', { phone: '+15550100' }, ctx));
```

Then run `npx voicelayer dev ./agent.ts` and call the agent from the Playground.

## Common errors

| Problem | Fix |
|---|---|
| `on_call_unsupported` | Move the opening line to `greeting` or the prompt. |
| `models_vad_unsupported` | Delete `models.vad`. |
| A tool gets `<PII_PHONE_1>` instead of a number | Add `sendRealValues: true` to that tool. |
| A transfer is refused | The destination is not declared or allowed by the transfer policy. |

## When you need the whole call

If you need raw audio, a custom pipeline or your own conversation loop, the call runs in your own runtime on your own LiveKit project, and VoiceLayer hands it over at SIP. The call then leaves VoiceLayer's guard. This is set up on request and is not part of the SDK.

## Next steps

- [Voice agents in code](https://docs.vlayers.ai/developers/sdks/voice-agents)
- [Brains in code](https://docs.vlayers.ai/developers/sdks/brains)
