Skip to content

Troubleshooting

Check health:

Terminal window
curl http://localhost:7100/health

Start the API:

Terminal window
make run

If the database is missing migrations:

Terminal window
uv run alembic upgrade head

Alembic Cannot Create The Vector Extension

Section titled “Alembic Cannot Create The Vector Extension”

If migration 202607050001 reports permission denied to create extension "vector" or says the extension is missing, pgvector is not enabled in the target database. The app role is intentionally not a superuser, so enable the extension with a local Postgres admin role and retry migrations:

Terminal window
make db-enable-vector
uv run alembic upgrade head

make db-enable-vector expects DB_HOST, DB_PORT, DB_NAME, POSTGRES_ADMIN_USER, and POSTGRES_ADMIN_PASSWORD in .env or the shell environment.

Check frontend/.env.local for Firebase web config and VITE_API_BASE_URL. Check root .env for FIREBASE_PROJECT_ID. The backend must verify the same Firebase project that issued the browser ID token.

The attach control appears only when the selected catalog model declares image input. Unknown/custom models are rejected server-side with 409 even if a client is stale. Current support is image input to text output only.

If upload fails, check that the source is JPEG, PNG, or HEIC, is at most the configured source limit, is not animated or corrupt, and does not exceed the pixel/dimension limits. The server normalizes the image before enforcing the 5 MiB default stored-image limit. A partial multi-image upload never starts the model stream; remove the failing image or retry the preserved draft.

For local Compose, confirm both API and agent-run-worker mount voxos-attachments at /var/lib/voxos/attachments and use ATTACHMENT_STORAGE_BACKEND=local. For host mode, ensure ATTACHMENT_LOCAL_PATH is writable and persistent. For deployed mode, use ATTACHMENT_STORAGE_BACKEND=s3, configure the bucket/prefix/region, and verify the task role can list the prefix and get, put, and delete its objects. The bucket must remain private; do not work around a 403 with public access.

If a historical thumbnail is missing, check the authenticated content request, the tenant/chat ownership, attachment deletion state, and object-store logs. The API intentionally returns the same not-found response for a missing object and a wrong-user request.

Flightradar24 Is Missing From The MCP Catalog

Section titled “Flightradar24 Is Missing From The MCP Catalog”

The catalog always offers Deployed HTTPS endpoint. Local Compose appears only when the API has the complete Compose URL registered for the mcp purpose. The root docker-compose.yml sets this automatically, but the API container must be recreated after pulling the setting:

Terminal window
docker compose up -d --no-deps --force-recreate api agent-run-worker
make mcp-stack-up

Refresh the MCP page after both containers are running. If the local connection fails, check make mcp-stack-logs, confirm FR24_API_KEY is set in .env, and confirm both containers are attached to the voxos_default network. Do not use 127.0.0.1:8181 in a custom server entry: inside the API container, 127.0.0.1 refers to the API container itself.

The Monzo preset appears only when the API registry contains the exact http://monzo-mcp:8000/mcp URL. Root Compose configures it; recreate the API and agent worker after pulling the change:

Terminal window
docker compose up -d --no-deps --force-recreate api agent-run-worker

If the container does not start, confirm MONZO_MCP_HOST_ENDPOINT_TOKEN_FILE is an absolute owner-only regular file, contains at least 32 bytes, and is readable by the configured non-root UID/GID. Enter the same value as the preset’s MCP endpoint bearer. A 401 from the MCP usually means those two values differ.

If Monzo sign-in does not start, confirm the OAuth client ID and secret and register the exact callback URI displayed in Voxos. If the callback succeeds but tools report that authorization is required, approve the new connection in the Monzo mobile app and reconnect.

Broker 401 or 403 responses indicate a missing, expired, tampered, or wrong-server delegation. Confirm the API and agent worker use the same MCP_CREDENTIAL_DELEGATION_SIGNING_SECRET, then restart them. Broker 409 requires reconnecting Monzo; 502 means provider refresh failed.

If container startup rejects the endpoint-bearer file, confirm it is a real non-symlink regular file, owner-only, at least 32 bytes, and readable by the configured container user.

Insufficient-permission responses after login normally require approving the new connection in the Monzo mobile app. Transaction history is also subject to Monzo’s documented 90-day window after the initial five minutes. Financial writes are intentionally unavailable in the supported Compose setup.

