Environment files live under `.ellipsis/` on the default branch, with `ellipsis.kind: environment` and a unique name. See [Environments](/docs/environments) for when each setup script runs.

## Node.js project

```yaml title=".ellipsis/environments/web-environment.yaml"
ellipsis:
  kind: environment
  name: web-environment

repositories:
  - name: web-repo

hooks:
  build_base:
    inputs:
      - web-repo/package.json
      - web-repo/package-lock.json
    run: |
      cd /sandbox/web-repo
      npm ci
  after_checkout: |
    cd /sandbox/web-repo
    npm run build --if-present
  before_start: |
    cd /sandbox/web-repo
    test -d node_modules

compute:
  memory: 8GB
```

`npm ci` reruns only when `package.json` or the lockfile changes. The build reruns for each new commit. `before_start` checks that dependencies exist each time a session starts or resumes.

## Python project

```yaml title=".ellipsis/environments/python-environment.yaml"
ellipsis:
  kind: environment
  name: python-environment

repositories:
  - name: api-repo

hooks:
  build_base:
    inputs: [api-repo/requirements.txt]
    run: |
      cd /sandbox/api-repo
      python -m venv .venv
      .venv/bin/pip install -r requirements.txt

variables:
  - name: PYTHONUNBUFFERED
    value: '1'

compute:
  cpu: 2
  memory: 8GB
  timeout: 45m
```

The agent can run `/sandbox/api-repo/.venv/bin/python` with the project's dependencies.

## Multiple repositories

```yaml title=".ellipsis/environments/full-stack-environment.yaml"
ellipsis:
  kind: environment
  name: full-stack-environment

repositories:
  - name: web-repo
    ref: main
  - name: api-repo
    ref: main

hooks:
  build_base:
    inputs:
      - web-repo/package.json
      - web-repo/package-lock.json
      - api-repo/requirements.txt
    run: |
      (cd /sandbox/web-repo && npm ci)
      (cd /sandbox/api-repo && python -m venv .venv)
      /sandbox/api-repo/.venv/bin/pip install -r /sandbox/api-repo/requirements.txt

compute:
  cpu: 4
  memory: 16GB
```

Each repository is checked out at `/sandbox/<name>`. A repository's `owner` defaults to your account.

## Add environment variables and secrets

Set a plain value with `value`. For a secret, [store it](/docs/secrets) first, then list its name without a `value`:

```yaml title=".ellipsis/environments/private-packages.yaml"
ellipsis:
  kind: environment
  name: private-packages

repositories:
  - name: web-repo

variables:
  - name: NPM_TOKEN
  - name: NODE_ENV
    value: test

hooks:
  build_base:
    inputs: [web-repo/package.json, web-repo/package-lock.json, web-repo/.npmrc]
    run: |
      cd /sandbox/web-repo
      npm ci
```

`NODE_ENV` is `test`, and `NPM_TOKEN` comes from the stored secret, so its value never appears in git. Both are available to setup scripts and the agent.

- Names use letters, digits, and underscores, and can't start with a digit.
- A missing secret fails the session.
- Changing a variable reruns all setup scripts.
- Names starting with `ANTHROPIC_`, `OPENAI_`, `CODEX_`, or `CLAUDE_CODE_USE_`, and `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CONFIG_DIR`, and `ELLIPSIS_LLM_PROXY_URL`, are rejected.

## Install system tools

```yaml title=".ellipsis/environments/system-tools.yaml"
ellipsis:
  kind: environment
  name: system-tools

repositories:
  - name: api-repo

hooks:
  build_base:
    inputs: [api-repo/package.json, api-repo/package-lock.json]
    run: |
      sudo apt-get update
      sudo apt-get install -y jq
      cd /sandbox/api-repo
      npm ci
```

The agent can run `jq`. Install system packages in `build_base` with `sudo`.

## Add an MCP server

List MCP servers under `mcp_servers`. Claude Code and Codex sessions both get their tools.

```yaml title=".ellipsis/environments/api-with-tools.yaml"
ellipsis:
  kind: environment
  name: api-with-tools

repositories:
  - name: api-repo

mcp_servers:
  - name: internal-tools
    url: https://tools.example.com/mcp
    headers:
      Authorization: Bearer ${TOOLS_TOKEN}
  - name: repo-tools
    command: node
    args: [/sandbox/api-repo/scripts/mcp-server.js]
```

Sessions using `api-with-tools` get the tools of both servers.

