Skip to content

zidane/contexts

Execution contexts, the environments where an agent runs shell commands and reads and writes files.

import { … } from 'zidane/contexts'src/contexts/index.ts

Use this entry point to choose or build the place where tool calls run. It holds the ExecutionContext interface that every backend implements, the in-process createProcessContext, and the helpers for the optional OS sandbox. Docker, E2B and Daytona have their own subpaths, so their SDKs load only when you import them. See Execution contexts for the guide.

TypeScript
import { createProcessContext } from 'zidane/contexts'

const context = createProcessContext({ cwd: process.cwd() })
const handle = await context.spawn()
const result = await context.exec(handle, 'echo hello')
console.log(result.stdout, result.exitCode)
await context.destroy(handle)

Functions#

createProcessContextFunction#

Creates an execution context that runs commands and file operations in the current process. No isolation unless config.isolation asks for an OS sandbox. This is the default context.

TypeScript
function createProcessContext(config?: SpawnConfig): ExecutionContext
Parameters
NameTypeDescription
config?SpawnConfig

Default spawn settings, such as cwd, file containment root and isolation.

Example
import { createAgent } from 'zidane'
import { createProcessContext } from 'zidane/contexts'
import { basic } from 'zidane/presets'
import { anthropic } from 'zidane/providers/anthropic'

const agent = createAgent({
  ...basic,
  provider: anthropic(),
  execution: createProcessContext({ cwd: process.cwd() }),
})

src/contexts/process.ts:42

entryPortOfFunction#

The context's own entry port for handle, else one over its commands.

TypeScript
function entryPortOf(context: ExecutionContext, handle: ExecutionHandle): EntryPort
Parameters
NameTypeDescription
contextExecutionContext

The execution context.

handleExecutionHandle

The handle the operations run in.

src/contexts/entries.ts:179

execEntryPortFunction#

An EntryPort over a context's exec (POSIX sh), its files written with the context's writeFile.

TypeScript
function execEntryPort(
  context: Pick<ExecutionContext, 'exec' | 'writeFile'>,
  handle: ExecutionHandle,
): EntryPort
Parameters
NameTypeDescription
contextPick<ExecutionContext, 'exec' | 'writeFile'>

The context: its commands and its file write.

handleExecutionHandle

The handle to run them in.

src/contexts/entries.ts:130

isolationAvailabilityFunction#

Whether the sandbox can actually be enforced on this host. Callers that requested isolation and get ok: false MUST fail closed (refuse to run the command) — silently running unsandboxed would turn a security setting into a placebo.

TypeScript
function isolationAvailability(
  platform?: Platform,
  env?: ProcessEnv,
): IsolationAvailability
Parameters
NameTypeDefaultDescription
platform?Platformprocess.platform

Platform to check.

env?ProcessEnvprocess.env

Environment used to look up bwrap on PATH.

IsolationAvailability

src/contexts/isolation.ts:135

listOwnedProcessesFunction#

The processes registered now.

TypeScript
function listOwnedProcesses(): OwnedProcess[]

src/contexts/owned-processes.ts:43

listSessionProcessesFunction#

The processes registered now.

TypeScript
function listSessionProcesses(): SessionProcess[]

src/contexts/session-processes.ts:41

nodeEntryPortFunction#

An EntryPort on this machine's filesystem.

TypeScript
function nodeEntryPort(options: NodeEntryPortOptions): EntryPort
Parameters
NameTypeDescription
optionsNodeEntryPortOptions

How paths resolve, what may change, and where removed entries go.

src/contexts/entries.ts:42

parseAllowWriteSettingFunction#

Parse the user-facing "extra writable paths" setting: comma- or newline-separated, ~ expanded, relative entries resolved against cwd, blanks dropped.

TypeScript
function parseAllowWriteSetting(raw: string, cwd: string, home?: string): string[]
Parameters
NameTypeDescription
rawstring

Comma- or newline-separated paths.

cwdstring

Base for relative entries.

home?string

Home directory that ~ expands to.

string[]

src/contexts/isolation.ts:175

registerSessionProcessFunction#

Records pid as started for sessionId.

TypeScript
function registerSessionProcess(pid: number, sessionId: string): () => void
Parameters
NameTypeDescription
pidnumber

The child's pid.

sessionIdstring

The session's id on this host.

() => void

Removes the record; idempotent, and a no-op once the pid was registered again.

src/contexts/session-processes.ts:31

resolveDetachedTasksCapabilityFunction#

Effective DetachedTasksCapability of a context, with the backward-compatible inference for contexts that predate the field.

TypeScript
function resolveDetachedTasksCapability(
  context: ExecutionContext,
): DetachedTasksCapability
Parameters
NameTypeDescription
contextExecutionContext

Execution context to inspect.

src/contexts/types.ts:80

resolveWritableRootsFunction#