Provider Key Saved But Runtime Is Not Ready

Section titled “Provider Key Saved But Runtime Is Not Ready”

Some providers support credential storage before worker runtime support. The Providers page marks those as Adapter needed. Runtime-ready voice integrations are OpenAI Realtime, Google Gemini Live, and xAI Grok Voice Agent.

OpenAI-Compatible Model Endpoint Cannot Connect

Section titled “OpenAI-Compatible Model Endpoint Cannot Connect”

The URL is called by the Voxos backend, not the browser. For root Docker Compose, use http://host.docker.internal:<port>/v1, not localhost. Confirm the model server listens on a host address reachable from Docker and that the host firewall permits only the intended local/Docker clients. Register the complete URL under OUTBOUND_ADDITIONAL_TRUSTED_ENDPOINTS_JSON with purpose text_provider and kind local_container.

For host-mode Voxos, register and use http://localhost:<port>/v1. Unregistered destinations require HTTPS on port 443 and global DNS. Every API and durable-worker replica that may execute a registered endpoint still needs a working network route.

Common errors:

  • A connection or DNS error: confirm the backend process or container can resolve and reach the entered hostname and port.
  • HTTP 401 or 403: enter the endpoint’s bearer key again. Saving the custom provider with an empty key intentionally selects unauthenticated access.
  • HTTP 404: the base URL normally needs its /v1 path.
  • An empty reply or textual tool syntax: confirm the model supports structured OpenAI-style tool calls and that the server has the correct tool-aware chat template or parser enabled.

Test reachability from the API container:

Terminal window
docker compose exec api curl http://host.docker.internal:11434/v1/models

The returned model ID must exactly match an ID entered on Providers. See Self-Hosted Models for current runtime commands and setup guidance.

Check:

  • A runtime-supported provider key is saved and active.
  • LiveKit credentials are saved.
  • The LiveKit dev server or LiveKit Cloud URL is reachable.
  • The room token request succeeds.
  • The selected provider model and voice are valid for that provider.
  • make worker is running and the call demand reaches ready before the configured worker startup timeout.

There is no supervisor HTTP health endpoint. Confirm liveness from the worker process log Started demand-driven LiveKit worker supervisor. A healthy cold project start then produces this correlated sequence across API and supervisor logs:

  1. Requested LiveKit project worker
  2. Assigned LiveKit project worker
  3. LiveKit project worker registered
  4. Prepared LiveKit call route

Correlate events with runtime_session_id and the opaque project_key. Do not copy an unfiltered tenant LiveKit URL into logs or support tickets; userinfo, query strings, fragments, and signed URL values must be treated as secrets.

Common demand or project-worker failure codes mean:

  • worker_start_timeout: no supervisor became ready before the API deadline. Confirm the supervisor is running and connected to the same database before increasing the timeout.
  • tenant_project_capacity_exhausted: that tenant already has the configured number of live or idle-retained project fingerprints. Reuse the saved project or wait for its idle TTL before rotating again.
  • project_capacity_exhausted: the cluster-wide live project-lease limit is full. Add supervisor capacity or wait for an idle registration to close.
  • project_call_capacity_exhausted: the shared project registration already has the configured number of ready or active calls. Retry after a call finishes.
  • invalid_livekit_settings: the tenant settings or vault record could not be resolved. Save the LiveKit settings again for that authenticated user.
  • livekit_settings_resolution_timeout: the tenant settings or vault decrypt exceeded the per-project resolution deadline. Check database/KMS latency and keep that deadline below the caller-facing worker start timeout.
  • livekit_settings_changed: settings changed after demand was created, or the API and supervisor use different VOICE_ROUTE_SIGNING_SECRET values. Align configuration and start a fresh call.
  • livekit_endpoint_not_allowed: the supervisor’s endpoint policy rejected the saved URL. Production defaults to public WSS with globally routable DNS; controlled local/private LiveKit deployments must explicitly use VOICE_LIVEKIT_ENDPOINT_POLICY=trusted_private and still constrain egress.
  • registration_timeout, registration_connection_failed, or registration_failed: the isolated child could not register with that LiveKit project. Check saved credentials, DNS/TLS, and reachability from the supervisor host.
  • worker_process_start_failed, worker_process_exited, project_worker_failed, agent_server_stopped, or agent_server_closed: inspect the adjacent supervisor traceback and host process/memory limits.
  • demand_expired: the call was not activated or heartbeated before its demand TTL elapsed.
  • worker_readiness_expired: the registration lease changed or expired between admission and token creation. Retry the call so readiness is re-established.
  • worker_lease_reclaimed or worker_registration_stopped: the owning supervisor or project child was lost; in-process calls are not resumed.
  • demand_already_active: the same route was replayed after one worker had already claimed it.
  • demand_lease_lost: an active job no longer owns its worker-fenced demand; the job shuts down instead of continuing on stale routing authority.
  • retained_project_route_conflict: a retry attempted to rotate a runtime to a different LiveKit project while its previous registration was still retained.

