Why Project Think exists

Earlier this year, a wave of coding agentsPi, OpenClaw, Claude Code, Codex — demonstrated something fundamental: give an LLM file access, code execution, and memory, and you get a general-purpose assistant, not just a developer tool. People now use these agents for calendars, data analysis, negotiations, taxes, and full business workflows. The pattern is consistent: read context, reason, write code to act, observe, iterate. Code is the universal medium of action.

But our daily use surfaced real limitations. These agents live on a laptop or an expensive VPS — no sharing, collaboration, or device handoff. They cost a flat monthly fee whether idle or working, which doesn't scale across a team. They demand manual setup: dependencies, updates, identity, secrets.

There's also a structural problem. Traditional apps serve many users from one instance; agents are one-to-one. Each agent is a unique instance for one user and one task. A restaurant kitchen is optimized for volume; an agent is a personal chef with different ingredients and tools every time. That math breaks at scale: a hundred million knowledge workers with even modest concurrency means tens of millions of simultaneous sessions, unsustainable at current per-container costs.

The core shift: durable, addressable agents

Current agents are ephemeral: a session tied to one process, gone when the laptop sleeps. Project Think's first shift is making agents infrastructure. Built on Durable Objects, every agent gets an identity, persistent state, and the ability to wake on message — the actor model. Each agent is addressable with its own SQLite database, consuming zero compute while hibernated. On an HTTP request, WebSocket message, scheduled alarm, or inbound email, the platform wakes it, loads state, and delivers the event. Work happens, then it sleeps again.

BLOG-3200 1

The economics invert: instead of one expensive agent per power user, you build one per customer, task, or email thread, with effectively zero marginal cost per new agent.

VMs / Containers

Durable Objects

Idle cost

Full compute cost, always

Zero (hibernated)

Scaling

Provision and manage capacity

Automatic, per-agent

State

External database required

Built-in SQLite

Recovery

You build it (process managers, health checks)

Platform restarts, state survives

Identity / routing

You build it (load balancers, sticky sessions)

Built-in (name → agent)

10,000 agents, each active 1% of the time

10,000 always-on instances

~100 active at any moment

Durable execution with fibers

An LLM call takes 30 seconds; a multi-turn loop runs far longer. Deploys, platform restarts, or resource limits can kill the environment mid-flight, severing the model connection and losing in-memory state. runFiber() makes invocations durable: registered in SQLite before execution, checkpointable anywhere via stash(), recoverable on restart through onFiberRecovered.

import { Agent } from "agents";

export class ResearchAgent extends Agent {
  async startResearch(topic: string) {
    void this.runFiber("research", async (ctx) => {
      const findings = [];

      for (let i = 0; i < 10; i++) {
        const result = await this.callLLM(`Research step ${i}: ${topic}`);
        findings.push(result);

        // Checkpoint: if evicted, we resume from here
        ctx.stash({ findings, step: i, topic });

        this.broadcast({ type: "progress", step: i });
      }

      return { findings };
    });
  }

  async onFiberRecovered(ctx) {
    if (ctx.name === "research" && ctx.snapshot) {
      const { topic } = ctx.snapshot;
      await this.startResearch(topic);
    }
  }
}

Keepalive is automatic during fiber execution. For minute-scale work, keepAlive() / keepAliveWhile() prevents eviction. For hour-scale jobs — CI pipelines, design reviews, video generation — the agent persists a job ID, hibernates, and wakes on callback.

Isolation through sub-agents

One agent shouldn't do everything. Sub-agents are child Durable Objects colocated via Facets, each with its own SQLite database and execution context. Storage isolation is runtime-enforced; sub-agent RPC latency is a function call. TypeScript catches cross-agent misuse at compile time.

import { Agent } from "agents";

export class ResearchAgent extends Agent {
  async search(query: string) { /* ... */ }
}

export class ReviewAgent extends Agent {
  async analyze(query: string) { /* ... */ }
}

export class Orchestrator extends Agent {
  async handleTask(task: string) {
    const researcher = await this.subAgent(ResearchAgent, "research");
    const reviewer = await this.subAgent(ReviewAgent, "review");

    const [research, review] = await Promise.all([
      researcher.search(task),
      reviewer.analyze(task)
    ]);

    return this.synthesize(research, review);
  }
}

Sessions with structure

Weeks-long agents need more than a flat message list. The experimental Session API, available on the Agent base class, stores conversations as trees where each message has a parent_id. That enables forking to explore alternative paths non-destructively, compaction that summarizes rather than deletes, and full-text search via FTS5.

import { Agent } from "agents";
import { Session, SessionManager } from "agents/experimental/memory/session";

export class MyAgent extends Agent {
  sessions = SessionManager.create(this);

  async onStart() {
    const session = this.sessions.create("main");
    const history = session.getHistory();
    const forked = this.sessions.fork(session.id, messageId, "alternative-approach");
  }
}

Sessions work directly with Agent and form the storage layer for the Think base class.

Code execution over tool calling

