Project Management
The VoiceRun CLI takes a project from scaffold to production: vr init creates the files, vr push uploads the handler, and vr release binds that code to an org-scoped environment with a snapshotted manifest. An entrypoint then routes incoming traffic to one or more releases.
Creating a Project#
Scaffold a new voice agent project with vr init:
vr init my-agent
Without arguments, vr init runs an interactive wizard that asks for a project name and (optionally) a remote template to start from. Pass --yes to skip the wizard.
The scaffold produces:
my-agent/
├── handler.py # Main agent code (entry point)
├── requirements.txt # Python dependencies
├── README.md # Project documentation
├── .gitignore
├── .vrignore # Files to exclude from vr push
├── .claude/CLAUDE.md # Instructions for Claude Code
├── AGENTS.md # Instructions for Codex / OpenClaw
└── .voicerun/
├── agent.yaml # Agent metadata (name, description)
├── values.yaml # Base deployment values
├── values.development.yaml # Development overrides
├── values.production.yaml # Production overrides
└── templates/ # Deployment, simulation, evaluator, and webhook resources
Options#
| Flag | 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 (repeatable) |
Using Templates#
Initialize from a shared template:
vr init my-agent --template restaurant-booking vr init my-agent --template restaurant-booking --var language=spanish --var region=us
List available templates:
vr get templates
Project Configuration Templates#
VoiceRun projects include Helm-style configuration templates under .voicerun/. The CLI renders these templates during vr validate, vr render, vr release, and vr simulate — which renders them locally to list simulations and preview the cost guardrail, though the run itself uses the release's manifest.
Template values come from .voicerun/values.yaml and can be overridden per environment with files such as .voicerun/values.development.yaml and .voicerun/values.production.yaml. Templates reference those values with .Values.*, and agent metadata from .voicerun/agent.yaml with .Agent.*.
For example, enable the optional session-end webhook by setting an HTTPS URL in the relevant values file:
webhook: url: https://example.com/voicerun/session-webhook
Leaving webhook.url as null skips the webhook resource during render and release, so new projects do not create a webhook unless you opt in.
You can comment out unused template examples with YAML comments (# ...) or Helm comment blocks ({{/* ... */}}). The CLI ignores template expressions inside those comments, so commented-out examples do not create placeholder values or affect rendering.
Validating a Project#
Check that your project structure, agent metadata, and (optionally) rendered templates are correct:
vr validate vr validate --environment production
Validation checks:
handler.pyexists and containsasync def handler(event, context)(skipped automatically when the renderedDeployment.spec.modeisrelay).voicerun/directory structure is correctagent.yamlis valid with required fields.voicerun/templates/*.yamlparse as YAML and have allowedkind+specfields- When
--environmentis given (or Helm templates are auto-detected), templates are rendered with Helm and the rendered output is validated
Use --quiet to only show errors.
Declarative Resources#
Anything under .voicerun/templates/ is rendered with Helm at release time and snapshotted onto the release manifest. Each YAML document declares one resource with apiVersion: voicerun/v1 and one of four kind: values:
| Kind | Purpose |
|---|---|
Deployment | Runtime config — mode, region, variables, stt, turnTaking, tts, relay, recording, redaction, tracing |
Simulation | Simulated caller config used by vr simulate |
Webhook | Session-end webhook delivery (URL + signing secret) |
Evaluator | Post-session scoring or extraction (judge / extraction) |
See the Declarative Resources reference for the full per-kind field list, types, and validation rules.
Values Files#
.voicerun/values.yaml is the base values file used by Helm. Per-environment overlays (e.g. prod.yaml, staging.yaml) live alongside it and are pulled in with --values prod.yaml on vr release, vr render, or vr simulate.
Secrets in rendered manifests are referenced with {{ Secrets.organization.NAME }}. The placeholder survives Helm rendering verbatim, lands in the manifest as-is, and is resolved against organization secrets at session start — secrets never round-trip through the release record.
Previewing the Render#
Preview what Helm will produce for the current project without creating a release:
vr render # default values.yaml vr render --values .voicerun/prod.yaml # overlay a values file vr render --set stt.model=nova-3 # override a single value vr render --output json # JSON instead of YAML
vr render also runs spec validation on the rendered output, so it doubles as a fast feedback loop while editing templates.
Pushing Code#
Upload your agent code to VoiceRun:
vr push
The first push creates a new agent and function on the server. Subsequent pushes update the existing function version until a release references it. A released function is immutable: a later vr push automatically creates a new child function version and writes the returned functionId back to .voicerun/agent.lock. Existing releases keep serving their snapshotted function until you create a new release. Use --new to force a new function version before release.
| Flag | Description |
|---|---|
--name | Name for the function version |
--new, -n | Create a new function version |
--yes, -y | Skip confirmation prompts, including the bind-to-existing-agent confirmation |
--agent, -a | Agent ID (or a name unique in the organization) to push to. Overrides the lock file; use it when several agents share the name in agent.yaml |
After pushing, an agent.lock file is created in .voicerun/ to track the agent ID, function ID, and a checksum of the project files at push time. Other commands (release, debug, simulate, etc.) read agent.lock so they can default the agent automatically when run from inside a project.
Pushing without a lock file#
Lock files are per context (agent.lock for the default context, agent.<context>.lock for any other) and many teams leave them out of version control. A clone with no lock for the current context still finds its agent: vr push looks for an agent in the organization whose name matches .voicerun/agent.yaml.
- Exactly one match: the push binds to it and creates a new function version. The confirmation reads
Bind to existing agent '<name>' (<id>) and push a new version?;--yesanswers it, and the lock is written once the push succeeds. - No match: a new agent is created, as on a first push.
- Several matches: the push stops and lists their IDs. Choose one with
--agent <id>. (--agentalso accepts a name, but only when it is unique; a shared name is rejected the same way.) --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 before doing so.
Tenant pushes (--tenant) skip the lookup, since every tenant shares the chart's agent.yaml.
Other commands that read the lock (vr release, vr describe, vr simulate, ...) fall back to the same name lookup for the agent. The function does not resolve by name: vr release without a lock or --function exits 1 rather than creating a release with nothing to run.
.vrignore#
Control which files are excluded from the push with a .vrignore file (works like .gitignore):
# Exclude test files
tests/
*_test.py
# Exclude data directories
data/
*.csv
# Exclude build artifacts
__pycache__/
*.pyc
The following are always excluded: .venv, __pycache__, .git.
Pulling Code#
Download agent code from the server to your local machine:
vr pull # Inside a project (uses agent.lock) vr pull AGENT_ID # Outside a project vr pull AGENT_ID -o ./dir # Specify output directory
Releasing to an Environment#
A release binds an agent + function to an org-scoped environment at a point in time, with an immutable snapshot of the rendered manifest. The latest release for a given (agent, environment) pair is implicitly the active one — there's no separate "activate" step.
vr release <ENVIRONMENT> # Inside a project (agent from agent.lock) vr release my-agent production # Explicit agent vr release production --entrypoint support # Cut traffic over to the new release vr release production --entrypoint support --weight 25 # 25% canary rollout vr release production --values prod.yaml # Overlay an environment-specific values file vr release production --function FUNC_ID # Pin to a specific function version
Options#
| Flag | Description |
|---|---|
--function, -f | Function ID to release (defaults to agent.lock's functionId; required when there is no lock) |
--values, -v | Values file in .voicerun/ to overlay (e.g. prod.yaml). Without it, only .voicerun/values.yaml is loaded |
--entrypoint, -e | Entrypoint name or ID to point at the new release after creation |
--weight | Partial-rollout weight (1-100). Without it, the entrypoint's release list is replaced with the new release at weight 100. With it, the new release is appended at the given weight, leaving existing entries unchanged |
--yes, -y | Skip the unpushed-changes confirmation prompt |
--json, -j | Print the created release as JSON on stdout; progress, prompts, and errors go to stderr |
Prerequisites#
- Run
vr pushfirst (or pass--functionexplicitly) so the release has a function to bind to. - The target environment must already exist — create one with
vr create environment <name>. - If
.voicerun/templates/exists, Helm must be installed.vr releaserenders the full manifest at release time so the runtime has an immutable snapshot.
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 CI use.
Dedicated releases: waiting for the build#
For a dedicated deployment (dedicated: true) the command follows the image build and pod rollout and exits 0 only once the release is running; --no-wait returns as soon as the release is created. A degraded state (no ready pod and a pod reporting a failure signal) is tolerated for up to 90 seconds, because a cold pod reports one while its image pulls; if it persists, or the build reports failed, the command exits 1 and any --entrypoint update is skipped, so traffic stays on the previous release.
JSON output for CI#
vr release --json prints the created release (and the entrypoint update, when --entrypoint was given) as one JSON document on stdout, with progress, prompts, and errors on stderr — including failures before the release exists, such as an ambiguous agent name. The document is printed on a failed build as well, so a pipeline always has the release ID to inspect or roll back, and the exit code still reports the outcome.
{ "release": { "id": "…", "agentId": "…", "environmentId": "…", "functionId": "…", "status": "released", "operationalStatus": "running", "manifest": [ … ] }, "entrypoint": { "id": "…", "name": "support-line", "releases": [ { "releaseId": "…", "weight": 100 } ] } }
Keys are camelCase, as the API returns them. entrypoint is null without --entrypoint. Capture the output before parsing it: inside $(vr … | jq …) the exit status is jq's, so a failed release would go unnoticed.
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")
Routing traffic with entrypoints#
An entrypoint is the public endpoint that callers reach (a phone number, web widget, native SDK client). It carries a weighted list of releases, and the API picks one per call. See Entrypoints for the create/update commands.
Opening the Dashboard#
Open your agent's page in the VoiceRun web dashboard:
vr open
Requires agent.lock (created after vr push).