If the supervisor exits immediately with VOICE_WORKER_LEASE_TTL_SECONDS must exceed shutdown timeout plus two poll intervals, increase the lease TTL or reduce the shutdown timeout or poll interval. All API and supervisor replicas must also share the same VOICE_AGENT_NAME.

If projects remain pending while one supervisor is healthy, check VOICE_WORKER_MAX_ACTIVE_PROJECTS_PER_SUPERVISOR and cluster replica count. The local cap intentionally leaves excess projects pending so another replica can claim them; the cluster-wide cap rejects excess project demand.

no warmed process available for job, waiting for one to be created is an expected LiveKit Agents message for the demand-driven worker. Voxos registers the active project with zero idle job processes and creates a job process when the call arrives. It is not a registration failure. Use one local make worker process unless intentionally testing multiple supervisor replicas; database leases fence duplicate project ownership, but duplicate terminals make logs harder to correlate.

Check:

  • The server URL is remote HTTPS and uses Streamable HTTP or SSE, or is an enabled exact-host local Compose preset.
  • Auth is complete if required.
  • Server test/discovery succeeded.
  • allowed_tools is explicit.
  • The server is enabled and trusted.
  • The agent has tool grants for the discovered MCP tools.

A ToolArgumentSchemaError means Voxos rejected model-produced arguments before contacting the MCP server. The activity panel shows the failing schema path, and the bounded tool loop gives the model a chance to submit a corrected call. An McpToolCallRejectedError means the server returned HTTP 400 or 422 after local validation; it is not a connection failure.

For Google Maps Grounding Lite weather, use a specific address directly: {"location":{"address":"London, UK"}}. Use exactly one camelCase location field. Do not combine address and placeId, send extra top-level fields, or pass the places/ prefix returned by resolve_names; Voxos strips that prefix when it is the only mismatch. A forecast date must be an object with integer year, month, and day fields.

If the run still cannot recover, open the MCP server in Tools, run its connection test again to refresh discovered tool definitions, and compare the tool’s current input schema with the failed path. For example, a provider may require location or date to be structured objects rather than strings. Voxos intentionally does not display rejected argument values in these errors.

Several completed lookup_weather cards are expected for a multi-day forecast: the upstream tool accepts one date per call. Voxos runs read-only MCP calls in waves of at most eight, so a seven-day request can show seven successful cards from one tool-call wave. If one request is rejected by the server, other calls already active in that wave may also report a result, but later same-tool waves are skipped and the model is prompted to repair one request.

The Chat control should be labeled Stop run. It cancels the durable run ID from the active stream and aborts the browser request. The backend records the turn as cancelled, propagates the request to child runs, and interrupts the current provider await or every tool call in the active parallel wave. A disconnected inline chat stream is also finalized as cancelled.

If an older local build only says Stop stream, restart both the API and frontend with the current code. Inspect Operations after stopping: the run should leave running/cancelling, release its lease, and show a run_cancelled event. If an MCP server accepts a request but never responds, the MCP session read deadline is the independent backstop; check the API log for a bounded connection failure rather than an indefinitely active tool card.

Open Operations → Webhooks and confirm that the definition is enabled. A deleted, disabled, or unknown webhook returns 404. An active definition with a missing, old, duplicated, or malformed token returns 401; rotate the token and replace the caller’s saved value when its status is uncertain. Send the token in exactly one supported header, not in the query string.

