> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unpod.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Unpod Voice Infrastructure

> Connect a SuperDialog dialog machine to Unpod voice calls via the unpod SDK.

## How it works

The `unpod` SDK connects your `AgentRunner` to Unpod's voice platform over WebSocket. Unpod handles STT, TTS, telephony, numbers, and recording - your code handles dialog logic using a SuperDialog `DialogMachine` or `LLMAgent`.

<Frame>
  <img src="https://mintcdn.com/unpodai/9OLw2S-v9psMSqik/images/diagrams/superdialog-session-integration.svg?fit=max&auto=format&n=9OLw2S-v9psMSqik&q=85&s=523b00a42e094afe363bba777ddfe445" alt="Animated SuperDialog and Unpod voice integration diagram showing caller speech through STT, user-turn hooks, DialogMachine.turn, agent-turn hooks, TTS, and caller playback." width="1672" height="941" data-path="images/diagrams/superdialog-session-integration.svg" />
</Frame>

Your dialog machine runs inside your process, in the same call as the rest of your agent logic. No separate WebSocket server needed.

***

## Step 1 - Build your dialog machine

```python theme={null}
from superdialog import DialogMachine, PythonTool

def lookup_customer(phone: str) -> dict:
    """Look up customer by phone number."""
    return crm.get_by_phone(phone)

dialog_machine = DialogMachine(
    "kyc.yaml",                              # any format; Playbook engine by default
    llm="anthropic/claude-haiku-4-5",
    tools=[PythonTool.of(lookup_customer)],
)
```

See [SuperDialog Quickstart](/superdialog/quickstart) to generate and save a playbook.

***

## Step 2 - Plug the machine into your session

Assign `dialog_machine` to `ctx.session.dialog_machine` inside your `AgentRunner` entrypoint. The SDK auto-wraps it - no adapter import needed.

```python theme={null}
from unpod import AgentRunner, CallContext
from superdialog import DialogMachine, Flow, PythonTool

def lookup_customer(phone: str) -> dict:
    """Look up customer by phone number."""
    return crm.get_by_phone(phone)

async def handle_call(ctx: CallContext) -> None:
    # Build (or re-use) the agent per call
    machine = DialogMachine(
        "kyc.yaml",
        llm="anthropic/claude-haiku-4-5",
        tools=[PythonTool.of(lookup_customer)],
    )
    ctx.session.dialog_machine = machine   # auto-wrapped by SuperDialogAdapter
    await ctx.session.run()                # blocks until the call ends

AgentRunner(
    entrypoint=handle_call,
    agent_id="kyc-bot",
).start()
```

***

## Step 3 - Create and register your Speech Pipe

```python theme={null}
import asyncio
from unpod import AsyncClient

async def setup():
    async with AsyncClient() as client:
        profiles = await client.voice_profiles.list(language="en")
        pipe = await client.pipes.create(
            name="KYC Bot",
            voice_profile=profiles[0].profile_id,
            agent_id="kyc-bot",    # must match AgentRunner(agent_id=...)
            recording=True,
        )

        numbers = await client.numbers.list()
        if numbers:
            await client.numbers.attach(numbers[0].id, pipe_id=pipe.pipe_id)

        print(f"Speech Pipe: {pipe.pipe_id}, Number: {numbers[0].number if numbers else 'none'}")

asyncio.run(setup())
```

***

## Complete example

```python theme={null}
import asyncio
import os
from superdialog import DialogMachine, PythonTool
from unpod import AgentRunner, AsyncClient, CallContext

# --- One-time setup (run once) ---

async def setup():
    async with AsyncClient() as client:
        profiles = await client.voice_profiles.list(language="en")
        pipe = await client.pipes.create(
            name="KYC Bot",
            voice_profile=profiles[0].profile_id,
            agent_id="kyc-bot",
            recording=True,
        )
        numbers = await client.numbers.list()
        if numbers:
            await client.numbers.attach(numbers[0].id, pipe_id=pipe.pipe_id)

# asyncio.run(setup())   # Run once to provision

# --- Playbook (built with `superdialog generate` or generate_simple_playbook) ---

def lookup_aadhaar(partial: str) -> dict:
    """Look up customer by partial Aadhaar."""
    return crm.lookup_by_partial_aadhaar(partial)

# --- Runner (long-lived process) ---

async def handle_call(ctx: CallContext) -> None:
    machine = DialogMachine(
        "kyc.yaml",
        llm="anthropic/claude-haiku-4-5",
        tools=[PythonTool.of(lookup_aadhaar)],
    )
    ctx.session.dialog_machine = machine
    await ctx.session.run()

AgentRunner(
    entrypoint=handle_call,
    agent_id="kyc-bot",
).start()
```

