Ver Fonte

Merge pull request #205 from deepseek-harness/code-runtime-pkg

feat: add the code-execution capability seam (ctx.codeRuntime)
Tianyi Cui há 2 meses atrás
pai
commit
dabc5e6225

+ 1 - 0
docs/architecture.md

@@ -26,6 +26,7 @@ The default distribution is a composition, not a hierarchy. `packages/core/` is
 |---|---|---|
 | `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls |
 | `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution |
+| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | model-written program execution |
 | `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives and policy events |
 | `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries |
 | `ctx.compact` | [`compact/`](../packages/compact/README.md) | session-surface compaction |

+ 4 - 0
docs/capability-seams.md

@@ -42,6 +42,8 @@ flowchart LR
   pkg_bash_local["bash-local"]
   pkg_hooks_claude["hooks-claude"]
   pkg_hooks_codex["hooks-codex"]
+  pkg_code_runtime["code-runtime"]
+  svc_codeRuntime["ctx.codeRuntime<br/>Code-execution seam"]
   pkg_fs["fs"]
   svc_fs["ctx.fs<br/>Filesystem provider seam"]
   pkg_fs_local["fs-local"]
@@ -64,6 +66,7 @@ flowchart LR
   pkg_agent_loop --> svc_agentLoop
   pkg_bash --> svc_bash
   pkg_bash_local --> svc_bash
+  pkg_code_runtime --> svc_codeRuntime
   pkg_compact --> svc_compact
   pkg_compact_basic --> svc_compact
   pkg_fs --> svc_fs
