fix: bootstrap nested sub-agents from root namespace#1546
Merged
Conversation
Fix nested sub-agent facet bootstrap so a facet parent does not need to expose Durable Object namespace helpers such as idFromName. The previous implementation derived each child facet's named Durable Object id from the immediate parent class export, which worked for top-level parents but failed when the parent was itself a facet, such as Chat spawning retained helper agents. The bootstrap path now derives the named id from the root supervisor namespace recorded in the parent path. This preserves the important PartyServer invariant that each facet receives a stable ctx.id.name matching the sub-agent name, while avoiding the incorrect requirement that every intermediate facet must also be a top-level Durable Object namespace. Update the runtime comments and error message to describe the root namespace requirement more accurately, including the remaining root export/binding-name edge case. Add regression coverage that hides idFromName on the immediate facet parent before spawning a nested child, which would have failed under the old implementation. Refresh the multi-ai-chat example to model the intended configuration: only Inbox is listed in new_sqlite_classes, while Chat remains a facet reached through sub-agent routing. Document the corrected id derivation in the living sub-agent routing design doc, and add a patch changeset for the agents package. Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 8a82c02 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 |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
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
This PR fixes nested sub-agent bootstrap when the immediate parent is itself a facet.
The previous bootstrap path derived each child facet's named Durable Object id from the immediate parent class export:
That worked for top-level parents, but it incorrectly required facet parents to expose Durable Object namespace helpers. In the agent starter shape,
Inboxis the root/supervisor Durable Object andChatis a facet. WhenChatthen spawns retained helper agents such asResearcherorPlanner,Chatshould not need to be a top-level Durable Object binding.This PR now derives the named facet id from the root/supervisor namespace recorded in the parent path. That keeps the existing PartyServer invariant that facets get a stable
ctx.id.name === name, while allowing intermediate parents to be facet-only.What changed
_cf_resolveSubAgent()to use the root/supervisor class namespace when constructing the named facet id.FacetCapableCtxtyping so facet-only exports are not modeled as requiring namespace helpers.idFromNamefrom the immediate facet parent export before spawning a nested child. This catches the original failure mode directly.design/sub-agent-routing.mdto document root/supervisor-based facet id construction.examples/multi-ai-chatso onlyInboxis listed innew_sqlite_classes;Chatremains a facet reached through sub-agent routing.examples/multi-ai-chat/env.d.ts.agents.Why
Sub-agents are implemented as Durable Object facets. A facet parent can spawn its own child facets, but that parent is not necessarily exposed as a top-level Durable Object namespace.
The old code accidentally encoded a stronger requirement than the facet model needs: every immediate parent in a nested sub-agent chain had to have
ctx.exports[ParentClass].idFromName. That is false for app shapes like:Inbox # root Durable Object binding Chat # facet Planner # nested facetThe root/supervisor Durable Object namespace is still available and sufficient for creating a named, stable id for PartyServer. The id is used for identity/name stability, not for routing the facet through that namespace.
Test plan
npx vitest --project workers src/tests/sub-agent.test.tsnpm run test:workers -w agentsnpx vite buildinexamples/multi-ai-chatnpm run checknpm run checkstill prints the existingsherifwarning aboutexamples/think-slide-deck/package.jsonmissing, but exits successfully.Notes for reviewers
idwithctx.facets.get(), since the documentedFacetStartupOptions.idtype allowsDurableObjectId | string. In the current PartyServer/workerd test environment that started the facet but did not populatectx.id.name, which brokethis.name. Using the root namespace'sidFromName(name)preserves the existing name behavior.parentAgent(Cls)still resolves throughenv[Cls.name], so directparentAgent()calls from a grandchild to a facet-only direct parent remain a separate limitation. This PR is scoped to bootstrap and creation, not parent-stub lookup.Made with Cursor