---
title: Runtime & sandbox
description: Sandbox lifecycle, injected environment, token families, and the image a session boots from.
---

Each session runs in its own isolated sandbox. This page is the reference for the sandbox lifecycle, the environment Beeuni injects at boot, the token families, and the sandbox image itself. For the session concept, see [Sessions](/docs/work/sessions).

## Sandbox lifecycle

A session row carries a `status`. The enum defines `queued`, `branching`, `provisioning`, `running`, `stopped`, `failed`, `completed`, but Beeuni only writes 4 of them.

| Status | Set when |
| --- | --- |
| `provisioning` | At session create. Beeuni creates the session branch and requests the sandbox. |
| `running` | Once the sandbox is live and reachable. |
| `stopped` | On explicit stop, or by the idle sweep that hibernates inactive sandboxes. |
| `failed` | If provisioning fails. |

`queued`, `branching`, and `completed` exist in the enum but stay dead in the session flow. Do not treat them as live states.

The sandbox itself carries a separate `status` in its own row, with its own enum.

| Status | Set when |
| --- | --- |
| `provisioning` | The provider boots the sandbox. |
| `active` | The provider confirms the sandbox is live. |
| `stopped` | Explicit stop, idle auto-stop, or mid-restart. |
| `error` | The provider reports a boot or runtime failure. |
| `archived` | Terminal. You deleted the session and the provider destroyed the sandbox, not paused it. |

Beeuni does not cap how many sessions run at once, per account or per project. Running sessions bill compute; an account out of credit gets `402` on create.

### Branch model

- The session branch is named after the session id (a UUID). `BEEUNI_SESSION_ID` and `BEEUNI_BRANCH_NAME` carry the same value.
- Beeuni cuts the branch from `base_ref`, which defaults to the project's default branch, at session-create time.
- Triggers create their session branch the same way an interactive session does.
- Nothing writes the default branch directly. Only a merged [change request](/docs/work/change-requests) does.

### Reconcile a session branch

Use **Ask Agent: Sync Branch & Reload** from the session command palette when the base branch changed. The agent inspects the session branch, preserves local work, fetches the latest `base_ref`, resolves conflicts, runs the relevant tests, and commits the reconciliation. Beeuni does not choose one side of a conflict or reset the working tree.

The agent finishes with `beeuni sessions reload "$BEEUNI_SESSION_ID" --project "$BEEUNI_PROJECT_ID" --no-repo --force --yes`. `--no-repo` is required because the agent already reconciled the branch. The reload replaces the runtime after the replacement becomes healthy. It ends the current turn, so send `continue` after the runtime returns.

The session header reports each server-confirmed reload phase in real time. A web reload checks the session, compiles the agent config, applies and validates the runtime replacement, then confirms the active config. It does not refresh the repository. The CLI can refresh the repository unless you pass `--no-repo`.

### Config convergence

<a id="where-a-box-gets-the-release"></a>

Every session uses the configuration from its base branch. Push changes to that branch, then reload the session. Configuration updates wait until the active turn finishes. There is no separate Config Releases feature flag.

Use **Reload config** or `beeuni sessions reload <session id>` to apply the current configuration. If loading fails, check the session’s error details and correct the project configuration.

#### `/workspace` is where you edit, the base branch is what runs

A session ALWAYS runs the base branch's current config. There is no per-session config mode.

`/workspace` is your editable checkout. Read the config there, change it there — and the change reaches the running session once it is **pushed to the base branch**, after which the normal convergence picks it up. An edit that is only in `/workspace` changes nothing about how the session behaves, and the running agent is told so: its instructions name the commit its config comes from and say that `/workspace` may be behind it.

The release copy is read-only, so an edit to the wrong copy fails with a permission error instead of silently doing nothing.

#### When the configuration no longer declares a session's agent

A session is created with an agent. If a later commit removes or renames that agent, the session moves to the project's default agent — once, and only when the session's owner may use that agent. Beeuni records the move in the audit log and states it in the running agent's instructions, so the session says what happened.

If the owner may not use the default agent, or the project declares no default, the session keeps no agent and no agent access. It still boots, and it reports the reason. Declare the agent again, set a default agent for the project, or ask a project manager for access to the default agent.

#### What the session header shows

| State | Condition | Header |
| --- | --- | --- |
| Current | Not stale, no fallback | Nothing |
| Update available | `stale: true` | The update badge, with **Reload config** |
| Fallback | `fallback_reason` set | A red chip: **Config failed to load** |

A reload that ends on a fallback reports an error, not a success.

#### Reload config

**Reload config** — and `beeuni sessions reload <session id>` — does two things in one step, and names what happened to each:

1. It moves the running session onto the base branch's current agents, skills, and tools.
2. It fast-forwards the `/workspace` checkout, so the files you read there match the config the session runs.

The checkout pull is fast-forward only, on the session's own branch, so it discards nothing. Pass `--no-repo` on the CLI to skip it. To bring the CODE of `base_ref` into the session branch, use **Ask agent to sync**.

#### Try a config change before it merges

## Injected environment

Beeuni injects these variables at boot. Only a project secret explicitly configured for `runtime` delivery enters the sandbox. Connector and model-provider credentials stay on the Beeuni server.