The set of directories a sandboxed command may write to: the workspace (cwd), the system temp dirs, git's common dir when cwd is a linked worktree, plus any user-configured extras. All canonicalized.

TypeScript
function resolveWritableRoots(
  cwd: string,
  options?: ResolveWritableRootsOptions,
): string[]
Parameters
NameTypeDefaultDescription
cwdstring

Workspace directory.

options?ResolveWritableRootsOptions{}

Extra roots and overrides for the environment, platform and git lookup.

string[]

src/contexts/isolation.ts:321

resolveWritableRootsAsyncFunction#

Async twin of resolveWritableRoots for hosts that must not block (the daemon).

TypeScript
function resolveWritableRootsAsync(
  cwd: string,
  options?: Omit<ResolveWritableRootsOptions, 'gitCommonDir'> & { gitCommonDir?: (cwd: string) => Promise<string | null> },
): Promise<string[]>
Parameters
NameTypeDefaultDescription
cwdstring

Workspace directory.

options?Omit<ResolveWritableRootsOptions, 'gitCommonDir'> & { gitCommonDir?: (cwd: string) => Promise<string | null> }{}

Extra roots and overrides for the environment and platform, with an async git lookup.

Promise<string[]>

src/contexts/isolation.ts:331

Interfaces#

ContextCapabilitiesInterface#

Features an execution context supports. Consumers check these before relying on a feature.

TypeScript
interface ContextCapabilities
ContextCapabilities properties
NameTypeDescription
detachedTasks?DetachedTasksCapability

Background-task lifetime guarantee. Optional for backward compatibility — when absent, callers infer 'process-lifetime' if the context implements execBackground, 'none' otherwise (see resolveDetachedTasksCapability).

filesystemboolean

Can read/write files in a workspace

glob?boolean

Can enumerate glob matches through ExecutionContext.listFiles(..., { glob: true }).

gpuboolean

Has GPU access

isolated?boolean

Shell commands run under an ENFORCED OS-level sandbox (macOS Seatbelt / Linux bwrap) — see SpawnConfig.isolation. false when isolation was not requested AND when it was requested but the backend is unavailable (that context fails every command closed instead). Absent / false = commands do not run sandboxed.

networkboolean

Can make outbound network requests

shellboolean

Can execute shell commands

stream?boolean

Can open a bidirectional byte stream to a long-lived process through ExecutionContext.execStream. Absent / false = the context only supports the buffered ExecutionContext.exec and (maybe) the write-to-a-log-file ExecutionContext.execBackground.

Consumers that NEED live stdin (stdio MCP servers routed into the context) must check this before calling; the seam is optional so contexts backed by a request/response-only API stay valid.

src/contexts/types.ts:35

ContextHardeningInterface#

Opt-in container hardening for the docker context. Every field defaults to "unset" so an omitted hardening (or omitted field) leaves the container unrestricted.

TypeScript
interface ContextHardening
ContextHardening properties
NameTypeDescription
dropAllCapabilities?boolean

Drop all Linux capabilities (CapDrop: ['ALL']). Strong isolation, but breaks images needing capabilities (e.g. binding low ports, ping). Default: false.

noNewPrivileges?boolean

Set no-new-privileges so processes can't gain privileges via setuid binaries. Low blast radius; safe for most workloads. Default: false.

pidsLimit?number

Max number of processes (PidsLimit) — caps fork-bombs. Default: unset (no limit). A few hundred is usually plenty for an agent sandbox.

readonlyRootfs?boolean

Mount the container root filesystem read-only. Pair with writable mounts for scratch space. Default: false.

src/contexts/types.ts:325

ContextMountInterface#

A host directory bind-mounted into an execution context.

TypeScript
interface ContextMount
ContextMount properties
NameTypeDescription
readonly?boolean

Mount read-only. Defaults to false for Docker's native bind behavior.

shared?boolean

Apply the SELinux shared label (:z) so the host user and the container user can both read/write the mount (docker context only). No-op on non-SELinux hosts. Combine with SpawnConfig.user to avoid root-owned files leaking onto the host. Mutually exclusive with readonly.

sourcestring

Absolute host path to mount.

targetstring

Absolute path inside the execution context.

src/contexts/types.ts:350

EntryPortInterface#

The few file operations every entry action is built from (createEntryOps, zidane/chat), paths resolved like the context's other file methods. None of them overwrites: a target is checked missing first, and the operations refuse one that appeared since.

TypeScript
interface EntryPort
EntryPort properties
NameTypeDescription
copy(from: string, to: string) => Promise<void>

Copies from (a folder whole, symlinks as links) to to (missing; its folder exists).

emptyDirs(path: string, limit: number) => Promise<string[]>

Empty folders under folder path (.git and node_modules skipped), relative to it, at most limit.

list(path: string) => Promise<string[]>

The names in folder path.

mkdir(path: string) => Promise<void>

Creates folder path and its missing parents.