The delivery must be an application/json object no larger than 16 KiB. Arrays, invalid JSON, and unsupported content types are rejected before enqueueing. If the source retries, send a stable Idempotency-Key so a timeout or 503 can wake the same durable run instead of creating another task. A 503 after enqueue can mean the worker launch failed even though the run exists; inspect Operations by the returned or previously recorded run id and check agent-run-worker logs before changing the definition.

If the run remains queued, start make worker-runs in host development or confirm the Compose agent-run-worker is healthy and uses the same database as the API. A run that fails during execution commonly needs an active text provider and valid credential for the webhook owner. The JSON body cannot grant tools; review the selected agent’s existing tool grants when the task cannot perform an expected action.

If Start sign-in reports MCP OAuth state signing is not configured, add a stable random value to the root .env:

Terminal window
python -c 'import secrets; print(secrets.token_urlsafe(32))'

Save the output as:

MCP_OAUTH_STATE_SIGNING_SECRET=replace-with-the-generated-value

Do not copy the placeholder literally, reuse a provider key, or commit the value. Recreate the Compose API container so it reloads .env:

Terminal window
docker compose up -d --no-deps --force-recreate api

For host-mode development, stop and restart make run. Then choose Start sign-in again; a state created before the secret was added or rotated cannot be reused.

OpenBnB uses OAuth metadata discovery, dynamic client registration, and PKCE, so its Voxos catalog flow does not need an OpenBnB client ID or client secret. If provider approval completes but the MCP card still says needs auth, return to the Voxos tab and select Finalize connection once. Also confirm that the callback shown in the setup panel is reachable by the browser and registered by the provider. The local OpenBnB flow supports the default loopback callback at http://localhost:7100/api/mcp/auth/callback; providers that reject loopback callbacks require a public HTTPS VITE_API_BASE_URL.

Check:

  • API_PUBLIC_BASE_URL is set to a public https:// URL.
  • The provider webhook points to the Voxos route.
  • Twilio/Telnyx signatures are valid.
  • The local tunnel is running if using ngrok.
  • Websocket routes use wss:// when provider media streams are enabled.
  • make worker is running, can read voice_call_demands, and can resolve the persisted call tenant’s LiveKit settings through tenant RLS and the vault.

Open Phone, expand the Telnyx integration, and use Verify before changing configuration. Troubleshoot from the earliest missing evidence:

  1. Find the test in Telnyx Message Detail Records by time and safe message ID. Confirm the account balance, number status, SMS capability, sender registration/compliance, and destination permissions.
  2. Check Telnyx webhook attempts. The number must be assigned to the stored enabled messaging profile, using webhook API v2 and the current public Voxos SMS URL. A changed ngrok hostname requires a confirmed Repair.
  3. Check the Phone activity panel. No message row after a successful provider send means the provider webhook did not reach or pass signature/resource validation. Do not troubleshoot the LLM first.
  4. If the reply is suppressed, the exact sender number is not a saved contact. Add the contact and send a new SMS; the older suppressed message is never replayed automatically.
  5. If the message row exists, inspect its agent-run and reply states. A queued run requires make worker-runs in host development (or the default Compose agent-run-worker), the same database/vault settings as the API, an active text provider, and a valid text credential. Inbound reply delivery does not use the agent’s send-SMS tool grant.
  6. If an outbound reply exists, follow its Telnyx message ID to the final recipient status. delivery_unconfirmed is not treated as delivered; sending_failed and delivery_failed retain only a safe error code/title.
  7. If the reply is generated or dispatching, inspect the worker failure near the correlated run. provider_accepted means Telnyx accepted the submission, not that the handset received it. ambiguous means submission may have occurred but Voxos did not receive a conclusive result; do not manually replay the run without first reconciling the provider message records.

Telnyx reports international inbound and outbound SMS as number capabilities, not messaging-profile switches. Voxos Verify or Repair can update the assigned profile and product but cannot turn a false international capability on. Ask Telnyx support for a number that explicitly reports the required capability; do not replace a UK mobile merely because another inventory result says sms. Telnyx UK coverage checked on 2026-07-21 reported international_sms: false for mobile numbers, and the normal UK mobile inventory returned no international_sms result.

