Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Cloudflare Computer is a virtual filesystem that lives inside a
Durable Object. The Durable Object holds the authoritative state in
SQLite and exposes one pluggable execution surface through
`workspace.runtime`. Two backends ship today:
`workspace.runtime`. Three backends ship today:

- **Container** projects the SQLite state into a sandbox container as
a real FUSE mount. A sandbox-side daemon (`computerd`) mounts the state
Expand All @@ -12,11 +12,15 @@ SQLite and exposes one pluggable execution surface through
- **Isolate shell** runs [just-bash](https://github.com/vercel-labs/just-bash)
in a Dynamic Worker. It reaches the authoritative Workspace over
Workers RPC, so there is no second store or sync round trip.
- **Isolate JavaScript** runs an ECMAScript module in a fresh Dynamic
Worker with structured input/results, durable relative imports,
configured libraries, Workspace-backed `node:fs/promises`, and trusted `ws:git` and
`ws:artifacts` modules.

A Workspace may register multiple backends under stable IDs.
`workspace.runtime.exec(source, { backend })` is the single execution
entry point; the selected backend defines how to interpret `source`.
The shipped backends treat it as a shell command. Backends connect lazily on
first use.
entry point; the selected backend defines whether `source` is a shell
command or an ECMAScript module. Backends connect lazily on first use.

Workspace can also be constructed without a backend at all, giving
callers the filesystem on its own.
Expand Down
55 changes: 21 additions & 34 deletions docs/05_runtime_interface.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 05. Runtime interface
# 05. Runtime Interface

Workspace exposes one execution router:

Expand All @@ -12,9 +12,7 @@ const handle = await workspace.runtime.exec(source, {
const result = await handle.result();
```

The backend ID defines how `source` is interpreted. The shipped
container and worker backends treat it as shell syntax. Module backends
can use the same surface for structured code execution.
The backend ID defines how `source` is interpreted. Command runtimes accept shell syntax; module runtimes accept their documented programming language.

## API

Expand Down Expand Up @@ -44,12 +42,7 @@ interface WorkspaceRuntimeExecHandle extends ReadableStream<WorkspaceRuntimeEven
}
```

`input` is accepted by structured module backends and rejected by command
backends. `cwd` is the command working directory or, for module backends,
the base path for backend-specific resolution. A handle is single-consumer:
call `result()` or consume its event stream, not both. Repeated `result()`
calls return the same promise. `backend` records the resolved backend needed
for later reattachment.
`input` is accepted by structured module backends and rejected by command backends. `cwd` is the command working directory or the base for durable relative module imports. A handle is single-consumer: call `result()` or consume its event stream, not both. Repeated `result()` calls return the same promise. `backend` records the resolved backend needed for later reattachment.

## Results

Expand All @@ -69,11 +62,7 @@ interface WorkspaceRuntimeResult {
}
```

Command backends leave `value` unset. Module backends can use `value` for a
structured return value. A command can complete while its post-command pull
fails; in that case `sync.status` is `"pending"`, and a configured
`SyncRetryScheduler` can durably retry the pull without rerunning the
command.
Command backends leave `value` unset. `isolate-javascript` uses `value` for the module's structured return value and reports a zero-entry completed sync. A command can complete while its post-command pull fails; in that case `sync.status` is `"pending"`, and a configured `SyncRetryScheduler` can durably retry the pull without rerunning the command.

## Backend routing

Expand All @@ -85,11 +74,17 @@ await workspace.runtime.exec("grep -R TODO .", {
await workspace.runtime.exec("npm test", {
backend: "container-shell",
});

await workspace.runtime.exec(
`
import fs from "node:fs/promises";
export default async () => fs.readFile("/workspace/package.json", "utf8");
`,
{ backend: "isolate-javascript" },
);
```

Omitting `backend` selects the first configured backend. Backend selection is
routing, not authorization; public gateways must validate it against
server-side policy.
Omitting `backend` selects the first configured backend. Backend selection is routing, not authorization; public gateways must validate it against server-side policy.

## Command synchronization

Expand All @@ -99,24 +94,16 @@ Command backends continue to use the existing synchronization bracket:
push → spawn → events/result → pull
```

A backend with `sync: "none"`, such as `isolate-shell`, shares the host
store and reports zero push/pull counts. A container has its own VFS and
synchronizes changes before and after command execution. Fully draining
either `result()` or the event stream completes the post-command pull before
the stream closes.
A backend with `sync: "none"`, such as `isolate-shell`, shares the host store and reports zero push/pull counts. A Container has its own VFS and synchronizes changes before and after command execution. Fully draining either `result()` or the event stream completes the post-command pull before the stream closes.

Module backends use host capability calls against the authoritative
Workspace and therefore require no push/pull round trip.
Module backends use host capability calls against the authoritative Workspace and therefore require no push/pull round trip.

## Lifecycle differences

`container-shell` provides computerd's retained process log, replay,
signals, and disposal.
`container-shell` provides computerd's retained process log, replay, signals, and disposal.

`isolate-javascript` provides a Workspace-owned execution journal, retained result/events, host cancellation, and explicit disposal. Active Workers cannot be serialized across host restart; orphaned running records are reconciled to failed.

`isolate-shell` intentionally preserves one-call, buffered-result behavior in this release. It does not retain executions for later reattachment or disposal. `timeoutMs` and a concurrent `killExec()` for a caller-supplied execution ID cooperatively abort just-bash at statement boundaries; by the time an ordinary `exec()` promise returns, the command has already settled. Use the Container or JavaScript isolate when detached execution and retained lifecycle are required.

`isolate-shell` intentionally preserves one-call, buffered-result behavior
in this release. It does not retain executions for later reattachment or
disposal. `timeoutMs` and a concurrent `killExec()` for a caller-supplied
execution ID cooperatively abort just-bash at statement boundaries; by the
time an ordinary `exec()` promise returns, the command has already settled.
Use the container backend when detached execution and retained lifecycle are
required.
See [16. Execution runtime architecture](./16_code_execution.md) and [17. Isolate JavaScript](./17_isolate_javascript.md).
3 changes: 2 additions & 1 deletion docs/10_project_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ packages/computer/
├── src/
│ ├── index.ts # Public entrypoint
│ ├── workspace.ts # Workspace facade
│ ├── runtime/ # Public runtime router
│ ├── runtime/ # Public runtime router and capabilities
│ ├── shell.ts # Internal command-backend adapter
│ ├── backend.ts # Command backend interface
│ ├── backends/
│ │ ├── container/ # Cloudflare Container + computerd backend
│ │ ├── worker/ # Dynamic Worker + just-bash backend
│ │ ├── javascript/ # Dynamic Worker ECMAScript backend
│ │ └── test.ts # In-process test backend
│ ├── proxy.ts # WorkspaceProxy
│ ├── proxy-stub.ts # Client-side stub plumbing
Expand Down
5 changes: 3 additions & 2 deletions docs/12_worker_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ The worker backend trades the real environment for a Workers
isolate that boots instantly, scales out cheaply, and has no
container lifecycle. The shell is the just-bash interpreter; the
supported command set is broad (`cat`, `grep`, `awk`, `sed`, `jq`,
`sort`) but not the full Linux userland. just-bash's Node-only language
commands are disabled on workerd. Filesystem operations forward into the same
`sort`) but not the full Linux userland. JavaScript modules run through the
[`isolate-javascript` backend](./17_isolate_javascript.md), not through just-bash's
Node-only language commands. Filesystem operations forward into the same
SQLite store as the container backend, so the storage shape, mount
rules, and read-only enforcement are unchanged.

Expand Down
88 changes: 88 additions & 0 deletions docs/16_code_execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Workspace execution runtimes

Workspace exposes one execution namespace:

```ts
const handle = await workspace.runtime.exec(source, {
backend: "container-shell",
cwd: "/workspace",
encoding: "utf8",
});
const result = await handle.result();
```

The selected backend defines how it interprets `source`.

| Backend | Source language | Intended use |
| --- | --- | --- |
| `container-shell` | shell command | Full Linux, native binaries, installed packages, processes |
| `isolate-shell` | just-bash command | Fast text tools and Workspace Git without a Container |
| `isolate-javascript` | ECMAScript module | Isolated structured JavaScript with trusted Workspace modules |

Applications may register additional command or module backends under their own IDs. Backend IDs are part of the execution contract: changing the backend may change the source language.

## Lifecycle

```ts
const handle = await workspace.runtime.exec(source, {
id: "build-1",
backend: "isolate-javascript",
});

handle.id;
await handle.kill();

const resumed = await workspace.runtime.getExec("build-1", {
backend: "isolate-javascript",
resume: "full",
});

await workspace.runtime.disposeExec("build-1", {
backend: "isolate-javascript",
});
```

The common result contains process-compatible output and an optional structured value:

```ts
interface WorkspaceRuntimeResult {
status: "completed" | "failed" | "cancelled";
exitCode: number;
stdout: Uint8Array | string;
stderr: Uint8Array | string;
value?: WorkspaceRuntimeValue;
pushed: number;
pulled: number;
skipped: SkippedEntry[];
}
```

Command backends leave `value` unset. Module backends use it for their structured return value.

`container-shell` retains the existing computerd process lifecycle. `isolate-javascript` keeps an execution journal in the Workspace database and retains events/results until `disposeExec`. Active isolate cancellation is host-driven by disposing the child Worker. An execution left running across a Workspace host restart is reconciled to failed because a live Worker capability cannot be serialized into SQLite.

`isolate-shell` intentionally retains its existing behavior in this release: it buffers a just-bash call to completion, does not retain cross-request events, and cannot reattach by ID. Callers that require supervised process behavior should use `container-shell`; callers that require a managed isolate should use `isolate-javascript`.

## Backend authority

There is no general `workspace.scope()` abstraction. Backend construction fixes maximum authority and module availability. A public gateway must validate which backend a signed capability is allowed to select.

For different authority levels, configure distinct backend instances:

```ts
new IsolateJavaScriptBackend({
id: "isolate-javascript-readonly",
loader: env.LOADER,
access: "read",
});

new IsolateJavaScriptBackend({
id: "isolate-javascript",
loader: env.LOADER,
access: "read-write",
});
```

The backend argument is never itself authorization.

See [17. Isolate JavaScript](./17_isolate_javascript.md) for module and trusted-package behavior.
Loading
Loading