| Variable | What |
| --- | --- |
| `BEEUNI_PROJECT_ID` | UUID of this project. |
| `BEEUNI_SESSION_ID` | UUID of this session. Also the branch name. |
| `BEEUNI_BRANCH_NAME` | Same value as `BEEUNI_SESSION_ID`. |
| `BEEUNI_REPO_URL` | Clone URL for the project repo. |
| `BEEUNI_DEFAULT_BRANCH` | The project's default branch. |
| `BEEUNI_BASE_REF` | The ref this session branched from. |
| `BEEUNI_API_URL` | The platform API base (`.../v1`). |
| `BEEUNI_AGENT_NAME` | The agent the session was created with. |
| `BEEUNI_MODEL` | The model to run, when set. `BEEUNI_OPENCODE_MODEL` carries the same value for daemons built before this name existed. |
| `BEEUNI_LLM_BASE_URL` | Beeuni LLM-gateway base URL. The gateway resolves provider credentials server-side. |
| `BEEUNI_TOKEN` | The session-bound Beeuni credential. See below. |

Beeuni does not inject `BEEUNI_WORKSPACE`. The image bakes in `/workspace` and no per-session step sets it. Beeuni does not inject a git token either — the daemon fetches a short-lived clone credential when it needs one; see [Pushing from a session](#pushing-from-a-session) below. Beeuni rejects a user secret named with the `BEEUNI_*` prefix, because it reserves that prefix for platform variables.

## Session credential

Beeuni has external tokens you create yourself and one credential inside each sandbox.

### External tokens

A personal access token (PAT, prefixed `beeuni_pat_`) or a service account (prefixed `beeuni_sa_`) authenticates calls to the Beeuni API, the SDK, and the CLI from outside a sandbox. See Authentication for PAT scope, service-account setup, and how to choose between them.

### In-sandbox token

`BEEUNI_TOKEN` is minted when Beeuni starts the session environment. It is bound to the launching user, project, session, and agent grant. Each API route still applies its own capability check.


## Pushing from a session

No upstream Git token enters the sandbox. `git push origin HEAD` sends commits to the session branch. Landing on the default branch requires a merged [change request](/docs/work/change-requests).

## The sandbox image

Your Dockerfile defines the base environment. Beeuni provides the session tools and dashboard connection.

### Declare a template

Reference your Dockerfile as a named template under `sandbox.templates` in `beeuni.yaml`.

```yaml
sandbox:
  templates:
    - slug: dev
      name: Dev box
      dockerfile: Dockerfile.dev
  default: dev
```

Set exactly one of `dockerfile` or `image` on each entry. Dockerfile paths must stay inside the repository. Set `default` to the template slug your sessions should use; omit it to use the platform default image. Older projects on `beeuni.toml` follow the same fields — see [legacy `beeuni.toml`](/docs/project/legacy-toml). Full field list: [manifest reference](/docs/project/manifest).

### Constraints

| Rule | Why |
| --- | --- |
| Don't set `ENTRYPOINT` or `CMD`. | Beeuni overrides both to start the daemon. |
| Don't claim port `8000`. | Reserved for the daemon's reverse proxy. Run dev servers on other ports. |
| `FROM` a Debian or Ubuntu base. | The runtime layer runs `apt-get`. Alpine, Fedora, and Arch fail the build. |
| Don't run `apt-get clean` without `rm -rf /var/lib/apt/lists/*`. | The runtime layer re-runs `apt-get update`; a broken cache breaks the build. |
| Don't bake credentials into the image. | Declare the name in `env:` and set the value as a project secret. Beeuni injects it at session start. |

### Hardware spec

`cpu`, `memory`, and `disk` on a template entry set the sandbox size. All three are optional; an omitted field uses the platform default: 2 vCPU, 4 GiB memory, 20 GiB disk.

```yaml
sandbox:
  templates:
    - slug: big
      image: ubuntu:24.04
      cpu: 4
      memory: 8
      disk: 50
```

`cpu` takes 1–32 cores, `memory` takes 1–128 GiB, and `disk` takes 1–500 GiB. Beeuni clamps any value above these limits down to the limit. Beeuni does not support GPUs; a `gpu` key on a template produces a warning, not an error.

The spec is part of the template's snapshot, not a per-session setting. Changing it rebuilds the snapshot and applies to the next session. The current session keeps its already-booted spec.

### Docker in the sandbox

Set `container_runtime: true` on a template to run Docker inside its sandboxes: bridge and user-defined networks, the `overlayfs` storage driver, and NAT egress. Local stacks such as `supabase start` and `docker compose` work as they do on a laptop.

```yaml
sandbox:
  templates:
    - slug: dev
      dockerfile: .beeuni/Dockerfile.dev
      container_runtime: true
```

### Ports and preview URLs

The daemon listens on port `8000` and proxies any other port your app uses inside the sandbox. Only port `8000` itself is blocked from the proxy.

The dashboard reaches a sandbox port two ways:

- **Path-based**: `https://<api-host>/v1/p/<sandbox-id>/<port>/...`. This is the default form and the only one that supports WebSocket upgrades.
- **Subdomain-based**: `https://p<port>-<sandbox-id>.<api-host>/...`. Use this form for apps that need root-relative paths or cookies, such as a Next.js or Vite dev server. WebSocket upgrades don't work on this form yet.

The preview proxy strips `X-Frame-Options` and any `frame-ancestors` policy. A session can embed your app's preview in an iframe without any config on your side.

A session's preview can also be shared through a public link, in view-only or interactive mode. Public share links block ports `22`, `4096`, `8000`, and the static file-share port.

### Snapshot rebuilds

Changes to the image or environment settings rebuild the environment for subsequent sessions. Unchanged settings reuse the existing environment image. The first session that needs a rebuild shows "preparing image"; later sessions reuse that build.

Editing the Dockerfile inside a session takes effect on the next session, not the current one. The edit reaches `main` only once its [change request](/docs/work/change-requests) merges.
