Repository navigation
feat(channels): Channels resolve their own participants; the agent object is the authorization boundary - #2497
Merged
Merged
Conversation
|
| Name | Type |
|---|---|
| agents | Minor |
| @cloudflare/agent-think | Patch |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
Contributor
🟢 agents import sizes: 2 entry points changed, no growth
Changed exports (5)
How this worksEach runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement. Compared |
cjol
marked this pull request as ready for review
October 6, 2026 05:33
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
…ject is the authorization boundary
cjol
force-pushed
the
feat/channels-participant-routing
branch
from
October 6, 2026 14:43
d435f12 to
277afd8
Compare
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.
This PR makes identity and authorization in
agents/experimental/channelsexplicit: each Channel says who its senders are through a requiredparticipantcallback, and the agent object (the Durable Object a route names) becomes the authorization boundary. It resolves the identity and authorization follow-ups from the Channels stack review.Why
webresolver let every browser in asanonymous, so separate browsers shared client tool ownership. Webhook senders became<channelKey>:<actorId>, so an app could not say that a Slack user and a web user were the same person.AiSdkHarnesscreated a session for any id it was given.authorizehook was considered and rejected. It overlaps with tool approvals, and it would still need app bookkeeping so that a client reconnecting to a conversation it created or forked is let back in.route, not an accident. Default routes are namespaced (participant:<id>) so they cannot collide with an app's own route names.routecan keep threads apart, for example(event, raw, participant) => `${participant.id}/${event.thread.id}`for a private object per participant and thread, orroutes.perThreadfor one object shared by everyone in a thread.main, webhook surfaces only have ingress, and feat(channels): Slack shows every turn of a conversation #2438 and feat(channels): Telegram shows every turn of a conversation #2439 will let each Channel say whether a surface follows the conversation or only gets replies to the turns it started.Public API Surface
Added:
web()agents/experimental/channels/webparticipant, optionalroute, optionalmatch(default/channels[/<conversation>])Channel.participantingressoremailIngress; the gateway throws at construction without itChannel.upgrade,ChannelUpgrade,ChannelUpgradeMatchParticipantResult,ChannelParticipantParticipant | string | null; a string is shorthand for{ id }andnullrefusesroutes.perParticipantChannelRouteEvent.participantnullwhen the sender was refusedparticipantonslack(),telegram(),email()webhookon Slack and Telegram; Email receives only when it is setChanged:
ChannelRouteandChannel.routetake(event, raw, participant)instead of(event, raw, context).routeis given, the route is nowparticipant:<id>, where before it was the event's thread id. A participant's separate threads therefore reach one object and share its default conversation, where before each thread had its own.Removed:
ChannelGatewayOptions.web,defaultRouteandfindUser, andGatewayWebIdentityChannelRouteContext,routes.byIdentityandroutes.byUserUserIdentityStore,createUserIdentityStore,linkChannelIdentitiesand their types. Linking a user across Channels is the app's own lookup insideparticipant.ChannelIdentityandidentityKeyremain for keying a provider identity.Code Changes
gateway.ts: webhook dispatch resolves the participant, then the route, then hands both to the agent.participantOfis gone, so the agent receives exactly the participant the app returned. Upgrades go to the first Channel whoseupgrade.matchclaims them. A refused participant gets 401 and a refused route 403. An upgrade no Channel matches is still returned to the Worker.ChannelGateway's docs name both boundaries. The gateway is the trust boundary, so a Channels agent must be reachable only through it: another fetch handler,routeAgentRequestor RPC would let a caller pick its own participant. The agent object is the authorization boundary.web/ingress.tsis new.matchexists so that unrelated WebSocket endpoints in the same Worker are not sent throughparticipantand refused.internal.ts:toParticipantvalidates what an app returns. It rejects an empty id or a wrong shape, so a typo throws rather than becoming a shared identity.#operateandAiSdkHarnessnow explain why there is nothing to add.docs/CONTEXT.mdadds Gateway and Agent object terms, says how a participant is decided, and says that surfaces naming no conversation share the default one.docs/diagrams.md,gateway.tsand the example README no longer describe a route as reaching "a conversation's agent".examples/next/channels: the Worker usesweb(), keeps?as=naming and open rooms, and labels both as demo-only.Compatibility
Everything here is still unreleased. The gateway changeset is amended rather than adding a new one. The open drafts #2438 and #2439 need
participantonslack()andtelegram()when they are rebased, and are where the follow/reply split for webhook surfaces will land.