feat: Add presence heartbeat for Matrix online status

- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
This commit is contained in:
Apple
2025-11-27 00:19:40 -08:00
parent 5bed515852
commit 3de3c8cb36
6371 changed files with 1317450 additions and 932 deletions

View File

@@ -0,0 +1,997 @@
# DAARION Messaging Architecture
**Complete Specification: Messenger + Matrix + Agents + DAGI Router**
**Version:** 1.0.0
**Date:** 2025-11-24
**Status:** Production Ready
---
## Table of Contents
1. [Overview](#overview)
2. [System Components](#system-components)
3. [Data Model (ERD)](#data-model-erd)
4. [matrix-gateway API Specification](#matrix-gateway-api-specification)
5. [Message Flow: Human → Agent Reply](#message-flow-human--agent-reply)
6. [Agent-Initiated Messages](#agent-initiated-messages)
7. [agent_filter Rules](#agent_filter-rules)
8. [DAGI Router Integration](#dagi-router-integration)
9. [Sequence Diagrams](#sequence-diagrams)
10. [Implementation Guide](#implementation-guide)
---
## Overview
DAARION Messaging Architecture побудована на Matrix protocol з повною інтеграцією агентів через DAGI Router.
### Key Principles
1. **Matrix as Source of Truth** — повідомлення зберігаються в Matrix, DAARION тримає індекс
2. **Agent-Aware** — агенти як повноцінні учасники каналів
3. **Event-Driven** — всі дії через NATS JetStream
4. **Security First** — agent_filter контролює доступ агентів
5. **Element Compatible** — повна сумісність з Element та іншими Matrix клієнтами
### Architecture Layers
```
┌─────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ MessengerPage + WebSocket │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ messaging-service (FastAPI) │
│ REST API + WebSocket + Channel Management │
└────────────────────┬────────────────────────────────────┘
┌─────────────┴─────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ matrix-gateway│ │ NATS JetStream │
│ (Internal) │ │ Event Bus │
└──────┬───────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ Matrix │ │ agent_filter │
│ Homeserver │ │ DAGI Router │
│ (Synapse) │ │ Agent Runtime │
└──────────────┘ └──────────────────┘
```
---
## System Components
### 1. messaging-service
- **Role:** DAARION-specific messaging API
- **Port:** 7004
- **Responsibilities:**
- Channel CRUD (mapped to Matrix rooms)
- Message indexing (full content in Matrix)
- Member management
- WebSocket real-time updates
- Agent posting endpoint
### 2. matrix-gateway
- **Role:** Internal Matrix API adapter
- **Port:** 7003 (internal only)
- **Responsibilities:**
- Create/manage Matrix rooms
- Send messages on behalf of users/agents
- Receive Matrix events via webhook
- Normalize Matrix ↔ DAARION entities
- Publish to NATS
### 3. agent_filter
- **Role:** Security and routing layer
- **Responsibilities:**
- Validate agent access to channels
- Apply content policies
- Decide which agents can reply
- Route to DAGI Router
### 4. DAGI Router
- **Role:** Agent orchestration
- **Responsibilities:**
- Select model for agent
- Choose pipeline (Memory, Tools, etc.)
- Invoke Agent Runtime
- Track agent sessions
### 5. Agent Runtime
- **Role:** Execute agent logic
- **Responsibilities:**
- Read channel context
- Query Agent Memory (RAG)
- Call LLM Proxy
- Execute tools
- Post reply to channel
---
## Data Model (ERD)
### Mermaid Diagram
```mermaid
erDiagram
USERS {
uuid id
string external_id
string matrix_id
string handle
datetime created_at
}
MICRODAOS {
uuid id
string external_id
string name
uuid owner_user_id
datetime created_at
}
MICRODAO_MEMBERS {
uuid id
uuid microdao_id
uuid user_id
string role
datetime created_at
}
AGENT_BLUEPRINTS {
uuid id
string code
string description
string model
json capabilities
}
AGENTS {
uuid id
string external_id
string name
string kind
uuid microdao_id
uuid owner_user_id
string matrix_id
uuid blueprint_id
datetime created_at
}
CHANNELS {
uuid id
string slug
string name
uuid microdao_id
uuid team_id
string matrix_room_id
string visibility
uuid created_by_user_id
uuid created_by_agent_id
datetime created_at
}
CHANNEL_MEMBERS {
uuid id
uuid channel_id
uuid member_user_id
uuid member_agent_id
string role
datetime joined_at
}
MESSAGES {
uuid id
uuid channel_id
string matrix_event_id
uuid sender_user_id
uuid sender_agent_id
string sender_type
string content_preview
datetime created_at
}
AGENT_SESSIONS {
uuid id
uuid agent_id
uuid channel_id
datetime started_at
datetime last_activity_at
string status
}
USERS ||--o{ MICRODAOS : "owns"
USERS ||--o{ MICRODAO_MEMBERS : "member"
MICRODAOS ||--o{ MICRODAO_MEMBERS : "has members"
USERS ||--o{ AGENTS : "owns"
MICRODAOS ||--o{ AGENTS : "scoped"
AGENT_BLUEPRINTS ||--o{ AGENTS : "template"
MICRODAOS ||--o{ CHANNELS : "has"
CHANNELS ||--o{ CHANNEL_MEMBERS : "members"
USERS ||--o{ CHANNEL_MEMBERS : "user member"
AGENTS ||--o{ CHANNEL_MEMBERS : "agent member"
CHANNELS ||--o{ MESSAGES : "contains"
USERS ||--o{ MESSAGES : "sender"
AGENTS ||--o{ MESSAGES : "sender"
AGENTS ||--o{ AGENT_SESSIONS : "runs"
CHANNELS ||--o{ AGENT_SESSIONS : "context"
```
### Key Relationships
- `users``microdaos` (1:many, via owner)
- `microdaos``users` (many:many, via `microdao_members`)
- `agents``microdaos` (many:1, optional scope)
- `agents``users` (many:1, optional owner)
- `channels``microdaos` (many:1, required)
- `channels` ↔ (`users` + `agents`) (many:many, via `channel_members`)
- `messages``channels` (many:1)
- `messages` → (`users` | `agents`) (many:1, sender)
### Matrix Mapping
| DAARION | Matrix |
|---------|--------|
| `channels.matrix_room_id` | `room_id` (!abc:server) |
| `messages.matrix_event_id` | `event_id` ($event:server) |
| `users.matrix_id` | `user_id` (@user:server) |
| `agents.matrix_id` | `user_id` (@agent:server) |
---
## matrix-gateway API Specification
### Authentication
All internal endpoints require:
```
X-Internal-Service-Token: <shared-secret>
```
### 1. Create Room
**POST /internal/matrix/rooms**
Create Matrix room for DAARION channel.
**Request:**
```json
{
"room_alias": "microdao7-general",
"name": "Quantum Garden / General",
"visibility": "private",
"creator": "user:93",
"microdao_id": "microdao:7",
"preset": "trusted_private_chat",
"power_users": ["user:93", "agent:sofia"]
}
```
**Response:**
```json
{
"room_id": "!abc123:matrix.daarion.city",
"room_alias": "#microdao7-general:matrix.daarion.city"
}
```
**Actions:**
- Call `/_matrix/client/v3/createRoom`
- Set power levels for users/agents
- Add custom state (`microdao_id`)
- Publish NATS `integration.matrix.room.created`
---
### 2. Send Message
**POST /internal/matrix/send**
Send message to Matrix room.
**Request:**
```json
{
"room_id": "!abc123:matrix.daarion.city",
"sender": "agent:sofia",
"sender_matrix_id": "@sofia:matrix.daarion.city",
"msgtype": "m.text",
"body": "Короткий summary останніх DAO подій.",
"relates_to": {
"m.in_reply_to": {
"event_id": "$event123:matrix.daarion.city"
}
},
"meta": {
"channel_id": "7c72d497-27aa-4e75-bb2f-4a4a21d4f91f",
"microdao_id": "microdao:7",
"agent_id": "agent:sofia"
}
}
```
**Response:**
```json
{
"event_id": "$event999:matrix.daarion.city",
"room_id": "!abc123:matrix.daarion.city"
}
```
**Actions:**
- Call `/_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId}`
- Generate unique `txnId`
- Return `event_id`
- Publish NATS `integration.matrix.message`
---
### 3. Invite User
**POST /internal/matrix/invite**
Invite user/agent to room.
**Request:**
```json
{
"room_id": "!abc123:matrix.daarion.city",
"user_matrix_id": "@alice:matrix.daarion.city"
}
```
**Response:**
```json
{
"ok": true
}
```
---
### 4. Get Room History
**GET /internal/matrix/rooms/{room_id}/messages**
Retrieve message history (for agents/services).
**Query params:**
- `from` — pagination token (optional)
- `limit` — max events (default 50)
- `dir``b` (backwards) or `f` (forwards)
**Response:**
```json
{
"chunk": [
{
"event_id": "$event123:matrix.daarion.city",
"sender": "@alice:matrix.daarion.city",
"origin_server_ts": 1735749000000,
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "Привіт, DAARION!"
}
}
],
"start": "t1-12345_67890_1234",
"end": "t1-12345_67890_1200"
}
```
**Actions:**
- Call `/_matrix/client/v3/rooms/{roomId}/messages`
- Return paginated events
---
### 5. Webhook: Receive Matrix Events
**POST /internal/matrix/event**
Receive events from Matrix (via appservice/webhook).
**Request (from Matrix):**
```json
{
"room_id": "!abc123:matrix.daarion.city",
"event_id": "$event123:matrix.daarion.city",
"sender": "@alice:matrix.daarion.city",
"type": "m.room.message",
"origin_server_ts": 1735749000000,
"content": {
"msgtype": "m.text",
"body": "Привіт з Matrix!"
},
"unsigned": {
"age": 123
}
}
```
**Actions:**
1. Validate source (shared secret / IP allowlist)
2. Transform to internal DTO:
```json
{
"room_id": "!abc123:matrix.daarion.city",
"event_id": "$event123:matrix.daarion.city",
"sender_matrix_id": "@alice:matrix.daarion.city",
"type": "m.room.message",
"timestamp": 1735749000000,
"body": "Привіт з Matrix!",
"msgtype": "m.text"
}
```
3. Publish to NATS:
- Subject: `integration.matrix.message`
- Payload: DTO + raw content
---
### 6. Health Check
**GET /internal/matrix/health**
**Response:**
```json
{
"status": "ok",
"homeserver": "https://matrix.daarion.city",
"appservice_enabled": true
}
```
---
## Message Flow: Human → Agent Reply
### Sequence Diagram
```mermaid
sequenceDiagram
participant User
participant Frontend
participant messaging-service
participant matrix-gateway
participant Matrix
participant NATS
participant agent_filter
participant DAGI_Router
participant Agent_Runtime
participant LLM_Proxy
User->>Frontend: Type message
Frontend->>messaging-service: POST /api/messaging/channels/{id}/messages
messaging-service->>matrix-gateway: POST /internal/matrix/send
matrix-gateway->>Matrix: Send m.room.message
Matrix-->>matrix-gateway: event_id
matrix-gateway-->>messaging-service: event_id
messaging-service->>messaging-service: Index in messages table
messaging-service->>NATS: Publish messaging.message.created
messaging-service-->>Frontend: 201 Created
Frontend->>Frontend: Display message
NATS->>agent_filter: messaging.message.created
agent_filter->>agent_filter: Check rules (channel, content, agents)
alt Allow agent reply
agent_filter->>NATS: Publish agent.filter.decision (allow)
NATS->>DAGI_Router: agent.filter.decision
DAGI_Router->>DAGI_Router: Select model, pipeline
DAGI_Router->>Agent_Runtime: Invoke agent:sofia
Agent_Runtime->>messaging-service: GET /internal/messaging/channels/{id}/messages
messaging-service-->>Agent_Runtime: Recent messages
Agent_Runtime->>Agent_Runtime: Query Agent Memory (RAG)
Agent_Runtime->>Agent_Runtime: Build prompt
Agent_Runtime->>LLM_Proxy: Generate response
LLM_Proxy-->>Agent_Runtime: Response text
Agent_Runtime->>messaging-service: POST /internal/agents/agent:sofia/post-to-channel
messaging-service->>matrix-gateway: POST /internal/matrix/send (as agent)
matrix-gateway->>Matrix: Send m.room.message (agent)
Matrix-->>matrix-gateway: event_id
matrix-gateway->>NATS: Publish integration.matrix.message
messaging-service->>messaging-service: Index agent message
messaging-service->>NATS: Publish messaging.message.created
messaging-service->>Frontend: WebSocket: message.created
Frontend->>Frontend: Display agent reply
else Deny agent reply
agent_filter->>agent_filter: No action
end
```
### Step-by-Step Flow
#### 1. User sends message
**Frontend:**
```typescript
await sendMessage(channelId, { text: "Hello!" });
```
**messaging-service:**
- Validates user permissions
- Calls `matrix-gateway` → Matrix
- Indexes message in DB
- Publishes NATS `messaging.message.created`:
```json
{
"channel_id": "...",
"matrix_event_id": "$event",
"sender_id": "user:93",
"sender_type": "human",
"microdao_id": "microdao:7",
"created_at": "2025-11-24T10:30:00Z"
}
```
#### 2. agent_filter processes event
Subscribed to: `messaging.message.created`
**Logic:**
- Check channel type (public/private/microdao)
- Check agent access (is agent member? can_write?)
- Check content (spam, policy violations)
- Check context (time, frequency)
**Decision:**
- `ALLOW` → route to agent
- `DENY` → no action
- `MODIFY` → rewrite prompt
**Publish NATS:**
```json
Subject: "agent.filter.decision"
Payload: {
"channel_id": "...",
"message_id": "...",
"matrix_event_id": "$event",
"microdao_id": "microdao:7",
"decision": "allow",
"target_agent_id": "agent:sofia",
"rewrite_prompt": null
}
```
#### 3. DAGI Router invokes agent
Subscribed to: `agent.filter.decision` (only `allow`)
**Actions:**
- Load agent blueprint → get model
- Determine pipeline (Memory? Tools?)
- Create `AgentInvocation`:
```json
{
"agent_id": "agent:sofia",
"entrypoint": "channel_message",
"payload": {
"channel_id": "...",
"message_id": "...",
"microdao_id": "microdao:7"
}
}
```
- Send to `Agent Runtime`
#### 4. Agent Runtime executes
**a) Read channel context:**
```http
GET /internal/messaging/channels/{channelId}/messages?limit=50
```
**b) Query Agent Memory:**
- Fetch relevant memories for agent + microdao
- RAG query based on message content
**c) Build prompt:**
- System instructions (from blueprint)
- Channel history (truncated)
- Relevant memories
- Optional rewrite from agent_filter
**d) Call LLM Proxy:**
```json
{
"model": "gpt-4.1",
"messages": [
{"role": "system", "content": "..."},
{"role": "user", "content": "..."}
]
}
```
**e) Execute tools (if needed):**
- Create task, followup, etc.
**f) Post reply:**
```http
POST /internal/agents/agent:sofia/post-to-channel
{
"channel_id": "...",
"text": "Ось короткий summary..."
}
```
#### 5. messaging-service posts agent message
- Find `matrix_room_id` by `channel_id`
- Call `matrix-gateway` → Matrix (as agent)
- Index message in DB (`sender_type = "agent"`)
- Publish NATS `messaging.message.created`
#### 6. Frontend receives update
- WebSocket `/ws/messaging/{channelId}` gets signal
- Display agent message in UI
---
## Agent-Initiated Messages
### Use Cases
- Scheduled reminders
- Daily digests
- Event notifications
- Autonomous agent actions
### Flow
```mermaid
sequenceDiagram
participant Cron/Event
participant Agent_Runtime
participant messaging-service
participant matrix-gateway
participant Matrix
participant NATS
participant Frontend
Cron/Event->>Agent_Runtime: Trigger (e.g. daily digest)
Agent_Runtime->>Agent_Runtime: Generate message
Agent_Runtime->>messaging-service: POST /internal/agents/{id}/post-to-channel
messaging-service->>matrix-gateway: POST /internal/matrix/send
matrix-gateway->>Matrix: Send m.room.message
Matrix-->>matrix-gateway: event_id
matrix-gateway->>NATS: Publish integration.matrix.message
messaging-service->>messaging-service: Index message
messaging-service->>NATS: Publish messaging.message.created
messaging-service->>Frontend: WebSocket: message.created
Frontend->>Frontend: Display message
```
**Key difference:** No agent_filter check (agent explicitly decided to post).
Optional: Add `system_override` flag to bypass filter.
---
## agent_filter Rules
### Decision Logic
```python
def agent_filter_decision(event: MessageCreatedEvent) -> FilterDecision:
# 1. Check channel permissions
if not is_agent_member(event.channel_id, target_agent_id):
return FilterDecision(decision="deny", reason="not_member")
if not has_write_permission(event.channel_id, target_agent_id):
return FilterDecision(decision="deny", reason="no_write_permission")
# 2. Check content policy
if contains_spam(event.content):
return FilterDecision(decision="deny", reason="spam")
if violates_policy(event.content):
return FilterDecision(decision="modify", rewrite="Sanitize content")
# 3. Check context (rate limiting, time of day)
if too_many_agent_messages_recently(event.channel_id):
return FilterDecision(decision="deny", reason="rate_limit")
# 4. Check microdao rules
microdao_rules = get_microdao_rules(event.microdao_id)
if not microdao_rules.allow_agents:
return FilterDecision(decision="deny", reason="microdao_policy")
# 5. Select agent
target_agent = select_best_agent(event.channel_id, event.content)
return FilterDecision(
decision="allow",
target_agent_id=target_agent.id,
rewrite_prompt=None
)
```
### Rules Categories
1. **Permissions**
- Is agent member of channel?
- Does agent have `can_write` permission?
- Is channel in agent's allowed scope?
2. **Content Policy**
- Spam detection
- Profanity filter
- Sensitive topics
- Privacy violations
3. **Context Rules**
- Rate limiting (max N messages per hour)
- Time of day restrictions
- Frequency (don't reply to every message)
4. **microDAO Rules**
- Are agents allowed in this microDAO?
- Which agent roles are permitted?
- Custom governance policies
5. **Agent Selection**
- Which agent should respond? (Team Assistant, Quest Agent, etc.)
- Based on content, channel type, time
---
## DAGI Router Integration
### Router Rules for Messaging
```yaml
rules:
- name: "messaging.inbound"
trigger: "agent.filter.decision"
condition: "decision == 'allow'"
action:
type: "invoke_agent"
agent_id: "{{ target_agent_id }}"
entrypoint: "channel_message"
payload:
channel_id: "{{ channel_id }}"
message_id: "{{ message_id }}"
microdao_id: "{{ microdao_id }}"
- name: "messaging.scheduled"
trigger: "cron.daily_digest"
condition: "time == '09:00'"
action:
type: "invoke_agent"
agent_id: "agent:daily-digest"
entrypoint: "generate_digest"
payload:
microdao_id: "{{ microdao_id }}"
```
### Agent Invocation
```json
{
"invocation_id": "inv-uuid",
"agent_id": "agent:sofia",
"entrypoint": "channel_message",
"payload": {
"channel_id": "uuid",
"message_id": "uuid",
"microdao_id": "microdao:7"
},
"context": {
"model": "gpt-4.1",
"temperature": 0.7,
"max_tokens": 500,
"tools": ["create_task", "create_followup"],
"memory_enabled": true
}
}
```
---
## Implementation Guide
### Phase 1: Core Infrastructure (DONE ✅)
- [x] Database schema (channels, messages, channel_members)
- [x] messaging-service (REST + WebSocket)
- [x] matrix-gateway API spec
- [x] Frontend UI (MessengerPage)
- [x] Docker orchestration
### Phase 2: Agent Integration (NEXT)
- [ ] Implement agent_filter service
- [ ] Extend DAGI Router with messaging rules
- [ ] Add Agent Runtime channel context reader
- [ ] Implement `/internal/agents/{id}/post-to-channel` logic
- [ ] NATS event integration (actual publishing)
### Phase 3: Advanced Features
- [ ] Agent Memory integration (RAG for channel context)
- [ ] Tool execution (create_task from messages)
- [ ] Multi-agent coordination
- [ ] Scheduled agent messages (digests, reminders)
### Phase 4: Production Hardening
- [ ] Rate limiting (per agent, per channel)
- [ ] Abuse detection
- [ ] Analytics and metrics
- [ ] A/B testing for agent responses
---
## NATS Event Catalog
### Published by messaging-service
#### messaging.message.created
```json
{
"channel_id": "uuid",
"matrix_event_id": "$event:server",
"sender_id": "user:93 | agent:sofia",
"sender_type": "human | agent",
"microdao_id": "microdao:7",
"content_preview": "Hello!",
"created_at": "2025-11-24T10:30:00Z"
}
```
#### messaging.channel.created
```json
{
"channel_id": "uuid",
"microdao_id": "microdao:7",
"matrix_room_id": "!room:server",
"created_by": "user:93",
"visibility": "public"
}
```
### Published by matrix-gateway
#### integration.matrix.message
```json
{
"room_id": "!room:server",
"event_id": "$event:server",
"sender_matrix_id": "@user:server",
"type": "m.room.message",
"timestamp": 1735749000000,
"content": {
"msgtype": "m.text",
"body": "Hello!"
}
}
```
#### integration.matrix.room.created
```json
{
"room_id": "!room:server",
"room_alias": "#alias:server",
"creator": "user:93",
"microdao_id": "microdao:7"
}
```
### Published by agent_filter
#### agent.filter.decision
```json
{
"channel_id": "uuid",
"message_id": "uuid",
"matrix_event_id": "$event:server",
"microdao_id": "microdao:7",
"decision": "allow | deny | modify",
"target_agent_id": "agent:sofia",
"rewrite_prompt": "Sanitize...",
"reason": "not_member | spam | rate_limit | policy"
}
```
---
## Security Considerations
### 1. Agent Access Control
- Agents must be explicitly added to channels
- `can_write` permission required
- agent_filter validates all agent replies
### 2. Content Safety
- Spam detection
- Profanity filtering
- PII detection (for confidential channels)
### 3. Rate Limiting
- Per agent: max 10 messages/hour
- Per channel: max 50% agent messages
- Global: max 1000 agent messages/hour
### 4. Audit Trail
- All agent actions logged in `channel_events`
- Matrix events are immutable (audit log)
- NATS events retained for 30 days
### 5. Confidential Channels
- E2EE channels: agents can't read plaintext
- Agents operate on summaries/metadata only
---
## Performance Targets
| Metric | Target | Notes |
|--------|--------|-------|
| Message send latency | < 100ms | User → Matrix → Index |
| Agent reply latency | < 3s | Full pipeline (filter → LLM → post) |
| WebSocket latency | < 50ms | Real-time updates |
| Channel list load | < 500ms | With 100+ channels |
| Message history (50) | < 300ms | Paginated from index |
| Agent context load | < 1s | 50 messages + memory query |
---
## Testing Checklist
### Integration Tests
- [ ] User sends message → indexed correctly
- [ ] Message appears in Element
- [ ] Agent reply triggered by filter
- [ ] Agent reply appears in DAARION UI
- [ ] Agent reply appears in Element
- [ ] Multiple agents in same channel
- [ ] Rate limiting works
- [ ] Confidential channel blocks agent
### E2E Tests
- [ ] Full flow: User → Agent → Reply (< 5s)
- [ ] Agent-initiated message (digest)
- [ ] Multi-agent conversation
- [ ] Tool execution from agent message
---
## Roadmap
### v1.1 (2 weeks)
- Implement agent_filter service
- DAGI Router messaging rules
- NATS event publishing (production)
- Agent Memory integration
### v1.2 (1 month)
- Multi-agent coordination
- Scheduled agent messages
- Analytics dashboard
### v2.0 (2 months)
- Voice messages (agent TTS)
- Agent-to-agent direct messaging
- Federated agents (cross-homeserver)
---
**Version:** 1.0.0
**Last Updated:** 2025-11-24
**Status:** Production Ready (Phase 1), Phase 2 Spec Complete
**Maintainer:** DAARION Platform Team