workspace: add a codemode backend - #19
Open
AntoniTok wants to merge 3 commits into
Open
Conversation
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 }); |
commit: |
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 andconsole.logoutput 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 makecodemodeless capable for no benefit. The one operation left out is the streamingreadFile, since a stream cannot cross the sandbox boundary, soreadFileBytesreturns the bytes instead. The full surface and this reasoning are covered indocs/16_codemode_backend.md.You can try it through the new
examples/codemodeproject, which registers all three backends over one filesystem plus an optional agent that picks a backend per command. Runnpm run dev --workspace @example/workspace-codemode, then./script/runround-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.