@cloudflare/think@0.11.0
cloudflare/agents@cloudflare/think@0.11.0Jun 26, 2026by github-actions[bot]
AI Summary
Introduces progress signalling and durable milestones for agent-tool sub-agents, allowing background runs to report status. Also enhances the `create-think` CLI with starter templates and removes legacy helper functions.
Key Highlights
- Progress signalling API (`reportProgress`) for awaited and detached sub-agents
- Durable milestones (persisted and replayable) for tracking specific events
- Added `detached: { notify: true }` support for automatic completion notifications
- `create-think` prompts for starter templates and initializes git repositories
- `ctx.attachReply(attachment)` API for action side-channels
New Features
- Sub-agent progress reporting with fraction, phase, and message
- Milestone tracking with `onProgress` hooks
- Detached run completion handling
- CLI project scaffolding improvements
- Reply attachment support for actions
Full Release Notes
### Minor Changes
- [#1758](https://github.com/cloudflare/agents/pull/1758) [`6b46b04`](https://github.com/cloudflare/agents/commit/6b46b044c03e9fda280c9916fef6ec8b6baa7d73) Thanks [@threepointone](https://github.com/threepointone)! - Add progress signalling and durable milestones for agent-tool sub-agents
(cloudflare/agents#1758, rfc-detached-agent-tools §progress, phases 4a + 4b).
A sub-agent running as an agent tool (awaited or detached/background) can now
report mid-run progress:
```ts
// Inside the child sub-agent (e.g. from a tool's execute):
await this.reportProgress({
fraction: 0.6,
phase: "deploying",
message: "Generating menu page…"
});
```
These signals ride the child's own turn stream as a transient
`data-agent-progress` part, so they re-broadcast to the parent's connected
clients and surface on `AgentToolRunState.progress` via `useAgentToolEvents` — a
background-runs tray can render a live bar / phase / status line without drilling
in. Highlights:
- **`reportProgress({ fraction?, message?, phase?, data? }, { persist? })`** on
chat agents (`@cloudflare/think`, `AIChatAgent`); a no-op with a dev warning on
the base `Agent` and when called outside an active agent-tool run. The framework
resolves the run id from the active turn — no threading required. Bursts are
coalesced (latest-wins; a `fraction >= 1` "done" frame always flushes). `data`
is live-only unless `{ persist: true }`.
- **`onProgress(run, progress)`** parent hook, fired best-effort from the tail
for both awaited and detached runs.
- **Latest-snapshot persistence + recovery inspect.** The child stores a
`progress_json` + `last_signal_at` on its run row and surfaces it through
`inspectAgentToolRun().progress`, so a rehydrated parent reconstructs progress
after eviction.
- **Resetting no-progress budget for detached runs.** Once a detached child has
reported at least one signal, the backbone gives up if it then goes silent for
`detachedNoProgressBudgetMs` (default 1h; per-run override via
`detached: { noProgressBudgetMs }`), surfaced as `interrupted` with the
`no-progress` reason. A child that never reports is bounded only by the absolute
`detachedMaxBudgetMs` ceiling — we never give up on a run merely for being slow.
## Durable milestones (phase 4b)
Naming a `milestone` promotes a signal from the ephemeral tier to a **durable**
one — there is still only one emit method:
```ts
// Inside the child sub-agent:
await this.reportProgress({
milestone: "sources-gathered",
data: { sources: 2 }
});
```
- **Persisted + replayable.** Each milestone is one row on the child
(`cf_agent_tool_milestones` / `cf_ai_chat_agent_tool_milestones`) with a
monotonic per-run `sequence`. It rides the stream as a **persisted**
`data-agent-milestone` part (vs. transient progress), so drill-in replay and a
rehydrated parent both see it. Surfaced via `inspectAgentToolRun().milestones`
and `AgentToolRunState.milestones` (deduped by `sequence`).
- **`onProgress` fires for milestones too** — the snapshot carries
`progress.milestone`, so a consumer can branch on milestone vs. ephemeral.
- **`detached: { onMilestones }` chat convenience** (`@cloudflare/think` and
`AIChatAgent`). When a configured milestone lands, the chat agent surfaces an
idempotent synthetic chat message (keyed/idempotent per `(runId, name)`)
_before_ the run finishes. Delivered from both the warm tail and the cold
backbone reconcile; the deterministic id collapses them to at-most-once. Two
modes (the `string[]` shorthand defaults to `"narrate"`):
- `"narrate"` (default) — a synthetic **assistant** message injected directly
(no inference): a cheap, honest status line that does not trigger a turn.
- `"react"` — a **user-role** turn so the model responds to the milestone
(steer, start dependent work). Costs a model turn.
```ts
detached: { onMilestones: ["preview-ready"] } // narrate (default)
detached: { onMilestones: { names: ["needs-approval"], mode: "react" } }
```
Override the wording via `formatDetachedMilestone(run, milestone)`. These
synthetic messages carry `metadata.source` so clients can render them as an
agent **event** rather than a human turn (the example does this).
The awaitable join point (`awaitAgentToolMilestone`, phase 4c) is intentionally
not included here — it is gated behind a design addendum.
- [#1758](https://github.com/cloudflare/agents/pull/1758) [`6b46b04`](https://github.com/cloudflare/agents/commit/6b46b044c03e9fda280c9916fef6ec8b6baa7d73) Thanks [@threepointone](https://github.com/threepointone)! - Add `detached: { notify: true }` support for `runAgentTool` on chat agents
(`@cloudflare/think` and `AIChatAgent`) (cloudflare/agents#1752).
When a detached sub-agent run finishes, a chat agent can inject a message back
into the chat so the model reacts to the result — without you wiring `onFinish`
by hand:
```ts
await this.runAgentTool(ResearchAgent, {
input,
detached: { notify: { source: "research-background" } }
});
```
The injected turn is idempotent per run + terminal status, so an exactly-once
finish never duplicates, while a soft give-up followed by a real late completion
surfaces as two distinct turns. (Think dedupes via a `submitMessages`
idempotency key; `AIChatAgent`, which has no durable-submission layer, persists
under a deterministic message id and runs the follow-up turn inline within the
already-serialized delivery slot.) Use `notify: true` for the default
`metadata.source`, pass `notify: { source }` to match your app's message
taxonomy, and override `formatDetachedCompletion(run, result)` to customize (or
suppress) the injected text.
- [#1817](https://github.com/cloudflare/agents/pull/1817) [`7f367d8`](https://github.com/cloudflare/agents/commit/7f367d88eb988d5f0285d4922d1091c7f69361a3) Thanks [@threepointone](https://github.com/threepointone)! - `create-think` now prompts for a starter template when `--template` is omitted (and falls back to `basic` when stdin is non-interactive). `npm create think` and `think init` initialize a git repository — skipping cleanly when the target is already inside one — and scaffold projects with Oxlint/Oxfmt config plus a `check` script. Removes the unused declarative `agent()` framework helper and the identity helpers (`defineMessengers`, `defineScheduledTasks`, `defineChannels`) in favor of class-based agents and typed object returns.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add `ctx.attachReply(attachment)` for actions: an advisory, recording-only reply-attachment side-channel surfaced on `ChatResponseResult.attachments` (in `onChatResponse`) and a public `replyAttachments(requestId?)` getter. Attachments are JSON-normalized, deep-copied on read, capped per turn, and never alter the model-visible tool output; policy callbacks are no-ops, failed executions discard their attachments, approval-gated approved actions support it, and durable-pause approved actions are a v1 no-op.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add a pending-retry lease for the action ledger via the new `actionLedgerPendingRetryLeaseMs` config (default 5 minutes). A `pending` ledger row left behind by a crashed executor is now reclaimed and re-run once it is stale, but ONLY for actions that declare an explicit `idempotencyKey` — the key is the developer's assertion that re-running the keyed side effect is safe. Behavior change: such a stale row previously blocked forever with `ActionPendingError`; it now reclaims (refreshing `updated_at` in place, still `pending`), emits `action:ledger:reclaimed`, and re-runs `execute`. Fresh rows, fallback `tool:${toolCallId}` keys, and a disabled lease (`actionLedgerPendingRetryLeaseMs = false`) keep the conservative `ActionPendingError` behavior. Same-isolate coalescing still wins first, so an in-flight run is never reclaimed.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add a durable action ledger for `action()` descriptors so settled server action outputs can be replayed by stable idempotency key without re-running side effects.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Generalize the messenger runtime into a public channel surface. Add `configureChannels()` and `ChannelDefinition` (web, voice, messenger, and custom channels) wrapping `getMessengers()`, a no-turn `deliverNotice()` with `informModel`, additive `DeliveryTag` (kind + turnEnded) on messenger snapshots, per-channel policy (instructions, tool-narrowing, `maxTurns`) applied as overridable defaults, turn-scoped channel context threaded through `runTurn` (persisted for recovery), reply-attachment rendering at delivery, and `channel:*`/`notice:*` observability events.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add durable-pause approval descriptors: `durable-pause` actions now park in a dedicated `cf_think_action_pending_approvals` store and resume via `approveExecution`/`rejectExecution` with a connection-independent continuation, so a turn can be approved from a dashboard with no live socket (this also fixes codemode `approveExecution` from a dashboard). A unified `ActionApprovalDescriptor` is attached to durable-pause, codemode, and approval-gated parts, `pendingApprovals()` lists all pending approvals for cold-load reconciliation, and an overridable `describePausedExecution()` hook enriches codemode descriptors.
- [#1801](https://github.com/cloudflare/agents/pull/1801) [`c58b401`](https://github.com/cloudflare/agents/commit/c58b4015b7616581b3d7fca86a5fde6e49bd9cd3) Thanks [@threepointone](https://github.com/threepointone)! - Add `@cloudflare/think/react`, a Think-tuned `useAgentChat` wrapper that keeps `setMessages` local-only by default while reusing the shared chat React implementation.
- [#1788](https://github.com/cloudflare/agents/pull/1788) [`3b2af54`](https://github.com/cloudflare/agents/commit/3b2af5444af5002cd54fd493452e03c721d31999) Thanks [@threepointone](https://github.com/threepointone)! - `Think` now annotates and logs row-size compaction the same way
`@cloudflare/ai-chat` does.
When a persisted message exceeds the SQLite row-size limit and `Think` compacts
its tool outputs or truncates its text parts to fit, the resulting message now
carries `metadata.compactedToolOutputs` (the compacted tool-call IDs) and/or
`metadata.compactedTextParts` (the truncated text-part indices), and `Think`
emits a `console.warn` describing the compaction. The compaction itself is
unchanged — `Think` already used the shared shape-preserving `truncateToolOutput`
compactor — this only adds the previously ai-chat-only annotations/warnings so a
client can tell that a stored message was compacted. Both packages now share one
`enforceRowSizeLimit` implementation.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add public `runTurn(options)` facade (Turns RFC step 2): unified turn admission
with `mode: "wait" | "submit" | "stream"` delegating to the existing
`saveMessages`, `continueLastTurn`, `submitMessages`, and `chat` methods.
Exports `TurnInputMessages`, `RunTurnWait`, `RunTurnSubmit`, `RunTurnStream`,
`RunTurnOptions`, and `TurnResult`.
- [#1799](https://github.com/cloudflare/agents/pull/1799) [`3c2afc9`](https://github.com/cloudflare/agents/commit/3c2afc9379f34fe51e401999ec03e9efc0fe93f2) Thanks [@threepointone](https://github.com/threepointone)! - Allow `runTurn({ mode: "stream" })` to accept array and function inputs, matching the existing `wait` mode input surface while preserving the durable `submit` function-input guard.
### Patch Changes
- [#1788](https://github.com/cloudflare/agents/pull/1788) [`3b2af54`](https://github.com/cloudflare/agents/commit/3b2af5444af5002cd54fd493452e03c721d31999) Thanks [@threepointone](https://github.com/threepointone)! - Converge recovery forward-progress crediting between `AIChatAgent` and `Think`.
Both hosts now credit the recovery no-progress counter through one shared, host-agnostic rule (`shouldCreditStreamProgress`): a progress milestone (a started text/reasoning segment or a settled tool input/output) credits unconditionally, and mid-segment streaming deltas (`text-delta`/`reasoning-delta`/`tool-input-delta`) credit at most once per throttle window via a per-isolate `StreamProgressCreditThrottle`. Previously `AIChatAgent` credited only on chunk-type milestones while `Think` credited on its flush cadence, so a long single content segment spanning repeated crashes could read as "no progress" under `AIChatAgent` and false-fire its `no_progress_timeout`. The new rule is never coarser than either host's prior cadence, so it can only delay or avoid a false no-progress timeout, never hasten give-up.
- [#1803](https://github.com/cloudflare/agents/pull/1803) [`c476265`](https://github.com/cloudflare/agents/commit/c476265c9f18a2a6eb5f01137515a8776ca8b63c) Thanks [@threepointone](https://github.com/threepointone)! - Fix AI SDK `status` getting stuck after a reconnect that races a turn's
pre-stream window ([#1784](https://github.com/cloudflare/agents/issues/1784)).
A turn is "accepted but pre-stream" while it is queued, debouncing, or awaiting
async setup before its resumable stream starts. A client that connected or sent
a `STREAM_RESUME_REQUEST` in that window was answered with `STREAM_RESUME_NONE`
("nothing to resume"), so its short resume probe resolved `null` and AI SDK
`status` settled on `ready` even though the server went on to stream — leaving
the UI unable to render the in-flight turn until a full remount.
This adds a shared `PreStreamTurns` tracker (`agents/chat`) and a new
server→client `cf_agent_stream_pending` frame:
- The resume handshake now parks resume requests that arrive during the
pre-stream window and emits `STREAM_PENDING` ("keep waiting") instead of
`STREAM_RESUME_NONE`, then flushes parked connections into the normal
`STREAM_RESUMING` handshake once the stream actually starts (and releases them
with `STREAM_RESUME_NONE` if the turn is superseded/cleared before streaming).
- On `STREAM_PENDING` the client transport extends its resume probe from the
5s fast-path to a 60s backstop so the probe stays open across the gap.
- `useAgentChat` re-probes the stream on a transparent socket reopen (e.g. a
1006 reconnect that does not remount the component) so `status` recovers.
- Continuation affinity is relaxed via an optional `isConnectionPresent` host
hook so a transparent reconnect (whose connection id changed) can resume a
continuation whose original owner connection is gone.
Wired into both `AIChatAgent` and `@cloudflare/think`.
The pre-stream tracker is in-memory only; it is hibernation-safe because a turn
in its pre-stream window is an unresolved message-handler promise that pins the
Durable Object in memory, so eviction only happens once a stream is durably
recorded (and resumes via `ResumableStream`) or the turn has finished. Skipped
turns (supersede/generation change) settle without releasing parked
connections, so a client parked during the window survives onto the successor
turn instead of being cut loose by a premature `STREAM_RESUME_NONE`.
- [#1788](https://github.com/cloudflare/agents/pull/1788) [`3b2af54`](https://github.com/cloudflare/agents/commit/3b2af5444af5002cd54fd493452e03c721d31999) Thanks [@threepointone](https://github.com/threepointone)! - Recovery give-up now resolves the orphaned stream by newest metadata row.
The stable-timeout/error give-up path that terminalizes an exhausted recovery
turn previously resolved the turn's orphaned stream id with an in-memory
first-match scan over all stream metadata, while the wake (restart) path already
used the newest durable row keyed by the recovery-root request id. These two
lookups are now a single seam, so both paths surface the same partial — the
newest stream the turn produced — when a request id spans more than one
recovery attempt. Single-attempt turns (one stream row per request id) are
unaffected.
- [#1794](https://github.com/cloudflare/agents/pull/1794) [`b6ad4d5`](https://github.com/cloudflare/agents/commit/b6ad4d5bc078ede978b9b68fd7beb6ed3194f848) Thanks [@threepointone](https://github.com/threepointone)! - Extract transcript repair into a shared `agents/chat` primitive.
`@cloudflare/think`'s `_repairToolTranscriptParts` — which flips an interrupted
tool call (a `tool-*` / `dynamic-tool` part with no settled result, left behind
when a stream was cut off mid-flight) into an errored tool-result so the next
provider call doesn't 400 with `AI_MissingToolResultsError`, and normalizes
malformed tool `input` — now lives once as the shared, `@internal`
`repairInterruptedToolParts` primitive (plus the `toolPartHasSettledResult`
terminal-state check) in `agents/chat`.
The primitive is pure (returns a new messages array plus repair stats; never
touches storage, broadcast, or events) and is parameterized by an overridable
`repairPart` hook plus an optional `shouldRepair(part)` skip predicate (defaults
to repairing every interrupted part), so both AI-SDK chat hosts can run repair
logic before re-entering inference on a recovered turn — a host whose default
errors the part (ai-chat) uses `shouldRepair` to leave a part still awaiting a
client interaction verbatim. `@cloudflare/think` delegates through its existing
`repairInterruptedToolPart` hook with no `shouldRepair` (repairs everything) — a
pure internal refactor with no observable behavior or API change; its suites pass
unchanged.
- [#1772](https://github.com/cloudflare/agents/pull/1772) [`d4f27fe`](https://github.com/cloudflare/agents/commit/d4f27fededefebc17cf455218e952ff76ade847b) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Include each package's documentation in its published package.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add stable approval descriptors for Think actions and preserve approval descriptor metadata on chat tool parts.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Harden action approval and authorization edge cases around approved inputs and continuation rechecks.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add action permission metadata and default-full-grant authorization hooks for Think actions.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add the `action()` descriptor and `getActions()` hook for compiling guarded
server actions into Think tools.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Harden Think action descriptors with schema-inferred inputs and JSON-safe output
normalization.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Route Think turn entry points through a shared internal `_admitTurn` spine and
throw a clear error for nested blocking turn admissions that previously could
deadlock.
- [#1797](https://github.com/cloudflare/agents/pull/1797) [`f599892`](https://github.com/cloudflare/agents/commit/f599892390991d9110311b51ac647b7018f95926) Thanks [@threepointone](https://github.com/threepointone)! - Fix: a recovered agent-tool **child** turn now re-binds its run row to the
recovery turn's request id, so a healthy long-running child is no longer
abandoned as `interrupted` after a deploy.
When a facet running as an agent-tool child was interrupted mid-run (e.g. a
deploy evicted it), its recovery continuation (`continueLastTurn` /
`_retryLastUserTurn`) minted a fresh request id but left
`cf_agent_tool_child_runs.request_id` pointing at the pre-eviction turn. Frame
attribution (`_agentToolRunForRequest`) then failed, so the recovered turn's
broadcast frames never reached the parent's re-attach tail; the parent saw no
forward progress and sealed a still-advancing child `interrupted` once its
no-progress budget elapsed. The recovery paths now re-bind the child-run row
(and the in-memory attribution map) to the current turn's request id, keeping
frames flowing across recovery so the parent re-attaches and follows the child
to its real terminal.
- [#1797](https://github.com/cloudflare/agents/pull/1797) [`f599892`](https://github.com/cloudflare/agents/commit/f599892390991d9110311b51ac647b7018f95926) Thanks [@threepointone](https://github.com/threepointone)! - Fix: a recovered pre-stream **retry** turn now re-applies per-channel policy.
`continueLastTurn` already re-resolved the channel from the persisted user
message (`metadata.channel`) so a recovered partial turn re-applied its
channel's instructions / tool narrowing. The pre-stream retry path
(`_retryLastUserTurn`, used by `_chatRecoveryRetry`) admitted the recovered turn
without re-resolving the channel, so an interrupted-before-streaming turn was
retried with the default policy instead of the channel's — even though the
`metadata.channel` stamp survived. It now re-resolves and re-applies the channel
on both recovery paths, matching the documented invariant.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - Add `chat:turn:start` and `chat:turn:finish` observability events for Think
turn execution.
- [#1790](https://github.com/cloudflare/agents/pull/1790) [`190ea81`](https://github.com/cloudflare/agents/commit/190ea814c4ea61c216509a431baa8be06d917256) Thanks [@threepointone](https://github.com/threepointone)! - `Think.waitUntilStable()` now waits out an armed-but-unfired auto-continuation
before reporting stable, converging onto `@cloudflare/ai-chat`.
Previously, when a turn ended with no pending human/client interaction,
`waitUntilStable()` reported stable immediately — even if an auto-continuation
was armed (its ~50ms coalesce timer still pending, or its completeness drain in
flight). In that window idle eviction or chat recovery could act on a transcript
that was about to be continued. `Think` now mirrors `@cloudflare/ai-chat`: while
a continuation is armed (`pending && !pastCoalesce` and the shared
`AutoContinuationController` reports armed), `waitUntilStable()` reports
not-stable and waits out the coalesce window, then re-checks (the continuation
either fires and enqueues a turn the loop drains, or parks and clears, at which
point the agent is genuinely stable).
- Updated dependencies \[[`7f367d8`](https://github.com/cloudflare/agents/commit/7f367d88eb988d5f0285d4922d1091c7f69361a3)]:
- create-think@0.1.1