Document facet bootstrap pattern, no runtime change#386
Merged
Conversation
Adds README + setName() docstring guidance pointing facet users at the explicit FacetStartupOptions.id pattern, plus an end-to-end test fixture (FacetParent / FacetChild) that pins both the implicit-id workerd contract and the recommended explicit-id behavior. Per the Cloudflare facet docs, passing `id: someBoundDoNamespace.idFromName(facetName)` to ctx.facets.get() gives the facet its own ctx.id.name, so partyserver's existing name getter does the right thing without any setName/__ps_name override mechanism. The runtime is byte-identical to 0.5.2. Also bumps sibling packages' devDependency on partyserver from ^0.5.1 to ^0.5.2 for consistency, and tidies CHANGELOG formatting. Made-with: Cursor
🦋 Changeset detectedLatest commit: 7b150f8 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 |
hono-party
partyfn
partyserver
partysocket
partysub
partysync
partytracks
partywhen
y-partyserver
commit: |
Merged
5 tasks
threepointone
added a commit
to cloudflare/agents
that referenced
this pull request
Apr 26, 2026
* chore(deps): bump partyserver to ^0.5.3 and roll up other dep updates
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
* Migrate facet bootstrap to explicit FacetStartupOptions.id
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
* Add changeset for facet bootstrap migration (minor)
Made-with: Cursor
* changeset: patch instead of minor
Made-with: Cursor
* Update agents-facet-explicit-id.md
* docs: parent-class requirements + freshen test comment
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
* fix(types): tighten FacetCapableCtx.exports to express runtime contract
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
* refactor(types): use DurableObjectFacets from workers-types
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
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
Adds documentation and end-to-end test coverage for using PartyServer with Durable Object Facets. No runtime behavior change — the
Serverclass (namegetter,setName(),#hydrateNameFromLegacyStorage,#ensureInitialized) is byte-identical to 0.5.2.What this fixes (and what it doesn't)
Facets spawned via
ctx.facets.get(name, factory)without an explicitidinFacetStartupOptionsinherit the parent DO'sctx.id— includingctx.id.name. PartyServer'snamegetter readsctx.id.namestraight through, so on an implicit-id facetthis.namereturns the parent's name rather than the facet's logical name. This is faithful to the workerd contract but it's almost never what framework authors expect.The fix is at the call site, not in PartyServer: pass
id: someBoundDoNamespace.idFromName(facetName)(or the equivalent viactx.exports[BoundClassName].idFromName(facetName)) toctx.facets.get(...). The facet then gets its own nativectx.id.name === facetNameand PartyServer'snamegetter does the right thing automatically. NosetName()is required, no__ps_namestorage record is written, and cold-wake recovery happens for free because the factory re-runs andidFromNameis deterministic.What ships
.namedescription; added a new "Using PartyServer with Durable Object Facets" section that walks through the recommended pattern with a code example, calls out the implicit-id footgun, and warns that plain-stringidvalues are not a substitute foridFromName(facetName)(workerd treats string ids asidFromString-like, so the resulting facet has noctx.id.name).setName()docstring — clarified that facets are NOT asetName()use case; pointed readers at the explicit-idpattern. ThesetName()ctx.id.namemismatch throw is preserved as a typo guard for theidFromNamehappy path.#hydrateNameFromLegacyStoragedocstring — corrected to no longer claim that Cloudflare Agents facets use this fallback.FacetParent/FacetChildfixtures plus adescribe("facets …")block inindex.test.tsthat covers:this.namereturns the parent's name; documentation-via-test so future readers aren't surprised)env.SomeNs.idFromName)ctx.exports[BoundClass].idFromName(no env knowledge required — the recommended pattern for framework code)id(negative case: produces a facet withctx.id.name === undefined,this.namethrows)hono-party,partysub,partysync,partywhen,y-partyserver) — devDependency bumped frompartyserver: ^0.5.1→^0.5.2for consistency..changeset/facet-name-resolution.md, patch bump.Why docs/tests instead of a runtime fix
The previous draft of this work added a runtime override mechanism in
Server(getter precedence flip, hydrate gate change, removed setName guard). That worked but introduced behavior changes for non-facet users (silent override onsetName()mismatch, ~1ms storage GET per cold wake on every DO). With the discovery thatFacetStartupOptions.idis the documented Cloudflare API for "give this facet its own identity," the override mechanism is unnecessary. We're going with the runtime contract instead of around it, which:ctx.id;The Cloudflare Agents repo is migrating its
_cf_initAsFacetto passidtoctx.facets.get()in a separate PR; this PR is the partyserver-side documentation that makes that pattern discoverable for everyone else.Test plan
npm run check:testpasses (60/60, including 3 new facet tests against the realctx.facets.get()API)npm run check:typepasses (all 41 projects)npm run check:lintpassesMade with Cursor