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

# Python realtime

> Mint a kupe-realtime session with pip install kupe and stream one text turn over WebSocket.

Mint with `client.realtime.sessions.create`, then `client.realtime.connect(session)`. Audio is PCM16 mono **24 kHz**. Model `kupe-realtime`. Web-only — no telephony minutes.

Identify the agent with **name** or **agent\_id**. A new name creates the agent with prompt, greeting, voice, and tools/mcp. Pass **voice** (sanitized name) or **voice\_id** — either one (copy the id from the voice library).

## Terminal

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
pip install kupe
export KUPE_API_KEY=sk-kupe-YOUR_KEY
python - <<'PY'
from kupe import Kupe

client = Kupe()
session = client.realtime.sessions.create(
    name="Priya",
    voice="priya",
    prompt="You collect overdue EMIs. Be warm and brief.",
    greeting="Hi, this is Priya from the bank.",
)
print("ws", session.websocket_url)

with client.realtime.connect(session) as rt:
    rt.send_text("Remind them EMI is due tomorrow.")
    for event in rt:
        if event.type == "response.output_audio_transcript.done":
            print("agent:", event.transcript)
            break
        if event.type == "error":
            print("error:", event)
            break
PY
```

Save the same code as `realtime_turn.py` and run `python realtime_turn.py`.

Existing agent from the console:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
session = client.realtime.sessions.create(
    agent_id="agt_...",
    voice="priya",
)
```

## Events you will see

| `event.type`                            | Meaning                                |
| --------------------------------------- | -------------------------------------- |
| `session.created`                       | Handshake done                         |
| `response.output_audio.delta`           | Base64 PCM16 chunk (`event.delta`)     |
| `response.output_audio_transcript.done` | Final spoken text (`event.transcript`) |
| `response.done`                         | Turn finished, includes usage          |
| `error`                                 | Protocol or runtime error              |

`send_text` sends `conversation.item.create` then `response.create`. Mic path: `rt.append_audio(pcm16_bytes)` at 24 kHz.

## Echo: speakers vs headset

If you play the agent's audio through **open speakers** next to the mic, the
mic records the agent and `append_audio` sends its own voice back as user
speech. The agent then answers itself, and its greeting shows up in the
transcript as a user turn.

Pass `echo_suppression="half_duplex"` to mute the mic while the agent is
still speaking:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
with client.realtime.connect(session, echo_suppression="half_duplex") as rt:
    ...
    rt.append_audio(pcm16_bytes)   # sent as silence while the agent talks
```

| Option             | Use when                                                                                                      | Barge-in                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `"none"` (default) | Headset, phone line, or a browser `getUserMedia({echoCancellation: true})` stream — echo is already cancelled | Yes                                                         |
| `"half_duplex"`    | Open speakers, e.g. a laptop running a terminal script                                                        | **No** — the server does not hear you until the agent stops |

`append_audio` returns `True` when the frame was sent and `False` when it was
muted; `rt.suppressed_frames` counts the muted frames, and
`rt.agent_is_speaking` exposes the gate. Pass `force=True` to bypass it for one
frame. Tune the hold after playback ends with `echo_tail_ms` (default `250`).

A muted frame is still sent, as silence. The server runs streaming VAD and STT
over a continuous audio stream, so sending nothing at all would stall turn
detection and the agent would stop hearing you even after it stopped talking.

<Note>
  The gate advances only while something is iterating the connection, since it
  is driven by `response.output_audio.delta` sizes. Keep your reader loop
  running — a common mistake is reading events only after the mic loop ends.
</Note>

Full event tables: [Realtime WebSocket](/realtime).
