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

# Agent connectivity

# Agent Connectivity

Every dispatch the mesh sends must reach your agent. Svantic supports two
connectivity modes that you pick per-instance at registration time:

* **Connected** (default) — your agent dials Svantic over a persistent
  WebSocket and the mesh pushes dispatches down that socket. Works
  anywhere outbound HTTPS/WSS works — behind firewalls, NATs,
  corporate egress-only networks, laptops, CI runners, or ephemeral
  containers without stable DNS. This is the default because it is
  table stakes for a great out-of-the-box experience.
* **Hosted** — your agent exposes a public HTTPS surface the mesh calls
  into. Opt in when your agent already runs as an HTTP service on
  infrastructure that can accept inbound traffic and you want a pure
  request/response shape.

You choose once, per `instance_id`. Deployment mode is not a deploy-time
topology concern (see [Deployment Models](./deployment-models) for
that) — it's a statement about *how the mesh reaches this particular
instance*. Two instances of the same agent type can use different modes.

## Connected mode (default)

<img src="https://mintcdn.com/svantic/DQEVJ_RnVW5_l6gu/images/diagrams/connected-mode.svg?fit=max&auto=format&n=DQEVJ_RnVW5_l6gu&q=85&s=d084e6a3692077a863b0119732013119" alt="Connected mode: agent dials Svantic via WebSocket, dispatches pushed bidirectionally" width="640" height="260" data-path="images/diagrams/connected-mode.svg" />

**How it works.** Connected mode is the default — you get it by calling
`new MeshConnector(agent, { svantic_url, client_id, client_secret })`
with no `deployment_mode` set and no `public_url`. The edge routes the
single `svantic_url` to the gateway for registration and to the mesh
for dispatch — you never hardcode service-specific hosts. The gateway
registration response carries back a `connect_url`; the SDK
dials it immediately and keeps the WebSocket open for the agent's
lifetime. The mesh pushes `dispatch` frames down the socket; your
agent pushes `dispatch_result` frames back. Full protocol details live
in [WebSocket API: agents/connect](../api-reference/websocket/agents-connect).

**When it's right (almost always).**

* The agent runs behind a firewall, NAT, or a corporate proxy with no
  inbound exception.
* The agent runs on a developer laptop, a CI runner, an ephemeral
  container, or any host without a stable DNS entry.
* You want zero inbound exposure — outbound-only is a compliance win.
* You want the easiest onboarding path: no ingress, no TLS cert, no
  reverse proxy.

**What it costs.**

* One WebSocket per live instance. Idle connections still cost a
  pod-local file descriptor; plan capacity like you would SSE.
* Reconnect semantics are the SDK's responsibility. The SDK handles
  it automatically (bounded backoff, resume cursor, outbox replay) —
  your handler code is oblivious — but long-term disconnects will
  surface as `AgentDisconnectedError` on the caller.
