Skip to content

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.

ContextIsolationUse it when
process (default)None, runs on the host. An optional OS sandbox limits writes and network.You trust the prompt
dockerLocal containerYou want a disposable environment on your machine
e2bRemote micro-VMYou want isolation off your machine, hosted or self-hosted
daytonaRemote sandboxSame, on Daytona
Terminal
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
FlagApplies to
--cwd, --env KEY=VALUE, --pass-env NAMEAll contexts
--imagedocker
--pregame, --sandbox, --sandbox-on-destroye2b, 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.

TypeScript
import { createProcessContext, parseAllowWriteSetting, resolveWritableRoots } from 'zidane'

const execution = createProcessContext({
  cwd,
  isolation: {
    writableRoots: resolveWritableRoots(cwd, { extra: parseAllowWriteSetting('~/.cache', cwd) }),
    network: 'allow', // or 'deny'
  },
})
OptionBehavior
writableRootsWhere 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.
unreadableDirsHidden 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:

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

TypeScript
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.

Terminal
bun add e2b
export E2B_API_KEY=e2b_xxxxxxxxxxxxxxxx
bun start --prompt 'run the test suite' --context e2b
Terminal
bun add @daytona/sdk
export DAYTONA_API_KEY=dtn_xxxxxxxxxxxxxxxx
bun start --prompt 'run the test suite' --context daytona
FlagBehavior
--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-destroyFor 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.
Terminal
# 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.

EnvironmentWhere commands runFiles
LocalThis computerYour checkout. With the OS sandbox on, commands run in the OS sandbox.
WorktreeThis computer, in the OS sandbox when it is onA new git worktree on its own branch, in the data folder, next to the repo, or in .zidane/worktrees/
DockerA container on this computerYour folder, or a fresh worktree, mounted into the container
E2BA 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.
DaytonaA Daytona sandbox per session (DAYTONA_API_KEY)A clone of the repo, like E2B
A machineAnother computer linked to a zidane-serverThat machine's folders. See 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:

TypeScript
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).

ContextImplementation
processLocal PTY
dockerLocal PTY running docker exec -it
e2b, daytonaThe 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.

Reference#