# Environments

A named execution context binding one sandbox + one repo policy + a set of secret-name bindings into something a thread can reference, durable and inspectable within a house. `threads.environment_id` carries an FK with `on delete set null`, and the thread row keeps a snapshot of the environment — so later env edits or teardown don't rewrite a thread's history.

```
environments {
  id, house_id (FK cascade), author_id, name, sandbox_id (required at row level),
  default_repo, allowed_repos[], github_installation_id,
  secret_bindings: jsonb [{name, required}]            # resolved against house secrets at dispatch
}
```

`POST /api/environments` with `sandbox_id` omitted **auto-provisions** a box on the env's `runtime`. Daytona (the default) creates a fresh house-scoped sandbox via `DAYTONA_API_KEY` — no pi install (a daytona box ships a shell; `delegate_task` spins its own per-run box). `runtime: 'sprite'` instead provisions a sprite (name `<env-name>-<short-id>`, requires the house's `SPRITES_TOKEN`) and runs `setupLifecycle` to install pi before writing the row. Bindings store names, not values — see [secrets](./secrets.md) for resolution. Sandbox pi defaults to `defaultModelRef` ([llm-keys](./llm-keys.md)) — bind `OPENROUTER_API_KEY`. Provider-specific keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …) still work paired with a matching `thread.config.model` override.

| Endpoint | Auth | Notes |
|---|---|---|
| `GET /api/environments?house_id=<uuid>` | house member | list |
| `GET /api/environments/<id>` | house member | fetch one |
| `POST /api/environments` | member-write | auto-provisions on the runtime (daytona default) when `sandbox_id` omitted |
| `PATCH /api/environments/<id>` | member-write | partial update |
| `DELETE /api/environments/<id>` | owner-only | soft delete |

GitHub App integration: when `github_installation_id` is set, dispatch mints a short-lived installation access token scoped to `allowed_repos` and injects it as `GITHUB_TOKEN`. Two browser-only endpoints (`/api/github/installations`, `/api/github/installations/:id/repos`) require a GitHub OAuth provider session — not callable with an arbe API key. Server needs `GITHUB_APP_ID` (numeric) + `GITHUB_APP_PRIVATE_KEY` (PEM RSA) as wrangler secrets; absent → 501 on listing endpoints, dispatch silently skips token minting (the environment still works, just without `GITHUB_TOKEN`).

CLI (all commands operate on the active house, set via `arbe house select`):

```sh
arbe env list [--json]
arbe env view <name-or-id>                 # name (case-insensitive) or UUID
arbe env create <name> --sandbox <id> --secret OPENROUTER_API_KEY [--optional-secret NICE_KEY]
arbe env bind-secret <name|id> <SECRET_NAME> [--optional]      # idempotent — same --optional is no-op,
arbe env unbind-secret <name|id> <SECRET_NAME>                 #   different value upgrades/downgrades
arbe env delete <name|id>
```

`--secret` (required) and `--optional-secret` (warn-only at dispatch) are repeatable on `create`. Dispatch with `--env`: `arbe --env work thread entries create <ref> "fix the bug"` — `--env` is a root-level option that resolves the environment by name or ID and uses its `sandbox_id`, overriding any `-s` flag. Sandboxes are created once and reused across dispatches.

Code: `packages/core/schemas/environment.ts` (`EnvironmentRowSchema`), `packages/core/environments.ts`, `apps/www/src/routes/api/environments/`.<br>
See [daytona runtime](./sandbox-daytona.md), [system/sandbox-sprite](./sandbox-sprite.md), [system/secrets](./secrets.md), [system/dispatch](./dispatch.md), [runtime](./runtime.md).
