Skip to content

computer: Carry backend information through Workspace clients - #182

Merged
mattzcarey merged 3 commits into
feat/ws-container-modulefrom
fix/exec-tool-review
Oct 1, 2026
Merged

mattzcarey merged 3 commits into
feat/ws-container-modulefrom
fix/exec-tool-review

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Stacked on #172, after #181 merged into it.

This fixes the issues review found in #181.

A WorkspaceClient from getWorkspace() wrapped the runtime with exec, getExec, killExec, and disposeExec only. The exec tool also asks the runtime for backendIds, isCallable, and describe, so a client lost all three. With exec left out, an agent got no exec tool, and a callable backend such as celld's lost its input argument and its module list:

A Durable Object that builds its own Workspace passes it straight to createAITools and was never affected. The bug hit the two cases that go through getWorkspace():

// 1. Inside a Durable Object using the withWorkspace mixin, which keeps
//    the Workspace private, as in examples/celld:
const workspace = await getWorkspace(this);
createAITools({ workspace }); // no exec tool, despite backends

// 2. From another Worker or Durable Object, over RPC, as in examples/egress:
const workspace = await getWorkspace(env.MyDO.get(id));
createAITools({ workspace }); // same

// Unaffected: a Durable Object that owns its Workspace, as in examples/think.
createAITools({ workspace: this.workspace });

The tool needs one list of facts per backend, so this PR also collapses those three methods into one:

runtime.backends(): { id: string; callable: boolean; description?: string }[]

The Workspace runtime, its RPC stub, the client, and the tool all speak that one method. backendIds and describe are gone. isCallable stays on the runtime only for its own check before running.

Backends are fixed when the Workspace is constructed, so a client takes one backends() snapshot when getWorkspace() creates it:

sequenceDiagram
  participant Agent
  participant Client as WorkspaceClient
  participant Stub as WorkspaceRuntimeStub (RPC)
  Agent->>Client: getWorkspace(handle)
  Client->>Stub: backends()
  Stub-->>Client: [{ id, callable, description }]
  Agent->>Client: createAITools({ workspace })
  Client-->>Agent: backends(), answered from the snapshot
Loading

The snapshot keeps createAITools synchronous over RPC, where the call would otherwise be a round trip.

Three smaller fixes:

  • CloudflareContainerBackend claimed network access even with the default egress: { mode: "none" }. Its description now follows the egress mode: direct, through a gateway, or none.
  • exec now takes precedence over the deprecated shell option, so exec: {} always means no exec tool.
  • The mcp README described backend as optional, and the think prompt named a default backend. Both now say to name a backend on every call.

client.test.ts builds tools from both a local and a remote client against a real WorkerJavaScriptBackend, checks the backend answers and the module list, and sends structured input through each client's exec tool to a callable backend that echoes it. The snapshot is frozen, so editing the list a client returns cannot change later answers. The other fixes each have a focused test.

A WorkspaceClient from getWorkspace() wrapped the runtime with exec,
getExec, killExec, and disposeExec only. The exec tool also asks the
runtime for backendIds, isCallable, and describe, so a client lost all
three: with exec omitted it offered no exec tool, and a callable
backend lost its input argument and its module list. Over RPC those
calls would be asynchronous, while the tool builds its schema
synchronously.

Backends are fixed when the Workspace is constructed, so the runtime
and its RPC stub now expose one backends() call, and a client takes
that snapshot when it is created and answers the three questions from
it, locally and remotely alike.

CloudflareContainerBackend now describes network access from its
egress setting instead of always claiming it, exec takes precedence
over the deprecated shell option so exec: {} always means no tool, and
the mcp README and think prompt stop implying a default backend.
@mattzcarey mattzcarey added the allow-pr Allow a PR to remain open. label Oct 1, 2026
@changeset-bot

changeset-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

馃 Changeset detected

Latest commit: 6511ce7

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

This PR includes changesets to release 4 packages
Name Type
@cloudflare/computer Minor
@cloudflare/dofs Minor
@cloudflare/computer-rpc Minor
@cloudflare/computerd Minor

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[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@cloudflare/computer@182

commit: 3de7511

The exec tool learned about backends through three runtime methods,
backendIds, isCallable, and describe, and every way of reaching a
Workspace had to forward all three. It now reads one list from
runtime.backends(), each entry carrying the id, whether the backend is
callable, and its description. backendIds and describe are gone, and
a Workspace client exposes the same backends() from its snapshot.
isCallable stays on the runtime for its own check before running.
devin-ai-integration[bot]

This comment was marked as resolved.

A client returned its backend snapshot array itself, so a caller that
edited it changed what later tool sets saw. The snapshot is now frozen
once when the client is created.

The client tests only checked that the exec schema offered input. They
now send structured input through the exec tool on a local and a
remote client to a callable backend that echoes it, and check the
value comes back as the result. The mcp README no longer calls
worker-shell the default.
@mattzcarey
mattzcarey merged commit 1adc1e6 into feat/ws-container-module Oct 1, 2026
13 of 14 checks passed
@mattzcarey
mattzcarey deleted the fix/exec-tool-review branch October 1, 2026 11:02
mattzcarey added a commit that referenced this pull request Oct 1, 2026
* computer: Carry backend information through Workspace clients

A WorkspaceClient from getWorkspace() wrapped the runtime with exec,
getExec, killExec, and disposeExec only. The exec tool also asks the
runtime for backendIds, isCallable, and describe, so a client lost all
three: with exec omitted it offered no exec tool, and a callable
backend lost its input argument and its module list. Over RPC those
calls would be asynchronous, while the tool builds its schema
synchronously.

Backends are fixed when the Workspace is constructed, so the runtime
and its RPC stub now expose one backends() call, and a client takes
that snapshot when it is created and answers the three questions from
it, locally and remotely alike.

CloudflareContainerBackend now describes network access from its
egress setting instead of always claiming it, exec takes precedence
over the deprecated shell option so exec: {} always means no tool, and
the mcp README and think prompt stop implying a default backend.

* computer: Answer backend questions with one backends() call

The exec tool learned about backends through three runtime methods,
backendIds, isCallable, and describe, and every way of reaching a
Workspace had to forward all three. It now reads one list from
runtime.backends(), each entry carrying the id, whether the backend is
callable, and its description. backendIds and describe are gone, and
a Workspace client exposes the same backends() from its snapshot.
isCallable stays on the runtime for its own check before running.

* computer: Freeze the client backend snapshot and test input through it

A client returned its backend snapshot array itself, so a caller that
edited it changed what later tool sets saw. The snapshot is now frozen
once when the client is created.

The client tests only checked that the exec schema offered input. They
now send structured input through the exec tool on a local and a
remote client to a callable backend that echoes it, and check the
value comes back as the result. The mcp README no longer calls
worker-shell the default.
mattzcarey added a commit that referenced this pull request Oct 1, 2026
Split each tool into a framework-neutral core under tools/common
(schema, description, executor; zod only) and an adapter per agent
library. tools/ai-sdk wraps the core with `tool()` from `ai`;
tools/pi-ai and tools/tanstack-ai build their own shapes from the
same core without importing their libraries, so each entry point
pulls in only what it uses.

The exec core is the one #181 and #182 settled on: `exec` takes a
backend map, every Workspace backend is offered by default, `backend`
appears only when there is a choice, and `input` only when a backend
is callable. All three tool sets resolve options through the same
resolveToolOptions, so they offer the same tools.

The pi and TanStack adapters come from #149.

Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

allow-pr Allow a PR to remain open.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant