VoiceRun CLI
A command-line interface for building and deploying voice agents on the VoiceRun platform.
Table of Contents
- Installation
- Quick Start
- Authentication
- Global Options
- Commands
- .vrignore
- Project Structure
- Declarative Resources
- Templates
- Values Files
- Configuration
- Environment Variables
- Releasing
Installation
Prerequisites
- Python 3.10 or higher
Install from PyPI
pip install voicerun-cli
Install from Source
git clone https://github.com/VoiceRun/voicerun-cli.git
cd voicerun-cli
pip install -e .
Install a Specific Version
uv tool install voicerun-cli==1.0.0 --force
Install Local Version
To install a local checkout as a CLI tool (useful for testing changes):
uv tool install --force -e .
Quick Start
# 1. Set up the CLI
vr setup
# 2. Create a new voice agent project
vr init my-agent
# 3. Navigate to the project directory
cd my-agent
# 4. Sign in to VoiceRun
vr signin
# 5. Edit handler.py to customize your agent logic
# 6. Validate your project
vr validate
# 7. Deploy to VoiceRun
vr push
# 8. Debug visually or open the web UI
vr debug # Launch Pipeline Debugger
vr open # Open web UI
Authentication
VoiceRun CLI supports three authentication methods:
Browser Authentication (Recommended)
vr signin
Opens your default browser for secure OAuth-style authentication.
Email/Password
vr signin
# Then select option 2 when prompted
API Key
vr signin
# Then select option 3 when prompted
Sign Out
vr signout
Credentials are stored in ~/.voicerun/:
- Cookie:
~/.voicerun/cookie - API Key:
~/.voicerun/apikey - Config:
~/.voicerun/config.json
Global Options
vr --version
Show the installed CLI version and exit.
vr --version
vr -V
vr --update
Update the VoiceRun CLI to the latest version from PyPI and refresh all installed skills and MCP server configurations.
vr --update
vr -U
What it does:
- Upgrades the
voicerun-clipackage to the latest version via pip - Reinstalls skills to the targets selected during
vr setup(e.g., Claude Code, Codex, OpenClaw) - Refreshes MCP server configuration for the targets selected during
vr setup(e.g., Claude Code, Cursor, Windsurf)
Only targets that were explicitly chosen during vr setup are updated — no interactive prompts are shown. If vr setup has not been run, the update will skip skills and MCP configuration.
Example:
# Update everything
vr --update
# Short form
vr -U
vr --context
Run a single command against a named context without switching the active context. This is the one-off equivalent of vr context switch — it changes the API/frontend URLs and per-context credentials/organization used for this invocation only, and leaves current_context in ~/.voicerun/config.json untouched.
# Run against 'staging' for this command only; active context is unchanged afterwards
vr --context staging get agents
# The long --context form may appear anywhere on the line
vr get agents --context staging
| Flag | Description |
|---|---|
--context, -c |
Name of an existing context (see vr context list) to use for this command only. |
Behavior:
- The override is applied via the
VOICERUN_CONTEXTenvironment variable, so an explicitVOICERUN_API_URL(if exported) still wins for the API URL — the context still selects the credentials and effective organization. - An unknown context name aborts with exit code
2before the command runs. - The long
--contextform is recognized in any position. The short-cform is only recognized before the subcommand (vr -c staging get agents), because several subcommands already use-cfor their own options (e.g.vr create phonenumber -c US→--country-code). After a subcommand, use the long form.
Example:
# Push to staging once without leaving your shell switched to it
vr --context staging push
# Equivalent persistent form (changes the active context until switched back):
# vr context switch staging && vr push && vr context switch <previous>
Commands
Agent auto-resolution: Many commands that take an
AGENTargument can infer it automatically when run inside a voicerun project directory (i.e., one with.voicerun/agent.lock). In those cases, theAGENTargument is optional. These commands are marked with[AGENT]in their usage.
Tenants
A project is laid out like a Helm chart: one .voicerun/ directory on disk, many releases. Tenant-specific overlays and release state live under .voicerun/tenants/<name>/.
my-agent/
handler.py, tools.py, … shared function code
.voicerun/ the chart (shared)
agent.yaml
values.yaml shared base values
templates/
deployment.yaml
simulation.yaml
tenants/ per-release overlays + state
millers/
values.yaml tenant base overlay
values.development.yaml per-env overlay (optional)
values.production.yaml
agent.lock per-tenant release state
counter-service/
values.yaml
agent.lock
Project-mutating commands (vr push, vr deploy, vr release, vr render, vr simulate, vr pull, vr test, vr debug, vr open, vr validate) accept:
| Option | Description |
|---|---|
--tenant, -t |
Tenant name (a directory under .voicerun/tenants/). |
VOICERUN_TENANT provides the same override when the flag is omitted.
Precedence: --tenant flag > VOICERUN_TENANT > none (legacy single-agent mode at .voicerun/agent.lock).
Values overlay order when rendering: chart values.yaml → tenant values.yaml → either --values <file> or the env-specific values.<env>.yaml (from the tenant dir when a tenant is set, else from the chart) → --set entries.
Examples:
# Push code and write per-tenant agent.lock under .voicerun/tenants/millers/
vr push --tenant millers
vr release production --tenant millers
# Short form
vr push -t counter-service
# Persist across commands in a shell session
export VOICERUN_TENANT=daves-hot-chicken
vr validate
vr push
vr release production
vr setup
Set up the VoiceRun CLI environment by installing required dependencies.
vr setup
Example:
vr setup
vr signin
Sign in to the VoiceRun API.
vr signin
Prompts for authentication method:
- Web browser (default) - Opens browser for OAuth authentication
- Email/Password - Enter credentials directly
- API Key - Use an API key for authentication
Example:
vr signin
# Select authentication method when prompted
Error behavior: commands distinguish authentication from authorization failures. A 401 prints Unauthorized. Please sign in with 'vr signin'. (missing/invalid credential). A 403 prints the server's actual error — e.g. which permission the credential is missing — since re-authenticating cannot fix a permission gap. Automation that matches on the old blanket Unauthorized message for 403s should match the server error text instead.
vr signout
Sign out and clear stored credentials.
vr signout
vr init
Create a new voice agent project from templates.
vr init [PROJECT_NAME]
Options:
| Option | Description |
|---|---|
--yes, -y |
Skip prompts and use defaults |
--force, -f |
Overwrite existing files |
--template, -t |
Initialize from a remote template (name or ID) |
--var |
Template variable as key=value (can be repeated) |
Example:
# Interactive mode
vr init
# Non-interactive with project name
vr init my-agent --yes
# Initialize from a remote template
vr init my-agent --template customer-support
# Initialize from template with variables
vr init my-agent -t customer-support --var greeting="Hello" --var language="en"
vr validate
Validate project structure and configuration.
vr validate
Options:
| Option | Description |
|---|---|
--environment, -e |
Environment to render templates for |
--quiet, -q |
Only output errors |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Validation Checks:
handler.pyexists and containsasync def handler().voicerun/directory structure is correctagent.yamlis valid with required fields- YAML syntax in all template files
- Deployment spec contains only allowed fields
Example:
# Validate with specific environment
vr validate -e production
# Quiet mode (errors only)
vr validate -q
vr pull
Pull agent code from the VoiceRun server to your local machine.
vr pull [AGENT_ID]
Arguments:
| Argument | Description |
|---|---|
AGENT_ID |
Agent ID to pull (not required if inside a voicerun project) |
Options:
| Option | Description |
|---|---|
--output, -o |
Output directory (defaults to agent name) |
--yes, -y |
Skip confirmation prompt |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Behavior:
- Inside a voicerun project: Pulls code using the agent ID from
agent.lockand overwrites local files - Outside a voicerun project: Requires an agent ID, creates a new directory with the pulled code and initializes
.voicerun/agent.lock
Example:
# Pull inside an existing project
vr pull
# Pull a specific agent into a new directory
vr pull 550e8400-e29b-41d4-a716-446655440000
# Pull into a custom directory
vr pull 550e8400-e29b-41d4-a716-446655440000 -o ./my-agent
# Skip confirmation
vr pull --yes
vr push
Deploy your agent code to the VoiceRun server.
vr push
Options:
| Option | Description |
|---|---|
--name |
Name for the function version |
--new, -n |
Create a new function version instead of updating |
--yes, -y |
Skip confirmation prompts |
--agent, -a |
Agent ID (or a name that is unique in the organization) to push to. Overrides the lock file; use it when several agents share the name in agent.yaml. A shared name is rejected here too. |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Behavior:
- Validates the project before pushing
- Packages code as a zip archive (excludes
.venv,__pycache__,.git, etc. and files matching.vrignore) - Creates or updates
agent.lockwith agent and function IDs - With a lock file: pushes update the recorded function in place (
--newcreates a new function version) - Without a lock file for the current context (fresh clone, CI runner): the push looks for an agent in the organization whose name matches
agent.yaml(GET /v1/agents?filters[name]=…; against an API without that filter it walks every page of the list instead). Exactly one match binds to it — the confirmation reads "Bind to existing agent '' () and push a new version?",--yesanswers it — and the lock is written after the push. No match creates a new agent, as before. Several matches stop with an error listing their IDs; pick one with--agent <id>. --agentpointing at the agent the lock already tracks is an ordinary in-place update; pointing at a different agent pushes a new version there and rewrites the lock (the confirmation names the agent and warns first).- Tenant pushes (
--tenant) skip the name lookup: every tenant shares the chart'sagent.yaml, so its name identifies the chart rather than a tenant's agent.
Lock files are per context (agent.lock for the default context, agent.<context>.lock otherwise) and can be left out of version control: a clone with none binds by name on its first push.
Example:
# Push with confirmation
vr push
# Push without confirmation
vr push --yes
# Create a new version
vr push --new --name "v2.0"
# Several agents share agent.yaml's name: choose one explicitly
vr push --agent 550e8400-e29b-41d4-a716-446655440000
vr deploy
Deploy the latest pushed function version to an environment.
Legacy.
vr deploytargets agent-scoped environments from before the IaC migration. It prints a deprecation warning on every run. New agents should usevr release <ENVIRONMENT>(org-scoped environment + release + entrypoint model) instead.
vr deploy <ENVIRONMENT>
Arguments:
| Argument | Description |
|---|---|
ENVIRONMENT |
Target environment name or ID (e.g., development, production). If not specified, you'll be prompted to select. |
Options:
| Option | Description |
|---|---|
--yes, -y |
Skip confirmation prompt |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Prerequisites:
- Run
vr pushfirst to create an agent and generateagent.lock
What it does:
- Validates the project structure
- Loads the agent ID and function ID from
agent.lock - Resolves the target environment
- Deploys the function version to the environment
Example:
# Deploy interactively (select environment from list)
vr deploy
# Deploy to development environment
vr deploy development
# Deploy to production without confirmation
vr deploy production --yes
vr release
Create a release tying an agent to an org-scoped environment at a point in time. The latest release for a given (agent, environment) pair is implicitly the active one — no isActive flag or manual version bump required.
vr release [AGENT] <ENVIRONMENT>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
ENVIRONMENT |
Org-scoped environment name or ID |
Options:
| Option | Description |
|---|---|
--route NAME |
Update an existing route on --entrypoint, preserving matchers. Requires --entrypoint; omitted means the catch-all. |
--function, -f |
Function ID to release (defaults to agent.lock's functionId) |
--values, -v |
Values file to overlay (e.g. prod.yaml). Resolved relative to the tenant overlay dir when --tenant is set, otherwise relative to .voicerun/. Without this flag, only the chart base + (tenant base, if any) are loaded. |
--entrypoint, -e |
Entrypoint name or ID. After the release is created, point this entrypoint at the new release in the same command. |
--weight |
Partial-rollout weight (1–100) for the new release on --entrypoint. Without it, the entrypoint's release list is replaced with the new release at weight 100 (full cutover). With it, the new release is appended at the given weight, leaving existing entries unchanged for canary / staged rollouts. |
--no-wait |
Dedicated releases only: return as soon as the release is created instead of following the asynchronous build/deploy to completion. The build continues server-side; watch it with vr get releases (STATE column) or vr describe release. When waiting, a degraded state is tolerated for up to 90 s (a cold pod reports it while its image pulls) before the release is declared unhealthy; failed ends the wait immediately. |
--yes, -y |
Skip the confirmation prompt when the project has unpushed local changes. |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
--json, -j |
Print the created release as JSON on stdout; all progress, prompts, and errors (including agent/environment resolution failures) go to stderr. The exit code still reports the outcome, and the document is printed on failure too so a caller always has the release ID. |
--json document (keys are camelCase, as the API returns them):
{
"release": { "id": "…", "agentId": "…", "environmentId": "…", "functionId": "…", "status": "released", "operationalStatus": "running", "manifest": [ … ] },
"entrypoint": { "id": "…", "name": "support-line", "releases": [ { "releaseId": "…", "weight": 100 } ] }
}
entrypoint is null when --entrypoint was not given. On a failed build the release is whatever the API last reported (status/operationalStatus say why) and the exit code is 1.
Prerequisites:
- Run
vr pushfirst to generateagent.lock, or pass--functionexplicitly. Without either, the command refuses to create a release: an agent can be resolved by name fromagent.yaml, a function cannot, and a release with no function has nothing to run. - The target environment must already exist (see
vr create environment) - If
.voicerun/templates/exists, Helm must be installed —vr releaserenders the full manifest at release time so the runtime has an immutable snapshot. No templates directory means no manifest is attached. If templates exist but Helm isn't on PATH, the command fails loudly rather than creating a silent release.
Unpushed-changes check:
Before creating the release, the CLI compares the project's current checksum against the one stored in agent.lock at last vr push. On mismatch, it warns that the release will run the previously-pushed function (not what's on disk) and prompts to confirm. Pass --yes for scripted/CI use. The check is silent for projects whose agent.lock predates this feature (no checksum field).
Secret resolution:
Rendered manifests may contain $VR_SECRET:<name> placeholders (e.g. in values files or template output). These are not embedded in the release payload — they're resolved against organization/environment secrets at session start, so secrets never round-trip through the release record.
Example:
# Inside a voicerun project (agent + function from agent.lock)
vr release production
# Explicit agent
vr release my-agent production
# Pin a specific function version
vr release my-agent production --function 550e8400-e29b-41d4-a716-446655440000
# Overlay a specific values file
vr release my-agent production --values prod.yaml
# Full cutover: route the entrypoint at the new release
vr release my-agent production --entrypoint support-line
# Staged 25% canary on the entrypoint, leaving prior releases in place
vr release my-agent production --entrypoint support-line --weight 25
# CI: capture the new release ID from the JSON document. Capture first, then
# inspect — inside `$(a | b)` the exit status would be jq's, not vr's, and a
# failed release still prints the document.
if ! OUT=$(vr release production -v values.production.yaml --yes --json); then
echo "release $(jq -r .release.id <<<"$OUT") did not become healthy" >&2
exit 1
fi
RELEASE_ID=$(jq -r .release.id <<<"$OUT")
# Skip the unpushed-changes prompt (CI)
vr release my-agent production --yes
vr debug
Launch the Pipeline Debugger — a visual Electron app for real-time agent testing. Can also place outbound phone calls directly from the CLI.
vr debug behaves differently depending on how the agent is deployed:
- Legacy (v1) agents — pushes your local code (unless
--skip-push) and debugs the per-agentdebugenvironment. - Declarative (v2) agents (deployed with
vr releaseinto org-level environments) — does not push or release. It starts a debug session against an existing, completed (released) release. Choose the release with--release, narrow the interactive picker with-e <environment>, or pick from the list. Non-interactive runs (--yes, piped stdin, or--script) require--release. Passing--releasealways selects this flow.
vr debug
Options:
| Option | Description |
|---|---|
--track NAME |
Stamp a track on the direct debug session; does not select an entrypoint route. |
--skip-push, -s |
(v1) Skip pushing code, use the existing deployment. Implied for v2 agents. |
--environment, -e |
(v1) Environment name (default: debug). (v2) Filter the release picker to this org environment. |
--release |
(v2 only) Release ID to debug. When omitted you pick from existing releases; required in non-interactive runs. |
--canary / --no-canary |
Super admins only (API returns 400 otherwise). Pin to the inactive/canary color or active color; omit for live routing. v2 release debugging only; rejected for legacy environment debugging, --outbound, or outside a project. |
--yes, -y |
Non-interactive: never prompt. For v2 agents this requires --release. |
--headless |
Run without GUI (for CI / coding agents). Streams JSONL events to stdout, reads text input from stdin, and exports session JSON on exit |
--output, -o |
Output file path for headless session JSON (auto-generated if omitted) |
--script |
Path to a JSON file with scripted messages (array of strings). Each message is sent one-per-turn automatically: the client waits for the agent's turn_end (greeting/response fully spoken, input accepted again) before sending the next line, so multi-turn scripts never interrupt an in-flight agent turn |
--outbound |
Place an outbound phone call instead of launching the debugger (v1 agents only) |
--to-phone-number |
Destination phone number in E.164 format (required with --outbound) |
--from-phone-number |
Caller ID / originating phone number in E.164 format (optional) |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Super-admin active/inactive deployment testing
This is operator documentation for super admins and their coding agents. Keep it in the CLI repository and bundled skills; do not publish it to the public documentation site.
--release chooses the agent release. --canary and --no-canary independently choose the agents service deployment that runs it. production is an organization environment, not a deployment color; selecting it or passing --release alone does not pin active/inactive routing.
| Intent | Flag | API request | Routing |
|---|---|---|---|
| Intentionally test the inactive/canary deployment | --canary |
canary: true |
Pin to the currently inactive color |
| Intentionally test the active deployment | --no-canary |
canary: false |
Pin to the currently active color |
| Exercise normal live routing | Neither | canary omitted |
Health failover and traffic splitting apply |
Omission is not the same as --no-canary. The web ReleaseDebugger and AgentDebugger initialize canaryRouting to false and explicitly send canary: false for super admins when their toggle is off. To reproduce that default in the CLI, supply --no-canary; to reproduce the toggle being on, supply --canary.
From the agent project containing .voicerun/agent.lock, use an existing completed release belonging to that agent. For a tenant, add --tenant <TENANT> to select its lock. Replace <RELEASE_ID> with the same release ID in both commands. Release-based debugging does not push code or create a release.
# One interactive GUI session intentionally on the inactive deployment
vr debug --release <RELEASE_ID> --canary
# The same agent release intentionally on the active deployment
vr debug --release <RELEASE_ID> --no-canary
# Coding agent: repeat the same conversation against both deployments
vr debug --headless --release <RELEASE_ID> --canary --script test-messages.json --output inactive-session.json
vr debug --headless --release <RELEASE_ID> --no-canary --script test-messages.json --output active-session.json
test-messages.json is a non-empty JSON array of strings, for example ["Hello", "Please help me reset my password", "Goodbye"]. Headless mode requires Node.js, writes JSONL events to stdout and status messages to stderr, and exports the session JSON to --output. Non-interactive runs require --release.
The authenticated CLI context must belong to a super-admin user. Organization admins and service accounts are not super admins. Either explicit flag otherwise returns HTTP 400 with canary is not a supported parameter. Do not remove the flag and retry when an intentional active/inactive test fails: that would change the requested routing.
Explicit pins use CallParametersService.build → BlueGreenRoutingService.getRoutedAgentsUrl, exactly as release debugging in the web app does. Both pins bypass health failover and the traffic-split roll; an unhealthy pinned deployment can fail to connect instead of switching colors. Active/inactive is resolved when routing is selected, not a fixed blue/green color or build version. Promotion between runs can change which deployment a flag selects.
Supported paths and debugger differences:
- Legacy agents: Web
AgentDebuggersupports authenticatedenvironmentId/functionIdcanary requests. The CLI's legacy launcher does not carry that override, so both flags are rejected before pushing or launching. Use a declarative (v2) release.--environmentmay still filter the v2 release picker; it does not enable canary routing for legacy agents. - Real phone calls:
vr debug --outboundrejects either flag. Simulations withmode: telephonyor itspstnalias also reject them because actual telephony ingress determines routing. - Direct simulations: Use the examples below to pin the entire run.
SimulationController.createselects one agents URL and shares it across every session, whereas each debugger session selects its own URL. Even without a flag, simulations retain their existing once-per-run routing selection. - Authorization and error order:
SimulationController.createusesctx.user?.isAdmin, equivalent toAgentSessionController.debug'sauth.isSuperAdmin. Simulations reject unauthorized overrides before loading resources; the debug endpoint checks ownership, request shape, and billing first. Requests invalid for multiple reasons can report different first errors. - Routing configuration: Relay releases use
config.getRelayUrlinstead of agents blue/green routing. Without a configured cluster for the region/tier,CallParametersService.buildfalls back toconfig.getAgentsUrlByRegion. Local overrides can supersede the selected URL:config.localAgentsBrowserUrlfor release debugging andconfig.localAgentsInternalUrlfor simulations. These paths do not establish that a production active/inactive deployment was exercised; use an agents release and an API environment configured for that cluster without local URL overrides.
For a released direct Simulation resource, use the same release, environment, and simulation name to compare deployments:
vr simulate production --name happy-path --release <RELEASE_ID> --canary --wait
vr simulate production --name happy-path --release <RELEASE_ID> --no-canary --wait
Run each command separately; each creates a new simulation run and reports its run/session IDs for inspection with vr session info <SESSION_ID>. The simulation API must support CreateSimulationRequest.canary (companion API PR #1147). The CLI validates local mode; the API validates the released manifest. Canary selection is a request override, not a Simulation.spec field. With mode: direct, either direction: inbound or direction: outbound can be pinned; this differs from a real vr debug --outbound call.
Prerequisites:
- Node.js must be installed for the debugger (not required for
--outboundmode) - For v2 agents: at least one completed (
released) release must exist (vr release <environment>)
What it does:
- Legacy (v1) agent in a project: Pushes your local code to the VoiceRun server (unless
--skip-push), installs debugger dependencies if needed, and launches the Pipeline Debugger connected to your agent'sdebugenvironment. - Declarative (v2) agent in a project: Resolves a completed release (via
--release, the-eenvironment filter, or an interactive picker), starts an authenticated debug session against it, and connects the debugger to that release. No code is pushed or released. If release discovery fails, the command exits instead of falling back to a legacy push. - Outside a project: Opens the Pipeline Debugger with no prefilled agent info, so you can connect manually.
- Headless mode: Runs a lightweight Node client (no Electron / browser) instead of the GUI — connects to the agent, streams JSONL events to stdout, and accepts text input on stdin (one message per line). It self-terminates when the session ends, on a scripted run's completion, or on stdin close / idle, and writes the session JSON on
SIGINT/SIGTERM. Useful for CI pipelines and coding agents. For v2 agents, pass--release(headless is non-interactive when stdin is piped). - Playback timeline: The GUI shows Gate, Input, and Output lanes, with expanded lanes and event pills for readability. It expands each
text_to_speechevent across its browser playback duration, derived from 24 kHz PCM media and confirmed by the playback mark. Visible timing labels truncate to whole milliseconds. Acleartruncates the TTS bar in red and reports discarded audio. Marks, Metrics, and Pipeline events remain in session exports but their lanes are currently hidden; exports preserve playback details on the TTS event without embedding raw audio frames. - Outbound mode: Pushes your code and places an outbound phone call to the specified number. Returns a
telephonyCallIdfor tracking. Does not launch the debugger GUI. (v1 agents only.)
Example:
# Legacy (v1): push and debug (inside a project)
vr debug
# Legacy (v1): debug without pushing (use existing deployment)
vr debug --skip-push
vr debug -s
# Legacy (v1): debug a specific environment
vr debug -e staging
# Declarative (v2): pick a release interactively
vr debug
# Declarative (v2): narrow the picker to one environment
vr debug -e staging
# Declarative (v2): debug a specific release (works non-interactively / in CI)
vr debug --release rel_abc123
# Super admin: test the canary, then compare against the active color
vr debug --release rel_abc123 --canary
vr debug --release rel_abc123 --no-canary
# Open the debugger standalone (from any directory)
vr debug
# Headless mode (CI / coding agents)
vr debug --headless # v1
vr debug --headless --release rel_abc123 # v2
vr debug --headless --output session.json
# Run a scripted conversation
vr debug --script test-messages.json
vr debug --headless --script test-messages.json -o results.json
vr debug --headless --release rel_abc123 --script test-messages.json -o results.json # v2
# Place an outbound phone call (v1 agents)
vr debug --outbound --to-phone-number "+15551234567"
# Outbound call with caller ID and custom environment
vr debug --outbound --to-phone-number "+15551234567" --from-phone-number "+15559876543" -e production
# Outbound call without pushing (use existing deployment)
vr debug --outbound --to-phone-number "+15551234567" --skip-push
vr open
Open the web interface to your deployed agent.
vr open
Options:
| Option | Description |
|---|---|
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Requires agent.lock to exist (created after vr push; per-tenant when --tenant is set). Opens your default browser to the agent's function page.
vr render
Render VoiceRun templates and display the output.
vr render
Options:
| Option | Description |
|---|---|
--values, -f |
Custom values file path |
--set, -s |
Override values (can be used multiple times) |
--output, -o |
Output format: yaml (default) or json |
--quiet, -q |
Only output templates, no validation messages |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Example:
# Render with default values
vr render
# Render with specific values file
vr render -f .voicerun/values.yaml
# Override specific values
vr render --set stt.model=deepgram-flux --set environment=production
# Output as JSON
vr render -o json
vr test
Run tests for the voice agent project.
vr test
Options:
| Option | Description |
|---|---|
--environment, -e |
Environment to fetch secrets from (e.g., development, production) |
--verbose, -v |
Run pytest in verbose mode |
--coverage, -c |
Run with coverage reporting |
--skip-install |
Skip dependency installation |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
What it does:
- Validates project structure
- Installs project dependencies (unless
--skip-install) - Optionally fetches secrets from the specified environment (legacy agents with environments; requires an owner or admin of the agent's organization)
- Runs pytest with the specified options
Example:
# Run all tests
vr test
# Run specific test file
vr test tests/test_handler.py
# Run tests with development secrets
vr test -e development
# Run with verbose output and coverage
vr test -v -c
# Skip dependency installation
vr test --skip-install
# Pass extra arguments to pytest
vr test -- -k "test_name" -x
vr simulate
Run a Simulation resource defined in .voicerun/templates/ against an agent in an environment. The API pre-creates N agent sessions (origin: simulation) and the voicerun-simulations service drives each one as a Gemini-Live caller against the active release for (agent, environment).
vr simulate [AGENT] <ENVIRONMENT> --name <SIMULATION_NAME>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
ENVIRONMENT |
Org-scoped environment name or ID |
Options:
| Option | Description |
|---|---|
--track NAME |
Stamp direct simulation sessions. |
--name |
Simulation resource name to run. If omitted, lists available simulations from .voicerun/templates/ and exits. |
--release, -r |
Pin the run to a specific release ID. Defaults to the latest release for (agent, environment). |
--canary / --no-canary |
Super admins only (API returns 400 otherwise). Pin every session to the inactive/canary color or active color; omit for live routing. Direct mode only; rejected for telephony and its pstn alias. |
--values, -v |
Values file to overlay for local listing/preview (e.g. prod.yaml). Resolved relative to the tenant overlay dir when --tenant is set, otherwise relative to .voicerun/. The server uses the release's manifest regardless. |
--wait |
Block until every spawned session reaches a terminal status (completed or failed), printing aggregate progress. Default: return immediately with the run ID. |
--yes, -y |
Skip the cost-guardrail confirmation prompt that appears when spec.numberOfSimulations exceeds 10, or always for telephony runs. |
--entrypoint, -e |
Telephony mode only: ID of the phone entrypoint to call. Defaults to the one inbound-capable phone entrypoint routing to the release; the API returns 400 listing candidates if there is none or more than one. The API rejects it for direct specs and beside a spec toNumber; the CLI warns locally but still submits, since its local render can differ from the released manifest. |
--tenant, -t |
Tenant name under .voicerun/tenants/ (see Tenants). Defaults to none or $VOICERUN_TENANT. |
Prerequisites:
.voicerun/templates/must exist and contain at least onekind: Simulationdocument with aspec.systemPrompt.- Helm must be installed —
vr simulaterenders the templates locally before submitting (runvr setupif missing). - The target environment must already have an active release for the agent (created via
vr release) — simulations call into the live/ws/entrypointroute. The active release's manifest is authoritative forspec.systemPromptandspec.numberOfSimulations; local edits not yet released are previewed but not used at run time.
Super-admin routing override: --canary sends canary: true; --no-canary sends canary: false. Without either flag, the request omits canary and preserves live routing. This selects the agents service color; --release still selects the agent release. Both explicit values bypass the traffic split and health failover. The CLI rejects either flag when the local simulation has mode: telephony or mode: pstn; the API also validates the released manifest before creating sessions. See Super-admin active/inactive deployment testing for scripted comparisons, the web debugger default, unsupported paths, and routing configuration limits.
vr simulate production --name happy-path --canary --wait
vr simulate production --name happy-path --no-canary --wait
Simulation resource shape:
kind: Simulation
metadata:
name: happy-path
spec:
systemPrompt: |
You are a customer calling support. Ask to reset your password.
numberOfSimulations: 5
Telephony mode (real calls): set mode: telephony to place a REAL call to the agent's phone entrypoint instead of a direct WebSocket, exercising its actual telephony ingress. The API picks how the call is placed from the entrypoint's telephony provider: an SBC-fronted entrypoint (voicerun-sbc) is dialed over SIP through the platform SBC (no carrier, no phone charge); any other provider (Twilio/Telnyx) is dialed over the PSTN via Twilio — a real, billable call through the full carrier path (codec transcoding, jitter, DTMF-over-PSTN). mode: pstn is a deprecated alias with identical behavior. The entrypoint called is chosen at run time: by default the API picks the one inbound-capable phone entrypoint routing to the release, and returns 400 listing the candidates if there is none or more than one; pass --entrypoint <entrypoint-id> to choose explicitly. Alternatively spec.toNumber (E.164) dials an explicit carrier number over the PSTN regardless of provider (--entrypoint and toNumber cannot be combined). spec.sipHeaders (string map, X- names only) adds custom SIP headers to the INVITE on SBC-originated calls and is ignored for PSTN; the API always adds X-VoiceRun-Simulation: <session id> itself. Each run is a real call — billable over the PSTN, not over the SBC, and the CLI cannot tell which from the manifest — so the CLI always confirms before submitting (skip with --yes). Not compatible with direction: outbound, and --wait is not supported (the agent-side session is created by its telephony ingress, so the run's session records track the simulated caller only).
kind: Simulation
metadata:
name: telephony-smoke
spec:
systemPrompt: |
You are a customer calling support. Ask to reset your password.
mode: telephony
numberOfSimulations: 1
Example:
# List available simulations in the project
vr simulate production
# Run a single simulation against an environment
vr simulate production --name happy-path
# Explicit agent + pin to a specific release
vr simulate my-agent production --name happy-path --release 550e8400-e29b-41d4-a716-446655440000
# Overlay a values file (for local preview) and block until done
vr simulate my-agent production --name happy-path --values prod.yaml --wait
# Bulk run, skipping the cost-guardrail prompt
vr simulate production --name stress-test --yes
# Telephony run, calling a specific phone entrypoint
vr simulate production --name telephony-smoke --entrypoint ep_01H...
Output:
Prints the run ID and the spawned session IDs. Inspect individual runs with:
vr describe session <id>— full session detailvr metrics session <id>— custom metrics recorded during the runvr experiments funnel <experiment>— when simulations are tied to an experiment
vr evaluation
View evaluations produced by Evaluator resources defined in .voicerun/templates/. Evaluators are scored or extracted 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 an agent, or for a specific session.
vr evaluation list [AGENT]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
Options:
| Option | Description |
|---|---|
--session, -S |
Limit to a single session ID. Pulls from /v1/sessions/:id/evaluations instead of the agent-scoped endpoint. |
--status, -s |
Filter by status (pending, complete, error, skipped). skipped rows are evaluations whose precondition predicate didn't match the session — no LLM call was made. |
--type, -T |
Filter by eval type (judge, extraction, deterministic, script). deterministic rows assert on the derived session view without an LLM. |
--limit, -l |
Page size. |
--page, -p |
Page number. |
--json, -j |
Output as JSON. |
--table, -t |
Output as table (default for interactive terminals). |
Example:
# List evaluations for the current project agent
vr evaluation list
# Limit to a single session
vr evaluation list --session 550e8400-e29b-41d4-a716-446655440000
# Filter to failed judge runs
vr evaluation list --status error --type judge
# Audit which sessions were skipped (precondition not met)
vr evaluation list --status skipped
# Audit cheap deterministic evals (no LLM cost)
vr evaluation list --type deterministic
vr evaluation info
Show full detail for a single evaluation, including ruling/extraction payload, success criteria, and token usage.
vr evaluation info <EVALUATION_ID>
Arguments:
| Argument | Description |
|---|---|
EVALUATION_ID |
Evaluation UUID |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON. |
--table, -t |
Output as rich panels (default for interactive terminals). |
Example:
vr evaluation info 550e8400-e29b-41d4-a716-446655440000
Output includes:
- Status, eval type, trigger, evaluator ID, session ID, model
- For judge evaluations: success flag, ruling, success criteria
- For extraction evaluations: the extracted payload
- For deterministic evaluations: success flag, assertion predicate, and structured details (
{ matched: true }on success, or{ matched: false, failedPath, reason }on failure) - For skipped evaluations: a "Skipped" panel showing the precondition reason — no per-type result panel is rendered, since the eval never ran
- Prompt and completion token usage (zero for deterministic and skipped rows)
- Timestamps
vr session
View sessions and session data for an agent.
vr session list
List an agent's sessions, optionally filtered by status, direction, or creation time.
vr session list [AGENT] [OPTIONS]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock when run inside a voicerun project) |
Options:
| Option | Description |
|---|---|
--track NAME |
Filter sessions by the exact track name; also available as vr get sessions --track NAME. |
--status, -s |
Filter by session status |
--direction, -d |
Filter by call direction |
--from |
Include sessions created at or after this timezone-aware RFC 3339 timestamp |
--to |
Include sessions created at or before this timezone-aware RFC 3339 timestamp |
--limit, -l |
Limit the number of results |
--page, -p |
Page number |
--json, -j |
Output as JSON |
--table, -t |
Output as a table |
Both bounds are inclusive and filter on the session's creation time. Both accept timezone-aware RFC 3339 timestamps such as 2026-07-01T09:30:00-04:00 or 2026-07-01T13:30:00Z. When both are provided, --from must be earlier than --to.
The table shows each session's status, ID, release ID, direction, origin, duration, and creation time. JSON output (release_id, environment_id, …) is the machine-readable source of truth; vr session info <SESSION_ID> shows the same Release ID for a single session, which is the quickest way to confirm which release served a call.
Examples:
# Sessions created during July 2026 (UTC)
vr session list my-agent \
--from 2026-07-01T00:00:00Z \
--to 2026-08-01T00:00:00Z
# One-sided range inside a voicerun project
vr session list --from 2026-07-23T09:00:00-04:00 --json
vr get
List resources by type.
kubectl-style filter: every
vr get <resource>command takes an optional positional argument that filters the same table down to a single matching row —vr get agents my-agentis the one-row form ofvr get agents. Most resources match by name or ID;phonenumbersandassignmentsalso match by phone number or friendly name;releasesmatch by ID only (no name field). When the filter doesn't match anything, the command exits non-zero with a clear "not found" message.
Output format: every
vr get <resource>command accepts-j/--json(emit{"data": [...]}with full, untruncated field values — the machine-parseable source of truth) and-t/--table(force the human-readable table). With neither flag, the format is auto-detected: table on an interactive terminal, JSON when output is piped, redirected, or consumed by a coding agent. One exception: onvr get entrypoints,-tis taken by--type, so use the long form--tablethere.vr get variables --jsonreturns{"data": {"organizationVariables": [...], "environmentVariables": [...]}}, whereenvironmentVariablesisnullwhen the agent-environment scope was not queried. In JSON mode the legacy-deprecation warning onagentenvironmentsis suppressed so stdout stays parseable.
vr get agents
List agents (or describe one as a single-row list when name or ID is given).
vr get agents [NAME_OR_ID]
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Optional: filter to a single agent by name or ID |
Displays a table with agent name, ID, description, voice, and transport.
vr get functions
List all functions for a specific agent.
vr get functions [AGENT]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
Example:
# Inside a voicerun project (uses agent.lock)
vr get functions
# Explicit agent
vr get functions my-agent
vr get functions 550e8400-e29b-41d4-a716-446655440000
vr get environments
List all org-scoped environments.
vr get environments
Example:
vr get environments
vr get agentenvironments
List legacy per-agent environments. For org-scoped environments, use vr get environments.
Legacy. Per-agent environments are deprecated in favor of org-scoped environments + releases. This command prints a deprecation warning but otherwise runs normally (reads are never blocked).
vr get agentenvironments [AGENT]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
Example:
# Inside a voicerun project (uses agent.lock)
vr get agentenvironments
# Explicit agent
vr get agentenvironments my-agent
vr get releases
List releases, optionally filtered by agent and/or environment. The latest release for a given (agent, environment) pair is implicitly the active one.
vr get releases
Options:
| Option | Description |
|---|---|
--agent, -a |
Filter by agent name or ID |
--environment, -e |
Filter by environment name or ID |
Example:
# List all releases
vr get releases
# Filter by agent
vr get releases --agent my-agent
# Filter by agent and environment
vr get releases --agent my-agent --environment production
The table resolves AGENT, ENVIRONMENT, and FUNCTION columns to their human-readable names; rows whose underlying entity has been deleted fall back to the bare ID.
vr get entrypoints
List all org-scoped entrypoints (phone, web, native).
vr get entrypoints
Options:
| Option | Description |
|---|---|
--type, -t |
Filter by entrypoint type (phone, web, or native) |
Example:
# List all entrypoints
vr get entrypoints
# Only phone entrypoints
vr get entrypoints --type phone
vr get variables
List variables that agents can read via context.variables.get(NAME) at runtime. Always lists organization-level variables. If an agent and environment are both resolvable (via --agent/agent.lock and --environment), also lists that environment's variables.
vr get variables
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (defaults to agent.lock) |
--environment, -e |
Environment name or ID (required to list agent variables) |
--org |
Only list organization-level variables |
Example:
# List organization variables
vr get variables --org
# Inside a voicerun project, list both org + environment variables
vr get variables --environment production
# Explicit agent
vr get variables --agent my-agent --environment production
Masked values render as •••••••• in the output.
vr get secrets
List organization-level secrets used by evaluators. Agent-scoped secrets are not yet supported for runtime injection — use vr create variable with --masked when you need a value readable via context.variables.get().
vr get secrets
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (for listing existing legacy agent-scoped secret records) |
Example:
# List organization secrets
vr get secrets
vr get phonenumbers
List organization phone numbers.
vr get phonenumbers
Displays a table with phone number, friendly name, ID, area code, and country code.
vr get telephony
List organization telephony providers.
vr get telephony
Displays a table with provider name, ID, and provider type.
vr get assignments
List all phone number assignments for an agent.
vr get assignments [AGENT]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
Example:
# Inside a voicerun project (uses agent.lock)
vr get assignments
# Explicit agent
vr get assignments my-agent
Displays a table with phone number ID, environment ID, assignment ID, and created at timestamp.
vr get templates
List available agent templates.
vr get templates
Displays a table with template name, ID, category, description, and public/private status.
vr get organizations
List the organizations you belong to (or describe one as a single-row list when name or ID is given). Aliases: vr get organization, vr get orgs.
vr get organizations [NAME_OR_ID]
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Optional: filter to a single organization by name or ID |
Example:
# List every org you belong to
vr get organizations
# Describe one by name or ID
vr get organizations "Prim Voices"
vr get organizations 8e77d4bd-2b70-4e10-8e60-18ea552eb117
Displays a table with organization name, ID, your role, and a ✓ in the DEFAULT column for your current org context. Backed by the user-scoped /v1/me/organizations endpoint, so it works for any signed-in user (not just admins).
vr describe
Show detailed information about a specific resource.
Output format: every
vr describe <resource>command accepts-j/--json(emit{"data": {...}}with full, untruncated field values — panels width-truncate long values like UUIDs, JSON never does) and-t/--table(force the human-readable panels). With neither flag, the format is auto-detected: panels on an interactive terminal, JSON when output is piped, redirected, or consumed by a coding agent. In JSON mode the legacy-deprecation warning onagentenvironmentis suppressed so stdout stays parseable.
vr describe assignment
Show detailed information about a phone number assignment.
vr describe assignment [AGENT] <ASSIGNMENT_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
ASSIGNMENT_ID |
Assignment UUID |
Example:
# Inside a voicerun project (uses agent.lock)
vr describe assignment 550e8400-e29b-41d4-a716-446655440000
# Explicit agent
vr describe assignment my-agent 550e8400-e29b-41d4-a716-446655440000
Output includes:
- Assignment details (ID, environment ID, phone number ID)
- Timestamps
vr describe agent
Show detailed information about an agent.
vr describe agent [NAME_OR_ID]
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Agent name or UUID (defaults to agent.lock) |
Example:
# Inside a voicerun project (uses agent.lock)
vr describe agent
# Explicit agent
vr describe agent my-agent
Output includes:
- Basic information (ID, name, description)
- Voice & transport configuration
- Organization details and creator attribution (user ID or service account ID for agents created by an organization service account)
- Debug & tracing settings
- Timestamps
vr describe function
Show detailed information about a function.
vr describe function [AGENT] <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
NAME_OR_ID |
Function name, display name, or UUID |
Example:
# Inside a voicerun project (uses agent.lock)
vr describe function main-handler
# Explicit agent
vr describe function my-agent main-handler
Output includes:
- Basic information (ID, name, display name)
- Code configuration (language, strategy, multifile)
- Code preview (if available)
- Test data (if configured)
- Timestamps
vr describe environment
Show detailed information about an org-scoped environment.
vr describe environment <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Environment name or UUID |
Example:
vr describe environment production
Output includes:
- Basic information (ID, name, description)
- Organization ID
- Timestamps
vr describe agentenvironment
Show detailed information about a legacy per-agent environment. For org-scoped environments, use vr describe environment.
Legacy. Per-agent environments are deprecated in favor of org-scoped environments + releases. This command prints a deprecation warning but otherwise runs normally (reads are never blocked).
vr describe agentenvironment [AGENT] <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
NAME_OR_ID |
Environment name or UUID |
Example:
# Inside a voicerun project (uses agent.lock)
vr describe agentenvironment production
# Explicit agent
vr describe agentenvironment my-agent production
Output includes:
- Basic information (ID, name, phone number, debug flag)
- Speech-to-text (STT) configuration
- Recording settings
- Error handling, audio processing, VAD, and advanced settings
- Timestamps
vr describe release
Show detailed information about a release.
vr describe release <RELEASE_ID>
Arguments:
| Argument | Description |
|---|---|
RELEASE_ID |
Release UUID |
Example:
vr describe release 550e8400-e29b-41d4-a716-446655440000
Output includes:
- Release ID, status, and timestamps
- Agent, environment, and function rendered as
Name (id)(or just the ID when the entity has been deleted) - Created-by user ID or service account ID (releases pushed by an organization service account, e.g. from CI, are attributed to the service account), and organization ID
vr describe entrypoint
Show detailed information about an entrypoint.
vr describe entrypoint <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Entrypoint name or UUID |
Example:
vr describe entrypoint support-line
Output includes:
- Basic information (ID, name, type, status, organization ID)
- Environment, ordered named routes, matcher expressions, overlap warnings, and each release’s percentage within its route. An entrypoint with no releases configured is flagged as a misconfiguration.
- Per-type
configblock (e.g., phone number details, allowed origins, client ID)
vr describe variable
Show detailed information about a variable. Searches organization-level variables by default, and agent environment variables when --agent and --environment are provided.
vr describe variable <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Variable name or UUID |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (defaults to agent.lock) |
--environment, -e |
Environment name or ID (required to search agent variables) |
--org |
Only search organization-level variables |
Example:
vr describe variable ANTHROPIC_API_KEY
vr describe variable ANTHROPIC_API_KEY --agent my-agent --environment production
Masked values render as •••••••• in the output.
vr describe secret
Show detailed information about an organization-level secret (evaluator-only).
vr describe secret <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Secret name or UUID |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (searches organization secrets first, then agent secrets) |
Example:
vr describe secret MY_API_KEY
vr describe phonenumber
Show detailed information about a phone number. Looks up by ID, the E.164 phone number itself, or the friendly name.
vr describe phonenumber <PHONE_NUMBER_OR_ID>
Arguments:
| Argument | Description |
|---|---|
PHONE_NUMBER_OR_ID |
Phone number ID, the E.164 phone number, or the friendly name |
Example:
# By UUID
vr describe phonenumber 550e8400-e29b-41d4-a716-446655440000
# By the phone number itself
vr describe phonenumber +15551234567
# By friendly name
vr describe phonenumber "Support Line"
Output includes:
- Phone number, friendly name, area code, country code
- Telephony provider ID
- Timestamps
vr describe telephony
Show detailed information about a telephony provider.
vr describe telephony <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Telephony provider name or UUID |
Example:
vr describe telephony my-twilio
Output includes:
- Provider name, type, and ID
- Organization ID
- Timestamps
vr describe organization
Show detailed information about an organization you belong to. Aliases: vr describe org, vr describe orgs, vr describe organizations.
vr describe organization <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Organization name or UUID |
Example:
vr describe organization "Prim Voices"
vr describe organization 8e77d4bd-2b70-4e10-8e60-18ea552eb117
Output includes:
- Organization name and ID
- Your role in the org (
owner,admin,member) - Whether this org is your current context
- Your membership ID
Backed by the user-scoped /v1/me/organizations endpoint, so it only resolves orgs you're a member of.
vr create
Create resources.
vr create assignment
Assign a phone number to an agent environment.
vr create assignment [AGENT] <ENVIRONMENT> <PHONE_NUMBER_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
ENVIRONMENT |
Environment name or ID |
PHONE_NUMBER_ID |
Organization phone number ID |
Options:
| Option | Description |
|---|---|
--configure |
Configure the phone number with the telephony provider after assignment |
Example:
# Inside a voicerun project (uses agent.lock)
vr create assignment production 550e8400-e29b-41d4-a716-446655440000
# Explicit agent
vr create assignment my-agent production 550e8400-e29b-41d4-a716-446655440000
vr create assignment my-agent production 550e8400-e29b-41d4-a716-446655440000 --configure
vr create environment
Create an org-scoped environment. Agents are released into it via vr release.
vr create environment <NAME>
Arguments:
| Argument | Description |
|---|---|
NAME |
Environment name (unique within the organization) |
Options:
| Option | Description |
|---|---|
--description, -d |
Description of the environment |
Example:
vr create environment production
vr create environment staging --description "Pre-production testing"
vr create agentenvironment
Create a legacy per-agent environment. For org-scoped environments, use vr create environment.
Legacy. Per-agent environments are deprecated in favor of org-scoped environments + releases. This command prints a deprecation warning and requires an interactive
y/Nconfirmation before creating. Pass--yes/-yto skip it (required in non-interactive shells, which otherwise abort).
vr create agentenvironment [AGENT] <NAME>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
NAME |
Environment name |
Options:
| Option | Description |
|---|---|
--stt-model |
Speech-to-text model (e.g. nova-3) |
--stt-language |
STT language code (e.g. en) |
--stt-endpointing |
STT endpointing timeout in ms |
--recording/--no-recording |
Enable or disable call recording |
--yes, -y |
Skip the legacy-deprecation confirmation |
Example:
# Inside a voicerun project (uses agent.lock)
vr create agentenvironment production
# Explicit agent
vr create agentenvironment my-agent production
# With STT and recording options
vr create agentenvironment my-agent staging --stt-model nova-3 --stt-language en --recording
# Skip the confirmation (e.g. in CI)
vr create agentenvironment production --yes
vr create entrypoint
Create or update an entrypoint that routes traffic to one or more releases. Entrypoints are org-scoped and polymorphic — pick the subcommand matching the channel (phone, web, or native).
Entrypoints belong to one environment and contain ordered named routes. Each route has optional exact matchers and a weighted release pool. The first matching route wins and its name is saved as session.track. Different agents may share an entrypoint; all releases must belong to its environment.
vr create entrypoint {phone|web|native} <NAME> [OPTIONS]
Common options (phone, web, native):
| Option | Description |
|---|---|
--release, -r |
Release routing target as <releaseId>[:<weight>] (required, repeatable). Weight defaults to 1 when omitted. Duplicate release IDs are rejected. |
To split traffic, pass --release multiple times with explicit weights (e.g. --release rel-a:70 --release rel-b:30). The weights don't need to sum to any particular number — shares are computed relative to the total. Creation requires --environment NAME_OR_ID and a non-empty release pool.
vr create entrypoint phone
vr create entrypoint phone <NAME> --environment <NAME_OR_ID> --release <ID>[:<WEIGHT>] --phone-number <E164>
Options:
| Option | Description |
|---|---|
--environment, -e |
Environment name or ID (required). |
--phone-number, -p |
E.164 phone number (e.g. +15551234567) (required) |
--telephony, -t |
Telephony provider ID |
--friendly-name |
Friendly name for the number |
--area-code |
Area code |
--country-code |
Country code |
--direction |
inbound, outbound, or both |
Example:
# Single release
vr create entrypoint phone support-line --environment production \
--release 550e8400-e29b-41d4-a716-446655440000 \
--phone-number "+15551234567" --friendly-name "Support" --direction inbound
# 70/30 A/B split across two releases
vr create entrypoint phone support-line --environment production \
--release rel-a:70 --release rel-b:30 \
--phone-number "+15551234567"
# Environment must be explicit
vr create entrypoint phone support-line --environment production --release rel-a --phone-number "+15551234567"
vr create entrypoint web
vr create entrypoint web <NAME> --environment <NAME_OR_ID> --release <ID>[:<WEIGHT>] --allowed-origin <ORIGIN>
Options:
| Option | Description |
|---|---|
--environment, -e |
Environment name or ID (required). |
--allowed-origin, -o |
Allowed origin (repeat for multiple) (required) |
Example:
vr create entrypoint web widget --environment production \
--release rel-latest \
-o https://acme.com -o https://app.acme.com
vr create entrypoint native
vr create entrypoint native <NAME> --environment <NAME_OR_ID> --release <ID>[:<WEIGHT>] --client-id <ID>
Options:
| Option | Description |
|---|---|
--environment, -e |
Environment name or ID (required). |
--client-id, -c |
Client ID for the native SDK (required) |
--client-secret-secret-id |
Secret ID holding the client secret |
--allowed-api-key-id |
Allowed API key ID (repeat for multiple) |
Example:
vr create entrypoint native mobile-app --environment production \
--release rel-latest \
--client-id mobile-ios --client-secret-secret-id 550e8400-e29b-41d4-a716-446655440000
vr update
Use vr update for resource mutations that don't fit create/delete flows, plus the existing global vr --update flag for upgrading the CLI itself.
vr update entrypoint
Update an existing entrypoint's routing or metadata in place. Passing --release replaces only the catch-all release pool; conditional routes are preserved.
vr update entrypoint <ENTRYPOINT_ID> [--release ... ] [--name ...] [--status ...] [--direction ...]
Options:
| Option | Description |
|---|---|
--environment |
Set the environment; all referenced releases must belong to it. Required to repair unresolved legacy routing. |
--route SPEC |
Replace all routes in order (repeatable). |
--set-route SPEC |
Create or replace a named route with explicit matchers and a non-empty release pool. |
--move-route NAME |
Move an existing route. |
--before NAME, --after NAME |
Position a set/move operation relative to another route. |
--remove-route NAME |
Remove a conditional route; the catch-all cannot be removed. |
--promote NAME |
Copy this route's release pool to the catch-all, then remove the named route. |
--expected-routing-version N |
Require the previously observed positive integer routingVersion for routing/environment edits. Fail if it changed; never retry with a newer version. |
--json, -j |
On success, print {"data": <updated entrypoint>}, including routes and routingVersion, instead of human-readable output. |
--release, -r |
Replace only the catch-all release pool with these <releaseId>[:<weight>] entries (repeatable); preserve conditional routes. |
--name |
Rename the entrypoint. |
--status |
active or disabled. |
--direction |
inbound, outbound, or both. Phone entrypoints only; the rest of the phone config (number, telephony provider, …) is preserved. |
Provide at least one routing, environment, or metadata option.
For automation, capture routingVersion with vr get entrypoints NAME --json, then pass it explicitly:
vr update entrypoint support-line --promote canary --expected-routing-version 7 --json
The command rejects a mismatch before writing and sends the same supplied version as If-Match on the PATCH, protecting against a change after its read too. Without this option, it retains the existing behavior of using the version read within the command. The option requires a routing or environment update, not a metadata-only edit; it fails against APIs without a valid routingVersion. A conflict exits nonzero without retrying. Only consume the JSON success document when the exit code is zero; diagnostics and errors (including DEBUG request logging) go to stderr in JSON mode, leaving stdout for the success document.
For conditional rollback, save the successful response's data.routes and data.routingVersion: the API returns the snapshot produced by that conditional update, rather than a later read. Restore the complete pre-update route list using repeated --route arguments and pass the successful response's data.routingVersion as --expected-routing-version. A subsequent routing change makes that rollback fail instead of overwriting the newer edit. --release alone restores only the catch-all pool, not a conditional route removed by promotion. Never substitute a newly read version after a conflict without inspecting the new state and deciding whether the edit is still appropriate. These preconditions protect one entrypoint per request; they do not make changes across entrypoints atomic. A transport failure can leave the result unknown, so inspect routing before attempting recovery.
Changing the direction is validated server-side against other entrypoints on the same phone number — the API rejects the update with a 409 if the new direction conflicts with an existing entrypoint's claim on that number.
Example:
# Shift a 50/50 split to 100% on the new release
vr update entrypoint 550e8400-e29b-41d4-a716-446655440000 \
--release rel-new:100
# Disable an entrypoint without deleting it
vr update entrypoint 550e8400-e29b-41d4-a716-446655440000 --status disabled
# Allow an inbound number to also originate outbound calls
vr update entrypoint 550e8400-e29b-41d4-a716-446655440000 --direction both
vr create entrypoint update still works as a deprecated alias and prints a warning directing you to vr update entrypoint.
vr update telephony
Update a telephony provider's name or credentials. The provider is resolved by name or ID. Credential flags are merged into the stored credentials server-side, so any field you don't pass is left unchanged — you can rotate a single secret without re-entering the rest.
vr update telephony <NAME_OR_ID> [--name ...] [credential flags]
Options:
| Option | Description |
|---|---|
--name |
Rename the telephony provider. |
--account-sid |
Twilio Account SID |
--api-key-sid |
Twilio API Key SID |
--api-key-secret |
Twilio API Key Secret |
--api-key |
Telnyx API Key |
--connection-id |
Telnyx Connection ID (TeXML application) used for outbound calls |
Credential flags are validated against the provider's type — passing a Twilio flag to a Telnyx provider (or vice versa) is an error. At least one option must be provided.
Example:
# Backfill the outbound Connection ID on an existing Telnyx provider
vr update telephony "My Telnyx" --connection-id 2606xxxxxxxxxxxxxx
# Rotate the Telnyx API key without touching other credential fields
vr update telephony "My Telnyx" --api-key NEW_KEY
vr update agent
Update an agent's telephony provider — the provider used when the agent itself places outbound calls. Both the agent and the telephony provider are resolved by name or ID; reserved platform IDs (voicerun, voicerun-sbc) are passed through as-is.
vr update agent <NAME_OR_ID> --telephony <TELEPHONY_NAME_OR_ID>
vr update agent <NAME_OR_ID> --clear-telephony
Options:
| Option | Description |
|---|---|
--telephony, -t |
Telephony provider name or ID for agent-initiated outbound calls. Pass an empty string (--telephony "") to clear. |
--clear-telephony |
Clear the agent's telephony provider so it falls back to the platform default. |
Example:
# Route the agent's outbound calls through the org's Telnyx provider
vr update agent my-agent --telephony "My Telnyx"
# Revert to platform telephony
vr update agent my-agent --clear-telephony
vr create variable
Create a variable that an agent handler can read via context.variables.get(NAME). By default creates a variable scoped to a specific agent environment; pass --org to create an organization-level variable visible to every agent in the org.
Use --masked for sensitive values like API keys — the value is still delivered to the agent at runtime, but hidden from CLI listings and UI reads.
vr create variable <NAME> <VALUE>
Arguments:
| Argument | Description |
|---|---|
NAME |
Variable name (use the same key in context.variables.get(NAME)) |
VALUE |
Variable value |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (defaults to agent.lock) |
--environment, -e |
Environment name or ID (required unless --org) |
--org |
Create an organization-level variable instead of an agent environment variable |
--masked |
Mask the value in listings and describe output |
Examples:
# Create an environment variable inside a voicerun project
vr create variable ANTHROPIC_API_KEY sk-ant-... --environment production --masked
# Explicit agent
vr create variable ANTHROPIC_API_KEY sk-ant-... --agent my-agent --environment production --masked
# Organization-level variable available to every agent
vr create variable SUPPORT_EMAIL help@acme.com --org
In the Python handler:
def handler(context):
api_key = context.variables.get("ANTHROPIC_API_KEY")
...
vr create secret
Create an organization-level secret for use by evaluators. Agent-scoped secrets are not yet supported for runtime injection — if you need a value readable via context.variables.get() at runtime, use vr create variable with --masked instead.
vr create secret <NAME> <VALUE>
Arguments:
| Argument | Description |
|---|---|
NAME |
Secret name |
VALUE |
Secret value |
Options:
| Option | Description |
|---|---|
--agent, -a |
(Unsupported) Rejects with a redirect to vr create variable --masked |
Example:
# Create an organization secret for evaluator usage
vr create secret MY_API_KEY sk-1234567890
vr create phonenumber
Create or purchase a phone number for the organization.
vr create phonenumber <TELEPHONY_ID>
Arguments:
| Argument | Description |
|---|---|
TELEPHONY_ID |
Telephony provider ID |
Options:
| Option | Description |
|---|---|
--purchase |
Purchase a new number from the telephony provider |
--area-code, -a |
Area code for the phone number |
--country-code, -c |
Country code (defaults to US) |
--friendly-name, -n |
Friendly name for the phone number |
--phone-number, -p |
Phone number to register (without --purchase) |
Example:
# Purchase a new phone number
vr create phonenumber <telephony-id> --purchase --area-code 415
# Register an existing phone number
vr create phonenumber <telephony-id> --phone-number "+15551234567" --friendly-name "Support Line"
vr create template
Create a reusable template from the current project files. All text files in the project directory are collected and uploaded. Binary files are skipped automatically. Files matching patterns in .vrignore are excluded.
vr create template <NAME>
Arguments:
| Argument | Description |
|---|---|
NAME |
Template name |
Options:
| Option | Description |
|---|---|
--description, -d |
Template description |
--category, -c |
Template category |
--public/--private |
Make template public or private to org (default: public) |
Example:
# Create a public template
vr create template my-agent-template
# Create a private template with metadata
vr create template my-agent-template --private -d "Customer support agent" -c "support"
vr create telephony
Create a telephony provider for the organization. Missing options will be prompted interactively.
vr create telephony
Options:
| Option | Description |
|---|---|
--name, -n |
Name for the telephony provider |
--provider-type, -p |
Provider type (e.g., twilio, telnyx) |
--account-sid |
Twilio Account SID |
--api-key-sid |
Twilio API Key SID |
--api-key-secret |
Twilio API Key Secret |
--api-key |
Telnyx API Key |
--connection-id |
Telnyx Connection ID (TeXML application) used for outbound calls |
Example:
# Interactive mode
vr create telephony
# Non-interactive with all options
vr create telephony --name "My Twilio" --provider-type twilio \
--account-sid AC123 --api-key-sid SK123 --api-key-secret secret123
# Telnyx requires both the API key and the outbound Connection ID
vr create telephony --name "My Telnyx" --provider-type telnyx \
--api-key KEY123 --connection-id 2606xxxxxxxxxxxxxx
vr delete
Delete resources.
vr delete assignment
Delete a phone number assignment from an agent.
vr delete assignment [AGENT] <ASSIGNMENT_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
ASSIGNMENT_ID |
Assignment ID |
Example:
# Inside a voicerun project (uses agent.lock)
vr delete assignment 550e8400-e29b-41d4-a716-446655440000
# Explicit agent
vr delete assignment my-agent 550e8400-e29b-41d4-a716-446655440000
vr delete agent
Delete an agent.
vr delete agent <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Agent name or ID |
Example:
vr delete agent my-agent
vr delete function
Delete a function from an agent.
vr delete function [AGENT] <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
NAME_OR_ID |
Function name, display name, or ID |
Example:
# Inside a voicerun project (uses agent.lock)
vr delete function main-handler
# Explicit agent
vr delete function my-agent main-handler
vr delete environment
Delete an org-scoped environment.
vr delete environment <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Environment name or ID |
Example:
vr delete environment staging
vr delete agentenvironment
Delete a legacy per-agent environment. For org-scoped environments, use vr delete environment.
Legacy. Per-agent environments are deprecated in favor of org-scoped environments + releases. This command prints a deprecation warning and requires an interactive
y/Nconfirmation before deleting. Pass--yes/-yto skip it (required in non-interactive shells, which otherwise abort).
vr delete agentenvironment [AGENT] <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (defaults to agent.lock) |
NAME_OR_ID |
Environment name or ID |
Options:
| Option | Description |
|---|---|
--yes, -y |
Skip the legacy-deprecation confirmation |
Example:
# Inside a voicerun project (uses agent.lock)
vr delete agentenvironment staging
# Explicit agent
vr delete agentenvironment my-agent staging
# Skip the confirmation (e.g. in CI)
vr delete agentenvironment staging --yes
vr delete release
Delete a release by ID.
vr delete release <RELEASE_ID>
Arguments:
| Argument | Description |
|---|---|
RELEASE_ID |
Release UUID |
Example:
vr delete release 550e8400-e29b-41d4-a716-446655440000
vr stop release
Stop a dedicated release's pods (scale to zero — standby). The built
image and infrastructure stay, so starting again takes seconds, not a
rebuild. Only releases whose Deployment spec sets dedicated: true have
managed pods; the API rejects shared-sandbox releases.
vr stop release <RELEASE_ID> [--force]
Arguments:
| Argument | Description |
|---|---|
RELEASE_ID |
Release UUID |
Options:
| Option | Description |
|---|---|
--force |
Stop even if the release is serving traffic. Without it, the API refuses (409) when the release is the newest for its environment or an active entrypoint routes to it. |
Prints the derived operational status (stopping, then standby once the
state monitor confirms the pods are gone). vr get releases shows the
state in the STATE column; vr describe release shows state, intent, and
pod counts.
vr start release
Start a dedicated release that is in standby. Pods come back in seconds — the release image was kept when it was stopped.
vr start release <RELEASE_ID>
Arguments:
| Argument | Description |
|---|---|
RELEASE_ID |
Release UUID |
vr restart release
Rolling restart of a dedicated release's pods (same image, fresh processes). Rejected while the release is in standby — start it instead.
vr restart release <RELEASE_ID>
Arguments:
| Argument | Description |
|---|---|
RELEASE_ID |
Release UUID |
vr delete entrypoint
Delete an entrypoint by name or ID.
vr delete entrypoint <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Entrypoint name or ID |
Example:
vr delete entrypoint support-line
vr delete variable
Delete a variable. Searches organization-level variables first, then agent environment variables when --agent and --environment are both provided.
vr delete variable <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Variable name or ID |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (defaults to agent.lock) |
--environment, -e |
Environment name or ID (required to delete agent variables) |
--org |
Only search organization-level variables |
Example:
vr delete variable SUPPORT_EMAIL --org
vr delete variable ANTHROPIC_API_KEY --agent my-agent --environment production
vr delete secret
Delete an organization-level secret (evaluator usage). Searches organization secrets first, then legacy agent-scoped secret records.
vr delete secret <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Secret name or ID |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID |
Example:
vr delete secret MY_API_KEY
vr delete phonenumber
Delete or release an organization phone number.
vr delete phonenumber <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Phone number ID, phone number, or friendly name |
Options:
| Option | Description |
|---|---|
--release |
Release the number back to the telephony provider |
Example:
# Delete from VoiceRun only
vr delete phonenumber "+15551234567"
# Release back to the telephony provider
vr delete phonenumber "+15551234567" --release
vr delete telephony
Delete an organization telephony provider.
vr delete telephony <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Telephony provider name or ID |
Example:
vr delete telephony my-twilio
vr context
Manage API contexts for different environments (development, staging, production). A context bundles an API URL, a frontend URL, and an optional pinned organization — all stored on the context definition in ~/.voicerun/config.json. Pinning an organization is how you work across multiple orgs in the same environment: create one context per org with the same URLs.
To run a single command against another context without switching, use the global
vr --contextflag instead ofvr context switch.
Session-scoped switching. When the environment identifies a terminal tab or coding-agent session — the first of VOICERUN_SESSION_KEY, CLAUDE_CODE_SESSION_ID, ITERM_SESSION_ID, TERM_SESSION_ID, WT_SESSION — vr context switch applies only to that session, so concurrent sessions on the same machine (e.g. several Claude Code sessions) never clobber each other's context. Resolution order for every command:
--contextflag /VOICERUN_CONTEXTenv var (this invocation only)- The session's own switch, if it made one
- The machine-wide
current_contextin~/.voicerun/config.json
Session entries live in ~/.voicerun/sessions/ (one small JSON file per session, created only by an explicit switch) and clean themselves up: entries idle for more than 7 days are pruned on the next switch, with a 100-entry cap as a backstop. A pruned entry is harmless — the session simply resolves the machine-wide context again (or, if that is unset, gets the guidance error and re-switches). vr context current shows how the context was resolved: local (session), staging (override), or default (global).
Coding-agent subagents share the top-level session. Claude Code exposes the same
CLAUDE_CODE_SESSION_IDto subagents as to the main session, so subagents share the session's pin — avr context switchinside one subagent changes the context for the parent and all sibling subagents. Subagents that need their own context should pass--context <name>on every command instead of switching.
vr context list
List all available contexts and show the current one. Columns include each context's API/frontend URLs and its pinned organization (blank when unpinned).
vr context list
vr context current
Show the current context with API and frontend URLs, including how it was resolved — (session), (override), or (global). When the context has been unset, prints Context: not set with guidance instead.
vr context current
vr context switch
Switch to a different context. Plain vr context switch <NAME> picks the narrowest scope available: inside an identifiable session (see Session-scoped switching) it pins the context for that session only and leaves the machine-wide context untouched; outside one (no session env var — e.g. a bare SSH shell or CI) it sets the machine-wide context, exactly as it always has.
--global always sets the machine-wide context — the default resolved by every session without its own pin, including future ones — and clears the running session's own pin, so the global choice takes effect where you ran it instead of being shadowed.
vr context switch <NAME>
vr context switch <NAME> --global
Options:
| Option | Description |
|---|---|
--global, -g |
Set the machine-wide current context even when running inside an identifiable session. Also clears the session's own pin so the global choice takes effect immediately in that session. |
Examples:
# Terminal tab A — pins 'local' for this tab only
vr context switch local
# Terminal tab B, concurrently — pins 'staging'; tab A is unaffected
vr context switch staging
# Either tab — sets the machine-wide default for everything un-pinned
# (new tabs, scripts, cron) and re-points this tab at it too
vr context switch development --global
# Check which scope your context came from
vr context current # e.g. "Context: local (session)" or "development (global)"
Rule of thumb: day-to-day, just use vr context switch — the session scoping makes it safe. Reach for --global when you mean "change the default for this machine," not "change it for me right now."
vr context unset
Unset the current context so that every command must pick one explicitly — the opt-in fail-closed mode for machines where multiple sessions (or coding agents) share the CLI and none of them should inherit an ambient context.
vr context unset
Behavior:
-
Writes
"current_context": nullto~/.voicerun/config.jsonand clears anycustom_api_urlfromvr context set-url. Also removes the enclosing session's own pin (other sessions' explicit pins are left alone). -
While unset, any command that reaches the API (or builds a frontend/API URL) exits with code
1and prints guidance:No context is set. Pass --context <name>, run 'vr context switch <name>', or set VOICERUN_CONTEXT Available contexts: local, development, default -
Nothing falls back to
default(production) implicitly. A context resolved viavr --context, a newvr context switch, theVOICERUN_CONTEXTenvironment variable, or aVOICERUN_API_URLoverride still works — only the stored fallback is disabled. -
vr context deleteof the context you are currently on also puts the CLI into this no-context-selected state, instead of automatically switching you todefault. -
Configs that have simply never set a context (fresh installs) keep resolving to
default; the fail-closed behavior only applies after an explicitvr context unset. -
Local read-only commands (
vr context list,vr context switch,vr render,vr --version, …) work as usual.
vr context create
Create a new user-defined context.
vr context create <NAME> <API_URL> <FRONTEND_URL> [--org <NAME_OR_ID>]
Options:
| Option | Description |
|---|---|
--org, -o |
Organization name or ID to pin on the context. A name is resolved against your memberships (/v1/me/organizations); a UUID is stored as-is. Omit to leave the context unpinned — requests then use your account's default organization. |
Examples:
vr context create staging https://api.staging.voicerun.com https://app.staging.voicerun.com
# Two contexts for the same environment, one per organization
vr context create acme https://api.voicerun.com https://app.voicerun.com --org "Acme Inc"
vr context create globex https://api.voicerun.com https://app.voicerun.com --org 8e77d4bd-2b70-4e10-8e60-18ea552eb117
vr context delete
Delete a user-defined context. If the context you delete is the one you are currently on, the CLI no longer switches you to default (production) automatically — instead you are left with no context selected, the same state as after vr context unset. API commands will error with guidance until you pick a new context with vr context switch <name>. Any session entries pinned to the deleted context are removed as well, so no session keeps resolving to a name that no longer exists.
vr context delete <NAME>
vr context set-url
Set a custom API URL for the current session.
vr context set-url <API_URL>
vr context set-org
Pin the organization for the current context. Useful when your account belongs to multiple organizations. The pin is stored on the context definition in ~/.voicerun/config.json (next to api_url/frontend_url) and is sent as the organization-id header on every request made under that context.
vr context set-org <NAME_OR_ID>
Arguments:
| Argument | Description |
|---|---|
NAME_OR_ID |
Organization name or ID (pass empty string to clear the pin) |
Behavior:
- A name is resolved against your memberships via
/v1/me/organizations; a UUID is stored as-is (super-admins can target orgs they aren't members of this way). - An unpinned context sends no organization header — the API resolves your account's default organization server-side.
- The pin is part of the context definition, so it survives
vr signout(like the context URLs do). - Service-account tokens are bound to their organization server-side and cannot be re-pinned;
set-orgrejects them.
Example:
# Pin by name or ID
vr context set-org "Acme Inc"
vr context set-org 8e77d4bd-2b70-4e10-8e60-18ea552eb117
# Clear the pin (fall back to your default organization)
vr context set-org ""
vr metrics
Query custom metrics for agents. Supports listing metric names, discovering tags, fetching time-series data, and viewing per-session metrics. Commands that accept an agent filter will resolve from --agent (or positional argument) first, falling back to agent.lock if run inside a voicerun project.
vr metrics names
List available custom metric names.
vr metrics names [AGENT]
Arguments:
| Argument | Description |
|---|---|
AGENT |
Agent name or ID (optional, filters by agent; uses agent.lock if omitted) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# List all metric names
vr metrics names
# List metric names for a specific agent
vr metrics names my-agent
vr metrics tags
Discover available tag keys and their values for filtering.
vr metrics tags
Options:
| Option | Description |
|---|---|
--metric, -m |
Filter tags by metric name |
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# List all tags
vr metrics tags
# List tags for a specific metric
vr metrics tags --metric my_custom_metric
# List tags for a specific agent
vr metrics tags --agent my-agent
vr metrics timeseries
Fetch time-series data for a custom metric with type-appropriate aggregation.
vr metrics timeseries <METRIC_NAME> --start <START> --end <END>
Arguments:
| Argument | Description |
|---|---|
METRIC_NAME |
The metric name to query |
Options:
| Option | Description |
|---|---|
--start, -s |
Start date (ISO 8601, e.g. 2026-03-15T00:00:00Z) (required) |
--end, -e |
End date (ISO 8601, e.g. 2026-03-16T00:00:00Z) (required) |
--step |
Time interval for aggregation (e.g. 30m, 1h, 1d). Default: 1h |
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--environment, -E |
Environment ID |
--tags |
Tag filters as JSON (e.g. '{"channel":"phone"}') |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# Fetch timeseries for a metric over a day
vr metrics timeseries call_duration --start 2026-03-15T00:00:00Z --end 2026-03-16T00:00:00Z
# With hourly aggregation for a specific agent
vr metrics timeseries call_duration -s 2026-03-15T00:00:00Z -e 2026-03-16T00:00:00Z --step 1h --agent my-agent
# Filter by tags
vr metrics timeseries call_duration -s 2026-03-15T00:00:00Z -e 2026-03-16T00:00:00Z --tags '{"channel":"phone"}'
vr metrics session
Fetch all custom metrics for a specific session.
vr metrics session <SESSION_ID>
Arguments:
| Argument | Description |
|---|---|
SESSION_ID |
Session ID |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
vr metrics session 550e8400-e29b-41d4-a716-446655440000
Output includes:
- Metric name, type, value, and timestamp for each metric recorded in the session
vr experiments
View experiments and A/B test results for agents. All subcommands resolve the agent from --agent or from agent.lock if run inside a voicerun project.
vr experiments list
List all experiments for an agent.
vr experiments list
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# List experiments for the current project
vr experiments list
# List experiments for a specific agent
vr experiments list --agent my-agent
vr experiments describe
Show detailed results for a specific experiment, including variant performance, conversion rates, and statistical significance.
vr experiments describe <EXPERIMENT_NAME>
Arguments:
| Argument | Description |
|---|---|
EXPERIMENT_NAME |
Experiment name |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# Describe an experiment in the current project
vr experiments describe greeting_style
# Describe an experiment for a specific agent
vr experiments describe greeting_style --agent my-agent
Output includes:
- Session count and completion status
- Conversion metrics being tracked
- Stop conditions (iterations and confidence thresholds)
- Variant breakdown with per-metric conversion rates
- Statistical significance results (confidence, p-value)
vr experiments timeseries
Fetch time-series data for an experiment metric, broken down by variant.
vr experiments timeseries <EXPERIMENT_NAME> --metric <METRIC> --start <START> --end <END>
Arguments:
| Argument | Description |
|---|---|
EXPERIMENT_NAME |
Experiment name |
Options:
| Option | Description |
|---|---|
--metric, -m |
Metric/outcome name to query (required) |
--start, -s |
Start date (ISO 8601, e.g. 2026-03-15T00:00:00Z) (required) |
--end, -e |
End date (ISO 8601, e.g. 2026-03-16T00:00:00Z) (required) |
--step |
Time interval for aggregation (e.g. 30m, 1h, 1d). Default: 1h |
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# Fetch daily timeseries for a conversion metric
vr experiments timeseries greeting_style --metric booking_completed \
--start 2026-03-01T00:00:00Z --end 2026-03-15T00:00:00Z --step 1d
# Hourly breakdown for a specific agent
vr experiments timeseries greeting_style -m call_duration \
-s 2026-03-15T00:00:00Z -e 2026-03-16T00:00:00Z --agent my-agent
vr experiments funnel
Show the conversion funnel for an experiment, comparing variant performance across each conversion metric with lift calculations.
vr experiments funnel <EXPERIMENT_NAME>
Arguments:
| Argument | Description |
|---|---|
EXPERIMENT_NAME |
Experiment name |
Options:
| Option | Description |
|---|---|
--agent, -a |
Agent name or ID (uses agent.lock if omitted) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Example:
# View conversion funnel for the current project
vr experiments funnel greeting_style
# View funnel for a specific agent
vr experiments funnel greeting_style --agent my-agent
Output includes:
- Total sessions and conversion metrics
- Per-variant breakdown: sessions, conversions, conversion rate, and lift vs baseline
vr ft
Post-train models and manage their lifecycle. vr ft uses the same authenticated, organization-scoped API as the Post trainings UI. Access is available to platform admins and owners/admins of explicitly enabled preview organizations; advanced operations remain restricted.
Managed environment-based RL
vr -c dev ft environments list
vr -c dev ft scenario-sets create --file scenarios.json
vr -c dev ft scenario-sets list
vr -c dev ft scenario-sets get SET_ID
vr -c dev ft episode-rl create --scenario-set SET_ID --request-id YOUR_UUID
vr -c dev ft get JOB_ID
vr -c dev ft episode-rl episodes JOB_ID
vr -c dev ft cancel JOB_ID
These new commands return JSON. scenarios.json contains name, environment_id,
training_scenarios and holdout_scenarios (arrays of IDs from the approved catalog).
Creating a scenario set does not launch compute. Creating an experiment does: it uses
the reviewed Qwen3-4B / LoRA GRPO recipe on one A100. Optional flags are --project,
--seed, --epochs (1–3), and --max-completion-length (512–8192).
Choose a UUID for --request-id and reuse it only when retrying the same submission.
The server returns the existing job instead of creating duplicate compute; reusing the
key with different settings is rejected. Inspect and cancel through the existing job commands.
Managed RL execution requires a separate org allowlist, disabled by default. A post-training UI entitlement alone does not enable RL compute. No custom code or package URI is accepted. Episode output is a bounded training sample, not a held-out evaluation or release gate.
The full lifecycle: files upload (data) → create (train) → deploy (serve on vLLM) → eval (score against a held-out test set) → release (publish under a stable name agents call as voicerun/<name>). Instead of uploading files by hand, datasets create can build the train/val/test files straight from production traffic.
Training data is conversational JSONL — one object per line of the form {"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}.
Every upload is an immutable file with its own id (ftfile-…) and a recorded sha256 — re-uploading the same filename creates a new file, never overwrites. Jobs pin an exact training_file (and validation_file) id, so runs stay reproducible no matter how many times you upload. Files are partitioned per organization on the server.
vr ft files upload
Upload a JSONL data file — training (default), validation, or a held-out test set for evals.
vr ft files upload data/train.jsonl
vr ft files upload data/val.jsonl --purpose validation
vr ft files upload data/test.jsonl --purpose test
The bytes go directly to object storage via a one-time resumable upload URL, not through the VoiceRun API — so large files aren't limited by the API/WAF. The flow is: validate locally → init (get the upload URL) → PUT the bytes → complete (the server reads them back, re-validates, and finalizes). An invalid file fails locally before anything is uploaded.
Arguments:
| Argument | Description |
|---|---|
PATH |
Path to a JSONL file (required) |
Options:
| Option | Description |
|---|---|
--purpose |
training (default), validation, or test |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft files check
Validate a JSONL file locally, without uploading (no network call). Same invariants the server enforces on complete, so a clean check means the upload will validate.
vr ft files check data/train.jsonl
Arguments:
| Argument | Description |
|---|---|
PATH |
Path to a JSONL file (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft files list
List uploaded training files.
vr ft files list
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft create
Create a fine-tuning job.
vr ft create --model Qwen/Qwen3-1.7B --training-file ftfile-abc123 --suffix inline-v4
# Block until the job finishes, streaming events (exit 0 only if it succeeded)
vr ft create --model Qwen/Qwen3-0.6B --training-file ftfile-abc123 --wait
# Auto-chain: on success, deploy to an ephemeral T4, eval against the test file, then tear down
vr ft create --model Qwen/Qwen3-1.7B --training-file ftfile-abc123 \
--test-file ftfile-test123 --auto-eval --eval-scorer slot_intent
When --auto-eval is set, a successful job auto-chains: the trained model is deployed to an ephemeral GPU in the home cluster (--eval-accelerator, default T4:1 — use L4:1/A100:1 for larger base models like 4B/8B that don't fit a 16GB T4), evaluated against --test-file with the chosen scorer/post-processor, and the deployment is torn down once the eval reaches a terminal state. --auto-eval requires both --test-file and --eval-scorer (there is no platform default scorer).
Options:
| Option | Description |
|---|---|
--model, -m |
Base model, e.g. Qwen/Qwen3-1.7B (required) |
--training-file |
Training file id from vr ft files upload (required) |
--validation-file |
Validation file id |
--method |
lora (default), qlora, or full |
--n-epochs |
Number of epochs (default 5) |
--n-checkpoints |
Checkpoints to keep (default 5) |
--learning-rate, --lr |
Learning rate (default 2e-5) |
--batch-size, -b |
Effective batch size (default 32) |
--gradient-accumulation-steps, --grad-accum |
Gradient accumulation steps (default 2) |
--max-seq-len |
Max sequence length (default 2048) |
--warmup-ratio |
LR warmup ratio (default 0.03) |
--lora-r |
LoRA rank (default 64) |
--lora-alpha |
LoRA alpha (default 128) |
--lora-dropout |
LoRA dropout (default 0.0) |
--lora-trainable-modules |
LoRA target modules (default all-linear) |
--train-on-inputs / --no-train-on-inputs |
Train on prompt tokens too (default off) |
--suffix |
Suffix for the output model name |
--seed |
Random seed (default 42) |
--accelerator |
GPU to train on: A100:1, L4:1, or T4:1 (default: platform default) |
--resume-from |
Continue a previous job (ftjob-…) instead of training from step 0 — restores its newest complete checkpoint: weights, optimizer state, LR schedule and step count, then runs on to this job's --n-epochs. For a run that died holding usable checkpoints (spot preemption, node drain, wall-clock deadline). The source is read-only, so a resume can be repeated. Requires the same base model, training file and training type/method; a full fine-tune additionally requires the same GPU count (ZeRO shards optimizer state one file per rank), though the card type may differ. To train further on new data from existing weights, pass the checkpoint as --model instead — that is a fresh optimizer and LR schedule, not a resume. |
--cluster |
GKE cluster to train in (default: the home cluster). Use when the GPU shape you need is stocked out at home but available in another region. The target cluster must run a worker with training enabled, or the job simply waits. |
--test-file |
Test file id (purpose=test) to auto-evaluate against; required with --auto-eval |
--auto-eval |
On success, deploy to an ephemeral T4, eval against --test-file, then tear down; requires --test-file and --eval-scorer |
--eval-scorer |
Scorer for the auto-eval — see vr ft scorers (required with --auto-eval) |
--eval-postprocessor |
Output transform applied before the auto-eval scores — see vr ft postprocessors (default none) |
--eval-max-tokens |
Generation cap per example for the auto-eval (default 64) |
--eval-accelerator |
GPU for the auto-eval ephemeral deployment: T4:1 (default), L4:1, or A100:1 — use L4:1/A100:1 for larger base models (4B/8B) that don't fit a 16GB T4 |
--wait |
Wait for the job to finish, printing events; exit 0 only if it succeeded |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft list
List fine-tuning jobs.
vr ft list
A Cluster column appears once any listed job trains outside the home cluster; jobs at home read
home. On a single-cluster install the column is absent and the table is unchanged.
Options:
| Option | Description |
|---|---|
--limit, -l |
Max jobs to return (default 50) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft get
Show a fine-tuning job. The detail panel includes Cluster — where the job trains, home unless
it was created with --cluster. When a job is stuck, that is the first thing worth knowing: it says
which cluster's GPU pool to go and look at.
vr ft get ftjob-abc123
Arguments:
| Argument | Description |
|---|---|
JOB_ID |
Fine-tuning job id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft cancel
Cancel a running or queued fine-tuning job.
vr ft cancel ftjob-abc123
Arguments:
| Argument | Description |
|---|---|
JOB_ID |
Fine-tuning job id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft events
List a job's events (status transitions, logs).
vr ft events ftjob-abc123
Arguments:
| Argument | Description |
|---|---|
JOB_ID |
Fine-tuning job id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft logs
Print the training pod's logs (plain text). 404s if the job has no training pod yet (still queued) or the pod is gone.
vr ft logs ftjob-abc123
vr ft logs ftjob-abc123 -f --tail 500 # follow until the job finishes
Arguments:
| Argument | Description |
|---|---|
JOB_ID |
Fine-tuning job id (required) |
Options:
| Option | Description |
|---|---|
--follow, -f |
Keep polling for new lines until the job finishes |
--tail |
Trailing lines to fetch (default 200, max 2000) |
vr ft deploy
Deploy a model to a dedicated vLLM endpoint (low-latency, one model per endpoint) — from a fine-tuning job or a merged model URI. Provide --job or --model-uri as the source (one is required); --base-model is required with --model-uri.
# Deploy a finished fine-tune (its adapter is merged, then served)
vr ft deploy --job ftjob-abc123 --served-model-name my-qwen
# Or serve an already-merged model straight from GCS
vr ft deploy --model-uri gs://prim-ai-development-models/my-merged-model \
--base-model Qwen/Qwen3-0.6B --served-model-name my-qwen --accelerator T4:1
Options:
| Option | Description |
|---|---|
--served-model-name, --name |
Model id clients send, e.g. my-qwen (required) |
--job |
Fine-tuning job id to deploy (its adapter is merged, then served) |
--model-uri |
OR a gs:// URI of an already-merged model to serve |
--base-model |
Base model (required with --model-uri), e.g. Qwen/Qwen3-0.6B |
--accelerator |
GPU: T4:1 (default), L4:1, or A100:1 |
--replicas |
Number of vLLM replicas (default 1) |
--wait |
Wait until the deployment reaches a terminal status, printing events; exit 0 only if running |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft deployments
List serving deployments.
vr ft deployments
Options:
| Option | Description |
|---|---|
--limit, -l |
Max deployments to return (default 50) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft deployment
Show a serving deployment.
vr ft deployment ftdeploy-abc123
Arguments:
| Argument | Description |
|---|---|
DEPLOYMENT_ID |
Deployment id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft terminate
Terminate (tear down) a serving deployment.
vr ft terminate ftdeploy-abc123
Arguments:
| Argument | Description |
|---|---|
DEPLOYMENT_ID |
Deployment id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft scorers
List the evaluation scorers (use one with vr ft eval --scorer). Current scorers: exact_match, contains:<text>, regex:<pattern>, json_field:<path>, classification:<field>, set_f1:<field>, slot_intent.
vr ft scorers
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft eval
Run a test set against a deployed model and score it.
vr ft eval --deployment ftdeploy-abc123 --test-file ftfile-test123 \
--scorer classification:intent --wait
Options:
| Option | Description |
|---|---|
--deployment, -d |
Deployment id to test — must be running (required) |
--test-file |
Test-set file id, upload with vr ft files upload --purpose test (required) |
--scorer |
Scorer spec — see vr ft scorers (default exact_match) |
--max-tokens |
Generation cap per example (default 64) |
--wait |
Poll until the eval finishes, then print metrics |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft evals
List evaluation runs.
vr ft evals --deployment ftdeploy-abc123
Options:
| Option | Description |
|---|---|
--deployment, -d |
Filter to one deployment |
--limit, -l |
Max evals to return (default 50) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft evaluation
Show an evaluation run and its metrics.
vr ft evaluation fteval-abc123
Arguments:
| Argument | Description |
|---|---|
EVAL_ID |
Eval id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft results
Show an eval's per-example results (the drill-down behind the metrics): each row's input, expected vs actual (or gold vs pred for classification scorers), pass/fail, and latency.
vr ft results fteval-abc123 --failures-only
Arguments:
| Argument | Description |
|---|---|
EVAL_ID |
Eval id (required) |
Options:
| Option | Description |
|---|---|
--failures-only |
Only show rows that failed |
--limit, -l |
Max rows to fetch (default 50) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft evals-compare
Compare two evaluation runs: metric deltas (A → B) and per-example pass/fail flips — regressions (passed in A, failed in B) and fixes (failed in A, passed in B). Warns when the two evals used different test files or scorers.
vr ft evals-compare fteval-baseline fteval-candidate
Arguments:
| Argument | Description |
|---|---|
EVAL_A |
Baseline eval id (A) (required) |
EVAL_B |
Candidate eval id (B) (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft release
Release a model under a stable name so agents call voicerun/<name>. Provide exactly one source: --deployment (adopt a running, evaluated one), --job (merge + serve a finished fine-tune), or --model-uri (serve an already-merged model). --cluster serves it in another cluster.
If the name already has an active model and both have comparable evals (same test file + scorer), a release whose headline metrics regress is blocked with a per-metric incumbent-vs-candidate table — pass --force to release anyway.
# Adopt a running, evaluated deployment
vr ft release --deployment ftdeploy-abc123 --name inline-agent
# Or release a finished fine-tune directly (merged, then served)
vr ft release --job ftjob-abc123 --name inline-agent
Options:
| Option | Description |
|---|---|
--deployment, -d |
Release a running, evaluated deployment |
--job |
Release a succeeded fine-tune job (its adapter is merged, then served) |
--model-uri |
Release an already-merged model in GCS (gs://...) |
--base-model |
Base model, e.g. Qwen/Qwen3-0.6B (required with --model-uri) |
--name |
Public model id agents call (required with --job/--model-uri) |
--cluster |
Serving cluster name, e.g. enterprise-development-sg (default: the home cluster) |
--accelerator |
GPU for a serving deployment created from --job/--model-uri (default T4:1) |
--replicas |
Serving replicas, one GPU each (default 1). >1 gives HA — a single replica cannot survive a node upgrade. The only way to set replicas on a remote --cluster, since vr ft deploy always serves in the home cluster |
--eval |
Eval id for provenance (default: the deployment's latest succeeded eval) |
--description |
Notes shown in the registry |
--force |
Release even if the candidate's eval regresses vs the active incumbent's |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft models
List released models.
vr ft models --status active
Options:
| Option | Description |
|---|---|
--status |
Filter by status (e.g. active) |
--cluster |
Only models served in this cluster |
--limit, -l |
Max models to return (default 100) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft model
Show a released model and where it's served.
vr ft model inline-agent
Arguments:
| Argument | Description |
|---|---|
NAME |
Model name (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft retire
Retire a released model (agents can no longer call it by name).
vr ft retire inline-agent
Arguments:
| Argument | Description |
|---|---|
NAME |
Model name (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets create
Build a training dataset with a recipe: pull sessions from the source (agents + time window), run the recipe's transform (optionally calling an LLM), split into train/val/test, and register the splits as immutable files (ftfile-…) ready for vr ft create. The flags come in four layers — generic build controls, the source block, the LLM block, and recipe params.
Use --project "SFT run" (or a project ID) to associate the dataset from the start. The project must already exist and be active in the current organization. The CLI validates it before creating the dataset, then attaches the dataset before reporting success or waiting for its build. Downstream resources inherit this association through the platform's lineage handling. If attachment fails, the command exits nonzero and reports the created dataset ID so you can associate it without rebuilding.
# Default recipe (inline_slot_v1) over June production traffic
vr ft datasets create --name june-prod --project "SFT run" \
--agent-id agent-abc123 \
--from 2026-06-01 --to 2026-07-01
# Relabel through an LLM, tune recipe params, and block until the build finishes
vr ft datasets create --name june-prod-llm \
--agent-id agent-abc123 \
--from 2026-06-01 --to 2026-07-01 \
--llm-model gpt-5.4 \
--param target_model=Qwen/Qwen3-1.7B --param split.val=0.15 \
--wait
# Estimate first: sessions, cache hits, LLM calls, and projected splits — no files written
vr ft datasets create --name june-prod --dry-run --wait \
--agent-id agent-abc123 \
--from 2026-06-01 --to 2026-07-01
The source flags (--agent-id, --from, --to) go together and are only sent when given — recipes that declare needs_source require them. The LLM block is sent only with --llm-model; --llm-base-url (default https://api.openai.com/v1) and --llm-key-env (default OPENAI_API_KEY, read on the control plane) refine it. Recipe params come from --params-file (a JSON object) overlaid with repeatable --param key=value — values parse as JSON when valid (60 → int, true → bool, [1,2] → list; bare text stays a string) and dotted keys nest (split.val=0.1). Each recipe validates its own params — vr ft datasets recipes shows what it accepts.
Options:
| Option | Description |
|---|---|
--name |
Dataset name (required) |
--recipe |
Dataset recipe (default inline_slot_v1) — see vr ft datasets recipes |
--project |
Existing active project name or ID in the current organization (optional) |
--seed |
Deterministic build seed (default 42) |
--dry-run |
Estimate the build (sessions, LLM calls, projected splits) without writing files |
--max-sessions |
Cap on sessions read from the source |
--max-examples |
Cap on examples in the built dataset |
--parent-dataset |
Derive from this dataset id instead of a fresh extraction |
--agent-id |
Source agent id — repeat for multiple |
--from |
Source window start (ISO 8601, e.g. 2026-06-01) |
--to |
Source window end (ISO 8601, exclusive) |
--llm-model |
LLM the recipe may call, e.g. gpt-5.4 (required/optional per recipe) |
--llm-base-url |
LLM API base URL (default https://api.openai.com/v1; requires --llm-model) |
--llm-key-env |
Env var on the control plane holding the LLM API key (default OPENAI_API_KEY; requires --llm-model) |
--param |
Recipe param as key=value (repeatable; value parsed as JSON when valid, dots nest) |
--params-file |
JSON file of recipe params (--param overrides its keys) |
--wait |
Wait for the build to finish, printing events; exit 0 only if it becomes ready |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets register
Register EXISTING files (train/val/test) as a ready dataset — no recipe build. For externally-produced or script-transformed files (e.g. prompt-reordered variants) that still need a tracked Dataset record. Each file id must already exist in your org with the matching purpose (--training-file → training, --validation-file → validation, --test-file → test); --name and --training-file are required, the other two optional. No build Job is launched — the dataset is created ready with its counts derived from the files' record counts, so it drops straight into vr ft create.
vr ft datasets register --name exp12-reordered --project "SFT run" \
--training-file ftfile-tr123 \
--validation-file ftfile-va456 \
--test-file ftfile-te789
Options:
| Option | Description |
|---|---|
--name |
Dataset name (required) |
--training-file |
Training file id, purpose training (required) |
--project |
Existing active project name or ID; attach the registered dataset before reporting success (optional) |
--validation-file |
Validation file id, purpose validation (optional) |
--test-file |
Test file id, purpose test (optional) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets recipes
List the dataset recipes the control plane can build. For each recipe: description, whether it needs the source block, whether it needs the LLM block (required, optional, or none), the transform version, and a field table of its params (name, type, default, description).
vr ft datasets recipes
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets list
List datasets.
vr ft datasets list
vr ft datasets list --status ready
Options:
| Option | Description |
|---|---|
--status |
Filter by status: queued, building, ready, failed, or cancelled |
--limit, -l |
Max datasets to return (default 50) |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets get
Show a dataset: status, train/val/test counts, relabel stats (cached/called/failed), the parent dataset id (for derived builds), and the three produced file ids. A finished --dry-run build renders its estimate instead — sessions, direct pairs, cache hits, LLM calls needed, and the projected splits. Once a real build is ready, the file ids are printed ready to paste into vr ft create --training-file … --validation-file … (and vr ft eval --test-file …).
vr ft datasets get ftdataset-abc123
Arguments:
| Argument | Description |
|---|---|
DATASET_ID |
Dataset id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets events
List a dataset build's events (status transitions, progress).
vr ft datasets events ftdataset-abc123
Arguments:
| Argument | Description |
|---|---|
DATASET_ID |
Dataset id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
vr ft datasets cancel
Cancel a dataset build (while it is queued or building).
vr ft datasets cancel ftdataset-abc123
Arguments:
| Argument | Description |
|---|---|
DATASET_ID |
Dataset id (required) |
Options:
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
.vrignore
You can create a .vrignore file in your project root to exclude files and directories from uploads. It works like .gitignore and applies to both vr push and vr create template.
Example .vrignore:
# Ignore log files
*.log
# Ignore data directory
data/
# Ignore build artifacts
build/**
# Ignore a specific file at root only
/config.local.yaml
# Ignore all .csv files
*.csv
Supported syntax:
| Pattern | Description |
|---|---|
*.log |
Glob pattern — matches any .log file at any depth |
secret.txt |
Exact name — matches secret.txt in any directory |
data/ |
Trailing / — matches directories only |
/config.yaml |
Leading / — anchored to project root only |
build/** |
Globstar — matches everything under build/ |
**/test.py |
Globstar prefix — matches test.py at any depth |
# comment |
Lines starting with # are ignored |
vr model-pricing
View and manage model price definitions across the pricing catalogs. Authorization is enforced by the API: today both reads and writes require super-admin (or a principal holding the model-pricing:* platform permissions); reads are expected to open to non-admins later — the command surface is named so that needs no rename. Non-authorized callers get a 403 naming the missing permission.
Manage model price definitions: --kind selects the catalog: completions, decisions (decision models served by the proxy's /v1/decisions: TypeSafe Jev, Cloudflare Clef, OpenAI Decisions), tts, stt, or realtime (speech-to-speech). completions and decisions are the same token-rate table split by interface, so one provider/model can carry different completion and decision rates; each kind only sees its own rows. Definitions are snapshot chains: upsert writes a new active row and expires the predecessor (history is the audit trail); expire retires a model with no successor. Rates are stored in provider quotation units — USD per million tokens/characters/bytes, or USD per minute — matching vendors' published rate cards.
vr model-pricing list --kind <kind> [--provider P] [--model M] [--search S] [--include-history] [-j|-t]
vr model-pricing get <id> --kind <kind> [-j|-t]
vr model-pricing upsert --kind <kind> --provider P --model M --rate field=value [--rate ...] [--notes TEXT] [-j|-t]
vr model-pricing apply --file changes.json [-j|-t]
vr model-pricing expire <id> --kind <kind> # `delete` also works as an alias
apply submits a multi-model changeset in ONE request — the API validates every item before writing anything and posts a single Slack pricing-change notification for the batch (repeated upsert calls notify once per call). File format:
[
{"kind": "tts", "provider": "openai", "model": "gpt-4o-mini-tts",
"rates": {"outputCostPerMillionAudioTokens": 13, "inputCostPerMillionTextTokens": 0.6},
"notes": "OpenAI price change 2026-07"},
{"kind": "completions", "provider": "openai", "model": "gpt-5.4",
"rates": {"inputCostPerMillionTokens": 2.4}}
]
| Flag | Description |
|---|---|
--kind, -k |
Definition kind: completions | decisions | tts | stt | realtime (required; sts is accepted as an alias for realtime, decision for decisions) |
--provider, -p |
Provider name (openai, deepgram, ...). Completions and decisions use the ApiProvider enum values; decisions accepts typesafe, cloudflare, openai |
--model, -m |
Model name exactly as the provider bills it |
--rate, -r |
Repeatable field=value pairs; field names are exactly the JSON keys list/get -j return (e.g. outputCostPerMillionAudioTokens=12). null leaves a rate unset |
--search, -s |
Substring search over provider/model (list) |
--include-history |
Include expired snapshot rows (list) |
--notes |
Free-text note stored on the new snapshot (upsert) |
Examples:
# What do we charge for OpenAI TTS today?
vr model-pricing list --kind tts --provider openai
# Update gpt-4o-mini-tts audio-out to $13/M tokens (text-in unchanged rides on a new snapshot)
vr model-pricing upsert --kind tts --provider openai --model gpt-4o-mini-tts \
--rate outputCostPerMillionAudioTokens=13 --rate inputCostPerMillionTextTokens=0.6 \
--notes "OpenAI price change 2026-07"
# Retire a model no longer served
vr model-pricing expire <row-id> --kind realtime
# Price a decision model (input-only: decision models don't charge for output)
vr model-pricing upsert --kind decisions --provider typesafe --model jev-1.14.0 \
--rate inputCostPerMillionTokens=0.042 --rate outputCostPerMillionTokens=0
Behavior:
- The API validates rates per kind (realtime requires all four core token rates; TTS/STT enforce a single pricing strategy) — invalid combinations 400 with the reason.
- Changes are recorded as
source: admin:<your email>and post to the internal Slack pricing channel immediately — one message per request, so useapplyfor multi-model changesets to get a single batched message. - Expiring a definition makes the model unpriceable: VoiceRun-keyed completions and decisions get blocked by the proxy allowlist and speech/realtime usage would land as bleeding records — expire only models that are no longer served.
vr model-pricing unbillable
The "what needs pricing attention" feed — reads the unbillable-usage records the billing workers write when usage can't be priced (the structured source behind the Slack bleeding/blocked digests).
vr model-pricing unbillable [--kind K] [--bleeding | --blocked] [--since-hours N] [--limit N] [-j|-t]
| Flag | Description |
|---|---|
--bleeding |
Only bleeding records: VoiceRun-keyed spend we couldn't price (urgent — add the rate row) |
--blocked |
Only blocked records: proxy allowlist refusals + client-keyed unpriceable usage (demand signal, no money lost) |
--kind, -k |
Filter to completions | tts | stt | realtime |
--since-hours |
Window, default 24, max 2160 (90-day retention) |
--limit |
Max records, newest first (default 500) |
vr report
Report issues to VoiceRun and track the tickets you've filed — see their status, read replies from the VoiceRun team, and respond.
vr report bug
vr report list
vr report show <ticket-id>
vr report reply <ticket-id> "message"
Subcommands:
| Subcommand | Description |
|---|---|
bug |
Submit a bug report |
list |
List your submitted bug reports |
show |
Show a bug report with its conversation (replies and status changes) |
reply |
Reply on one of your bug reports |
Options (bug):
| Option | Description |
|---|---|
--title, -t |
Bug title/summary |
--description, -d |
Bug description |
--include-system-info |
Include CLI version and OS info (default: true) |
--no-system-info |
Exclude system information |
--dry-run |
Show what would be submitted without sending |
Options (list):
| Option | Description |
|---|---|
--status, -s |
Filter by status: open, in_progress, resolved, closed |
--limit, -l |
Limit number of results |
--page, -p |
Page number |
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Options (show / reply):
| Option | Description |
|---|---|
--json, -j |
Output as JSON |
--table, -t |
Output as table (default for interactive terminals) |
Behavior:
showincludes the full conversation: replies (yours and VoiceRun Support's) interleaved with status-change entries.replyprompts for the message when it isn't passed as an argument.- Replying to a
resolvedorclosedticket re-opens it — the command tells you when that happens, and JSON output carries areopenedflag plus the currentticketStatus. - You are notified in the web app (and by email, per your notification preferences) when VoiceRun replies to your ticket or changes its status.
Examples:
# Interactive mode - prompts for title and description
vr report bug
# Provide title and description inline
vr report bug --title "Login error" --description "Can't log in with SSO"
# Exclude system information
vr report bug --no-system-info
# Dry run - preview what would be submitted
vr report bug --title "Test bug" --dry-run
# List your bug reports (open ones only)
vr report list --status open
# Show one ticket with its conversation
vr report show 58eb775a-577c-44cf-86bd-abac21b25d78
# Reply (re-opens the ticket if it was resolved/closed)
vr report reply 58eb775a-577c-44cf-86bd-abac21b25d78 "Still happening on 1.7.2"
vr edge
Spin up and upgrade a self-hosted ("on-prem") VoiceRun Edge in your own
cloud account, from your own machine. You log in to your cloud locally (aws sso login / gcloud auth login) and vr edge uses those ambient credentials to
shell out to Terraform and the cloud CLI (aws / gcloud) — there is no
VoiceRun-hosted control plane, and the command ships no edge runtime source and
no secrets. The per-instance EDGE_API_KEY is generated on the instance and
read back from it; on a successful up the CLI points the edge context at
http://localhost:8081 and saves that key, so you chain straight into
vr --context edge get agents.
Prerequisites (checked per command; vr edge doctor reports them):
Terraform, plus the aws CLI (AWS) or gcloud CLI (GCP), and — for the console
tunnel on AWS — the SSM session-manager-plugin. vr edge does not install
these; it prints an actionable hint and exits if one is missing.
The edge image (AMI on AWS, image on GCP) is shared to your account by VoiceRun
out of band. vr edge up only prechecks that the image is visible/
launchable and, if not, prints exactly what to ask VoiceRun to share and to
which account. Discover the image with --ami-id / --image, or point
--release-details at the release-details.json VoiceRun gives you (it carries
the ami id, region, KMS arn, and instance type).
# Preview everything (no cloud calls, nothing applied):
vr edge up --dry-run --release-details ./release-details.json
# Provision (default region us-east-1; first boot is slow — 10-25+ min on cold EBS):
vr edge up --release-details ./release-details.json
vr edge up --ami-id ami-08bca75a18ebde8f0 --region us-east-1 --name prod
# Inspect local deployments (state lives under ~/.voicerun/edge/<name>/):
vr edge status
vr edge status --name prod --json
# Open the management console (port-forward 8081; alias: vr edge console):
vr edge tunnel --name prod
# then open http://localhost:8081 and sign in with the saved EDGE_API_KEY
# Upgrade via new-instance migration (launches a new instance from the new
# image beside the old one, migrates data/ + .env, keeps the old as rollback;
# v0.4.0 -> v0.4.1 makes zero DB schema changes):
vr edge upgrade --name prod --ami-id <new-ami> \
--migration-bucket s3://my-edge-migration \
--public-ws-url wss://edge.example.com \
--telnyx-public-key <base64-key>
# Roll back to the retained old instance (before cutover):
vr edge rollback --name prod-upgrade
# Tear down a deployment's cloud resources + local record:
vr edge down --name prod
# Check required external tools:
vr edge doctor
Key flags (up / upgrade unless noted):
| Flag | Applies to | Purpose |
|---|---|---|
--name / -n |
most | Deployment name; multiple named deployments coexist (default default). (Not doctor.) |
--cloud |
up | aws (default) or gcp. |
--ami-id / --image |
up, upgrade | The AWS AMI id / GCP image to launch (or use --release-details). |
--release-details |
up, upgrade | Read ami/image + region + KMS from a release-details.json. |
--region, --zone |
up, upgrade | Region and GPU AZ/zone; --zone pins the capacity sweep. |
--instance-type |
up, upgrade | GPU instance/machine type (AWS default g6e.2xlarge). |
--telephony-domain |
up, upgrade | Public hostname for the telephony ingress (optional). |
--telephony-certificate-arn |
up, upgrade | ACM cert for --telephony-domain (required with it on AWS). |
--migration-bucket |
up, upgrade | s3:// (AWS) / gs:// (GCP) bucket for upgrade state carriage. Set it on up to pre-grant the instance read+write so a later upgrade can migrate data/ + .env; on upgrade it defaults to the bucket the old deployment was provisioned with. |
--sa-project-roles / --no-sa-project-roles |
up, upgrade (GCP) | Grant the edge service account project roles (Cloud Logging/Monitoring, GCS read for bundle-mode). Granting needs project-IAM-admin (setIamPolicy); image-mode needs none of them, so --no-sa-project-roles lets a lower-privileged identity deploy (grant them to the SA later for observability). |
--project |
up, upgrade (GCP) | GCP project id (defaults to your active gcloud project). |
--dry-run |
up | Print the plan + prechecks with no cloud call. |
--yes / -y |
up, upgrade, down | Skip the confirmation prompt. Required in a non-interactive shell (pipe/CI); these commands refuse rather than auto-confirm. |
--json / -j |
most | Emit one JSON document on stdout (progress goes to stderr). (tunnel uses --print instead.) |
Notes:
- Multiple named deployments coexist via
--name(defaultdefault); each gets its own Terraform workspace and local tfstate under~/.voicerun/edge/<name>/. A name must be a single safe path segment. - GPU capacity is zone-volatile.
upsweeps the AZ/zone across the region onInsufficientInstanceCapacity(AWS) /ZONE_RESOURCE_POOL_EXHAUSTED(GCP); pin one with--zone. - Cutover is partly outside Terraform.
upgradeandrollbackprint a checklist for the client-owned DNS / load-balancer-target / security-group steps. - GCP is wired symmetrically over IAP; VoiceRun shares the GCP image to your
project with
roles/compute.imageUser(no key grant needed, unlike the AWS KMS-encrypted AMI). The console tunnel uses SSH-over-IAP so it can reach the loopback-bound management port.
Project Structure
After running vr init, your project will have this structure:
my-agent/
├── handler.py # Main agent code (entry point)
├── README.md # Project documentation
├── requirements.txt # Python dependencies
└── .voicerun/ # Configuration directory
├── agent.yaml # Agent metadata and configuration
├── agent.lock # Created after vr push (tracks agent/function IDs)
├── values.yaml # Base values for template rendering
├── values.<env>.yaml # Per-environment value overlays (e.g. values.production.yaml)
└── templates/ # Declarative resources (see below)
├── deployment.yaml # Runtime config: STT/TTS, recording, redaction, tracing
├── simulation.yaml # Simulated caller for regression testing
├── webhook.yaml # Session-end webhook delivery
└── evaluator.yaml # Post-session scoring / extraction
Declarative Resources
Files under .voicerun/templates/ are rendered with Helm at vr release time and snapshotted onto the release manifest. Each file contains one or more YAML documents with an apiVersion: voicerun/v1 header and a kind: of Deployment, Simulation, Webhook, or Evaluator. Preview the rendered output with vr render; validate spec keys with vr validate.
Deployment
Runtime configuration for the agent in an environment — region, runtime mode, variables, STT/TTS, recording, redaction, tracing, and the optional relay endpoint.
apiVersion: voicerun/v1
kind: Deployment
metadata:
name: my-agent-deployment
spec:
mode: coderunner # 'coderunner' (handler.py sandbox) or 'relay'
region: us-central1-a
variables:
LOG_LEVEL: info
stt:
model: flux-general-en
language: en
failover:
model: nova-3
tts:
provider: cartesia
model: sonic-2
voice: lyric
language: en
speed: 1.0
relay: # only when mode: relay
url: wss://my-relay.example.com/ws/agent
failover: # optional second URL, tried once if the primary dial fails
url: wss://my-relay-standby.example.com/ws/agent
# optional — spoken to the caller when both dials fail. Omit for the
# platform default message; set "" to end the session silently.
failureMessage: "We're having trouble connecting you. Please call back shortly."
recording:
enabled: false
location: gs://my-bucket/recordings/
redaction:
enabled: false
tracing:
enabled: true
In relay mode the agent runs in voicerun-relay and handler.py is not required at the project root — vr validate skips the handler check automatically.
Simulation
A simulated caller used by vr simulate. The CLI submits the simulation's metadata.name and the server resolves spec.systemPrompt and spec.numberOfSimulations from the active release's manifest.
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
Webhook
Session-end webhook delivery configuration. The URL must be HTTPS. Leave secret unset to have the platform generate one.
apiVersion: voicerun/v1
kind: Webhook
metadata:
name: my-agent-webhook
spec:
url: https://example.com/voicerun/session-webhook
secret: shhh
Evaluator
Scores or extracts data from a session after it completes. View results with vr evaluation list and vr evaluation info.
Common fields (every evalType):
| Field | Description |
|---|---|
title |
Human-readable title shown in evaluation listings. |
evalType |
judge, extraction, or deterministic. |
targetFormat |
events or transcript. Deterministic always reads the session view — this field is ignored for that type. |
precondition |
(optional) JSON predicate evaluated against the derived session view. Sessions that don't satisfy it are recorded as status="skipped" with no LLM call. See Preconditions. |
Judge — LLM scores the session against successCriteria:
apiVersion: voicerun/v1
kind: Evaluator
metadata:
name: resolution-judge
spec:
title: Resolution Judge
evalType: judge
targetFormat: transcript
systemPrompt: |
Score the session 1-5 on whether the agent resolved the caller's
request. Respond with a JSON object matching the response schema.
responseSchema: {}
successCriteria:
score: { $gte: 4 }
apiProvider: openai
model: gpt-4o
organizationSecretName: OPENAI_API_KEY
Extraction — LLM pulls structured data; no pass/fail:
apiVersion: voicerun/v1
kind: Evaluator
metadata:
name: call-summary
spec:
title: Call Summary
evalType: extraction
targetFormat: transcript
systemPrompt: "Extract the intent, whether the issue was resolved, and a one-sentence summary."
responseSchema:
type: object
properties:
intent: { type: string }
resolved: { type: boolean }
summary: { type: string }
apiProvider: openai
model: gpt-4o
Deterministic — asserts on the derived session view without an LLM. Zero token cost, fully repeatable. Use for purely factual checks (did the caller say a word, did the call run long enough, was it routed through a specific environment):
apiVersion: voicerun/v1
kind: Evaluator
metadata:
name: inbound-cancellation-request
spec:
title: Inbound caller mentioned cancellation
evalType: deterministic
assertion:
direction: "inbound"
events:
$any:
name: "transcript_part"
data.role: "user"
data.content: { $icontains: "cancel" }
The assertion is a JSON predicate over the session view:
| Field | Description |
|---|---|
turn_count |
Number of completed turns (count of turn_end events). A mid-turn hangup doesn't count. |
duration_seconds |
Seconds between startedAt and endedAt. Returns 0 when the session hasn't ended. |
direction |
inbound or outbound |
origin |
Where the session came from (e.g. phone, web, simulation, native) |
tags |
Session tags as a string array. Matchable by bare primitive (tags: "billing") or by $inc / $ninc. |
environment |
[id, name] for the session's environment — prefers the org-scoped environmentId, falls back to legacy agentEnvironmentId. Matchable by bare primitive against either value: environment: "production" or environment: "<uuid>" both work. |
events |
Raw event list in arrival order — each element is { name, data, timestamp }. Use with $any to assert on event names or payloads (see below). |
Operators:
| Operator | Meaning |
|---|---|
$eq / $ne |
Equals / not equals (deep equality) |
$gt / $gte / $lt / $lte |
Numeric comparison |
$in / $nin |
Value is / is not in a literal array |
$inc / $ninc |
Input array does / does not contain this value |
$contains |
String input contains this substring (case-sensitive) |
$icontains |
String input contains this substring (case-insensitive) |
$any |
Input array has at least one element matching the sub-predicate |
Dotted field paths walk nested objects. On match → success: true with details: { matched: true }; on mismatch → success: false with details: { matched: false, failedPath, reason } so the failing clause is captured.
Bare primitive vs array input. When a predicate's value is a primitive ("production", 42, true) and the input field is an array, the engine does membership matching — equivalent to $inc. This lets you write tags: "billing" instead of tags: { $inc: "billing" }, and environment: "production" matches whether you wrote the env's name or its ID (since environment is exposed as [id, name]). Scalar-vs-scalar equality is unchanged.
$any for event payload checks:
# Did the caller mention 'refund'?
assertion:
events:
$any:
name: "transcript_part"
data.role: "user"
data.content: { $icontains: "refund" }
# Did the agent close with a goodbye?
assertion:
events:
$any:
name: "transcript_part"
data.role: "agent"
data.content: { $icontains: "goodbye" }
Preconditions
Any evaluator type can declare a precondition to gate whether it runs. If the predicate doesn't satisfy, the evaluator is skipped — the row is persisted with status="skipped" and a skipReason naming the failing field, no LLM call. Audit skipped rows with vr evaluation list --status skipped.
apiVersion: voicerun/v1
kind: Evaluator
metadata:
name: objection-handling
spec:
title: Objection Handling
evalType: judge
targetFormat: transcript
systemPrompt: "…"
responseSchema:
type: object
properties:
score: { type: integer }
successCriteria: { score: { $gte: 4 } }
precondition:
turn_count: { $gte: 3 }
duration_seconds: { $gte: 30 }
Templates
agent.yaml
Defines your agent's metadata:
apiVersion: voicerun/v1
name: my-agent
description: "My voice agent description"
handler.py
The main entry point for your agent. Must contain an async def handler() function:
from primfunctions.events import (
Event, StartEvent, TextEvent, StopEvent, InterruptEvent, TextToSpeechEvent
)
from primfunctions.context import Context
async def handler(event: Event, context: Context):
if isinstance(event, StartEvent):
yield TextToSpeechEvent(text="Hello! How can I help you?")
elif isinstance(event, TextEvent):
# Handle user speech transcription
user_text = event.data.get("text", "")
yield TextToSpeechEvent(text=f"You said: {user_text}")
elif isinstance(event, StopEvent):
pass
elif isinstance(event, InterruptEvent):
pass
Releasing
Releases are published to PyPI by CI. The flow matches other VoiceRun repositories:
- Bump
versioninpyproject.tomlonmain(PyPI rejects the publish if the version already exists). - Open a PR from
maintoproduction. The Tests workflow runs against the PR. - Comment
/deploy-productionon the PR. CI fast-forward mergesmainintoproduction, then dispatches a separate run of Deploy Production onproductionto build the package withuv buildand publish to PyPI via Trusted Publishing (no tokens involved).
PyPI rejects Trusted Publishing from issue_comment runs, so publishing uses workflow_dispatch. Check the separate publishing run in Actions for its result. To retry publishing, use Actions → Deploy Production → Run workflow and select production.
Links
License
MIT License
Git Worktrees (Parallel Agent Development)
This repo supports running multiple coding agents in parallel using git worktrees. Each agent gets an isolated working directory with its own branch while sharing the same git object store.
Worktrees are stored in .worktrees/ (gitignored).
Create a worktree
git worktree add .worktrees/feature-xyz -b feature-xyz
List active worktrees
git worktree list
Remove a worktree
git worktree remove .worktrees/feature-xyz
Clean up stale worktrees
git worktree prune
Routing
An entrypoint belongs to one organization environment and has an ordered list of named routes. The first matching route chooses a release from its weighted pool. Releases may belong to different agents, but must share the entrypoint's environment. Exactly one catch-all route comes last. Its conventional name is default.
Create an entrypoint with an explicit environment and either a non-empty --release pool or a complete repeatable --route list:
vr create entrypoint phone support-line --environment production \
--phone-number +14015550100 --direction both --release <stable-release>
Canary rollout: release, route, force, promote
Create the new release with vr release production. Use its returned ID below. Route selected callers to it, and give other calls a 10% chance of using it:
vr update entrypoint support-line \
--route 'canary: caller=+15551234567,+15557654321 => <new-release>' \
--route 'default: => <stable-release>:90 <new-release>:10'
vr describe entrypoint support-line
Call from either listed number to force the canary route. Other callers use the default route's weighted pool. session.track records the route name, so a call that draws the new release from the default pool still has track: default. Use releaseId when comparing release versions and track when comparing routing cohorts.
vr get sessions --track canary
vr update entrypoint support-line --promote canary
Promotion replaces the catch-all's pool with the canary's pool and removes the canary route. Roll back by pointing the catch-all at the previous release:
vr update entrypoint support-line --release <stable-release>
Route grammar and edits
<name>: [<attribute>=<value1>,<value2> [<attribute>=<value>] [| ...]] => <releaseId>[:<weight>] ...
Values separated by commas are OR; attributes separated by whitespace are AND; | separates OR entries. Matching is exact and case-sensitive. A release weight defaults to 100 in a route string; weights are relative and need not total 100. Quote the whole route string in your shell.
| Command option | Behavior |
|---|---|
--route SPEC (repeatable) |
Replace all routes in the given order |
--set-route SPEC |
Replace a named route, or insert it before the catch-all |
--before NAME / --after NAME |
Place a set or moved route relative to another named route |
--move-route NAME |
Move an existing route; requires before or after |
--remove-route NAME |
Remove a conditional route |
--promote NAME |
Copy its pool to the catch-all, then remove it |
--release ID[:WEIGHT] |
Replace only the catch-all pool; preserve conditional routes |
--environment NAME_OR_ID |
Repair or change the environment; every route's releases must belong to it |
Route edits use If-Match: "N" with the integer Entrypoint.routingVersion read before editing. The API increments routingVersion once when updating routes, the releases alias, or environmentId; metadata-only changes leave it unchanged. Replacing routes without a version returns 428; an outdated version returns 412. The app can send the same expected value as routingVersion in the PATCH body. updatedAt remains a modification timestamp and is not a concurrency token. A conflict requires reloading and reapplying the edit; the CLI never silently retries with stale routes. Route names must be unique slugs; default is reserved for the catch-all. Empty or zero-total pools, unknown attributes, deleted/cross-environment releases, and shadowed/duplicate routes are rejected. Overlapping routes are allowed; vr describe and the app warn that order matters.
For automation, capture routingVersion with vr get entrypoints NAME --json, then pass it explicitly:
vr update entrypoint support-line --promote canary --expected-routing-version 7 --json
The command rejects a mismatch before writing and sends the same supplied version as If-Match on the PATCH, protecting against a change after its read too. Without this option, it retains the existing behavior of using the version read within the command. The option requires a routing or environment update, not a metadata-only edit; it fails against APIs without a valid routingVersion. A conflict exits nonzero without retrying. Only consume the JSON success document when the exit code is zero; diagnostics and errors (including DEBUG request logging) go to stderr in JSON mode, leaving stdout for the success document.
For conditional rollback, save the successful response's data.routes and data.routingVersion: the API returns the snapshot produced by that conditional update, rather than a later read. Restore the complete pre-update route list using repeated --route arguments and pass the successful response's data.routingVersion as --expected-routing-version. A subsequent routing change makes that rollback fail instead of overwriting the newer edit. --release alone restores only the catch-all pool, not a conditional route removed by promotion. Never substitute a newly read version after a conflict without inspecting the new state and deciding whether the edit is still appropriate. These preconditions protect one entrypoint per request; they do not make changes across entrypoints atomic. A transport failure can leave the result unknown, so inspect routing before attempting recovery.
vr release production -e support-line replaces the catch-all with the newly created release at weight 100; --weight 10 appends to that pool. vr release production -e support-line --route canary updates that named route. The route must already exist; a missing route fails before release creation. Its matchers are preserved. Create it explicitly first with vr update entrypoint support-line --set-route 'canary: caller=+15551234567 => <existing-release>'. The name canary has no special meaning.
Web/native client tracks
A web/native /start body can include {"track":"next"}. It only selects a different route if the operator configured a corresponding matcher:
vr update entrypoint widget --set-route 'next: track=next => <new-release>'
vr outbound support-line --to +15551234567 --track next
vr debug --release <new-release> --track next
Outbound uses track as a routing attribute. Debugging a release directly stamps the supplied track without selecting a route. Sessions created without an entrypoint have a null track unless explicitly supplied. vr simulate --track NAME stamps direct simulation sessions. origin remains independent of track.
Chained sessions
The engine sends parent_session_id automatically. The API exposes its track to routing only when the parent belongs to the same organization. A null or foreign parent track matches nothing.
vr update entrypoint outbound-line \
--route 'canary: parent[track]=canary => <new-outbound-release>' \
--route 'default: => <stable-outbound-release>'
A handler may supply input_parameters={"track": "spanish"}. To make that override inheritance, place a track=spanish route before the parent[track] route.
SIP trunk / PBX headers
SIP delivery can carry custom headers; ordinary PSTN callers carry no custom headers. Twilio SIP trunk webhooks expose SipHeader_<Name>. The VoiceRun SBC forwards headers configured in the DID's sip_headers allowlist.
vr update entrypoint support-line \
--set-route 'pbx-canary: sip_headers[X-VR-Track]=canary => <new-release>' --before canary
Custom URL query parameters
On inbound phone /v1/entrypoints/:id/start, a matcher custom_parameters[<name>] reads the URL query parameter custom_<name> before release selection. Route authors choose the names; there is no platform-maintained allowlist. Names are case-sensitive, start with a letter, digit or underscore, and contain only letters, digits, underscores, dots or hyphens ([A-Za-z0-9_][A-Za-z0-9._-]*). Values are exact strings: false is different from true, False, and 0. Case and whitespace are preserved.
For example, configure the upstream Twilio webhook or redirect URL as:
https://api.voicerun.com/v1/entrypoints/<entrypoint-id>/start?custom_agent_profile=assistant&custom_warm_transfer=false
In TwiML XML, escape the query separator as &. These are HTTP URL query parameters, not nested TwiML Stream <Parameter> values: stream parameters arrive after release selection and cannot select an entrypoint route. Web/native /start, /outbound, request-body fields and release deployment defaults do not supply custom_parameters routing attributes.
vr update entrypoint support-line \
--set-route 'pilot: custom_parameters[agent_profile]=assistant custom_parameters[warm_transfer]=false => <stable-release>:95 <candidate-release>:5'
This inserts the route before the existing catch-all and preserves other routes. Calls with both values enter the 95/5 pool; calls with another profile, warm_transfer=true, or missing/invalid required parameters continue to later routes and ultimately the catch-all. Ordinary strings can be chosen for any parameter; agent_profile and warm_transfer have no special platform meaning.
You can also configure the complete route list at creation:
vr create entrypoint phone support-line --environment production \
--phone-number +15555550100 \
--route 'pilot: custom_parameters[customer_segment]=blue => <candidate-release>' \
--route 'default: => <stable-release>'
Use --route or --release, not both. --route requires exactly one catch-all. The existing route-string grammar uses whitespace, commas and | as separators; use the app or the API's JSON route representation for literal values containing those delimiters. vr get entrypoints --json and vr describe entrypoint --json preserve all matcher keys and values. Human output shows the route conditions and weighted targets. Unrelated named edits and vr release --route preserve existing custom conditions.
Routing eligibility (separate from agent parameter forwarding):
- For routing, URL encoding is decoded exactly once;
+means a space,%2Ba literal plus, and%2526the literal text%26. - Missing and empty parameters do not match. An empty string still forwards to the agent and overrides a deployment default.
- Repeated names (even identical values), malformed percent/UTF-8 encoding and bracket/object/array forms do not match. These routing checks do not filter parameters forwarded to the agent. A mixed scalar/bracket value invalidates the scalar too.
- Agent parameter forwarding is unchanged:
CallParametersService.build()continues to read Expressreq.query, apply its existingdecodeURIComponentconversion, and merge request parameters over deploymentcustomParameterswith the existing precedence. Routing name validation, duplicate rejection and scalar checks apply only to route selection. Existing malformed-value forwarding errors are unchanged. Deployment defaults never determine the routing cohort. - Routing and forwarding intentionally use different decoding paths: for example, incoming
custom_text=%2526matches routing value%26, while the existing forwarding decoder passes&to the agent. - Existing exact AND/OR semantics, route ordering, shadowing validation, authorization, simulation release pinning and
routingVersion/If-Matchprotection still apply. Deploy the API before clients that author custom matchers. No route is changed automatically.
Compatibility window
Existing entrypoints route through a default route even before backfill. The releases field remains a readable/writable alias for the catch-all pool during one CLI/app cycle. Legacy entrypoints may have environmentId: null until backfill. A release update can infer it when all stored and proposed releases belong to one environment. Empty pools, mixed environments, and missing/deleted or environment-less releases require explicit repair: set the environment and valid target releases before adding routes. Existing sessions keep a null track.
Each route must retain a non-empty release pool with finite, non-negative weights and a positive total. To retire a conditional route, remove it; recreate the same name later with explicit matchers and releases. --promote copies its releases into the catch-all and removes it.
vr describe entrypoint displays “environment unresolved — fix before adding routes” when environmentId is absent; it does not assume the entrypoint spans multiple environments.
When the API does not return a valid routingVersion, explicit route edits report that the API must be upgraded. vr release --route checks this before creating a release. Legacy catch-all --release updates retain their compatibility behavior.
Nested matcher JSON and compatibility
Route conditions use nested JSON groups: custom_parameters, sip_headers, and parent. caller, called, and track remain top-level arrays. All conditions within one match object are ANDed; separate match objects are ORed.
{
"name": "pilot",
"match": [{
"custom_parameters": {
"agent_profile": ["assistant"],
"warm_transfer": ["false"]
},
"sip_headers": {
"X-Customer-Segment": ["premium"]
}
}],
"releases": {"stable-release": 95, "candidate-release": 5}
}
CLI route arguments use custom_parameters[agent_profile]=assistant, sip_headers[X-Customer-Segment]=premium, and parent[track]=pilot. Quote the complete route argument to protect brackets and | from the shell. A name such as segment.v2 remains a literal name inside its group; dots never introduce further nesting.
The API accepts nested and legacy dotted matchers, including legacy CLI syntax. Specifying the same condition through both forms in one match object is rejected. The app reads either form and saves nested matchers; the CLI also presents nested JSON from get/describe, even when reading a legacy response.
For rolling-upgrade compatibility, the API normalizes writes to the existing flat JSONB storage representation and keeps default HTTP responses flat. API clients can request nested responses with ?matcherFormat=nested on list, get, create, or update. This changes only response representation and never routingVersion. Existing If-Match requirements still apply to writes. No database migration or automatic route edits are needed.
Deploy the updated API to all instances before using the updated app or CLI to save nested matchers. Older APIs reject nested writes; ordinary older clients continue receiving their existing response format. Custom-parameter parsing and forwarding to agents are unchanged.
Metadata
Release files for voicerun-cli 1.7.26
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| voicerun_cli-1.7.26.tar.gz | 561.9 kB | With provenance | Details for voicerun_cli-1.7.26.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| voicerun_cli-1.7.26-py3-none-any.whl | Python 3 | none | any | Details for voicerun_cli-1.7.26-py3-none-any.whl |
Total release size: 938.2 kB
Release files / voicerun_cli-1.7.26.tar.gz
| Download URL | voicerun_cli-1.7.26.tar.gz |
|---|---|
| Size | 561.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a10e9bf8b06bca01e53a115bd93643f1025d2e43975ca9ec8a5224c9f4a87c5
|
|
BLAKE2b-256 checksum How to use checksums |
1a11dad2098367c82a2ccd10694f5c51f8929b009c89d8f39b1000a55646b4df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logRelease files / voicerun_cli-1.7.26-py3-none-any.whl
| Download URL | voicerun_cli-1.7.26-py3-none-any.whl |
|---|---|
| Size | 376.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
591dc7700e18ad1ff8b5d9b07b4ffb4858f523f76b590968267b2ac6429354e2
|
|
BLAKE2b-256 checksum How to use checksums |
86eb3073bdecd212a5f5a9713687a30d0329a547f1fdfef67af22c33fcb0e8f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logSecurity for voicerun-cli 1.7.26
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for voicerun_cli-1.7.26.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.