# Think

import {
	TypeScriptExample,
	WranglerConfig,
	PackageManagers,
	LinkCard,
	CardGrid,
} from "~/components";

`@cloudflare/think` lets you build a stateful AI chat agent — one that streams replies, remembers the conversation, and calls tools — by extending a single base class. You provide a model with `getModel()`, and Think wires up the rest of the chat lifecycle for you: the agentic loop (the model calls tools, reads the results, and keeps going until it has an answer), message persistence, streaming, client tools, stream resumption, and extensions — all backed by Durable Object SQLite.

Think works as both a **top-level agent** (WebSocket chat to browser clients via `useAgentChat`) and a **sub-agent** (a child agent that another agent drives over RPC via `chat()`).

:::note[New to Cloudflare Agents?]
If this is your first agent, start with the [Getting started tutorial](/agents/harnesses/think/getting-started/) for a guided build. For the bigger picture of what agents are and how they run, read [What are agents?](/agents/concepts/what-are-agents/). Think builds on two Cloudflare primitives worth a quick look: [Workers AI](/workers-ai/) provides the model, and each agent instance is a [Durable Object](/durable-objects/) that stores its state. The rest of this section is reference material you can dip into as you need it.
:::

## Quick start

### Install

<PackageManagers pkg="@cloudflare/think @cloudflare/computer @cloudflare/ai-chat agents ai zod @ai-sdk/provider" />

Think supports AI SDK v6 and v7. Use `ai@^6` with `@ai-sdk/react@^3`, or use `ai@^7` with `@ai-sdk/react@^4`. Keep the AI SDK packages on matching major versions throughout your project.

### Server

<TypeScriptExample>

```ts
import { Think } from "@cloudflare/think";
import { createAI } from "agents/models/ai-sdk";
import { routeAgentRequest } from "agents";

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

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env)) ||
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;
```

</TypeScriptExample>

That is it. Think handles the WebSocket chat protocol, message persistence, the agentic loop, message sanitization, stream resumption, client tool support, and workspace file tools.

### Client

<TypeScriptExample>

```ts
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 });

	return (
		<div>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem(
						"input",
					) as HTMLInputElement;
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Send a message..." />
				<button type="submit">Send</button>
			</form>
		</div>
	);
}
```

</TypeScriptExample>

### Configuration

<WranglerConfig>

```toml
compatibility_date = "$today"
compatibility_flags = ["nodejs_compat"]

[ai]
binding = "AI"

[[durable_objects.bindings]]
class_name = "MyAgent"
name = "MyAgent"

[[migrations]]
new_sqlite_classes = ["MyAgent"]
tag = "v1"
```

</WranglerConfig>

### Tracing

Think uses `wrapAISDK()` internally to instrument model turns, tool calls, and approval lifecycle segments. You do not need to wrap the AI SDK or configure an adapter. To send `invoke_agent`, `chat`, `execute_tool`, and `tool_approval` spans to Workers Observability, turn on Workers traces:

<WranglerConfig>

```toml
[observability.traces]
enabled = true
```

</WranglerConfig>

Traces appear in the Agents view in the Cloudflare Dashboard. You can inspect conversations and trace timelines. To turn tracing off, set `observability.traces.enabled` to `false`. You do not need to change the Think agent class.

For span attributes, payload controls, exporting traces, and direct AI SDK v6 or v7 setup, refer to [Tracing](/agents/runtime/observability/tracing/).

## Think vs AIChatAgent

Both Think and [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) extend `Agent` and speak the same `cf_agent_chat_*` WebSocket protocol. They serve different goals.

**AIChatAgent** is a protocol adapter. You override `onChatMessage` and are responsible for calling `streamText`, wiring tools, converting messages, and returning a `Response`. AIChatAgent handles the plumbing — message persistence, streaming, abort, resume — but the LLM call is entirely your concern.

**Think** is an opinionated framework. It makes decisions for you: `getModel()` returns the model, `getSystemPrompt()` or `configureSession()` sets the prompt, `getTools()` returns tools. The default `onChatMessage` runs the complete agentic loop. You override individual pieces, not the whole pipeline.

| Concern                | AIChatAgent                                                      | Think                                                               |
| ---------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------- |
| **Minimal subclass**   | ~15 lines (wire `streamText` + tools + system prompt + response) | 3 lines (`getModel()` only)                                         |
| **Storage**            | Flat SQL table                                                   | Session: tree-structured messages, context blocks, compaction, FTS5 |
| **Regeneration**       | Destructive (old response deleted)                               | Non-destructive branching (old responses preserved)                 |
| **Context management** | Manual                                                           | Context blocks with LLM-writable persistent memory                  |
| **Sub-agent RPC**      | Not built in                                                     | `chat()` with `StreamCallback`                                      |
| **Programmatic turns** | `saveMessages()`                                                 | `saveMessages()`, `submitMessages()`, `continueLastTurn()`          |
| **Compaction**         | `maxPersistedMessages` (deletes oldest)                          | Non-destructive summaries via overlays                              |
| **Search**             | Not available                                                    | FTS5 full-text search per-session and cross-session                 |

