e376d0aec9
Captures the full workspace state built up to this point:
- CLAUDE.md + README.md describing conventions and the four-host fleet
(ana-ml2, ana-docker, nh3-docker, esh-docker-vm).
- Per-host notes under servers/<host>/ with ssh-target fallback files
and latest system-details snapshots (two in-compose credential leaks
scrubbed; the upstream compose files still need to move those to .env).
- scripts/: server_inspect.sh (read-only remote diagnostic),
refresh-server-info.sh (dir-driven discovery + snapshot capture with
validation warnings), add-host.sh, sync-stacks.sh (pull
compose/conf trees), deploy-stack.sh (push with per-file diff + prompt).
- stacks/: canonical compose for backrest, beszel, dozzle, llama-swap,
rest-server-ana, rest-server-nh3, vllm-qwen3, plus the retired
infinity reference. All use the .env-driven + traefik-net + homepage
label pattern.
- configs/restic/ana-docker/: first resticprofile config + pre-backup
hook (Synapse pg_dump, Seafile mysqldump, Vaultwarden SQLite); templates
for the other three hosts to come.
- docs/pfi/: general infrastructure reference carried over.
- .gitignore excludes .env, stacks-mirror/, and assorted secret/state
filenames to prevent re-leaks on later commits.
262 lines
9.0 KiB
Markdown
262 lines
9.0 KiB
Markdown
---
|
|
id: vm-102-matrix-appservice
|
|
title: "VM 102 — Matrix Appservice Configuration"
|
|
summary: "AIPA Matrix Application Service bridge setup for VM 102. Covers appservice registration, agent virtual users, room routing, env vars, operational notes, and troubleshooting."
|
|
tags: ["infrastructure", "pfi", "pfi-ana", "matrix", "docker", "vm-102", "appservice", "bridge", "aipa", "deployment", "integration"]
|
|
keywords: ["matrix.pfi.local", "appservice", "as_token", "hs_token", "aipa-bridge", "matrix_bridge.py", "8009", "send_notification", "virtual-users", "room-routing", "element"]
|
|
links:
|
|
- "[VM 102 — Matrix Synapse Deployment](vm-102-matrix-synapse.md)"
|
|
- "[Matrix Bridge — Application Service Integration](../../../KB/projects/aipa/matrix-bridge.md)"
|
|
- "[Docker Stack Conventions](docker-stack.md)"
|
|
created: 2026-04-11T00:00:00+00:00
|
|
modified: 2026-04-11T00:00:00+00:00
|
|
path: docs/pfi-ana/vm-102-matrix-appservice.md
|
|
---
|
|
|
|
# VM 102 — Matrix Appservice Configuration
|
|
|
|
## Overview
|
|
|
|
The AIPA Matrix bridge registers with Synapse as a Matrix Application Service. This means:
|
|
- Synapse **pushes** all relevant events to the bridge (no polling)
|
|
- The bridge manages **virtual users** for each agent — no real accounts needed
|
|
- The bridge authenticates to Synapse with a shared `as_token`
|
|
- Synapse authenticates its pushes to the bridge with a shared `hs_token`
|
|
|
|
> Prerequisite: [VM 102 — Matrix Synapse Deployment](vm-102-matrix-synapse.md) must be complete.
|
|
|
|
---
|
|
|
|
## Application Service Model
|
|
|
|
```
|
|
Synapse ──── push events ────► AIPA Bridge (port 8009)
|
|
│
|
|
routes to agent
|
|
│
|
|
posts response ◄──── Synapse ◄──── Element ◄──── User
|
|
```
|
|
|
|
### Agent Virtual Users
|
|
|
|
| Agent | Matrix ID | Display Name |
|
|
|----------|----------------------------|--------------|
|
|
| Atlas | `@atlas:matrix.pfi.local` | Atlas |
|
|
| Linus | `@linus:matrix.pfi.local` | Linus |
|
|
| Hermione | `@hermione:matrix.pfi.local` | Hermione |
|
|
|
|
These are **virtual** — managed entirely by the bridge. Do not register them as real Synapse accounts.
|
|
|
|
---
|
|
|
|
## Step 1 — Generate Tokens
|
|
|
|
Two random tokens are needed:
|
|
|
|
```bash
|
|
python3 -c "import secrets; print(secrets.token_hex(32))" # as_token (bridge → Synapse)
|
|
python3 -c "import secrets; print(secrets.token_hex(32))" # hs_token (Synapse → bridge)
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2 — Create Appservice Registration File
|
|
|
|
File: `/opt/docker/data/synapse/aipa_appservice.yaml`
|
|
|
|
```yaml
|
|
id: aipa-bridge
|
|
url: http://<BRIDGE_HOST_IP>:8009 # IP/hostname the bridge is reachable from Synapse container
|
|
as_token: "<AS_TOKEN_FROM_STEP_1>"
|
|
hs_token: "<HS_TOKEN_FROM_STEP_1>"
|
|
sender_localpart: aipa-bot # @aipa-bot:matrix.pfi.local (unused fallback sender)
|
|
namespaces:
|
|
users:
|
|
- exclusive: true
|
|
regex: "@(atlas|linus|hermione):.*"
|
|
rooms: []
|
|
aliases: []
|
|
rate_limited: false
|
|
```
|
|
|
|
- **`exclusive: true`** — only the bridge can act as those users; no one can register `@atlas` as a real account.
|
|
- **`url`** — must be reachable from inside the Synapse container. If the bridge runs on the VM host, use the host IP or Docker gateway IP (e.g., `172.17.0.1`).
|
|
|
|
### Restart Synapse to Load
|
|
|
|
```bash
|
|
cd /opt/docker/compose/synapse && docker compose restart synapse
|
|
```
|
|
|
|
---
|
|
|
|
## Step 3 — Configure AIPA Environment
|
|
|
|
### env.sh additions
|
|
|
|
```bash
|
|
# ── Matrix bridge ──────────────────────────────────────────────────────
|
|
export MATRIX_HOMESERVER_URL="http://localhost:8008"
|
|
export MATRIX_SERVER_NAME="matrix.pfi.local"
|
|
export MATRIX_AS_TOKEN="<as_token from Step 1>"
|
|
export MATRIX_HS_TOKEN="<hs_token from Step 1>"
|
|
# Optional: room to send agent notifications when no channel is specified
|
|
# export MATRIX_DEFAULT_ROOM="!roomid:matrix.pfi.local"
|
|
```
|
|
|
|
### providers.yaml matrix section
|
|
|
|
```yaml
|
|
matrix:
|
|
homeserver_url: "${MATRIX_HOMESERVER_URL}"
|
|
server_name: "${MATRIX_SERVER_NAME}"
|
|
as_token: "${MATRIX_AS_TOKEN}"
|
|
hs_token: "${MATRIX_HS_TOKEN}"
|
|
bridge_host: "0.0.0.0"
|
|
bridge_port: 8009
|
|
default_notification_room: "${MATRIX_DEFAULT_ROOM}"
|
|
agents:
|
|
atlas:
|
|
display_name: "Atlas"
|
|
avatar_url: ""
|
|
linus:
|
|
display_name: "Linus"
|
|
avatar_url: ""
|
|
hermione:
|
|
display_name: "Hermione"
|
|
avatar_url: ""
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4 — Start the Bridge
|
|
|
|
```bash
|
|
source .venv/bin/activate && source env.sh
|
|
python -m core.matrix_bridge
|
|
```
|
|
|
|
Options:
|
|
|
|
```
|
|
python -m core.matrix_bridge --host 0.0.0.0 --port 8009
|
|
python -m core.matrix_bridge --no-profile-setup # skip display name / avatar init
|
|
```
|
|
|
|
On first start, the bridge:
|
|
1. Registers display names for each agent user
|
|
2. Starts the appservice HTTP server on port 8009
|
|
3. Listens for Matrix transactions from Synapse
|
|
4. Accepts invitations to rooms
|
|
5. Begins routing messages
|
|
|
|
---
|
|
|
|
## Step 5 — First Use
|
|
|
|
1. Open Element at `http://10.250.50.70:8080`
|
|
2. Log in with your admin account (server: `matrix.pfi.local`)
|
|
3. Open a DM with `@atlas:matrix.pfi.local`
|
|
4. Send a message — the bridge routes it to Atlas and posts the response
|
|
|
|
To start a room with a specific agent, invite them:
|
|
- New room → Invite `@linus:matrix.pfi.local` → Linus joins automatically
|
|
- All messages in that room go to Linus
|
|
|
|
---
|
|
|
|
## Room Routing
|
|
|
|
1. User invites an agent user to a room (or opens a DM)
|
|
2. Bridge receives the `m.room.member` invite event, agent auto-joins
|
|
3. Room is permanently mapped to that agent in `sessions/matrix_rooms.json`
|
|
4. All subsequent messages in that room are routed to the mapped agent
|
|
5. If multiple agent users are in the same room, the first invite wins for session purposes;
|
|
subsequent agents each get their own session (multi-agent collaboration rooms)
|
|
|
|
---
|
|
|
|
## Operational Notes
|
|
|
|
### Room Session Persistence
|
|
|
|
The bridge persists the room→agent→session mapping to `sessions/matrix_rooms.json`
|
|
under `AIPA_ROOT`. If the bridge restarts, existing rooms continue their sessions.
|
|
|
|
To reset a room's conversation history:
|
|
- Ask the agent `/reset`, or
|
|
- Delete the entry from `matrix_rooms.json` and restart the bridge.
|
|
|
|
### Formatting
|
|
|
|
The bridge converts markdown in agent responses to Matrix HTML
|
|
(`format: org.matrix.custom.html`). Code blocks, bold, and inline code are
|
|
rendered correctly in Element.
|
|
|
|
### Typing Indicators
|
|
|
|
The bridge sends a typing indicator (`m.typing`) while the agent is processing,
|
|
so users see the animated dots while waiting.
|
|
|
|
### Proactive Notifications
|
|
|
|
Agents can push messages to Matrix rooms via `send_notification`:
|
|
|
|
```
|
|
send_notification(
|
|
content="KB rebuild complete — 1,247 chunks indexed.",
|
|
channel="!roomid:matrix.pfi.local"
|
|
)
|
|
```
|
|
|
|
- If `channel` is set, it must be a Matrix room ID (`!roomid:server`)
|
|
- If `channel` is omitted, the notification goes to `MATRIX_DEFAULT_ROOM` if configured
|
|
|
|
### Agent Users Always Show as Offline
|
|
|
|
Expected behavior — virtual users don't have presence. This is normal for appservice users.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Fix |
|
|
|---------|-------------|-----|
|
|
| Bridge starts but Synapse doesn't push events | Appservice URL wrong in registration YAML | Verify `url:` is reachable from the Synapse container; check `docker inspect synapse` network |
|
|
| Agent joins room but doesn't respond | `hs_token` mismatch | Verify token in `aipa_appservice.yaml` matches `MATRIX_HS_TOKEN` env var |
|
|
| 401 errors from Synapse | `as_token` mismatch | Verify token in `aipa_appservice.yaml` matches `MATRIX_AS_TOKEN` env var |
|
|
| Agent user shows as offline always | Expected — virtual users don't have presence | Normal for appservice users; no fix needed |
|
|
| Messages loop (agent replies to itself) | Bridge not filtering its own messages | Check `SENDER_LOCALPART` filter in bridge; bridge ignores all managed agent users as senders |
|
|
| Element can't connect to homeserver | Wrong `base_url` in element-config.json | Must be the IP/hostname Element's browser can reach, not the Docker container name |
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
| Dependency | Version | Purpose |
|
|
|------------|---------|---------|
|
|
| `markdown` | any | Markdown→HTML rendering for Matrix responses |
|
|
| `fastapi` | any | Appservice HTTP server |
|
|
| `httpx` | any | Client-Server API calls to Synapse |
|
|
|
|
Install: `pip install markdown` (or `pip install -r requirements.txt`)
|
|
|
|
---
|
|
|
|
## Security Notes
|
|
|
|
- The `as_token` and `hs_token` are secrets equivalent to admin credentials. Store
|
|
them in `env.sh` (gitignored) and never commit them.
|
|
- The bridge runs with `MATRIX_HS_TOKEN` to authenticate Synapse's push requests. Any
|
|
request without this token in the `Authorization` header is rejected (403).
|
|
- Restrict port 8009 to internal access only (firewall or bind to `127.0.0.1` if bridge and Synapse are on the same host).
|
|
|
|
---
|
|
|
|
## Sources
|
|
|
|
- Source deployment guide: `projects/matrix/matrix-deployment.md` (2026-04-11)
|
|
- AIPA bridge code: `core/matrix_bridge.py`
|
|
- AIPA bridge KB entry: `KB/projects/aipa/matrix-bridge.md`
|
|
- Matrix Synapse appservice docs: https://element-hq.github.io/synapse/latest/application_services.html
|