> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Mount

`mountAgents` *(SDK ≥ 0.12.0)* is the front door for an Edge Compute
[function](/docs/edge-compute/stateful-actors/guides/project-structure#two-exports) that serves agents.
One call replaces the routing file a function would otherwise write by hand. The function keeps only its own policy. It ships at `@telnyx/edge-runtime/mount`.

```ts theme={null}
import { mountAgents } from "@telnyx/edge-runtime/mount";
import type { ActorNamespace } from "@telnyx/edge-runtime";

interface Env {
  CONVERSATION: ActorNamespace;
}

export default {
  fetch: mountAgents<Env>((env) => ({ conversation: env.CONVERSATION })),
};
```

> [!WARNING]
>
> **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 mounted agent answers on the same shape, `<base>/<mount>/<name>`, where `<mount>` is a key of the map and `<name>` is the routing name of one instance. The
**request** picks the transport; the path never changes:

| Request | Address | Serves |
| - | - | - |
| `Upgrade: websocket` | `<base>/<mount>/<name>` | the agent socket protocol |
| `GET` | `<base>/<mount>/<name>?subscribe=state` | Server-Sent Events |
| `POST` | `<base>/<mount>/<name>/rpc/<method>` | one RPC call |

`base` defaults to `/agents`, so the map above serves
`/agents/conversation/alice` on all three. **That is the URL the
[AgentClient](/docs/agent-sdk/client-sdk/constructor) examples connect to** —
`wss://my-func.telnyxcompute.com/agents/conversation/alice` is this default `base`,
this mount key, and this name:

```ts theme={null}
import { AgentClient } from "@telnyx/edge-runtime/client";

// mount key ─────────────────────────┐    ┌─── routing name
const agent = new AgentClient(
  "wss://my-func.telnyxcompute.com/agents/conversation/alice",
);
```

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`](/docs/edge-compute/stateful-actors/api-reference/base#websocket-ws-req); `GET` and `POST` are handed to the agent's
[`fetch`](/docs/edge-compute/stateful-actors/api-reference/base#fetch-req), where an
[AgentHttpServer](/docs/agent-sdk/internals/the-http-server) over the agent's
connection engine answers them once the class routes `fetch` there. 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.

> [!WARNING]
>
> **SSE arrives in batches on deployed functions.** Each SSE response carries the frames ready when the agent answers, and the `EventSource` reconnects about once a second. The WebSocket and RPC paths are unaffected, and SSE streams live in local development. [AgentHttpServer](/docs/agent-sdk/internals/the-http-server) has the detail. The WebSocket path is the live one for production, on the same address under the same policy.

## The Hand-Off

The handler the mount returns resolves the binding, then hands the request to the instance.

```ts theme={null}
// mount.ts
return async function handleAgentRequest(req, env) {
  // …
  const table = map(env);
  const namespace = table !== null && typeof table === "object" && ownKey(table, mount) ? table[mount] : undefined;
  if (namespace === undefined || typeof namespace.idFromName !== "function") {
    return withCors(notFound(), cors, req);
  }
  // …
  const stub = namespace.idFromName(actorId);
  return withCors(await stub.fetch(new Request(forwardable, { headers })), cors, req);
};
```

* [`fetch`](/docs/edge-compute/stateful-actors/guides/project-structure#two-exports) on the function's default export is the entry this handler fills. The platform calls it with each request and the function's `env`.
* [`ActorNamespace`](/docs/edge-compute/stateful-actors/api-reference/namespace) is what each map value must be. `map(env)` reads the bindings on every request. The mount answers `404` for a value without `idFromName`.
* [`idFromName`](/docs/edge-compute/stateful-actors/api-reference/namespace#idfromname-name-options) receives the actor id that `encodeAgentName` built. The same id always reaches the same instance, as [Addressing](/docs/edge-compute/stateful-actors/concepts/addressing) describes.
* [`ActorStub.fetch`](/docs/edge-compute/stateful-actors/api-reference/stub) receives the request with its URL unchanged and the stamped headers. An upgrade reaches the instance's [`webSocket`](/docs/edge-compute/stateful-actors/api-reference/base#websocket-ws-req) entry, and any other request reaches its [`fetch`](/docs/edge-compute/stateful-actors/api-reference/base#fetch-req) entry.

## Two Gates, Not One

The mount's [`authorize`](/docs/agent-sdk/internals/mount/mountAgents#mountoptions) 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`](/docs/agent-sdk/interface/authorize)
   resolves this connection's [claims](/docs/agent-sdk/interface/authorize#claim)
   from it exactly as it would unmounted, and `onAttach` may still veto.

Stamp identity at the mount; decide grants in the agent.

```ts theme={null}
export default {
  fetch: mountAgents<Env>((env) => ({ conversation: env.CONVERSATION }), {
    authorize: async (req, { mount, name }) => {
      const user = await verify(req.headers.get("authorization"));
      if (!user) return new Response("unauthorized", { status: 401 });
      if (user.id !== name) return new Response("forbidden", { status: 403 });
      return { "x-desk-user": user.id }; // stamped — overwrites any caller header
    },
  }),
};
```

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 the mount does **not** stamp are forwarded as the caller sent them, so the agent trusts only what the mount stamps.

The [Mounting Agents](/docs/agent-sdk/mounting-agents) guide walks the whole path,
including composing the mount with other routes and the cross-origin opt-in.

## 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. Any front door written by hand that must address the same instances calls the same function. Re-deriving the rule points a name at a different, empty instance. The instance reads its actor id as
[`ctx.id`](/docs/edge-compute/stateful-actors/api-reference/context#ctx-id).

The ceiling is on the id **once URL-escaped**, not on the name — see
[MAX\_ESCAPED\_ACTOR\_ID\_LENGTH](/docs/agent-sdk/limits#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.
