Chat protocol consolidation: remaining XMPP, MUC and shared-store work
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 434
- Forks
- 21
- Avg merge
- 18m
- Merged PRs (30d)
- 326
Description
IMAP follow-up — PR #1824
The IMAP gaps noted during cleanup are now addressed in code: web/CLI threads are included, assistant replies are incoming, and authenticated mail-client replies queue work on the same owned conversation. Bridged read flags and deletes persist in the IMAP view; source conversations remain available on the web. Web Inbox polls for messages and list changes automatically. See #1823 for the remaining unified asynchronous/client-identity work and verification limits. This does not complete the XMPP/MUC protocol work below; no live IMAP verification is claimed.
Current status — 18 September 2026
The earlier product requirement that Inbox exclude chat and /agent own all chat is superseded. Current Inbox intentionally includes channel-labelled conversations, with channel-specific presentation and retained navigation (#1814/#1818); #1804 owns the current assistant experience. Keep the underlying XMPP/WebSocket, MUC, presence and store-consolidation work open alongside #89 rather than treating that old page split as an implementation requirement.
Related source finding while answering the owner's IMAP question: inbox/imapbridge.go bridges incoming non-mail thread records, excludes Web/CLI threads through thread.Arrived, and renders the user's and agent's messages as sent mail. It does not expose standalone Saved notes/docs/apps/schedules. IMAP is therefore not a full mirror of the current web Inbox. Reconcile the intended client contract before broadening it; no live IMAP verification or fix is claimed here.
The model this comes from
Four layers, distinguished by how much the consumer already knows rather than by capability:
| We supply | They supply | Layer | Standard |
|---|---|---|---|
| state | the exact call | services | HTTP API — open |
| a described menu | the goal and the reasoning | tools | MCP — open, and it won |
| the reasoning too | the goal | agents | chat — universal form, no open protocol |
| the occasion too | a policy, once | work | forming (A2A tasks, MCP Tasks) |
Two rungs have open standards. The third has a universal form and every implementation of it is proprietary, because chat is the product surface and nobody opens their product surface. Serving XMPP is how that rung gets an open one: the same interaction model everyone has, over a protocol any existing client already speaks.
Then: agents via chat, work via mail. Mail is the work protocol — asynchronous, durable, an inbox is a queue, a thread is a task's history, and forward/cc/reply-all is delegation. People have handed each other work over it for forty years without needing to define "work".
Two genres, not two data sets
internal/thread holds both. What differs is how a conversation reads:
- Thread — a correspondence. Composed messages with envelopes: subject, addresses, quoted history, signatures. Opened, read top to bottom, replied to. Latency assumed.
- Chat — a stream. Short spoken turns, no subject, no quoting. You are in it; the unit of attention is the conversation and the messages are its texture.
This is not cosmetic. The genre decides whether the page is legible at all. A mail thread rendered as a chat stream repeats quoted history on every line. A chat rendered as a thread is fifty one-line messages each with a header. One conversation cannot be shown both ways with either working.
The discriminator already exists — thread.Client — and nothing renders from it. So:
genre (thread.Client) |
carriers | page | reads as |
|---|---|---|---|
mail |
SMTP, IMAP, submission | /inbox |
threads |
chat |
XMPP, WebSocket, the web page | /agent |
a stream |
The client is the genre. The carrier is the transport, and a detail.
No new page
/messages was considered and rejected. thread.Message is a struct; naming a page after it exposes the storage, exactly as /threads would. A page is named for what it is for, not for what it holds — /inbox and /agent survive replacing internal/thread, and /messages does not.
And "agents via chat" means the agent page is the chat page. The rail on /agent is already every conversation on every client (see the package comment on inbox/conversation.go). A third view of that list is the duplication this repository has spent a lot of effort deleting.
What is actually wrong today
1. Two chat implementations in one package.
service/chat/chat.go 1494 lines bespoke websocket protocol, room_<id>.json, pruned at 24h
service/chat/xmpp*.go ~790 lines XMPP, writes to internal/thread
Two protocols, two stores. xmppRoom() prefixes xmpp_ so the namespaces cannot collide, which is the admission. Mail has one model — SMTP and IMAP are carriers over one store.
The fix is already named in service/chat/xmpp.go's own package comment: "The rooms in this package are a bespoke websocket protocol with a bespoke roster; MUC is the standard version of exactly that."
- RFC 7395 — XMPP over WebSocket. The browser speaks XMPP. The web page stops being a second protocol and becomes a second carrier. This is what Converse.js and Movim do.
- XEP-0045 — MUC. Article rooms become MUC rooms. Presence, roster, join and leave, history: specified, not hand-written.
- One store.
internal/thread.room_<id>.jsonand its 24-hour prune go. agent/chatbecomes a participant that joins a room, exactly asagent/mailreacts to mail arriving — a JID in the room rather than a special case wired to an event.
2. Presence does not exist.
It is the one thing XMPP gives that mail structurally cannot, and it is what a chat surface should be built around. A message list without it is a slower inbox. The rail should say who is here — the agent always is, which is the point of it.
3. Nothing renders by genre.
inbox/conversation.go branches on who wrote a message — agent gets markdown, a person gets escaped text, mail gets mail.Rendered. It does not branch on genre, so a chat conversation opened from the rail arrives with thread furniture: address lines, quoted-text folds, and a Reply control offering to send mail.
4. A person-to-person conversation has nowhere to be.
internal/thread records it and Parties knows who is on it. The rail treats a conversation as being with an agent.
5. A chat has a subject it should not have.
service/chat/xmpp_record.go calls thread.Name with the other party's address. A chat has no subject; the field was filled because it was there, and it renders as a title on a page that should not have one.
Scope
- XMPP is the one chat protocol, including for the browser (RFC 7395)
- Rooms are MUC (XEP-0045);
room_<id>.jsonis deleted and rooms live in the record - Presence, on
/agent - Rendering by genre:
t.Clientchooses thread or stream /agent's rail is chat;/inboxkeeps mailagent/chatjoins rooms as a participant rather than subscribing to an event- Drop the subject on chat conversations
Closes #59 when done. Folds in #24 — "make /agents a conversation, not a token inventory" is the same observation arriving from the model: the plural page is an inventory about a thing whose entire interface is conversation.
Done when
A conversation with your agent reads the same in Conversations, in Dino, and in the browser, because all three are XMPP clients of the same server — and opening one from /inbox is not possible, because that is not where chat lives.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with service/chat/xmpp.go and service/chat/xmpp_record.go, then inspect service/chat/chat.go, inbox/conversation.go, and the internal/thread model. Trace how browser chat, XMPP rooms, presence, and agent/chat currently connect to separate stores. Done means chat uses XMPP and MUC across clients, shares the thread record, renders by genre, supports presence, and keeps mail in /inbox.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, databases, frontend, networking
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100