agents@0.11.3

cloudflare/agentsagents@0.11.3Apr 18, 2026by github-actions[bot]

AI Summary

This patch release fixes a critical bug where `subAgent()` caused cross-DO I/O errors on first use, and introduces a new `_cf_initAsFacet()` RPC to properly initialize facets. It also removes the requirement for the `"experimental"` compatibility flag for `ctx.facets`, `ctx.exports`, and `env.LOADER`. Additionally, it adds a `createdAt` field to recovery contexts to help apps guard against stale interruptions.

Key Highlights

  • Fixed `subAgent()` cross-DO I/O errors by replacing fetch-based handshake with new `_cf_initAsFacet()` RPC
  • Dropped requirement for `"experimental"` compatibility flag — features now work without it
  • Added `createdAt` field to `FiberRecoveryContext` and `ChatRecoveryContext` for stale recovery detection
  • Added `_isFacet` guards to broadcast methods to prevent facet-to-parent WebSocket leaks
  • Removed internal `_cf_markAsFacet()` method in favor of `_cf_initAsFacet()`

Breaking Changes

  • Internal `_cf_markAsFacet()` method removed (replaced by `_cf_initAsFacet()`) — this is an internal API change

New Features

  • New `createdAt: number` field on `FiberRecoveryContext` (epoch milliseconds when `runFiber` started)
  • New `createdAt: number` field on `ChatRecoveryContext` threaded through from fiber
  • New `_cf_initAsFacet(name)` RPC method for facet initialization
  • `_isFacet` guards added to `_broadcastProtocol()` and `broadcast()` to prevent improper broadcasts from facets
  • Documentation added for 'Guarding against stale recoveries' pattern

Full Release Notes

### Patch Changes

-   [#1330](https://github.com/cloudflare/agents/pull/1330) [`b4d3fcf`](https://github.com/cloudflare/agents/commit/b4d3fcfcce7363b137ad47c31d40aebcb34d9a28) Thanks [@threepointone](https://github.com/threepointone)! - Fix `subAgent()` cross-DO I/O errors on first use and drop the `"experimental"` compatibility flag requirement.

    ### `subAgent()` cross-DO I/O fix

    Three issues in the facet initialization path caused `"Cannot perform I/O on behalf of a different Durable Object"` errors when spawning sub-agents in production:

    -   `subAgent()` constructed a `Request` in the parent DO and passed it to the child via `stub.fetch()`. The `Request` carried native I/O tied to the parent isolate, which the child rejected.
    -   The facet flag was set _after_ the first `onStart()` ran, so `broadcastMcpServers()` fired with `_isFacet === false` on the initial boot.
    -   `_broadcastProtocol()`, the inherited `broadcast()`, and `_workflow_broadcast()` iterated the connection registry without an `_isFacet` guard, letting broadcasts reach into the parent DO's WebSocket registry from a child isolate.

    Replaces the fetch-based handshake with a new `_cf_initAsFacet(name)` RPC that runs entirely in the child isolate, sets `_isFacet` before init, and seeds partyserver's `__ps_name` key directly. Adds `_isFacet` guards to `_broadcastProtocol()` and overrides `broadcast()` to no-op on facets so downstream callers (chat-streaming paths, workflow broadcasts, user `this.broadcast(...)`) are covered. Removes the previous internal `_cf_markAsFacet()` method — `_cf_initAsFacet(name)` is the correct entry point (it sets the flag before running the first `onStart()`, which `_cf_markAsFacet` did not).

    ### `"experimental"` compatibility flag no longer required

    `ctx.facets`, `ctx.exports`, and `env.LOADER` (Worker Loader) have graduated out of the `"experimental"` compatibility flag in workerd. `agents` and `@cloudflare/think` no longer require it:

    -   `subAgent()` / `abortSubAgent()` / `deleteSubAgent()` — the `@experimental` JSDoc tag and runtime error messages no longer reference the flag. The runtime guards on `ctx.facets` / `ctx.exports` stay in place and now nudge users toward updating `compatibility_date` instead.
    -   `Think` — the `@experimental` JSDoc tag no longer references the flag.

    No code change is required; remove `"experimental"` from your `compatibility_flags` in `wrangler.jsonc` if it was only there for these features.

-   [#1332](https://github.com/cloudflare/agents/pull/1332) [`7cb8acf`](https://github.com/cloudflare/agents/commit/7cb8acff8281a30bc17980e506ab5582f3cb1c72) Thanks [@threepointone](https://github.com/threepointone)! - Expose `createdAt` on fiber and chat recovery contexts so apps can suppress continuations for stale, interrupted turns.

    -   `FiberRecoveryContext` (from `agents`) gains `createdAt: number` — epoch milliseconds when `runFiber` started, read from the `cf_agents_runs` row that was already tracked internally.
    -   `ChatRecoveryContext` (from `@cloudflare/ai-chat` and `@cloudflare/think`) gains the same `createdAt` field, threaded through from the underlying fiber.

    With this, the stale-recovery guard pattern described in [#1324](https://github.com/cloudflare/agents/issues/1324) is a short override:

    ```typescript
    override async onChatRecovery(ctx: ChatRecoveryContext): Promise<ChatRecoveryOptions> {
      if (Date.now() - ctx.createdAt > 2 * 60 * 1000) return { continue: false };
      return {};
    }
    ```

    No behavior change for existing callers. See `docs/chat-agents.md` (new "Guarding against stale recoveries" section) for the full recipe, including a loop-protection pattern using `onChatResponse`.