A response can span multiple SMS segments even when it looks short. GSM-7 uses 160 units for one segment and 153 for multipart messages, while Unicode uses 70 and 67 UTF-16 units. Curly punctuation or emoji can switch the whole message to Unicode. Voxos records its estimated encoding and segment count and attempts one semantic condensation pass; it never fixes overflow by cutting the answer at a raw character boundary.

Telnyx retries slow or unsuccessful webhooks. Voxos claims inbound messages by provider message ID before enqueueing, so a retry should still show one message and at most one run. Record timestamps and safe IDs only; do not copy message bodies, complete phone numbers, webhook route keys, API keys, or raw payloads into logs or support notes.

Outbound status sends use their per-message status callback while inbound traffic continues to use the messaging profile webhook. If inbound messages appear on the status route or final outbound state never arrives, inspect the profile-versus-message webhook settings and run Verify again.

Start with the prerequisite:

  1. The exact number must be a connected Twilio or Telnyx phone resource in the current Voxos tenant.
  2. That same E.164 number must be accepted by the WhatsApp Business mobile app. A Telnyx/Twilio number appearing in Voxos does not guarantee WhatsApp will accept it for mobile-app registration.
  3. The API, agent-run-worker, and whatsapp-worker must share the same database and secret-encryption/KMS configuration.

For host development, run:

Terminal window
make worker-runs
make worker-whatsapp

For Compose, run:

Terminal window
make up
docker compose --profile whatsapp ps whatsapp-worker
docker compose --profile whatsapp logs whatsapp-worker

The container healthcheck requires a fresh worker health file, a successful database poll, and a connected bridge. Sustained failed or stalled polls cause the worker to exit so Compose restarts it automatically. If it exits before polling, check migrations, database/RLS access, vault encryption configuration, the Node executable, and the compiled bridge script. Node.js 20 or newer is required for a host worker.

If the UI shows no eligible number, reconnect the Twilio or Telnyx integration and phone resource. Setup options exclude disabled, deleted, non-E.164, and wrong-tenant resources. Voxos currently permits one linked WhatsApp session per user; disconnect the existing one before selecting another number.

For a new mobile registration, choose a verification method immediately before requesting the code in WhatsApp Business. The capture window is ten minutes and matches the exact selected provider resource.

  • For Telnyx, choose Verify with a call, then choose Call me in WhatsApp Business. The incoming setup call should be answered without starting the normal voice agent. WhatsApp can first request a varying DTMF digit; Voxos extracts the digit from the final prompt transcript, sends that exact digit, then collects the spoken code across subsequent transcript events. The spoken code should appear in the panel. If it does not, check the signed Telnyx voice webhook, call.transcription delivery, and the safe verification_dtmf_sent event summary.
  • Choose Verify with SMS for Twilio or Telnyx. If a code does not appear, confirm that the provider’s SMS webhook is publicly reachable and receiving the message. It must pass the normal signature/account/number checks and contain a plausible numeric code. The parser accepts variable formatting and a bounded four-to-eight-digit code; users and operators must not assume a particular length or grouping.

If there is no inbound provider event, stop debugging the frontend or agent worker: Voxos never received the registration traffic. Check the number’s international-inbound capability and switch to the call fallback. A domestic SMS test proves only domestic routing and does not prove that an international OTP route will work.

Arm Voxos before every WhatsApp request. If the capture window expired or a previous attempt was cancelled, select Verify with a call or Verify with SMS again and wait for the corresponding waiting state before requesting the code. Caller numbers and requested DTMF digits can vary, so neither should be allowlisted as a fixed value. If WhatsApp imposes a retry timer, wait for it to finish; repeated requests can extend the lockout.

A code delivered outside the active window follows the normal provider path. If the wrong code is captured, choose Not the WhatsApp code. An SMS is replayed once through normal SMS handling; a voice candidate is deleted.

Voxos cannot enter the registration code into the mobile app. Enter the shown code manually, wait for WhatsApp Business to confirm the number, then choose I entered this code.

If the number is already registered, select It is already registered and then Link WhatsApp. If the panel says it is preparing a link code, keep the worker running: the challenge API can return challenge_not_ready while the bridge creates a code, and the frontend polls again. QR and pairing codes expire after the configured challenge TTL; use Refresh code rather than scanning an obsolete image. The UI intentionally hides the code while settings are read-only.