Conventional tool calling is clumsy at scale. Each tool result pulls back through the context window; a hundred files means a hundred model round-trips. But models write code to use systems better than they play the tool-calling game — the insight behind @cloudflare/codemode. Instead of sequential calls, the LLM writes one program for the entire task.

// The LLM writes this. It runs in a sandboxed Dynamic Worker.
const files = await tools.find({ pattern: "**/*.ts" });
const results = [];
for (const file of files) {
  const content = await tools.read({ path: file });
  if (content.includes("TODO")) {
    results.push({ file, todos: content.match(/\/\/ TODO:.*/g) });
  }
}
return results;

One program replaces 100 round-trips: fewer tokens, faster execution, better results. The Cloudflare API MCP server proves it — exposing search() and execute() consumes ~1,000 tokens versus ~1.17 million for a naive tool-per-endpoint design, a 99.9% reduction.

Sandboxes by default

Once models write code for users, the question is where it runs. Right now, for this user, against this system, with tight permissions. Dynamic Workers are that sandbox: a fresh V8 isolate in milliseconds with a few megabytes of memory — roughly 100x faster and up to 100x more memory-efficient than a container. Spawn one per request, run a snippet, discard it.

The capability model is the key choice. Dynamic Workers start with nearly no ambient authority (globalOutbound: null, no network), and developers grant capabilities explicitly through bindings. The question flips from "how do we constrain this machine?" to "what exactly should this thing do?"

The execution ladder

That model yields a spectrum of compute, an execution ladder the agent escalates through:

BLOG-3200 2

Tier 0: the Workspace, a durable virtual filesystem on SQLite and R2 — read, write, edit, search, grep, diff, via @cloudflare/shell.

Tier 1: a Dynamic Worker with LLM-generated JavaScript in a sandboxed, network-less isolate, powered by @cloudflare/codemode.

Tier 2: adds npm. @cloudflare/worker-bundler fetches registry packages, bundles with esbuild, loads into the Dynamic Worker. import { z } from "zod" just works.

Tier 3: a headless browser via Cloudflare Browser Run — navigate, click, extract, screenshot — for services without MCP or API support.

Tier 4: a Cloudflare Sandbox with your toolchains, repos, dependencies: git clone, npm test, cargo build, bidirectionally synced with the Workspace.

The design principle: the agent is useful at Tier 0 alone; each tier is additive.

Primitives, not a framework

Every primitive ships standalone. Dynamic Workers, @cloudflare/codemode, @cloudflare/worker-bundler, and @cloudflare/shell each work directly with the Agent base class. Combine them to give any agent a workspace, code execution, and runtime package resolution — no opinionated framework required.

Putting the stack together

Capability

What it does

Powered by

Per-agent isolation

Every agent is its own world

Durable Objects (DOs)

Zero cost when idle

$0 until the agent wakes up

DO Hibernation

Persistent state

Queryable, transactional storage

DO SQLite

Durable filesystem

Files that survive restarts

Workspace (SQLite + R2)

Sandboxed code execution

Run LLM-generated code safely

Dynamic Workers + @cloudflare/codemode

Runtime dependencies

import * from react just works

@cloudflare/worker-bundler

Web automation

Browse, navigate, fill forms

Browser Run

Full OS access

git, compilers, test runners

Sandboxes

Scheduled execution

Proactive, not just reactive

DO Alarms + Fibers

Real-time streaming

Token-by-token to any client

WebSockets

External tools

Connect to any tool server

MCP

Agent coordination

Typed RPC between agents

Sub-agents (Facets)

Model access

Connect to an LLM to power the agent

AI Gateway + Workers AI (or Bring Your Own Model)

Each layer is a building block. Together they form something new: a platform for building, deploying, and running AI agents as capable as local ones — but serverless, durable, and safe by construction. Project Think wires these primitives together with an opinionated base class: use the pieces for custom needs, or the Think base class to move fast.

Wiring the primitives together

The building blocks described so far only become useful when orchestrated. Think is a harness that ties the full chat lifecycle together: the agentic loop, message persistence, streaming, tool execution, stream resumption, and extension management. You subclass it and write only what is unique to your agent.

The minimal working agent needs just a model and a system prompt:

import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";

export class MyAgent extends Think<Env> {
  getModel() {
    return createWorkersAI({ binding: this.env.AI })(
      "@cf/moonshotai/kimi-k2.5"
    );
  }
}

That single subclass delivers streaming, persistence, cancel/abort, error handling, resumable streams, and a workspace filesystem. Deployment is a single npx wrangler deploy.

Think makes opinionated choices by default but exposes overrides at each decision point:

Override

Purpose

getModel()

Return the LanguageModel to use

getSystemPrompt()

System prompt

getTools()

AI SDK compatible ToolSet for the agentic loop

maxSteps

Max tool-call rounds per turn

configureSession()

Context blocks, compaction, search, skills

Internally, each turn runs the full loop: assemble context (base instructions, tool descriptions, skills, memory, conversation history), call streamText, execute any tool calls with output truncation to protect the context window, append results, and repeat until the model finishes or hits the step limit. Messages persist after every turn.

