refactor(agents): extract facets into a dynamic-agents capability, reposition for isolation not chat sessions (#2193)
* refactor(agents): extract dynamic-agent types, identity, and RPC bridges
First step of moving the facet (sub-agent) machinery out of the Agent
god class into packages/agents/src/dynamic-agents/. Pure motion: the
moved types, identity helpers, and connection/reply bridges keep their
behavior; index.ts imports them under the old local names. Public
exports (SubAgentClass/SubAgentStub) are unchanged, now aliasing the
module's DynamicAgent* names. No wire- or storage-visible identifier
is renamed.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): move the sub-agent registry into dynamic-agents/registry
The cf_agents_sub_agents table (identity versioning, has/list/record/
forget) now lives in DynamicAgentRegistry with a two-method SQL host
port. Agent keeps hasSubAgent/listSubAgents overloads and the _cf_
identity entry point as thin delegates. Table and column names are
storage-frozen and unchanged.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): move lifecycle routing and root facet bookkeeping into DynamicAgents
Introduces the DynamicAgents class with an explicit host port
(DynamicAgentHostPort) that documents every Agent internal the facet
machinery touches. Moves lifecycle route addressing/traversal, root
alarm-owner resolution, facet-prefix cleanup, facet keepAlive leases,
and the cf_agents_facet_runs row index. Agent keeps all _cf_* RPC
entry points as one-line delegates.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): move facet resolution, teardown, and fiber recovery into DynamicAgents
resolveSubAgent (the ctx.facets.get bootstrap + identity handshake),
abort/delete, recursive descendant destroy, workflow path invocation,
and the root-side facet fiber recovery scan now live on the
DynamicAgents class; the registry is owned by it. Agent keeps _cf_*
RPC entry points and public methods as delegates, plus a private
_runFacetInitInvocation helper so the module never touches the
invocation context machinery directly.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): move WebSocket forwarding and facet invocation into DynamicAgents
The parent-side frame forwarding, facet-side virtual connections,
connection-operation queues and broadcast barrier, /sub/ request
forwarding, stub invocation (single-expression RpcProperty dispatch
preserved verbatim), facet init handshake, and connection hydration
now live on DynamicAgents, with module-owned state (bridge ALS,
virtual-connection map, operation tails) and terse method names
(invoke, invokePath, forward, resolve, delete, init, ...). Agent keeps
every _cf_* RPC entry point as a delegate; calls that subclasses
override (_cf_broadcastToSubAgent, _cf_checkRunFibersForFacet) still
dispatch through the host so overrides keep intercepting. The WS
multiplexing keys move to the module; wire and storage names are
unchanged. Full workers project: 1966 tests green.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): move facet-context restore into DynamicAgents
The startup restore of cf_agents_is_facet / cf_agents_facet_name /
cf_agents_parent_path plus best-effort virtual-connection hydration
becomes DynamicAgents.restoreFacetContext(); Agent keeps only the
startup-span wrapper so ordering relative to onStart is unchanged.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* feat(agents): this.dynamicAgents capability facade over facet children
Adds the public dynamic-agents capability surface: an @experimental
this.dynamicAgents accessor exposing get/abort/delete/has/list over
the same machinery as the deprecated subAgent()/abortSubAgent()/
deleteSubAgent()/hasSubAgent()/listSubAgents(), which stay working
with @deprecated pointers. DynamicAgentsInternal now registers with
lifecycle.use() under capabilityId "dynamic-agents" (its hot paths
remain composition-root wired; the class doc explains why the runner
hooks cannot express them). New agents/dynamic-agents subpath export
carries DynamicAgents, DynamicAgentClass, and DynamicAgentStub; the
main entry gains no new surface. Facade behavior pinned against the
legacy API in dynamic-agents-api.test.ts.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* refactor(agents): no new package surface — legacy sub-agent methods ride this.dynamicAgents
Drops the agents/dynamic-agents subpath export; this.dynamicAgents on
the Agent class is the only public addition. subAgent/abortSubAgent/
deleteSubAgent/hasSubAgent/listSubAgents now delegate through the
facade so both names are one code path.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* feat(examples): next/chats and next/dynamic-agents
examples/next/chats: the recommended many-chats shape — one top-level
ChatAgent DO per conversation plus a per-user UserAgent index DO that
chats push {title, lastMessage, updatedAt} into. Listing, ordering,
and cross-chat search read only the index; deletion is destroy() plus
one row. Five workers-pool tests pin the pattern.
examples/next/dynamic-agents: what facets are for — a Supervisor agent
stores user-submitted DO code, loads it via Worker Loader, and mounts
it as a facet with its own SQLite. Demonstrates supervised abort with
surviving storage, code upgrades over stable state, capability
confinement (globalOutbound: null), and full teardown. Four tests,
including loader-backed facets under vitest-pool-workers.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* feat(examples): full-stack React + Vite UIs for next/chats and next/dynamic-agents
Converts both examples to the mcp-client-style stack: Vite +
@cloudflare/vite-plugin + Tailwind/Kumo, served with 'pnpm run start'.
chats: sidebar lists/searches via one useAgent connection to the
per-user index DO; each open chat gets its own WebSocket straight to
that chat's ChatAgent DO.
dynamic-agents: an editor + invoke panel drives the Supervisor —
create a gadget, edit its code, deploy (aborts the facet, loads the
new class over the same storage), invoke, abort, delete, all visible
in a live log.
Pinned react/react-dom to the exact 19.2.7 the agents workspace
package resolves — pnpm had picked 19.2.8 for these two new packages,
producing two React copies and an Invalid hook call crash in
useAgent/partysocket.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* docs: reposition sub-agents as dynamic agents; light touches + multi-ai-chat caveat
- docs/agents/sub-agents.md: full rewrite. Facet semantics section
(separate isolate, own SQLite, no alarms, depth limit, machine-pinned
tree, design intent); when-to-use table built on the decision rule
(facet = parent-supervised child that must live inside the parent;
independent peer = its own DO); corrects the false claim that WS
frames flow directly to the child post-upgrade (they don't — every
frame wakes the root and is forwarded over RPC); this.dynamicAgents
documented as the primary API with the legacy names as a migration
table; links to both new examples.
- index.md / long-running-agents.md / agent-tools.md: one-sentence
repositioning touches, no restructuring.
- examples/multi-ai-chat/README.md: caveat pointing at
examples/next/chats for the many-chats case.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* chore: fix unused import + dependency version alignment (sherif/oxlint clean)
- packages/agents/src/index.ts: drop the now-unused isValidParentPath
import (moved to restoreFacetContext).
- examples/next/chats, examples/next/dynamic-agents: pin react/react-dom
and vitest to the exact versions the rest of the monorepo uses so
sherif's multiple-dependency-versions check passes.
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* chore: add changeset
Claude-Session: https://claude.ai/code/session_01KZ4booD9Pt5jXkRVmjhdb7
* fix(agents): address dynamic-agent review findings
- load the selected gadget's stored source before deploying edits
- order chat activity in the User DO and prevent delayed pushes from recreating deleted rows
- remove dead extraction delegates and pin the public Agent.dynamicAgents type surface
- add a proposed RFC for a User hub plus independent top-level Chat DOs; no Think or AIChatAgent topology change in this PR
* fix(example): make chat index projection idempotent
- key message writes by caller-supplied ids so retries do not duplicate them
- project complete chat snapshots with monotonic revisions and ignore stale delivery
- keep message acceptance independent from index availability and provide pull repair
- cover failed delivery, repair, stale snapshots, deletion, and stable retry results
* fix(example): close chat projection and deletion races
- accept messages idempotently by caller-supplied id
- project complete revision-fenced snapshots and repair stale index rows by pull
- gate browser routes through the User catalog instead of exposing physical Agent names
- mark catalog rows deleting before Chat destruction and refuse stale-handle writes
- keep Chat activity time primary, using User sequence only for deterministic ties
* refactor(example): keep the chat topology example focused
- remove idempotency, repair, gated-routing, and deletion protocols from the example
- document its User index as a best-effort eventually consistent projection
- keep production consistency concerns in the separate RFC
- retain deterministic User ordering and fix colon-containing user ids
* refactor(example): keep the chat topology example focused
- remove idempotency, repair, gated-routing, and deletion protocols from the example
- document its User index as a best-effort derived projection
- keep production consistency concerns in the separate RFC
- retain deterministic ordering, reject older metadata timestamps, and support colon-containing user ids
* fix(agents): address public API review
- export DynamicAgentClass and DynamicAgentStub alongside legacy aliases
- mark the new public Agent.dynamicAgents surface as a minor release
- order chat lists by activity time with deterministic receipt-order ties
- cover delayed cross-DO metadata delivery M
Matt committed
87bd59401f4644d1fd2c82cae439e9848ce437b1
Parent: 58c586a
Committed by GitHub <noreply@github.com>
on 9/1/2026, 3:41:19 PM