### When to use AIChatAgent

- You need full control over the LLM call (RAG, multi-model, custom streaming)
- You want the `Response` return type for HTTP middleware or testing
- You are building a simple chatbot with no memory requirements

### When to use Think

- You want to ship fast (3-line subclass with everything wired)
- You need persistent memory (context blocks the model can read and write)
- You need long conversations (non-destructive compaction)
- You need conversation search (FTS5)
- You are building a sub-agent system (parent-child RPC with streaming)
- You need proactive agents (programmatic turns from scheduled tasks or webhooks)
- You need durable async submission for webhook or RPC callers

## Choose a turn API

Think has several ways to start or continue a turn. They all funnel through one public entry point — `runTurn(options)` — and the older methods remain as convenience shortcuts.

### runTurn()

:::note[Experimental]
`runTurn()` is stable in shape, but may evolve before Think graduates out of experimental.
:::

`runTurn()` is the unified turn-admission API. One method, three modes, selected by `options.mode`:

| Mode               | Use when                                                     | Returns                         | Shortcut for       |
| ------------------ | ------------------------------------------------------------ | ------------------------------- | ------------------ |
| `"wait"` (default) | The caller can block until the model response is finished    | `Promise<TurnResult>`           | `saveMessages()`   |
| `"submit"`         | The caller needs fast, durable acceptance and a later status | `Promise<SubmitMessagesResult>` | `submitMessages()` |
| `"stream"`         | The caller wants the response streamed to a callback (RPC)   | `Promise<void>`                 | `chat()`           |

The `input` accepts a string, a `UIMessage`, an array of messages, or — in `wait` and `stream` modes — a function `(current) => UIMessage[]` evaluated at admission. (`submit` does not accept function input.)

<TypeScriptExample>

```ts
export class Assistant extends Think<Env> {
	async examples(inboundEventId: string) {
		// wait — block for the result
		const result = await this.runTurn({ input: "Summarize the latest thread" });
		if (result.status === "completed") {
			// result.message is the assistant message; result.continuation is false
		}

		// submit — durable acceptance, check status later
		const submission = await this.runTurn({
			mode: "submit",
			input: "Process this webhook",
			idempotencyKey: inboundEventId, // dedupe; safe to retry
		});
		// submission.accepted is true on first accept; submission.status is "pending"

		// stream — drive a callback (the same surface as chat())
		await this.runTurn({
			mode: "stream",
			input: "Stream me",
			callback: {
				onStart({ requestId }) {},
				onEvent(json) {}, // UIMessageChunk JSON
				onDone() {},
				onError(error) {},
			},
		});

		// continuation — continue the last assistant turn instead of sending input
		await this.runTurn({ continuation: true });
	}
}
```

</TypeScriptExample>

Key behaviors:

- **Blocking modes cannot nest.** Calling `wait`/`stream`/`continuation` (or the equivalent shortcut) from _inside_ an active turn — for example, from a tool's `execute` — throws, because it would deadlock the turn queue. From inside a turn, use `runTurn({ mode: "submit" })` (durable, runs after the current turn frees the queue) or [`addMessages()`](#add-messages-without-a-turn) (transcript only, no inference).
- **`submit` is idempotent.** Pass `submissionId` and/or `idempotencyKey`; re-submitting a known key returns the existing record with `accepted: false` instead of starting a second turn. See [Programmatic submissions](/agents/harnesses/think/programmatic-submissions/).
- **Recovery-safe.** The `wait`, `stream`, and drained `submit` paths run inference inside a recovery fiber, so an interrupted turn resumes after eviction.

`runTurn` is exported alongside its option and result types: `RunTurnOptions`, `RunTurnWait`, `RunTurnSubmit`, `RunTurnStream`, `TurnInputMessages`, and `TurnResult`.

### Pick a shortcut

The table below maps each scenario to the most direct call. Each shortcut has an unchanged signature; reach for them when you want the narrower surface, or use `runTurn()` when you want one mental model.

| Use case                                                       | API                                             |
| -------------------------------------------------------------- | ----------------------------------------------- |
| A browser user sends chat messages                             | `useAgentChat` over the WebSocket chat protocol |
| Server code can wait for the model response                    | `saveMessages()`                                |
| Server code needs fast durable acceptance and later status     | `submitMessages()`                              |
| Code should create recurring prompt-driven turns or handlers   | `getScheduledTasks()`                           |
| Parent code needs direct streaming RPC to a specific child     | `subAgent(...).chat()`                          |
| A parent delegates work to a retained child agent              | `agentTool()` or `runAgentTool()`               |
| Surround a turn with idempotent app-owned side effects         | `startFiber()`                                  |
| Coordinate multi-step durable orchestration                    | Workflows                                       |
| Add context or messages without starting a model turn          | `addMessages()`                                 |
| Advanced subclass or recovery code continues an assistant turn | `continueLastTurn()`                            |

Use `saveMessages()` when the caller owns the trigger and can wait for the turn to finish. Use [`submitMessages()`](/agents/harnesses/think/programmatic-submissions/) when timeout ambiguity would make retries unsafe.

