Skip to content

workspace: add a codemode backend - #19

Open
AntoniTok wants to merge 3 commits into
mainfrom
codemode-backend
Open

workspace: add a codemode backend#19
AntoniTok wants to merge 3 commits into
mainfrom
codemode-backend

Conversation

@AntoniTok

Copy link
Copy Markdown

The workspace can already run commands against its files through a container or through a Dynamic Worker shell, but neither is a natural fit for a task that is really a small piece of logic over the files. This change adds a third backend, codemode, that runs a JavaScript snippet in a network-isolated Dynamic Worker and reports the snippet's return value and console.log output as standard output, with a thrown error becoming standard error and a non-zero exit code.

The snippet reaches the files through a state.* namespace whose functions forward straight to the live workspace filesystem, so there is no second copy of the files and no synchronization step. Each call runs in a fresh sandbox with no network access, which makes it a safe place to run model-authored code that should touch the files and nothing else.

The state.* namespace deliberately mirrors the filesystem surface the shell backend already exposes, rather than a smaller subset. Because the caller picks the backend and every backend acts on the same store, a narrower surface would not contain anything; it would only make codemode less capable for no benefit. The one operation left out is the streaming readFile, since a stream cannot cross the sandbox boundary, so readFileBytes returns the bytes instead. The full surface and this reasoning are covered in docs/16_codemode_backend.md.

You can try it through the new examples/codemode project, which registers all three backends over one filesystem plus an optional agent that picks a backend per command. Run npm run dev --workspace @example/workspace-codemode, then ./script/run round-trips a file through every backend and checks they all see the same store.

The backend ships with node unit tests for the provider and workerd integration tests that drive real snippets through the sandbox against a live workspace, covering every state.* call, its error paths, binary round-trips, and the no-network guarantee.

Antoni T added 3 commits July 27, 2026 12:36
Add a third WorkspaceBackend that runs a JavaScript snippet in a
Dynamic Worker minted through env.LOADER, alongside the existing
container and worker backends. The snippet executes in a fresh,
network-isolated sandbox and reaches the host workspace through a
state.* namespace whose functions forward to the live
WorkspaceFilesystem, so there is no second store and no sync round
trip. Its return value and console.log output become stdout; a
thrown error becomes stderr with exit code 1.

The state.* surface mirrors the filesystem the worker backend
already exposes to just-bash: reads (readFile, readFileBytes, stat,
lstat, exists, readlink, readdir, find, ls, grep) and mutations
(writeFile, mkdir, rm, chmod, symlink). Keeping it smaller would not
contain anything, since the caller picks the backend and all
backends act on the same store. The only operation left out is the
streaming readFile variant, whose ReadableStream cannot cross the
sandbox boundary; readFileBytes drains it into bytes host-side, and
binary survives in both directions through codemode's base64
transport codec.

Each exec runs in a throwaway isolate with globalOutbound set to
null, so a snippet has no network and nothing carries between runs;
getExec rejects with ENOENT and kill and dispose are no-ops. The
backend reports sync "none", so push and pull short-circuit.

Ship the backend under the @cloudflare/workspace/backends/codemode
subpath, with node unit tests for the provider and event mapping and
workerd integration tests that drive real snippets through the
sandbox against a live workspace. DESIGN-NOTES.md records the
divergences from the other backends.
Add a wrangler project that runs one Workspace with all three
backends registered — shell, codemode, and container — over a single
filesystem, so a file written through one backend is visible to the
others. It exposes the same file and exec HTTP routes as the
container and worker examples, plus an optional agent route that runs
a model loop and lets the model pick the backend per command.

The durable object materializes the /workspace mount root on first
use, mirroring what registering a mount does, so the file surface
works on a fresh instance without a manual mkdir. script/run is a
smoke test that round-trips a file through every backend. The
Dockerfile installs Debian's own Node rather than fetching from
NodeSource, so the image builds behind a TLS-intercepting proxy.
Add docs/16_codemode_backend.md covering the backend, its state.*
surface and the reasoning behind exposing the full filesystem, the
wire shape, and its isolation and lifecycle. Reference it from the
docs index, the entrypoints table, the backends overview, and the
project-layout tree.
function errorJSON(error: unknown, status: number): Response {
const message = error instanceof Error ? error.message : String(error);
const code = (error as { code?: string }).code;
return new Response(JSON.stringify({ error: message, code }), {
return new Response(text, { status: 200, headers: { "content-type": "text/plain" } });
} catch (error) {
const code = (error as { code?: string }).code;
return new Response(String(error), { status: code === "ENOENT" ? 404 : 500 });
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/workspace/@cloudflare/workspace@19

commit: c7b576f

@AntoniTok

Copy link
Copy Markdown
Author

Since the model can pick any backend and all three can change files (and the container even has network), a human-in-the-loop approval step before each agent-chosen command is the natural safety layer, I am yet to implement this hence why it's on the codemode-backend branch, not main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants