Campus Messaging over Matrix
Campus Messaging is the protocol-independent product surface for live conversations. Matrix is the transport, federation, room, sync, and encryption implementation; applications and agents call the campus-matrix.* MCP tools and never receive a Matrix access token, password, device key, homeserver admin credential, or raw sync response.
The system-supplied campus-matrix package is presented to users as Messages. Opening it for the first time calls GET /v1/services/messaging/identity. CampusOS derives the expected Matrix user ID from the authenticated immutable account binding and asks the trusted Matrix bridge to provision it idempotently:
Campus account alice / acct_…
│ authenticated installation service capability
▼
CampusOS trusted Messages service executor
│ { accountId, campusName, campusAddress, matrixUserId }
▼
Trusted Matrix bridge ── persistent Matrix SDK device + crypto store
│
▼
Federating homeserver identity @alice:campus.hostThe app supplies neither accountId nor matrixUserId; CampusOS always derives both. A bridge result is accepted only when it reports the exact expected user ID and a provisioned Matrix transport identity. Direct-conversation creation additionally fails closed unless the bridge reports an encrypted room.
Application API
Browser applications and agents use the campus-matrix.* MCP tools. Their trusted service executor uses the capability-authenticated /v1/services/messaging routes internally:
| Operation | Permission | Meaning |
|---|---|---|
GET /identity | messaging.read(*) | Idempotently provision and return the bound public identity. |
GET /contacts/:address | messaging.resolve(*) | Resolve a Campus address or full Matrix user ID. |
GET /conversations | messaging.read(*) | List high-level encrypted conversation summaries. |
POST /conversations/direct | messaging.send(*) | Resolve a recipient and open/reuse an encrypted direct conversation. |
GET /conversations/:id/messages | messaging.read(*) | Return decrypted high-level message objects. |
POST /conversations/:id/messages | messaging.send(*) | Send a text message with an idempotency transaction ID. |
POST /sync | messaging.read(*) | Long-poll bounded high-level changes, with an opaque cursor. |
Responses are allowlisted. Matrix ciphertext, access tokens, device lists, one-time keys, room keys, raw state events, and raw /sync documents are discarded even if a bridge accidentally returns them. Conversation IDs and cursors are opaque application values.
Example browser use:
const direct = await campus.mcp.call({
name: 'campus-matrix.direct',
arguments: { to: '@friend:matrix.org' },
})
const conversation = direct.structuredContent.conversation
await campus.mcp.call({
name: 'campus-matrix.send',
arguments: { conversationId: conversation.id, message: { body: 'Hello from Campus' } },
})Contacts remains protocol-independent
Contacts stores one normalized address plus optional transport resolution metadata. It accepts both alice.campus.host and @alice:matrix.org. External Matrix contacts are excluded from Campus-only Merkle policies because they have no immutable Campus account ID; they can still be messaged through the same campus-matrix.* MCP contract. Campus Contacts never stores Matrix credentials or a Matrix room model.
Trusted bridge contract
When no bridge is configured, CampusOS provides a bundled account-bound transport for direct conversations between local Campus accounts. Bodies are AES-GCM sealed at rest and the caller identity is always derived from the authenticated installation. This makes the system Messages app usable on a fresh Campus deployment without provisioning another service.
Configure the Matrix bridge to add Matrix E2EE and federation. In that mode, CampusOS sends one authenticated POST /v1/campus-messaging to the configured bridge:
{
"version": 1,
"operation": "direct",
"subject": {
"accountId": "acct_immutable",
"campusName": "alice",
"campusAddress": "alice.campus.host",
"matrixUserId": "@alice:campus.host"
},
"input": { "to": "@friend:matrix.org" }
}The bridge is part of the trusted Campus service plane, not an installable user application. It must:
- authenticate
CAMPUS_MATRIX_SERVICE_TOKENand accept traffic only from CampusOS; - bind a Matrix account and one persistent SDK device to
subject.accountIdidempotently; - keep registration credentials, access tokens, device keys, cross-signing material, sync tokens, and SDK crypto databases in server-owned encrypted storage;
- use a Matrix SDK with end-to-end encryption and a persistent crypto store (ephemeral stores lose historical decryption keys after restart);
- enable
m.room.encryptionbefore a direct room becomes usable, wait for the room to sync, and refuse plaintext fallback; - resolve and invite arbitrary remote Matrix user IDs through the homeserver, rather than keeping a Campus-only directory;
- return only the version-1 high-level result objects described above.
The default transport is intentionally unavailable until both CAMPUS_MATRIX_SERVICE_URL and CAMPUS_MATRIX_SERVICE_TOKEN are configured. Tests inject an in-process adapter through :messaging-adapter; production must not use that hook.
Federation deployment
Set CAMPUS_MATRIX_SERVER_NAME before provisioning the homeserver. It is immutable in practice because it becomes the domain in every user and room identifier. For clean Campus identities, use the public Campus domain (for example campus.host) as the Synapse server_name, even if the homeserver itself runs at matrix.campus.host.
Normal Matrix federation requires the homeserver's federation listener to be publicly reachable. If it is not served directly as https://<server_name>:8448, publish https://<server_name>/.well-known/matrix/server to delegate to the actual TLS endpoint. Also publish /.well-known/matrix/client for client-server discovery. These are ordinary Matrix federation endpoints; do not route federation through Campus app ingress or require a Campus app capability.
Operational acceptance requires all of the following:
- the Matrix federation tester succeeds for
CAMPUS_MATRIX_SERVER_NAME; - a Campus user can invite, send to, and receive from
@user:matrix.org(or another independent homeserver); - that external user can start a conversation with a previously provisioned Campus identity;
- messages visible to the homeserver are
m.room.encrypted, neverm.room.messagein an encrypted room; - bridge restart preserves decryption of existing history;
- Campus app responses contain no Matrix credentials or raw crypto/sync data.
See the official Synapse documentation for federation, server-name delegation, and application services. The Matrix Rust SDK documents why E2EE requires persistent SDK storage in its encryption guide.