persona-voice
The real-time voice trunk for Open Persona — LiveKit WebRTC transport, streaming STT/TTS, turn-taking, and persona-conditioned generation.
persona-voice is the voice layer of the Open Persona
stack: a real-time, full-duplex voice surface that layers sub-second-latency
audio onto the same persona, typed memory, and tier-routed runtime the text
stack uses. The voice persona is the persona — never a thin prompt bypass.
What it is / where it fits
persona-voice runs in-process with persona-core (no separate language,
no cross-process IPC) so the typed-memory stores, audit log, and credits
service compose directly. From V5 it also composes
persona-runtime (prompt builder, router, shared
retrieval) so a voice turn is conditioned exactly like a text turn. The
layering stays acyclic: voice → runtime → core; runtime never imports voice.
WebRTC transport is provided by a LiveKit OSS substrate. The browser joins
a LiveKit room; an in-process agent worker joins the same room and becomes the
persona. The package's HTTP surface is a single endpoint —
POST /v1/voice/token — that mints a short-lived LiveKit AccessToken after
auth, ownership, and credit pre-flights.
Like the rest of the stack, it carries an edition stance (PERSONA_EDITION):
- cloud — the token endpoint verifies the Clerk JWT (today's deployed behavior), scopes DB access by RLS, and meters credits.
- community — no-auth local voice: a fixed local owner, no JWT, unmetered, single-owner ownership.
Features
- V1 — WebRTC transport. LiveKit OSS substrate (
livekit>=1.1), thePOST /v1/voice/tokenAccessToken endpoint, aVoiceRoomfacade (inbound resample to PCM16 mono 16 kHz, outbound 24 kHz publish), aSessionstate machine, and per-user voice-call concurrency viapg_try_advisory_xact_lock. - V2 — Streaming STT. A provider-independent
StreamingSTTprotocol (mirroring the coreChatBackendadapter boundary), a Deepgram Nova-3 backend, and a Silero VAD (ONNX-only) endpointing adapter. - V3 — Streaming TTS. A provider-independent
StreamingTTSprotocol, a Cartesia Sonic backend, per-persona voice as a first-class identity attribute, and mid-utterancecancel()(the barge-in foundation). Emotion-aware delivery (V12): the persona's emotional stance (its N5 feeling-tags) drives Cartesiageneration_configexpressivity so the voice sounds its feeling — bounded by character and restrained, fail-soft to a flat read, with aPERSONA_TTS_EMOTION_ENABLEDBeta kill-switch. - V4 — Turn-taking + barge-in. A four-state conversational machine (Listening / UserSpeaking / Processing / PersonaSpeaking), automatic endpointing, fast-and-discriminating interruption, a cancel watchdog, and full-loop latency attribution — pure-Python decision logic on the V1/V2/V3 seams.
- V5 — Persona / runtime / memory integration. Fills V4's reply-producer seam with real persona-conditioned, tier-routed, streaming, cancellable generation, and writes voice turns to the same episodic store as text (unified memory) — plus a voice latency-routing gate, off-critical-path history compaction, conversational voice tools, and barge-over-honest memory.
- V10 — Capability parity. A call does what a chat can: the persona invokes
tools mid-call and produces artifacts that render on screen in the same
FileRendererPanelchat uses. Tools partition by measured latency — search / diagram run inline;generate_imageruns on a bounded async production lane, decoupled from the audio turn, so the artifact renders the instant it's ready and the persona's "it's on screen" line is floor-gated (a new agent-initiatedLISTENING→PROCESSINGturn that never talks over the user). Rich-output rides the sameRunEventvocabulary as chat (tool_result+activity_*) over the data channel — no parallel format. - V6 — Frontend voice client (in development). Browser-side audio plumbing
- UI in
persona-web; an optional dev agent launcher fires from the token endpoint.
- UI in
- V13 — Voice memory. The persona remembers on a call in both directions.
Read: graph retrieval on voice, routed through K4's wellbeing gate (mirrored
exactly from chat — allowlist subtraction + recent-window lift + care-text
surfacing + recency), executed off the loop with timeout-fail-soft (a slow turn
degrades to a clean memoryless one, never a stall). Gated by
PERSONA_VOICE_GRAPH_MEMORY_ENABLED(default OFF → byte-identical graph-OFF; flip ON after the operator pass ratifies latency + care). Write: completed calls enqueue post-call graph synthesis through the existing K2 background seam (source: voiceprovenance, idempotent) and write episodic chunks at chat parity — so what you say on a call becomes memory the persona knows in chat, and vice versa. - V8 — STT cost gating. Bill Deepgram for the user's speech, not the whole
call. The seam adapter's tee is split — the Silero VAD is always fed (so
barge-in is never starved) while the billed backend leg is gated by
conversational state: the shipped idle-gate streams only the user's turn
(closed during persona-speaking + listening idle), and a shared
ring-buffer-on-reopen flushes the run-up on every gated→open transition so
the barge-in / post-idle first word is never clipped. The actual billed audio
is surfaced as
VoiceLog.stt_streamed_seconds, re-basingstt_total_centsoff streamed seconds (not wall-clock). ~85 % cost reduction on a listen-heavy call; the within-turn onset gate was measured sub-threshold and declined.
Install / run
persona-voice is a uv workspace package. From the repo root:
uv sync # install the workspace
persona-voice is consumed by persona-api; there is no standalone CLI. The
token-issuance app boots from persona_voice.http.app:
uv run uvicorn persona_voice.http.app:create_app --factory --port 8001
You also need a running LiveKit OSS Server (docker compose up -d livekit)
and, for real STT/TTS, a Deepgram key (PERSONA_STT_API_KEY) and a Cartesia
key (PERSONA_TTS_API_KEY). For local web development, packages/api/run-local.sh
boots the api (:8000) and persona-voice (:8001) together.
Test
uv run pytest packages/voice # unit (default)
uv run pytest packages/voice -m integration # live LiveKit + Postgres
uv run pytest packages/voice -m external # live Deepgram / Cartesia
uv run mypy packages/voice/src
uv run ruff check packages/voice
Usage / key surfaces
The token flow. A client that wants a voice call calls
POST /v1/voice/token with a persona_id (and optional conversation_id):
- auth — cloud verifies the Clerk JWT; community returns a fixed local owner with no token required.
- pre-flight — RLS-scoped persona-ownership check + credit gate (both no-ops in community).
- mint — a short-lived LiveKit AccessToken is signed with the LiveKit API secret, granting access to a per-session room.
- response —
{ token, room_name, livekit_url }. The client joins the room over WebRTC; the in-process agent joins the same room as the persona.
GET /v1/voices returns the provider voice catalogue (optionally filtered by
language) for the persona voice-selector, degrading to an empty list when TTS
is unconfigured.
Architecture (brief)
browser ──WebRTC──▶ LiveKit OSS Server ◀──WebRTC── agent worker (in-process)
▲ │
└── POST /v1/voice/token ──▶ persona-voice ──▶ persona-runtime ──▶ persona-core
(auth · ownership · credits · mint) (STT → turn-taking → reply → TTS)
The trunk owns the LiveKit substrate, audio frame plumbing, the streaming STT
and TTS protocols + concrete backends, the session lifecycle, voice-call
concurrency, the persona-conditioned reply producer + unified-memory write, and
the additive VoiceLog. Per-minute billing and the V6 frontend land later.
License
persona-voice is licensed under the MIT License — see LICENSE.
It is true OSI open source: free for any use, including commercial. It
is part of the MIT-licensed Open Persona engine
(persona-core / persona-runtime / persona-voice); the application layer
(persona-api / persona-web) is separately licensed
PolyForm Noncommercial 1.0.0 (source-available, noncommercial).
Links
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file persona_voice-1.1.0.tar.gz.
File metadata
- Download URL: persona_voice-1.1.0.tar.gz
- Upload date:
- Size: 702.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9d3d96be706496183f9652926b83a9c4ae05ac6838e45bed25e91a67a6d3c17
|
|
| MD5 |
05f423b829d4a5c283e274576fd00df0
|
|
| BLAKE2b-256 |
8057cc878a8ae48ca310645966131d185dfea14ad4737f2adb5169e909eeddc3
|
File details
Details for the file persona_voice-1.1.0-py3-none-any.whl.
File metadata
- Download URL: persona_voice-1.1.0-py3-none-any.whl
- Upload date:
- Size: 243.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7df21bd51f08e8c28f8d84d8640a01552cb9db82676db2a6a24fafa1cf5167c
|
|
| MD5 |
7a360f5f3e1c1f6ed2492f059699085a
|
|
| BLAKE2b-256 |
3b918e61050f66958d2a2d933d22a1b9aa2685457ea837201078bece58ed85dd
|