Skip to content

Fix sub-agent workflow origins#1430

Merged
mattzcarey merged 8 commits into
mainfrom
fix/subagent-workflow-origins
Jun 24, 2026
Merged

Fix sub-agent workflow origins#1430
mattzcarey merged 8 commits into
mainfrom
fix/subagent-workflow-origins

Conversation

@threepointone

@threepointone threepointone commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

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

  • Adds a versioned AgentWorkflowOrigin internal payload for workflow starts:
    • top-level Agents use { kind: "agent", binding, name }
    • sub-agent facets use { kind: "facet", rootBinding, path }
  • Updates Agent.runWorkflow() to preserve root binding + selfPath for facet callers.
  • Adds path-based internal RPC dispatch via _cf_invokeAgentPath() so AgentWorkflow.agent, progress, completion, error, event, and state callbacks land on the originating facet.
  • Makes facet workflow AgentWorkflow.agent explicitly RPC-only and throws a clear error for .fetch().
  • Registers sub-agents before facet startup so workflows started in 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:

  • RPC dispatch guard. _cf_invokeAgentPath() now matches real Durable Object stub RPC semantics: it refuses JS-internal probes (constructor, toString, symbol keys, thenable checks) and anything inherited from Object.prototype, so a facet-origin workflow can't reach a method surface a top-level workflow's stub would deny. Error messaging is aligned.
  • Origin version validation. A mismatched/unknown AgentWorkflowOrigin.version now fails with a clear "upgrade the agents package" error instead of silently misreading a future payload shape.
  • Docs. Documented the callback routing constraints: callbacks resolve the originating Agent by name (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 stale onWorkflowCallback doc comment that referenced a non-existent /_workflow/callback HTTP 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:

  • callbacks route correctly to a facet that was restarted mid-workflow (explicitly aborted between start and the approval-resumed completion callback, then re-initialized from durable storage);
  • a deleted-mid-flight sub-agent surfaces a clear "no longer exists" error, non-descendant paths are rejected, and built-in/prototype method names are refused;
  • the documented HTTP escape hatch (routeSubAgentRequest() / nested /sub/... URL) reaches a facet while its workflow is running;
  • a workflow started by an older SDK without __agentOrigin still resumes through the legacy __agentName + __agentBinding path.

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 lint clean; oxfmt --check clean

@changeset-bot

changeset-bot Bot commented Apr 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 636ec01

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

This PR includes changesets to release 1 package
Name Type
agents 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

@pkg-pr-new

pkg-pr-new Bot commented Apr 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

create-think

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: 636ec01

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

Support workflows started from sub-agent facets by preserving the origin path for callbacks, RPC, tracking, and docs.

Made-with: Cursor
@mattzcarey mattzcarey force-pushed the fix/subagent-workflow-origins branch from 975480c to bc32e21 Compare June 24, 2026 12:16
Matt Carey 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.
@mattzcarey mattzcarey self-assigned this Jun 24, 2026
mattzcarey and others added 2 commits June 24, 2026 16:49
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.
@mattzcarey mattzcarey marked this pull request as ready for review June 24, 2026 15:00
@mattzcarey

Copy link
Copy Markdown
Contributor

I'm pretty happy with this. will merge tomorrow

@mattzcarey

Copy link
Copy Markdown
Contributor

can you make not draft @threepointone

@mattzcarey mattzcarey merged commit d1c4342 into main Jun 24, 2026
7 checks passed
@mattzcarey mattzcarey deleted the fix/subagent-workflow-origins branch June 24, 2026 17:03
@github-actions github-actions Bot mentioned this pull request Jun 24, 2026
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.

2 participants