Skip to content

think: make createSandboxTools actually work #1319

Description

@threepointone

Parent roadmap: #1439

Summary

@cloudflare/think/tools/sandbox currently exports createSandboxTools(), but the implementation is intentionally a no-op: it logs a warning once and returns {}.

That means the blog/docs-level execution ladder says Think can wire in Cloudflare Sandbox as Tier 4, but the package export does not yet provide any usable tools.

This issue tracks turning createSandboxTools() into a real integration with the Cloudflare Sandbox SDK, or narrowing/removing the public promise until the integration exists.

Current behavior

Current implementation in packages/think/src/tools/sandbox.ts:

export function createSandboxTools(
  _binding?: unknown,
  _options?: CreateSandboxToolsOptions
): ToolSet {
  if (!warned) {
    warned = true;
    console.warn(
      "[@cloudflare/think] createSandboxTools is not yet implemented. " +
        "No tools will be registered."
    );
  }

  return {};
}

The export exists in packages/think/package.json as @cloudflare/think/tools/sandbox, so users can import and spread it, but they get no tools.

Why this matters

Project Think's execution ladder is meant to be additive:

  • Tier 0: Think Workspace / @cloudflare/shell
  • Tier 1: Dynamic Worker / codemode execution
  • Tier 2: npm/runtime dependency loading
  • Tier 3: browser automation
  • Tier 4: full Sandbox for OS-level toolchains, repos, test runners, compilers, and long-running processes

Tier 4 is the escape hatch for tasks that cannot fit in a Workers isolate: git clone, npm test, cargo build, real CLIs, shell scripts, code review bots, and heavyweight project work.

Right now Think has a named Sandbox tool export, but no actual Sandbox bridge. That creates a confusing gap for users and for the roadmap.

Sandbox SDK capabilities to target

The Sandbox SDK already exposes useful primitives:

  • getSandbox(env.Sandbox, id) to create/reuse a sandbox instance
  • exec() for command execution with complete stdout/stderr/exit code
  • execStream() for long-running commands with streamed output
  • startProcess() / process log streaming for services
  • file operations: readFile, writeFile, mkdir, exists, delete/move/rename
  • code interpreter contexts for Python/JavaScript/TypeScript
  • preview URLs / port exposure for web servers
  • terminal and WebSocket connections
  • request-proxy patterns so real credentials stay in the Worker, not inside sandbox code

For Think v1, we should probably start with a small subset instead of exposing the whole SDK.

Proposed first milestone

Implement a minimal createSandboxTools(binding, options) that exposes a conservative tool set for Think agents:

  1. sandbox_exec

    • Run a shell command in a named sandbox.
    • Inputs: command, optional sandboxId, optional cwd, optional timeout.
    • Output: stdout, stderr, exitCode, success, maybe truncated output metadata.
  2. sandbox_write_file

    • Write a file into /workspace or another allowlisted path.
    • Inputs: path, content, optional encoding.
  3. sandbox_read_file

    • Read a file back from the sandbox.
    • Inputs: path, optional encoding / max bytes.
  4. Optional later: sandbox_exec_stream

    • Stream long-running output into Think chunks or a retained run/event model.
    • This is probably not required for the first PR unless the API shape is obvious.
  5. Optional later: sandbox_expose_port / preview URL helpers.

Keep the first implementation boring and testable: command execution + file transfer.

Workspace sync question

The important design question is how Think's DO-local Workspace interacts with the Sandbox filesystem.

Possible approaches:

A. Explicit file transfer tools

The model calls read / find / grep against Think Workspace, then calls sandbox_write_file for files it wants in the Sandbox. After commands run, it calls sandbox_read_file to pull results back.

Pros: simple, no hidden sync, minimal API.
Cons: tedious for whole repos / many files.

B. Helper methods for push/pull workspace snapshots

Expose tools like:

  • sandbox_push_workspace({ paths, sandboxPath })
  • sandbox_pull_workspace({ sandboxPath, paths })

Pros: better UX for project work.
Cons: needs careful path filtering, binary handling, limits, and conflict behavior.

C. Git/Artifacts handoff

Use Git/Artifacts as the handoff boundary: Think commits workspace state to an Artifacts repo, Sandbox clones/mounts it, pushes results, and Think imports/reviews the diff.

Pros: clean versioned boundary, aligns with #1440.
Cons: larger integration; should not block the minimal createSandboxTools() milestone.

Recommendation: start with A, design B after using it, and keep C linked to #1440.

API shape questions

  • Should createSandboxTools() accept a Sandbox Durable Object namespace directly, or a factory function like (id) => getSandbox(binding, id)?
  • How should the sandbox id be chosen by default: Think agent name, chat/session id, caller-provided id, or a generated per-run id?
  • Should tools reuse a long-lived sandbox per Think agent/session, or create per-run sandboxes?
  • How should timeouts work, given Sandbox command timeout behavior does not necessarily kill the underlying process?
  • Should path access be restricted to /workspace by default?
  • Should network/proxy setup be part of createSandboxTools() or left to application code?
  • How do we surface preview URLs / running background processes without turning v1 into a full IDE API?

Security constraints

  • Do not pass durable secrets directly into sandbox commands by default.
  • Prefer Worker-side proxy patterns for external APIs so the sandbox gets short-lived scoped credentials, not real API keys.
  • Require explicit opt-in for network credentials, repo write tokens, or access to user data.
  • Default path access should be scoped, probably to /workspace.
  • Tool outputs must be truncated/structured to avoid blowing up model context.
  • Commands should have explicit timeouts and clear behavior for non-zero exit codes.
  • Avoid logging secrets in command strings, outputs, or chat history.

Relationship to existing Think tools

  • createExecuteTool() remains the default for small JavaScript programs in Dynamic Worker isolates.
  • createSandboxTools() is for full OS/toolchain work that cannot run in codemode: real shell commands, package managers, compilers, tests, CLI agents, and services.
  • Browser tools remain separate; Sandbox should not replace createBrowserTools().
  • Artifacts integration (Think + Artifacts: versioned workspaces, forks, and handoff #1440) may become the preferred handoff mechanism for large repo workflows, but this issue should make the Sandbox export useful on its own first.

Acceptance criteria

  • createSandboxTools() returns at least one real AI SDK tool instead of {} when passed a valid Sandbox binding/factory.
  • The minimal tools can run a command in a sandbox and return structured result data.
  • The minimal tools can write a file into the sandbox and read it back.
  • Tool outputs are bounded/truncated and include enough metadata for the model to continue.
  • Timeouts and non-zero exit codes have documented behavior.
  • The implementation has worker tests or a focused example covering command execution and file round trip.
  • Docs explain when to use createExecuteTool() versus createSandboxTools().
  • Docs show the required Wrangler binding/configuration.
  • The no-op warning is removed, or retained only when the function is called without a usable binding.

Nice-to-have follow-ups

  • Streaming command output into Think events.
  • Background process and preview URL tools.
  • Workspace push/pull helpers.
  • Artifacts-backed clone/push handoff with Think + Artifacts: versioned workspaces, forks, and handoff #1440.
  • Sandbox request proxy helpers for GitHub, package registries, model providers, or R2.
  • A Think example that runs tests/builds in Sandbox and reports results back into chat.

Useful files

  • packages/think/src/tools/sandbox.ts
  • packages/think/src/tools/execute.ts
  • docs/think/tools.md
  • examples/assistant/src/server.ts
  • experimental/gadgets-sandbox/README.md

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingon the roadmapFeature accepted and planned for implementation

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions