Skip to content

think: Extract the legacy Shell workspace - #2073

Draft
aron-cf wants to merge 5 commits into
mainfrom
think-computer/01-legacy-workspace
Draft

think: Extract the legacy Shell workspace#2073
aron-cf wants to merge 5 commits into
mainfrom
think-computer/01-legacy-workspace

Conversation

@aron-cf

@aron-cf aron-cf commented Aug 7, 2026

Copy link
Copy Markdown

Currently Think uses a @cloudflare/shell + just-bash powered interface. We'd like to swap this out for a @cloudflare/computer powered interface.

This touches a lot of moving pieces:

  1. The just-bash based bash tool will be replaced by the @cloudflare/computer worker-shell backend.
  2. The codemode interface needs to use the workspace binding directly.
  3. The various integration points. that assume a filesystem need to be updated.
  4. Existing clients need to be able to maintain backward compatibility while new ones get the computer workspace.
  5. The two virtual filesystems use different schemas and approaches to storing large files.

There is also a bunch of stuff (tool arguments, media support & network config) that is on the @cloudflare/computer backlog that needs to be implemented so we have feature parity here.

See PR stack starting at cloudflare/computer#79 and cloudflare/computer#88

To begin this transition, this PR introduces a Think-owned workspace contract with separate filesystem and runtime surfaces. It moves the existing @cloudflare/shell storage, R2 support, snapshot-based Bash, and rich state.* connector into @cloudflare/think/workspace-shell-legacy. This legacy implementation remains Think's default in this pull request, so existing data and execution behavior do not change.

In a follow up PR we'll swap this out with a @cloudflare/computer backed workspace at which point it will be possible to swap out the workspace and have the legacy one tree-shaken out of the final build. Ultimately we should be able to then remove the @shell/just-bash backend.

The Assistant example selects the legacy workspace explicitly for its shared proxy filesystem. Agent Skills and the Think starters use createWorkspaceOperations() to run scripts against the new structural interface. These updates prepare consumers for alternative workspace implementations without introducing @cloudflare/computer.

A following pull request will add @cloudflare/computer and make its backend-free workspace the default. Existing legacy data will continue to require the explicit legacy workspace and will not be migrated automatically.


Open in Devin Review

@aron added 5 commits August 7, 2026 15:26
Move the existing storage, Bash, and codemode state behavior behind an explicit legacy workspace while adapting Think's file access to a narrow filesystem and runtime contract.
Keep the shared assistant filesystem on the legacy storage layout and expose its filesystem, runtime, and codemode state capabilities to child agents.
Wrap Think's workspace in the operations interface expected by skill scripts.
Pass skill runners the operations adapter for Think's workspace.
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd6c167

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

This PR includes changesets to release 2 packages
Name Type
@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

@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 3 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

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.

🔴 Newly scaffolded projects fail their own checks because the generated agent file is only half-migrated

The generated starter agent still hands the raw workspace to the skill script runner (workspaceInstance: this.workspace at packages/think/src/cli/init.ts:433) even though the new adapter is imported at the top of the same generated file, so every freshly scaffolded project ships with an unused import and a value the runner's interface does not accept.
Impact: Users who scaffold a new project get code that fails the project's own lint/typecheck (npm run check) out of the box.

Incomplete mechanical migration of the skills runner call

The PR migrated every other call site to createWorkspaceOperations(this.workspace) (examples/agent-skills/src/server.ts:26, think-starters/coding-agent/agents/coder/agent.ts:50, think-starters/customer-support/agents/support/agent.ts:51), but the CLI template at packages/think/src/cli/init.ts:411 only added the import while line 433 still passes this.workspace.

Think.workspace is now typed ThinkWorkspace (packages/think/src/think.ts:2880), which exposes only fs/runtime and does not structurally satisfy SkillWorkspace (readFile/writeFile/readDir/glob/stat, see packages/agents/src/skills/runner.ts:18-24). The generated project's typecheck script therefore fails, and oxlint flags the unused createWorkspaceOperations import (no-unused-vars is an error per AGENTS.md), so the generated check script fails.

(Refers to line 433)

Open in Devin Review

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

