Runtime & sandbox
Sandbox lifecycle, injected environment, token families, and the image a session boots from.
A session runs in an isolated sandbox on Daytona, Platinum, or E2B Cloud, built from a layered image. This page is the reference for the sandbox lifecycle, the environment SingulaComp injects at boot, the token families, and the sandbox image itself. For the session concept, see Sessions.
Sandbox lifecycle
A session row carries a status. The enum defines queued, branching, provisioning, running, stopped, failed, completed, but SingulaComp only writes 4 of them.
| Status | Set when |
|---|---|
provisioning | At session create. SingulaComp 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. |
SingulaComp enforces a concurrent-session limit per account. Exceeding your tier's limit returns 429.
Active-turn protection
While an OpenCode turn is busy or retrying, the sandbox daemon renews a short execution lease with the API every 60 seconds. The lease blocks the idle reaper. Each renewal also touches the provider, so the provider's own inactivity timer cannot hibernate the sandbox mid-run. session.idle and session.error release the lease.
An open dashboard tab, preview, SSE connection, or health poll does not create a lease. A passive tab cannot keep an idle sandbox alive.
Branch model
- The session branch is named after the session id (a UUID).
SINGULACOMP_SESSION_IDandSINGULACOMP_BRANCH_NAMEcarry the same value. - SingulaComp 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 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. SingulaComp does not choose one side of a conflict or reset the working tree.
The agent finishes with singulacomp sessions reload "$SINGULACOMP_SESSION_ID" --project "$SINGULACOMP_PROJECT_ID" --no-repo --force --yes. --no-repo is required because the agent already reconciled the branch. The reload replaces the OpenCode 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.
Layout inside the sandbox
/workspace ← WORKDIR. The project repo is cloned here.
/workspace/.singulacomp/ ← Repo-internal SingulaComp folder (Dockerfile + opencode config dir).
/usr/local/bin/singulacomp-agent ← The daemon (supervisor + reverse proxy).
/usr/local/bin/singulacomp-entrypoint ← The container ENTRYPOINT (PID 1).
/opt/singulacomp/home ← OpenCode's HOME — its object store lives here, off the repo.OpenCode's HOME is /opt/singulacomp/home, not /workspace. Its object store never lands among your repo files.
Injected environment
SingulaComp 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 SingulaComp server.
| Variable | What |
|---|---|
SINGULACOMP_PROJECT_ID | UUID of this project. |
SINGULACOMP_SESSION_ID | UUID of this session. Also the branch name. |
SINGULACOMP_BRANCH_NAME | Same value as SINGULACOMP_SESSION_ID. |
SINGULACOMP_REPO_URL | Clone URL for the project repo. |
SINGULACOMP_DEFAULT_BRANCH | The project's default branch. |
SINGULACOMP_BASE_REF | The ref this session branched from. |
SINGULACOMP_SERVICE_PORT | 8000 — the daemon's external port. |
SINGULACOMP_API_URL | The platform API base (.../v1). |
SINGULACOMP_AGENT_NAME | The agent the session was created with. |
SINGULACOMP_OPENCODE_MODEL | The model to run, when set. |
SINGULACOMP_PROJECT_AUTO_CLONE | 1 — tells the daemon to clone the repo on boot. |
SINGULACOMP_PROJECT_SECRET_NAMES | Comma-separated names of the project's secrets. |
SINGULACOMP_PROJECT_SECRETS_REVISION | Revision marker for the secret set. |
SINGULACOMP_BOOTSTRAP_OPENCODE_SESSION | 1 — always set. Tells the daemon to create the OpenCode root on cold boot. |
SINGULACOMP_LLM_BASE_URL | SingulaComp LLM-gateway base URL. The gateway resolves provider credentials server-side. |
SINGULACOMP_TOKEN | The session-bound SingulaComp credential. See below. |
SingulaComp does not inject SINGULACOMP_WORKSPACE. The image bakes in /workspace and no per-session step sets it. SingulaComp does not inject a git token either — the daemon fetches a short-lived clone credential when it needs one; see Pushing from a session below. SingulaComp rejects a user secret named with the SINGULACOMP_* prefix, because it reserves that prefix for platform variables.
Session credential
SingulaComp has external tokens you create yourself and one credential inside each sandbox.
External tokens
A personal access token (PAT, prefixed singulacomp_pat_) or a service account (prefixed singulacomp_sa_) authenticates calls to the SingulaComp 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
SINGULACOMP_TOKEN is minted when SingulaComp starts the session environment. It is bound to the launching user, project, session, and agent grant. The daemon, CLI, Git credential helper, LLM gateway, and connector gateway use this same credential. Each API route still applies its own capability check.
The initial prompt and turn-ledger identifiers are not environment variables. The daemon claims them from the API with SINGULACOMP_TOKEN after boot.
Pushing from a session
The daemon sends SINGULACOMP_TOKEN only to the SingulaComp Git proxy. The proxy resolves the upstream Git credential on the server. 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.
The agent runtime
The daemon launches OpenCode as opencode serve --port 4096 --hostname 127.0.0.1, with OPENCODE_CONFIG_DIR set to the project's config directory (default .singulacomp/opencode) inside the cloned repo. See Agents for how a session picks an agent and its config.
Transcript attachments and memory
OpenCode stores tool screenshots as base64 data: URLs inside its SQLite transcript. The daemon keeps that store small and the box alive:
- Attachment offload. While no turn runs (every 5 minutes, at boot, and after a memory-guard event), attachment bytes older than the newest 12 per session — and every tool result OpenCode's compaction already cleared — move to
~/.local/share/singulacomp/attachments/<id>. The row keeps a 1×1 PNG placeholder plus asingulacomp.offloadedmarker;/singulacomp/partserves the real bytes to the UI. Models never receive those old images anyway (the LLM proxy keeps the newest 12 per request). SetSINGULACOMP_ATTACHMENT_OFFLOAD=0to disable. - Resource telemetry.
[resources]in the daemon log every 60 s and on every OpenCode state change: box memory, cgroup limit, load, disk, daemon and OpenCode RSS. - Memory guard. Above 80 % memory the daemon samples every 10 s; at 92 % (
SINGULACOMP_MEMORY_GUARD_PCT) with a turn in flight it aborts the turn cleanly and reportsSandboxMemoryGuardwith the numbers as the turn's error, instead of letting the kernel kill OpenCode mid-turn.
The daemon control surface
The singulacomp-agent binary runs as PID 1's child and fronts OpenCode on SINGULACOMP_SERVICE_PORT (8000). Every route outside /singulacomp/* requires the HMAC-signed X-SingulaComp-User-Context header, validated against SINGULACOMP_TOKEN.
| Path | Purpose |
|---|---|
GET /singulacomp/health | Liveness check (no auth required). Reports daemon and OpenCode state, repo, branch, commit. |
POST /singulacomp/refresh | Re-pull the session branch and restart OpenCode in place. |
POST /singulacomp/abort | Abort the current run. |
POST /singulacomp/env | Update the runtime environment. |
/singulacomp/pty | Backs the in-dashboard terminal. |
GET /singulacomp/logs | Tails the daemon's own log file (/opt/singulacomp/logs/daemon.log, rotated at 32 MiB) or OpenCode's. ?source=daemon|opencode|all, ?tail=N (default 500, max 5000). Plain text. Same auth as /singulacomp/refresh. |
GET /singulacomp/part/:sessionID/:messageID/:partID | Attachment bytes on demand — a top-level file part or a tool result's state.attachments[] entry. Bytes the daemon offloaded to a sidecar file are served from there. |
GET /singulacomp/diag | One JSON error report: OpenCode state/pid/port/port pair, boot timeline, a fresh resource snapshot (memory, cgroup limit, load, disk, RSS, duplicate opencode processes), the runtime-assets report, and the tail of both logs (?tail=N, default 200). Same auth as /singulacomp/logs. |
/proxy/{port}/* | Reverse-proxy to another port inside the sandbox. The daemon's own port is blocked. |
* | Catch-all reverse-proxy to OpenCode on 127.0.0.1:4096. Returns 503 while OpenCode boots. |
Run /singulacomp/refresh to apply an out-of-band change, such as a manifest edit committed from a parallel session, without re-provisioning the sandbox.
The sandbox image
Every sandbox boots from an image built in two layers. Your Dockerfile defines the base environment. The SingulaComp runtime layer is added on top, so the dashboard can connect to the sandbox.
┌─────────────────────────────────────────┐
│ SingulaComp runtime layer (added on top) │ ← opencode + singulacomp-agent + entrypoint
├─────────────────────────────────────────┤
│ Your Dockerfile │ ← .singulacomp/Dockerfile
└─────────────────────────────────────────┘If your project has no Dockerfile, SingulaComp builds sessions from a bare ubuntu:24.04 image plus the runtime layer below.
Declare a template
Reference your Dockerfile as a named template under sandbox.templates in singulacomp.yaml.
sandbox:
templates:
- slug: dev
name: Dev box
dockerfile: .singulacomp/Dockerfile
default: devSet 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 singulacomp.toml follow the same fields — see legacy singulacomp.toml. Full field list: manifest reference.
What the runtime layer adds
SingulaComp appends this layer on top of your Dockerfile's final stage.
- A system package floor with
git,curl,build-essential,ffmpeg, andtmux. - pnpm-managed Node.js and npm, plus uv-managed Python 3.
- A document-tools floor: LibreOffice, Pandoc, and OCR tools.
- An exact uv-managed Python version exposed as
pythonandpython3. Useuv run --with <package>for third-party dependencies. opencode-ai, thebunruntime, andagent-browserwith a baked Chromium build.- The
singulacomp-agentdaemon, thesingulacompCLI, and the entrypoint script. ENV SINGULACOMP_WORKSPACE=/workspace,WORKDIR /workspace,EXPOSE 8000, and the entrypoint that starts the daemon.
Everything you install in your own Dockerfile stays on PATH. SingulaComp does not remove or relocate it.
Constraints
| Rule | Why |
|---|---|
Don't set ENTRYPOINT or CMD. | SingulaComp 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. SingulaComp 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.
sandbox:
templates:
- slug: big
image: ubuntu:24.04
cpu: 4
memory: 8
disk: 50cpu takes 1–32 cores, memory takes 1–128 GiB, and disk takes 1–500 GiB. SingulaComp clamps any value above these limits down to the limit. SingulaComp 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.
The default spec costs about $0.10 per hour on Daytona, the default sandbox provider. The same spec on Platinum or E2B costs about twice as much, because the Daytona rate includes a volume discount the other providers don't. SingulaComp meters this cost only for Team accounts; free and self-hosted plans aren't billed for it.
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
SingulaComp content-addresses each snapshot: it hashes your Dockerfile's bytes, the hardware spec, and the platform's own runtime version. An unchanged hash reuses the existing snapshot. A changed hash triggers a rebuild, shown as "preparing image" on the first session that needs it; 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 merges.