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`.