For a pairing code, enter it under WhatsApp Business Linked devices using Link with phone number instead. Do not enter it as the mobile app’s initial account-registration SMS code; these are two separate codes and steps.

number_mismatch means the authoritative WhatsApp PN identity differs from the selected provider number. pn_identity_unavailable means WhatsApp reported only an LID or another identity that cannot prove the phone number. Neither can be overridden in the browser. Voxos removes the attempted linked device and requires a fresh link for the correct WhatsApp Business account.

needs_relink means WhatsApp revoked or invalidated the companion session. Confirm that the mobile account is still active, then use Relink WhatsApp. reconnecting can be transient; persistent reconnecting usually means the worker cannot restore the encrypted session or WhatsApp no longer accepts it. waiting_for_capacity means all configured session slots are occupied; add a worker replica or raise WHATSAPP_MAX_SESSIONS_PER_WORKER only within the bridge’s advertised capacity.

If inbound text appears to do nothing:

  • An unknown LID-only sender is recorded with a suppressed reply and receives no automated response. It remains unlinked until WhatsApp supplies an authoritative PN/LID mapping that matches a saved contact. Send a new message after the contact is linked; an older suppressed message is not replayed.
  • A stored identity marked conflict means WhatsApp aliases previously pointed at different people or phone numbers. Voxos intentionally withholds saved contact context and suppresses automated replies instead of guessing.
  1. Confirm the panel is connected, not merely linked or reconnecting.
  2. Test a new direct or group text from another account. Broadcasts, newsletters/status, media, historical message appends, and messages sent by the linked account are intentionally ignored.
  3. If the activity reply is suppressed, confirm that the sender has an exact saved-contact match. Otherwise, check that a durable communication_reply run was queued and that agent-run-worker has an active text provider and credential.
  4. Check the WhatsApp outbox. queued needs a connected whatsapp-worker; submitting is in flight; sent means WhatsApp returned a message ID; failed is terminal; and ambiguous means submission may have happened and must not be automatically replayed.

If a group participant is shown only as an LID, confirm the linked account can still see the participant in the group. Voxos checks current group metadata and the encrypted Baileys LID mapping store. If no authoritative mapping can match the participant to a saved phone-number contact, Voxos records the group message but suppresses its automated reply.

The linked-device transport does not yet persist recipient delivery receipts, so provider_accepted/sent is not proof that a handset displayed the reply. For proactive chat messages, the approved action queues delivery only after it resolves a saved contact and verifies the connected linked-device session.

If disconnect remains in disconnecting, check that whatsapp-worker is healthy and has a fresh successful poll before waiting for the remote logout. The local Compose worker ignores cache artifacts, so test-created Python bytecode must not restart a live linked-device session. A healthy restarted worker reclaims an expired session lease and retries the logout safely. Do not delete database rows manually; that could leave the mobile account’s linked device active.

Finally, protocol or account restrictions can be external to Voxos. WhatsApp may change companion-device behavior, limit the account, or revoke a device. Do not bypass those controls. Review WhatsApp and carrier terms, recipient consent, privacy/data-processing requirements, and bridge dependency licenses before a production rollout.

Outbound Call Asks About Contacts Or Loses Its Goal

Section titled “Outbound Call Asks About Contacts Or Loses Its Goal”

For a named recipient, the text agent should call search_contacts before asking whether the person is saved. Check the selected agent’s Tools grants: connecting a call-capable resource adds the low-risk prerequisite only when no explicit decision exists. An explicit search_contacts=denied decision hides the dependent place-call tool. One result proceeds, multiple results require a choice, and no result requires a number or new contact.

Every new agent-started call must contain both the returned contact ID and a non-empty objective. If the call connects but starts with generic assistance, inspect the tenant call row using safe IDs: it should have an outbound_objective, stable media_participant_identity, agent_ready_at, and opening state. Do not paste the objective or full destination into logs or support notes. The runtime prompt snapshot may contain the objective for auditing because it is tenant-owned persisted context.

No opening should be generated until answered_at and media_ready_at are both present. Telnyx streaming.started is media evidence only; it must not set answered_at. An immediate callee “hello” cannot win the first-turn race because input remains disabled until the objective kickoff is scheduled, after which normal listening and interruption are enabled.