Lifecycle hooks, memory and compaction

Rather than forcing you to own the entire pipeline, Think exposes hooks at each stage of a chat turn:

beforeTurn()
  → streamText()
    → beforeToolCall()
    → afterToolCall()
  → onStepFinish()
→ onChatResponse()

These hooks let you swap in a cheaper model for follow-up turns, restrict tool availability, pass client-side context per turn, log tool calls to analytics, or trigger an extra follow-up turn automatically — all without overriding onChatMessage.

Storage is built on the Session API, which gives tree-structured messages with native branching. On top of that, Think adds long-term memory via context blocks: structured sections of the system prompt the model can read and update, persisted across hibernation. The model sees a prompt section like "MEMORY (Important facts, use set_context to update) [42%, 462/1100 tokens]" and can proactively record facts. Sessions themselves are flexible — you can run multiple conversations per agent and fork one without losing the original.

Context growth is handled through non-destructive compaction. Instead of discarding old messages, Think summarizes them while the complete history stays in SQLite. Search uses FTS5, letting you query conversation history within a session or across all sessions; the agent can also search its own past with the search_context tool.

Tools and self-authored extensions

The execution ladder is exposed as a single getTools() return:

import { Think } from "@cloudflare/think";
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
import { createExecuteTool } from "@cloudflare/think/tools/execute";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
import { createSandboxTools } from "@cloudflare/think/tools/sandbox";
import { createExtensionTools } from "@cloudflare/think/tools/extensions";

export class MyAgent extends Think<Env> {
  extensionLoader = this.env.LOADER;

  getModel() {
    /* ... */
  }

  getTools() {
    return {
      execute: createExecuteTool({
        tools: createWorkspaceTools(this.workspace),
        loader: this.env.LOADER
      }),
      ...createBrowserTools(this.env.BROWSER),
      ...createSandboxTools(this.env.SANDBOX), // configured per-agent: toolchains, repos, snapshots
      ...createExtensionTools({ manager: this.extensionManager! }),
      ...this.extensionManager!.getTools()
    };
  }
}

Think extends code execution further: an agent can write its own TypeScript extensions that run in Dynamic Workers, declaring permissions for network and workspace operations.

{
  "name": "github",
  "description": "GitHub integration: PRs, issues, repos",
  "tools": ["create_pr", "list_issues", "review_pr"],
  "permissions": {
    "network": ["api.github.com"],
    "workspace": "read-write"
  }
}

The ExtensionManager bundles the source (optionally with npm dependencies via @cloudflare/worker-bundler), loads it into a Dynamic Worker, and registers the new capabilities as tools. The extension is stored in DO storage and survives hibernation, so a user who asks about pull requests may find a github_create_pr tool available that did not exist 30 seconds earlier. This self-improvement works through code, not fine-tuning or RLHF — the agent writes new capabilities for itself in sandboxed, auditable, revocable TypeScript.

Sub-agents and getting started

Think also operates as a sub-agent. A parent calls chat() over RPC and receives streaming events via callback:

const researcher = await this.subAgent(ResearchSession, "research");
const result = await researcher.chat(`Research this: ${task}`, streamRelay);

Each child maintains its own conversation tree, memory, tools, and model, so the parent stays decoupled from the details.

The project is experimental: the API surface is stable but will keep changing. Cloudflare already uses it internally for background agent infrastructure and is sharing it publicly for others to build on.

npm install @cloudflare/think agents ai @cloudflare/shell zod workers-ai-provider
// src/server.ts
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think<Env> {
  getModel() {
    return createWorkersAI({ binding: this.env.AI })(
      "@cf/moonshotai/kimi-k2.5"
    );
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;
// src/client.tsx
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
  const agent = useAgent({ agent: "MyAgent" });
  const { messages, sendMessage, status } = useAgentChat({ agent });
  // Render your chat UI
}

Think communicates over the same WebSocket protocol as @cloudflare/ai-chat, so existing UI components work unmodified. If you have client code written against AIChatAgent, it requires no changes.

Agents as infrastructure

Cloudflare sees three generations of AI agents. The first were chatbots: stateless, reactive, fragile, restarting every conversation with no memory or tools. The second — coding agents like Pi, Claude Code, OpenClaw, and Codex — added state and tool use, proving that an LLM with the right tools is a general-purpose machine, but bound to a laptop, a single user, and no durability guarantees.

The third wave is agents as infrastructure: durable, distributed, structurally safe, serverless. These agents run on the Internet, survive failures, cost nothing when idle, and enforce security through architecture rather than behavior. The Agents SDK already runs thousands of production agents; Project Think, together with primitives like persistent workspaces, sandboxed code execution, durable long-running tasks, structural security, and sub-agent coordination, aims to make those agents dramatically more capable.

Think is available in preview as part of the Agents SDK (@cloudflare/think). APIs may evolve based on feedback until the surface is finalized.

BLOG-3200 3