Skip to content

think: work in a @cloudflare/computer Workspace - #2553

Closed
mattzcarey wants to merge 4 commits into
docs/shell-to-computer-migrationfrom
think-computer-workspace
Closed

mattzcarey wants to merge 4 commits into
docs/shell-to-computer-migrationfrom
think-computer-workspace

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Think's workspace is now a Workspace from @cloudflare/computer. By default Think creates one on the agent's own storage, with no execution backends. Agent Skills accept the same shape, and a new lazyWorkspace() covers a Workspace another Durable Object owns.

Shell is being deprecated in favour of Computer (#2552):

  • Think's internals now go through one adapter over workspace.fs: media eviction, skills projection, fetch spill and the extension host functions. The writeFileBytes checks are gone, so media eviction and skills projection work with every workspace.
  • Think writes its own files under /workspace, Computer's working root.
  • @cloudflare/computer is a new peer dependency. Workspace and WorkspaceLike are no longer exported, and createExecuteTool(this) no longer builds state.* from the workspace.
  • agent-think and examples/assistant move to lazyWorkspace(() => getWorkspace(parent)). The assistant's file browsers now refresh after each turn instead of on every write, because Computer has no change events.

Tests ran: think workers suite, agents skills and skill runner, the assistant example, and the pi harness. Repo typecheck is clean, apart from the voice examples, whose packages weren't built locally.


Devin Review

@changeset-bot

changeset-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5f88352

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
agents Minor
@cloudflare/think Minor
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@agent-think

agent-think Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

✅ agents import sizes: no significant changes (2afb115b → 5f883521, workflow run)

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 7 potential issues.

Devin Review

Comment thread docs/think/index.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Package reference still advertises Shell workspace

The exports and dependency tables still name Think's removed Workspace export and bundled Shell filesystem. Update them to describe the Computer peer dependency.

(Refers to this code)

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread agent-think/src/agent.ts
Comment on lines +198 to +204
this.workspace = lazyWorkspace(
async () =>
// SAFETY: The remote Workspace is Computer's Workspace behind an RPC
// stub. Its published types come from @cloudflare/workspace, which
// predates the rename, so TypeScript cannot relate the two.
(await this.#getWorkspace()) as unknown as ThinkWorkspace
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Later turns reuse a disposed workspace

After a turn disposes its workspace stub, lazyWorkspace keeps forwarding Think operations to that stub. Subsequent turns cannot reliably access skills or workspace files.

Learn more

The remote workspace client is a Workers RPC stub. #cleanupTurn disposes it after each turn, while #getWorkspace creates a new stub for the next turn. The resolver in lazyWorkspace runs only once, so its cached workspace remains the disposed stub. A failed #getWorkspace() promise is also cached permanently in #workspaceReady.

Example: The first issue comment completes and disposes client A. A second mention starts in the same ThinkAgent isolate; Think attempts to read a projected skill through client A rather than the newly available client B.

Recommended fix: Keep Think's lazy workspace in sync with stub disposal: reset or replace its resolver cache when disposing a stub, and clear rejected #workspaceReady promises so later turns can retry.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

bytes,
mediaType ?? "application/octet-stream"
);
await files.write(path, bytes);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Evicted images lose readable media types

When files.write stores an AVIF attachment, it discards mediaType. The read tool cannot recognize AVIF bytes, so the model receives an unsupported-binary result instead of the image.

Learn more

Eviction replaces aged media with a file path so the model can reopen the image later. Previously the write supplied the original media type. WorkspaceFiles.write accepts only bytes and never records that type. detectWorkspaceMediaType then sniffs a short list of signatures before returning application/octet-stream; sniffMediaType does not recognize AVIF.

Example: An aged image/avif file part is evicted to an .avif workspace file. Reading that path yields kind: "binary", unsupported: true instead of image content.

Recommended fix: Preserve MIME metadata when writing evicted media, or add reliable media-type detection for supported input formats and honor the extension/original type in the read tool.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

}
const directory = `/${segments.slice(0, firstGlob).join("/")}`;
const relative = segments.slice(firstGlob).join("/");
const matches = await this.#fs.find(directory, relative);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing directories abort glob searches

For a pattern under a missing directory, WorkspaceFiles.glob lets fs.find reject with ENOENT. The built-in find and grep tools fail instead of returning empty results.

Learn more

The first non-glob segments become the directory supplied to fs.find. glob handles missing literal paths with stat, but does not handle missing directories before find. Both createFindTool and createGrepTool call glob without catching that failure.

Example: On a workspace with no /workspace/src, a find for src/**/*.ts raises a missing-directory error instead of producing files: [].

Recommended fix: Treat ENOENT from fs.find as an empty match list, while propagating other errors.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +329 to +336
// The turn may have written files. Let every open tab refresh its file
// browser. Best-effort, like the preview below.
try {
const directory = await this.parentAgent(AssistantDirectory);
await directory.workspaceChanged();
} catch (err) {
console.warn("[MyAssistant] Failed to announce workspace change:", err);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Shared file browsers stay stale during turns

When a chat writes files before its turn completes, workspaceChanged waits for onChatResponse. Other open file browsers keep showing stale contents throughout long or paused turns.

Learn more

The file-browser client refreshes only when it receives a workspace-change frame from useChats. Previously the shared workspace sent one on each mutation; now the only notification occurs after the full assistant response. A long-running tool or approval pause can leave a completed write invisible to other chats until the turn finishes.

Example: Chat A writes /notes.txt and then waits for approval on an announcement. Chat B's open file panel still shows no note while the approval is pending.

Recommended fix: Broadcast after workspace-mutating tool results (and other file mutations), or offer an explicit refresh/polling mechanism for the file browser while turns are in progress.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +377 to 379
* // cdp.* if env.BROWSER is bound,
* // executor from env.LOADER
* execute: createExecuteTool(this)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 One-line execute example needs a connector

The documented createExecuteTool(this) example now throws without a browser binding or explicit connector. Supply a tool or state backend in the example.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +5 to +16
Think's `workspace` is now a `Workspace` from `@cloudflare/computer`. By default Think creates one on the agent's own storage, with no execution backends. To add backends, mounts, or git, override `workspace` with `new Workspace({ storage: this.ctx.storage, ... })`. For a Workspace another Durable Object owns, use `lazyWorkspace(async () => getWorkspace(stub))`.

Breaking changes:

- `@cloudflare/computer` is a new peer dependency. Install it alongside `@cloudflare/think`.
- `Workspace` is no longer re-exported. Import it from `@cloudflare/computer`.
- `WorkspaceLike` is replaced by `ThinkWorkspace`, which needs only `workspace.fs`.
- Think writes its own files under `/workspace`. Evicted media goes to `/workspace/attachments/evicted/`, and the fetch tool saves responses to `/workspace/fetched/`.
- `createExecuteTool(this)` no longer builds `state.*` from the agent's workspace. Pass `state` explicitly.
- Media eviction and skill projection now work with every workspace. The `writeFileBytes` checks and their warnings are gone.

Files a `@cloudflare/shell` Workspace stored are not moved. See [Migrating to `@cloudflare/computer`](https://github.com/cloudflare/agents/blob/main/docs/shell/migrating-to-computer.md).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Changeset description exceeds repository format

Repository guidance requires a one-line changeset description linking to documentation. This multi-paragraph migration list breaks that convention.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new

pkg-pr-new Bot commented Oct 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2553

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2553

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2553

hono-agents

npm i https://pkg.pr.new/hono-agents@2553

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2553

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2553

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2553

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2553

commit: 5f88352

@mattzcarey
mattzcarey deleted the branch docs/shell-to-computer-migration October 12, 2026 11:48
@mattzcarey

Copy link
Copy Markdown
Member Author

Replaced by a cleaner stack, #2559 to #2565.

@mattzcarey mattzcarey closed this Oct 12, 2026
@mattzcarey
mattzcarey deleted the think-computer-workspace branch October 12, 2026 11:48
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.

1 participant