Context
The OS app currently talks to swf-node over 127.0.0.1:7777 for peer search. Open question: how should the app talk to the cohort Matrix server (mtrx.shaperotator.xyz, Phala TEE, source at teleport-computer/shape-rotator-matrix)?
Prior art worth re-reading first:
docs/MATRIX.md (currently on a worktree, not main) — onboarding flow, signup-code mechanics, and the explicit warning that programmatic E2EE is hard: mautrix-python is the recommended SDK; matrix-nio dead-ended on cross-signing.
apps/os/swf-node.js — the supervisor pattern (state machine, IPC events, bundled binary) we'd likely mirror for any in-process daemon.
matrix-bot-setup skill — currently a stub.
Two distinct use cases that should be named up front, because they pull in opposite directions:
- A. User-facing client: render rooms, send messages, read history in the OS app's renderer.
- B. Agent / bot channel: the app (or a sidecar) acts as the user's automated identity — posts status, listens for commands, reacts to events.
(A) cares about UX, (B) cares about reliable E2EE on a headless account. Same homeserver, very different shapes.
Client-side options
1. matrix-js-sdk in the renderer
Cheapest path. npm i matrix-js-sdk, instantiate in renderer, sync over HTTPS.
- 👍 ~1 day to working send/receive.
- 👎 Credentials + E2EE store live in renderer IndexedDB. With our current preload (contextIsolation on, no nodeIntegration), the SDK works in-browser fine, but secrets aren't isolated from page JS.
- 👎 E2EE in JS is the known-hard area
docs/MATRIX.md calls out. For headless/agent use (B), this is the worst option.
2. matrix-js-sdk in main, IPC bridge to renderer
Mirrors the swf-node supervisor pattern.
- A
matrix-node.js module in main owns the MatrixClient, persists tokens in userData/, exposes IPC (matrix:login, matrix:send, matrix:onTimeline) alongside the existing getSwfNodeStatus etc.
- E2EE via
@matrix-org/matrix-sdk-crypto-wasm, WASM bundled through electron-builder extraResources (same mechanism as the swf-node binary).
- 👍 Tokens out of renderer; matches existing architecture.
- 👎 Still the JS-crypto path
docs/MATRIX.md warns against. Likely fine for (A), risky for (B).
- Est: 2–3 days for login + room list + send/receive; +1–2 for E2EE.
3. mautrix-python sidecar, supervised like swf-node
Bundle a Python mautrix process as an extra resource; main spawns + supervises it; renderer talks to its local HTTP/WS surface.
- 👍 Uses the SDK we already know works for our headless accounts (knock-approver is the reference impl).
- 👍 Cleanly separates (A) renderer client from (B) agent identity.
- 👎 Adds Python to our build matrix — non-trivial for Win/Linux packaging (we already ship Python-derived
swf-node binaries, so the muscle exists, but it's another moving part).
- Est: 3–5 days end-to-end.
Server-side options (we own the homeserver)
These don't replace a client-side choice — they make whichever client-side option we pick dramatically easier.
4a. Pantalaimon E2EE proxy
Pantalaimon is a Matrix-developed transparent proxy that handles E2EE itself and exposes the plain client-server API.
- Bundle a per-user Pantalaimon either (i) on the homeserver host as a per-account proxy or (ii) shipped inside the OS app and run locally.
- Renderer (option 1) or main-process client (option 2) speaks plain HTTP, no crypto WASM, no key store.
- 👍 Eliminates the JS E2EE problem entirely.
- 👎 If hosted alongside the homeserver, keys live there — degrades the E2EE threat model (though the TEE deployment recovers some of it). If shipped locally, we're packaging Python anyway, at which point option 3 becomes more attractive.
4b. Application Service registration
Register the OS app's machine identity as an Application Service on mtrx.shaperotator.xyz.
- AS users get a privileged token, can puppet namespaced users (
@_srwk_*:mtrx.shaperotator.xyz), and live in non-E2EE namespaces by convention.
- Good fit for (B) agent channel: status broadcasts, presence, structured events to a dedicated
#srwk-bot-noise room.
- 👎 Requires homeserver config (operator action, low cost since we control it).
- 👎 Not a fit for (A) — humans still want E2EE in cohort rooms.
4c. Non-encrypted cohort rooms
Configure #announcements, #general, #bot-noise as non-encrypted from the start.
- 👍 Any Matrix client (including option 1 in the renderer) works with zero crypto work.
- 👍 The homeserver is in a TEE — transport confidentiality is provided by TLS + the attestation story, so the marginal value of E2EE on top is debatable for cohort-wide rooms.
- 👎 DMs and small-group rooms still want E2EE; this is rooms-by-room.
- 👎 Reversal is hard once messages exist — needs to be a setup-time decision.
4d. Custom shim API on the homeserver host
A small service co-located with Synapse exposing cohort-opinionated endpoints (POST /srwk/status, GET /srwk/feed, etc.) that proxy to the Matrix client-server API with an AS token.
- 👍 Renderer becomes trivial — just
fetch() calls, no Matrix SDK at all.
- 👎 We're now maintaining a bespoke API. Couples us tighter to the cohort homeserver and makes it harder to ever federate this with off-cohort Matrix accounts.
4e. Sliding Sync proxy
Run matrix-org/sliding-sync alongside Synapse.
- Orthogonal to the E2EE question, but makes building a custom client cheaper — incremental room-list sync instead of full
/sync deltas.
- Worth doing eventually regardless of which client option we pick.
Recommendation
For (A) user-facing client: option 2 (matrix-js-sdk in main, IPC bridge) combined with 4c for cohort-wide rooms. The crypto WASM path is fine if the only rooms that need it are DMs and small groups. Mirrors our existing supervisor pattern; tokens stay out of the renderer.
For (B) agent channel: option 3 (mautrix-python sidecar) gated by 4b (Application Service namespace). This is the path the knock-approver already validated; the AS registration prevents agent-noise from leaking into human-encrypted rooms.
Net work: roughly 1 week for (A) end-to-end, plus separate scoping for (B). Server-side changes (4b/4c) are operator actions on teleport-computer/shape-rotator-matrix — small cost, large payoff.
Open questions
- Is the AS namespace something Andrew is willing to grant per app, or does this need a cohort-wide convention first?
- Are cohort rooms currently E2EE? (If yes, 4c isn't retroactive; if no, we've already paid the decision.)
- Do we want one shared bot identity per user, or one per app surface (OS app vs. field-kit
rotate vox etc.)?
cc @amiller for server-side feasibility on 4b/4c.
Context
The OS app currently talks to
swf-nodeover127.0.0.1:7777for peer search. Open question: how should the app talk to the cohort Matrix server (mtrx.shaperotator.xyz, Phala TEE, source atteleport-computer/shape-rotator-matrix)?Prior art worth re-reading first:
docs/MATRIX.md(currently on a worktree, not main) — onboarding flow, signup-code mechanics, and the explicit warning that programmatic E2EE is hard:mautrix-pythonis the recommended SDK;matrix-niodead-ended on cross-signing.apps/os/swf-node.js— the supervisor pattern (state machine, IPC events, bundled binary) we'd likely mirror for any in-process daemon.matrix-bot-setupskill — currently a stub.Two distinct use cases that should be named up front, because they pull in opposite directions:
(A) cares about UX, (B) cares about reliable E2EE on a headless account. Same homeserver, very different shapes.
Client-side options
1.
matrix-js-sdkin the rendererCheapest path.
npm i matrix-js-sdk, instantiate in renderer, sync over HTTPS.docs/MATRIX.mdcalls out. For headless/agent use (B), this is the worst option.2.
matrix-js-sdkin main, IPC bridge to rendererMirrors the
swf-nodesupervisor pattern.matrix-node.jsmodule in main owns theMatrixClient, persists tokens inuserData/, exposes IPC (matrix:login,matrix:send,matrix:onTimeline) alongside the existinggetSwfNodeStatusetc.@matrix-org/matrix-sdk-crypto-wasm, WASM bundled through electron-builderextraResources(same mechanism as the swf-node binary).docs/MATRIX.mdwarns against. Likely fine for (A), risky for (B).3.
mautrix-pythonsidecar, supervised like swf-nodeBundle a Python
mautrixprocess as an extra resource; main spawns + supervises it; renderer talks to its local HTTP/WS surface.swf-nodebinaries, so the muscle exists, but it's another moving part).Server-side options (we own the homeserver)
These don't replace a client-side choice — they make whichever client-side option we pick dramatically easier.
4a. Pantalaimon E2EE proxy
Pantalaimon is a Matrix-developed transparent proxy that handles E2EE itself and exposes the plain client-server API.
4b. Application Service registration
Register the OS app's machine identity as an Application Service on
mtrx.shaperotator.xyz.@_srwk_*:mtrx.shaperotator.xyz), and live in non-E2EE namespaces by convention.#srwk-bot-noiseroom.4c. Non-encrypted cohort rooms
Configure
#announcements,#general,#bot-noiseas non-encrypted from the start.4d. Custom shim API on the homeserver host
A small service co-located with Synapse exposing cohort-opinionated endpoints (
POST /srwk/status,GET /srwk/feed, etc.) that proxy to the Matrix client-server API with an AS token.fetch()calls, no Matrix SDK at all.4e. Sliding Sync proxy
Run
matrix-org/sliding-syncalongside Synapse./syncdeltas.Recommendation
For (A) user-facing client: option 2 (matrix-js-sdk in main, IPC bridge) combined with 4c for cohort-wide rooms. The crypto WASM path is fine if the only rooms that need it are DMs and small groups. Mirrors our existing supervisor pattern; tokens stay out of the renderer.
For (B) agent channel: option 3 (mautrix-python sidecar) gated by 4b (Application Service namespace). This is the path the knock-approver already validated; the AS registration prevents agent-noise from leaking into human-encrypted rooms.
Net work: roughly 1 week for (A) end-to-end, plus separate scoping for (B). Server-side changes (4b/4c) are operator actions on
teleport-computer/shape-rotator-matrix— small cost, large payoff.Open questions
rotate voxetc.)?cc @amiller for server-side feasibility on 4b/4c.