* The WebSocket JWT authenticates the **transport** (who opened the
  socket). Per-invocation authentication still flows through the
  `dispatch_auth` envelope; see [Dispatch auth](#dispatch-auth) below.

## Hosted mode

<img src="https://mintcdn.com/svantic/DQEVJ_RnVW5_l6gu/images/diagrams/hosted-mode.svg?fit=max&auto=format&n=DQEVJ_RnVW5_l6gu&q=85&s=f3cac9f22aac34d57a59fec494b62bbb" alt="Hosted mode: Svantic sends HTTPS dispatch to agent, agent returns response" width="640" height="180" data-path="images/diagrams/hosted-mode.svg" />

**How it works.** Opt in by passing a `public_url` at registration (or
setting `deployment_mode: hosted` explicitly). The gateway probes
`GET {public_url}/.well-known/agent-card.json` to confirm the agent is
reachable and publishing a valid card, then admits the instance. Every
subsequent dispatch is an HTTPS POST from the mesh to
`{public_url}/send`, authenticated per the `dispatch_auth` block you chose.

**When to pick it.**

* Your agent already runs as a long-lived HTTP service on infrastructure
  that accepts inbound traffic and you don't want to hold a socket open.
* You have an existing ingress, LB, or API gateway you want to reuse.
* You prefer the simplest request/response mental model and control the
  infra on both sides.

**Requirements.**

* Publicly reachable HTTPS URL (ingress through your own LB or API
  gateway is fine).
* `/.well-known/agent-card.json` served at the root of that URL.
* TLS terminated by you. The mesh will not call `http://` endpoints.

**Dispatch authentication.** See the [Dispatch auth](#dispatch-auth)
section below — the same model applies to hosted and connected agents.

## Dispatch auth

Regardless of transport, every dispatch carries a per-invocation
authentication envelope when the mesh runs with
`SVANTIC_DISPATCH_AUTH_ENABLED=true`. Three schemes:

* **`svantic_jwt`** (default) — the mesh mints a short-lived HS256 JWT
  against its `SIGNING_SECRET` and the SDK verifies with the same
  secret. No per-agent credential management; rotating the secret
  rotates every agent.
* **`shared_secret`** — you supply a raw secret at registration time
  under a `vault://` reference. The mesh stores it encrypted, resolves
  it at dispatch time, and attaches it to the envelope; the SDK
  compares against its local copy. Use this when the agent must prove
  possession of an account-minted secret.
* **`mtls`** — reserved, not implemented.

The envelope is **fail-closed**: if resolution fails (signing secret
missing, vault unreachable, stale `credentials_ref`), the dispatch
fails with `AgentUnhealthyError` and no frame reaches the agent.

See [Registering Agents — Dispatch Auth](../guides/registering-agents#dispatch-auth)
for the step-by-step setup and
[Mesh Security — Dispatch Auth](./security#dispatch-auth-per-invocation)
for the security rationale.

## Strict control plane / data plane split

Regardless of mode, these operations are **always** plain HTTPS calls
to the gateway, never WebSocket:

* `POST /agents/register`, `POST /agents/deregister`
* `POST /sessions/init`
* File uploads, knowledge ingestion, credential rotation
* Any administrative action visible in the dashboard

The WebSocket carries **only** real-time messaging: dispatches,
streaming chunks, status updates, heartbeats. This is a deliberate
split so that the control plane remains inspectable, auditable, and
version-skew tolerant — and so a dropped socket never loses control
state.

## Choosing a mode: quick guide

| You have…                                        | Pick        |
| ------------------------------------------------ | ----------- |
| A public HTTPS service                           | `hosted`    |
| Corporate firewall, egress-only allowed          | `connected` |
| Ephemeral containers (Fly, Vercel CI)            | `connected` |
| Kubernetes pod with Ingress                      | `hosted`    |
| Developer laptop for demos                       | `connected` |
| Agent that must be callable from outside Svantic | `hosted`    |

You can migrate an existing agent-type from one mode to another by
registering a *new* `instance_id` with the target mode and draining
traffic off the old one. Svantic rejects an in-place mode switch on
the same `instance_id` with `409 DEPLOYMENT_MODE_MISMATCH`.

## Observability

Both modes populate the same `last_dispatch_error` snapshot on the
instance detail — same status codes, same error taxonomy — so
dashboards and alerts work uniformly regardless of transport. Trace
context (W3C `traceparent`, `baggage`) also propagates identically:
the SDK parses it off the dispatch payload and exposes it on
`CapabilitySessionContext` whether the dispatch arrived via HTTPS or
WebSocket.

### Connection-state endpoints

The gateway exposes two read endpoints for live transport state:

* **`POST /agents/get_connection`** — returns a unified envelope for
  one `instance_id`. Shape is identical for hosted and connected
  agents: the consumer switches on `transport` (`callback` vs. `ws`)
  and `connection_status` (`online` / `healthy` / `degraded` /
  `offline` / `unknown`).
* **`POST /agents/get_connection_stats`** — returns fleet-wide
  aggregates grouped by `deployment_mode`, `connection_status`, and
  `transport`. Optional `agent_type` filter.

The dashboard uses these endpoints to display agent health panels.

### Events

The mesh emits transport-lifecycle events in real time so dashboards
update without polling:

* `agent.connected` — WebSocket upgrade accepted.
* `agent.disconnected` — WebSocket closed.

Time-based transitions (stale heartbeat → `unhealthy`) remain the
reaper's job. See [Agent Health](./agent-health#connection_status-—-transport-liveness)
for the full split and the status value catalog.

### Metrics

Every mesh pod exposes `GET /metrics` in Prometheus exposition format
with per-transport dispatch counters, WebSocket-connection gauges,
inter-pod routing latency, and event-loop lag. Per-instance state is
deliberately **not** labeled on `/metrics` — use the connection
endpoints above for that. See the API documentation for the full
metrics schema.

## See also

* [Agents](./agents) — how agents and capabilities work end-to-end.
* [Agent Health](./agent-health) — how dispatch failures roll up.
* [Deployment Models](./deployment-models) — topology choices
  (standalone, sidecar, mesh) which are orthogonal to connectivity.
* [WebSocket API: agents/connect](../api-reference/websocket/agents-connect)
  — wire-level reference for connected-mode agents.
