# How voxal works

> The host and sandbox split, the lifecycle of an SSH connection, and the model your app runs in

voxal turns one SSH connection into one session of your app. Understanding the path a
connection takes, and the boundary your code runs behind, makes everything else in these
docs click into place.

## The core idea: host and sandbox

voxal is built on a strict split between a trusted **host** and an untrusted **sandbox**.

- The **host** is the platform. It owns the dangerous capabilities: the SSH connections and
  real network access.
- Your **app** runs in a **sandbox** that can do pure JavaScript and nothing else, except
  through two narrow bridges the host provides: writing to the terminal, and a guarded
  [`fetch`](/sdk/fetch).

Everything your app does that touches the outside world goes through the host. The sandbox
has no filesystem, no process spawning, no raw sockets, and no global `fetch`. That boundary
is what makes it safe to run anyone's app next to anyone else's. See
[Sandbox and security](/platform/security) for the guarantees.

## The lifecycle of a connection

When someone runs `ssh hello@voxal.sh`, here is what happens.

1. **Handshake.** voxal accepts the SSH connection. The username (`hello`) is the app name,
   not a Unix account. No shell and no command execution are ever granted; the channel is
   wired only to your app.
2. **Warm or boot.** If `hello` is already running, voxal reuses it. If not, it boots the
   app's sandbox, which takes a few milliseconds.
3. **Connect.** voxal fires your [`onConnect`](/sdk/lifecycle) handler with a
   [connection](/sdk/connection) object that carries the terminal size (`conn.cols`,
   `conn.rows`). This is where you do your first render.
4. **Input.** Every keystroke fires [`onKey`](/sdk/lifecycle) with the decoded input. A
   terminal resize fires [`onResize`](/sdk/lifecycle), and `conn.cols` / `conn.rows` are
   updated for you first.
5. **Output.** Your `conn.write(...)` calls send text (and ANSI escapes) back down the SSH
   channel to that user's terminal.
6. **Close.** When the user disconnects, [`onClose`](/sdk/lifecycle) fires so you can clean
   up that session's state.

## One app, many users

There is exactly **one sandbox per app**, and every user connected to that app runs inside
it. Each connection has its own `conn` object and its own id, but they share the same module
scope.

That dual nature is the key thing to design around:

- **Per-connection state** (one user's cursor, draft, or view) should be keyed by `conn.id`,
  or in the [UI layer](/sdk/ui) it lives naturally in that connection's own React tree.
- **Shared state** (a chat room, a leaderboard, who is online) is a plain module-level
  variable, visible to everyone connected.

The [Share state across connections](/guides/sharing-state) guide shows both patterns.

## The sandbox

Each app runs in its own isolate: a separate heap and event loop, not a shared one. One
app's infinite loop or memory spike cannot affect another app or the host. The isolate
enforces a memory cap and bounds CPU time per event, and timers run inside it under their own
limits. The exact numbers are on [Limits and quotas](/platform/limits).

Because the sandbox is pure JavaScript, your app must be pure JavaScript too: no native npm
modules. The CLI bundles your app, the SDK, and any pure-JS dependencies into a single file
before deploying.

## Scale to zero

An app with no active connections is dropped after a short idle period and booted again on
the next connection. Boots are fast (a few milliseconds), so this is invisible to users. The
practical consequence: do not rely on in-memory state surviving while nobody is connected.
Treat each boot as a fresh start.

## What runs where

| Concern | Where it runs |
| --- | --- |
| Your app code (`onConnect`, render, key handling) | The sandbox |
| Writing to the terminal (`conn.write`) | Bridged to the host |
| HTTP requests ([`fetch`](/sdk/fetch)) | The host, behind SSRF guards |
| SSH connections and networking | The host |
| `console.log` | Server logs (not the user's terminal) |

## Next steps

- Read the [SDK overview](/sdk) and start with [createApp and lifecycle](/sdk/lifecycle).
- Prefer components? See the [UI layer](/sdk/ui).
- Review the [limits](/platform/limits) and [security guarantees](/platform/security).

---

Source: https://docs.voxal.sh/how-it-works
