Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ only when no persisted state exists.

Give the state an integer version and handle every shipped shape. The
`migratePersistedState()` hook is currently supplied by the Rook Agents SDK fork;
it is not part of upstream Agents 0.23.
upstream Agents does not provide it.

```ts
import { Agent } from "agents";
Expand Down
9 changes: 5 additions & 4 deletions examples/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ popup.html ──sendMessage──▶ service worker ──chrome.offscreen.crea
scheduler delivers the stored event on its own. The e2e also stops the service
worker in the middle of a held delivery; the coordinator's journaled watchdog
brings it back, and the delivery completes exactly once.
- **The Agents SDK queue.** The e2e enqueues an increment and observes its state
write through `snapshot()`, exercising the SDK's SQLite-backed queue rather
than a host callback.
- **The Agents SDK queue.** The e2e enqueues an increment and polls `snapshot()`
for its state write. `Agent.queue()` runs each item as an alarm-driven
Lifecycle job, so this also exercises the in-worker alarm path rather than a
host callback.
- **Real Agents SDK sub-agents.** The root uses the public `subAgent()`,
`parentAgent()`, `abortSubAgent()`, and `deleteSubAgent()` APIs. The e2e proves
sibling isolation, overlapping awaits, nested children, restart persistence,
Expand Down Expand Up @@ -272,7 +273,7 @@ substrate. Cloudflare-managed products remain explicit integration boundaries:
| Hibernatable WebSockets and bidirectional state sync across same-Worker container eviction | Socket survival across Worker/offscreen destruction: a platform-owned transport |
| Decorated callable and streaming RPC | Production model providers and client/tool approval integrations |
| Think chat with a deterministic model: durable submit, Stop, partial persistence, and recovery across Worker teardown | |
| SQLite-backed queue and root/sub-agent `Agent.schedule()` | Outbound email: an Email Routing send binding |
| Alarm-driven `Agent.queue()` and root/sub-agent `Agent.schedule()` | Outbound email: an Email Routing send binding |
| Local sub-agents, nesting, restart, abort, and delete | |
| Stateless MCP server and tools | |
| Inbound `routeAgentEmail()` and `onEmail()` | |
Expand Down
5 changes: 4 additions & 1 deletion examples/extension/scripts/e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,10 @@ async function main() {
);

await op(popup, "enqueueIncrement", [2]);
snapshot = await op(popup, "snapshot");
// Agents 0.24 runs each queue item as an alarm-driven Lifecycle job.
snapshot = await pollOp(popup, "snapshot", [], (value) =>
value.events.some((event) => event.kind === "sdk-queue"),
);
check("the SDK queue callback updated Agent state", snapshot.value, 12);
check(
"the SDK queue callback completed",
Expand Down
2 changes: 1 addition & 1 deletion examples/vibe-platform/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ Run \`pnpm exec wrangler deploy\` when you are ready to deploy.
type: "module",
scripts: { deploy: "wrangler deploy", "deploy:dry": "wrangler deploy --dry-run" },
// The vendored fork's version; scripts/e2e.mjs checks it against the installed package.
dependencies: { agents: "0.23.0" },
dependencies: { agents: "0.24.0" },
devDependencies: { wrangler: "^4.114.0" },
},
null,
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions vendor/agents/.changeset/agents-0-24-refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
"agents": minor
"@cloudflare/think": minor
---

Refresh the vendored SDK to the `agents@0.24.0` / `@cloudflare/think@0.19.0`
release (upstream `c076e4c9`). Only those two packages moved upstream; AI Chat
0.12.0, Voice 0.5.0, Shell 0.4.3 and Codemode 0.5.2 are unchanged, and Think now
declares `agents >=0.24.0`.

Queues are a Lifecycle capability (`agents/queue`). `Agent.queue()` returns
before its callback runs; the callback runs from the next alarm, one item at a
time, without the enqueuer's `connection` or `request`. `dequeue*` and
`getQueue*` return Promises, `QueueItem.created_at` is `createdAt`, and
`LifecycleServices.starting()` is `status()`. Think's submission drain, workflow
notifications, connection-less continuations and media eviction run as queue
items looked up by callback name.

State is a Lifecycle capability (`agents/state`) that owns `cf_agents_state`.
The fork's lossless `migratePersistedState` hook is re-homed onto the
capability's load path, so the connect-time state push sees migrated state and
a rejecting override still fails construction as before.

Storage changes on the first wake after the upgrade are **one-way**:
`cf_agents_queues` rows lift into the Lifecycle job queue and the table is
dropped (upstream removes this lift in the next minor, so deployments must pass
through 0.24), Agent's `cf_schema_version` row moves to the
`cf_agents:schema_version` KV key, and Think drops
`cf_think_workflow_notifications`. `cf_think_submissions` gains nullable
`result_status` and `output_json`, which 0.18 ignores. A 0.23 build reopening a
0.24 database recreates its tables harmlessly but drops due queue jobs.

`WebSockets` owns the Agent protocol's identity, state sync and per-connection
readonly/protocol flags, and gains a Cap'n Web transport. That transport is not
supported on do-runtime (see `docs/browser-compatibility.md`); the experimental
`?__agents_rpc=capnweb` endpoint and `callablesFromDecorated` are gone.
`agents/client` and `agents/react` now import `capnweb` statically.

Fork-owned fixes ride along. A persisted chat request that resolves `stale`
after `stopCurrentWork()` now records its completed-request receipt, so a
reconnecting client's replay is refused instead of running the stopped turn.
Child cancellation also dequeues a pending connection-less continuation, which
as a queue item was invisible to `cancelAgentToolRun` until the alarm ran it.
Stop semantics remain the fork's `stopCurrentWork`: a request admitted while
another turn runs is persisted as `uA, uB, aA` and its own turn ends with
Think's continuation prompt, and a per-request `cancel` arriving before the
message is persisted is dropped; both are pinned by tests.
6 changes: 3 additions & 3 deletions vendor/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ Chromium, including native sockets in a real Worker. See

The native Agents and Think files run serially. Agents has a recorded
parallel-worker bridge-test flake; Think's expanded suite hit its 60-second
module-warmup deadline locally when loaded in parallel. All 626 Think cases
module-warmup deadline locally when loaded in parallel. All 763 Think cases
passed with serial file execution; retries remain disabled.

## Packages

- `agents@0.23.0`
- `agents@0.24.0`
- `@cloudflare/ai-chat@0.12.0`
- `@cloudflare/think@0.18.0`
- `@cloudflare/think@0.19.0`
- `@cloudflare/voice@0.5.0`
- `@cloudflare/shell@0.4.3`
- `@cloudflare/codemode@0.5.2`
Expand Down
10 changes: 5 additions & 5 deletions vendor/agents/VENDOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

Upstream: <https://github.com/cloudflare/agents>

Release pin: `agents@0.23.0`
(`5f7ad7e4edac2ec8dd1d6a31758f251cb52373fa`).
Release pin: `agents@0.24.0`
(`c076e4c9ff6cfb72931085226edfd3ee7965ac48`).

The package versions match the 0.23 release:
The package versions match the 0.24 release:

- `agents@0.23.0`
- `@cloudflare/think@0.18.0`
- `agents@0.24.0`
- `@cloudflare/think@0.19.0`
- `@cloudflare/ai-chat@0.12.0`
- `@cloudflare/voice@0.5.0`
- `@cloudflare/shell@0.4.3`
Expand Down
4 changes: 2 additions & 2 deletions vendor/agents/docs/agents/agent-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ console.log(result); // 5

### `this.queue` and friends

Agents include a built-in task queue for deferred execution. This is useful for offloading work or retrying operations. The available methods are `this.queue`, `this.dequeue`, `this.dequeueAll`, `this.dequeueAllByCallback`, `this.getQueue`, and `this.getQueues`.
Agents include a durable background queue. This is useful for offloading work or retrying operations. The available methods are `this.queue`, `this.dequeue`, `this.dequeueAll`, `this.dequeueAllByCallback`, `this.getQueue`, and `this.getQueues`, all asynchronous.

```ts
class MyAgent extends Agent {
Expand All @@ -241,7 +241,7 @@ class MyAgent extends Agent {
}
```

Tasks are stored in the `cf_agents_queues` SQL table and are automatically flushed in sequence. If a task succeeds, it's automatically dequeued.
Items are stored as jobs in the `cf_agents_jobs` SQL table, the Lifecycle-owned job queue, and run from the alarm loop in push order. A successful item is removed; a failing one is retried, then dropped with a `queue:error` event. See [Queue](./queue.md).

### `this.schedule` and friends

Expand Down
30 changes: 30 additions & 0 deletions vendor/agents/docs/agents/client-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ useAgent({
});
```

### Transport

`useAgent` speaks the Agent protocol over one of two wires. The default,
`"cf-websocket"`, is a hibernating WebSocket managed by PartySocket. The
experimental `"capnweb"` transport carries protocol frames through a single
[Cap'n Web](https://github.com/cloudflare/capnweb) RPC session whose root
also serves the host's callables natively:

```typescript
useAgent({
agent: "ChatAgent",
name: "room-123",
transport: "capnweb" // default: "cf-websocket"
});
```

Identity, state sync, reconnection with backoff, and terminal close codes are
identical on both wires; PartySocket still owns the connection and the
transport only swaps the socket class it instantiates. `call()` and `stub`
differ by design. On `cf-websocket` they send JSON `rpc` frames. On `capnweb`
they invoke the host's `callables` natively on the Cap'n Web session root: a
method that returns an `RpcTarget` hands you a live stub you can keep
calling, a `ReadableStream` result streams, and chained calls pipeline.
`@callable()` decorators on an `Agent` are a JSON-wire feature and are not
available on `capnweb` unless the Agent also passes a `callables` target.
The trade-off is that a Cap'n Web connection does not hibernate: the Durable
Object stays in memory while one is open. `AgentClient` accepts the same
`transport` option.

### Async Query Parameters

For authentication tokens or other async data, pass a function that returns a Promise:
Expand Down Expand Up @@ -351,6 +380,7 @@ type UseAgentOptions<State> = {
name?: string; // Instance name (default: "default")
host?: string; // Custom host
path?: string; // Custom path prefix
transport?: "cf-websocket" | "capnweb"; // Wire (default: "cf-websocket")

// Query parameters
query?:
Expand Down
2 changes: 1 addition & 1 deletion vendor/agents/docs/agents/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ The differentiator is not "we have durable state" — it is what happens when a

## Background Processing

- [Queue](./queue.md) - Immediate background task execution
- [Queue](./queue.md) - Durable background task execution
- [Scheduling](./scheduling.md) - Delayed, scheduled, and cron-based tasks
- [Retries](./retries.md) - Automatic retries with exponential backoff and jitter
- [Durable Execution](./durable-execution.md) - `runFiber()`, `startFiber()`, `stash()`, and crash recovery for long tasks
Expand Down
119 changes: 97 additions & 22 deletions vendor/agents/docs/agents/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,16 @@ sequentially. Request handling is middleware dispatch: it stops at the first
returned `Response`, and returning `undefined` passes the request on. A phase
failure propagates, and failed startup can be retried.

A capability installed with `{ fallback: true }` dispatches after every
non-fallback capability, whenever it was installed. This is for a host's
catch-all: `Agent` installs its WebSockets capability as a fallback, so a
subclass that installs request or upgrade middleware from its own
constructor still runs first, even though `Agent`'s constructor ran earlier.
A capability declares how it claims traffic with `claims`, which defaults to
`"selective"`. A `"catch-all"` capability dispatches after every other
capability, whenever it was installed. Catch-alls are unique per dispatch
hook: Lifecycle refuses a second catch-all for `onRequest` or for
`onWebSocketUpgrade`, since it could never be reached, but one of each
coexists. The `WebSockets` capability is a catch-all for upgrades only, so
an HTTP catch-all installs beside it, and selective HTTP capabilities that
target their own routes run before both. A subclass that installs request or
upgrade middleware from its own constructor still runs first, even though
`Agent`'s constructor ran earlier.

Capabilities extending `LifecycleCapability` receive one standard service
surface: storage, readiness, startup state, the job queue, a host
Expand All @@ -153,8 +158,8 @@ host implicitly.

Capability hooks run outside host context, but user callbacks run through
`this.lifecycle.runInHostContext(fn)` inside the host invocation context.
Scheduler dispatches its registered callbacks through this boundary, and a
future capability that calls user code should do the same.
Scheduler and Queue dispatch their registered callbacks through this
boundary, and a future capability that calls user code should do the same.

## The job queue

Expand Down Expand Up @@ -306,8 +311,8 @@ to export `getCurrentAgent()` for the `Agent` class as a compatibility alias.

Lifecycle itself does not model WebSockets. Hosts that want connections
install the `WebSockets` capability, which owns the subsystem end to end —
it claims upgrades, accepts hibernating sockets, dispatches handlers inside
the host invocation boundary, and answers `getConnections()`:
it claims upgrades, dispatches handlers inside the host invocation boundary,
and answers `getConnections()`:

```ts
import { WebSockets } from "agents/websockets";
Expand All @@ -321,26 +326,96 @@ export class MyObject extends DurableObject<Env> {
onMessage: (connection, message) => {
connection.send(`echo:${message}`);
}
}
},
callables: new MyCallables(this)
});
readonly lifecycle = Lifecycle.install(this).use(this.webSockets);
}
```

Without the capability installed, WebSocket upgrades are declined.

The capability can also serve remote methods: pass an `RpcTarget` as
`callables` and its prototype methods become the complete remote interface,
served over a Cap'n Web session (`?__agents_rpc=capnweb`). An `Agent` adds
no new surface for this — its `@callable()`-decorated methods are its
interface, served on every wire: natively over the legacy JSON RPC protocol
and, through the decorator-derived target, over the Cap'n Web endpoint.

Connections use Cloudflare's WebSocket Hibernation API. Idle clients remain
connected while the Durable Object can leave memory; when a message wakes the
object, its constructor and lifecycle startup run again before `onMessage`.
State needed after a wake must be stored durably or through
`connection.setState()`. There is no non-hibernating mode.
### A plain host works with `useAgent`

The capability speaks the Agent protocol on every connection, so a plain
Durable Object is reachable from `useAgent` and `AgentClient` exactly like an
`Agent`:

- On connect it sends the identity frame, which resolves the client's
`ready` and `identified`, then the current state when `state` is set.
`protocol` controls this: `true` (default) for every connection, a
function to decide per connection — `false` marks it no-protocol, so it
gets no protocol text frames on connect or by broadcast but still sends
and receives ordinary messages and callables — or `false` to have the
host drive the sequence itself with `sendIdentity()` and `sendState()`.
`Agent` passes `false`, because it must decide whether a connection
belongs to a facet before any frame is sent.
- `readonly` decides per connection whether state writes over the wire are
refused; `setReadonly()` flips it later. Both flags are stored in the
connection's own state under `_cf_` keys, hidden from `connection.state`
and preserved across `setState`, so they survive hibernation. `Agent`'s
`isConnectionReadonly`, `setConnectionReadonly`, and
`isConnectionProtocolEnabled` delegate to the same storage.
- `callables` is an `RpcTarget` whose prototype methods are the host's
complete remote interface, reached through `call()` and `stub`. On the
`cf-websocket` wire the capability answers them as JSON `rpc` frames. On
the `capnweb` wire they are native Cap'n Web methods on the session root:
a returned `RpcTarget` arrives as a live stub the client can keep
calling, a `ReadableStream` streams, and chained calls pipeline. Methods
run through the host invocation boundary with the calling connection in
scope.
- `state` takes a `State` capability (from `agents/state`) and syncs it over
connections, so `useAgent().state` and `setState()` work against a plain
host:

```ts
readonly state = new State({
initialState: { count: 0 },
// The state owner decides who hears about a change.
onChanged: (_state, source) => this.webSockets.broadcastState(source)
});
readonly webSockets = new WebSockets({ state: this.state });
readonly lifecycle = Lifecycle.install(this)
.use(this.state)
.use(this.webSockets);
```

The current value is pushed to each new connection after identity. A
client's `cf_agent_state` frame goes through the `State` capability, so
the host's own `validateStateChange` decides; a readonly connection is
refused, and a rejected change is logged server-side and answered with a
generic `cf_agent_state_error`. `broadcastState(source)` pushes the
current value to every protocol-enabled connection except `source`. This
is distinct from `connection.setState()`, which is per-connection and
never leaves the host. Without the option, state is never sent or
accepted over connections.

- Any other frame goes to `handlers.onMessage`.

An `Agent` adds no new surface for this: its `@callable()`-decorated methods
are its JSON-wire interface, answered by its own message handler. They are
not mirrored onto the Cap'n Web root; an Agent that wants native calls on
`capnweb` passes a `callables` target like any other host.

### Two wires

The client chooses how frames travel:

- **`cf-websocket`** (default) — accepted with Cloudflare's WebSocket Hibernation
API. Idle clients remain connected while the Durable Object leaves memory;
when a message wakes it, the constructor and lifecycle startup run again
before `onMessage`. State needed after a wake belongs in storage or
`connection.setState()`.
- **Cap'n Web** (`?__agents_transport=capnweb`, or
`useAgent({ transport: "capnweb" })`) — protocol frames travel through one
pipe method on a Cap'n Web session whose root also carries the host's
`callables` natively. The connection is an in-memory socket and does not
hibernate: the object stays pinned while it is open, and clients reconnect
after an eviction.

Handlers are wire-agnostic. Both kinds of connection dispatch the same
`onConnect`/`onMessage`/`onClose`/`onError`, appear in `getConnections()`,
and honour `connection.close(code, reason)`.

## Native RPC

Expand Down
Loading
Loading