If dialing never begins, distinguish project readiness from per-call readiness:

  • worker_start_timeout means no project worker registered.
  • voice_agent_readiness_timeout means explicit dispatch did not reach agent_ready_at before VOICE_AGENT_READY_TIMEOUT_SECONDS.
  • outbound_call_ended_before_agent_ready means the reservation ended while the API was waiting.
  • outbound_media_ready_timeout means the carrier bridge did not become ready after answer.
  • outbound_opening_failed means bounded generation/playout attempts were exhausted.

Use GET /api/integrations/{integration_id}/calls/latency?provider=twilio (or telnyx) for safe sample counts and p50/p95 component timings. It does not return objectives, phone numbers, transcripts, or raw provider data. Carrier doubles verify ordering and idempotency, not real delivery or audio latency; establish a baseline with approved destinations before setting an SLO.

Voxos checks LiveKit first, then reads the Telnyx number, Voice API application, outbound voice profile, assignment, enabled state, and non-empty destination allowlist before reserving worker demand. Use the Phone readiness result to distinguish LiveKit routing from Telnyx routing. Agent tools also require a saved tenant-owned contact; authenticated direct API calls require a valid E.164 destination.

If Telnyx rejects the dial after worker admission, the call activity row shows a bounded failure code and Voxos cancels the reserved demand. Correlate the call-session ID with the provider call-control ID and safe Telnyx error code. Do not immediately retry an ambiguous provider response because outbound call submission is not durably idempotent across a new request.

If the signed webhook succeeds but the media websocket closes with code 1011, look for Closing Twilio media stream because LiveKit voice routing is not configured or the corresponding Telnyx log. Then use the voice failure-code guidance above; one tenant’s routing failure should not stop other project workers.

If a Telnyx call answers and the persisted agent enters speaking but the caller hears silence, look for Started Telnyx PCMU media downlink. That log means Voxos subscribed to the agent track and sent a valid 20 millisecond, 8 kHz mono PCMU frame to Telnyx. If it is absent, inspect the adjacent call-session/runtime-session correlation for media_downlink_failed.

If the agent cannot hear the caller, look for Started Telnyx PCMU media uplink. It is emitted once after the first valid caller PCMU payload is decoded and published to LiveKit. If uplink is present but no user speech state or transcript follows, confirm the carrier bridge published its synthetic track as LiveKit SOURCE_MICROPHONE; Agents RoomIO ignores SOURCE_UNKNOWN for user audio. Then inspect participant subscription and model VAD. If uplink is absent, inspect the Telnyx stream configuration and incoming media frames before changing model settings.

The API and isolated voice job configure the voxos logging namespace at INFO without raising third-party SDK log levels. A healthy Telnyx exchange should make Activated LiveKit call demand, Started Telnyx PCMU media uplink, Started Telnyx PCMU media downlink, and Completed LiveKit call demand visible using server-derived correlation fields only.

A Telnyx or Twilio call should not end merely because both sides are silent for VOICE_INACTIVITY_TIMEOUT_SECONDS; that timeout applies to direct LiveKit sessions. Correlate Activated LiveKit call demand and Completed LiveKit call demand by runtime session and check their safe source and close_reason fields. For phone sources, a normal completion should follow the verified media participant disconnect, provider stop, an explicit trusted action, lease loss, or a real model/session failure—not a generic away event. A dropping pass-through signal — no stream available warning immediately after an old 30-second shutdown usually reflects room deletion after the premature close rather than the original cause.

media_stream_protocol_error means Telnyx returned a websocket media error. Voxos persists a sanitized media_stream_error containing only the documented numeric code, bounded title, and stream identifier; raw details, media bytes, phone numbers, route keys, and authentication tokens are not logged. Common provider titles include invalid_media and rate_limit_reached.

A normal Telnyx stop frame or websocket disconnect is awaited during bridge shutdown and should not produce Task exception was never retrieved. If that warning appears, verify the API is running the current bridge code and capture the first preceding media_downlink_failed or protocol-error log.

Run:

Terminal window
make docs-validate

Fix missing Last updated lines, broken links, duplicate titles, or a stale manifest. Regenerate the manifest if page content changed intentionally.