Deploying the Voicebox Service
Page Contents
Applies to: Launchpad v4.0.0+ · Voicebox Service v1.0.0+
Upgrading from an earlier Launchpad release requires a configuration change in every deployment. See the 4.0.0 release notes for the steps.
A practical guide for operating the Voicebox Service: how it stores the results it computes, which storage backend to choose, what to tune, and what to watch.
For a quickstart — pulling the image and running the service — see Running the Voicebox Service. For the vbx-config.json file and the supported LLM providers, see Voicebox Configuration File.
Background
The Voicebox Service produces frames: the tabular results of the queries it runs against Stardog to answer a conversation turn. It persists them so a later turn can reuse a previous result without re-querying Stardog.
Conversation memory is separate from frames. Launchpad resends the conversation lineage on every turn, so the server holds no per-conversation memory between turns. The practical upshot is that frames are an optimization, not a source of truth. If a frame is missing — disk full, object expired, volume lost — nothing is corrupted.
This is the main way v1.x differs from the v0.x service, which kept no frames and recomputed every result.
Frame Stores
The backend is selected with VOICEBOX_FRAME_STORE_BACKEND. Every backend writes frames as snappy-compressed Parquet, one object per frame, in the same layout: {prefix}/{conversation_id}/{frame_id}.parquet.
Choosing a Backend
local (default) | s3 | azure | |
|---|---|---|---|
| Frames live on | the container’s own disk | an S3 bucket, AWS or otherwise | an Azure Blob container |
| Persistent volume | required | not needed | not needed |
| Replicas | exactly one | multiple | multiple |
| Expiring old frames | built-in sweeper, on a TTL | bucket lifecycle rule, which you configure | Blob lifecycle rule, which you configure |
| Credentials | none | standard AWS credential chain, or the store’s own key pair | connection string, account key, SAS token, or an Entra identity |
| Suited to | a single-node deployment, or evaluation | a deployment in AWS, or with an S3-compatible store of its own | a deployment already running in Azure |
The deciding constraint is usually replica count. local pins the service to a single instance, because the store is that instance’s own disk and a second replica would read an empty one. The s3 and azure backends remove both the volume requirement and the single-instance limit. s3 covers Amazon S3 and any S3-compatible store, such as MinIO or Dell ObjectScale, which is usually the practical choice for an on-premises deployment.
Local Disk
The default. Frames are written under the frame store path, which defaults to /var/lib/voicebox/frames.
Mount a writable volume at that path. Without one, frames land in the container’s writable layer and vanish on restart — the service still works, it just re-fetches from Stardog every time.
- Docker (named volume): Mount it at the frame store path. The image pre-creates that directory owned by the non-root container user, and a named volume inherits that ownership, so no extra steps are needed. Prefer a named volume over a bind mount.
- Kubernetes: Use a single-replica deployment with a
ReadWriteOncepersistent volume mounted at the frame store path, aRecreateupdate strategy, and a security context that makes the mount writable by the non-root container user.
Two behaviors are specific to this backend:
- Eviction is built in. A background sweeper deletes frames older than the TTL and reaps orphaned
.tmpfiles after an hour. No external cron or cleanup job is needed. The sweeper does not run on thes3orazurebackends, where expiry is the storage service’s own lifecycle rule. - Disk-full is non-fatal. When the volume fills, the user still gets their answer; the frame just isn’t written and the service logs a warning. Recover by growing the volume or lowering the TTL.
| Environment Variable | Default | Notes |
|---|---|---|
VOICEBOX_FRAME_STORE_LOCAL_PATH | /var/lib/voicebox/frames | Mount the volume here. |
VOICEBOX_FRAME_STORE_LOCAL_TTL_DAYS | 7 | Lower on a small volume to reclaim disk faster; raise for longer-lived conversations. Must be > 0 while the sweeper is enabled. |
VOICEBOX_FRAME_STORE_SWEEPER_ENABLED | true | Set to false to disable the background eviction sweeper entirely. |
VOICEBOX_FRAME_STORE_SWEEP_INTERVAL_SECONDS | half the TTL | How often a sweep runs. Leave unset unless you need a specific cadence. |
VOICEBOX_FRAME_STORE_LARGE_FRAME_WARN_MB | 10 | Lower for earlier oversized-frame warnings; 0 disables them. |
Sizing. Rough disk need is avg frame size × frames per turn × turns per day × TTL days. 20 GB is a comfortable start for a small team at the 7-day default. The service periodically logs volume telemetry, which you can use to trend real usage and resize from data.
Amazon S3
Set VOICEBOX_FRAME_STORE_BACKEND=s3.
| Environment Variable | Default | Notes |
|---|---|---|
VOICEBOX_FRAME_STORE_S3_BUCKET | not set | Required for this backend. The service fails to start without it. |
VOICEBOX_FRAME_STORE_S3_PREFIX | voicebox/frames | Key prefix within the bucket. Bucket-relative. |
Credentials are not a Voicebox setting. The service uses the standard AWS credential chain, so on EKS an IAM role for the service account is usually all that is needed; configure it the way you would for any other AWS client in your deployment. The bucket region normally comes from the same AWS environment, such as AWS_REGION, and VOICEBOX_FRAME_STORE_S3_REGION overrides it when you need to set it explicitly.
Configure expiry with an S3 lifecycle rule on the bucket. The built-in sweeper does not run on this backend, so without a lifecycle rule frames accumulate indefinitely.
Example
A complete environment file for a service running on AWS, using Bedrock for the LLM and S3 for frames.
# The JSON configuration file, as in any deployment
VBX_CONFIG_FILE=/voicebox-config/vbx-config.json
# Frames in S3 rather than on local disk
VOICEBOX_FRAME_STORE_BACKEND=s3
VOICEBOX_FRAME_STORE_S3_BUCKET=my-voicebox-frames
# The region the LLM is served from
BEDROCK_REGION=us-west-2
# The region the bucket is in, read by the AWS client rather than by Voicebox.
# Omit it and the credentials below if the pod assumes an IAM role.
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID=<access_key_id>
AWS_SECRET_ACCESS_KEY=<secret_access_key>
with the matching vbx-config.json:
{
"default_llm_config": {
"llm_provider": "bedrock",
"llm_name": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
"max_tokens": 16000
}
}
BEDROCK_REGION and AWS_REGION are separate settings and do not have to match. The first selects where the model is served from, the second where the bucket lives.
No volume is mounted and no replica limit applies, so the service can be scaled like any other stateless deployment.
S3-Compatible Stores
The s3 backend is not limited to AWS. Point it at any store that speaks the S3 API by setting an endpoint URL; everything else stays the same, including the bucket and prefix settings, the key layout, and lifecycle-based expiry.
| Environment Variable | Default | Notes |
|---|---|---|
VOICEBOX_FRAME_STORE_S3_ENDPOINT_URL | not set | The store’s S3 endpoint, including the scheme. Unset means AWS S3. Must start with http:// or https://, or the service fails to start. |
VOICEBOX_FRAME_STORE_S3_ADDRESSING_STYLE | auto | auto uses path-style addressing whenever an endpoint is set, and the SDK default otherwise. Force it with path or virtual. |
VOICEBOX_FRAME_STORE_S3_REGION | not set | The region to sign requests with. Unset defers to the surrounding AWS environment. Some stores validate this and reject a value that does not match theirs; others ignore it but still need one to be present. |
Setting an endpoint changes two client behaviors on your behalf, because most non-AWS implementations need it. Requests use path-style addressing, since custom endpoints rarely have the wildcard DNS that virtual-hosted addressing requires, and the CRC checksums that recent AWS SDKs attach by default are not sent unless an operation requires one, since many implementations reject them. If your store prefers virtual-hosted URLs, override the first with VOICEBOX_FRAME_STORE_S3_ADDRESSING_STYLE=virtual.
Authentication is the store’s own key pair. Supply it in the standard AWS variables the client already reads — AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY — whatever the store calls those values in its own console. Temporary credentials work too: add AWS_SESSION_TOKEN alongside them. There is no separate Voicebox credential setting for this backend, and nothing is discovered automatically outside AWS, so a key pair is required, in the environment or anywhere else the AWS credential chain looks.
Because every request is signed with a key pair, this backend cannot use identity-based access such as Azure Managed Identity or GCP Workload Identity. On Azure, use the azure backend for that.
If you use Bedrock as the LLM provider, set BEDROCK_PROFILE so its credentials come from a named AWS profile rather than the environment. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY belong to the object store here, and Bedrock would otherwise pick them up and fail to authenticate.
The bucket must already exist; the service never creates it. Expiry works as it does on AWS: the built-in sweeper does not run, so configure the store’s own lifecycle or object-expiration rule against the bucket and prefix. If the store has no such feature, schedule a job that deletes objects under the prefix past a chosen age.
Any S3-Compatible Store
Any store that exposes an S3-compatible API and accepts SigV4 request signing works through these same settings. There is no per-provider code path and no allowlist, so what works is simply whatever speaks S3: MinIO, SeaweedFS, Dell ECS and ObjectScale, NetApp StorageGRID and Cloudflare R2 among them, and Google Cloud Storage through its S3 interoperability endpoint.
Three of the values belong to the store rather than to Voicebox, so take them from your provider’s documentation:
- The endpoint URL, with the scheme and, where it is not the default, the port. These vary by product and by deployment.
- The region, where the store validates one. Some stores reject a signature whose region does not match the one they are configured with; others ignore the value but still require one to be present.
- The key pair, which each store issues under its own name for the idea: an access key and secret, an object user’s secret key, an API token that yields a key pair, or an HMAC key.
On Google Cloud Storage, Voicebox authenticates with an HMAC key, since every request this backend makes is signed with a key pair. The constraints/storage.restrictAuthTypes organization policy can deny HMAC-signed requests, and where it denies the key type you would use, keys of that type cannot be created or activated and existing ones stop working, leaving this backend unavailable for the affected project.
Example
An environment file for a service storing frames in MinIO on the same network.
# The JSON configuration file, as in any deployment
VBX_CONFIG_FILE=/voicebox-config/vbx-config.json
# Frames in an S3-compatible store rather than on local disk
VOICEBOX_FRAME_STORE_BACKEND=s3
VOICEBOX_FRAME_STORE_S3_BUCKET=voicebox-frames
VOICEBOX_FRAME_STORE_S3_ENDPOINT_URL=http://minio.internal:9000
# A region must be set when the surrounding environment supplies
# none. us-east-1 works with a default MinIO.
VOICEBOX_FRAME_STORE_S3_REGION=us-east-1
# The key pair the store itself issued
AWS_ACCESS_KEY_ID=<access_key_id>
AWS_SECRET_ACCESS_KEY=<secret_access_key>
As with AWS, no volume is mounted and no replica limit applies.
Azure Blob
Set VOICEBOX_FRAME_STORE_BACKEND=azure.
| Environment Variable | Default | Notes |
|---|---|---|
VOICEBOX_FRAME_STORE_AZURE_CONTAINER | not set | Required for this backend. The service fails to start without it. |
VOICEBOX_FRAME_STORE_AZURE_ACCOUNT | not set | Storage account name, expanded to https://<account>.blob.core.windows.net. Required unless a connection string is set. |
VOICEBOX_FRAME_STORE_AZURE_PREFIX | voicebox/frames | Blob name prefix within the container. |
VOICEBOX_FRAME_STORE_AZURE_CONNECTION_STRING | not set | Carries its own endpoint and key. The simplest single-value option. |
VOICEBOX_FRAME_STORE_AZURE_ACCOUNT_KEY | not set | Shared account key, paired with the account name. |
VOICEBOX_FRAME_STORE_AZURE_SAS_TOKEN | not set | A scoped, expiring alternative to the account key. |
Credentials resolve in that order: connection string, then account key, then SAS token, then the ambient Azure identity. Leaving all of them unset is the no-secrets path, which picks up a service principal from the environment, AKS workload identity, a managed identity, or a developer’s az login session. The startup log records which one was used, so a silent fall-through to the wrong credential is visible rather than mysterious.
One thing about the storage account is worth getting right up front, because it is not obvious once you hit it: the role on the container must be a data-plane role, such as Storage Blob Data Contributor. Control-plane roles like Owner and Contributor grant no access to blob data at all — an account that looks fully permissioned in the portal can still return 403.
Either kind of account works. An account with hierarchical namespace enabled (Azure Data Lake Storage Gen2) serves the same Blob API, and lifecycle rules apply there too, so no separate configuration is needed. Voicebox has been verified against an account without it.
Configure expiry with an Azure Blob Lifecycle Management rule, using daysAfterModificationGreaterThan for the age condition. One difference from S3 catches most people out: the rule’s prefixMatch starts with the container name, so for container frames and the default prefix the value is frames/voicebox/frames, not voicebox/frames. Blob soft delete must also be off, or deleted frames are retained anyway.
Azure evaluates lifecycle rules asynchronously, and the first pass can take up to 48 hours. Do not conclude a rule is broken because nothing disappeared overnight.
Example
An environment file for a service running in Azure, storing frames in a Blob container.
# The JSON configuration file, as in any deployment
VBX_CONFIG_FILE=/voicebox-config/vbx-config.json
# Frames in Azure Blob rather than on local disk
VOICEBOX_FRAME_STORE_BACKEND=azure
VOICEBOX_FRAME_STORE_AZURE_ACCOUNT=myvoiceboxstorage
VOICEBOX_FRAME_STORE_AZURE_CONTAINER=frames
# Omit this and the service uses the ambient Azure identity instead,
# such as a workload or managed identity
VOICEBOX_FRAME_STORE_AZURE_ACCOUNT_KEY=<account_key>
Timeouts and Retries
These apply to the s3 and azure backends, which reach a store over the network. The local backend ignores them.
| Environment Variable | Default | Notes |
|---|---|---|
VOICEBOX_FRAME_STORE_TIMEOUT_SECONDS | 10 | Connect and read budget for one attempt at a frame operation. The read timeout applies per socket read, so a large frame transferring steadily is unaffected; only a stalled connection trips it. Must be >= 1. |
VOICEBOX_FRAME_STORE_MAX_RETRIES | 1 | Retries on top of the first attempt, so the worst case is roughly timeout × (retries + 1). Set to 0 to fail on the first attempt. |
Raise these for a store that is slow to respond, but keep the resulting budget modest. A stalled operation holds up a turn, and loading a conversation walks many frames in sequence, so any per-operation budget multiplies.
Tuning the Agent
These apply to every backend. Most deployments only touch these; leave the rest at their defaults.
| Environment Variable | Default | When to change |
|---|---|---|
VOICEBOX_CODE_EXEC_TIMEOUT_SECONDS | 30 | How long the service may spend analyzing query results while answering a question, in seconds. Raise if large result sets are timing out during analysis; must be >= 1. |
VOICEBOX_QUERY_EXEC_TIMEOUT_SECONDS | 60 | Timeout applied to each query the service runs against Stardog, in seconds. Raise for slow queries on large databases; set to 0 to apply no Voicebox timeout, so the Stardog endpoint’s own configured query timeout governs. |
VOICEBOX_QUERY_MAX_RESULTS | 100000 | Maximum rows a single query may return, applied as the query LIMIT. Lower it to reduce memory use and frame sizes on wide result sets; must be >= 1. |
VOICEBOX_RECURSION_LIMIT | not set | Maximum number of steps the agent may take to answer one question. Unset uses the built-in default (35). Raise for complex, multi-step questions that report running out of steps; must be >= 1. |
VOICEBOX_FRAME_STORE_CACHE_SIZE | 100 | Frames held in each instance’s in-memory read cache. Raise to cut reads against the store on long conversations, at the cost of memory. |
Health and Readiness Probes
- Liveness:
GET /system/health(returns204). - Readiness:
GET /system/storage-ready(200when the frame path is writable,503otherwise).
Keep the disk-writability check on readiness, not liveness, so a transient full disk pulls the instance from traffic instead of restarting it into a crash loop.
On the s3 and azure backends, /system/storage-ready always returns 200 with "checked": false in the body. That means the check does not apply here, not that the bucket or container was verified. Do not treat a healthy readiness probe as evidence that the store is reachable.
The service does check the store once, at startup, and logs the result: look for frame_store.startup_check_ok, or frame_store.startup_check_failed with a reason such as a missing bucket, denied access, or an unreachable endpoint. The check is advisory and never blocks startup.