Skip to content

Fix dropped async callable replies on facets - #2027

Open
cjol wants to merge 6 commits into
mainfrom
fix-1991-facet-rpc-replies
Open

Fix dropped async callable replies on facets#2027
cjol wants to merge 6 commits into
mainfrom
fix-1991-facet-rpc-replies

Conversation

@cjol

@cjol cjol commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This PR routes callable replies on facet WebSockets through the bridge that delivered the originating RPC frame. Fixes #1991.

Why

  • Facet WebSocket frames share one durable virtual Connection, but each forwarded frame arrives with a temporary SubAgentConnectionBridge.
  • Concurrent frames replace the bridge stored on the virtual connection. When an asynchronous callable resumes, its reply can therefore use a newer frame's already-disposed bridge and never reach the client.
  • We could pin the entire virtual connection to the originating bridge, but general connection.send() calls and broadcasts represent durable connection operations and must not inherit a short-lived frame transport.
  • We could pass the bridge through a private dispatcher, but that would bypass onMessage wrappers installed by AIChatAgent, Think, or application middleware.
  • An invocation-local reply context keeps only framework RPC replies scoped to their originating frame while preserving the existing connection and wrapper semantics.

Architectural Changes

Before:

RPC frame A
  → shared virtual Connection
  → stored bridge replaced by frame B
  → async reply uses frame B's disposed bridge

After:

RPC frame A
  → invocation-local reply bridge A
  → async framework reply uses bridge A

durable connection operations
  → shared virtual Connection
  → existing connection routing

Code Changes

  • Capture the originating facet reply bridge across asynchronous onMessage wrappers without changing general virtual connection behavior.
  • Route callable success and error responses through that captured target and await delivery before the forwarding RPC completes.
  • Preserve the synchronous StreamingResponse API while tracking pending bridge deliveries and flushing them before the callable invocation returns.
  • Carry the originating reply bridge through downstream facet forwarding so intermediate virtual connections cannot replace it.
  • Add an agents patch changeset.

Open in Devin Review

Co-authored-by: Dustin Graham <dustin.graham@hhandr.com>
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dea62cf

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

This PR includes changesets to release 2 packages
Name Type
agents 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 2 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

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

pkg-pr-new Bot commented Aug 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

create-think

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: dea62cf

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.

RPC reply silently dropped for a facet @callable that awaits before returning, under a burst of concurrent useAgent().call() frames

1 participant