Skip to content

fix: bootstrap nested sub-agents from root namespace#1546

Merged
threepointone merged 1 commit into
mainfrom
fix-nested-subagent-facet-bootstrap
May 18, 2026
Merged

fix: bootstrap nested sub-agents from root namespace#1546
threepointone merged 1 commit into
mainfrom
fix-nested-subagent-facet-bootstrap

Conversation

@threepointone
Copy link
Copy Markdown
Contributor

@threepointone threepointone commented May 17, 2026

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:

ctx.exports[parentClassName].idFromName(name)

That worked for top-level parents, but it incorrectly required facet parents to expose Durable Object namespace helpers. In the agent starter shape, Inbox is the root/supervisor Durable Object and Chat is a facet. When Chat then spawns retained helper agents such as Researcher or Planner, Chat should 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

  • Updated _cf_resolveSubAgent() to use the root/supervisor class namespace when constructing the named facet id.
  • Relaxed the internal FacetCapableCtx typing so facet-only exports are not modeled as requiring namespace helpers.
  • Updated bootstrap comments to explain why the root namespace is used for nested facets.
  • Tightened the root bootstrap error message so it says the root class must be exported under the class name and registered as a Durable Object binding, without implying every immediate parent facet must be bound.
  • Added a regression test that hides idFromName from the immediate facet parent export before spawning a nested child. This catches the original failure mode directly.
  • Updated existing tests and fixtures from "parent binding" language to "root binding" language.
  • Updated design/sub-agent-routing.md to document root/supervisor-based facet id construction.
  • Updated examples/multi-ai-chat so only Inbox is listed in new_sqlite_classes; Chat remains a facet reached through sub-agent routing.
  • Regenerated examples/multi-ai-chat/env.d.ts.
  • Added a patch changeset for 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 facet

The 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.ts
  • npm run test:workers -w agents
  • npx vite build in examples/multi-ai-chat
  • npm run check

npm run check still prints the existing sherif warning about examples/think-slide-deck/package.json missing, but exits successfully.

Notes for reviewers

  • I tried using a plain string id with ctx.facets.get(), since the documented FacetStartupOptions.id type allows DurableObjectId | string. In the current PartyServer/workerd test environment that started the facet but did not populate ctx.id.name, which broke this.name. Using the root namespace's idFromName(name) preserves the existing name behavior.
  • Historical changelog and WIP planning references to PR Migrate facet bootstrap to explicit FacetStartupOptions.id #1393 were left unchanged because they describe past behavior. The current living design doc was updated instead.
  • parentAgent(Cls) still resolves through env[Cls.name], so direct parentAgent() 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


Open in Devin Review

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-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 17, 2026

🦋 Changeset detected

Latest commit: 8a82c02

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
Copy link
Copy Markdown

pkg-pr-new Bot commented May 17, 2026

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: 8a82c02

Copy link
Copy Markdown
Contributor

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

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 4 additional findings.

Open in Devin Review

@threepointone threepointone merged commit c935d7c into main May 18, 2026
4 checks passed
@threepointone threepointone deleted the fix-nested-subagent-facet-bootstrap branch May 18, 2026 08:34
@github-actions github-actions Bot mentioned this pull request May 18, 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.

1 participant