# Brains in code

> Make your own model or agent framework the brain of a voice call, while VoiceLayer keeps the voice.

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

Kind: How-to guide

## What you'll build

A voice agent whose replies come from your own code: a function, or an agent built with the OpenAI Agents SDK, the Claude Agent SDK or LangGraph. VoiceLayer keeps speech-to-text, text-to-speech, turn-taking, telephony and the transcript. Your worker serves the thinking over the same connection as your tools.

## Prerequisites

- A voice agent in code: follow [Voice agents in code](https://docs.vlayers.ai/developers/sdks/voice-agents) first.
- Node.js 22 or later, and `@voicelayer/sdk` 0.8 or later.
- For a framework brain: that framework installed in your project, at a version you pin.

There are three ways to bring a brain:

| Option | When to use it |
|---|---|
| `onQuery` | Your brain is a function: text in, text out |
| `brain: openaiBrain(…)`, `claudeBrain(…)`, `langchainBrain(…)` | Your brain is a framework agent that needs to transfer, hang up or use your tools |
| A [brain connector](https://docs.vlayers.ai/build/brain-connectors) | Your brain is an HTTP endpoint, possibly behind a firewall, with no SDK code |

## Step 1: Answer with a function

`onQuery` gets the caller's latest words and the text history, and returns the reply. Return a string, or an async iterable of strings to stream: the first sentence is spoken as soon as it arrives.

```ts
import { defineAgent } from '@voicelayer/sdk/v1';

async function* reply(text: string) {
  yield 'Let me check. ';
  yield `You asked about: ${text}.`;
}

export default defineAgent({
  name: 'support',
  prompt: 'You answer for Acme.',
  // the second argument carries the text history (`messages`) and an abort `signal`
  onQuery: (text) => reply(text)
});
```

> **Warning:** Honour `signal`: it aborts when the caller interrupts or the call ends, and a brain that keeps running keeps spending your model tokens.

## Step 2: Use a framework agent with VoiceLayer's tools

`onQuery` is text only. When the brain needs to control the call (transfer, hang up, send keypad digits) or use your dashboard tools, use an adapter. It hands the turn's tools to the framework as its own. Each tool call goes back to VoiceLayer and passes the same checks as a tool VoiceLayer's own model calls.

```ts
import { Agent } from '@openai/agents';
import { defineAgent } from '@voicelayer/sdk/v1';
import { openaiBrain } from '@voicelayer/agents/openai';

export default defineAgent({
  name: 'harbor-brain',
  prompt: 'You are the front desk of Harbor Dental.',
  capabilities: ['system.say', 'system.transfer_call', 'system.end_call'],
  toolBindings: [],

  brain: openaiBrain(new Agent({ name: 'front-desk', model: 'gpt-4o-mini', instructions: 'Short, natural sentences. If someone wants a person, use transfer_call.' }))
});
```

`openaiBrain` comes from [`@voicelayer/agents`](https://docs.vlayers.ai/developers/frameworks/openai): it adds VoiceLayer's tools to your agent for each turn. Install it with your framework:

```bash
npm install @voicelayer/agents @openai/agents@0.3
```

`claudeBrain` (from `@voicelayer/agents/claude`) and `langchainBrain` (from `@voicelayer/agents/langchain`) work the same way for the Claude Agent SDK and LangGraph. Without `@voicelayer/agents`, `openaiAgentsBrain`, `claudeAgentBrain` and `langgraphBrain` in `@voicelayer/sdk/v1` are the lower-level adapters: you turn VoiceLayer's tools into your framework's tools yourself. The SDK does not bundle any framework, so pin the framework's version and try a call in the Playground when you upgrade it.

## What VoiceLayer guards

VoiceLayer guards what goes to your brain, what comes back, and every action it asks for. The caller's phone number reaches your brain as a placeholder. What your brain says passes the output guard before it is spoken. Every tool call is checked. Your brain's own model usage is on your provider account; VoiceLayer does not bill it.

## Test it

`runBrain` runs your brain for one turn, exactly as the worker would, with no network:

```ts
import { runBrain } from '@voicelayer/sdk/testing';
import agent from './agent.js';

const out = await runBrain(agent, { messages: [{ role: 'user', content: 'What time do you open?' }] });
console.log(out.text);
```

End to end, run `npx voicelayer dev ./agent.ts` and talk to the agent in the Playground.

## Common errors

| Problem | Fix |
|---|---|
| The agent keeps talking after an interruption | Pass `signal` to your model call and stop when it aborts. |
| A framework tool call is refused | Declare the matching `system.*` capability in `capabilities`. |
| No tools appear in the framework | The agent declares no capabilities and `toolBindings` is empty, so there is nothing to offer. |

## Next steps

- [Brain connectors](https://docs.vlayers.ai/build/brain-connectors): bring an LLM endpoint with no SDK code.
- [Voice agents in code](https://docs.vlayers.ai/developers/sdks/voice-agents): deploy the agent.
- [Moving from 0.x to 1.0](https://docs.vlayers.ai/developers/sdks/migrating): `connector.llm({ onQuery })` becomes `onQuery` on the agent.
