Initial commit: PFI fleet inventory, stacks, tooling, and backup pipeline
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.
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user