***

## Using pre-call data in the flow

Data passed when triggering an outbound call (or injected by the platform) is available on `ctx.data`:

```python theme={null}
async def handle_call(ctx: CallContext) -> None:
    machine = DialogMachine("onboarding.yaml", llm="anthropic/claude-haiku-4-5")
    # Inject caller context before the first turn
    if customer_name := ctx.data.get("customer_name"):
        machine.assist(f"The customer's name is {customer_name}. Address them by name.")

    ctx.session.dialog_machine = machine
    await ctx.session.run()
```

***

## Mid-call context injection

Inject system instructions at any point during an active call from your own business logic:

```python theme={null}
async def handle_call(ctx: CallContext) -> None:
    machine = DialogMachine("support.yaml", llm="openai/gpt-4.1-mini")

    @ctx.session.on("user_turn")
    async def _(text: str) -> None:
        sentiment = await analyze_sentiment(text)
        if sentiment == "frustrated":
            machine.assist("The customer seems frustrated. Be empathetic and offer escalation.")

    ctx.session.dialog_machine = machine
    await ctx.session.run()
```

***

## Switching flows mid-call (graph engine)

`switch_flow` is a **graph-engine** feature - construct the machine with a
`FlowSet` and `engine="flow"`:

```python theme={null}
from superdialog import DialogMachine, Flow, FlowSet

async def handle_call(ctx: CallContext) -> None:
    flows = FlowSet({"triage": Flow.load("triage.json"), "billing": Flow.load("billing.json")})
    machine = DialogMachine(flows, llm="openai/gpt-4.1-mini", engine="flow")

    @ctx.session.on("user_turn")
    async def _(text: str) -> None:
        if "billing" in text.lower():
            machine.switch_flow("billing", preserve_memory=True)

    ctx.session.dialog_machine = machine
    await ctx.session.run()
```

<Note>
  On the **Playbook engine** (the default), model the same behaviour with multiple
  `journeys` and `interrupts` inside a single playbook instead of swapping flows.
  See [Playbooks](/superdialog/playbooks).
</Note>

***

## vs. LiveKit / PipeCat adapters

|                           | Unpod Voice (SDK)                | LiveKit adapter           | PipeCat adapter            |
| ------------------------- | -------------------------------- | ------------------------- | -------------------------- |
| **Who handles STT/TTS**   | Unpod                            | You (via LiveKit plugins) | You (via PipeCat services) |
| **Who handles telephony** | Unpod                            | You / LiveKit SIP         | You / Twilio / etc.        |
| **Dialog runs in**        | Your AgentRunner process         | Your LiveKit agent        | Your PipeCat pipeline      |
| **Best for**              | Fastest path to production voice | Full media layer control  | Existing PipeCat pipelines |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="SDK Setup" icon="code" href="/speech-stack/agent-runner">
    AgentRunner constructor, credentials, and lifecycle.
  </Card>

  <Card title="Session Controls" icon="sliders-horizontal" href="/speech-stack/agent-runner">
    say(), transfer(), recording controls during live calls.
  </Card>

  <Card title="Playbooks" icon="book" href="/superdialog/playbooks">
    Author the simple and full playbook formats.
  </Card>

  <Card title="SuperDialog Tools" icon="wrench" href="/superdialog/tools">
    Add HTTP, Python, and MCP tools to your agent.
  </Card>
</CardGroup>