move(from: string, to: string) => Promise<void>

Renames from to to (missing; its folder exists).

remove(path: string) => Promise<void>

Removes path (a folder whole).

stat(path: string) => Promise<EntryKind | null>

What path is, null when nothing is there. Never follows a final symlink.

write(path: string, bytes: Uint8Array) => Promise<void>

Writes a new file (its folder exists).

src/contexts/types.ts:122

ExecOptionsInterface#

Options of ExecutionContext.exec.

TypeScript
interface ExecOptions
ExecOptions properties
NameTypeDescription
cwd?string

Folder to run in, resolved against the handle's. Default: the handle's folder.

env?Record<string, string>

Variables added to the command's environment.

onOutput?(stream: 'stdout' | 'stderr', text: string) => void

Output as it arrives, decoded per stream. A chunk can end mid-line.

signal?AbortSignal

Kills the command (its whole process group, in the process context) when aborted.

timeout?number

Seconds. Default 30.

src/contexts/types.ts:374

ExecResultInterface#

Result of a buffered command.

TypeScript
interface ExecResult
ExecResult properties
NameTypeDescription
exitCodenumber

Process exit code. 0 means success.

stderrstring

Captured standard error.

stdoutstring

Captured standard output.

src/contexts/types.ts:104

ExecutionContextInterface#

An environment where an agent runs commands and reads and writes files: the host process, a Docker container or a remote sandbox.

TypeScript
interface ExecutionContext
ExecutionContext properties
NameTypeDescription
capabilitiesreadonlyContextCapabilities

What this context supports

destroy(handle: ExecutionHandle) => Promise<void>

Destroy the execution environment and clean up resources

entries?(handle: ExecutionHandle) => EntryPort

The context's own entry operations (create, move, copy, remove) for handle, with paths resolved like every other file method. Optional: without it, entryPortOf (./entries) runs POSIX commands through exec (mkdir -p, mv, cp -RP, rm -rf), which every shell context can do. Implement it where a native call is cheaper or safer (in-process fs, a machine link).

exec(handle: ExecutionHandle, command: string, options?: ExecOptions) => Promise<ExecResult>

Execute a shell command in the context.

signal propagates abort all the way down to the underlying child process — the implementation is expected to wire it into whatever spawn primitive it uses so the OS receives a SIGTERM (or equivalent) when the caller aborts. Without it, a cancelled tool returns its cancellation marker to the model but the underlying process keeps running in the background, orphaning compute / locks / IO. Pass ctx.signal from a tool body to inherit the per-call + run-level abort union; pass a freshly-built one for a host-driven kill.

