# Organization environments Configure Cloud sandboxes with revisioned setup, variables, secrets, extensions and cache paths. Source: https://docs.cloud.vampikez.fun/concepts/environments/ An organization environment is a revisioned configuration document. It is a different resource from the managed sandbox platform reported by `GET /v1/capabilities`. When a Session receives a sandbox, Cloud applies the environment revision pinned to that lease before its first Turn starts. Create one with a caller-owned UUID and `PUT /v1/environments/{environmentId}`. The document accepts a name, description, shell `setup` script, `variables`, an `extensions` list, `cache.paths`, an optional `github` boolean (default `false`), and an optional `repositories` list. When `github` is on, sandboxes of Sessions on this environment are intended to receive short-lived tokens of WAMP's GitHub App for GitHub installations granted to the installation that owns the Session, with `contents` and `pull_requests` write. Delivery to the sandbox arrives with a later engine release; today the flag is stored and checked but no token is minted. Invalid inputs return `400 environment_invalid` with field paths in `issues`. Read with `GET /v1/environments` or `GET /v1/environments/{environmentId}`. People need `sessions:create` to read; an App installation needs `environments:manage`. Writes need the sensitive `wamp.cloud.environments:manage` capability. Every change creates a numbered revision with its writer. Quote the current revision in `If-Match`, for example `If-Match: "3"`, when updating a document, secret, or archive state. A stale revision returns `409`; an identical write does not advance the revision. `PUT .../secrets/{name}` accepts `{ "value": "..." }`; reads list names, versions and writers, never values. Secret values are encrypted in Cloud's database. `DELETE .../secrets/{name}` removes the name from the head. Restore creates a new head from a prior document and its pinned secret versions; it cannot reverse a credential rotation at the external provider, and does not change the environment's default or archive state. `GET /v1/environments/{environmentId}/revisions` lists revisions newest first, each with its writer, whole document and the secret versions it pins. Archive, unarchive and default changes also write a revision, with the document unchanged. Pass the page's `nextBefore` as `before` for older revisions; `limit` is 1–50, default 20. A writer is `createdBy: { kind, id }`: a person by membership id, or an installation. The agent of a Session that a person or installation started can change environments on their behalf, with their capabilities; what it writes also carries `createdBy.via: { sessionId, runId }`, the agent's Session and Run. Secret versions name their writer the same way. `PUT /v1/organization/default-environment` with `{ "id": "..." }` selects the default for new human Sessions; `{ "id": null }` clears it. Installations never inherit the default. A Session create request can name `environment: { "id": "..." }`, `environment: { "name": "..." }`, or `environment: null` to use the standard sandbox. Omission uses the default for a person and no environment for an installation. Installations need `environments:manage` to select one. The selection is part of the idempotent create request. A Session response shows its bound id, name and current revision, or `null`. The sandbox receives `variables` and the pinned secret versions. `setup` runs in the workspace with a 600-second limit. An `environment_forbidden` result means the stored Turn authority can no longer use the Session; a failed or timed-out setup fails the waiting Turn with `environment_setup_failed` and a redacted output tail in the `wamp.environment.apply` event. Stop during setup releases the sandbox. A later Turn on the same lease joins the in-flight apply. `setup` runs as the sandbox user, without root, on the same image whichever compute the organization uses: Debian 12 with Node.js 22, compilers, `make`, `pkg-config`, the common `-dev` libraries, `git`, `curl`, `ssh`, and Python 3 with its headers, `pip` and `venv`. It cannot install `apt` packages or call `sudo`; install toolchains and CLIs into the user's home or the workspace instead. The system Python is externally managed (PEP 668), so install Python packages into a virtual environment: ```bash npm ci python3 -m venv .venv .venv/bin/pip install -r requirements.txt echo "PATH=$PWD/.venv/bin:$PATH" >> "$WAMP_ENV" ``` The last line puts the environment's Python first on `PATH` for the agent. The image also has the `wamp` command, so `wamp ext init` and `wamp ext pack` build an extension archive inside the sandbox without a network. `cache.paths` may contain workspace-relative paths or `~/` paths. On a cache hit Cloud restores the archive before running `setup`. The key includes the environment inputs, GitHub access, the repositories the sandbox clones, sandbox image and pool, launch repository and branch, and the UTC day. A clean sandbox may save a new archive once per key and day; restored or forked sandboxes never receive a save slot. A save skipped because of a secret or the size limit appears as `cacheSkip` in the apply event, and a restore the sandbox discarded (a failed download, a sha256 mismatch, a malformed archive) as `cacheDiscarded` with the reason. Cache content must not include credentials or WAMP state. `repositories` names up to 20 GitHub repositories a Session without a launch repository starts with. It needs `github: true`, because the clones use that access. Each entry is `{ "repository": "owner/name" }` with an optional `branch` (a branch or tag; default: the repository's default branch) and an optional `path`, the directory under `/workspace` (default: the repository name). A path is one name of letters, digits, `.`, `_` or `-`, does not start with `.` or `-`, and is not `project`; paths must differ, ignoring case, after the defaults are filled in, so `acme/web` and `other/web` need a `path` for one of them. Reads return the entries as written, and `[]` when none is set. ```json { "name": "product", "setup": "(cd backend && npm ci)\n(cd site && npm ci)", "github": true, "repositories": [ { "repository": "acme/backend" }, { "repository": "acme/web", "branch": "develop", "path": "site" } ] } ``` Before the cache restore and `setup`, every fresh sandbox of such a Session clones each listed repository that is missing from `/workspace/`, without file contents until they are needed (`git clone --filter=blob:none`). Anything already at a path, such as a directory restored from a checkpoint, is left alone. A clone that fails fails the apply with `environment_setup_failed` and names the repository; nothing after it runs. All clones of one sandbox share a five-minute limit. A Session with a launch repository keeps its single repository at `/workspace/project` and skips the list. The terminal `wamp.environment.apply` event reports each entry as `cloned`, `kept`, `skipped` or `failed`. Editing the list affects each Session's next fresh sandbox; it only adds clones and never removes a directory. ## Extensions `extensions` lists up to 20 extensions that every sandbox installs after `setup` has run and the cache has been saved, each listed once: `{ "catalog": "" }` for an item of the WAMP extension catalog, or `{ "archive": "sha256:" }` for a packed extension the environment owns. ```json { "name": "issues", "setup": "npm ci", "extensions": [ { "catalog": "wamp-mcp-postgres" }, { "archive": "sha256:5e5e…" } ] } ``` A catalog item installs at the version the catalog serves when the sandbox starts. A write checks only the catalog items it adds: a slug the catalog does not list, or lists with no release this platform runs, is `400 environment_invalid`, and a catalog Cloud cannot reach is `503 cloud_extension_catalog_unavailable` with `Retry-After`. An archive is the `.zip` that `wamp ext pack` writes. Upload it with `PUT /v1/environments/{environmentId}/archives/{sha256}`, `Content-Type: application/zip` and at most 32 MiB, where `sha256` is the hex digest of the bytes. The answer is `{ "archive": { "archive", "sizeBytes", "extension": { "id", "name", "version" } } }`. An upload writes no revision and takes no `If-Match`; the archive runs once a revision lists it, and uploading the same bytes again answers the same archive. Cloud refuses, with `400 environment_invalid` and the reason in `issues`, an archive that is not a zip, has an entry outside its root, carries no valid `extension.json`, needs a plugin API this platform does not serve, declares an agent runtime, or needs the desktop app (`requiresElectron`). An environment's reads list its archives under `archives`. An organization holds at most 1 GiB of archives (`409 quota_exceeded`). An archive that no revision in the retention window lists — the last 30 days, the last 50 revisions and the newest revision a person wrote — is deleted a day after it was uploaded, and restoring a revision that names a deleted archive is `400 environment_invalid` naming it: upload it again first. The agent of a Session adds an archive it built without sending bytes. It presents the `.zip` with `present_files`, then names its digest with the `environments.archives.put` operation, or lists `{ "archive": "sha256:" }` in `environments.put` directly. Cloud copies the newest file with that digest presented in the agent's own Session or in a Session it started; any other digest is refused. An extension that cannot install does not fail the apply. It may be refused in the sandbox — built into the sandbox already, declaring an agent runtime, needing the desktop app, or connecting only through an OAuth sign-in a sandbox cannot complete — or its package may fail to download or verify. After the installs, MCP servers already running restart on the environment's variables and secrets, MCP servers newly declared since the sandbox started (for example by `setup`) start, and pooled agent-runtime connections are recycled. The apply then waits up to 120 seconds for the MCP servers the extensions declare. The terminal `wamp.environment.apply` event lists every entry as `active` or `failed` with a reason, and the transcript's setup row shows them: an extension whose MCP server did not start is `failed`, with the server's error. ### MCP servers An environment gets an MCP server in one of three ways: - a catalog extension that declares one; - an archive that declares one — an `extension.json` with `contributes.mcpServers` and no code is a complete extension, and one that ships its own server keeps it under `runtime/` and names it as `"args": ["${extensionDir}/runtime/server.mjs"]` (see the [manifest reference](https://docs.vampikez.fun/reference/manifest/#contributesmcpservers)); - a `.wamp/mcp-servers.json` in the Session's workspace, committed with the repository or written by `setup`, which runs in that directory. ```json { "servers": { "tracker": { "transport": "http", "url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer ${TRACKER_TOKEN}" } }, "search": { "command": "npx", "args": ["-y", "@example/search-mcp@1.4.0"], "env": { "SEARCH_API_KEY": "${SEARCH_API_KEY}", "REGION": "${REGION:-eu}" } } } } ``` `${NAME}` and `${NAME:-default}` expand from the sandbox's environment, which holds the environment's variables and secrets, in `command`, `args`, `env`, `url` and `headers`. A local server's process receives a basic environment (such as `PATH` and `HOME`), what its `env` names, and the variables its extension declares — not every secret. Keep tokens in secrets and reference them this way rather than writing them into the file. MCP servers reach every agent runtime; an extension's own tools reach only the WAMP runtime, so tools meant for every runtime belong in an MCP server. ## Archiving Archiving is reversible and leaves existing Session bindings intact. The current default cannot be archived. Unarchiving a name already used by a live environment returns `409 environment_name_conflict`. At most 200 live environments may belong to an organization.