Environments and Secrets

An environment is the reusable runtime definition for a repo or set of repos. It answers: which platform should boot, which sources should clone, which setup commands should run once, which services should start every boot, and which agent or simulator features should be available.

VMs and sessions boot from environments. A good environment is the foundation for fast, repeatable API automation.

What Goes In An Environment

An environment has a name, visibility, and optional AES manifest.

The manifest can define:

  • platform: linux/amd64 or darwin/arm64, plus memory and vCPU settings.
  • sources: Git repositories and workspaces.
  • env: string environment variables, including ${secrets.NAME} references.
  • setup: sequential commands run while building the environment snapshot.
  • services: long-running processes started on every boot.
  • commands: named commands available to the ready phase, API command execution, or UI.
  • ready: lightweight named commands to run after services are healthy on each boot.
  • simulators: iOS simulator configuration for macOS environments.
  • docker_compose: an alternative to setup and services for Docker Compose projects.
  • agent: instruction files and MCP servers available to coding sessions.
  • callbacks: lifecycle webhook configuration.

Use the Manifest Reference for field-level manifest details.

Visibility

New environments default to private.

VisibilityWho can use it
privateOnly the creator can see, use, or change it.
account_readAccount members can see and use it; only the creator can edit or delete it.
account_editAccount members can see, use, edit, and delete it.

Only the creator can change visibility.

Create An Environment

Creating or updating an environment with a manifest starts snapshot generation. The snapshot bakes in source checkout and setup so later VMs and sessions can boot quickly.

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: env-my-api-v1" \
  -d '{
    "name": "my-api",
    "visibility": "private",
    "manifest": {
      "version": 1,
      "platform": { "os": "linux/amd64" },
      "sources": [
        {
          "repo": "acme/my-api",
          "workspace": "/workspace/my-api",
          "branch": "main"
        }
      ],
      "setup": [
        {
          "name": "install",
          "run": "npm ci",
          "working_dir": "/workspace/my-api"
        }
      ],
      "services": [
        {
          "name": "web",
          "run": "npm run dev",
          "working_dir": "/workspace/my-api",
          "ports": [{ "port": 3000, "name": "web", "protocol": "http" }],
          "healthcheck": { "url": "http://localhost:3000" }
        }
      ]
    }
  }'

Store the returned environment ID. You pass it to tasks, sessions, and direct VM boots.

Add Secrets Before Referencing Them

Secret values are encrypted, write-only, and scoped to one environment. After creation, the API returns metadata only; you cannot read the value back. Rotate the secret if you need to replace it.

Secret names must start with an uppercase letter and contain only uppercase letters, digits, and underscores. Values can be up to 64 KiB and may contain multiple lines.

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID/secrets" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: secret-db-password-v1" \
  -d '{
    "name": "DB_PASSWORD",
    "value": "replace-me"
  }'

Reference secrets from string-valued manifest fields:

{
  "env": {
    "DATABASE_URL": "postgres://app:${secrets.DB_PASSWORD}@localhost:5432/app"
  }
}

If a manifest references a missing secret, environment creation, environment update, or snapshot generation fails with missing_secrets and includes the unresolved names and field paths.

For a brand-new environment, create it without secret references, create the secrets, then patch the manifest in.

Deleting a secret is rejected with secret_in_use while the environment manifest still references it. Remove the ${secrets.NAME} references from the manifest first, patch the environment, then delete the secret.

MCP Servers

mcp_servers sits on the environment beside manifest, not inside it, and maps each server's name to its definition. Changing it never regenerates the snapshot; the next session picks it up.

curl -s -X PATCH "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mcp_servers": {
      "issues": {
        "url": "https://mcp.example.com/mcp",
        "headers": { "Authorization": "Bearer ${secrets.ISSUES_TOKEN}" }
      },
      "observability": {
        "url": "https://mcp.example.com/o/mcp",
        "auth": { "type": "oauth", "scopes": ["project:read"] }
      },
      "database": {
        "command": "npx",
        "args": ["-y", "@anthropic/mcp-postgres", "$DATABASE_URL"]
      }
    }
  }'
  • A remote server is {url, headers}: a hosted https endpoint the session calls with the headers you supply. Header values may reference environment secrets as ${secrets.NAME} and are returned as written, never as the value.
  • A remote server that signs in with OAuth is {url, auth: {"type": "oauth", scopes}}. It carries no header you supply, and auth and headers are mutually exclusive.
  • A sandbox server is {command, args, env}: a stdio MCP server the platform runs inside the session VM.

Server names are 1-64 characters of lowercase letters, digits, hyphens, and underscores. sandbox, simulator, comms, and submit_result are reserved.

Sending mcp_servers replaces the whole map, so patch the full set each time; pass null to remove every server. At most 20 servers, and each kind of definition must serialize to at most 32 KiB, measured after ${secrets.NAME} references are substituted.

Sign In To An OAuth MCP Server

A server configured with auth cannot be used until somebody has signed in to it once. The environment's read-only mcp_connections map reports where each one stands: disconnected, connected, or needs_reconnect.

Start a sign-in by creating a connection:

curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID/connections" \
  -H "Authorization: Bearer $WALLFACER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: connect-observability-v1" \
  -d '{ "server_name": "observability" }'

The response carries authorization_url. Open it in a browser: the provider shows its own consent screen and, on approval, redirects back to Wallfacer, which completes the exchange. The link is good for one use and expires in ten minutes. The server reads connected in mcp_connections once that lands, and the platform renews the grant in the background from then on. Signing in again to a connected server replaces the grant rather than adding a second one.

If the provider requires a client you registered yourself, pass client_id and client_secret alongside server_name. If the server publishes no usable sign-in metadata, the request fails with mcp_discovery_failed; configure that server with a header instead.

GET .../connections lists the sign-ins established for the environment, oldest first. DELETE .../connections/{connection_id} discards the stored grant and returns the server to disconnected. The provider is not told, so revoke the grant there too if you want it gone everywhere.

Sessions reach an OAuth server through the platform, which unseals the token for each request and forwards the call. The VM the agent works on never receives the token, and the agent cannot tell an OAuth server from any other. needs_reconnect means the grant stopped working, revoked upstream or expired with no way to renew: sign in again to repair it, and no session can use the server until you do.

Wait For The Base Snapshot

The environment response includes base_snapshot. While generation is running, its status is generating. When setup completes and the disk is captured, the status becomes ready. If setup or service health checks fail, the status becomes failed.

while true; do
  status=$(curl -s "$WALLFACER_API/accounts/$ACCOUNT_ID/environments/$ENVIRONMENT_ID" \
    -H "Authorization: Bearer $WALLFACER_TOKEN" \
    | jq -r ".data.base_snapshot.status // \"none\"")

  echo "snapshot: $status"

  case "$status" in
    ready) break ;;
    failed) exit 1 ;;
    *) sleep 10 ;;
  esac
done

A ready base snapshot is what makes later VM and session boots fast.

Update Carefully

Patching the manifest starts a new snapshot generation cycle. Manifest-changing edits can be rejected while generation is already in flight.

Use the snapshot and log routes to inspect failed generations rather than repeatedly patching the environment. See Snapshots and Logs.