- 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>
538 lines
19 KiB
Markdown
538 lines
19 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
make matrix-setup-bot
|
|
|
|
# Or directly
|
|
docker/matrix/scripts/setup-bot.sh
|
|
```
|
|
|
|
The script:
|
|
|
|
1. Registers an admin account (`admin` / `admin-dev-password`)
|
|
2. Obtains an admin access token
|
|
3. Creates the bot account (`mosaic-bot` / `mosaic-bot-dev-password`)
|
|
4. Retrieves the bot access token
|
|
5. Prints the environment variables to add to `.env`
|
|
|
|
Custom credentials can be passed:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
MATRIX_HOMESERVER_URL=http://synapse:8008
|
|
```
|
|
|
|
### 4. Restart the API
|
|
|
|
```bash
|
|
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
|
|
|
|
1. Open http://localhost:8501
|
|
2. Register or log in with any account
|
|
3. Create a room and invite `@mosaic-bot:localhost`
|
|
4. Send `@mosaic help` or `!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.message` events in all joined rooms
|
|
- Resolves workspace context via MatrixRoomService (or falls back to control room)
|
|
- Normalizes `!mosaic` prefix to `@mosaic` for 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 `workspace` table's `matrixRoomId` column
|
|
|
|
**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 `ParsedCommand` objects 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_TOKEN` is set, Discord bridge is added to `CHAT_PROVIDERS`
|
|
- If `MATRIX_ACCESS_TOKEN` is set, Matrix bridge is added to `CHAT_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_PROVIDERS` injection 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](https://github.com/matrix-org/matrix-spec-proposals/pull/3440):
|
|
|
|
- A **thread root** is created by sending a regular `m.room.message` event
|
|
- Subsequent messages reference the root via `m.relates_to` with `rel_type: "m.thread"`
|
|
- The `is_falling_back: true` flag and `m.in_reply_to` provide 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:
|
|
|
|
```typescript
|
|
await matrixRoomService.linkWorkspaceToRoom(workspaceId, "!roomid:localhost");
|
|
```
|
|
|
|
And unlinked:
|
|
|
|
```typescript
|
|
await matrixRoomService.unlinkWorkspace(workspaceId);
|
|
```
|
|
|
|
### Message Routing
|
|
|
|
When a message arrives in a room:
|
|
|
|
1. MatrixRoomService performs a reverse lookup: room ID -> workspace ID
|
|
2. If no mapping is found, the service checks if the room is the configured control room
|
|
(`MATRIX_CONTROL_ROOM_ID`) and uses `MATRIX_WORKSPACE_ID` as fallback
|
|
3. 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
|
|
|
|
1. An initial placeholder message ("Thinking...") is sent to the room
|
|
2. The bot's typing indicator is activated
|
|
3. Tokens from the LLM arrive via an `AsyncIterable<string>`
|
|
4. Tokens are buffered and the message is edited via `m.replace` events
|
|
5. Edits are rate-limited to a maximum of once every **500ms** to avoid flooding the
|
|
homeserver
|
|
6. When streaming completes, a final clean edit is sent and the typing indicator clears
|
|
7. On error, the message is edited to include an error notice
|
|
|
|
### Message Edit Format (m.replace)
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. Add the action to the `CommandAction` enum in
|
|
`apps/api/src/bridge/parser/command.interface.ts`
|
|
|
|
2. Add parsing logic in `CommandParserService.parseActionArguments()`
|
|
(`apps/api/src/bridge/parser/command-parser.service.ts`)
|
|
|
|
3. Add the handler case in `MatrixService.handleParsedCommand()`
|
|
(`apps/api/src/bridge/matrix/matrix.service.ts`)
|
|
|
|
4. Implement the handler method (e.g., `handleNewCommand()`)
|
|
|
|
5. Update the help text in `MatrixService.handleHelpCommand()`
|
|
|
|
6. 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
1. Create a new service implementing `IChatProvider`
|
|
2. Register it in `BridgeModule` with a conditional check on its environment variable
|
|
3. Add it to the `CHAT_PROVIDERS` factory
|
|
4. 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 `AutojoinRoomsMixin` and implementing allow-list logic
|
|
|
|
**Environment Variables**
|
|
|
|
- `MATRIX_WORKSPACE_ID` should 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_URL` points to the
|
|
correct endpoint
|
|
- For federation, configure DNS SRV records and `.well-known` delegation
|
|
|
|
### 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:
|
|
|
|
```bash
|
|
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 |
|