Skip to main content
mountAgents (SDK ≥ 0.12.0) is the front door for a function that serves agents. One call replaces the routing file you would otherwise hand-write — URL parsing, upgrade detection, credential extraction, name encoding, the hand-off to a namespace, health routes — and leaves your function holding only the policy that is actually yours. It ships at @telnyx/edge-runtime/mount.
The entry must export an object carrying fetch, not the handler itself. mountAgents returns a fetch handler, so it goes on the fetch property of the module’s default export. A bare-function default export — export default mountAgents(map) — is refused by the function runtime at load time, and the failure is a bundle that never starts rather than a request that 500s.

The address, and what selects the transport

Every agent you mount answers on the same shape, <base>/<mount>/<name>, where <mount> is a key of your map and <name> is the routing name of one instance. The request picks the transport; the path never changes: base defaults to /agents, so the map above serves /agents/conversation/alice on all three. That is the URL the AgentClient examples connect to — wss://my-func.telnyxcompute.com/agents/conversation/alice is this default base, this mount key, and this name:
A hand-written front door can serve any shape it likes; mountAgents is what makes this shape route. The upgrade is handed to the agent’s webSocket; GET and POST are handed to the agent’s fetch, where an AgentHttpServer over the agent’s connection engine answers them. The forwarded request keeps its URL and query string, so subscribe, Last-Event-ID resume, and the /rpc/<method> segment all arrive as the caller wrote them.
SSE does not stream on deployed functions today. The edge gateway buffers a response until it completes, so an open-ended text/event-stream never reaches the client — an EventSource against a deployed mount connects and then receives nothing. The WebSocket and RPC paths are unaffected, and SSE works normally in local development. This is a platform-side gap tracked as COMPUTE-819; see AgentHttpServer for the full caveat. Use the WebSocket path in production — same address, same policy.

Two gates, not one

The mount’s authorize does not replace the agent’s own authorize(token, req). It composes with it, and both run:
  1. At the edge, before any actor wakes — authorize(req, route) sees the inbound request and what the mount resolved from it (mount, name, actorId, transport, and for RPC the method). Return a Response to reject — nothing is forwarded and no actor is woken — or return headers to stamp identity onto the request, or nothing to admit as-is. It runs identically on all three transports, so no request shape reaches an agent around it.
  2. Inside the agent — the caller’s credential rides through untouched (?token=, or an Authorization header), so Agent.authorize resolves this connection’s claims from it exactly as it would unmounted, and onAttach may still veto.
Stamp identity at the mount; decide grants in the agent.
A stamped header always wins: it overwrites a header of the same name the caller sent, so a client cannot forge an identity the agent trusts. Headers you do not stamp are forwarded as the caller sent them — trust only what you stamp. The Mounting Agents guide walks the whole path, including composing the mount with routes of your own and the cross-origin opt-in.

mountAgents()

AgentMountMap

MountOptions

MountRoute

MountCorsOptions

MountAuthorizeResult

MountHeadersInit

MountTransport

Names

A routing name is what a caller writes in the address; an actor id is what the platform files an instance’s state and timers under, and only ASCII letters, digits, -, _, and . are addressable. mountAgents runs every name it resolves through encodeAgentName, so natural names — a phone number, an email address, a composite key, a name with accents or emoji — reach a persistent instance without the caller thinking about encoding. Call the same function from any front door you write by hand that must address the same instances; re-deriving the rule points a name at a different, empty instance. The ceiling is on the id once URL-escaped, not on the name — see MAX_ESCAPED_ACTOR_ID_LENGTH on the Limits page for what each kind of character costs (225 ASCII, 54 two-byte, 36 three-byte, 27 emoji, plus 6 per escaped run) and why an over-long id fails only after an eviction rather than on first use.

encodeAgentName()

decodeAgentName()

MAX_AGENT_NAME_LENGTH