### Add messages without a turn

Use `addMessages()` to write to the transcript **without** starting a model turn — for importing prior history or injecting background context the next turn should see:

<TypeScriptExample>

```ts
export class Assistant extends Think<Env> {
	async importContext() {
		await this.addMessages([
			{
				id: crypto.randomUUID(),
				role: "user",
				parts: [{ type: "text", text: "Imported context" }],
			},
		]);
	}
}
```

</TypeScriptExample>

`addMessages()` appends (or upserts) into the Session tree:

- It does **not** run inference and does **not** enter the turn queue, so it is safe to call from inside a tool's `execute` without deadlocking.
- Array entries are appended **linearly** (each attaches under the previous one), so imported history stays a single path. By default the first message attaches to the latest committed leaf; pass `parentId` to attach elsewhere, or `null` for a root message.
- Appends are **idempotent by message id**. Pass `{ mode: "upsert" }` to update an existing message in place instead.

The supported pattern is "add context, then run a turn": call `addMessages()`, then `runTurn()`.

Use `chat()` for low-level parent-to-child streaming when your code owns forwarding, cancellation, and replay policy. Use [Agents as tools](/agents/agent-class/agent-tools/) when a parent model or workflow delegates to a child agent and you want retained child runs, event replay, abort bridging, and UI drill-in.

Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside Think when the durable unit is an application job around a turn: accepting a webhook once, restoring a serialized channel or thread target, posting a visible reply, or recording app-level recovery policy. Think submissions own conversation admission and turn serialization; managed fibers own external job acceptance, idempotent side effects, and application recovery.

## In this section

<CardGrid>
	<LinkCard
		title="Getting started"
		href="/agents/harnesses/think/getting-started/"
		description="Build a Think agent step by step."
	/>
	<LinkCard
		title="Configuration"
		href="/agents/harnesses/think/configuration/"
		description="Configuration overrides, dynamic configuration, and Session integration."
	/>
	<LinkCard
		title="Tools"
		href="/agents/harnesses/think/tools/"
		description="Workspace tools, code execution, browser tools, and extensions."
	/>
	<LinkCard
		title="Actions"
		href="/agents/harnesses/think/actions/"
		description="Server actions with idempotency, approvals, authorization, and reply attachments."
	/>
	<LinkCard
		title="Channels"
		href="/agents/harnesses/think/channels/"
		description="Per-channel policy, channel selection, and out-of-band notices."
	/>
	<LinkCard
		title="Lifecycle hooks"
		href="/agents/harnesses/think/lifecycle-hooks/"
		description="beforeTurn, beforeStep, onStepFinish, onChatResponse, and more."
	/>
	<LinkCard
		title="Client tools"
		href="/agents/harnesses/think/client-tools/"
		description="Browser-side tools, approvals, and concurrency."
	/>
	<LinkCard
		title="Messengers"
		href="/agents/harnesses/think/messengers/"
		description="Receive and reply to Chat SDK messenger webhooks."
	/>
	<LinkCard
		title="Scheduled tasks"
		href="/agents/harnesses/think/scheduled-tasks/"
		description="Declarative recurring prompts and handlers."
	/>
	<LinkCard
		title="Workflows"
		href="/agents/harnesses/think/workflows/"
		description="Durable model-driven reasoning steps inside Cloudflare Workflows."
	/>
	<LinkCard
		title="Sub-agent RPC"
		href="/agents/harnesses/think/sub-agents/"
		description="chat() streaming, saveMessages, continueLastTurn, and abort."
	/>
	<LinkCard
		title="Programmatic submissions"
		href="/agents/harnesses/think/programmatic-submissions/"
		description="Durable turn admission for webhooks and RPC callers."
	/>
	<LinkCard
		title="Durable recovery"
		href="/agents/harnesses/think/recovery/"
		description="Chat recovery, stream-stall watchdog, and stability detection."
	/>
	<LinkCard
		title="Agent Skills"
		href="/agents/skills/"
		description="On-demand instructions, resources, and scripts via getSkills()."
	/>
</CardGrid>

## Acknowledgments

Think's design is inspired by [Pi](https://pi.dev).

## Example

<LinkCard
	title="Assistant example"
	href="https://github.com/cloudflare/agents/tree/main/examples/assistant"
	description="Explore a multi-session Think assistant with sub-agent routing, shared workspace, MCP, chat recovery, and GitHub OAuth."
/>

## Related

- [Sessions](/agents/runtime/capabilities/sessions/) — context blocks, compaction, search, multi-session (the storage layer Think builds on)
- [Sub-agents](/agents/agent-class/sub-agents/) — `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` (the base Agent methods for spawning children)
- [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `AIChatAgent` for when you need full control over the LLM call
- [Long-running agents](/agents/concepts/agentic-patterns/long-running-agents/) — sub-agent delegation patterns for multi-week agent lifetimes
- [Durable execution](/agents/agent-class/durable-execution/) — `runFiber()` and crash recovery (used by `chatRecovery`)
- [Browse the web](/agents/tools/browser/) — full CDP helper API reference