# Execution contexts

> Where the agent's shell commands and file operations run.

Every filesystem or shell tool call runs inside an execution context. All contexts share one interface (spawn, exec, read, write, list files, destroy). They differ in where the work runs and how it is isolated.

| Context | Isolation | Use it when |
| - | - | - |
| `process` (default) | None, runs on the host. An optional [OS sandbox](#os-sandbox) limits writes and network. | You trust the prompt |
| `docker` | Local container | You want a disposable environment on your machine |
| `e2b` | Remote micro-VM | You want isolation off your machine, hosted or self-hosted |
| `daytona` | Remote sandbox | Same, on Daytona |

```bash
bun start --prompt 'build a TODO API'                       # process (default)
bun start --prompt 'build a TODO API' --context docker      # docker
bun start --prompt 'build a TODO API' --context e2b         # e2b
```

| Flag | Applies to |
| - | - |
| `--cwd`, `--env KEY=VALUE`, `--pass-env NAME` | All contexts |
| `--image` | `docker` |
| `--pregame`, `--sandbox`, `--sandbox-on-destroy` | `e2b`, `daytona` (other contexts warn and ignore them) |

## OS sandbox

The `process` context runs commands as host child processes with full filesystem and network access. `isolation` wraps every shell command in a kernel sandbox: `sandbox-exec` (Seatbelt) on macOS, `bwrap` (bubblewrap) on Linux. Windows is unsupported.

```ts
import { createProcessContext, parseAllowWriteSetting, resolveWritableRoots } from 'zidane'

const execution = createProcessContext({
  cwd,
  isolation: {
    writableRoots: resolveWritableRoots(cwd, { extra: parseAllowWriteSetting('~/.cache', cwd) }),
    network: 'allow', // or 'deny'
  },
})
```

| Option | Behavior |
| - | - |
| `writableRoots` | Where commands can create, change or delete files. `resolveWritableRoots` returns `cwd`, `$TMPDIR`, `/tmp`, `/var/tmp` and, in a linked worktree, git's common dir. `extra` takes absolute paths. `parseAllowWriteSetting` expands `~` and relative paths. |
| `network` | `'allow'` or `'deny'`. There is no per-domain allowlist. |
| `unreadableDirs` | Hidden from commands. Defaults to credential paths: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.azure`, `~/.config/gcloud`, `~/.kube`, `~/.docker/config.json`, `~/.netrc`, `~/.credentials.json` and zidane's credential files. |

The kernel sandbox covers `exec`, `execStream` and `execBackground`: the `shell` tool, stdio MCP servers bound to the context, sub-agents and terminals. `write_file`, `edit` and `multi_edit` apply the same writable roots in-process. `read_file`, `glob` and `list_files` stay unrestricted.

If the backend is missing (`bwrap` not installed, unsupported platform), every shell call fails with exit 127. Nothing runs unsandboxed. A denied command gets a line naming the writable roots and the setting that widens them, so the model can ask you. The apps call these settings Sandbox shell commands and Sandbox writable paths.

## Docker

Needs a running Docker daemon and the optional `dockerode` and `tar-stream` peers:

```bash
bun add dockerode tar-stream
bun start --prompt 'run pytest' --context docker --image python:3.12
```

Defaults: image `oven/bun:latest`, `cwd` `/workspace`. zidane pulls missing images, keeps the container alive with `sleep infinity` and removes it when the run ends. Mounts, port mappings, run user and resource limits need `createDockerContext`:

```ts
import { createDockerContext } from 'zidane/contexts/docker'

createDockerContext({ image: 'node:22', cwd: '/workspace', limits: { memory: 512, cpu: '1.0' } })
```

Docker has its own subpath, so `dockerode` enters your bundle only if you import it.

## E2B and Daytona

Each run gets a fresh remote sandbox, destroyed when the run ends.

```bash
bun add e2b
export E2B_API_KEY=e2b_xxxxxxxxxxxxxxxx
bun start --prompt 'run the test suite' --context e2b
```

```bash
bun add @daytona/sdk
export DAYTONA_API_KEY=dtn_xxxxxxxxxxxxxxxx
bun start --prompt 'run the test suite' --context daytona
```

| Flag | Behavior |
| - | - |
| `--cwd <path>` | Created with `mkdir -p`. If unset, zidane finds the home directory with `pwd`. |
| `--pregame <file>` | Uploads the script to `/tmp/<filename>` and runs it before the first turn, with a 30 minute timeout. A non-zero exit aborts the run. |
| `--sandbox <id>` | Attaches to an existing sandbox, which zidane never kills or pauses |
| `--sandbox-on-destroy` | For sandboxes zidane created: `kill` (default), `pause` (keeps the state for a later `--sandbox <id>`), or `leave` (drops the handle, no API call). On Daytona, `kill` deletes the sandbox and `pause` stops it. |

```bash
# Pause at the end of the run so you can reconnect to the same filesystem later.
bun start --prompt '...' --context e2b --sandbox-on-destroy pause
# prints the sandbox id; pass it to the next run with --sandbox
```

E2B's `domain` option (or `E2B_DOMAIN`) targets a self-hosted cluster. Self-hosted clusters have no `base` template, so set `E2B_TEMPLATE`. An invalid template still returns a sandbox id, but the sandbox never boots and every command hangs until timeout. `createDaytonaProvider` also accepts `onDestroy` values `stop`, `archive` and `delete`, and an `ownAttached` flag. The CLI has neither.

## Environments in the apps

In the apps, the environment picker sets where a new session runs. Each kind has its options under Settings, Environments.

| Environment | Where commands run | Files |
| - | - | - |
| Local | This computer | Your checkout. With the OS sandbox on, commands run in the [OS sandbox](#os-sandbox). |
| Worktree | This computer, in the OS sandbox when it is on | A new git worktree on its own branch, in the data folder, next to the repo, or in `.zidane/worktrees/` |
| Docker | A container on this computer | Your folder, or a fresh worktree, mounted into the container |
| E2B | A micro-VM per session, hosted or self-hosted (`E2B_API_KEY`) | A clone of the repo. The agent can push and open a pull request. |
| Daytona | A Daytona sandbox per session (`DAYTONA_API_KEY`) | A clone of the repo, like E2B |
| A machine | Another computer linked to a zidane-server | That machine's folders. See [machines](https://zidane.si/docs/server-clients.md#machines). |

E2B and Daytona clone the repo with a token from the environment's `gitToken` setting, then `GH_TOKEN` or `GITHUB_TOKEN` (github.com only), then `gh auth token`. zidane also stores the token in the sandbox for `git push`, so the agent can read it.

Once a minute, the host suspends sandboxes idle past their environment's limit. E2B pauses and keeps its state. Daytona stops and keeps its disk. The next message resumes it.

## Programmatic use

Each CLI flag maps to a factory:

```ts
import { createProcessContext, createSandboxContext } from 'zidane/contexts'
import { createDaytonaProvider } from 'zidane/contexts/daytona'
import { createDockerContext } from 'zidane/contexts/docker'
import { createE2BProvider } from 'zidane/contexts/e2b'

const proc = createProcessContext({ cwd: '/tmp/work', env: { CI: '1' } })
const docker = createDockerContext({ image: 'python:3.12', cwd: '/app' })

// e2b: pause on exit so the sandbox can be reconnected later
const e2b = createSandboxContext(createE2BProvider({ cwd: '/home/user/project', onDestroy: 'pause' }))
// e2b: reconnect to a previously paused sandbox
const e2bResume = createSandboxContext(createE2BProvider({ sandboxId: 'sbx-abc123def456' }))

const daytona = createSandboxContext(createDaytonaProvider({ cwd: '/home/daytona/project' }))
```

`createSandboxContext` accepts any `SandboxProvider`, including your own backend.

## Terminals

`openTerminal(handle, { cols, rows, cwd?, env?, unsetEnv?, onData })` opens an interactive login shell in the context and returns a `TerminalProcess` (`write`, `resize`, `kill`, `detach`, `exited`).

| Context | Implementation |
| - | - |
| `process` | Local PTY |
| `docker` | Local PTY running `docker exec -it` |
| `e2b`, `daytona` | The SDK's PTY API, with `reconnectTerminal`, because the PTY outlives the host process |

To run a stdio MCP server inside the context, see [where a stdio server runs](https://zidane.si/docs/mcp.md#where-a-stdio-server-runs).

## Reference

- [`zidane/contexts`](https://zidane.si/docs/reference/contexts.md)
- [`zidane/contexts/sandbox`](https://zidane.si/docs/reference/contexts-sandbox.md)
- [`zidane/contexts/docker`](https://zidane.si/docs/reference/contexts-docker.md)
- [`zidane/contexts/e2b`](https://zidane.si/docs/reference/contexts-e2b.md)
- [`zidane/contexts/daytona`](https://zidane.si/docs/reference/contexts-daytona.md)
