Skip to content

Commit 37b2ce3

Browse files
feat(think): built-in workspace for every Think agent (#1275)
Every Think instance now gets `this.workspace` — a Workspace backed by the DO's SQLite storage, with no R2 required. Workspace tools (read, write, edit, list, find, grep, delete) are automatically merged into every `onChatMessage` call before `getTools()`. Changes: - think.ts: add `workspace` field (auto-initialized from DO SQLite), import Workspace + createWorkspaceTools, merge workspace tools first in onChatMessage tool spread, re-export Workspace from main entry - package.json: @cloudflare/shell is now a required peer dependency (removed from peerDependenciesMeta.optional) - e2e worker: simplified TestAssistant — removed manual getTools() override and direct Workspace/createWorkspaceTools imports, kept R2 override as the canonical pattern for large file spillover - examples/assistant: removed manual Workspace + createWorkspaceTools wiring, removed @cloudflare/shell direct dependency - README.md: added built-in workspace section, updated exports/peer deps tables, added configureSession/onChatResponse to override points, added session + skills documentation - design/think.md: added built-in workspace subsection, updated exports table descriptions Made-with: Cursor
1 parent 5380223 commit 37b2ce3

9 files changed

Lines changed: 138 additions & 68 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/think": patch
3+
---
4+
5+
Add built-in workspace to Think. Every Think instance now has `this.workspace` backed by the DO's SQLite storage, and workspace tools (read, write, edit, list, find, grep, delete) are automatically merged into every chat turn. Override `workspace` to add R2 spillover for large files. `@cloudflare/shell` is now a required peer dependency.

design/think.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,17 @@ Think inherits `runFiber()` from the `Agent` base class. Fiber state is persiste
319319

320320
## Tools
321321

322-
Think provides factory functions for common tool patterns, published as separate export paths.
322+
Think provides a built-in workspace and factory functions for additional tool patterns.
323+
324+
### Built-in workspace
325+
326+
Every Think instance gets `this.workspace` — a `Workspace` (from `@cloudflare/shell`) backed by the DO's SQLite storage. Workspace tools (`read`, `write`, `edit`, `list`, `find`, `grep`, `delete`) are automatically merged into every `onChatMessage` call, before `getTools()`.
327+
328+
Override to add R2 spillover: `override workspace = new Workspace({ sql: this.ctx.storage.sql, r2: this.env.R2, name: () => this.name })`.
323329

324330
### Workspace tools (`@cloudflare/think/tools/workspace`)
325331

326-
Seven file operation tools backed by abstract operation interfaces (`ReadOperations`, `WriteOperations`, etc.). A convenience function creates all tools from a `Workspace` instance, but you can also create tools against custom storage backends.
332+
The individual tool factories are also exported for custom storage backends. Seven file operation tools backed by abstract operation interfaces (`ReadOperations`, `WriteOperations`, etc.).
327333

328334
| Tool | Description | Operations interface |
329335
| ---------------- | ------------------------------------------------- | -------------------- |
@@ -464,13 +470,13 @@ Tests in `packages/think/src/tests/`, running inside the Workers runtime via `@c
464470

465471
## Package exports
466472

467-
| Import path | Source | Purpose |
468-
| ------------------------------------ | ------------------------- | --------------------------------------- |
469-
| `@cloudflare/think` | `src/think.ts` | Think base class, StreamCallback, types |
470-
| `@cloudflare/think/extensions` | `src/extensions/index.ts` | ExtensionManager, HostBridgeLoopback |
471-
| `@cloudflare/think/tools/workspace` | `src/tools/workspace.ts` | File operation tools (7 tools) |
472-
| `@cloudflare/think/tools/execute` | `src/tools/execute.ts` | Sandboxed code execution tool |
473-
| `@cloudflare/think/tools/extensions` | `src/tools/extensions.ts` | Extension management AI tools |
473+
| Import path | Source | Purpose |
474+
| ------------------------------------ | ------------------------- | ------------------------------------------------------ |
475+
| `@cloudflare/think` | `src/think.ts` | Think base class, Session, Workspace re-exports, types |
476+
| `@cloudflare/think/extensions` | `src/extensions/index.ts` | ExtensionManager, HostBridgeLoopback |
477+
| `@cloudflare/think/tools/workspace` | `src/tools/workspace.ts` | File operation tool factories (for custom backends) |
478+
| `@cloudflare/think/tools/execute` | `src/tools/execute.ts` | Sandboxed code execution tool |
479+
| `@cloudflare/think/tools/extensions` | `src/tools/extensions.ts` | Extension management AI tools |
474480

475481
## History
476482

examples/assistant/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ A chat agent built with `@cloudflare/think` — the opinionated chat agent base
44

55
## What this demonstrates
66

7-
- **Think overrides**`getModel()`, `getSystemPrompt()`, `getTools()` for a batteries-included agent
8-
- **Workspace tools** — read, write, edit, find, grep files in the DO's SQLite filesystem
7+
- **Think overrides**`getModel()`, `configureSession()`, `getTools()` for a batteries-included agent
8+
- **Built-in workspace**every Think agent gets `this.workspace` with file tools (read, write, edit, find, grep, delete) auto-wired
99
- **Server-side tools**`getWeather` executes on the server automatically
1010
- **Client-side tools**`getUserTimezone` runs in the browser via `onToolCall`
1111
- **Tool approval**`calculate` requires user approval for large numbers
@@ -22,17 +22,18 @@ npm start
2222

2323
## Key code
2424

25-
**Server** (`src/server.ts`)~150 lines:
25+
**Server** (`src/server.ts`):
2626

2727
```typescript
2828
export class MyAssistant extends Think<Env> {
29-
workspace = new Workspace({ sql: this.ctx.storage.sql, name: () => this.name });
3029
waitForMcpConnections = true;
3130

3231
getModel() { return createWorkersAI({ binding: this.env.AI })("@cf/moonshotai/kimi-k2.5"); }
33-
getSystemPrompt() { return "You are a helpful assistant..."; }
34-
getTools() { return { ...createWorkspaceTools(this.workspace), ...this.mcp.getAITools(), ... }; }
32+
configureSession(session) { return session.withContext("memory", { ... }).withCachedPrompt(); }
33+
getTools() { return { ...this.mcp.getAITools(), getWeather: tool({ ... }), ... }; }
3534
}
3635
```
3736

37+
Workspace tools are included automatically — no manual wiring needed.
38+
3839
**Client** (`src/client.tsx`) — uses `useAgentChat` from `@cloudflare/ai-chat/react`, which works with both Think and AIChatAgent out of the box.

examples/assistant/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"dependencies": {
1313
"@cloudflare/ai-chat": "*",
1414
"@cloudflare/kumo": "^1.17.0",
15-
"@cloudflare/shell": "*",
1615
"@cloudflare/think": "*",
1716
"@phosphor-icons/react": "^2.1.10",
1817
"agents": "*",

examples/assistant/src/server.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,19 @@
11
/**
2-
* Assistant — a Think-based chat agent with workspace tools and MCP.
2+
* Assistant — a Think-based chat agent with MCP and custom tools.
33
*
4-
* Demonstrates Think's core features:
5-
* - getModel() — Workers AI with session affinity
6-
* - configureSession() — persistent memory via context blocks
7-
* - getTools() — workspace tools + MCP tools + custom tools
8-
* - waitForMcpConnections — MCP integration
9-
* - Client-side tools — getUserTimezone (no execute, handled by onToolCall)
10-
* - Tool approval — calculate (needsApproval for large numbers)
11-
* - Workspace — file read/write/edit via @cloudflare/shell
4+
* Think provides workspace tools (read, write, edit, find, grep, delete)
5+
* out of the box. This agent adds MCP integration, client-side tools,
6+
* and tool approval on top.
127
*/
138

149
import { createWorkersAI } from "workers-ai-provider";
1510
import { routeAgentRequest, callable } from "agents";
1611
import { Think, Session } from "@cloudflare/think";
17-
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
18-
import { Workspace } from "@cloudflare/shell";
1912
import { tool } from "ai";
2013
import type { LanguageModel, ToolSet } from "ai";
2114
import { z } from "zod";
2215

2316
export class MyAssistant extends Think<Env> {
24-
workspace = new Workspace({
25-
sql: this.ctx.storage.sql,
26-
name: () => this.name
27-
});
28-
2917
waitForMcpConnections = { timeout: 5000 };
3018

3119
getModel(): LanguageModel {
@@ -65,7 +53,6 @@ Always respond concisely.`
6553
const mcpTools = this.mcp.getAITools();
6654

6755
return {
68-
...createWorkspaceTools(this.workspace),
6956
...mcpTools,
7057

7158
getWeather: tool({

packages/think/README.md

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,38 @@ export class MyAgent extends Think<Env> {
2525
}
2626
```
2727

28-
That's it. Think handles the WebSocket chat protocol, message persistence, the agentic loop, message sanitization, stream resumption, and client tool support. Connect from the browser with `useAgentChat` from `@cloudflare/ai-chat`.
28+
That's it. Think handles the WebSocket chat protocol, message persistence, the agentic loop, message sanitization, stream resumption, client tool support, and workspace file tools. Connect from the browser with `useAgentChat` from `@cloudflare/ai-chat`.
29+
30+
## Built-in workspace
31+
32+
Every Think agent gets `this.workspace` — a virtual filesystem backed by the DO's SQLite storage. Workspace tools (`read`, `write`, `edit`, `list`, `find`, `grep`, `delete`) are automatically available to the model.
33+
34+
```ts
35+
export class MyAgent extends Think<Env> {
36+
getModel() { ... }
37+
// this.workspace is ready to use — no setup needed
38+
// workspace tools are auto-merged into every chat turn
39+
}
40+
```
41+
42+
Override to add R2 spillover for large files:
43+
44+
```ts
45+
export class MyAgent extends Think<Env> {
46+
override workspace = new Workspace({
47+
sql: this.ctx.storage.sql,
48+
r2: this.env.R2,
49+
name: () => this.name
50+
});
51+
}
52+
```
2953

3054
## Exports
3155

3256
| Export | Description |
3357
| ------------------------------------ | ------------------------------------------------------------- |
34-
| `@cloudflare/think` | `Think` — the main class, plus types |
35-
| `@cloudflare/think/tools/workspace` | `createWorkspaceTools()`file operation tools |
58+
| `@cloudflare/think` | `Think`, `Session`, `Workspace`main class + re-exports |
59+
| `@cloudflare/think/tools/workspace` | `createWorkspaceTools()`for custom storage backends |
3660
| `@cloudflare/think/tools/execute` | `createExecuteTool()` — sandboxed code execution via codemode |
3761
| `@cloudflare/think/tools/extensions` | `createExtensionTools()` — LLM-driven extension loading |
3862
| `@cloudflare/think/extensions` | `ExtensionManager`, `HostBridgeLoopback` — extension runtime |
@@ -41,15 +65,17 @@ That's it. Think handles the WebSocket chat protocol, message persistence, the a
4165

4266
### Override points
4367

44-
| Method | Default | Description |
45-
| ------------------------- | -------------------------------- | ------------------------------------- |
46-
| `getModel()` | throws | Return the `LanguageModel` to use |
47-
| `getSystemPrompt()` | `"You are a helpful assistant."` | System prompt |
48-
| `getTools()` | `{}` | AI SDK `ToolSet` for the agentic loop |
49-
| `getMaxSteps()` | `10` | Max tool-call rounds per turn |
50-
| `assembleContext()` | prune older tool calls | Customize what's sent to the LLM |
51-
| `onChatMessage(options?)` | `streamText(...)` | Full control over inference |
52-
| `onChatError(error)` | passthrough | Customize error handling |
68+
| Method | Default | Description |
69+
| ------------------------- | -------------------------------- | ----------------------------------------------- |
70+
| `getModel()` | throws | Return the `LanguageModel` to use |
71+
| `getSystemPrompt()` | `"You are a helpful assistant."` | System prompt (fallback when no context blocks) |
72+
| `getTools()` | `{}` | AI SDK `ToolSet` for the agentic loop |
73+
| `getMaxSteps()` | `10` | Max tool-call rounds per turn |
74+
| `configureSession()` | identity | Add context blocks, compaction, search, skills |
75+
| `assembleContext()` | prune older tool calls | Customize what's sent to the LLM |
76+
| `onChatMessage(options?)` | `streamText(...)` | Full control over inference |
77+
| `onChatResponse(result)` | no-op | Post-turn lifecycle hook |
78+
| `onChatError(error)` | passthrough | Customize error handling |
5379

5480
### Client tools
5581

@@ -60,13 +86,44 @@ Think supports client-defined tools that execute in the browser. The client send
6086
{ messages: [...], clientTools: [{ name: "search", description: "Search the web" }] }
6187

6288
// In onChatMessage, the default implementation merges:
63-
// getTools() + clientTools + options.tools
89+
// workspace + getTools() + clientTools + session context tools + options.tools
6490
```
6591

6692
When the LLM calls a client tool, the tool call chunk is sent to the client. The client executes it and sends back `CF_AGENT_TOOL_RESULT`. Think applies the result, persists the updated message, broadcasts `CF_AGENT_MESSAGE_UPDATED`, and optionally auto-continues the conversation (debounce-based — multiple rapid tool results coalesce into one continuation turn).
6793

6894
Tool approval flows are also supported via `CF_AGENT_TOOL_APPROVAL`.
6995

96+
### Session and context blocks
97+
98+
Think uses Session for conversation storage. Override `configureSession` to add persistent memory, skills, compaction, and search:
99+
100+
```ts
101+
export class MyAgent extends Think<Env> {
102+
getModel() { ... }
103+
104+
configureSession(session: Session) {
105+
return session
106+
.withContext("memory", { description: "Learned facts", maxTokens: 2000 })
107+
.withCachedPrompt();
108+
}
109+
}
110+
```
111+
112+
Skills support load/unload for explicit context management:
113+
114+
```ts
115+
import { R2SkillProvider } from "agents/experimental/memory/session";
116+
117+
configureSession(session: Session) {
118+
return session
119+
.withContext("skills", {
120+
provider: new R2SkillProvider(this.env.SKILLS_BUCKET, { prefix: "skills/" })
121+
})
122+
.withCachedPrompt();
123+
}
124+
// Model gets load_context and unload_context tools automatically
125+
```
126+
70127
### MCP integration
71128

72129
Think inherits MCP client support from the Agent base class. Set `waitForMcpConnections` to ensure MCP-discovered tools are available before `onChatMessage` runs:
@@ -113,6 +170,7 @@ export class MyAgent extends Think<Env, MyConfig> {
113170
### Production features
114171

115172
- **WebSocket protocol** — wire-compatible with `useAgentChat` from `@cloudflare/ai-chat`
173+
- **Built-in workspace** — every agent gets `this.workspace` with file tools auto-wired
116174
- **Stream resumption** — page refresh replays buffered chunks via `ResumableStream`
117175
- **Client tools** — accept tool schemas from clients, handle results and approvals
118176
- **Auto-continuation** — debounce-based continuation after tool results
@@ -122,22 +180,19 @@ export class MyAgent extends Think<Env, MyConfig> {
122180
- **Partial persistence** — on error, the partial assistant message is saved
123181
- **Message sanitization** — strips ephemeral provider metadata before storage
124182
- **Row size enforcement** — compacts tool outputs exceeding 1.8MB
125-
- **Incremental persistence** — skips SQL writes for unchanged messages
126-
- **Storage bounds** — set `maxPersistedMessages` to cap stored history
127-
- **Messages on connect** — newly connected clients receive the current message list immediately
128183

129184
## Workspace tools
130185

131-
File operation tools backed by the Agents SDK `Workspace`:
186+
File operation tools are built into Think and available to the model on every turn. For custom storage backends, the individual tool factories are also exported:
132187

133188
```ts
134189
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
135190

136-
const tools = createWorkspaceTools(this.workspace);
137-
// Tools: read, write, edit, list, find, grep, delete
191+
// Use with a custom ReadOperations/WriteOperations implementation
192+
const tools = createWorkspaceTools(myCustomStorage);
138193
```
139194

140-
Each tool is an AI SDK `tool()` with Zod schemas. The underlying operations are abstracted behind interfaces (`ReadOperations`, `WriteOperations`, etc.) so you can create tools backed by custom storage.
195+
Each tool is an AI SDK `tool()` with Zod schemas. The underlying operations are abstracted behind interfaces (`ReadOperations`, `WriteOperations`, etc.) so you can create tools backed by any storage.
141196

142197
## Code execution tool
143198

@@ -148,7 +203,6 @@ import { createExecuteTool } from "@cloudflare/think/tools/execute";
148203

149204
getTools() {
150205
return {
151-
...createWorkspaceTools(this.workspace),
152206
execute: createExecuteTool({ tools: wsTools, loader: this.env.LOADER })
153207
};
154208
}
@@ -181,5 +235,5 @@ getTools() {
181235
| `agents` | yes | Cloudflare Agents SDK |
182236
| `ai` | yes | Vercel AI SDK v6 |
183237
| `zod` | yes | Schema validation (v3.25+ or v4) |
238+
| `@cloudflare/shell` | yes | Workspace filesystem |
184239
| `@cloudflare/codemode` | optional | For `createExecuteTool` |
185-
| `@cloudflare/shell` | optional | For workspace tools |

packages/think/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
"peerDependenciesMeta": {
3636
"@cloudflare/codemode": {
3737
"optional": true
38-
},
39-
"@cloudflare/shell": {
40-
"optional": true
4138
}
4239
},
4340
"exports": {

packages/think/src/e2e-tests/worker.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
*/
66
import { createWorkersAI } from "workers-ai-provider";
77
import { callable, routeAgentRequest } from "agents";
8-
import type { LanguageModel, ToolSet, UIMessage } from "ai";
9-
import { Workspace } from "@cloudflare/shell";
10-
import { Think } from "../think";
8+
import type { LanguageModel, UIMessage } from "ai";
9+
import { Think, Workspace } from "../think";
1110
import type { ChatRecoveryContext, ChatRecoveryOptions } from "../think";
12-
import { createWorkspaceTools } from "../tools/workspace";
1311

1412
type Env = {
1513
TestAssistant: DurableObjectNamespace<TestAssistant>;
@@ -19,7 +17,7 @@ type Env = {
1917
};
2018

2119
export class TestAssistant extends Think<Env> {
22-
workspace = new Workspace({
20+
override workspace = new Workspace({
2321
sql: this.ctx.storage.sql,
2422
r2: this.env.R2,
2523
name: () => this.name
@@ -39,10 +37,6 @@ When asked to write a file, use the write tool. When asked to read a file, use t
3937
Always respond concisely.`;
4038
}
4139

42-
getTools(): ToolSet {
43-
return createWorkspaceTools(this.workspace);
44-
}
45-
4640
@callable()
4741
override getMessages(): UIMessage[] {
4842
return this.messages;

0 commit comments

Comments
 (0)