LiveKit target

The livekit target tests a realtime voice agent by joining its LiveKit room as a simulated
participant, whatever the agent was built with: LiveKit Agents, Voiceflow, Pipecat, a custom
participant. Text mode exchanges text streams, so a text-capable agent is tested without
audio: fast, cheap, deterministic. Audio mode speaks to the agent and listens to it the way a
caller would: TTS in, VAD and STT out, interruptions, pauses, background noise, a phone line, and
a stereo WAV of every run.
targets:
voice:
type: livekit
url: ${LIVEKIT_URL} # ws:// or wss://
api_key: "{{secrets.LIVEKIT_API_KEY}}"
api_secret: "{{secrets.LIVEKIT_API_SECRET}}"
room: "support-{{uuid}}" # a new room per run
dispatch: { agent_name: support-agent } # optional explicit agent dispatch
agent_identity_prefix: agent- # how to recognise the agent participant
mode: text # text | audio
data_topic: lk.chat # text-mode transport
join_timeout: 30s
transcript_source: auto # audio: auto | agent | stt
How a turn works
- tamesu mints a token for
tamesu-sim-<uuid>that may join and createroom, with the agent dispatch whendispatch.agent_nameis set, and connects. - It waits up to
join_timeoutfor the agent participant: a participant of kindagent, or one whose identity starts withagent_identity_prefix. Without a prefix, any remote participant counts. No agent is an infrastructure error (exit 3) with a message that says so. - Each user turn is sent as a text stream on
data_topic(lk.chat, what LiveKit Agents read as text input). - The agent’s reply is read from its text streams on
lk.transcription(where LiveKit Agents publish their speech as text) and on the data topic, and from transcription events. The turn is complete when text arrived and the agent’slk.agent.stateattribute wentthinking/speakingand back tolistening, or the stream was markedlk.transcription_final, or no new text arrived for 1.5 s (the fallback for agents that publish no state). An agent that leaves ends the session. - Tool calls: a custom agent can report them as JSON data packets on the topic
tamesu.tool_calls({"name": "...", "args": {...}}or a list);tool_calledassertions read them.
A greeting the agent sends before the first user turn is attached to the first reply.
Audio mode
mode: audio on the target, a test or --mode audio needs speech.tts and speech.stt in
tamesu.yaml (see AI) and a full build (the lite build has no Opus). A turn then goes:
- The simulator’s text is spoken by the TTS model with the persona’s
voice, resampled to 48 kHz, run throughbehaviors.line_qualitywhen set, mixed withbehaviors.background_noiseatbackground_noise_snr_db, and published as the caller’s Opus microphone track, 20 ms at a time. Between turns the track carries the noise alone (or silence). Adtmffrom the simulator is sent as real keypad tones. - tamesu subscribes to the agent’s audio track, decodes it and runs the voice activity
detector (
speech.vad.provider:energyby default, or the neuralsilero; see Speech).time_to_first_audio_msis the caller’s speech end to the agent’s speech start, from the audio itself, never from a model. - The turn is complete when the agent stopped speaking and went back to
listening(itslk.agent.state), or stayed silent for 1.5 s. The agent’s words come from its ownlk.transcriptionstream when it publishes one (transcript_source: agent), else tamesu transcribes the utterance with the STT model (stt);auto(the default) prefers the agent’s.result.jsonrecords which (transcript_sourceevents). - With
behaviors.interrupt_probability, the caller starts its next turn 0.6 to 2 s into the agent’s reply;interruption_stop_msis how long the agent kept talking after the caller started,talk_over_ratiothe overlap over the agent’s total speech.silence_after_turn_msadds a pause before each caller turn;seedmakes both repeatable. audio.wavin the run directory is the whole session: left channel the caller (noise included), right channel the agent, aligned on one clock.
Keys
| Key | Meaning |
|---|---|
url | The LiveKit server, ws:// or wss://. |
api_key, api_secret | As {{secrets.NAME}}; used to sign the join token only. |
token | A ready-made access token instead of api_key and api_secret: a hosted platform’s web call, usually a {{session.NAME}} capture of session.open (the request that creates the call). The participant identity is read from the token unless identity is set. |
identity | The simulated caller’s participant identity; default tamesu-sim-<uuid>, or the token’s. |
session.open | A request made once per run before joining (create the call, mint the token), with capture values as {{session.NAME}}; see Recipes for a hosted platform. |
room | Room name template; default tamesu-{{uuid}}. |
dispatch.agent_name | Requests an explicit agent dispatch when the room is created. |
agent_identity_prefix | Identity prefix of the agent participant; default: a participant of kind agent, else any. |
mode | text or audio. A test’s mode and --mode override it. |
data_topic | The text-input topic; default lk.chat. |
join_timeout | How long to wait for the agent; default 30s. |
transcript_source | Audio: auto (default), agent or stt. With stt the agent’s audio is transcribed as it arrives when the STT model streams (Deepgram, assemblyai/universal, soniox/stt-rt-v3), so the turn waits for its last words only; see stt_ms. |
Metrics
| Metric | Definition |
|---|---|
turn_latency_ms (_p50, _p95, _max) | user text sent → agent turn complete |
time_to_first_token_ms | user text sent → the agent’s first text |
agent_join_ms | room joined → the agent participant present |
turns | the number of agent turns |
time_to_first_audio_ms (_p50, _p95, _max) | audio: caller speech end → agent speech start (VAD) |
interruption_stop_ms (_p50, _p95, _max) | audio: caller talks over → agent audio stops |
talk_over_ratio | audio: overlapping speech / the agent’s speech |
silence_gap_max_ms | audio: the longest dead air while a reply was expected |
agent_speech_ms, user_speech_ms, interruptions | audio: totals for the run |
livekit.max_concurrent_rooms in tamesu.yaml caps how many livekit tests run at once.
Events
result.json records joined, agent_joined, agent_left and disconnected with
timestamps, plus in audio mode audio_published, user_speech_start/_end,
agent_speech_start/_end, interruption, interruption_stopped, dtmf_sent and
transcript_source; the sip target adds the call events to the same timeline.
Leaving the room is bounded: if the LiveKit SDK stalls while it reconnects after a
network blip, each step of the hang-up is abandoned after 5 seconds and a
close_timeout event says which one, so the run still ends and the recording is written.
Example
examples/livekit-agent is
an agent built with the LiveKit Go SDK and Genkit that joins every tamesu-* room, in text mode
or (--audio) speaking and listening, and the suites that test it in both modes. To run it
against livekit-server --dev:
livekit-server --dev &
(cd examples/livekit-agent && go run . --scripted) &
LIVEKIT_API_KEY=devkey LIVEKIT_API_SECRET=secret tamesu run --config examples/livekit-agent/tamesu.llmfake.yaml
tamesu doctor probes every livekit target’s server over HTTP.