Skip to content

Organization 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:

Terminal window
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.

{
"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/<path>, 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 lists up to 20 extensions that every sandbox installs after setup has run and the cache has been saved, each listed once: { "catalog": "<slug>" } for an item of the WAMP extension catalog, or { "archive": "sha256:<hex>" } for a packed extension the environment owns.

{
"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:<hex>" } 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.

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);
  • a .wamp/mcp-servers.json in the Session’s workspace, committed with the repository or written by setup, which runs in that directory.
{
"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 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.