Declarative Resources

Anything under .voicerun/templates/ is rendered with Helm at vr release time and snapshotted onto the release manifest. Each YAML document declares one resource:

apiVersion: voicerun/v1 kind: Deployment | Simulation | Webhook | Evaluator metadata: name: <unique within the manifest> spec: ...

Preview rendered output with vr render; validate spec shape with vr validate. Values referenced as {{ .Values.foo }} come from .voicerun/values.yaml (and overlays); {{ .Agent.Name }} and friends come from .voicerun/agent.yaml. Secrets are referenced as {{ Secrets.organization.NAME }} — Helm leaves the placeholder intact and the API resolves it at session start.

metadata.name must be unique within a manifest for each kind. It's the handle used by other commands (e.g. vr simulate --name <…>, vr evaluation list --type <…>).

Deployment#

Runtime configuration for the agent in an environment. Every field is optional except kind/metadata.name — omitted fields take platform defaults. The mode field decides whether handler.py is required at the project root.

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 FEATURE_FLAG: "true" stt: model: flux-general-en language: en failover: model: nova-3 turnTaking: mode: smart_turn externalEndpointing: 300 smartTurnVadStopSecs: 0.4 smartTurnStopSecs: 3.0 smartTurnTimeout: 5.0 tts: provider: cartesia model: sonic-2 voice: lyric language: en speed: 1.0 relay: url: wss://my-relay.example.com/ws/agent recording: enabled: false location: gs://my-bucket/recordings/ redaction: enabled: false tracing: enabled: true

Top-level fields#

FieldTypeDescription
modecoderunner | relayRuntime mode. coderunner (default) runs your handler.py in the sandbox. relay runs in voicerun-relay — no handler.py is required and vr validate skips the handler check automatically.
regionstringCluster region (e.g. us-central1-a).
variablesmap<string, string>Values injected into context.variables at session start. Merged with the org/agent-environment variable scopes.
relayobjectRelay endpoint config. Only used when mode: relay.
sttobjectSpeech-to-text config.
turnTakingobjectTurn-taking strategy. Sibling of stt/tts because the signal can come from STT (provider EoT), raw audio (Silero VAD, Smart Turn V3), or — eventually — semantic analyzers.
ttsobjectText-to-speech config.
recordingobjectCall recording config.
redactionobjectPII redaction applied to traces and session events.
tracingobjectDistributed tracing for the call pipeline.

spec.relay#

Only honored when mode: relay. Optional failover swaps to a backup relay endpoint on connection errors.

