Voice agents
A voice agent on Aetherfy is an ordinary service agent. What makes it
different is who calls whom: in the common setups your agent connects out to
a hosted media server, and nothing sends the Aetherfy agent HTTP requests. That
one fact decides the three settings every voice agent needs.
| Setting | Value | Why |
|---|---|---|
keep_alive | true | Aetherfy’s idle timer counts only incoming requests, so an Aetherfy agent that only connects out is suspended after idle_timeout_minutes. With keep_alive: true Aetherfy keeps one machine running and does not stop it when idle |
runtime | dockerfile | The Python and Node runtimes have Aetherfy run the HTTP server and import your app; a LiveKit worker or a Pipecat bot is its own process, so the Aetherfy agent runs your container instead |
| Port | 8080 | An Aetherfy dockerfile service that does not listen on port 8080 never becomes healthy and the deploy fails: the supervisor answers GET /health with 503 while your server starts and 200 once it accepts connections on 8080 |
What an Aetherfy voice agent needs from the network
An Aetherfy agent is reached through the edge at <label>.aetherfy.dev, over
HTTP and WebSocket. It does not receive incoming WebRTC or UDP.
| Setup | Works on Aetherfy | Why |
|---|---|---|
| LiveKit Agents worker, LiveKit Cloud or your own LiveKit server | Yes | The worker opens its connection out to LiveKit |
| Pipecat bot on the Daily transport | Yes | The bot joins the Daily room out from the machine |
| Pipecat bot on the LiveKit transport | Yes | The same, out to LiveKit |
| Twilio Media Streams | Yes | Twilio opens a WebSocket to your Aetherfy agent, and the edge passes WebSocket connections through |
Pipecat SmallWebRTCTransport | No | It connects the caller’s browser directly to the bot and needs incoming UDP, which the edge cannot carry. Use the Daily or LiveKit transport |
Which Aetherfy plans allow a voice agent
An always-on voice agent needs two things, and both are plan features. Neither is available on Free.
| Plan | keep_alive: true | runtime: dockerfile |
|---|---|---|
| Free | Not available | Not available |
| Starter | Available up to 2 GB, as a paid add-on | Available |
| Performance | Available up to 4 GB, as a paid add-on | Available |
| Enterprise | Included | Available |
The add-on is priced by memory: see /platform/billing and
https://aetherfy.com/pricing . A voice agent that must not sleep is the case
keep_alive exists for. An agent without it is suspended when idle, and the
first request after that adds a short wait described in
/agents/aetherfy-yaml.
Run a LiveKit worker as an Aetherfy service
The Aetherfy manifest:
runtime: dockerfile
type: service
memory_mb: 1024
regions:
- us-east-1
keep_alive: trueA LiveKit worker runs a small HTTP server of its own for health checks. Its
default port in production is 8081, which is not the port an Aetherfy agent
listens on, so set it in code:
server = AgentServer(port=8080)AgentServer takes host and port (the LiveKit Agents documentation, at
docs.livekit.io/agents/worker/options, read 2026-10-27, names no environment
variable for either). The same server answers GET / with OK once the worker
has registered with LiveKit, and with 503 before that. Aetherfy answers its own
GET /health from a supervisor and routes only to a machine whose port 8080
accepts connections, so the worker is healthy to Aetherfy as soon as that server
is up. With a wrong LIVEKIT_URL the deploy therefore goes healthy at first and
GET / answers 503. The worker then retries its connection 16 times and exits
when they all fail (measured with a closed port), which stops the machine like any
server that exits after it started serving (/agents/dockerfile).
The Aetherfy agent’s logs show failed to connect to livekit.
Start the worker with python -m livekit.agents start agent.py as the container’s
CMD; agents.cli.run_app is the deprecated start form.
Put the LiveKit credentials in Aetherfy secrets, then redeploy, because secrets reach a machine when it is built:
afy secrets set my-voice LIVEKIT_URL=wss://example.livekit.cloud LIVEKIT_API_KEY=key LIVEKIT_API_SECRET=secret
afy redeploy my-voiceRun a Pipecat bot on the Daily transport as an Aetherfy service
Use the same manifest as above. The container must run a server on port 8080
that starts a bot for each call. Pipecat’s development runner
(python bot.py -t daily --host 0.0.0.0 --port 8080) does that, but it serves
POST /start, /client/ and /docs with no authentication, and the public
Aetherfy URL is open to anyone. Anyone who can reach it can start rooms and
spend your Daily and model credits. Write a small server of your own instead,
and give it these five properties:
POST /startneeds a bearer token from an Aetherfy secret, compared in constant time, and no other route,/docsincluded, is served.- The server refuses to start, exiting with a message on stderr, when that token
is missing or short. A server that exits before it listens fails the Aetherfy
deploy and shows that message, which is better than serving an open
/start. - A cap on bots running at once, answered with
429beyond it, sized to the agent’smemory_mb: each bot loads its own voice-activity and turn-detection models. - A bot that ends when its caller leaves, when it is ejected, and when nobody joins its room within a timeout.
- A room created for each call. Pipecat’s helper reuses the room named in the
DAILY_ROOM_URLenvironment variable when it is set, which would put every bot in one room.
The bot connects to the Daily room out from the machine, so the Daily transport needs nothing inbound.
Run Twilio Media Streams on an Aetherfy service that sleeps
A phone call does not need an always-on agent, because Twilio starts it with an HTTP request:
-
Point the phone number’s incoming-call webhook at your Aetherfy agent’s public URL. That request is a real request, so it wakes a sleeping agent, and Aetherfy holds it until the agent is listening.
-
Answer with TwiML that connects the call to a WebSocket on the same agent:
<Response> <Connect> <Stream url="wss://my-voice-k3m7x2q5hwd4nzra.aetherfy.dev/ws" /> </Connect> </Response> -
Twilio opens the WebSocket. An open WebSocket counts as activity for as long as it is held, so the agent stays up for the whole call and the idle window starts only when it closes.
The cost is the wake-up on the first ring of a call after a quiet period. Set
keep_alive: true if that matters to you. On the Node runtimes, export the
http.Server your WebSocket library is attached to, as described at
/agents/node-and-bun. Read serves_websocket on the
agent (/agents/api-workspaces) to confirm Aetherfy
sees the route.
What the public Aetherfy agent URL exposes
Everything your server serves, to anyone. An Aetherfy agent’s key authenticates you to Aetherfy, not your callers to your agent (/agents/quickstart).
| Agent | What https://<label>.aetherfy.dev shows |
|---|---|
| LiveKit worker | GET / returns OK once the worker has registered with LiveKit and 503 before, and GET /worker returns the worker’s SDK version and load as JSON. No one can start a call through it: calls arrive from LiveKit |
Pipecat bot behind your own /start check | Only what you serve. Without the check, anyone can start a bot |
| Twilio webhook and WebSocket | Your webhook and /ws. Validate Twilio’s request signature in the webhook and do not accept a WebSocket you did not hand out |
Where the Aetherfy edge has not been measured
The edge’s code relays a WebSocket upgrade, and the Aetherfy docs state that WebSocket connections are passed through to your server (/agents/dockerfile). The edge’s own measurements of 2026-08-31, however, did not measure a WebSocket upgrade end to end. Run one real call before you depend on Twilio Media Streams in production.