- Quick start guide for dev environment - Architecture overview with service responsibilities - Command reference with examples - Configuration reference - Streaming response architecture - Deployment considerations Refs #386 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 KiB
Matrix Bridge
Integration between Mosaic Stack and the Matrix protocol, enabling workspace management and job orchestration through Matrix chat rooms.
Overview
The Matrix bridge connects Mosaic Stack to any Matrix homeserver (Synapse, Dendrite, Conduit, etc.), allowing users to interact with the platform through Matrix clients like Element, FluffyChat, or any other Matrix-compatible application.
Key capabilities:
- Command interface -- Issue bot commands (
@mosaic fix #42) from any mapped Matrix room - Workspace-room mapping -- Each Mosaic workspace can be linked to a Matrix room
- Threaded job updates -- Job progress is posted to MSC3440 threads, keeping rooms clean
- Streaming AI responses -- LLM output streams to Matrix via rate-limited message edits
- Multi-provider broadcasting -- HeraldService broadcasts status updates to all active chat providers (Discord and Matrix can run simultaneously)
Architecture
Matrix Client (Element, FluffyChat, etc.)
|
v
Synapse Homeserver
|
matrix-bot-sdk
|
v
+------------------+ +---------------------+
| MatrixService |<----->| CommandParserService |
| (IChatProvider) | | (shared, all platforms)
+------------------+ +---------------------+
| |
| v
| +--------------------+
| | MatrixRoomService | workspace <-> room mapping
| +--------------------+
| |
v v
+------------------+ +----------------+
| StitcherService | | PrismaService |
| (job dispatch) | | (database) |
+------------------+ +----------------+
|
v
+------------------+
| HeraldService | broadcasts to CHAT_PROVIDERS[]
+------------------+
|
v
+---------------------------+
| MatrixStreamingService | streaming AI responses
| (m.replace edits, typing) |
+---------------------------+
Quick Start
1. Start the dev environment
The Matrix dev environment uses a Docker Compose overlay that adds Synapse and Element Web alongside the existing Mosaic Stack services.
# Using Makefile (recommended)
make matrix-up
# Or manually
docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -d
This starts:
| Service | URL | Purpose |
|---|---|---|
| Synapse | http://localhost:8008 | Matrix homeserver |
| Element Web | http://localhost:8501 | Web-based Matrix client |
Both services share the existing Mosaic PostgreSQL instance. A synapse-db-init container
runs once to create the synapse database and user, then exits.
2. Create the bot account
After Synapse is healthy, run the setup script to create admin and bot accounts:
make matrix-setup-bot
# Or directly
docker/matrix/scripts/setup-bot.sh
The script:
- Registers an admin account (
admin/admin-dev-password) - Obtains an admin access token
- Creates the bot account (
mosaic-bot/mosaic-bot-dev-password) - Retrieves the bot access token
- Prints the environment variables to add to
.env
Custom credentials can be passed:
docker/matrix/scripts/setup-bot.sh \
--username custom-bot \
--password custom-pass \
--admin-username myadmin \
--admin-password myadmin-pass
3. Configure environment variables
Copy the output from the setup script into your .env file:
# Matrix Bridge Configuration
MATRIX_HOMESERVER_URL=http://localhost:8008
MATRIX_ACCESS_TOKEN=<token from setup-bot.sh>
MATRIX_BOT_USER_ID=@mosaic-bot:localhost
MATRIX_CONTROL_ROOM_ID=!roomid:localhost
MATRIX_WORKSPACE_ID=<your-workspace-uuid>
If running the API inside the Docker Compose network, use the internal hostname:
MATRIX_HOMESERVER_URL=http://synapse:8008
4. Restart the API
pnpm dev:api
# or
make docker-restart
The BridgeModule will detect MATRIX_ACCESS_TOKEN and enable the Matrix bridge
automatically.
5. Test in Element Web
- Open http://localhost:8501
- Register or log in with any account
- Create a room and invite
@mosaic-bot:localhost - Send
@mosaic helpor!mosaic help
Configuration
Environment Variables
| Variable | Description | Example |
|---|---|---|
MATRIX_HOMESERVER_URL |
Matrix server URL | http://localhost:8008 |
MATRIX_ACCESS_TOKEN |
Bot access token (from setup script or login) | syt_bW9z... |
MATRIX_BOT_USER_ID |
Bot's full Matrix user ID | @mosaic-bot:localhost |
MATRIX_CONTROL_ROOM_ID |
Default room for status broadcasts | !abcdef:localhost |
MATRIX_WORKSPACE_ID |
Default workspace UUID for the control room | 550e8400-e29b-41d4-a716-... |
All variables are read from process.env at service construction time. The bridge activates
only when MATRIX_ACCESS_TOKEN is set.
Dev Environment Variables (docker-compose.matrix.yml)
These configure the local Synapse and Element Web instances:
| Variable | Default | Purpose |
|---|---|---|
SYNAPSE_POSTGRES_DB |
synapse |
Synapse database name |
SYNAPSE_POSTGRES_USER |
synapse |
Synapse database user |
SYNAPSE_POSTGRES_PASSWORD |
synapse_dev_password |
Synapse database password |
SYNAPSE_CLIENT_PORT |
8008 |
Synapse client API port |
SYNAPSE_FEDERATION_PORT |
8448 |
Synapse federation port |
ELEMENT_PORT |
8501 |
Element Web port |
Architecture
Service Responsibilities
MatrixService (apps/api/src/bridge/matrix/matrix.service.ts)
The primary Matrix integration. Implements the IChatProvider interface.
- Connects to the homeserver using
matrix-bot-sdk - Listens for
room.messageevents in all joined rooms - Resolves workspace context via MatrixRoomService (or falls back to control room)
- Normalizes
!mosaicprefix to@mosaicfor the shared CommandParserService - Dispatches parsed commands to StitcherService for job execution
- Creates MSC3440 threads for job updates
- Auto-joins rooms when invited (
AutojoinRoomsMixin)
MatrixRoomService (apps/api/src/bridge/matrix/matrix-room.service.ts)
Manages the mapping between Mosaic workspaces and Matrix rooms.
- Provision: Creates a private Matrix room named
Mosaic: {workspace_name}with alias#mosaic-{slug}:{server} - Link/Unlink: Maps existing rooms to workspaces via
workspace.matrixRoomId - Lookup: Forward lookup (workspace -> room) and reverse lookup (room -> workspace)
- Room mappings are stored in the
workspacetable'smatrixRoomIdcolumn
MatrixStreamingService (apps/api/src/bridge/matrix/matrix-streaming.service.ts)
Streams AI responses to Matrix rooms using incremental message edits.
- Sends an initial "Thinking..." placeholder message
- Activates typing indicator during generation
- Buffers incoming tokens and edits the message every 500ms (rate-limited)
- On completion, sends a final clean edit with optional token usage stats
- On error, edits the message with an error notice
- Supports threaded responses via MSC3440
CommandParserService (apps/api/src/bridge/parser/command-parser.service.ts)
Shared, platform-agnostic command parser used by both Discord and Matrix bridges.
- Parses
@mosaic <action> [args]commands - Supports issue references in multiple formats:
#42,owner/repo#42, full URL - Returns typed
ParsedCommandobjects or structured parse errors with help text
BridgeModule (apps/api/src/bridge/bridge.module.ts)
Conditional module loader. Inspects environment variables at startup:
- If
DISCORD_BOT_TOKENis set, Discord bridge is added toCHAT_PROVIDERS - If
MATRIX_ACCESS_TOKENis set, Matrix bridge is added toCHAT_PROVIDERS - Both can run simultaneously; neither is a dependency of the other
HeraldService (apps/api/src/herald/herald.service.ts)
Status broadcaster that sends job event updates to all active chat providers.
- Iterates over the
CHAT_PROVIDERSinjection token - Sends thread messages for job lifecycle events (created, started, completed, failed, etc.)
- Uses PDA-friendly language (no "OVERDUE", "URGENT", etc.)
- If one provider fails, others still receive the broadcast
Data Flow
1. User sends "@mosaic fix #42" in a Matrix room
2. MatrixService receives room.message event
3. MatrixRoomService resolves room -> workspace mapping
4. CommandParserService parses the command (action=FIX, issue=#42)
5. MatrixService creates a thread (MSC3440) for job updates
6. StitcherService dispatches the job with workspace context
7. HeraldService receives job events and broadcasts to all CHAT_PROVIDERS
8. Thread messages appear in the Matrix room thread
Thread Model (MSC3440)
Matrix threads are implemented per MSC3440:
- A thread root is created by sending a regular
m.room.messageevent - Subsequent messages reference the root via
m.relates_towithrel_type: "m.thread" - The
is_falling_back: trueflag andm.in_reply_toprovide compatibility with clients that do not support threads - Thread root event IDs are stored in job metadata for HeraldService to post updates
Commands
All commands accept either @mosaic or !mosaic prefix. The !mosaic form is
normalized to @mosaic internally before parsing.
| Command | Description | Example |
|---|---|---|
@mosaic fix <issue> |
Start a job for an issue | @mosaic fix #42 |
@mosaic status <job-id> |
Check job status | @mosaic status job-abc123 |
@mosaic cancel <job-id> |
Cancel a running job | @mosaic cancel job-abc123 |
@mosaic retry <job-id> |
Retry a failed job | @mosaic retry job-abc123 |
@mosaic verbose <job-id> |
Stream full logs to thread | @mosaic verbose job-abc123 |
@mosaic quiet |
Reduce notification verbosity | @mosaic quiet |
@mosaic help |
Show available commands | @mosaic help |
Issue Reference Formats
The fix command accepts issue references in multiple formats:
@mosaic fix #42 # Current repo
@mosaic fix owner/repo#42 # Cross-repo
@mosaic fix https://git.example.com/o/r/issues/42 # Full URL
Noise Management
Job updates are scoped to threads to keep main rooms clean:
- Main room: Low verbosity -- milestone completions only
- Job threads: Medium verbosity -- step completions and status changes
- DMs: Configurable per user (planned)
Workspace-Room Mapping
Each Mosaic workspace can be associated with one Matrix room. The mapping is stored in the
workspace table's matrixRoomId column.
Automatic Provisioning
When a workspace needs a Matrix room, MatrixRoomService provisions one:
Room name: "Mosaic: My Workspace"
Room alias: #mosaic-my-workspace:localhost
Visibility: private
The room ID is then stored in workspace.matrixRoomId.
Manual Linking
Existing rooms can be linked to workspaces:
await matrixRoomService.linkWorkspaceToRoom(workspaceId, "!roomid:localhost");
And unlinked:
await matrixRoomService.unlinkWorkspace(workspaceId);
Message Routing
When a message arrives in a room:
- MatrixRoomService performs a reverse lookup: room ID -> workspace ID
- If no mapping is found, the service checks if the room is the configured control room
(
MATRIX_CONTROL_ROOM_ID) and usesMATRIX_WORKSPACE_IDas fallback - If still unmapped, the message is ignored
This ensures commands only execute within a valid workspace context.
Streaming Responses
MatrixStreamingService enables real-time AI response streaming in Matrix rooms.
How It Works
- An initial placeholder message ("Thinking...") is sent to the room
- The bot's typing indicator is activated
- Tokens from the LLM arrive via an
AsyncIterable<string> - Tokens are buffered and the message is edited via
m.replaceevents - Edits are rate-limited to a maximum of once every 500ms to avoid flooding the homeserver
- When streaming completes, a final clean edit is sent and the typing indicator clears
- On error, the message is edited to include an error notice
Message Edit Format (m.replace)
{
"m.new_content": {
"msgtype": "m.text",
"body": "Updated response text"
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$original_event_id"
},
"msgtype": "m.text",
"body": "* Updated response text"
}
The top-level body prefixed with * serves as a fallback for clients that do not
support message edits.
Thread Support
Streaming responses can target a specific thread by passing threadId in the options.
The initial message and all edits will include the m.thread relation.
Development
Running Tests
# All bridge tests
pnpm test -- --filter @mosaic/api -- matrix
# Individual service tests
pnpm test -- --filter @mosaic/api -- matrix.service
pnpm test -- --filter @mosaic/api -- matrix-room.service
pnpm test -- --filter @mosaic/api -- matrix-streaming.service
pnpm test -- --filter @mosaic/api -- command-parser
pnpm test -- --filter @mosaic/api -- bridge.module
Adding a New Command
-
Add the action to the
CommandActionenum inapps/api/src/bridge/parser/command.interface.ts -
Add parsing logic in
CommandParserService.parseActionArguments()(apps/api/src/bridge/parser/command-parser.service.ts) -
Add the handler case in
MatrixService.handleParsedCommand()(apps/api/src/bridge/matrix/matrix.service.ts) -
Implement the handler method (e.g.,
handleNewCommand()) -
Update the help text in
MatrixService.handleHelpCommand() -
Add tests for the new command in both the parser and service spec files
Extending the Bridge
The IChatProvider interface (apps/api/src/bridge/interfaces/chat-provider.interface.ts)
defines the contract all chat bridges implement:
interface IChatProvider {
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
sendMessage(channelId: string, content: string): Promise<void>;
createThread(options: ThreadCreateOptions): Promise<string>;
sendThreadMessage(options: ThreadMessageOptions): Promise<void>;
parseCommand(message: ChatMessage): ChatCommand | null;
editMessage?(channelId: string, messageId: string, content: string): Promise<void>;
}
To add a new chat platform:
- Create a new service implementing
IChatProvider - Register it in
BridgeModulewith a conditional check on its environment variable - Add it to the
CHAT_PROVIDERSfactory - HeraldService will automatically broadcast to it with no further changes
File Layout
apps/api/src/
bridge/
bridge.module.ts # Conditional module loader
bridge.constants.ts # CHAT_PROVIDERS injection token
interfaces/
chat-provider.interface.ts # IChatProvider contract
index.ts
parser/
command-parser.service.ts # Shared command parser
command-parser.spec.ts
command.interface.ts # Command types and enums
matrix/
matrix.service.ts # Core Matrix integration
matrix.service.spec.ts
matrix-room.service.ts # Workspace-room mapping
matrix-room.service.spec.ts
matrix-streaming.service.ts # Streaming AI responses
matrix-streaming.service.spec.ts
discord/
discord.service.ts # Discord integration (parallel)
herald/
herald.module.ts
herald.service.ts # Status broadcasting
herald.service.spec.ts
docker/
docker-compose.matrix.yml # Dev overlay (Synapse + Element)
docker-compose.sample.matrix.yml # Production sample (Swarm)
matrix/
synapse/
homeserver.yaml # Dev Synapse config
element/
config.json # Dev Element Web config
scripts/
setup-bot.sh # Bot account setup
Deployment
Production Considerations
The dev environment uses relaxed settings that are not suitable for production. Review and address the following before deploying:
Synapse Configuration
- Set a proper
server_name(this is permanent and cannot change after first run) - Disable open registration (
enable_registration: false) - Replace dev secrets (
macaroon_secret_key,form_secret) with strong random values - Configure proper rate limiting (dev config allows 100 msg/sec)
- Set up TLS termination (via reverse proxy or Synapse directly)
- Consider a dedicated PostgreSQL instance rather than the shared Mosaic database
Bot Security
- Generate a strong bot password (not the dev default)
- Store the access token securely (use a secrets manager or encrypted
.env) - The bot auto-joins rooms when invited -- consider restricting this in production
by removing
AutojoinRoomsMixinand implementing allow-list logic
Environment Variables
MATRIX_WORKSPACE_IDshould be a valid workspace UUID from your database; all commands from the control room execute within this workspace context
Network
- If Synapse runs on a separate host, ensure
MATRIX_HOMESERVER_URLpoints to the correct endpoint - For federation, configure DNS SRV records and
.well-knowndelegation
Sample Production Stack
A production-ready Docker Swarm compose file is provided at
docker/docker-compose.sample.matrix.yml. It includes:
- Synapse with Traefik labels for automatic TLS
- Element Web with its own domain
- Dedicated PostgreSQL instance for Synapse
- Optional coturn (TURN/STUN) for voice/video
Deploy via Portainer or Docker Swarm CLI:
docker stack deploy -c docker/docker-compose.sample.matrix.yml matrix
After deploying, follow the post-deploy steps in the compose file comments to create accounts and configure the Mosaic Stack connection.
Makefile Targets
| Target | Description |
|---|---|
make matrix-up |
Start Synapse + Element Web (dev overlay) |
make matrix-down |
Stop Matrix services |
make matrix-logs |
Follow Synapse and Element logs |
make matrix-setup-bot |
Run bot account setup script |