# Architecture

Rivet separates the process that decides *where* work runs from the process that *runs* it. The **control plane** schedules, routes, and persists. **Workers** are your processes, running your code. Everything else in these docs is a detail of one of those two sides, or of the boundary between them.

<svg viewBox="0 0 720 360" role="img" aria-label="Your client calls the gateway, which routes to a specific Actor. The gateway sits above the control plane, which schedules, versions, and persists. The control plane talks to a worker over the worker protocol. The worker is your own process running your code, and it hosts many Actors." style="width:100%;max-width:720px;height:auto;display:block;margin:2.5rem auto;font-family:system-ui,sans-serif">
  <defs>
    <marker id="arch-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="rgb(var(--site-ink, 27 25 22))"/></marker>
    <marker id="arch-arrow-hl" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="rgb(var(--runtime-highlight, 183 75 35))"/></marker>
  </defs>

  <text x="20" y="72" font-size="13" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Your client</text>
  <line x1="112" y1="67" x2="228" y2="67" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4" marker-end="url(#arch-arrow)"/>

  <rect x="230" y="28" width="470" height="120" rx="8" fill="rgb(var(--site-paper-mid, 227 227 229))" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
  <text x="252" y="58" font-size="14" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Gateway</text>
  <text x="252" y="78" font-size="12" fill="rgb(var(--site-ink-soft, 86 82 74))">routes to a specific Actor</text>
  <line x1="230" y1="92" x2="700" y2="92" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
  <text x="252" y="118" font-size="14" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Control plane</text>
  <text x="252" y="138" font-size="12" fill="rgb(var(--site-ink-soft, 86 82 74))">schedules, versions, persists</text>

  <line x1="465" y1="149" x2="465" y2="209" stroke="rgb(var(--runtime-highlight, 183 75 35))" stroke-width="1.4" marker-start="url(#arch-arrow-hl)" marker-end="url(#arch-arrow-hl)"/>
  <text x="479" y="184" font-size="12" fill="rgb(var(--site-ink-soft, 86 82 74))">worker protocol</text>

  <rect x="230" y="210" width="470" height="122" rx="8" fill="rgb(var(--runtime-highlight, 183 75 35) / 0.14)" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
  <text x="252" y="240" font-size="14" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Worker</text>
  <text x="252" y="260" font-size="12" fill="rgb(var(--site-ink-soft, 86 82 74))">your process, your code</text>
  <g fill="rgb(var(--site-paper, 239 239 239))" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.3">
    <rect x="252" y="276" width="96" height="38" rx="6"/>
    <rect x="364" y="276" width="96" height="38" rx="6"/>
    <rect x="476" y="276" width="96" height="38" rx="6"/>
  </g>
  <g text-anchor="middle" font-size="12" fill="rgb(var(--site-ink, 27 25 22))">
    <text x="300" y="300">Actor</text>
    <text x="412" y="300">Actor</text>
    <text x="524" y="300">Actor</text>
  </g>
  <text x="600" y="300" font-size="13" fill="rgb(var(--site-ink-faint, 138 132 120))">…</text>
</svg>

## Control Plane

The control plane is a single Rust binary. It tracks every Actor, decides which worker each one runs on, routes requests to it, and persists its state. It is the only stateful component you operate, and it never runs your code.

Use it on [Rivet Cloud](/docs/deploy/cloud), inside your own account with [BYOC](/docs/deploy/byoc), or [self-host](/docs/deploy/self-host/control-plane) it. In local development, RivetKit starts one for you at `http://localhost:6420` with no authentication.

## Namespaces

A namespace is the tenancy boundary. Actor IDs and keys, worker names, pools, and tokens are all scoped to one namespace. Use namespaces to separate production from staging, or one customer from another. Every connection names a namespace, defaulting to `default`.

See [Namespaces](/docs/namespaces).

## Actors

An Actor is one schedulable unit of work with its own durable state and its own address. The control plane's view of every Actor is the same regardless of type: it has an ID, a key, a status, and the worker it is currently allocated to.

An Actor is created on demand, runs until it goes idle, hibernates with its state intact, and wakes on the next request. It does not move between regions. Workflows, agentOS, and Dynamic Apps are Actors with a job already built in.

See [Actor Statuses](/docs/statuses) for the lifecycle the control plane exposes.

## Workers

A worker is a process you operate that has your code loaded and the Rivet SDK running inside it. It is the only place your code executes. Workers connect *outbound* to the control plane, so they need no public URL and no inbound firewall rule.

Each worker reports a version number. The control plane prefers the highest version it can see when allocating new Actors, which is what makes a rolling deploy work without coordination. See [Versions & Upgrades](/docs/versions).

## Worker Pools

A pool is the set of workers registered under one name within a namespace. Pools route Actors to the right hardware: a `gpu-workers` pool and a `default` pool can serve the same application, and a client names the pool when it creates an Actor.

A pool's kind matches how its workers attach. `normal` pools hold long-running workers that connect themselves. `serverless` pools are woken by the control plane over HTTP. Scaling, drain behavior, and eviction rate limits are set per pool.

See [Workers & Pools](/docs/workers) for running them and [Pool Configuration](/docs/pool-configuration) for the options.

## Gateway

The gateway is the part of the control plane that accepts client traffic and proxies it to whichever worker currently holds the target Actor. It speaks plain HTTP and WebSocket:

```
{RIVET_ENDPOINT}/gateway/{actor_id}/{path}
```

Because it resolves the target on every request, a client never needs to know where an Actor is running, and an Actor that is rescheduled onto a different worker keeps the same address. See [Connect](/docs/connect) for the endpoint and [Management API](/docs/debugging) for addressing Actors directly.

## Serverless vs. Long-Running Workers

How a worker connects to the control plane is the biggest deployment decision. There are two options, and they can coexist in different pools of the same namespace.

### Long-Running Workers

The worker starts as a normal process, opens a persistent connection to the control plane, and waits for work. When a client creates an Actor, the control plane sends a command over that connection to start it. This is the default mode, used for local development and for containers, VMs, and bare metal (Kubernetes, Railway, AWS ECS, Hetzner).

<img src={imgWorkers.src} alt="Long-running worker architecture diagram" />

You control how many workers run and how they scale. Nothing needs to be publicly reachable.

### Serverless Workers

There is no persistent process. When a client creates an Actor, the control plane calls `GET /api/rivet/start` on your serverless deployment, and that invocation becomes a worker for the length of the request. Use it for Vercel, Cloudflare Workers, Supabase, and AWS Lambda. Rivet Cloud deploys in this mode.

<img src={imgServerless.src} alt="Serverless worker architecture diagram" />

Serverless workers scale to zero, follow your platform's autoscaling, and work with preview deployments. Function timeouts are handled by migrating Actors between invocations with their state intact, so you write code as if it runs forever.

| | Long-running | Serverless |
| --- | --- | --- |
| Process | Always on, connects out | Started by the control plane per request |
| Public endpoint | Not required | Required |
| Scaling | You manage it | Platform autoscaling, scale to zero |
| Typical hosts | Kubernetes, Docker, VMs | Vercel, Cloudflare, Supabase, Lambda |

See [Workers & Pools](/docs/workers) for starting a worker in either mode and [Deploy Workers](/docs/deploy/self-host/workers) for per-platform guides.

## Regions

A datacenter is one control plane deployment with its own hostname. A multi-region deployment runs one per region over a shared database, and Actors are placed in a datacenter at creation and stay there. See [Regions & Multi-Region](/docs/regions).
