micro / micro/mu

Chat protocol consolidation: remaining XMPP, MUC and shared-store work

Open
#1,471 0 comments 0 reactions 0 assignees View on GitHub

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>.json and its 24-hour prune go.
  • agent/chat becomes a participant that joins a room, exactly as agent/mail reacts 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

  1. XMPP is the one chat protocol, including for the browser (RFC 7395)
  2. Rooms are MUC (XEP-0045); room_<id>.json is deleted and rooms live in the record
  3. Presence, on /agent
  4. Rendering by genre: t.Client chooses thread or stream
  5. /agent's rail is chat; /inbox keeps mail
  6. agent/chat joins rooms as a participant rather than subscribing to an event
  7. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.