Skip to content

What "code runtime" means here

packages/code-runtime/code-runtime defines ctx.codeRuntime — the seam that runs one model-written program against host-provided async bindings. The defining contract (from the class doc): "Runtimes know nothing about tools or sessions; consumers own those concerns." A consumer (the tools registry, for "Code Mode") hands in a program source plus a set of binding namespaces, and the runtime executes it as a hostile peer — reporting program failures as result fields, never exceptions.

The seam is deliberately portable-identifier driven: a program written for one backend must be valid on every backend. Only 'typescript' has a published backend today, but 'python' is a first-class portability target, so identifier validation is the union of both languages' reserved words.

Package versions

PackageRole
@deepseek-ai/dsh-code-runtimeService Definition (CodeRuntime, ctx.codeRuntime)
@deepseek-ai/dsh-code-runtime-worker-threadProvider: fresh worker thread per run (TypeScript)
@deepseek-ai/dsh-e2bCore: shared E2B sandbox owner (ctx.e2b)
@deepseek-ai/dsh-fs-e2bProvider: E2B filesystem backend for ctx.fs
@deepseek-ai/dsh-subprocess-e2bProvider: E2B process backend for ctx.subprocess
@deepseek-ai/dsh-subprocessService Definition (SubprocessRuntime, ctx.subprocess)
@deepseek-ai/dsh-subprocess-localProvider: local OS-process spawn

The seam definition

CodeRuntime (packages/code-runtime/code-runtime/src/index.ts):

ts
export abstract class CodeRuntime extends Service {
  abstract readonly language: string   // 'typescript' | 'python' (informational)
  abstract readonly isolation: string  // 'worker-thread' | 'process' | 'container' (informational)
  constructor(ctx: Context) { super(ctx, 'codeRuntime') }
  abstract run(request: CodeRunRequest): Promise<CodeRunResult>
}

run resolves a CodeRunResult — an error is a field on a resolved result, never a rejection. Only Service Definition contract misuse rejects. The portable-identifier contract is enforced through three shared sets:

  • RESERVED_BINDING_GLOBALS{ console, __dsh_main__, __builtins__, __name__, __debug__ }: slots some backend owns; one shared set keeps a namespace list that is valid on one backend valid on all.
  • RESERVED_ERROR_MEMBERS{ name, message, stack, args, with_traceback, add_note }, plus any dunder form (__x__) refused wholesale.
  • PORTABLE_RESERVED_WORDS — the union of ECMAScript reserved words and Python 3 keywords/soft keywords (lambda, match, type, _, …). Adding a new language means widening this union (a breaking review of existing binding names, by design).

The request/result shapes

CodeRunRequest carries everything the runtime acts on (explicit-over-implicit — no hidden ?? tuning knobs):

ts
export interface CodeRunRequest {
  program: string                     // runs as the body of an async function: top-level return/await legal
  bindings: CodeBindingNamespace[]    // host functions, one global object per namespace
  signal?: AbortSignal                // abort stops the program hard, even mid-loop
}

A CodeBindingNamespace is { global, functions, errorClass? }: global must match the portable identifier subset [A-Za-z_][A-Za-z0-9_]* (a JS-only spelling like $tools is rejected by design); functions is a Record<string, CodeBindingFunction> mapping callable member names to functions (args: unknown) => Promise<CodeJsonValue>, where CodeJsonValue is a lossless JSON type; errorClass names a typed rejection constructor the runtime injects under name with a memberNameProperty.

CodeRunResult = optional value (the top-level return, if it crossed the lossless-JSON boundary), ordered logs: string[], and optional error. One of three taxonomy kinds:

ts
export type CodeRunFailureKind =
  | 'exception'      // program threw or failed to parse/transform
  | 'timeout'        // an implementation budget expired
  | 'abort'          // the request signal fired
  | 'worker-exit'    // the substrate died without settling (e.g. OOM)
  | 'invalid-output' // completion value was not lossless JSON
  | 'output-limit'   // serialized logs/value/diagnostic exceeded the cap

