Fix sub-agent workflow origins#1430
Merged
Merged
Conversation
🦋 Changeset detectedLatest commit: 636ec01 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
This was referenced Apr 30, 2026
agents
@cloudflare/ai-chat
@cloudflare/codemode
create-think
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
34 tasks
Support workflows started from sub-agent facets by preserving the origin path for callbacks, RPC, tracking, and docs. Made-with: Cursor
975480c to
bc32e21
Compare
added 3 commits
June 24, 2026 14:40
…ard, version check, docs, tests - Make facet broadcastToClients() await the parent hop so a facet that hibernates right after the workflow RPC no longer silently drops the message - Match real DO-stub RPC semantics in _cf_invokeAgentPath: refuse built-in/ prototype and JS-internal method names; align error messaging - Validate AgentWorkflowOrigin.version so an SDK mismatch fails clearly - Fix stale onWorkflowCallback doc comment (_workflow_handleCallback RPC, not HTTP) - Document routing constraints: name-based resolution, facet-local workflow tracking (getWorkflows scoping), class names must survive bundling - Add tests: callback routing to a facet evicted mid-workflow, deleted-mid-flight guard + unsafe-method rejection, and HTTP reach via routeSubAgentRequest while a workflow runs
Adds a regression test for the backward-compat path in AgentWorkflow._initAgent(): a workflow started before this change carries only __agentName/__agentBinding/ __workflowName (no __agentOrigin) and must still resolve its originating Agent by name+binding for callbacks and this.agent RPC. Starts TEST_WORKFLOW with legacy-only params (bypassing runWorkflow, which now always injects __agentOrigin) and asserts the RPC result lands and the tracking row reaches 'complete'.
Workerd tracks outbound actor RPC through IoContext::awaitIo/addTask and promotes actor tasks to wait-until tasks even when the JS promise is not awaited. Correct the prior claim that a facet could hibernate and drop the root broadcast. Keep the await for accurate completion, ordering, and error propagation. Also rename the explicit abort coverage from eviction/hibernation to restart coverage.
Broadcast delivery to clients is best-effort everywhere in facet routing: facet broadcast(), the connection bridge, and connection-routing broadcasts all fire-and-forget their root hop on main. A facet does not care whether the client socket is still connected, and workerd keeps the outbound actor RPC alive after the caller returns, so the message is still delivered. Awaiting only the workflow path would impose a stronger completion/error contract than the rest of the broadcast API without any durable-delivery guarantee. Revert _workflow_broadcast() to the standard broadcast() path and drop the related changeset bullet.
Contributor
|
I'm pretty happy with this. will merge tomorrow |
Contributor
|
can you make not draft @threepointone |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes workflows started from sub-agent facets by preserving the exact originating Agent path and routing workflow callbacks/RPC back to that facet instead of assuming a top-level Agent binding.
This addresses the failure mode from #1359 where
runWorkflow()could not detect a binding for sub-agents and gives facet-started workflows the same tracking, callback, and Agent RPC semantics as top-level workflows.What changed
AgentWorkflowOrigininternal payload for workflow starts:{ kind: "agent", binding, name }{ kind: "facet", rootBinding, path }Agent.runWorkflow()to preserve root binding +selfPathfor facet callers._cf_invokeAgentPath()soAgentWorkflow.agent, progress, completion, error, event, and state callbacks land on the originating facet.AgentWorkflow.agentexplicitly RPC-only and throws a clear error for.fetch().onStart()can route callbacks immediately, with rollback if initialization fails.Hardening (post-rebase review)
After rebasing onto
main, the change was reviewed for restart safety, edge cases, and unusual usage. Follow-up fixes:_cf_invokeAgentPath()now matches real Durable Object stub RPC semantics: it refuses JS-internal probes (constructor,toString, symbol keys, thenable checks) and anything inherited fromObject.prototype, so a facet-origin workflow can't reach a method surface a top-level workflow's stub would deny. Error messaging is aligned.AgentWorkflowOrigin.versionnow fails with a clear "upgrade the agents package" error instead of silently misreading a future payload shape.idFromName, not raw DO ids); sub-agent workflow tracking is facet-local (getWorkflows()/getWorkflowById()don't see child runs — aggregate yourself); class names must survive bundling (keepNames: true). Fixed a staleonWorkflowCallbackdoc comment that referenced a non-existent/_workflow/callbackHTTP endpoint.Tests
Regression coverage for direct facets, nested facets,
onStart()workflow starts, approval/rejection, errors, durable events, state updates, and facet-local tracking. New coverage from the hardening pass:routeSubAgentRequest()/ nested/sub/...URL) reaches a facet while its workflow is running;__agentOriginstill resumes through the legacy__agentName+__agentBindingpath.Test plan
pnpm --filter agents test -- workflow-sub-agent(11 tests pass; the logged exceptions are intentional thrown/rejected workflow cases)pnpm --filter agents test -- workflow(86 workflow tests pass)pnpm run typecheck(all 113 projects)pnpm lintclean;oxfmt --checkclean