FieldTypeDescription
urlstringPrimary relay WebSocket URL (e.g. wss://relay.example.com/ws/agent).
failover.urlstringBackup relay WebSocket URL.

spec.stt#

FieldTypeDescription
modelstringSTT model identifier (e.g. flux-general-en, nova-3).
languagestringBCP-47 language code (e.g. en).
promptstringPrompt biasing for providers that support it.
filterstringProvider-specific filter string.
endpointingnumberProvider-side endpointing silence threshold (ms).
audioInputDelaynumberAudio input delay (ms) before transcription starts.
noiseReductionTypestringProvider noise-reduction profile.
eot.thresholdnumberDeepgram-style end-of-turn confidence threshold.
eot.timeoutMsnumberHard cap on EoT detection (ms).
eot.eagerThresholdnumberEager-EoT pre-confirmation threshold.
vad.modeserver_vad | semantic_vadOpenAI Realtime / Qwen3 VAD mode.
vad.eagernessauto | low | medium | highOpenAI semantic-VAD eagerness.
failoverobjectSame shape as stt (minus failover). Used on provider errors.

spec.turnTaking#

FieldTypeDescription
modeprovider | silero | smart_turnHow turn boundaries are decided. provider uses the STT provider's built-in endpointing. silero runs local Silero VAD on the relay/agent. smart_turn runs Silero VAD + Smart Turn V3 ML.
externalEndpointingnumberSilence-stop threshold (ms) for silero / smart_turn.
smartTurnVadStopSecsnumbersmart_turn only — VAD silence-stop window before the ML gates. Default 0.4.
smartTurnStopSecsnumbersmart_turn only — ML's per-window timeout. Default 3.0.
smartTurnTimeoutnumbersmart_turn only — hard cap on the ML's running window. Default 5.0.

spec.tts#

FieldTypeDescription
providerstringTTS provider (e.g. cartesia, elevenlabs).
modelstringProvider-specific model id (e.g. sonic-2).
voicestringProvider-specific voice id (e.g. lyric).
languagestringBCP-47 language code.
speednumberSpeech rate (provider-specific scale).
failoverobjectSame shape as tts (minus failover). Used on provider errors.

spec.recording#

FieldTypeDescription
enabledbooleanRecord the call audio. Default false.
locationstringStorage URI override (e.g. gs://my-bucket/recordings/). Leave unset to use the platform default.

spec.redaction#

FieldTypeDescription
enabledbooleanApply PII redaction to traces and session events. Default false.

spec.tracing#

FieldTypeDescription
enabledbooleanEmit distributed traces for the call pipeline. Default true.

Simulation#

A simulated caller used by vr simulate. The CLI submits the simulation's metadata.name; the API resolves spec from the active release's manifest, so the version that runs is always the released one, not whatever is on disk.

apiVersion: voicerun/v1 kind: Simulation metadata: name: happy-path spec: direction: inbound systemPrompt: | You are a customer calling this agent. Keep turns short and realistic. inputData: accountTier: premium numberOfSimulations: 5 provider: gemini_live model: gemini-3.1-flash-live-preview voice: Aoede # Optional: pin delivery variation so a simulated caller is reproducible. varietySeed: 12345 phases: - type: ring durationSecs: 8 - type: message text: "Thank you for calling. All representatives are busy. Please hold." - type: holdMusic durationSecs: 30 loopPhases: true humanPickupAfterSecs: 90

Top-level fields#

FieldTypeRequiredDescription
directioninbound | outboundnoWhich side of the call to simulate. Defaults to inbound, where the persona is a caller dialing the agent. Use outbound to test agents that place calls; the persona is the callee who answers.
systemPromptstringyesPrompt driving the simulated user's behavior.
inputDataobjectnoTask payload persisted to each spawned session and exposed to the handler as context.input_data. Most useful with direction: outbound, but allowed for inbound simulations too.
numberOfSimulationsinteger (1-100)noNumber of simulated sessions spawned per vr simulate invocation.
providergemini_live | openai_realtimenoPersona engine vendor. Defaults to gemini_live.
modelstringnoProvider-specific model id. For gemini_live: e.g. gemini-3.1-flash-live-preview (default). For openai_realtime: e.g. gpt-realtime (default), gpt-realtime-mini.
voicestringnoProvider-specific voice id. For gemini_live: Aoede, Puck, Charon, Kore, Fenrir, etc. For openai_realtime: alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar.
languagestringnoBCP-47 language tag for the persona, such as en-US or es-MX. Falls back to the simulator default when omitted.
varietySeedinteger (0-9007199254740991)noSeed for the simulator's delivery variation (greeting, phrasing, mood, and verbosity). Omit for fresh randomness; set it to reproduce a specific simulated caller.
phasesPhaseSpec[]noPre-pickup phase script — see below.
loopPhasesbooleannoWhen phases is non-empty, restart the phase list when it ends. Default true.
humanPickupAfterSecsinteger (0-600)noSeconds of phase playback before the persona takes over. Omit to never auto-pickup (tests the agent's give-up logic).

For direction: outbound, omit phases, loopPhases, and humanPickupAfterSecs. Outbound simulations model the callee answering immediately, so the API rejects pre-pickup phases for outbound manifests.

Outbound example#

apiVersion: voicerun/v1 kind: Simulation metadata: name: appointment-reminder spec: direction: outbound systemPrompt: | You are Dana Lee. You just answered your phone and are willing to talk briefly. inputData: customerName: Dana Lee appointmentTime: Tuesday at 3pm reason: confirm upcoming appointment numberOfSimulations: 3

spec.phases#

Phase entries play before the simulated persona starts speaking — useful for warm-transfer testing where the outbound leg waits through ringing/queue/IVR before someone "answers".

typeFieldsDescription
ringdurationSecs (int, 1-600)North-American ringback tone (440+480 Hz, 2s on / 4s off).
holdMusicdurationSecs (int, 1-600)Looping arpeggio that reads as hold music to a VAD.
messagetext (non-empty string)Pre-rendered automated-IVR speech (Gemini TTS).
ivrMenuprompt, options, optional timeoutSecs / maxRepeats / onNoInput / onInvalidRecursive IVR menu node. See below.

ivrMenu phase

FieldTypeDescription
promptstringThe IVR prompt the simulator plays.
optionsmap<DTMF digit, PhaseSpec[]>Single-character keys (0-9, *, #) mapped to the sub-phases that fire when the agent sends that digit. Phases can themselves be ivrMenu entries for multi-level trees.
timeoutSecsinteger (1-120)Seconds to wait for a digit after the prompt finishes. Default 8.
maxRepeatsinteger (0-10)Additional re-prompts when no digit arrives. Default 2.
onNoInputPhaseSpec[]Phases to run after maxRepeats re-prompts produce no input.
onInvalidPhaseSpec[]Phases to run when the agent sends a digit not in options.

Webhook#

Session-end webhook delivery configuration. The destination URL must be http(s).

apiVersion: voicerun/v1 kind: Webhook metadata: name: my-agent-webhook spec: url: https://example.com/voicerun/session-webhook events: - session.ended signingToken: "{{ Secrets.organization.WEBHOOK_SIGNING_TOKEN }}"

Top-level fields#

FieldTypeRequiredDescription
urlstring (http/https URL)yesDestination URL.
eventsstring[]yesEvent triggers this webhook listens for. Only session.ended is supported today; the list is intentionally small so adding a new event requires explicit code review.
signingTokenstringnoHMAC-SHA256 signing token for outgoing deliveries. Typically supplied via {{ Secrets.organization.NAME }} and resolved at consume time. When absent, the worker sends an unsigned request.

Evaluator#

Scores or extracts data from a session after it completes. Results are surfaced through vr evaluation list and vr evaluation info.

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: type: object properties: score: { type: integer, minimum: 1, maximum: 5 } reasoning: { type: string } required: [score, reasoning] successCriteria: score: { ">=": 4 } apiProvider: google model: gemini-3.5-flash

Common fields#

FieldTypeRequiredDescription
titlestringyesHuman-readable title (shown in evaluation listings).
evalTypejudge | extraction | deterministicyesWhether this evaluator scores a session against criteria (judge), extracts structured data (extraction), or asserts on the derived session view without an LLM (deterministic).
targetFormatevents | transcriptnoWhat the evaluator sees. events passes the raw session-event stream; transcript passes a flattened user/agent transcript. (Deterministic always reads the session view; this field is ignored.)
apiProviderstringnoLLM provider (e.g. google, openai, anthropic). Not applicable to deterministic.
modelstringnoModel id within the provider (e.g. gemini-3.5-flash). Not applicable to deterministic.
preconditionobjectnoOptional 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 evaluators#

FieldTypeRequiredDescription
systemPromptstringyesInstructions for the judge model.
responseSchemaobjectyesJSON schema describing the judge's structured response.
successCriteriaobjectyesCriteria evaluated against the judge's response — drives the success flag on the resulting Evaluation.

extraction evaluators#

FieldTypeRequiredDescription
systemPromptstringyesInstructions describing what to extract.
responseSchemaobjectnoOptional JSON schema constraining the extracted payload.

deterministic evaluators#

A deterministic evaluator asserts on the derived session view rather than calling an LLM — same input, same result, zero token cost. Use for purely factual checks: did a specific tool get called, did the caller say a specific word, was the duration in range.

FieldTypeRequiredDescription
assertionobjectyesJSON predicate (same operator set as successCriteria) evaluated against the session view. Match → success: true; mismatch → success: false with a structured details.failedPath and details.reason.
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" }

Session view fields the assertion (or a precondition) can read:

FieldDescription
turn_countNumber of completed turns (count of turn_end events). A mid-turn hangup doesn't count.
duration_secondsSeconds between startedAt and endedAt. Returns 0 when the session hasn't ended.
directioninbound or outbound
originWhere the session came from (e.g. phone, web, simulation, native)
tagsSession 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.
eventsRaw event list in arrival order — each element { name, data, timestamp }. Use with $any to assert on event names or payloads (transcript content, tool arguments, etc.).

Operators:

OperatorMeaning
$eq / $neEquals / not equals (deep equality)
$gt / $gte / $lt / $lteNumeric comparison
$in / $ninValue is / is not in a literal array
$inc / $nincInput array does / does not contain the target value
$contains / $icontainsString input contains the substring (case-sensitive / -insensitive). Returns false when either side isn't a string.
$anyInput array has at least one element matching the sub-predicate. Recurses; the sub-predicate is itself a predicate object.

Dotted field paths walk nested objects. Mixing operators and field names at the same level is rejected at evaluation time.

Bare primitive vs array input. When a predicate's value is a primitive and the input field is an array, the engine does membership matching (equivalent to $inc). This lets tags: "billing" work without $inc, and lets environment: "production" match against [id, name] regardless of whether you wrote the name or the ID. Scalar-vs-scalar equality is unchanged.

Event-payload checks via $any — ask questions like "did the caller use a phrase" or "did the agent reach a particular closing" without flattening the event stream up front:

apiVersion: voicerun/v1 kind: Evaluator metadata: name: refund-mentioned spec: title: Caller mentioned refund evalType: deterministic assertion: events: $any: name: "transcript_part" data.role: "user" data.content: { $icontains: "refund" }
apiVersion: voicerun/v1 kind: Evaluator metadata: name: agent-closed-with-goodbye spec: title: Agent ended with a goodbye evalType: deterministic 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 is made. This protects you from running expensive "did the agent handle the objection well?" evals against 1-turn hangups while keeping the skip auditable.

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 }

Filter skipped rows with vr evaluation list --status skipped or the web dashboard's status filter.


Values and Secrets#

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.

# .voicerun/values.yaml variables: LOG_LEVEL: info region: us-central1-a stt: model: flux-general-en language: en tts: provider: cartesia model: sonic-2 voice: lyric recording: enabled: false tracing: enabled: true webhook: url: null # leave null to skip the Webhook resource entirely simulation: numberOfSimulations: 1 evaluator: apiProvider: google model: gemini-3.5-flash

Secret placeholders#

Anywhere a string value lands in the rendered manifest, you can reference an organization secret as:

signingToken: "{{ Secrets.organization.WEBHOOK_SIGNING_TOKEN }}"

Helm leaves the placeholder intact through rendering. The API resolves it against organization secrets at session start, so secrets never round-trip through the release record itself. Create secrets with vr create secret.

Validation#

Both vr validate and vr render run shape-only validation against rendered manifests. The validator checks that:

  • Each document has a known kind (Deployment, Simulation, Webhook, Evaluator).
  • spec contains only the allowed top-level keys for that kind.
  • Required fields are present (e.g. Webhook.spec.url, Evaluator.spec.systemPrompt for judge, Evaluator.spec.assertion for deterministic).
  • precondition and assertion (when present) are plain objects.
  • Bounded fields are in range (e.g. numberOfSimulations is 1-100).
  • metadata.name is unique per kind within a manifest.

Validator-level checks don't hit the database, so they don't catch missing organization secrets or unknown providers — those are surfaced at vr release time when the API processes the manifest.

clitemplatesdeploymentsimulationwebhookevaluatorhelm