From 36facb0630e51588a05a99be07abcb5c62bc4bb5 Mon Sep 17 00:00:00 2001
From: Aron <263346377+aron-cf@users.noreply.github.com>
Date: Thu, 15 Oct 2026 11:08:08 +0000
Subject: [PATCH 1/3] docs(agents): add File system page under Tools
Add /agents/tools/filesystem/, placed above Sandbox in the Tools sidebar.
It covers adding a Cloudflare Computer workspace to a Durable Object,
giving a model the workspace file tools with createAITools(), and
optionally running JavaScript with WorkerJavaScriptBackend.
---
src/content/docs/agents/tools/filesystem.mdx | 423 +++++++++++++++++++
1 file changed, 423 insertions(+)
create mode 100644 src/content/docs/agents/tools/filesystem.mdx
diff --git a/src/content/docs/agents/tools/filesystem.mdx b/src/content/docs/agents/tools/filesystem.mdx
new file mode 100644
index 00000000000..b9744649331
--- /dev/null
+++ b/src/content/docs/agents/tools/filesystem.mdx
@@ -0,0 +1,423 @@
+---
+title: File system
+pcx_content_type: concept
+sidebar:
+ order: 0
+description: Give an agent a durable file system stored in its Durable Object, with file tools for the model and an optional JavaScript runtime.
+products:
+ - agents
+---
+
+import {
+ LinkCard,
+ PackageManagers,
+ TypeScriptExample,
+ WranglerConfig,
+} from "~/components";
+
+Give an agent a durable working directory with [Cloudflare Computer](https://github.com/cloudflare/computer). Computer stores a virtual file system in the SQLite storage of a [Durable Object](/durable-objects/). The agent reads and writes files through an API that resembles `node:fs/promises`, and the model works with them through ready-made tools such as `read`, `write`, `edit`, and `grep`.
+
+Use a file system when an agent needs to:
+
+- Keep notes, drafts, plans, or generated files across turns and restarts.
+- Read, search, and edit text files with tools built for a model.
+- Run model-written JavaScript against its files, without starting a container.
+
+## How it works
+
+A `Workspace` lives inside a Durable Object and keeps every file in the SQLite storage of that object. Agents with different names get separate workspaces, and every request sent to the same agent reaches the same files. Files remain after the Durable Object is evicted or restarted, with no external storage to configure.
+
+`workspace.fs` reads, writes, lists, searches, and deletes files. `workspace.runtime` runs code against the same files through an execution backend. A workspace can have no backend at all, which gives you the file system on its own.
+
+## Install
+
+
+
+`zod` is required by every tool entry point, and `ai` by the [AI SDK](https://ai-sdk.dev/) tools. Your Worker needs the `nodejs_compat` [compatibility flag](/workers/configuration/compatibility-flags/).
+
+## Add a workspace to a Durable Object
+
+Create a `Workspace` in your Durable Object class and pass it the storage of the Durable Object. The file system is ready to use immediately.
+
+
+
+```ts
+import { Workspace } from "@cloudflare/computer";
+import { DurableObject } from "cloudflare:workers";
+
+export class NotesAgent extends DurableObject {
+ workspace = new Workspace({ storage: this.ctx.storage });
+
+ async addTodo(item: string) {
+ const path = "/workspace/notes/todo.md";
+ await this.workspace.fs.mkdir("/workspace/notes", { recursive: true });
+
+ let todo = "";
+ try {
+ todo = await this.workspace.fs.readFile(path, "utf8");
+ } catch (error) {
+ // A missing file means there are no to-do items yet.
+ if ((error as { code?: string }).code !== "ENOENT") throw error;
+ }
+
+ todo += `- [ ] ${item}\n`;
+ await this.workspace.fs.writeFile(path, todo);
+ return todo;
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env) {
+ const agent = env.NotesAgent.getByName("user-123");
+ return new Response(await agent.addTodo("Ship it"));
+ },
+} satisfies ExportedHandler;
+```
+
+
+
+Each call to `addTodo()` on the `user-123` instance appends to the same file, which remains after the Durable Object restarts.
+
+Configure the Durable Object binding and a SQLite migration in your Wrangler configuration:
+
+
+
+```jsonc
+{
+ "compatibility_date": "$today",
+ "compatibility_flags": ["nodejs_compat"],
+ "durable_objects": {
+ "bindings": [
+ {
+ "class_name": "NotesAgent",
+ "name": "NotesAgent"
+ }
+ ]
+ },
+ "migrations": [
+ {
+ "tag": "v1",
+ "new_sqlite_classes": ["NotesAgent"]
+ }
+ ]
+}
+```
+
+
+
+### Work with files
+
+`workspace.fs` is asynchronous and uses absolute paths. Strings are stored as UTF-8. Pass a `Uint8Array` or a `ReadableStream` for binary content.
+
+
+
+```ts
+const { fs } = this.workspace;
+
+// Write a string, bytes, or a stream.
+await fs.writeFile("/workspace/data/blob.bin", new Uint8Array([1, 2, 3]));
+await fs.writeFile("/workspace/uploads/report.csv", request.body!);
+
+// Read a file as text, or as a stream when you omit the encoding.
+const text = await fs.readFile("/workspace/uploads/report.csv", "utf8");
+const stream = await fs.readFile("/workspace/uploads/report.csv");
+
+// List, search, and remove.
+for (const entry of await fs.readdir("/workspace/notes")) {
+ console.log(entry.isDirectory ? `d ${entry.name}` : `f ${entry.name}`);
+}
+const matches = await fs.grep("TODO", "/workspace", { ignoreCase: true });
+await fs.rm("/workspace/notes", { recursive: true });
+```
+
+
+
+For the full list of file operations, refer to the [file system interface](https://github.com/cloudflare/computer/blob/main/docs/04_filesystem_interface.md).
+
+## Give the model file tools
+
+`createAITools()` from `@cloudflare/computer/tools/ai-sdk` returns an AI SDK `ToolSet` for a workspace. Pass it to `generateText()`, `streamText()`, or the `getTools()` method of an agent.
+
+The following [Think](/agents/harnesses/think/) agent replaces its default workspace with a Computer workspace and gives the model the Computer file tools.
+
+
+
+```ts
+import {
+ type ThinkWorkspaceCompatibility,
+ Workspace,
+} from "@cloudflare/computer";
+import { createAITools } from "@cloudflare/computer/tools/ai-sdk";
+import { Think } from "@cloudflare/think";
+import { routeAgentRequest } from "agents";
+import { createWorkersAI } from "workers-ai-provider";
+
+export class WriterAgent extends Think {
+ // The file tools below replace the built-in workspace tools.
+ override workspaceBash = false;
+
+ override workspace = new Workspace({
+ storage: this.ctx.storage,
+ // Adds the methods that Think expects from its workspace.
+ useThink: true,
+ }) as Workspace & ThinkWorkspaceCompatibility;
+
+ override getModel() {
+ return createWorkersAI({ binding: this.env.AI })(
+ "@cf/moonshotai/kimi-k2.6",
+ );
+ }
+
+ override getSystemPrompt() {
+ return "You are a writing assistant. Keep drafts and notes as files under /workspace.";
+ }
+
+ override getTools() {
+ return createAITools({
+ workspace: this.workspace,
+ read: { maxBytes: 32 * 1024, maxLines: 800 },
+ });
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env) {
+ return (
+ (await routeAgentRequest(request, env)) ||
+ new Response("Not found", { status: 404 })
+ );
+ },
+} satisfies ExportedHandler;
+```
+
+
+
+The tool set contains the following tools:
+
+| Tool | What it does |
+| -------- | -------------------------------------------------------------------------------- |
+| `read` | Reads a text file by line, or passes an image or PDF to a model that accepts it. |
+| `ls` | Lists one directory, page by page. |
+| `find` | Finds paths with `*`, `**`, and `?` globs. |
+| `grep` | Searches file contents with a regular expression or a fixed string. |
+| `write` | Writes a whole file. |
+| `edit` | Replaces targeted text in a file and returns a diff. |
+| `delete` | Deletes a file or directory. |
+| `exec` | Runs code on an execution backend. Present only when the workspace has one. |
+
+Think merges custom tools with its [built-in workspace tools](/agents/harnesses/think/tools/#built-in-workspace-tools), and a custom tool replaces a built-in tool with the same name. `workspaceBash = false` removes the built-in `bash` tool, because the Computer workspace provides its own way to run code.
+
+### Configure the tools
+
+`createAITools()` accepts the following options:
+
+| Option | Default | Description |
+| ----------- | ------------- | ----------------------------------------------------------------------------------- |
+| `workspace` | Required | A `Workspace`, or a client from `getWorkspace()`. |
+| `readonly` | `false` | Offers only `read`, `ls`, `find`, and `grep`. |
+| `read` | Default caps | Limits for `read`, such as `maxBytes`, `maxLines`, and `includeLineNumbers`. |
+| `write` | Default caps | Limits for `write`. |
+| `edit` | Default caps | Limits for `edit`. |
+| `exec` | Every backend | A map from backend ID to `{ description }`. Pass `{}` to leave out the `exec` tool. |
+
+To let the model read and search files but not change them, pass `readonly: true`:
+
+
+
+```ts
+createAITools({ workspace: this.workspace, readonly: true });
+```
+
+
+
+The same tools, with the same names and options, are available for other agent libraries:
+
+| Library | Entry point | Function |
+| -------------------------------------- | ---------------------------------------- | ----------------------- |
+| [AI SDK](https://ai-sdk.dev/) | `@cloudflare/computer/tools/ai-sdk` | `createAITools()` |
+| [Pi AI](/agents/models/pi-ai/) | `@cloudflare/computer/tools/pi-ai` | `createPiTools()` |
+| [TanStack AI](https://tanstack.com/ai) | `@cloudflare/computer/tools/tanstack-ai` | `createTanStackTools()` |
+
+## Run JavaScript in the workspace
+
+Add a `WorkerJavaScriptBackend` to let the agent, or the model, run JavaScript against workspace files. Each run is an ECMAScript module that executes in a new [Dynamic Worker](/dynamic-workers/). It starts faster than a container, has no network access by default, and reaches files through a `node:fs/promises` module that reads and writes the workspace.
+
+
+
+```ts
+import {
+ type ThinkWorkspaceCompatibility,
+ Workspace,
+} from "@cloudflare/computer";
+import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";
+import { createAITools } from "@cloudflare/computer/tools/ai-sdk";
+import { Think } from "@cloudflare/think";
+
+export class WriterAgent extends Think {
+ override workspaceBash = false;
+
+ override workspace = new Workspace({
+ storage: this.ctx.storage,
+ backends: [new WorkerJavaScriptBackend({ loader: this.env.LOADER })],
+ useThink: true,
+ }) as Workspace & ThinkWorkspaceCompatibility;
+
+ // getModel() and getSystemPrompt() as before.
+
+ override getTools() {
+ // The workspace now has a backend, so the tool set includes `exec`.
+ return createAITools({ workspace: this.workspace });
+ }
+}
+```
+
+
+
+The backend needs a [Worker Loader binding](/workers/runtime-apis/bindings/worker-loader/):
+
+
+
+```jsonc
+{
+ "compatibility_date": "$today",
+ "compatibility_flags": ["nodejs_compat"],
+ "worker_loaders": [
+ {
+ "binding": "LOADER"
+ }
+ ]
+}
+```
+
+
+
+The `exec` tool describes the backend to the model, including the modules that its code can import, so the model can write a module like the following without further instructions:
+
+```js
+import fs from "node:fs/promises";
+
+export default async function (input) {
+ const names = await fs.readdir("/workspace/notes");
+ const counts = {};
+ for (const name of names) {
+ const text = await fs.readFile(`/workspace/notes/${name}`, "utf8");
+ counts[name] = text.split(/\s+/).filter(Boolean).length;
+ }
+ return counts;
+}
+```
+
+The module must have a default export. A default-exported function is called with the input of the run, and its return value becomes the result. Put file and module calls inside that function. The Workers runtime does not allow I/O while a module loads, so a call at the top level of the module fails the run.
+
+### Run code from the agent
+
+Your own code can run a module through `workspace.runtime.exec()`. Pass structured data as `input`, and read the return value from `value`:
+
+
+
+```ts
+using run = await this.workspace.runtime.exec(
+ `
+ import fs from "node:fs/promises";
+
+ export default async function ({ path }) {
+ const text = await fs.readFile(path, "utf8");
+ return { lines: text.split("\\n").length };
+ }
+ `,
+ { input: { path: "/workspace/notes/todo.md" }, encoding: "utf8" },
+);
+
+const { exitCode, value, stdout, stderr } = await run.result();
+```
+
+
+
+`console.log()` writes to `stdout`, and `console.error()` writes to `stderr`. The input and the return value must be JSON-compatible plain data. A module can also import other files in the workspace with relative imports, such as `import { summarize } from "./lib/summarize.js"`, which resolve from the `cwd` option of the run.
+
+### Configure the backend
+
+`WorkerJavaScriptBackend` accepts the following common options:
+
+| Option | Default | Description |
+| ------------------ | -------------- | ------------------------------------------------------------------------------------------------- |
+| `loader` | Required | The Worker Loader binding. |
+| `root` | `/workspace` | The directory that code can access. Paths outside it are rejected. |
+| `access` | `"read-write"` | Set to `"read"` to let code read files but not change them. |
+| `defaultTimeoutMs` | `60000` | How long a run can take when the caller does not set a timeout. |
+| `maxTimeoutMs` | `180000` | The longest timeout that a caller can request. |
+| `globalOutbound` | `null` | A `Fetcher` that receives every outbound request from the code. `null` blocks network access. |
+| `modules` | — | Extra modules that code can import, such as bundled libraries or functions that run in the agent. |
+
+### Give code access to your own functions
+
+A host module is a set of functions that run in the Durable Object of the agent, which code in the Dynamic Worker can import by a `ws:` name. Use a host module to give model-written code a narrow, typed capability, such as access to your data, without giving it your bindings or credentials.
+
+
+
+```ts
+// In the `backends` list of the workspace.
+new WorkerJavaScriptBackend({
+ loader: this.env.LOADER,
+ modules: {
+ "ws:orders": {
+ // Arguments arrive as an array of JSON-compatible values.
+ lookup: ([orderId]) => this.lookUpOrder(String(orderId)),
+ },
+ },
+});
+```
+
+
+
+Code imports the module like any other:
+
+```js
+import { lookup } from "ws:orders";
+
+export default async function ({ ids }) {
+ return Promise.all(ids.map((id) => lookup(id)));
+}
+```
+
+Arguments come from model-written code, so validate them before use. Computer also includes prebuilt host modules for Git, [Artifacts](/artifacts/), and the agent's own tools. For more information, refer to the [isolate JavaScript runtime documentation](https://github.com/cloudflare/computer/blob/main/docs/17_isolate_javascript.md#modules).
+
+## Limits
+
+- Each workspace shares the storage of its Durable Object. For storage limits, refer to [Durable Objects limits](/durable-objects/platform/limits/). Size workspaces for an agent's working files, not for full monorepos or large dependency trees.
+- The JavaScript backend runs ECMAScript modules, not shell commands or native binaries. When an agent needs a Linux environment, use a [sandbox](/agents/tools/sandbox/).
+- Each JavaScript run is bounded in CPU time, wall-clock time, output size, and the number and size of file operations. Completed runs are kept for 60 minutes by default.
+
+## Security considerations
+
+The model reads everything that the file tools and `exec` return. A file can contain text written to steer the model, such as an instruction to delete other files. Check tool results before the agent acts on them outside the workspace, and require [approval](/agents/harnesses/think/tools/#tool-approval) for tools with side effects.
+
+Code that the JavaScript backend runs cannot read the environment variables, bindings, or storage of the agent. It reaches only the files under `root`, the modules you configure, and the network when you pass a `globalOutbound`. Host modules run with the full access of the agent, so expose only the functions that model-written code needs.
+
+Every user who talks to the same agent instance shares its workspace. Give each user their own agent instance when their files must stay apart.
+
+## Related resources
+
+
+
+
+
+
+
+
From 2f9d56ae825cc2a213e14e233bcae54b1c462592 Mon Sep 17 00:00:00 2001
From: Aron <263346377+aron-cf@users.noreply.github.com>
Date: Thu, 15 Oct 2026 12:34:40 +0100
Subject: [PATCH 2/3] Apply batched suggestions from code review
Co-authored-by: Aron <263346377+aron-cf@users.noreply.github.com>
---
src/content/docs/agents/tools/filesystem.mdx | 24 ++++++++++----------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/content/docs/agents/tools/filesystem.mdx b/src/content/docs/agents/tools/filesystem.mdx
index b9744649331..325a0cc6882 100644
--- a/src/content/docs/agents/tools/filesystem.mdx
+++ b/src/content/docs/agents/tools/filesystem.mdx
@@ -3,7 +3,7 @@ title: File system
pcx_content_type: concept
sidebar:
order: 0
-description: Give an agent a durable file system stored in its Durable Object, with file tools for the model and an optional JavaScript runtime.
+description: Give an agent a virtual file system stored in its Durable Object, with model tools for and an optional JavaScript runtime.
products:
- agents
---
@@ -15,19 +15,20 @@ import {
WranglerConfig,
} from "~/components";
-Give an agent a durable working directory with [Cloudflare Computer](https://github.com/cloudflare/computer). Computer stores a virtual file system in the SQLite storage of a [Durable Object](/durable-objects/). The agent reads and writes files through an API that resembles `node:fs/promises`, and the model works with them through ready-made tools such as `read`, `write`, `edit`, and `grep`.
+[Cloudflare Computer](https://github.com/cloudflare/computer) provides an agent a persistent working directory backed by SQLite along with the standard set of tools for working with the filesystem including `read`, `write`, `edit`, and `grep`. An optional `exec` tool can be provided to allow the agent to manipulate the filesystem and programmatically run tools.
Use a file system when an agent needs to:
- Keep notes, drafts, plans, or generated files across turns and restarts.
- Read, search, and edit text files with tools built for a model.
-- Run model-written JavaScript against its files, without starting a container.
+- Write and run JavaScript against files, without starting a container.
+- Provide skills, source files and other external documents to the agent without filling it's context window.
## How it works
-A `Workspace` lives inside a Durable Object and keeps every file in the SQLite storage of that object. Agents with different names get separate workspaces, and every request sent to the same agent reaches the same files. Files remain after the Durable Object is evicted or restarted, with no external storage to configure.
+A `Workspace` lives inside a Durable Object and keeps every file in the SQLite storage of that object. Files remain after the Durable Object is evicted or restarted, with no external storage to configure.
-`workspace.fs` reads, writes, lists, searches, and deletes files. `workspace.runtime` runs code against the same files through an execution backend. A workspace can have no backend at all, which gives you the file system on its own.
+`workspace.fs` reads, writes, lists, searches, and deletes files. `workspace.runtime` runs code against the same files through an execution backend. We provide a JavaScript runtime out of the box as well as an experimental container based runtime for running shell commands.
## Install
@@ -37,7 +38,7 @@ A `Workspace` lives inside a Durable Object and keeps every file in the SQLite s
## Add a workspace to a Durable Object
-Create a `Workspace` in your Durable Object class and pass it the storage of the Durable Object. The file system is ready to use immediately.
+Create a `Workspace` in your Durable Object class and pass it the `storage` object. The file system is ready to use immediately.
@@ -76,7 +77,6 @@ export default {
-Each call to `addTodo()` on the `user-123` instance appends to the same file, which remains after the Durable Object restarts.
Configure the Durable Object binding and a SQLite migration in your Wrangler configuration:
@@ -107,7 +107,7 @@ Configure the Durable Object binding and a SQLite migration in your Wrangler con
### Work with files
-`workspace.fs` is asynchronous and uses absolute paths. Strings are stored as UTF-8. Pass a `Uint8Array` or a `ReadableStream` for binary content.
+The `workspace.fs` object provides common methods for working with the filesystem. It takes `string` or binary `Uint8Array` types; use a `ReadableStream` when reading/writing large files.
@@ -205,7 +205,7 @@ The tool set contains the following tools:
| `delete` | Deletes a file or directory. |
| `exec` | Runs code on an execution backend. Present only when the workspace has one. |
-Think merges custom tools with its [built-in workspace tools](/agents/harnesses/think/tools/#built-in-workspace-tools), and a custom tool replaces a built-in tool with the same name. `workspaceBash = false` removes the built-in `bash` tool, because the Computer workspace provides its own way to run code.
+Think merges custom tools with its [built-in workspace tools](/agents/harnesses/think/tools/#built-in-workspace-tools). A custom tool overrides a built-in. The `workspaceBash = false` call is required to remove the built-in `bash` tool because the Computer workspace provides its own way to run code.
### Configure the tools
@@ -240,7 +240,7 @@ The same tools, with the same names and options, are available for other agent l
## Run JavaScript in the workspace
-Add a `WorkerJavaScriptBackend` to let the agent, or the model, run JavaScript against workspace files. Each run is an ECMAScript module that executes in a new [Dynamic Worker](/dynamic-workers/). It starts faster than a container, has no network access by default, and reaches files through a `node:fs/promises` module that reads and writes the workspace.
+Add a `WorkerJavaScriptBackend` to let the agent, or the model, run JavaScript against workspace files. Each run is an ECMAScript module that executes in a new [Dynamic Worker](/dynamic-workers/). It is faster and cheaper to run than a container, network access is configurable but off by default and provides the `node:fs/promises` module to interact with the workspace.
@@ -307,7 +307,7 @@ export default async function (input) {
}
```
-The module must have a default export. A default-exported function is called with the input of the run, and its return value becomes the result. Put file and module calls inside that function. The Workers runtime does not allow I/O while a module loads, so a call at the top level of the module fails the run.
+The module must have a default export. A default-exported function is called with the input of the run, and its return value becomes the result. Put file and module calls inside that function. The Workers runtime does not allow I/O while a module loads so accesing the filesystem at the module level will error.
### Run code from the agent
@@ -351,7 +351,7 @@ const { exitCode, value, stdout, stderr } = await run.result();
### Give code access to your own functions
-A host module is a set of functions that run in the Durable Object of the agent, which code in the Dynamic Worker can import by a `ws:` name. Use a host module to give model-written code a narrow, typed capability, such as access to your data, without giving it your bindings or credentials.
+Custom modules can be registered under the `ws:` namespace. Use these modules to provide the model access to your application logic, filesystem tools or perform external actions.
From cf8fa40055f72b8a895c16e90336666344bd1bd7 Mon Sep 17 00:00:00 2001
From: Aron <263346377+aron-cf@users.noreply.github.com>
Date: Thu, 15 Oct 2026 11:47:38 +0000
Subject: [PATCH 3/3] docs(agents): address review feedback on File system page
- Cover the Think, AI SDK, Pi, and OpenCode harnesses, with a note
pointing to each and typed adapters for Pi Durable and OpenCode.
- Update the Think guidance for the Computer workspace that Think now
includes (PR #34297): no useThink, workspaceBash, or createAITools().
- Add an API reference for Workspace, workspace.fs, workspace.runtime,
the tool functions, and the host modules, and link to it.
- Link the Worker Loader binding to /dynamic-workers/ directly, which
fixes the nimbus/internal-link redirect error.
- Fix the description, the "accesing" and "it's" typos, and wording.
---
src/content/docs/agents/tools/filesystem.mdx | 373 ++++++++++++++-----
1 file changed, 272 insertions(+), 101 deletions(-)
diff --git a/src/content/docs/agents/tools/filesystem.mdx b/src/content/docs/agents/tools/filesystem.mdx
index 325a0cc6882..f8a1cf1d3e7 100644
--- a/src/content/docs/agents/tools/filesystem.mdx
+++ b/src/content/docs/agents/tools/filesystem.mdx
@@ -3,7 +3,7 @@ title: File system
pcx_content_type: concept
sidebar:
order: 0
-description: Give an agent a virtual file system stored in its Durable Object, with model tools for and an optional JavaScript runtime.
+description: Give an agent a virtual file system stored in its Durable Object, with file tools for the model and an optional JavaScript runtime.
products:
- agents
---
@@ -15,26 +15,32 @@ import {
WranglerConfig,
} from "~/components";
-[Cloudflare Computer](https://github.com/cloudflare/computer) provides an agent a persistent working directory backed by SQLite along with the standard set of tools for working with the filesystem including `read`, `write`, `edit`, and `grep`. An optional `exec` tool can be provided to allow the agent to manipulate the filesystem and programmatically run tools.
+[Cloudflare Computer](https://github.com/cloudflare/computer) gives an agent a persistent working directory backed by SQLite, along with the standard set of tools for working with the file system, including `read`, `write`, `edit`, and `grep`. An optional `exec` tool lets the agent manipulate the file system and run tools programmatically.
Use a file system when an agent needs to:
- Keep notes, drafts, plans, or generated files across turns and restarts.
- Read, search, and edit text files with tools built for a model.
- Write and run JavaScript against files, without starting a container.
-- Provide skills, source files and other external documents to the agent without filling it's context window.
+- Provide skills, source files, and other external documents to the agent without filling its context window.
## How it works
A `Workspace` lives inside a Durable Object and keeps every file in the SQLite storage of that object. Files remain after the Durable Object is evicted or restarted, with no external storage to configure.
-`workspace.fs` reads, writes, lists, searches, and deletes files. `workspace.runtime` runs code against the same files through an execution backend. We provide a JavaScript runtime out of the box as well as an experimental container based runtime for running shell commands.
+`workspace.fs` reads, writes, lists, searches, and deletes files. `workspace.runtime` runs code against the same files through an execution backend. Computer includes a JavaScript runtime, as well as an experimental container-based runtime for running shell commands.
## Install
-
+
-`zod` is required by every tool entry point, and `ai` by the [AI SDK](https://ai-sdk.dev/) tools. Your Worker needs the `nodejs_compat` [compatibility flag](/workers/configuration/compatibility-flags/).
+Every tool entry point requires `zod`. What else you need depends on how your agent calls tools:
+
+- **[Think](/agents/harnesses/think/)** includes the workspace tools. Install `@cloudflare/computer` alongside it.
+- **[AI SDK](https://ai-sdk.dev/)** tools also require `ai`.
+- **[Pi](/agents/harnesses/pi/)** and **[OpenCode](/agents/harnesses/opencode/)** use the Pi AI tools, which need nothing beyond `zod`.
+
+Your Worker needs the `nodejs_compat` [compatibility flag](/workers/configuration/compatibility-flags/).
## Add a workspace to a Durable Object
@@ -77,7 +83,6 @@ export default {
-
Configure the Durable Object binding and a SQLite migration in your Wrangler configuration:
@@ -107,7 +112,7 @@ Configure the Durable Object binding and a SQLite migration in your Wrangler con
### Work with files
-The `workspace.fs` object provides common methods for working with the filesystem. It takes `string` or binary `Uint8Array` types; use a `ReadableStream` when reading/writing large files.
+The `workspace.fs` object provides common methods for working with the file system. It accepts `string` or binary `Uint8Array` content. Use a `ReadableStream` to read or write large files.
@@ -132,84 +137,184 @@ await fs.rm("/workspace/notes", { recursive: true });
-For the full list of file operations, refer to the [file system interface](https://github.com/cloudflare/computer/blob/main/docs/04_filesystem_interface.md).
+For every file operation, refer to [`workspace.fs`](#workspacefs) in the API reference.
## Give the model file tools
-`createAITools()` from `@cloudflare/computer/tools/ai-sdk` returns an AI SDK `ToolSet` for a workspace. Pass it to `generateText()`, `streamText()`, or the `getTools()` method of an agent.
+Computer builds one set of file tools for a workspace, with adapters for different agent libraries:
-The following [Think](/agents/harnesses/think/) agent replaces its default workspace with a Computer workspace and gives the model the Computer file tools.
+| Tool | What it does |
+| -------- | -------------------------------------------------------------------------------- |
+| `read` | Reads a text file by line, or passes an image or PDF to a model that accepts it. |
+| `ls` | Lists one directory, page by page. |
+| `find` | Finds paths with `*`, `**`, and `?` globs. |
+| `grep` | Searches file contents with a regular expression or a fixed string. |
+| `write` | Writes a whole file. |
+| `edit` | Replaces targeted text in a file and returns a diff. |
+| `delete` | Deletes a file or directory. |
+| `exec` | Runs code on an execution backend. Present only when the workspace has one. |
+
+:::note[Harnesses]
+[Think](#think) adds these tools to every turn with no configuration. The [Pi](#pi) and [OpenCode](#opencode) harnesses take the Pi AI tools through a small adapter. Any other agent built on the [AI SDK](#ai-sdk) can use `createAITools()`.
+:::
+
+### Think
+
+Every [Think](/agents/harnesses/think/) agent has a Computer `Workspace` at `this.workspace`, and Think adds the workspace tools to every turn. To change the workspace, override `workspace`. A custom tool from `getTools()` with the same name as a workspace tool replaces it.
+
+For more information, refer to [Built-in workspace tools](/agents/harnesses/think/tools/#built-in-workspace-tools).
+
+### AI SDK
+
+`createAITools()` from `@cloudflare/computer/tools/ai-sdk` returns an AI SDK `ToolSet` for a workspace. Pass it to `generateText()`, `streamText()`, or the `getTools()` method of an agent.
```ts
-import {
- type ThinkWorkspaceCompatibility,
- Workspace,
-} from "@cloudflare/computer";
+import { Workspace } from "@cloudflare/computer";
import { createAITools } from "@cloudflare/computer/tools/ai-sdk";
-import { Think } from "@cloudflare/think";
-import { routeAgentRequest } from "agents";
+import { generateText, stepCountIs } from "ai";
+import { DurableObject } from "cloudflare:workers";
import { createWorkersAI } from "workers-ai-provider";
-export class WriterAgent extends Think {
- // The file tools below replace the built-in workspace tools.
- override workspaceBash = false;
+export class WriterAgent extends DurableObject {
+ workspace = new Workspace({ storage: this.ctx.storage });
- override workspace = new Workspace({
- storage: this.ctx.storage,
- // Adds the methods that Think expects from its workspace.
- useThink: true,
- }) as Workspace & ThinkWorkspaceCompatibility;
-
- override getModel() {
- return createWorkersAI({ binding: this.env.AI })(
- "@cf/moonshotai/kimi-k2.6",
- );
+ async write(prompt: string) {
+ const { text } = await generateText({
+ model: createWorkersAI({ binding: this.env.AI })(
+ "@cf/moonshotai/kimi-k2.6",
+ ),
+ system:
+ "You are a writing assistant. Keep drafts and notes as files under /workspace.",
+ prompt,
+ tools: createAITools({
+ workspace: this.workspace,
+ read: { maxBytes: 32 * 1024, maxLines: 800 },
+ }),
+ stopWhen: stepCountIs(20),
+ });
+ return text;
}
+}
+```
- override getSystemPrompt() {
- return "You are a writing assistant. Keep drafts and notes as files under /workspace.";
- }
+
- override getTools() {
- return createAITools({
- workspace: this.workspace,
- read: { maxBytes: 32 * 1024, maxLines: 800 },
- });
- }
+### Pi
+
+`createPiTools()` from `@cloudflare/computer/tools/pi-ai` returns Pi AI tool declarations and one `execute` function that runs a call by name. [Pi Durable](/agents/harnesses/pi/) runs the agent loop itself and expects each tool to carry its own `execute`, so pair them up as `ToolRegistration` objects:
+
+
+
+```ts
+import type { Workspace } from "@cloudflare/computer";
+import { createPiTools } from "@cloudflare/computer/tools/pi-ai";
+import type { ToolRegistration } from "@earendil-works/pi-durable";
+
+// Running these again after an eviction is harmless.
+const REPLAY_SAFE = new Set(["read", "ls", "find", "grep", "write", "delete"]);
+
+export function workspaceTools(workspace: Workspace): ToolRegistration[] {
+ const { tools, execute } = createPiTools({ workspace });
+ return tools.map((tool): ToolRegistration => ({
+ name: tool.name,
+ description: tool.description,
+ parameters: tool.parameters,
+ replay: REPLAY_SAFE.has(tool.name) ? "safe" : "unsafe",
+ async execute(args, api, context) {
+ const { content, isError } = await execute(
+ { id: api.callId, name: tool.name, arguments: args },
+ { abortSignal: context.abortSignal },
+ );
+ return { content, isError };
+ },
+ }));
}
+```
-export default {
- async fetch(request: Request, env: Env) {
- return (
- (await routeAgentRequest(request, env)) ||
- new Response("Not found", { status: 404 })
- );
- },
-} satisfies ExportedHandler;
+
+
+Install the tools in an [extension](/agents/harnesses/pi/extensions/) inside the harness factory, before `Harness.open()`:
+
+
+
+```ts
+this.registry.install({
+ name: "workspace",
+ tools: workspaceTools(this.workspace),
+});
```
-The tool set contains the following tools:
+`execute` validates the arguments against the schema of the tool and reports failures as `isError` results, so the model can retry.
-| Tool | What it does |
-| -------- | -------------------------------------------------------------------------------- |
-| `read` | Reads a text file by line, or passes an image or PDF to a model that accepts it. |
-| `ls` | Lists one directory, page by page. |
-| `find` | Finds paths with `*`, `**`, and `?` globs. |
-| `grep` | Searches file contents with a regular expression or a fixed string. |
-| `write` | Writes a whole file. |
-| `edit` | Replaces targeted text in a file and returns a diff. |
-| `delete` | Deletes a file or directory. |
-| `exec` | Runs code on an execution backend. Present only when the workspace has one. |
+### OpenCode
-Think merges custom tools with its [built-in workspace tools](/agents/harnesses/think/tools/#built-in-workspace-tools). A custom tool overrides a built-in. The `workspaceBash = false` call is required to remove the built-in `bash` tool because the Computer workspace provides its own way to run code.
+[OpenCode](/agents/harnesses/opencode/) receives tools through plugins. The following plugin replaces the OpenCode tools that need a local file system with the workspace tools:
+
+
+
+```ts
+import type { Workspace } from "@cloudflare/computer";
+import { createPiTools } from "@cloudflare/computer/tools/pi-ai";
+import type { Plugin } from "@opencode/plugin";
+
+// OpenCode tools that need a local file system or process.
+const LOCAL_TOOLS = ["read", "write", "edit", "patch", "glob", "grep", "shell"];
+
+export function workspacePlugin(workspace: Workspace): Plugin.Plugin {
+ const { tools, execute } = createPiTools({ workspace });
+ return {
+ id: "workspace",
+ async setup(ctx) {
+ await ctx.tool.transform((editor) => {
+ for (const id of LOCAL_TOOLS) editor.remove(id);
+ for (const tool of tools) {
+ editor.add({
+ name: tool.name,
+ description: tool.description,
+ input: tool.parameters,
+ // Offer each tool by name instead of through OpenCode's code mode.
+ options: { codemode: false },
+ async execute(input, context) {
+ const { content, isError } = await execute(
+ { id: context.id, name: tool.name, arguments: input },
+ { abortSignal: context.signal },
+ );
+ const text = content
+ .map((part) => (part.type === "text" ? part.text : ""))
+ .join("\n");
+ if (isError) throw new Error(text || `${tool.name} failed`);
+ return { content: text };
+ },
+ });
+ }
+ });
+ },
+ };
+}
+```
+
+
+
+Add the plugin to the `plugins` array that you pass to `OpenCodeWorkerd.create()` in the harness factory:
+
+
+
+```ts
+OpenCodeWorkerd.create({
+ storage,
+ plugins: [this.ai.plugin, workspacePlugin(this.workspace)],
+});
+```
+
+
### Configure the tools
-`createAITools()` accepts the following options:
+`createAITools()`, `createPiTools()`, and `createTanStackTools()` accept the same options:
| Option | Default | Description |
| ----------- | ------------- | ----------------------------------------------------------------------------------- |
@@ -230,50 +335,30 @@ createAITools({ workspace: this.workspace, readonly: true });
-The same tools, with the same names and options, are available for other agent libraries:
-
-| Library | Entry point | Function |
-| -------------------------------------- | ---------------------------------------- | ----------------------- |
-| [AI SDK](https://ai-sdk.dev/) | `@cloudflare/computer/tools/ai-sdk` | `createAITools()` |
-| [Pi AI](/agents/models/pi-ai/) | `@cloudflare/computer/tools/pi-ai` | `createPiTools()` |
-| [TanStack AI](https://tanstack.com/ai) | `@cloudflare/computer/tools/tanstack-ai` | `createTanStackTools()` |
-
## Run JavaScript in the workspace
-Add a `WorkerJavaScriptBackend` to let the agent, or the model, run JavaScript against workspace files. Each run is an ECMAScript module that executes in a new [Dynamic Worker](/dynamic-workers/). It is faster and cheaper to run than a container, network access is configurable but off by default and provides the `node:fs/promises` module to interact with the workspace.
+Add a `WorkerJavaScriptBackend` to let the agent, or the model, run JavaScript against workspace files. Each run is an ECMAScript module that executes in a new [Dynamic Worker](/dynamic-workers/). It is faster and cheaper to run than a container. Network access is configurable but off by default, and the module reaches the workspace through `node:fs/promises`.
```ts
-import {
- type ThinkWorkspaceCompatibility,
- Workspace,
-} from "@cloudflare/computer";
+import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";
-import { createAITools } from "@cloudflare/computer/tools/ai-sdk";
-import { Think } from "@cloudflare/think";
-
-export class WriterAgent extends Think {
- override workspaceBash = false;
+import { DurableObject } from "cloudflare:workers";
- override workspace = new Workspace({
+export class NotesAgent extends DurableObject {
+ workspace = new Workspace({
storage: this.ctx.storage,
backends: [new WorkerJavaScriptBackend({ loader: this.env.LOADER })],
- useThink: true,
- }) as Workspace & ThinkWorkspaceCompatibility;
-
- // getModel() and getSystemPrompt() as before.
-
- override getTools() {
- // The workspace now has a backend, so the tool set includes `exec`.
- return createAITools({ workspace: this.workspace });
- }
+ });
}
```
-The backend needs a [Worker Loader binding](/workers/runtime-apis/bindings/worker-loader/):
+In a Think agent, set the same `Workspace` as `override workspace`. Once the workspace has a backend, every tool adapter includes `exec`.
+
+The backend needs a [Worker Loader binding](/dynamic-workers/):
@@ -307,7 +392,7 @@ export default async function (input) {
}
```
-The module must have a default export. A default-exported function is called with the input of the run, and its return value becomes the result. Put file and module calls inside that function. The Workers runtime does not allow I/O while a module loads so accesing the filesystem at the module level will error.
+The module must have a default export. A default-exported function is called with the input of the run, and its return value becomes the result. Put file and module calls inside that function. The Workers runtime does not allow I/O while a module loads, so accessing the file system at the top level of the module fails the run.
### Run code from the agent
@@ -339,19 +424,20 @@ const { exitCode, value, stdout, stderr } = await run.result();
`WorkerJavaScriptBackend` accepts the following common options:
-| Option | Default | Description |
-| ------------------ | -------------- | ------------------------------------------------------------------------------------------------- |
-| `loader` | Required | The Worker Loader binding. |
-| `root` | `/workspace` | The directory that code can access. Paths outside it are rejected. |
-| `access` | `"read-write"` | Set to `"read"` to let code read files but not change them. |
-| `defaultTimeoutMs` | `60000` | How long a run can take when the caller does not set a timeout. |
-| `maxTimeoutMs` | `180000` | The longest timeout that a caller can request. |
-| `globalOutbound` | `null` | A `Fetcher` that receives every outbound request from the code. `null` blocks network access. |
-| `modules` | — | Extra modules that code can import, such as bundled libraries or functions that run in the agent. |
+| Option | Default | Description |
+| ------------------ | --------------------- | ------------------------------------------------------------------------------------------------- |
+| `loader` | Required | The Worker Loader binding. |
+| `id` | `"worker-javascript"` | The backend ID that `exec` and `runtime.exec()` use to select this backend. |
+| `root` | `/workspace` | The directory that code can access. Paths outside it are rejected. |
+| `access` | `"read-write"` | Set to `"read"` to let code read files but not change them. |
+| `defaultTimeoutMs` | `60000` | How long a run can take when the caller does not set a timeout. |
+| `maxTimeoutMs` | `180000` | The longest timeout that a caller can request. |
+| `globalOutbound` | `null` | A `Fetcher` that receives every outbound request from the code. `null` blocks network access. |
+| `modules` | — | Extra modules that code can import, such as bundled libraries or functions that run in the agent. |
### Give code access to your own functions
-Custom modules can be registered under the `ws:` namespace. Use these modules to provide the model access to your application logic, filesystem tools or perform external actions.
+Custom modules can be registered under the `ws:` namespace. Use these modules to give the model access to your application logic or file system tools, or to perform external actions.
@@ -380,7 +466,7 @@ export default async function ({ ids }) {
}
```
-Arguments come from model-written code, so validate them before use. Computer also includes prebuilt host modules for Git, [Artifacts](/artifacts/), and the agent's own tools. For more information, refer to the [isolate JavaScript runtime documentation](https://github.com/cloudflare/computer/blob/main/docs/17_isolate_javascript.md#modules).
+Always validate input to your module functions, because it comes directly from the model. Computer also includes prebuilt host modules for Git, [Artifacts](/artifacts/), and the agent's own tools. For more information, refer to [Host modules](#host-modules) in the API reference.
## Limits
@@ -396,6 +482,91 @@ Code that the JavaScript backend runs cannot read the environment variables, bin
Every user who talks to the same agent instance shares its workspace. Give each user their own agent instance when their files must stay apart.
+## API reference
+
+### `Workspace`
+
+`new Workspace(options)` from `@cloudflare/computer` creates a workspace. It accepts the following options:
+
+| Option | Description |
+| -------------------- | -------------------------------------------------------------------------------------------------------------- |
+| `storage` | Required. The storage of the Durable Object, `this.ctx.storage`. |
+| `backends` | Execution backends, such as `WorkerJavaScriptBackend`. The first backend is the default. |
+| `mounts` | Read-only content mounted at a path, from an R2 bucket (`R2Bucket()`) or the Worker bundle (`WorkerBundle()`). |
+| `git` | A Git client from `createGitClient()` in `@cloudflare/computer/git`, which turns on `workspace.git`. |
+| `defaultGitIdentity` | The `{ name, email }` that Git commits use. |
+| `artifacts` | An [Artifacts](/artifacts/) binding, as `{ binding }`. |
+| `output` | Where long command output is saved, and how much `result()` keeps. |
+
+### `workspace.fs`
+
+Every path is absolute. Every method returns a promise.
+
+| Method | Description |
+| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `readFile(path, encoding?)` | Reads a file. Returns a string with `"utf8"`, or a `ReadableStream` without an encoding. Accepts `{ byteOffset, byteLength }` to read part of a file. |
+| `writeFile(path, content, options?)` | Writes a string, `Uint8Array`, or `ReadableStream`. Set `options.mode` for permissions. |
+| `mkdir(path, options?)` | Creates a directory. Set `recursive: true` to create parents. |
+| `readdir(path)` | Lists a directory as entries with `name`, `parentPath`, `isFile`, and `isDirectory`. |
+| `stat(path)` | Returns `name`, `mode`, `mtime`, `size`, `isFile`, and `isDirectory`, following symbolic links. |
+| `lstat(path)` | Like `stat`, but reports a symbolic link itself, with `isSymbolicLink`. |
+| `rm(path, options?)` | Removes a file or directory. Set `recursive: true` for a non-empty directory, and `force: true` to ignore a missing path. |
+| `rename(oldPath, newPath)` | Moves a file, directory, or symbolic link in one transaction. |
+| `symlink(target, path)` | Creates a symbolic link at `path` that points to `target`. |
+| `readlink(path)` | Returns the target of a symbolic link. |
+| `chmod(path, mode)` | Changes the permission bits of a path. |
+| `find(directory, pattern?, options?)` | Finds paths that match a glob, relative to `directory`. Accepts `exclude`, `limit`, and `offset`. |
+| `ls(prefix)` | Lists every file at or under `prefix`. |
+| `grep(pattern, path, options?)` | Searches file contents. Accepts `regex`, `ignoreCase`, `context`, `include`, `exclude`, `limit`, and `offset`. |
+
+Failed operations reject with an error whose `code` is a POSIX error code, such as `ENOENT` for a missing path.
+
+### `workspace.runtime`
+
+| Method | Description |
+| --------------------------- | ---------------------------------------------------------------------------------------------------- |
+| `exec(source, options?)` | Runs `source` on a backend and returns a handle. Call `result()` on the handle for the final result. |
+| `getExec(id, options?)` | Reattaches to a run by ID. |
+| `killExec(id, options?)` | Stops a run. |
+| `disposeExec(id, options?)` | Discards a completed run. |
+| `backends()` | Lists the backends and the description that each gives to the model. |
+
+`exec()` accepts the following options:
+
+| Option | Description |
+| ----------- | ----------------------------------------------------------------------------- |
+| `backend` | The backend ID to run on. Defaults to the first backend. |
+| `input` | JSON-compatible data passed to the default export of a JavaScript module. |
+| `encoding` | Set to `"utf8"` to receive `stdout` and `stderr` as strings instead of bytes. |
+| `cwd` | The working directory, and the base for relative imports. |
+| `env` | Environment variables for this run, available to JavaScript as `process.env`. |
+| `stdin` | Standard input, as a string or `Uint8Array`. |
+| `timeoutMs` | How long the run can take, up to the backend's `maxTimeoutMs`. |
+
+The result contains `status` (`"completed"`, `"failed"`, or `"cancelled"`), `exitCode`, `stdout`, `stderr`, and `value`, the return value of a JavaScript module. When output is too long, `truncated` names the workspace file that holds all of it.
+
+Declare handles from `exec()` with `using` so they are disposed when they go out of scope.
+
+### Tool functions
+
+| Function | Entry point | Returns |
+| ----------------------- | ---------------------------------------- | ------------------------------------------------------- |
+| `createAITools()` | `@cloudflare/computer/tools/ai-sdk` | An AI SDK `ToolSet`. |
+| `createPiTools()` | `@cloudflare/computer/tools/pi-ai` | Pi AI tool declarations and an `execute` function. |
+| `createTanStackTools()` | `@cloudflare/computer/tools/tanstack-ai` | A list of [TanStack AI](https://tanstack.com/ai) tools. |
+
+### Host modules
+
+Pass these to the `modules` option of `WorkerJavaScriptBackend`:
+
+| Function | Entry point | Module | Description |
+| ------------------------- | ---------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| `createGitModule()` | `@cloudflare/computer/modules/git` | `ws:git` | Git operations on the workspace. Requires the `git` workspace option. Pass `{ allowNetwork: true }` to clone, fetch, and push. |
+| `createArtifactsModule()` | `@cloudflare/computer/modules/artifacts` | `ws:artifacts` | [Artifacts](/artifacts/) repositories. Requires the `artifacts` workspace option. |
+| `createToolBindings()` | `@cloudflare/computer/modules/tools` | `ws:tools` | The agent's own tools, callable from code. Build the bindings with `forAISDKTools()` or `forPiTools()`. |
+
+For the full interfaces, refer to the [Computer design documentation](https://github.com/cloudflare/computer/tree/main/docs).
+
## Related resources