The worker-thread provider

@deepseek-ai/dsh-code-runtime-worker-thread (packages/code-runtime/code-runtime-worker-thread/src/index.ts) is the only published backend. Its own doc is explicit about the trust model: "This is containment, not a security boundary: model code has bash-equivalent trust." It runs a fresh Worker per run, wrapping the program in async function __dsh_program__() { … } and stripping TypeScript types with the position-preserving native type-strip, bridging bindings over the worker's message port.

Config (every execution cap is declared in the provider's Config schema — changeable from cordis.yml):

KeyDefaultMeaning
computeMsbusy-time budget: fails kind:'timeout' when the worker's measured event-loop active time (eventLoopUtilization) exceeds it. Metering measured busy time (not wall time) makes the budget fair (a program awaiting a slow tool accrues nothing) and ungameable.
maxWallMswall-clock ceiling (backstop for promises nobody resolves), at most 2_147_483_647 (Node's max setTimeout delay).
maxOutputByteshard cap for serialized log-array / value / failure-message payloads.
maxOldGenerationSizeMbworker max old-gen heap in MiB (resourceLimits); overflow kills the worker → kind:'worker-exit'.

Runtime mechanics worth noting:

  • Fresh worker per run isolates runs from one another, and termination also stops synchronous loops (worker.terminate()).
  • The worker entry runs unbuilt from src/worker.ts (erasable-only with type-only imports); the built package ships it as a sibling CommonJS bundle (lib/worker.cjs).
  • Inbound port traffic is re-validated and rebuilt field by field. The doc is blunt: the peer runs MODEL CODE and can post anything — so the compile-time WorkerToHost type means nothing and a forged extra field never rides along.
  • Bindings cross the port via snapshotJsonValue; resolution values must be lossless JSON or the run fails invalid-output.
  • RESERVED_BINDING_GLOBALS/PORTABLE_RESERVED_WORDS are enforced at request time so a namespace list valid on this backend is valid on any future backend.

The worker→host wire protocol

packages/code-runtime/code-runtime-worker-thread/src/protocol.ts is a versionless, structured-clone protocol between co-shipped host and worker code. The direction of trust is asymmetric and stated explicitly: the host treats inbound traffic as hostile (model code can forge parentPort messages), so it re-validates and rebuilds every message; the worker trusts host replies. The host hands the worker WorkerBootData at spawn via workerData — the type-stripped program code, the binding namespaces to materialize (function bodies stay host-side), and the combined maxOutputBytes cap.

The message union (WorkerToHost):

MessagePayloadMeaning
call{ id, global, name, args }one bridged binding call; the host answers each id at most once and ignores duplicates
log{ text }captured text, streamed eagerly so output survives a mid-run termination (timeout, abort, OOM)
output-limitworker-side capture or completion measurement exceeded the outer cap
done{ value? / error? }program settled; logs are not carried (they streamed already)

The host answers a call with a ReplyMessage (ok: true + lossless value, or ok: false + message). Because budgets, aborts, and substrate death are observed host-side, done carries only program-exception / invalid-output / output-limit errors; timeout, abort, and worker-exit are classified by the host around the settled worker.

E2B: the remote sandboxing backend

packages/e2b/e2b owns one shared E2B sandbox (ctx.e2b) that both fundamental E2B providers share, so filesystem and process operations inhabit the same remote Linux runtime. Config:

KeyDefaultMeaning
apiKey$E2B_API_KEYE2B API key; never forwarded into the sandbox
cwd/home/user/workspaceshared remote working directory, created before adapters receive the sandbox
timeoutMs300_000sandbox lifetime; expiry always deletes the sandbox

E2BRuntime exposes getSandbox() — creation begins at plugin construction; adapters await the same handle. It quotes args for the SDK's hard-coded /bin/bash -l -c layer (quoteE2BShellArg) and isolates the login shell behind a fresh randomized HOME (e2bControlEnvs). On disposal or timeout the sandbox is deleted.

The two E2B-backed providers are the same file-substrate pair under the E2B world:

  • fs-e2b (packages/e2b/fs-e2b) — implements FileSystem over the remote sandbox filesystem, so ctx.fs reads/writes become remote-Linux file operations and processPath returns a remote in-sandbox path.
  • subprocess-e2b (packages/e2b/subprocess-e2b) — implements SubprocessRuntime over Sandbox.command/PTY, mapping the CommandHandle/CommandResult vocabulary (output.ts, process.ts, environment.ts, remote.ts, terminal.ts).

Composing fs-e2b + subprocess-e2b gives one coherent remote execution world: a shell command's workdir path and a ctx.fs writeText target resolve against the same in-sandbox filesystem.

The subprocess service

packages/subprocess/subprocess defines ctx.subprocess — the seam the shell executors, the PTY backend, the LSP host, and the out-of-process subagent transports all spawn through. It is the substrate bash-local/bash-sandbox/terminal-bash build on (their env layering, output caps, and kill escalation all come from here).

SubprocessRuntime semantics (from packages/subprocess/subprocess/src/index.ts):

  • One execution world shared with the mounted filesystem provider — executable paths and file paths agree.
  • spawn returns immediately with a SubprocessHandle; done resolves at process close with exit facts and rejects only for spawn-level failures.
  • Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream.
  • terminate (and the spec's abort signal) escalates SIGTERM → grace → SIGKILL, tree-scoped on every platform — the only termination verb.
  • Piped stdio is handed to the caller raw, never buffered here.
  • Disposal terminates all still-running managed processes and awaits their exit.

Environment hygiene: scrubbedParentEnv() is the canonical base every harness child starts from — it drops credential-shaped names (SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i) and all ambient DSH_* names (matching DSH_ENV_PREFIX), so PATH/HOME/locale/proxies survive but harness identity never leaks implicitly. A deliberately supplied entry survives because explicit env layers merge after the scrub.

subprocess-local

@deepseek-ai/dsh-subprocess-local is the local OS-process provider (spawn.ts, terminal.ts, process-inspector.ts). Process coordinates (process-inspector) let it deliver tree-scoped PID-group termination. bash-local's LocalBashExecutor injects subprocess and hands it a fully-specified SubprocessSpawnSpec (argv, cwd, per-stream collect budgets, graceMs, signal, layered env).

Relationship to the sandbox

  • subprocess-local spawns unconfined OS processes; the bash layer decides policy. bash-local is bare, while bash-sandbox wraps the same argv through ctx.sandbox (see Shell & Terminal and Sandbox Architecture).
  • fs-sandbox fences ctx.fs mutations by the shared sandbox mode (see Filesystem and Filesystem Observation & Sandbox Policy).
  • E2B is itself a sandbox boundary — a remote, disposable Linux VM/container — so subprocess-e2b/fs-e2b need no local OS sandbox wrapper; the substrate is the containment. ctx.e2b is listed as core (not a seam) in docs/capability-seams.md.
  • code-runtime-worker-thread is containment, not a security boundary — the worker isolates, but the code carries bash-equivalent trust; prefer a sandboxed/E2B composition for truly untrusted programs.

Further reading

  • Shell & Terminalbash-local/bash-sandbox as ctx.subprocess consumers.
  • Filesystem Tools & Policiesfs-e2b as the remote ctx.fs backend.
  • Sandbox Architecture — the mode/enforcement story E2B participates in.
  • Permissions & Approval — the escalation flow shared by the executors.
  • packages/code-runtime/code-runtime/src/index.ts — the CodeRuntime definition and portability contract.
  • packages/subprocess/subprocess/src/index.ts — the SubprocessRuntime definition and scrub rules.