You have a Plivo Voice app. Plivo’s model is an answer URL. Their cloud calls your web server. Your server returns XML verbs: <Speak>, <Play>, <GetDigits>, <Dial>. Plivo executes those verbs against the call. ClutchCall inverts that model. Your code runs as an external agent. The external agent is a worker. The worker dials one outbound QUIC/WebTransport session to the engine. It authenticates with a media app key/secret. It registers under an agent handle. The engine routes calls to the worker through a VENDOR_BRIDGE agent config. It hands each live call to your function. Audio flows in both directions as 20 ms pcm16 frames at 8 kHz. You do not need Flask, a public URL, or ngrok. You do not return XML for a remote executor to interpret. Your process is the executor.

What maps to what

Port the voice quickstarts

The runnable starter is a port of Plivo’s Python voice quickstarts (greet_caller.py, receive_calls.py, forward_calls.py, text_to_speech.py). It ships in the provider starters repo next to the telequick-agents SDK.
1

Install the SDK

2

Replace the answer URL with a handler

Plivo’s greet-the-caller flow (answer, <Speak> a greeting, hang up) becomes one coroutine:
greet_caller.py
send_transcript lands the turn in the platform transcript store. It fires the voice.transcript.ready webhook. Plivo’s XML model never had this piece.
3

Create the external agent and route a DID

The console’s External agents create flow (agent.clutchcall.dev) does three tasks:
  • It reserves the agent handle.
  • It mints the media key/secret. AgentConfig.from_env() reads them from TELEQUICK_MEDIA_KEY / TELEQUICK_MEDIA_SECRET.
  • It writes the VENDOR_BRIDGE config.
Route your number to the agent (see SIP Trunking). Then dial the number.
4

Port the REST calls

Management operations use an mpk_ API key (console → Settings → API keys) against POST /mcp. The SDK wraps it. Plivo’s calls.create(..., answer_url=...) becomes originate with an agent. On answer, the engine connects the callee to your worker:
originate_call.py
The forward_calls.py flow (<Dial>) is voice.calls.transfer with the call’s sid. voice.calls.hangup ends a leg. For lifecycle events (answer/hangup callbacks), register a signed webhook with webhooks.create {vertical:'voice', url, eventTypes}. Check deliveries with telequick_agents.webhooks.verify_signature.

Troubleshooting

Your WAV is not 8 kHz mono 16-bit PCM. send_audio is a raw passthrough at the call’s sample rate. Resample the file offline. Do not send 44.1 kHz samples onto an 8 kHz leg.
A return from the handler ends the call (Plivo’s implicit end-of-XML). send_audio queues audio. Wait for playout, or for the caller, before you return.
vendor_room in the agent config must exactly match the handle that the worker registered (TELEQUICK_AGENT). The DID must route to that agent. Presence is tied to the live session. Check for a ready line after the most recent reconnect.

LiveKit integration

The external-agent seam in depth: the same worker pattern with a full agent framework on top.

Run a LiveKit Agent on the Transport

The end-to-end external-agent recipe: register a worker, route a number, take a call.

SIP Trunking

Bring your numbers over, or point a Plivo SIP trunk at the platform.

Telnyx integration

The same port for Call Control’s command/webhook model.