Migrate facet bootstrap to explicit FacetStartupOptions.id#1393
Merged
Conversation
Bumps partyserver from ^0.4.1 to ^0.5.3 across the workspace (root and
all examples / experimental / packages / sites that referenced it),
plus a sweep of other dependency updates that landed during this
maintenance pass:
- @cloudflare/vite-plugin: 1.33.0 -> 1.33.2
- @cloudflare/vitest-pool-workers: 0.14.8 -> 0.15.0
- @cloudflare/workers-types: 4.20260420.1 -> 4.20260425.1
- @openai/agents: 0.8.4 -> 0.8.5
- @vitest/{browser,browser-playwright,runner}: 4.1.4 -> 4.1.5
- nx: 22.6.5 -> 22.7.0
- (plus consumer-side bumps in examples/* and experimental/*)
The partyserver bump is the load-bearing one: 0.5.3 ships docs and
test coverage for using PartyServer with Durable Object Facets via
explicit FacetStartupOptions.id, which the next commit consumes in
the agents framework itself. Runtime behavior of partyserver is
unchanged from 0.5.2, so this commit alone is non-breaking.
No code changes; just package.json + package-lock.json.
Made-with: Cursor
Drops the __ps_name storage write and setName() bootstrap from _cf_initAsFacet in favor of the documented Cloudflare facet API: pass id: parentNs.idFromName(name) to ctx.facets.get() so the facet gets its own ctx.id.name. PartyServer's existing 0.5.x name getter then resolves this.name correctly without any override mechanism, storage write, or cold-wake hydrate cost. Background: facets spawned without an explicit id inherit the parent DO's ctx.id, so on a facet ctx.id.name was the parent's name, and this.name silently misreported. The fix lives at the call site, not in partyserver. See #1385 for the full investigation and cloudflare/partykit#386 for the partyserver-side documentation. Changes: - _cf_resolveSubAgent: pass id: parentNs.idFromName(name) where parentNs = ctx.exports[this.constructor.name]. The parent agent is always bound (it's the entry-point DO), so its namespace is always available via ctx.exports. The id is opaque + a name; nothing routes through the namespace at runtime for facets. - _cf_initAsFacet: drops the __ps_name storage write entirely; drops the setName(name) call. Just persists cf_agents_is_facet and cf_agents_parent_path, then __unsafe_ensureInitialized() to fire onStart(). Adds a defensive `this.name !== name` guard so any future bug in the parent's id construction surfaces with a clear error instead of silently mis-identifying the facet. - alarm() docstring: updated to reflect that this.name resolves from ctx.id.name on the happy path (including for facets). - New error-path coverage: if the parent class isn't bound as a DO namespace, throw a descriptive error pointing at wrangler.jsonc. If this.constructor.name looks minified (heuristic regex), append a hint about preserving class names (e.g. esbuild keepNames). - New tests: TestUnboundParentAgent and TestMinifiedNameParentAgent fixtures (parent classes exported under a different name from their declaration) exercise both error paths. Existing test "should set this.name to the facet name" already covers the happy path. - MCP test cleanup: removes vestigial setName("default") + onStart() call pairs from create-oauth-provider, oauth2-mcp-client, and wait-connections-e2e tests. These were originally needed for partyserver 0.4.x bootstrap but became actual ctx.id.name mismatches under partyserver 0.5.x's strict guard. Renamed setName args to match idFromName names where the test addresses by name. All 1008 tests pass (up from 1006 — the 2 new error-path tests). The full workers test project, all sub-agent tests, alarms tests, and MCP tests are green against the published partyserver 0.5.3. Closes #1385 Made-with: Cursor
🦋 Changeset detectedLatest commit: 3fef2aa 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 |
Made-with: Cursor
Made-with: Cursor
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Adds a parent-class requirements note to docs/sub-agents.md describing the implicit contract that the parent must be bound as a DO namespace and have its class name preserved by the bundler. This makes the new unbound-parent / minified-name error path discoverable in the docs. Also clarifies the hibernation note (this.name is now carried by ctx.id, not hydrated from storage), and fixes a stale comment in the abort-and-re-access test that still referenced `__ps_name` (now only the agent-specific keys are written by _cf_initAsFacet). Made-with: Cursor
Per code review feedback on PR #1393: the previous type `Record<string, DurableObjectClass>` was too narrow and didn't explain why `ctx.exports[parentClassName].idFromName(...)` works for facet-only classes that aren't bound in `durable_objects.bindings` (e.g. `OuterSubAgent` spawning `InnerSubAgent` in the test fixtures). The workerd runtime contract is: any class registered via `migrations.new_sqlite_classes` (or `new_classes`) — bindings or not — is exposed in ctx.exports as both a `DurableObjectClass` AND a `DurableObjectNamespace`. The intersection is what makes the explicit-id facet bootstrap work for nested sub-agents. Updates the FacetCapableCtx.exports type to `Record<string, (DurableObjectClass & DurableObjectNamespace) | undefined>` with a docstring explaining the runtime semantics, and drops the now-redundant `as unknown as DurableObjectNamespace | undefined` cast at the call site in `_cf_resolveSubAgent`. Empirically verified: 47/47 sub-agent tests still pass, all 75 typecheck projects green. Made-with: Cursor
Drops our hand-rolled inline facets shape in favor of DurableObjectFacets from @cloudflare/workers-types now that the bumped peer (^4.20260425.1) ships it natively. Also adds a top-of-interface docstring explaining why FacetCapableCtx exists at all (we widen ctx.exports from the MainModule-keyed Cloudflare.Exports to a generic Record because we can't see the consumer worker's main module from inside this library). No runtime change. 79/79 sub-agent tests pass, all 75 typecheck projects green. Made-with: Cursor
Merged
threepointone
added a commit
that referenced
this pull request
Apr 26, 2026
Add a new section summarizing four post-#1384 maintenance PRs (#1393–#1396) and their effects on the multi-session assistant plan. Notes include facet bootstrap via explicit FacetStartupOptions.id (#1393) which removes the storage write/setName shim and makes MyAssistant.name resolve natively; the new beforeStep hook and TurnConfig.output passthrough (#1394); SubmitConcurrencyController being moved into agents/chat (#1395); and message-reconciler moved into agents/chat with Think now reconciling incoming messages (#1396). Clarifies that the chat-shared-layer has been incrementally hoisted into agents/chat and highlights the lack of a vitest+workers harness for examples/assistant, recommending a minimal test harness before hoisting useAgentChat.
4 tasks
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
Migrates facet bootstrap in
Agentfrom the__ps_namestorage-write hack to the documented Cloudflare facet API: passid: parentNs.idFromName(name)toctx.facets.get()so the facet gets its ownctx.id.name. PartyServer's existing 0.5.xnamegetter then resolvesthis.namecorrectly without any override mechanism, storage write, or cold-wake hydrate cost.Closes #1385.
Background
partyserver0.5.0 madethis.nameresolve fromctx.id.namenatively, eliminating the need forsetName()round-trips for normalidFromName/getByNameDOs. That change initially missed facets — but for a different reason than #1385 originally diagnosed.The original issue assumed facets have
ctx.id.name === undefinedand proposed thatsetName(facetName)should be the bootstrap primitive. Investigation showed that's wrong: facets spawned viactx.facets.get(name, factory)without an explicitidinFacetStartupOptionsinherit the parent DO'sctx.id— includingctx.id.name. So on a facet,ctx.id.namewas the parent's name, andthis.namesilently misreported. With partyserver 0.5.x's strictsetName()guard (throws onctx.id.namemismatch), the proposedsetName(facetName)migration would have failed immediately.The actual fix lives at the call site, not in partyserver.
FacetStartupOptions.idis optional and documented — when supplied, the facet sees that id as its ownctx.id. Passid: ctx.exports[ParentClassName].idFromName(facetName)toctx.facets.get()and the facet gets a realctx.id.name === facetName. Partyserver's existingnamegetter then does the right thing automatically.See:
idfacet pattern. Zero runtime change.What ships
This PR is split across two commits for review clarity:
Commit 1 —
chore(deps): bump partyserver to ^0.5.3 and roll up other dep updatespartyserverfrom^0.4.1to^0.5.3across the workspace.@cloudflare/vite-plugin,@cloudflare/vitest-pool-workers,@cloudflare/workers-types,@openai/agents,@vitest/*,nx).package.json+package-lock.json.Commit 2 —
Migrate facet bootstrap to explicit FacetStartupOptions.idCode changes (
packages/agents/src/index.ts):_cf_resolveSubAgentnow passesid: parentNs.idFromName(name)whereparentNs = ctx.exports[this.constructor.name]. The parent agent is always bound (it's the entry-point DO), so its namespace is always available viactx.exports. The id is opaque + a name; nothing routes through the namespace at runtime for facets._cf_initAsFacetdrops the__ps_namestorage write entirely and drops thesetName(name)call. Just persistscf_agents_is_facetandcf_agents_parent_path, then__unsafe_ensureInitialized()to fireonStart().this.name !== nameguard inside_cf_initAsFacetso any future bug in the parent's id construction surfaces with a clear error instead of silently mis-identifying the facet.wrangler.jsonc. Ifthis.constructor.namelooks minified (heuristic regex^_*[a-z][a-z0-9]{0,2}$), append a bundler-config hint (e.g. esbuildkeepNames: true).alarm()docstring: updated to reflect thatthis.nameresolves fromctx.id.nameon the happy path (including for facets).Test coverage:
tests/sub-agent.test.tsplusTestUnboundParentAgentandTestMinifiedNameParentAgentfixtures (parent classes exported under a different name from their declaration). Both error paths now have regression guards.__ps_nameapproach.MCP test cleanup:
setName("default")+ explicitonStart()call pairs fromcreate-oauth-provider,oauth2-mcp-client, andwait-connections-e2etests. These were originally needed for partyserver 0.4.x bootstrap but became actualctx.id.namemismatches under 0.5.x's strict guard. RenamedsetNameargs to matchidFromNamenames where the test addresses by name.Test plan
npx vitest run --project workerspasses — 1008 tests, 7 skipped, 0 failures (up from 1006 — the 2 new error-path tests)npm run checkpasses — 75 typecheck projects green, format/lint clean, exports validpartyserver@0.5.3from npmMade with Cursor