mountAgents, every project wrote the function’s front door by hand, and each copy differed a little.
- It parsed the URL and checked for an upgrade.
- It pulled the credential and encoded the name.
- It picked the binding and called
idFromName(...).fetch(req). - It answered health checks.
mountAgents (SDK ≥ 0.12.0) is that front door, already written. The user supplies the routing table and the policy, and the mount supplies the rest.
404 with no binding consulted. It is a function of env, so bindings resolve per
request and nothing is cached between them.
Each value is an actor binding, an ActorNamespace. An
[[actors]] block in telnyx.toml declares it, as the Stateful Actors
configuration reference shows.
One Address, Three Transports
Each mounted agent answers on<base>/<mount>/<name>:
<base>is the path prefix the mount owns,/agentsunless the option sets another,base.<mount>is a key of the map above.<name>is the routing name of one instance — one agent, one persistent identity.
/agents/conversation/alice and
/agents/billing/acct-1004.
Each name reaches one actor instance, the same one on every
request. The first request brings it up, as
Lifecycle & Placement describes.
The shape of the request selects the transport, not the path. The same address
answers all three:
This is why the AgentClient reference
connects to
wss://my-func.telnyxcompute.com/agents/conversation/alice — that URL is
the default base, the conversation mount key, and the name alice. A browser
talking to the function above needs no route of its own:
result / error
frame a WebSocket caller would receive. Only methods decorated with
@rpc() are callable.
Authorization Composes — It Does Not Replace
The mount adds no policy of its own. It routes, and it runs the oneauthorize
callback it is given. That callback does not stand in for the agent’s own
authorize(token, req): both run, and they do different jobs.
Gate one, at the edge. authorize(req, route) runs after the address resolves and
before anything touches an actor, on every transport — an upgrade, an SSE stream,
and each RPC POST alike. It receives the request unmodified and the resolved
route: the mount key, the decoded
name, the actor id, the transport, and the method for an RPC. Answer with a Response
to reject (nothing is forwarded, no actor wakes), with headers to admit and stamp, or
with nothing to admit as-is.
Gate two, inside the agent. The caller’s credential rides through untouched —
?token= or an Authorization header, exactly as sent. The agent’s
authorize resolves this
connection’s claims from it
exactly as it would with no mount in front, and
onAttach may still veto.
- A stamped header overwrites any header of the same name the caller sent, so a client cannot forge an identity the agent trusts.
- Headers the mount does not stamp are forwarded as the caller sent them. The agent trusts only what the mount stamps.
authorize is not an admission — it propagates and the request
never reaches an actor. Reading the body is safe: the agent is handed an independent
copy, so a signature verified here still arrives there intact. That copy is made only
for a request that carries a body, and it does buffer while both copies are outstanding
— on a large streamed upload, prefer deciding from the headers.
Names Customers Already Use
Actor ids are not free-form: only ASCII letters, digits,-, _, and . are
addressable, and an id built from anything else belongs to an instance that never
activates. The names an application routes on are not so confined — a phone number, an
email address, a composite key, a name with accents or emoji.
The mount decodes the name segment and re-encodes it with
encodeAgentName, which maps any
name onto the addressable set reversibly:
/agents/conversation/%2B15550100 therefore addresses the agent for +15550100, and
no two names ever share an instance. Three things to keep in mind:
- The mapping is part of an actor’s identity and is frozen. The id a name first
resolves to is where that instance’s state and timers live. Do not re-derive the rule
by hand — call
encodeAgentNamefrom any other front door that addresses the same instances, or that door points the name at a different, empty instance. - Case is significant.
aliceandAliceare two agents. Names meant to reach one agent are lowercased before routing. - Encode the raw name exactly once. Encoding an already-encoded name escapes its
:signs and addresses something else.
How Long a Name May Be
The limit is on the actor id once URL-escaped —MAX_ESCAPED_ACTOR_ID_LENGTH,
225 characters — because the id is carried into the name of the file the instance’s
persistent state is kept in, which is capped at 255 bytes. How many characters of name
that allows depends on which characters they are, since an escape is longer than what it
replaces:
Every separate escaped run also spends 6 on the pair of
: bracketing it, so a mixed
name fits less than any single row suggests. A name whose id would exceed the ceiling is
refused by the mount with 400 malformed_name before any actor wakes, and
encodeAgentName throws rather than handing back an unusable id.
What the Mount Answers Itself
No actor is woken for any of them. Refusals from the mount carry a JSON body with an
error of not_found, method_not_allowed, or malformed_name; answers from the
agent — including its own errors — are relayed unchanged. Set health: false when the
surrounding function owns its own health routes.
Composing with Other Routes
A mount does not have to own the whole function.fallback receives every request whose
path is not under base, so the function’s own handler keeps its routes and the agent addresses
never reach it:
Cross-origin Browsers
Absentcors, the mount sends no CORS headers and answers OPTIONS with 405, so a
page from another origin cannot reach it. Opt in when a browser page served elsewhere
must open an EventSource or fetch an RPC address:
authorize. Mount names are ordinary path segments, not secrets — they are part of
the address every legitimate client already knows.
Next
mountAgentsreference — every option, type, and refusal in full- AgentClient — the browser/Node client that connects to the address above
- AgentSocketServer and AgentHttpServer — what answers on the agent side of each transport
webSocket()— the entry a class overrides to own its sockets