# references to DOs in Discord messages *In Smalltalk, JavaScript, and Unix sockets, a message can carry references the receiver can then send messages on. Discord messages can refer to messages, threads, channels, and servers, but not to our Durable Objects. This note works out how they could, after [@endo/chat](https://github.com/endojs/endo-but-for-bots/tree/llm/packages/chat). Written 2026-10-08.* ## webkeys in markdown: works, but discloses The cheapest option is a [webkey](webkeys.md) as a markdown link, `[cookie store](https://webapp.example/store/#)`. It gets the basics right: following it in a browser works, and the key sits in the fragment, so Discord's link-preview fetcher never sends it anywhere. But the message body itself is the leak. Every reader of the channel now holds the capability, and so does Discord: its servers, its logs, its search index, its moderation tooling, and whoever reads those. That is fine for capabilities meant to be public (a public cache page) and wrong for anything like webapp's store id, where holding it means choosing whose session the cache uses. ## what @endo/chat does instead In @endo/chat a message is not a string with secrets in it. Its parser (`message-parse.js`) splits `Here is @my-file for you` into - `strings`: the text between references, - `edgeNames`: the names the *sender* put in the text (`my-file`), - `petNames`: the sender's own names for those references, and the daemon delivers the references themselves beside the text, over CapTP. The receiver sees the edge name and **adopts** the reference under a petname of their own. Three things follow: - **References never appear in text.** Only names do, and names are safe to show anyone. - **Names are per-person.** My `cookie-store` and your `alice's store` can be the same object, and neither name has authority by itself. - **Adopting is an act.** Receiving a message gives you the option to hold a reference, not the reference itself, until you choose a name for it. ## the endo-shaped version for Discord A capsite bot (a Worker serving Discord's HTTP interactions endpoint) plays the daemon's part: - **Petstores.** Each Discord user has a petstore DO: petname → DO reference. Minting files results there directly: webapp's mint could file the store id under `diigo cookie store` in Alice's petstore instead of handing it to her password manager. - **Sending.** A slash command, e.g. `/send to:@bob text:"here is @cookie-store"`. The bot parses the text like `parseMessage`, looks up each `@name` in the sender's petstore, posts the text (names only) to the channel, and files `{ discord message id → recipients, edgeName → reference }` in a mailbox DO. - **Adopting.** The posted message carries an **Adopt** button. On a click, Discord tells the bot who clicked (in a signed interaction); if they are a recipient, the bot answers with an *ephemeral* message, shown only to them, asking for a petname, then files the reference in *their* petstore. - **Using.** References are exercised through the bot by petname (`/call cookie-store set cookie …`, or a link to a page the bot serves). The reference stays inside our Workers. ### what this does and doesn't keep from Discord Ephemeral replies hide content from other channel readers, but **not from Discord**: they still pass through its servers. So the design above never sends a reference through Discord at all, ephemeral or not; only petnames, edge names, and message ids cross it. Handing over a webkey in an ephemeral reply would be a weaker variant: hidden from the channel, still disclosed to the infrastructure. What Discord *does* hold is identity: the bot believes Discord about who clicked **Adopt**. A compromised Discord account, or Discord itself, could therefore adopt references meant for that user. That is the same trust users already put in Discord for their messages, but it should be stated where references matter more than chat does. ## open questions - **Durable references across RPC.** A petstore entry is a DO id plus enough to say what kind of thing it is (webapp instance, header store, cache). Getting from that back to a live RpcTarget is the marshalling-layer question from the cookie-rotation work (`docs/cookie-rotation-wiring.html` on the `webapp-cookie-rotation` branch). - **Petstore ids.** `idFromName(discordUserId)` is convenient but ambient: any worker with the namespace binding can recompute it. Acceptable if only the bot has the binding; otherwise mint with `newUniqueId` and keep the mapping in one place. - **Leaving Discord.** The same mailbox and petstore could back a web page or @endo/chat itself; Discord would then be one front end of several.