Implementations are free to ignore signal (the contract degrades gracefully — the process simply won't be killed), but the process context honors it.

timeout is in SECONDS (default 30) — every implementation converts internally (process ×1000 to ms, docker wraps with coreutils timeout, sandbox SDKs take seconds/ms per their API). Documented here because the unit is invisible at call sites and a caller assuming milliseconds would set an instant-kill timeout.

onOutput hears the output as it arrives, decoded, before the result settles (a UI showing a running command). Optional for implementations: one that cannot stream calls it never, and the result is the same.

execBackground?(handle: ExecutionHandle, command: string, options: { cwd?: string; env?: Record<string, string>; maxOutputBytes?: number; onExit?: (info: TaskExitInfo) => void; onStall?: (info: TaskStallInfo) => void; outputDir: string; stallTimeoutMs?: number; terminal?: BackgroundTerminalOptions }) => Promise<TaskHandle>

Start a process in the background. Settles as soon as spawn returns — does NOT wait for the child to exit. Stdout + stderr stream interleaved to the file at the returned outputPath. The caller (typically the agent) reads incremental output via the regular ExecutionContext.readFile seam.

Optional — contexts without background support (some remote sandboxes) just don't implement it. The shell tool surfaces a clean "background mode is not supported in this execution context" error when this is undefined.

onExit is called once when the child terminates (natural, killed, or error). The same instance that called execBackground is the exclusive owner of the callback — it's not a multi-cast bus. Hosts wire this to the agent's pending-notification queue so the model gets a <task-notification> on its next turn.

onExit is OPTIONAL: remote / durable contexts may have no legal way to push a callback from a timer back into the host (a replay journal, for instance, forbids out-of-band writes). Such contexts record the exit in their registry; the agent loop reconciles by polling ExecutionContext.listBackground at run boundaries, where a host journal wrapper is legal (the background:reconcile hook is the journalable seam).

See docs/RUN_IN_BACKGROUND.md for the broader design contract (file location, replay semantics, suppression rules).

execStream?(handle: ExecutionHandle, command: string, options?: { cwd?: string; env?: Record<string, string>; signal?: AbortSignal }) => Promise<StreamHandle>

Open a LIVE bidirectional byte stream to a long-lived process in the context. The third execution mode, alongside the buffered exec (request/response) and execBackground (fire-and-forget into a log file): neither of those can carry a protocol that interleaves reads and writes over the child's lifetime.

Motivating consumer: stdio MCP servers. An MCP server is an ordinary process doing fs.readFileSync / fs.writeFile against whatever filesystem it was spawned on. Spawned on the host (the default — see McpServerConfig.scope) it sees the HOST filesystem while the agent's own tools see the context's, so the two disagree about what a path means. Routing the server through this seam puts both on the same filesystem.

Optional — a context backed by a request/response-only API can't implement it. Advertise support via capabilities.stream; callers check that (or execStream !== undefined) and degrade with a clear error rather than silently falling back to a host spawn, which would reintroduce exactly the split this seam exists to close.

IMPLEMENTOR CONTRACT — the shape is deliberately the SUPERSET of what the backing APIs can do, because they do not agree:

  • stdout / stderr are byte streams, never pre-decoded strings. A provider SDK that hands back string has already destroyed any multibyte codepoint split across a chunk boundary; the caller needs the raw bytes to run its own streaming decoder.
  • stderr is OPTIONAL because a PTY-backed context physically cannot separate it — one terminal, one stream. Such contexts redirect the child's stderr to a file; they may then surface it here out-of-band (the Daytona context polls that file back), but they are not obliged to, so consumers must tolerate its absence.
  • A PTY-backed context MUST also neutralise terminal semantics before the child's first byte (echo of everything written to stdin, \n→\r\n rewriting, and the ~4096-byte canonical-mode line cap that arbitrary protocol frames exceed). In practice: launch through stty raw -echo; exec <cmd> 2><file>.
  • exited resolves with the exit code. On a PTY the child is an interactive shell that never exits on its own, so kill() is the normal termination path, not an abnormal one.
  • Providers that impose a default command timeout (E2B and Daytona both default to 60s) MUST disable or maximise it here. A stream opened by this method is expected to live for the whole session.
fileSize?(handle: ExecutionHandle, path: string) => Promise<number | null>

Cheap size probe for a file in the context's filesystem, in bytes. null = unknowable (missing file, not a regular file, probe failed, or containment rejected the path — a probe must not leak even the size of files outside the workspace root).

Exists because readFile has no partial-read form: tools that must materialize a whole file (read_file's text path, edit/multi_edit, grep's no-rg fallback) consult this first to refuse multi-GB targets instead of OOMing the HOST — the read lands in host memory no matter where the file lives. Per-context posture:

  • process — implements it (local stat, µs).
  • docker — implements it via wc -c through its exec; its readFile gets transport headroom above the tool ceiling so the probe (tool policy), not the 10MB exec cap, decides read limits — same experience as every other context.
  • sandbox (and the e2b / daytona providers behind it) — implements it via wc -c through the provider's exec: one extra round-trip per whole-read tool call, paid so a multi-GB sandbox file is refused BEFORE the SDK transfers it into host memory.

KNOWN RESIDUAL (sandbox providers): this guards the readFile path only. Their exec output is buffered inside the provider SDKs (e2b commands.run, daytona executeCommand), which we cannot cap from this side — a command that streams GBs to stdout within its timeout window can still balloon host memory. Docker doesn't share the residual (we own its transport; see output-cap.ts). A shell wrapper (| head -c) was considered and rejected: preserving exit codes, stderr separation, and timeout semantics through a pipe for EVERY command is fragile, and the timeout already time-bounds the exposure.

getMappedPort?(handle: ExecutionHandle, containerPort: number) => Promise<number | null>

Resolve the host-side port that a container port was published on (docker context only, and only when the matching entry was created with host omitted in SpawnConfig.ports).

Resolves to null if the container port isn't published. Other contexts (process, sandbox) don't implement this; they don't have a port-mapping concept.

killBackground?(handle: ExecutionHandle, taskId: string) => Promise<TaskExitInfo | null>

SIGTERM the whole process group of a running background task. Idempotent — second call returns null (or the cached exit info). Resolves once the process has exited AND its output stream has been flushed + closed.

null return on miss (unknown id, already cleaned up) so the shell_kill tool can surface a clean "no such task" message without throwing.

listBackground?(handle: ExecutionHandle) => Promise<readonly TaskEntry[]>

Snapshot of every task in the context's registry — running AND terminated (entries remain until the next context destroy, so the model can still read output of exited tasks).

listFiles(handle: ExecutionHandle, path: string, options?: ListFilesOptions) => Promise<ListFilesEntry[]>

List files in the context filesystem.

Plain paths list immediate directory entries. Paths that contain glob metacharacters (*, ?, [], {}) request matched file enumeration inside the target context. Contexts that cannot support globbing should throw a clear error rather than silently returning no matches. metadata is best-effort and only required when the context can provide it without expensive per-file shell calls.

openTerminal?(handle: ExecutionHandle, options: TerminalOptions) => Promise<TerminalProcess>

Open an interactive terminal (a PTY running the user's login shell) in the context: on the host, in the container, in the sandbox. For people, not protocols: the opposite of execStream, which defeats every terminal semantic this one keeps (echo, line editing, job control, window size).

Optional: a context without a PTY primitive (or a runtime without one: the local contexts need Bun) leaves it undefined, and callers offer no terminal rather than falling back to a host shell.

readFile(handle: ExecutionHandle, path: string) => Promise<string>

Read a file from the context's filesystem

readFileBinary?(handle: ExecutionHandle, path: string) => Promise<Uint8Array<ArrayBufferLike>>

Read a file from the context's filesystem as raw bytes.

Used by read_file to dispatch image / binary files into the multimodal ToolResultContent[] route. Optional — when not implemented, the tool falls back to base64 < path via the exec seam, which works in any shell-capable context. Implementations that already have a native binary read (in-process fs.readFile without encoding, container API, sandbox SDK) should override for the latency win.

reassignBackgroundTasks?(fromHandle: ExecutionHandle, toHandle: ExecutionHandle, newOnExit?: (info: TaskExitInfo) => void, newOnStall?: (info: TaskStallInfo) => void) => Promise<readonly TaskEntry[]>

Transfer ownership of every still-running task from fromHandle to toHandle. Used by the spawn tool to "promote" a subagent's background tasks up to the parent's handle so they outlive the subagent's destroy() — matching shell semantics, where a &-ed command outlives the parent process.

With fromHandle === toHandle ownership stays put and only the callbacks are rebound: a host that keeps one handle across agent rebuilds (chat/execution-cache.ts) hands the running tasks to the freshly built agent this way.

Side effects:

  • The task's handleId is rewritten, so subsequent listBackground(toHandle) / killBackground(toHandle, …) / destroy(toHandle) see it (and fromHandle-scoped operations don't).
  • When newOnExit / newOnStall are provided, the task's OWNER callbacks are REPLACED with them. Critical: they were captured against the spawning agent's hook bus, which is about to be destroyed; without rewiring, the new owner never learns when the task terminates or stalls. Pass closures that fire the new owner's background:exit / background:stall hooks. In-flight killBackground / waitBackground resolvers are not owner callbacks and keep firing.

Returns the entries that were actually reassigned (running tasks only; terminated ones stay where they are). Implementations that don't support reassignment can leave this undefined; the caller (spawn.ts) falls back to the current behavior of killing child tasks at subagent shutdown.

reconnectTerminal?(handle: ExecutionHandle, id: string, options: TerminalOptions) => Promise<TerminalProcess | null>

Re-attach to a terminal opened earlier, by its TerminalProcess.id, after the host lost the connection (daemon restart, paused sandbox). Resolves null when it is gone. Only contexts whose terminals outlive the host (remote sandboxes) implement it.

spawn(config?: SpawnConfig) => Promise<ExecutionHandle>

Spawn a new execution environment

typereadonlyContextType

Context type identifier

waitBackground?(handle: ExecutionHandle, taskId: string, options?: { signal?: AbortSignal; timeoutMs?: number }) => Promise<TaskExitInfo | null>

Block until a background task terminates, then resolve with its exit info. Resolves immediately for already-terminated tasks.

Returns null when:

  • the task id is unknown (or owned by another handle — same isolation contract as killBackground), OR
  • timeoutMs elapsed / signal aborted before the task exited.

Callers that need to distinguish "unknown" from "still running" should consult listBackground first (the wait_task tool does).

This is the injectable wait seam for the wait_task tool: in-process contexts implement it on the existing onExit machinery; durable hosts implement it as an awakeable park (runner fires task-exit over their bridge → resolveAwakeable).

writeFile(handle: ExecutionHandle, path: string, content: string | Buffer<ArrayBufferLike>, encoding?: BufferEncoding) => Promise<void>

Write a file to the context's filesystem.

encoding applies only when content is a string (how its bytes are produced; defaults to utf-8) and is ignored for a Buffer, which is written verbatim.

src/contexts/types.ts:388

ExecutionHandleInterface#

Opaque handle to a running execution context instance

TypeScript
interface ExecutionHandle
ExecutionHandle properties
NameTypeDescription
cwdstring

Working directory within the context

idstring

Identifier of the spawned instance, unique within its context.

typeContextType

Type of the context that created the handle.

src/contexts/types.ts:90

IsolationPolicyInterface#

Policy for an OS-level sandbox around shell commands.

TypeScript
interface IsolationPolicy
IsolationPolicy properties
NameTypeDescription
network'allow' | 'deny'

'allow' = unrestricted network (the default: bun install, git fetch, gh keep working). 'deny' = no network access. Without network isolation a compromised command can still exfiltrate what it can read — the setting description says so.

unreadableDirs?string[]

Directories hidden from the sandboxed command (read AND write denied). Defaults to DEFAULT_UNREADABLE_DIRS when omitted.

writableRootsstring[]

Directories the sandboxed command may write to (create, modify, delete). Absolute paths; callers should pass them through resolveWritableRoots so symlinked locations (/tmp → /private/tmp on macOS) match what the kernel sees.

src/contexts/isolation.ts:37

ListFileMetadataInterface#

A listed file with optional metadata.

TypeScript
interface ListFileMetadata
ListFileMetadata properties
NameTypeDescription
mtimeMs?number

Last modification time, in milliseconds since the epoch.

pathstring

Path of the file.

size?number

Size in bytes.

src/contexts/types.ts:142

ListFilesOptionsInterface#

Options for ExecutionContext.listFiles.

TypeScript
interface ListFilesOptions
ListFilesOptions properties
NameTypeDescription
gitignore?boolean

Skip git-ignored files when globbing. Default true. Contexts that can resolve git's ignore rules (e.g. the local process context) honor it; the rest treat it as a no-op. Set false to match every file, ignored or not.

glob?boolean

Treat path as a glob match pattern instead of a literal directory path.

limit?number

Maximum number of entries to return.

metadata?boolean

Return ListFileMetadata objects instead of plain paths.

signal?AbortSignal

Abort the listing early — forwarded from the calling tool's ctx.signal.

src/contexts/types.ts:155

NodeEntryPortOptionsInterface#

Options of nodeEntryPort.

TypeScript
interface NodeEntryPortOptions
NodeEntryPortOptions properties
NameTypeDescription
entry?(path: string) => Promise<string>

Same, for the entry itself, its own link never followed (stat, move, remove, a target to create). Default: resolve.

resolve(path: string) => Promise<string>

A path as the caller names it → the absolute path to act on; throws when it is out of bounds.

trash?(absolute: string) => Promise<void>

Removes absolute recoverably (the system trash). Default: removed for good.

writable?(absolute: string, path: string) => Promise<void>

Throws when the caller may not change absolute (a sandbox's writable roots). Default: anything goes.

src/contexts/entries.ts:26

OwnedProcessInterface#

One process the app started for itself.

TypeScript
interface OwnedProcess
OwnedProcess properties
NameTypeDescription
pidnumber

The child's pid.

roleOwnedProcessRole

What the app started it for.

src/contexts/owned-processes.ts:17

SessionProcessInterface#

One process a session started.

TypeScript
interface SessionProcess
SessionProcess properties
NameTypeDescription
pidnumber

The child's pid.

sessionIdstring

The session it runs for, by its id on this host.

src/contexts/session-processes.ts:15

SpawnConfigInterface#

Configuration for ExecutionContext.spawn. Not every context honors every field.

TypeScript
interface SpawnConfig
SpawnConfig properties
NameTypeDescription
cwd?string

Working directory (created if it doesn't exist)

destroyGraceMs?number

How long destroy() waits for a SIGTERM'd background task to settle before escalating to SIGKILL and abandoning the wait (process context only). Bounds agent.destroy() against tasks that trap SIGTERM or whose stdio pipes are held open by escaped grandchildren.

Default: 5000.

env?Record<string, string>

Environment variables

hardening?ContextHardening

Container hardening options (docker context only).

All fields are opt-in and OFF by default to preserve existing behavior (containers run with the image's default user/capabilities). Enable them for untrusted workloads. dropAllCapabilities and a non-root user (see SpawnConfig.user) can break images that expect root or specific capabilities, so they are not applied unless requested.

image?string

Docker image (only for 'docker' context)

inheritEnv?boolean

Whether spawned shells inherit the parent process.env (process context).

Default true (preserves existing behavior — children see the parent environment including anything Bun auto-loaded from .env). Set false for untrusted workloads so secrets in the parent environment aren't readable by tool commands; only env / per-call env plus a minimal base (PATH, HOME, SHELL, LANG, LC_ALL, USER, TERM, TMPDIR) are passed through.

isolation?IsolationPolicy

OS-level sandboxing of shell commands (process context only). When set, every exec / execStream / execBackground runs /bin/sh -c under macOS sandbox-exec or Linux bwrap: filesystem writes are limited to writableRoots, credential directories are hidden, and network is allowed or denyed as a whole. writeFile applies the same writableRoots check in-process (reads are unrestricted — this is the Claude Code / Codex boundary, not a full jail; use workspaceRoot for read containment).

Fails closed: when the backend is unavailable on the host, every command returns exit 127 with the reason instead of running unsandboxed.

Build the policy with resolveWritableRoots from contexts/isolation.

labels?Record<string, string>

Docker labels to attach to the container (docker context only).

Useful for ownership tracking — callers can sweep abandoned containers (e.g. from a crashed parent process) by filtering on a label they own: docker ps -aq --filter label=my-app=true.

limits?{ cpu?: string; memory?: number; timeout?: number }

Resource limits

mounts?ContextMount[]

Host paths mounted into the context (only for 'docker' context today)

name?string

Docker container name prefix (only for 'docker' context)

network?string

User-defined Docker network to join (docker context only).

Defaults to Docker's default bridge. Use a user network when you need multiple sibling containers (e.g. agent + database + dev server) to discover each other by name.

ports?{ container: number; host?: number; proto?: 'tcp' | 'udp' }[]

Publish container ports on the host (docker context only).

Each entry maps a container port to either an explicit host port or (when host is omitted) a Docker-assigned random port. Retrieve the actual host port at runtime via getMappedPort(handle, containerPort) on the docker context.

sandbox?{ [key: string]: unknown; apiKey?: string; provider: string; sandboxId?: string }

Sandbox provider config (only for 'sandbox' context)

sessionId?string

The session this context runs commands for (process context only): each command it starts (exec, execStream, execBackground) is recorded in the session-process registry (listSessionProcesses) while it runs.

user?string

UID/GID the container should run as (docker context only).

Accepts the same forms Docker's --user does: uid, uid:gid, or a named user that exists in the image. Default is the image's default user (typically root). Setting this to the host user's uid:gid avoids the EACCES-on-cleanup problem when sharing a workspace via a shared mount.

workspaceRoot?string

Optional workspace-containment root for the file methods (readFile, writeFile, listFiles) of the process context.

When set, a resolved path that escapes this root (via absolute path, .., or a symlink pointing outside) is rejected before any I/O. Default unset = no containment (back-compat; the agent can read/write anywhere the host process can). Set this for untrusted workloads to confine file tools to a directory. Shell commands are not covered — use a sandbox / docker context for full isolation.

src/contexts/types.ts:177

StreamHandleInterface#

A live bidirectional stream to a process in the context. Returned by ExecutionContext.execStream; see that method for the full implementor contract (why the streams are bytes, why stderr is optional, what PTY-backed contexts must do before the first byte).

Ownership: the caller owns the lifetime. Nothing reaps this handle automatically, so kill() belongs in the consumer's teardown path — for MCP that is the transport's close(), reached via the agent's mcpConnection.close() during destroy().

TypeScript
interface StreamHandle
StreamHandle properties
NameTypeDescription
exitedPromise<number>

Resolves with the exit code when the child terminates, including termination via kill. Never rejects.

kill() => Promise<void>

Terminate the child. Idempotent — killing a dead child is a no-op.

pidnumber

Process id inside the context — NOT meaningful on the host.

stderr?AsyncIterable<Uint8Array<ArrayBufferLike>, any, any>

Child's stderr, when the implementation can provide it separately. Present on pipe-backed contexts. A PTY-backed context cannot split it from the terminal stream, so it redirects stderr to a file and either surfaces it here out-of-band or leaves this unset — expect a lower latency guarantee there than on stdout.

stdoutAsyncIterable<Uint8Array<ArrayBufferLike>>

Child's stdout as raw byte chunks, in emit order.

CONSUMER OBLIGATION: drain this (and stderr) for the stream's lifetime. Nothing here is bounded, and the consequence of ignoring it varies by backend and by host runtime, none of it good:

  • process / docker hand back real OS pipes. Under Node an unread stream stops draining the fd and the child BLOCKS once the pipe fills (~64 KiB) — a deadlock, not a slowdown. Under Bun the runtime buffers eagerly instead, so it presents as unbounded host memory (measured: 32 MiB of unread stderr never blocked the child).
  • e2b / daytona buffer in a StreamQueue, which grows without limit. It cannot do otherwise: both SDKs push output through a synchronous (data) => void callback, so there is no channel to apply backpressure, and dropping bytes would desynchronise a framed protocol permanently. The queue logs a one-shot warning once the unread backlog passes 8 MiB so a stalled consumer is diagnosable.

createContextStdioTransport satisfies this for both streams.

write(data: Uint8Array) => Promise<void>

Write to the child's stdin. Resolves once the bytes are handed off.

src/contexts/types.ts:857

TaskEntryInterface#

One row in listBackground's snapshot. Living entries (status 'running') have exitCode / signal / endedAt unset; terminated entries carry the same data TaskExitInfo returned at exit time.

TypeScript
interface TaskEntry
TaskEntry properties
NameTypeDescription
bytesWrittennumber

Total bytes written to the output file so far — useful for "task X has produced N KB" UX hints.

commandstring

Command the task runs.

cwdstring

Working directory of the task.

endedAt?number

Set when the task terminated. Lets pull-based consumers (the agent's run-boundary reconcile) derive durationMs = endedAt - startedAt without depending on when the snapshot was taken.

exitCode?number

Exit code. Set once the task terminated.

outputPathstring

Absolute path of the log file holding the task output.

pidnumber

Process id.

signal?Signals

Signal that terminated the process, when there was one.

startedAtnumber

Start time, in milliseconds since the epoch.

statusBackgroundTaskStatus

Current state of the task.

taskIdstring

Id of the task, as in TaskHandle.taskId.

src/contexts/types.ts:933

TaskExitInfoInterface#

Fired exactly once per task when the child process terminates, via execBackground's onExit callback. The agent layer translates this into a queued <task-notification> for the next turn.

signal is set when the child was terminated by a signal (e.g. SIGTERM from our own kill-tree, SIGKILL from oom-killer); absent on natural exit. exitCode is 128 + signal-number on signal-killed children, matching POSIX shell conventions — 143 for SIGTERM, etc.

TypeScript
interface TaskExitInfo
TaskExitInfo properties
NameTypeDescription
commandstring

The original command string the model invoked — useful for telemetry / banner summary.

durationMsnumber

Date.now() delta between spawn and exit.

exitCodenumber

Exit code of the process.

outputPathstring

Absolute path of the log file holding the task output.

signal?Signals

Signal that terminated the process, when there was one.

status'exited' | 'killed'

How the task ended.

taskIdstring

Id of the task, as in TaskHandle.taskId.

src/contexts/types.ts:911

TaskHandleInterface#

Returned by execBackground once the process has spawned — the handle the model and the framework use to refer to the task until it terminates.

outputPath is an absolute path to the log file the context is appending stdout + stderr to (interleaved by emit order). The model reads it via the normal read_file tool; no special tool is required.

TypeScript
interface TaskHandle
TaskHandle properties
NameTypeDescription
outputPathstring

Absolute path to the log file the context is streaming output into.

pidnumber

OS pid of the spawned shell wrapper (process-group leader on POSIX).

taskIdstring

Stable id minted by the context — typically bash_<n> for ProcessContext. Sequential within a single context instance, resets when a new context is constructed. Forwarded to killBackground / listBackground and stamped into every <task-notification> block.

terminal?boolean

True when the task runs in a terminal (execBackground's terminal option was honored).

src/contexts/types.ts:830

TaskStallInfoInterface#

Fired by the optional stall watchdog (see execBackground's stallTimeoutMs) when a running task has produced no output for the configured window. The process is still alive — this is a signal, not a state transition.

TypeScript
interface TaskStallInfo
TaskStallInfo properties
NameTypeDescription
bytesWrittennumber

Total bytes the task has written so far.

commandstring

Command the task runs.

outputPathstring

Absolute path of the log file holding the task output.

stalledForMsnumber

Milliseconds since the last output chunk (>= the configured window).

taskIdstring

Id of the stalled task.

src/contexts/types.ts:968

Types#

BackgroundTaskStatusType#

Lifecycle status of a background task.

  • 'running' — process is still live; exitCode / signal unset.
  • 'exited' — process terminated on its own (clean or non-zero exit).
  • 'killed' — the host issued killBackground (SIGTERM to the group).

The status is a coarse-grained signal; the exit code carries the fine-grained detail (e.g. 143 = SIGTERM for 'killed').

TypeScript
type BackgroundTaskStatus = 'running' | 'exited' | 'killed'

src/contexts/types.ts:820

ContextTypeType#

Kind of execution context: process, docker or sandbox.

TypeScript
type ContextType = 'process' | 'docker' | 'sandbox'

src/contexts/types.ts:371

DetachedTasksCapabilityType#

Lifetime guarantee of background tasks started through ExecutionContext.execBackground.

  • 'none' — the context cannot detach tasks at all.
  • 'process-lifetime' — tasks live as long as the HOST process; a crash/restart of the host orphans or kills them (ProcessContext).
  • 'durable' — tasks live on a remote runner and survive host process death (remote execution contexts driven by durable runtimes).

Durable-execution runtimes consult this to decide whether backgrounding is safe to expose (disableBackgroundTasks: 'non-durable', the default in replay mode): a 'durable' context keeps its tasks across worker crashes, so there is no reason to strip the capability from the model.

TypeScript
type DetachedTasksCapability = 'none' | 'process-lifetime' | 'durable'

src/contexts/types.ts:32

EntryKindType#

What a path is: a file (a symlink counts as one, never followed) or a folder.

TypeScript
type EntryKind = 'file' | 'dir'

src/contexts/types.ts:114

IsolationBackendType#

Sandbox backend: macOS Seatbelt or Linux bubblewrap.

TypeScript
type IsolationBackend = 'seatbelt' | 'bwrap'

src/contexts/isolation.ts:60

ListFilesEntryType#

One listing entry: a plain path, or a ListFileMetadata object when metadata was requested.

TypeScript
type ListFilesEntry = string | ListFileMetadata

src/contexts/types.ts:152

OwnedProcessRoleType#

Why the app started the process.

TypeScript
type OwnedProcessRole = 'mcp' | 'tool'

src/contexts/owned-processes.ts:14

Re-exports#

These names are documented on the entry point that owns them.