Skip to content

fix(agents): initialize cold native RPC calls - #2005

Open
cjol wants to merge 5 commits into
mainfrom
fix-1990-think-cold-rpc
Open

fix(agents): initialize cold native RPC calls#2005
cjol wants to merge 5 commits into
mainfrom
fix-1990-think-cold-rpc

Conversation

@cjol

@cjol cjol commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

This PR initializes Agent and Think instances before native Durable Object RPC methods run on a cold instance. Fixes #1990.

Why

  • Native Durable Object RPC invokes methods directly, bypassing the PartyServer entry points that normally run onStart(). A method that wakes an evicted Agent can therefore observe missing session state, stale caches, unrestored MCP connections, or incomplete recovery.
  • getAgentByName() cannot provide a lasting guarantee because stubs outlive the in-memory instances they address. Eviction or deployment can make any later RPC the next cold-start entry point.
  • Requiring every inherited and application-defined RPC method to call __unsafe_ensureInitialized() would expose framework lifecycle concerns to users and leave room for inconsistent coverage.
  • Eagerly starting the lifecycle from the base constructor would run before derived fields and constructors finish. Instead, this change extends the existing method-context wrapper, arms it after construction, and initializes only when a public application RPC is the cold entry point.

Architectural Changes

Before:

native RPC -> application method
                  |
                  +-> may observe pre-onStart state

After:

native RPC -> Agent invocation wrapper -> PartyServer initialization -> application method
                         |
                         +-> lifecycle/control-plane methods retain their existing dispatch
  • Agent tracks whether RPC initialization is constructing, pending, starting, or started so startup extension points can call public methods without recursively initializing.
  • Prototype discovery now wraps inherited Agent and application methods while preserving callable metadata and the nearest property descriptor.
  • PartyServer lifecycle and control-plane methods, plus underscore-prefixed framework internals, remain outside the application RPC wrapper.

Code Changes

  • agents initializes cold public application RPC calls through __unsafe_ensureInitialized() and delays arming the guard until derived construction completes.
  • agents preserves synchronous constructor-local calls, inherited descriptor shadowing, and existing WebSocket @callable behavior.
  • @cloudflare/think marks its startup chain as in progress while it initializes session, workspace, message, protocol, and recovery state before invoking extension points.
  • The OAuth provider fixtures now use named IDs because their application RPCs enter Agent startup. An unbootstrapped newUniqueId() has no resolvable PartyServer name and is not a valid initialized Agent instance.
  • Patch changesets cover both agents and @cloudflare/think.

Compatibility

  • Native Durable Object RPC does not require @callable. The decorator remains the explicit allowlist for RPC exposed through Agent WebSocket clients.
  • Agents addressed through idFromName() or getByName() initialize without an extra handshake.
  • A raw newUniqueId() or idFromString() instance must call setName() before its first application RPC. Once bootstrapped, PartyServer persists the name and recovers it after eviction. Previously, name-independent methods could appear to work by running against uninitialized Agent state.

Open in Devin Review

…bject RPC methods run on a cold instance. Fixes #1990.

## Why

- Native Durable Object RPC invokes methods directly, bypassing the PartyServer entry points that normally run `onStart()`. A method that wakes an evicted Agent can therefore observe missing session state, stale caches, unrestored MCP connections, or incomplete recovery.
- `getAgentByName()` cannot provide a lasting guarantee because stubs outlive the in-memory instances they address. Eviction or deployment can make any later RPC the next cold-start entry point.
- Requiring every inherited and application-defined RPC method to call `__unsafe_ensureInitialized()` would expose framework lifecycle concerns to users and leave room for inconsistent coverage.
- Eagerly starting the lifecycle from the base constructor would run before derived fields and constructors finish. Instead, this change extends the existing method-context wrapper, arms it after construction, and initializes only when a public application RPC is the cold entry point.

## Architectural Changes

Before:

```text
native RPC -> application method
                  |
                  +-> may observe pre-onStart state
```

After:

```text
native RPC -> Agent invocation wrapper -> PartyServer initialization -> application method
                         |
                         +-> lifecycle/control-plane methods retain their existing dispatch
```

- Agent tracks whether RPC initialization is constructing, pending, starting, or started so startup extension points can call public methods without recursively initializing.
- Prototype discovery now wraps inherited Agent and application methods while preserving callable metadata and the nearest property descriptor.
- PartyServer lifecycle and control-plane methods, plus underscore-prefixed framework internals, remain outside the application RPC wrapper.

## Code Changes

- `agents` initializes cold public application RPC calls through `__unsafe_ensureInitialized()` and delays arming the guard until derived construction completes.
- `agents` preserves synchronous constructor-local calls, inherited descriptor shadowing, and existing WebSocket `@callable` behavior.
- `@cloudflare/think` marks its startup chain as in progress while it initializes session, workspace, message, protocol, and recovery state before invoking extension points.
- The OAuth provider fixtures now use named IDs because their application RPCs enter Agent startup. An unbootstrapped `newUniqueId()` has no resolvable PartyServer name and is not a valid initialized Agent instance.
- Patch changesets cover both `agents` and `@cloudflare/think`.

## Compatibility

- Native Durable Object RPC does not require `@callable`. The decorator remains the explicit allowlist for RPC exposed through Agent WebSocket clients.
- Agents addressed through `idFromName()` or `getByName()` initialize without an extra handshake.
- A raw `newUniqueId()` or `idFromString()` instance must call `setName()` before its first application RPC. Once bootstrapped, PartyServer persists the name and recovers it after eviction. Previously, name-independent methods could appear to work by running against uninitialized Agent state.
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 781dce0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
agents Patch
@cloudflare/think Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/agents/src/index.ts Outdated
@pkg-pr-new

pkg-pr-new Bot commented Jul 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2005

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2005

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2005

create-think

npm i https://pkg.pr.new/create-think@2005

hono-agents

npm i https://pkg.pr.new/hono-agents@2005

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2005

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2005

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2005

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2005

commit: 781dce0

@threepointone

Copy link
Copy Markdown
Contributor

I'll review this one

@cjol
cjol requested a review from threepointone August 3, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent/Think: an RPC-triggered cold start serves calls against uninitialised state, causing errors and incorrect responses

2 participants