@@ -134,6 +137,7 @@ flowchart LR
 | `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`stdio-agent`](../packages/ui/stdio-agent), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles and the create/resume factory seam. |
 | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-core`](../packages/core/agent-core) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
 | `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local. |
+| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | - | - | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer). |
 | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-policy contributes observed-state checks through the fs/* event gate. |
 | `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. |
 | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-mock`](../packages/support/subagent-mock) | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. |

+ 1 - 0
docs/config-catalog.md

@@ -802,6 +802,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
 Abstract service classes — a deployment loads a concrete implementation package instead ([capability seams](rfc/implemented/architecture/2026-06-13-capability-seams.md)).
 
 - `@deepseek-ai/dsh-bash` — abstract `BashExecutor` ([`packages/bash/bash/src/index.ts`](../packages/bash/bash/src/index.ts))
+- `@deepseek-ai/dsh-code-runtime` — abstract `CodeRuntime` ([`packages/code-runtime/code-runtime/src/index.ts`](../packages/code-runtime/code-runtime/src/index.ts))
 - `@deepseek-ai/dsh-compact` — abstract `CompactService` ([`packages/compact/compact/src/index.ts`](../packages/compact/compact/src/index.ts))
 - `@deepseek-ai/dsh-fs` — abstract `FileSystem` ([`packages/fs/fs/src/index.ts`](../packages/fs/fs/src/index.ts))
 - `@deepseek-ai/dsh-session-persistence` — abstract `SessionPersistence` ([`packages/session-persistence/session-persistence/src/index.ts`](../packages/session-persistence/session-persistence/src/index.ts))

+ 19 - 0
docs/cordis-catalog/services.md

@@ -67,6 +67,25 @@ Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../c
 
 Source: [`packages/bash/bash/src/index.ts:59`](../../packages/bash/bash/src/index.ts)
 
+## `ctx.codeRuntime` — `CodeRuntime` (abstract seam)
+
+Abstract code-execution service. Subclass, implement run and the two descriptors, and load the subclass as a plugin — it registers as `ctx.codeRuntime` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior).
+
+Semantics every implementation must honor:
+
+- run resolves with an error FIELD for every program outcome — parse/transform failures, thrown exceptions, budget expiry, abort, substrate death (CodeRunFailure's taxonomy). It REJECTS only for caller misuse of the seam itself (e.g. a run submitted after disposal).
+- Binding calls bridge to the caller's CodeBindingFunctions verbatim; arguments and resolutions must be structured-cloneable, and the runtime treats the program as a hostile peer (arbitrary binding names are own properties, malformed traffic is rejected or ignored, never crashes the host).
+- Runs are isolated from each other: no state survives from one run to the next through the runtime.
+- Disposal reaches quiescence: in-flight runs are terminated AND awaited before the service's own teardown completes (no orphan substrate survives `fiber.dispose()`).
+
+```ts cordis-catalog
+abstract run(request: CodeRunRequest): Promise<CodeRunResult>
+```
+
+Types: [CodeRunRequest](../core-data-structures/code-runtime.md) · [CodeRunResult](../core-data-structures/code-runtime.md)
+
+Source: [`packages/code-runtime/code-runtime/src/index.ts:59`](../../packages/code-runtime/code-runtime/src/index.ts)
+
 ## `ctx.compact` — `CompactService` (abstract seam)
 
 Abstract compaction service. Subclass implement the two abstract methods, and load the subclass as a plugin — it registers as `ctx.compact` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior).

+ 94 - 0
docs/core-data-structures/code-runtime.md

@@ -0,0 +1,94 @@
+# Code Runtime
+
+The code-execution seam — a [capability seam](../rfc/implemented/architecture/2026-06-13-capability-seams.md) whose interface ([dsh-code-runtime](../../packages/code-runtime/code-runtime), `ctx.codeRuntime`) runs one model-written program against host-provided async bindings and reports what it printed and returned. Code execution is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). Backends differ by execution substrate and source language, both readonly descriptors on the service; the worker-thread backend and the tool-registry consumer (Code Mode) are specified in the [Code Mode RFC](../rfc/proposed/feature/2026-06-15-code-mode.md).
+
+Source: [`packages/code-runtime/code-runtime/src/types.ts`](../../packages/code-runtime/code-runtime/src/types.ts)
+
+## The run: request in, result out
+
+A `CodeRunRequest` carries **everything the runtime acts on** — per the "explicit > implicit at package seams" rule, defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`:
+
+```ts type-equiv
+interface CodeRunRequest {
+  /**
+   * The program source, in the runtime's {@link ../index.ts | language}. It
+   * runs as the body of an async function: top-level `await` and `return`
+   * are available, and the completion value becomes
+   * {@link CodeRunResult.value}.
+   */
+  program: string
+  /** Host functions exposed to the program, one global object per namespace. */
+  bindings: CodeBindingNamespace[]
+  /**
+   * Abort the run: the runtime stops the program (hard, even mid-loop) and
+   * resolves with a {@link CodeRunFailure} of kind `'abort'`. In-flight
+   * binding calls are the CALLER's to settle — the runtime only stops asking.
+   */
+  signal?: AbortSignal
+}
+```
+
+The result reports an error as a **field**, never a rejection of `run()` — reporting a failed program is the caller's job, not an exception path (mirroring `BashExecutor.run`'s resolve-on-failure contract):
+
+```ts type-equiv
+interface CodeRunResult {
+  /**
+   * The program's completion value (its top-level `return`), when it ran to
+   * completion and the value survived the runtime's serialization boundary;
+   * a non-transferable value is replaced by a string rendering, and a failed
+   * or value-less run leaves this absent.
+   */
+  value?: unknown
+  /** Everything the program emitted, in order (capped by the implementation). */
+  logs: CodeLogEntry[]
+  /** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
+  error?: CodeRunFailure
+}
+```
+
+## Bindings: host functions as program globals
+
+Each `CodeBindingNamespace` becomes one global object of async callables inside the program (the Code Mode consumer passes one: `tools`). Arguments and resolutions must be structured-cloneable — a runtime may bridge calls across a serialization boundary — and a runtime treats binding names as hostile input (`__proto__` is an ordinary own property, never a prototype collision):
+
+```ts type-equiv
+interface CodeBindingNamespace {
+  /** The global identifier the program sees (must be a valid JS identifier). */
+  global: string
+  /** The callable members, keyed by the exact name the program calls. */
+  functions: Record<string, CodeBindingFunction>
+}
+```
+
+```ts type-equiv
+type CodeBindingFunction = (args: unknown) => Promise<unknown>
+```
+
+## Captured output and the failure taxonomy
+
+Logs arrive in emission order, attributed to their channel (the runtime's `console` shim, or stray writes to the underlying streams):
+
+```ts type-equiv
+interface CodeLogEntry {
+  /** Which channel produced the text. */
+  source: 'console' | 'stdout' | 'stderr'
+  /** The console method used; present only when `source` is `'console'`. */
+  level?: 'log' | 'info' | 'warn' | 'error' | 'debug'
+  /** The captured text (possibly truncated by the implementation's caps, marked in-band). */
+  text: string
+}
+```
+
+Failure kinds are **orthogonal outcomes reported independently** (per [defensive-patterns](../defensive-patterns.md)): a budget expiry is not an exception, an abort is not a timeout, and a substrate death (e.g. OOM) is neither:
+
+```ts type-equiv
+interface CodeRunFailure {
+  /** The failure class (see the interface doc for each kind's meaning). */
+  kind: 'exception' | 'timeout' | 'abort' | 'worker-exit'
+  /** Human-readable detail, suitable for feeding back to a model to self-correct. */
+  message: string
+}
+```
+
+## The service
+
+`CodeRuntime` (`ctx.codeRuntime`, abstract — defined in [`packages/code-runtime/code-runtime/src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)) is `run(request)` plus two readonly descriptors: `language` (what the program must be written in — `'typescript'` is the well-known value; a consumer generating language-specific presentation switches on it and fails loud on one it cannot present) and `isolation` (the execution substrate — `'worker-thread'`, `'process'`, `'container'`; a diagnostic label, **not a security claim**). Implementations must keep runs isolated from each other (no cross-run state) and dispose to quiescence: in-flight runs are terminated and awaited before teardown completes.

+ 1 - 0
docs/core-data-structures/core.md

@@ -20,6 +20,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
 | [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
 | [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, the `tools/pre-execute`/`tools/post-execute` pipeline |
 | [bash.md](bash.md) | the bash executor seam: `BashExecRequest`/`Spec`, `BashRunResult`, background `BashTask`s |
+| [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
 | [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
 | [compaction.md](compaction.md) | the compaction seam: the `compact/*` session events, `CompactionResult`, the `CompactService` interface |
 | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |

+ 4 - 0
docs/module-graph.md

@@ -79,6 +79,9 @@ flowchart TD
     pkg_app_boot["app-boot"]
     pkg_stdio_agent["stdio-agent"]
   end
+  subgraph group_code_runtime["packages/code-runtime"]
+    pkg_code_runtime["code-runtime"]
+  end
   pkg_llm --> pkg_brand
   pkg_bash --> pkg_brand
   pkg_llm_deepseek --> pkg_llm
@@ -210,6 +213,7 @@ flowchart TD
 | [`brand`](../packages/util/brand) | `util` | — |
 | [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | — |
 | [`app-boot`](../packages/ui/app-boot) | `ui` | — |
+| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — |
 | [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) |
 | [`bash`](../packages/bash/bash) | `bash` | [`brand`](../packages/util/brand) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) |

+ 1 - 1
docs/rfc/proposed/feature/2026-06-15-code-mode.md

@@ -65,7 +65,7 @@ Per explicit-over-implicit at seams, the request spells out everything the runti
 
 ### The worker-thread runtime
 
-`packages/code-runtime/code-runtime-worker/` — `@deepseek-ai/dsh-code-runtime-worker`. Per `run()`:
+`@deepseek-ai/dsh-code-runtime-worker`, the second package of the `packages/code-runtime/` group. Per `run()`:
 
 1. **Type-strip host-side** with Node's built-in `stripTypeScriptTypes` (`node:module`; present across the repo's whole engines range, `^22.19.0 || >=24.0.0`, and position-preserving, so runtime error line numbers match the model's source). Strip-only mode rejects non-erasable syntax (`enum`, namespaces) — that rejection returns as `error.kind: 'exception'` with Node's message, the SDK instructions say "erasable TypeScript only", and the model self-corrects like any other program error. A syntax-level failure never spawns a worker.
 2. **Spawn one fresh `Worker` per run** from the package's own bootstrap module: `env: {}` (truly empty — stronger than the scrubbed-env rule for spawned commands), `resourceLimits` from config, `stdout`/`stderr` captured into `logs` rather than inherited. No pooling and no cross-run state: a program's world dies with its worker, which keeps runs reconstructable from the log alone and makes state bleed unrepresentable.

+ 1 - 0
packages/README.md

@@ -11,6 +11,7 @@ Packages are grouped by modular role at `packages/<group>/<pkg>/`. The group dir
 | [`core/`](core/README.md) | Product API spine: session, system-prompt, tools, agent, and the concrete loop | Product — stable surface |
 | [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface |
 | [`bash/`](bash/README.md) | Bash capability family: the executor seam, a local impl, and the model-facing tool | Product — stable surface |
+| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: the abstract runtime seam for model-written programs | Product — stable surface |
 | [`fs/`](fs/README.md) | Filesystem capability family: the abstract seam, a local impl, and the model-facing file tools | Product — stable surface |
 | [`compact/`](compact/README.md) | Compaction capability family: the abstract seam + a basic backend (tool deferred) | Product — stable surface |
 | [`subagent/`](subagent/README.md) | Subagent capability family: the provider-registry seam and the model-facing delegation tool | Product — stable surface |

+ 9 - 0
packages/code-runtime/README.md

@@ -0,0 +1,9 @@
+# code-runtime/ — code-execution capability family
+
+The code-execution capability seam (see [capability seams](../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): an abstract runtime interface for executing one model-written program against host-provided async bindings, capturing what it printed and returned. The consumer is the tool registry's Code Mode, and the first implementation (a Node worker-thread backend) is specified alongside it in the [Code Mode RFC](../../docs/rfc/proposed/feature/2026-06-15-code-mode.md). **Product** packages.
+
+| Package | Role | ctx key |
+|---|---|---|
+| `code-runtime/` | Abstract code-execution seam (interface + vocabulary) | `ctx.codeRuntime` |
+
+The interface lives at `code-runtime/code-runtime/`. Backends differ by execution substrate (worker thread, process, container) and by source language — both readonly descriptors on the service — and register `ctx.codeRuntime` without touching the interface or its consumer; that split is what makes a hardened backend a drop-in later.

+ 19 - 0
packages/code-runtime/code-runtime/README.md

@@ -0,0 +1,19 @@
+# @deepseek-ai/dsh-code-runtime
+
+The **code-execution seam**: an abstract `CodeRuntime` service (`ctx.codeRuntime`) defining WHAT a code runtime does — run one model-written program against a set of host-provided async bindings and report `{ value, logs, error? }` — without saying HOW.
+
+This package is the interface third of the capability (the bash trio is the template — see [capability seams](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): implementations subclass `CodeRuntime` and register the service; the consumer is the tool registry's Code Mode, which generates the model-facing SDK and bridges tool dispatch — both specified in the [Code Mode RFC](../../../docs/rfc/proposed/feature/2026-06-15-code-mode.md), whose first implementation is a Node worker-thread backend. The runtime knows nothing about tools or sessions: it is handed named async functions and a program string, and everything tool-shaped stays with the consumer.
+
+## Service API (`ctx.codeRuntime`)
+
+| Member | Semantics |
+|---|---|
+| `run(request)` | Execute one program against the request's bindings. **Resolves with an error FIELD for every program outcome** — parse/transform failure, thrown exception, budget expiry, abort, substrate death (`CodeRunFailure`'s orthogonal `kind` taxonomy); it rejects only for caller misuse of the seam itself (e.g. a run submitted after disposal). The program runs as the body of an async function: top-level `await`/`return` work, and the completion value becomes `result.value` when it survives the serialization boundary. |
+| `language` | Readonly descriptor: the source language `run` expects (`'typescript'` is the well-known value). Informational, not gating — a consumer that generates language-specific presentation switches on it and fails loud on a language it cannot present. |
+| `isolation` | Readonly descriptor: the execution substrate (`'worker-thread'`, `'process'`, `'container'`). A label for deployments and diagnostics, **not a security claim**. |
+
+Semantics every implementation must honor (contract details in the class JSDoc): binding calls bridge to the caller's functions verbatim with structured-cloneable arguments/resolutions; the program is treated as a hostile peer (arbitrary binding names are own properties, malformed traffic never crashes the host); no state survives between runs; disposal terminates in-flight runs AND awaits their exit before completing.
+
+## Vocabulary
+
+`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.

+ 30 - 0
packages/code-runtime/code-runtime/package.json

@@ -0,0 +1,30 @@
+{
+  "name": "@deepseek-ai/dsh-code-runtime",
+  "description": "Abstract code-execution seam (ctx.codeRuntime) for the DeepSeek Harness",
+  "version": "0.0.1",
+  "private": true,
+  "type": "module",
+  "main": "lib/index.js",
+  "types": "lib/types/index.d.ts",
+  "exports": {
+    ".": {
+      "types": "./lib/types/index.d.ts",
+      "default": "./lib/index.js"
+    },
+    "./src/*": "./src/*",
+    "./package.json": "./package.json"
+  },
+  "files": [
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
+    "src"
+  ],
+  "license": "BSD-3-Clause",
+  "peerDependencies": {
+    "cordis": "^4.0.0-rc.6"
+  },
+  "devDependencies": {
+    "cordis": "^4.0.0-rc.6"
+  }
+}

+ 93 - 0
packages/code-runtime/code-runtime/src/index.ts

@@ -0,0 +1,93 @@
+/**
+ * The code-execution seam (`ctx.codeRuntime`): an abstract service defining
+ * WHAT a code runtime does — run one model-written program against a set of
+ * host-provided async bindings and report `{ value, logs, error? }` — without
+ * saying HOW. Implementations subclass {@link CodeRuntime} and register
+ * themselves as the `codeRuntime` service; backends may differ by execution
+ * substrate (worker thread, separate process, container) and by source
+ * language, both declared as readonly descriptors. The design and its
+ * consumer (the tool registry's Code Mode) are specified in the Code Mode RFC
+ * (docs/rfc/proposed/feature/2026-06-15-code-mode.md).
+ *
+ * The split mirrors the bash seam (`BashExecutor`): the runtime knows nothing
+ * about tools or sessions — it is handed named async functions and a program,
+ * and everything tool-shaped stays with the consumer.
+ *
+ * @module @deepseek-ai/dsh-code-runtime
+ */
+
+import { Context, Service } from 'cordis'
+import type { CodeRunRequest, CodeRunResult } from './types.ts'
+
+export type {
+  CodeBindingFunction,
+  CodeBindingNamespace,
+  CodeLogEntry,
+  CodeRunFailure,
+  CodeRunRequest,
+  CodeRunResult,
+} from './types.ts'
+
+declare module 'cordis' {
+  interface Context {
+    codeRuntime: CodeRuntime
+  }
+}
+
+/**
+ * Abstract code-execution service. Subclass, implement {@link run} and the
+ * two descriptors, and load the subclass as a plugin — it registers as
+ * `ctx.codeRuntime` (one implementation per context; loading a second throws,
+ * cordis' standard duplicate-service behavior).
+ *
+ * Semantics every implementation must honor:
+ * - {@link run} resolves with an error FIELD for every program outcome —
+ *   parse/transform failures, thrown exceptions, budget expiry, abort,
+ *   substrate death ({@link CodeRunFailure}'s taxonomy). It REJECTS only for
+ *   caller misuse of the seam itself (e.g. a run submitted after disposal).
+ * - Binding calls bridge to the caller's {@link CodeBindingFunction}s
+ *   verbatim; arguments and resolutions must be structured-cloneable, and the
+ *   runtime treats the program as a hostile peer (arbitrary binding names are
+ *   own properties, malformed traffic is rejected or ignored, never crashes
+ *   the host).
+ * - Runs are isolated from each other: no state survives from one run to the
+ *   next through the runtime.
+ * - Disposal reaches quiescence: in-flight runs are terminated AND awaited
+ *   before the service's own teardown completes (no orphan substrate survives
+ *   `fiber.dispose()`).
+ */
+export abstract class CodeRuntime extends Service {
+  /**
+   * The source language {@link run} expects `program` to be written in, as a
+   * lowercase identifier. Informational, not gating — a consumer that
+   * generates language-specific presentation (typed SDK stubs, usage
+   * instructions) switches on it and fails loud on a language it cannot
+   * present. Well-known value: `'typescript'`.
+   */
+  abstract readonly language: string
+
+  /**
+   * The execution substrate, as a lowercase identifier. Informational, not
+   * gating — a descriptor so deployments and diagnostics can tell backends
+   * apart, not a security claim. Well-known values: `'worker-thread'`,
+   * `'process'`, `'container'`.
+   */
+  abstract readonly isolation: string
+
+  constructor(ctx: Context) {
+    super(ctx, 'codeRuntime')
+  }
+
+  /**
+   * Execute one program against the request's bindings and capture what it
+   * emitted. See the class doc for the resolution contract (error is a result
+   * field; rejection means seam misuse only).
+   * @param request - the program, its bindings, and the abort signal; the
+   *   request carries everything the runtime acts on, with no hidden defaults.
+   * @returns the run's outcome: completion value (when transferable), the
+   *   ordered log capture, and the failure (if any).
+   */
+  abstract run(request: CodeRunRequest): Promise<CodeRunResult>
+}
+
+export default CodeRuntime

+ 105 - 0
packages/code-runtime/code-runtime/src/types.ts

@@ -0,0 +1,105 @@
+/**
+ * Vocabulary types for the code-execution seam: what a caller hands a
+ * {@link ../index.ts | CodeRuntime} and what it gets back. Pure types — no
+ * runtime code lives here.
+ *
+ * @module @deepseek-ai/dsh-code-runtime/src/types
+ */
+
+/**
+ * One host-side function exposed to the program as an async callable. The
+ * runtime bridges calls to it (possibly across a serialization boundary), so
+ * `args` and the resolution value MUST be structured-cloneable; a runtime
+ * rejects a non-cloneable value with a descriptive error rather than
+ * corrupting the run. A rejection of this function surfaces inside the
+ * program as a rejection of the corresponding call.
+ */
+export type CodeBindingFunction = (args: unknown) => Promise<unknown>
+
+/**
+ * A named group of {@link CodeBindingFunction}s the runtime exposes to the
+ * program as one global object (e.g. `tools`). Function names are arbitrary
+ * strings — a runtime must treat names like `__proto__` or `constructor` as
+ * ordinary own properties (null-prototype construction), never as prototype
+ * collisions.
+ */
+export interface CodeBindingNamespace {
+  /** The global identifier the program sees (must be a valid JS identifier). */
+  global: string
+  /** The callable members, keyed by the exact name the program calls. */
+  functions: Record<string, CodeBindingFunction>
+}
+
+/**
+ * One run: the program source plus everything the runtime acts on. Per the
+ * explicit-over-implicit convention, defaulting (time budgets, output caps)
+ * is the implementation's validated config — a request carries no optional
+ * tuning knobs for a hidden `??` to fill in.
+ */
+export interface CodeRunRequest {
+  /**
+   * The program source, in the runtime's {@link ../index.ts | language}. It
+   * runs as the body of an async function: top-level `await` and `return`
+   * are available, and the completion value becomes
+   * {@link CodeRunResult.value}.
+   */
+  program: string
+  /** Host functions exposed to the program, one global object per namespace. */
+  bindings: CodeBindingNamespace[]
+  /**
+   * Abort the run: the runtime stops the program (hard, even mid-loop) and
+   * resolves with a {@link CodeRunFailure} of kind `'abort'`. In-flight
+   * binding calls are the CALLER's to settle — the runtime only stops asking.
+   */
+  signal?: AbortSignal
+}
+
+/**
+ * One captured output entry, in emission order. `source` says which channel
+ * produced it: the program's `console` (shimmed by the runtime), or a stray
+ * write to the underlying stdout/stderr streams.
+ */
+export interface CodeLogEntry {
+  /** Which channel produced the text. */
+  source: 'console' | 'stdout' | 'stderr'
+  /** The console method used; present only when `source` is `'console'`. */
+  level?: 'log' | 'info' | 'warn' | 'error' | 'debug'
+  /** The captured text (possibly truncated by the implementation's caps, marked in-band). */
+  text: string
+}
+
+/**
+ * Why a run failed. The kinds are orthogonal outcomes reported independently
+ * (per docs/defensive-patterns.md): a budget expiry is not an exception, an
+ * abort is not a timeout, and a substrate death is neither.
+ *
+ * - `'exception'` — the program threw or failed to parse/transform.
+ * - `'timeout'` — an implementation-owned budget expired; the message says which.
+ * - `'abort'` — {@link CodeRunRequest.signal} fired.
+ * - `'worker-exit'` — the execution substrate died without settling (e.g. OOM).
+ */
+export interface CodeRunFailure {
+  /** The failure class (see the interface doc for each kind's meaning). */
+  kind: 'exception' | 'timeout' | 'abort' | 'worker-exit'
+  /** Human-readable detail, suitable for feeding back to a model to self-correct. */
+  message: string
+}
+
+/**
+ * The outcome of one run. An error is a FIELD on a resolved result, never a
+ * rejection of `run()` — reporting a failed program is the caller's job, not
+ * an exception path.
+ */
+export interface CodeRunResult {
+  /**
+   * The program's completion value (its top-level `return`), when it ran to
+   * completion and the value survived the runtime's serialization boundary;
+   * a non-transferable value is replaced by a string rendering, and a failed
+   * or value-less run leaves this absent.
+   */
+  value?: unknown
+  /** Everything the program emitted, in order (capped by the implementation). */
+  logs: CodeLogEntry[]
+  /** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
+  error?: CodeRunFailure
+}

+ 87 - 0
packages/code-runtime/code-runtime/tests/service.spec.ts

@@ -0,0 +1,87 @@
+import { describe, expect, it } from 'vitest'
+import { Context } from 'cordis'
+import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
+import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
+
+/**
+ * Minimal concrete runtime: records requests, "executes" by invoking every
+ * binding once in declaration order, and lets tests script the outcome. The
+ * seam package ships no implementation, so the contract is exercised through
+ * the smallest subclass that honors it.
+ */
+class StubRuntime extends CodeRuntime {
+  readonly language = 'typescript'
+  readonly isolation = 'in-process-stub'
+  requests: CodeRunRequest[] = []
+  nextResult: CodeRunResult = { logs: [] }
+
+  async run(request: CodeRunRequest): Promise<CodeRunResult> {
+    this.requests.push(request)
+    if (request.signal?.aborted) {
+      return { logs: [], error: { kind: 'abort', message: String(request.signal.reason) } }
+    }
+    for (const namespace of request.bindings) {
+      for (const fn of Object.values(namespace.functions)) {
+        await fn({ from: 'stub' })
+      }
+    }
+    return this.nextResult
+  }
+}
+
+async function setup() {
+  const ctx = new Context()
+  await ctx.plugin(StubRuntime)
+  const runtime = ctx.codeRuntime as StubRuntime
+  return { ctx, runtime }
+}
+
+describe('CodeRuntime service seam', () => {
+  it('registers as ctx.codeRuntime and serves the abstract API', async () => {
+    const { runtime } = await setup()
+    expect(runtime.language).toBe('typescript')
+    expect(runtime.isolation).toBe('in-process-stub')
+
+    const calls: unknown[] = []
+    const result = await runtime.run({
+      program: 'return 1',
+      bindings: [{ global: 'tools', functions: { probe: async args => void calls.push(args) } }],
+    })
+    expect(result).toEqual({ logs: [] })
+    expect(calls).toEqual([{ from: 'stub' }])
+    expect(runtime.requests).toHaveLength(1)
+  })
+
+  it('reports a failed run as an error field on a resolved result, never a rejection', async () => {
+    const { runtime } = await setup()
+    runtime.nextResult = {
+      logs: [{ source: 'console', level: 'error', text: 'boom' }],
+      error: { kind: 'exception', message: 'boom' },
+    }
+    const result = await runtime.run({ program: 'throw new Error("boom")', bindings: [] })
+    expect(result.error).toEqual({ kind: 'exception', message: 'boom' })
+    expect(result.value).toBeUndefined()
+  })
+
+  it('reports a pre-aborted signal as an abort failure', async () => {
+    const { runtime } = await setup()
+    const controller = new AbortController()
+    controller.abort('cancelled')
+    const result = await runtime.run({ program: 'return 1', bindings: [], signal: controller.signal })
+    expect(result.error).toEqual({ kind: 'abort', message: 'cancelled' })
+  })
+
+  it('is removed from the context when the providing fiber disposes (HMR safety)', async () => {
+    const ctx = new Context()
+    const fiber = await ctx.plugin(StubRuntime)
+    expect(ctx.get('codeRuntime')).toBeInstanceOf(StubRuntime)
+
+    await fiber.dispose()
+    expect(ctx.get('codeRuntime')).toBeUndefined()
+  })
+
+  it('rejects a second implementation in the same context (duplicate service)', async () => {
+    const { ctx } = await setup()
+    await expect(ctx.plugin(StubRuntime)).rejects.toThrow(/registered/)
+  })
+})

+ 18 - 0
packages/code-runtime/code-runtime/tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "extends": "../../../tsconfig.base.json",
+  "compilerOptions": {
+    "rootDir": "src",
+    "outDir": "lib/types"
+  },
+  "include": [
+    "src"
+  ],
+  "references": [
+    {
+      "path": "../../../vendor/cosmokit"
+    },
+    {
+      "path": "../../../vendor/cordis"
+    }
+  ]
+}

+ 6 - 0
pnpm-lock.yaml

@@ -127,6 +127,12 @@ importers:
         specifier: ^4.0.0-rc.6
         version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
 
+  packages/code-runtime/code-runtime:
+    devDependencies:
+      cordis:
+        specifier: ^4.0.0-rc.6
+        version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
+
   packages/compact/compact:
     devDependencies:
       '@deepseek-ai/dsh-llm':

+ 1 - 1
scripts/doc-budgets.manifest.json

@@ -1,7 +1,7 @@
 {
   "AGENTS.md": 1691,
   "docs/AGENTS.md": 1315,
-  "docs/architecture.md": 1630,
+  "docs/architecture.md": 1640,
   "docs/cordis-primer.md": 550,
   "docs/defensive-patterns.md": 550,
   "docs/testing.md": 800,

+ 2 - 0
scripts/gen-cordis-catalog.ts

@@ -97,6 +97,8 @@ export const LINK_MAP: Record<string, string> = {
   BashRunResult: 'bash.md',
   BashTask: 'bash.md',
   BashTaskRead: 'bash.md',
+  CodeRunRequest: 'code-runtime.md',
+  CodeRunResult: 'code-runtime.md',
   FsEditOutcome: 'filesystem.md',
   FsEditRequest: 'filesystem.md',
   FsInfo: 'filesystem.md',

+ 9 - 0
scripts/gen-doc-graphs.ts

@@ -149,6 +149,15 @@ const SERVICE_ROLES: ServiceRole[] = [
     consumers: ['tool-bash', 'hooks-claude', 'hooks-codex'],
     note: 'The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local.',
   },
+  {
+    key: 'codeRuntime',
+    pkg: 'code-runtime',
+    title: 'Code-execution seam',
+    mode: 'seam',
+    implementations: [],
+    consumers: [],
+    note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer).',
+  },
   {
     key: 'fs',
     pkg: 'fs',

+ 7 - 0
scripts/type-equiv.manifest.json

@@ -55,6 +55,13 @@
     { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTask", "source": "packages/bash/bash/src/types.ts" },
     { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTaskRead", "source": "packages/bash/bash/src/types.ts" },
 
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunRequest", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunResult", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingNamespace", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingFunction", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeLogEntry", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunFailure", "source": "packages/code-runtime/code-runtime/src/types.ts" },
+
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTarget", "source": "packages/fs/fs/src/types.ts" },
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTargetKey", "source": "packages/fs/fs/src/types.ts" },
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsVersion", "source": "packages/fs/fs/src/types.ts" },

+ 1 - 0
tsconfig.base.json

@@ -43,6 +43,7 @@
         "./packages/core/*/src",
         "./packages/llm/*/src",
         "./packages/bash/*/src",
+        "./packages/code-runtime/*/src",
         "./packages/fs/*/src",
         "./packages/compact/*/src",
         "./packages/subagent/*/src",

+ 1 - 0
tsconfig.build.json

@@ -22,6 +22,7 @@
     { "path": "./packages/core/agent-loop" },
     { "path": "./packages/core/agent-core" },
     { "path": "./packages/bash/bash" },
+    { "path": "./packages/code-runtime/code-runtime" },
     { "path": "./packages/compact/compact" },
     { "path": "./packages/compact/compact-basic" },
     { "path": "./packages/llm/llm-deepseek" },

+ 1 - 0
tsconfig.json

@@ -33,6 +33,7 @@
     { "path": "./packages/core/agent-loop" },
     { "path": "./packages/core/agent-core" },
     { "path": "./packages/bash/bash" },
+    { "path": "./packages/code-runtime/code-runtime" },
     { "path": "./packages/llm/llm-deepseek" },
     { "path": "./packages/llm/llm-pi-ai" },
     { "path": "./packages/bash/bash-local" },