Skip to Content
Agent computeVoice agents
Raw

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.

SettingValueWhy
keep_alivetrueAetherfy’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
runtimedockerfileThe 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
Port8080An 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.

SetupWorks on AetherfyWhy
LiveKit Agents worker, LiveKit Cloud or your own LiveKit serverYesThe worker opens its connection out to LiveKit
Pipecat bot on the Daily transportYesThe bot joins the Daily room out from the machine
Pipecat bot on the LiveKit transportYesThe same, out to LiveKit
Twilio Media StreamsYesTwilio opens a WebSocket to your Aetherfy agent, and the edge passes WebSocket connections through
Pipecat SmallWebRTCTransportNoIt 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.

Plankeep_alive: trueruntime: dockerfile
FreeNot availableNot available
StarterAvailable up to 2 GB, as a paid add-onAvailable
PerformanceAvailable up to 4 GB, as a paid add-onAvailable
EnterpriseIncludedAvailable

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: true

A 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-voice

Run 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 /start needs a bearer token from an Aetherfy secret, compared in constant time, and no other route, /docs included, 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 429 beyond it, sized to the agent’s memory_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_URL environment 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:

  1. 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.

  2. 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>
  3. 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).

AgentWhat https://<label>.aetherfy.dev shows
LiveKit workerGET / 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 checkOnly what you serve. Without the check, anyone can start a bot
Twilio webhook and WebSocketYour 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.

Last updated on