Comment on lines +194 to +204
const inferred = agent ? optionsFromAgent(agent) : undefined;
const options: CreateExecuteToolOptions = inferred
? {
...inferred,
...overrides,
connectors: [
...(inferred.connectors ?? []),
...(overrides?.connectors ?? [])
]
}
: { ...(source as CreateExecuteToolOptions), ...overrides };

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.

🟡 Supplying a custom sandbox filesystem to an agent's code-execution tool now breaks every code run

A caller-supplied sandbox filesystem is now added on top of the one the agent already derives from its workspace (connectors: [...inferred.connectors, ...] at packages/think/src/tools/execute.ts:194-204 combined with options.state at packages/think/src/tools/execute.ts:232-234), so two identically named sandbox namespaces are registered and the sandbox refuses to run any code.
Impact: Agents that pass their own filesystem backend to the code-execution tool see every execution fail with a duplicate-name error instead of running.

Additive merge replaces the previous override semantics

Previously optionsFromAgent(source) set state and { ...optionsFromAgent(source), ...overrides } meant an explicit overrides.state REPLACED the agent-derived one. Now the agent path derives connectors from workspace[workspaceStateProvider](ctx) (packages/think/src/tools/execute.ts:170-175) and overrides.state still pushes an extra StateConnector at packages/think/src/tools/execute.ts:232-234.

StateConnector.name() returns "state" (packages/shell/src/connector.ts:79-81), and the executor rejects duplicates: if (seenNames.has(connector.name)) return { error: 'Duplicate name "state" ...' } (packages/codemode/src/executor.ts:351-357). So createExecuteTool(this, { state: myBackend }) on a Think agent whose workspace provides a state connector fails on every execution.

A fix would be to drop the agent-derived state connectors when the caller supplies state (or their own state-named connector), preserving the old override semantics.

Prompt for agents
In packages/think/src/tools/execute.ts, createExecuteRuntime now concatenates connectors inferred from the agent's workspace state provider with overrides.connectors, while options.state still creates an additional StateConnector later in the function. Both produce a connector named "state", and packages/codemode/src/executor.ts rejects duplicate connector names, so every execution fails. Restore the previous override semantics: when the caller explicitly supplies `state` (or a connector that already occupies the `state` namespace) in the overrides, the agent-derived state connectors should not also be registered.
Open in Devin Review

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

Comment on lines +255 to +262
>;

type FileInfoSource = Awaited<ReturnType<ThinkWorkspace["fs"]["stat"]>>;

async function fileInfo(
workspace: ThinkWorkspace,
path: string,
hint?: FileInfoHint

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.

🟡 File search and text search now issue one extra metadata lookup per matched file

Every file returned by a search is re-queried individually for its metadata (workspace.fs.stat(path) at packages/think/src/tools/workspace.ts:260) after the search already returned the whole list, so a search across a large workspace fans out into hundreds or thousands of extra lookups.
Impact: The find and grep tools get much slower on large workspaces, and in the shared-workspace setup each extra lookup is a separate cross-object call.

Metadata dropped by the find adapter and then re-fetched per entry

LegacyShellFilesystem.find maps glob results down to { path, type } (packages/think/src/workspace-shell-legacy.ts:140-149), discarding size/mimeType/timestamps that the underlying single SQL glob already returned (packages/shell/src/filesystem.ts:1070). workspaceFindOps then calls fileInfo(...) for every entry (packages/think/src/tools/workspace.ts:178-194), and fileInfo performs workspace.fs.stat(path) — one additional query per result. createGrepTool and createFindTool both go through this path, and createWorkspaceOperations().glob inherits it.

The same shape appears in _hostListFiles (packages/think/src/think.ts:7090-7108), which now stats each directory entry; it will also reject the whole listing if any single stat throws.

A fix is to let the filesystem find/readdir surface carry the metadata it already has (or to have the legacy adapter return richer entries) so the tools don't need a per-entry stat.

Open in Devin Review

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

@aron-cf
aron-cf marked this pull request as draft August 7, 2026 15:48
@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

create-think

npm i https://pkg.pr.new/create-think@2073

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: fd6c167

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