- A server with `url` is a remote server that speaks streamable HTTP. Replace the example URL with your server's.
- A server with `command` runs inside the sandbox, with optional `args` and `env`. Install anything it needs in `build_base`.
- `${NAME}` in `headers` or `env` reads the [secret](/docs/secrets) `NAME` when the session starts. It doesn't need a `variables` entry. A missing secret fails the session.
- To add the tools of a connected [Slack](/docs/integrations/slack) or [Linear](/docs/integrations/linear) integration, list it by name: `- slack` or `- linear`. Listing an integration that isn't connected fails the session at start.
- A repository's own `.mcp.json` is ignored.
- With Codex, a server that fails to start fails the turn.

## Inline environments

An agent can define its environment in `session.environment` instead of naming a saved one. Use the same fields without the `ellipsis` block:

```yaml fragment
session:
  claude_code:
    model: claude-opus-5-5
  environment:
    repositories:
      - name: api-repo
    hooks:
      build_base:
        inputs: [api-repo/package.json, api-repo/package-lock.json]
        run: |
          cd /sandbox/api-repo
          npm ci
    compute:
      memory: 8GB
```

## Fields

| Field | Type | Meaning |
| --- | --- | --- |
| `ellipsis` | `object`, required | Identifies the file. |
| `ellipsis.kind` | `string`, required | `environment`. One of `environment`. |
| `ellipsis.name` | `string` | Unique environment name. |
| `ellipsis.description` | `string` | Description shown in the dashboard. |
| `ellipsis.metadata` | `object` | `labels` and `annotations` for your own use. |
| `ellipsis.metadata.labels` | `list of string` | Short tags. |
| `ellipsis.metadata.annotations` | `map of string` | Free text values by key. |
| `repositories` | `list of object` | Each repository is checked out at `/sandbox/<name>`. |
| `repositories[].name` | `string`, required | The repository name, without the owner. |
| `repositories[].owner` | `string` | The GitHub owner. Default your account. |
| `repositories[].ref` | `string` | Branch, tag, or commit. Default the default branch. |
| `variables` | `list of object` | Environment variables for setup scripts and the agent. Changing a variable reruns all setup scripts. |
| `variables[].name` | `string`, required | Letters, digits, and underscores; can't start with a digit. Names starting with `ANTHROPIC_`, `OPENAI_`, `CODEX_`, or `CLAUDE_CODE_USE_` are rejected. |
| `variables[].value` | `string` | A plain value; omit it to use the stored [secret](/docs/secrets) of the same name. A missing secret fails the session. |
| `hooks` | `object` | Setup scripts; see [Setup and hooks](/docs/environments#setup-and-hooks). |
| `hooks.build_base` | `string or object` | Script or `{run, inputs}`; installs dependencies. A string reruns on any source change. |
| `hooks.build_base.run` | `string`, required | The script. |
| `hooks.build_base.inputs` | `list of string` | Exact files, starting with the repository name; omit for all source, or `[]` for none. |
| `hooks.after_checkout` | `string or object` | Script or `{run}`; prepares each new commit. |
| `hooks.after_checkout.run` | `string`, required | The script. |
| `hooks.before_start` | `string or object` | Script or `{run}`; runs whenever a session starts or resumes. |
| `hooks.before_start.run` | `string`, required | The script. |
| `compute` | `object` | The size of the environment. |
| `compute.cpu` | `integer` | 2 to 32 vCPUs. Default `2`. |
| `compute.memory` | `string` | 4096MB to 64GB. Accepts `MB` or `GB`. Default `4096MB`. |
| `compute.timeout` | `string` | 60 seconds to 1 hour. Accepts `s`, `m`, and `h`, including combinations such as `1h30m`. Default `1h`. |
| `mcp_servers` | `list of string or object` | `slack`, `linear`, remote servers, or local servers; see [Add an MCP server](/docs/environments/schema#add-an-mcp-server). |
| `mcp_servers[].name` | `string`, required | The server name. `slack` or `linear` attaches a connected integration. |
| `mcp_servers[].url` | `string` | A remote server that speaks streamable HTTP. |
| `mcp_servers[].headers` | `map of string` | Sent with every request. `${NAME}` reads the [secret](/docs/secrets) `NAME` when the session starts. |
| `mcp_servers[].command` | `string` | A local server run inside the environment. Install anything it needs in `build_base`. |
| `mcp_servers[].args` | `list of string` | Arguments for `command`. |
| `mcp_servers[].env` | `map of string` | Environment variables for `command`. `${NAME}` reads the [secret](/docs/secrets) `NAME` when the session starts. |

Memory accepts `MB` or `GB`. Timeout accepts `s`, `m`, and `h`, including combinations such as `1h30m`, within the allowed range.
