Repository navigation
feat(agents): experimental ThinkHarness, Think's agent loop as a Lifecycle capability #2512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b2b19e2
feat(agents): add experimental ThinkHarness, Think's agent loop as a …
mattzcarey 2867164
fix(agents): ThinkHarness refuses to steer instead of queueing as a f…
mattzcarey 0f12f8a
test(think): score Think's suite against a Think built on ThinkHarness
mattzcarey e4ec4b9
refactor(agents): ThinkHarness owns its Streams; tools carry their ow…
mattzcarey 1b31048
docs(examples): add examples/next/harnesses/think
mattzcarey 365437a
refactor(agents): ThinkHarness owns its Sessions
mattzcarey 0fa6114
fix(examples): dedupe React in the think harness example
mattzcarey 0949c9c
refactor(agents): branches, search and compact on ThinkSession; renam…
mattzcarey 3bf929d
chore: ThinkHarness ships as a patch
mattzcarey cbc4f8f
fix(agents): address review of ThinkHarness
mattzcarey 8fd03a3
fix(agents): address second ThinkHarness review
mattzcarey bcaccaf
fix(agents): migrate ThinkHarness operations tables that predate aban…
mattzcarey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "agents": patch | ||
| --- | ||
|
|
||
| `Streams` rechecks that the v1 chunk table still exists before using it. Two Streams instances on one object, such as a host's own and the one `ThinkHarness` keeps, each remembered the table separately, so after one folded the last v1 rows and dropped the table the other failed its next append with `no such table`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "agents": patch | ||
| --- | ||
|
|
||
| Add `ThinkHarness` from the new experimental `agents/harness/think` entry point. It runs Think's agent loop as a Lifecycle capability with the same shape as `PiHarness` (`prompt`, `submit`, `wait`, `abort`, `sessions`, `session(id)`), and implements the shared harness interface Channels serves. | ||
|
|
||
| The harness owns its storage: transcripts in the Sessions tables, with `branches()`, `search()` and `compact()` on each session, and in-flight model output in the Streams tables, so a host installs only the harness. Each session has one Lifecycle wake job, so a turn interrupted by an eviction picks up from its last durable write: a cut-short model call is rebuilt from its stream and continued in the same message, and a cut-short tool call is reported to the model, or rerun when the tool carries `recovery: "rerun"`. `ThinkChat` serves a session over Think's `useAgentChat` WebSocket protocol. See [Think harness](https://github.com/cloudflare/agents/blob/main/docs/agents/harnesses/think.md). |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Think harness | ||
|
|
||
| An experimental example of `ThinkHarness` from `agents/harness/think`: Think's | ||
| agent loop as a Lifecycle capability on a plain Durable Object, served to | ||
| `useAgentChat` by `ThinkChat`. | ||
|
|
||
| `src/server.ts` composes three capabilities: | ||
|
|
||
| - `ThinkHarness` keeps the transcript and runs turns with the AI SDK. It | ||
| keeps each model call's output in storage while it streams, and one | ||
| Lifecycle job per session wakes the object after an eviction. | ||
| - `WebSockets` accepts the browser's connection. | ||
| - `ThinkChat` speaks Think's `cf_agent_chat_*` protocol over it, so the | ||
| client is the stock `useAgentChat` from `agents/chat/react`. | ||
|
|
||
| ```ts | ||
| export class ThinkAgent extends DurableObject<Env> { | ||
| readonly harness = new ThinkHarness({ | ||
| model: createAI({ binding: this.env.AI })("@cf/moonshotai/kimi-k2.7-code"), | ||
| tools: { getWeather, sendNotification, getUserTimezone } | ||
| }); | ||
| readonly webSockets = new WebSockets(); | ||
| readonly chat = new ThinkChat({ | ||
| harness: this.harness, | ||
| webSockets: this.webSockets | ||
| }); | ||
| readonly lifecycle = Lifecycle.install(this) | ||
| .use(this.harness) | ||
| .use(this.webSockets) | ||
| .use(this.chat); | ||
| } | ||
| ``` | ||
|
|
||
| Each browser gets its own chat, named by a random id kept in | ||
| `localStorage`, so visitors to a deployed copy do not share a transcript. | ||
|
|
||
| The tools show the three ways a tool call runs: | ||
|
|
||
| | Tool | Runs | After an eviction cuts a call short | | ||
| | ------------------ | ----------------------- | -------------------------------------------------- | | ||
| | `getWeather` | on the server | runs again, because it carries `recovery: "rerun"` | | ||
| | `sendNotification` | on the server, approved | reported to the model as interrupted (the default) | | ||
| | `getUserTimezone` | in the browser | waits for the browser's result, as it did before | | ||
|
|
||
| ## Run locally | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm run start | ||
| ``` | ||
|
|
||
| The example uses the remote Workers AI binding and may incur Workers AI | ||
| usage. It needs no API key. | ||
|
|
||
| ## Try | ||
|
|
||
| - "What is the weather in Lisbon?" runs a server tool. | ||
| - "Notify Sam that lunch is ready" asks for your approval first. | ||
| - "What time zone am I in?" runs a tool in the browser. | ||
| - Refresh the page while an answer streams. The client reconnects and the | ||
| stream picks up where it was. | ||
|
|
||
| ## Learn more | ||
|
|
||
| - [Think harness docs](../../../../docs/agents/harnesses/think.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* eslint-disable */ | ||
| // Generated by Wrangler by running `wrangler types --include-runtime=false env.d.ts` (hash: 0cb7d4d3f8bfd65f4a85db3f24bf892b) | ||
| interface __BaseEnv_Env { | ||
| AI: Ai; | ||
| ThinkAgent: DurableObjectNamespace<import("./src/server").ThinkAgent>; | ||
| } | ||
| declare namespace Cloudflare { | ||
| interface GlobalProps { | ||
| mainModule: typeof import("./src/server"); | ||
| durableNamespaces: "ThinkAgent"; | ||
| } | ||
| interface Env extends __BaseEnv_Env {} | ||
| } | ||
| interface Env extends __BaseEnv_Env {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <link rel="icon" href="/favicon.ico" /> | ||
| <title>Think harness</title> | ||
| <script> | ||
| (() => { | ||
| const stored = localStorage.getItem("theme"); | ||
| const mode = stored || "light"; | ||
| document.documentElement.setAttribute("data-mode", mode); | ||
| document.documentElement.style.colorScheme = mode; | ||
| })(); | ||
| </script> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/client.tsx"></script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "name": "@cloudflare/agents-next-think-harness-example", | ||
| "description": "Experimental: Think's agent loop on a plain Durable Object with agents/harness/think, served to useAgentChat", | ||
| "private": true, | ||
| "type": "module", | ||
| "version": "0.0.0", | ||
| "scripts": { | ||
| "start": "vite dev", | ||
| "build": "vite build", | ||
| "deploy": "vite build && wrangler deploy", | ||
| "typecheck": "tsc --noEmit", | ||
| "types": "wrangler types env.d.ts --include-runtime false" | ||
| }, | ||
| "dependencies": { | ||
| "@cloudflare/kumo": "^2.6.0", | ||
| "@phosphor-icons/react": "^2.1.10", | ||
| "@streamdown/code": "^1.1.1", | ||
| "agents": "*", | ||
| "ai": "^7.0.0", | ||
| "react": "^19.2.7", | ||
| "react-dom": "^19.2.7", | ||
| "streamdown": "^2.5.0", | ||
| "zod": "^4.5.4" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vite-plugin": "1.62.3", | ||
| "@cloudflare/workers-types": "^5.20260729.1", | ||
| "@tailwindcss/vite": "^4", | ||
| "@types/react": "^19.2.17", | ||
| "@types/react-dom": "^19.2.3", | ||
| "@vitejs/plugin-react": "^6.0.3", | ||
| "tailwindcss": "^4.3.2", | ||
| "typescript": "^6.0.3", | ||
| "vite": "^8.1.0", | ||
| "wrangler": "^4.145.0" | ||
| } | ||
| } |
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Coordinate the upstream documentation update
The docs rules require a matching cloudflare-docs update for new reference pages. Coordinate that separate update before publishing this API.
Was this helpful? React with 👍 or 👎 to provide feedback.