Development
The VoiceRun CLI ships tools for interactive debugging, automated testing, simulated calls, post-session evaluations, custom metrics, and A/B experiments — covering the full feedback loop on a voice agent.
Debugging#
Launch the Pipeline Debugger — a visual Electron app for real-time testing — against an existing, completed (released) release:
vr debug vr debug --release <RELEASE_ID>
Without --release, the CLI opens an interactive picker containing only completed releases. Use --environment to narrow the picker. Inside a VoiceRun project, passing --release always selects the release-based flow. The release must belong to the agent in the selected lock file: .voicerun/agent.lock by default, or .voicerun/tenants/<name>/agent.lock with --tenant or $VOICERUN_TENANT. Scripted and other non-interactive runs require an explicit release. If release discovery fails, vr debug exits without pushing local code.
Options#
| Flag | Description |
|---|---|
--environment, -e | Filter the release picker by organization environment |
--release | Completed release ID for the active project or tenant agent; required for non-interactive runs |
--tenant, -t | Tenant name under .voicerun/tenants/; defaults to $VOICERUN_TENANT, otherwise no tenant |
--yes, -y | Disable interactive release selection; requires --release |
--headless | Run without GUI — streams JSONL events to stdout, reads text input from stdin, exports session JSON on exit |
--output, -o | Output path for the headless session JSON (auto-generated if omitted) |
--script | Path to a JSON file containing scripted messages; paces entries using agent-response completion signals, with compatibility timeouts |
Interactive Controls#
- Enter — Send a text message to the agent
- Ctrl+C — End the session
The GUI shows Gate, Input, and Output lanes with expanded lanes and event pills for readability; Marks, Metrics, and Pipeline events remain in session exports while their lanes are currently hidden. Visible timing labels truncate to whole milliseconds. Each text_to_speech event expands across its browser playback duration, derived from 24 kHz PCM media and confirmed by the playback mark. If a clear interrupts queued audio, the TTS bar is truncated in red and its details show played and discarded durations. Session exports preserve the playback details on the TTS event without embedding raw audio frames.
Outbound call testing#
Use a phone entrypoint configured for outbound traffic:
vr outbound call <ENTRYPOINT> --to +15551234567 --wait
Use --release <RELEASE_ID> to pin a specific release while testing.
Headless Debugging (CI / Coding Agents)#
--headless runs the debugger without a window, streams JSONL events to stdout, and reads text input from stdin (one message per line). Combine it with --script to drive a fully scripted test run:
vr debug --headless --release <RELEASE_ID> vr debug --headless --release <RELEASE_ID> --output session.json vr debug --headless --release <RELEASE_ID> \ --script test-messages.json -o out.json
The script file is a JSON array of strings. Entries are normally paced by the debugger's response-completion turn_end event rather than turn starts or input-gate changes: the first waits for the greeting boundary, and later entries wait for the preceding response boundary. Compatibility quiet and stall timeouts keep scripts moving if no completion event arrives. For relay sessions, turn_end follows TTS completion, audio forwarding, and playback-mark acknowledgement. The GUI acknowledges the mark after queued audio plays; headless mode discards audio and acknowledges it immediately.
Testing#
Run pytest against your voice agent project:
vr test
vr test validates the project, installs dependencies, and runs pytest.
Options#
| Flag | Description |
|---|---|
--environment, -e | Environment context used while preparing the test |
--verbose, -v | Run pytest in verbose mode |
--coverage, -c | Run with coverage reporting |
--skip-install | Skip dependency installation |
Running Specific Tests#
vr test tests/test_handler.py vr test tests/test_handler.py --verbose --coverage
Passing Arguments to pytest#
Use -- to pass additional arguments directly to pytest:
vr test -- -k "test_greeting" --tb=short
LLM Completions in Tests#
Your handler's LLM completion calls — configure_provider and generate_chat_completion from primfunctions.completions — work under vr test just as they do in a released agent. When you exercise your handler with primfunctions' TestRunner, vr test wires the completions client up automatically.
Availability: completion support in
vr testis currently available to enterprise organizations. If it is not enabled for your organization,vr teststill runs your suite — completion calls just won't succeed.
Bring your own API key
In vr test, completions use your own provider API key — voicerun_managed=True is not available. Your handler reads the key from context.variables, exactly as in a released agent:
from primfunctions.completions import configure_provider, generate_chat_completion # In your handler: configure_provider("openai", api_key=context.variables.get("OPENAI_API_KEY")) response = await generate_chat_completion({ "provider": "openai", "model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Say hello"}], })
Export the key before running the test:
OPENAI_API_KEY=sk-... vr test
Then pass it to the runner as variables, so it reaches context.variables:
import os from primfunctions.test_runner import create_test_runner runner = create_test_runner( handler, variables={"OPENAI_API_KEY": os.environ["OPENAI_API_KEY"]}, )
If your handler configures a provider with voicerun_managed=True, those completions won't run under vr test. To exercise that code path locally, have the handler use a key from context.variables when one is present and fall back to voicerun_managed=True otherwise.
Notes
- Sign in with
vr signinbefore runningvr test. - Because tests use your own API key, completion calls in
vr testcount against that provider account's usage.
Simulating an Agent#
vr simulate runs a Simulation resource (defined in .voicerun/templates/) against the active release for an agent and environment. The API pre-creates N agent sessions (origin: simulation) and drives each one against the live /ws/entrypoint route.
Simulations can be inbound (the default), where the simulator plays a caller dialing the agent, or outbound, where the simulator plays the person who answers a call the agent placed. Outbound simulations use the same context.input_data path as real outbound calls, so they are useful for testing task-driven callback, reminder, or follow-up agents before placing live calls.
vr simulate <ENVIRONMENT> # List available simulations vr simulate <ENVIRONMENT> --name happy-path # Run a single simulation vr simulate my-agent production --name happy-path # Explicit agent
Options#
| Flag | Description |
|---|---|
--name | Simulation resource name. If omitted, lists available simulations and exits |
--release, -r | Pin to a specific release ID (defaults to the latest release for the agent/environment) |
--values, -v | Values file in .voicerun/ to overlay for local listing/preview |
--wait | Block until every spawned session reaches a terminal status (completed or failed) |
--yes, -y | Skip the cost-guardrail confirmation prompt (fires when spec.numberOfSimulations > 10) |
The active release's manifest is authoritative for spec.systemPrompt and spec.numberOfSimulations — local edits not yet released are previewed but not used at run time.
Example Simulation resource#
apiVersion: voicerun/v1 kind: Simulation metadata: name: happy-path spec: systemPrompt: | You are a customer calling support. Ask to reset your password. numberOfSimulations: 5 voice: Aoede # optional Gemini-Live prebuilt voice varietySeed: 12345 # optional reproducible delivery variation
Use varietySeed when you want the same simulated caller delivery — greeting, phrasing, mood, and verbosity — across repeated runs. Omit it for fresh variation every run.
Outbound simulation#
Set spec.direction: outbound when the agent is the caller and the simulated persona is the callee. Use spec.inputData for the dynamic task payload the handler reads from context.input_data.
apiVersion: voicerun/v1 kind: Simulation metadata: name: appointment-reminder spec: direction: outbound systemPrompt: | You are Dana Lee. You just answered your phone and are willing to talk briefly. inputData: customerName: Dana Lee appointmentTime: Tuesday at 3pm reason: confirm upcoming appointment numberOfSimulations: 3
Pre-pickup phases are inbound-only. Outbound simulations model the callee answering immediately, so manifests with both direction: outbound and phases are rejected during validation.
After submitting, the CLI prints the run ID and the spawned session IDs. Inspect with:
vr session info <id>— full session detailvr session transcript <id>— conversation transcriptvr metrics session <id>— custom metrics recorded during the run
Evaluations#
Evaluations are produced by Evaluator resources defined in .voicerun/templates/. Evaluators run server-side after each session completes; the CLI is read-only — define new evaluators by adding a kind: Evaluator document and shipping it via vr release.
vr evaluation list # List evaluations for the project agent vr evaluation list my-agent # Explicit agent vr evaluation list --session SESSION_ID # Only evaluations for one session vr evaluation list --status error --type judge # Filter vr evaluation info <EVALUATION_ID> # Full detail for one evaluation
List Options#
| Flag | Description |
|---|---|
--session, -S | Session ID to get evaluations for |
--status, -s | Filter by status (pending, complete, error, skipped) |
--type, -T | Filter by eval type (judge, extraction, deterministic, script) |
--limit, -l | Page size |
--page, -p | Page number |
--json, -j / --table, -t | Output format |
Example Evaluator resource#
apiVersion: voicerun/v1 kind: Evaluator metadata: name: resolution-judge spec: evalType: judge # judge | extraction | deterministic | script targetFormat: transcript # events | transcript systemPrompt: | Score the session 1-5 on whether the agent resolved the caller's request. responseSchema: {} successCriteria: {} apiProvider: google model: gemini-3.5-flash
vr evaluation info renders deterministic evaluation details, including the assertion predicate and structured match/failure details. Skipped evaluations show their skipReason so precondition failures are auditable without an LLM call.
Custom Metrics#
vr metrics queries custom metrics emitted by agents at runtime. Time-series queries use type-appropriate aggregation server-side.
vr metrics names # List metric names vr metrics names my-agent # Scoped to one agent vr metrics tags # Discover tag keys/values vr metrics tags --metric call_duration # Tags for one metric vr metrics timeseries call_duration \ --start 2026-05-15T00:00:00Z --end 2026-05-16T00:00:00Z --step 1h vr metrics session <SESSION_ID> # All metrics for one session
Most metric subcommands accept --agent/-a to scope results to a single agent (or fall back to agent.lock), and support --json / --table output.
A/B Experiments#
vr experiments reads experiment definitions and results. The agent is resolved from --agent (or positional argument) first, falling back to agent.lock.
vr experiments list # List experiments for the project agent vr experiments describe greeting_style # Variants, conversions, significance vr experiments funnel greeting_style # Per-variant funnel with lift vr experiments timeseries greeting_style \ --metric booking_completed \ --start 2026-05-01T00:00:00Z --end 2026-05-15T00:00:00Z --step 1d
vr experiments describe shows session count, conversion metrics, stop conditions (iteration and confidence thresholds), variant performance, and statistical significance (confidence, p-value).
Development Workflow#
A typical development cycle looks like this:
- Create a new project with
vr init - Write your agent handler in
handler.pyand declare runtime config in.voicerun/templates/deployment.yaml - Render templates with
vr renderto confirm the manifest looks right - Validate with
vr validate(orvr validate -e production) - Unit-test locally with
vr test - Push your code with
vr push - Release to a non-production environment with
vr release staging - Debug interactively with
vr debug --environment staging, or run a script withvr debug --headless --release <RELEASE_ID> --script test-messages.json - Simulate regression scenarios with
vr simulate staging --name happy-path - Cut over by releasing to production and pointing an entrypoint:
vr release production --entrypoint support-line - Observe in production with
vr session list,vr metrics,vr experiments, andvr evaluation list
