- Node-guardian running on MacBook and updating metrics - NODE2 agents (Atlas, Greeter, Oracle, Builder Bot) assigned to node-2-macbook-m4max - Swapper models displaying correctly (8 models) - DAGI Router agents showing with correct status (3 active, 1 stale) - Router health check using node_cache for remote nodes
425 lines
13 KiB
Markdown
425 lines
13 KiB
Markdown
# TASK: PHASE 2 — Agent Integration (agent_filter + DAGI Router + Agent Runtime)
|
||
|
||
**Goal:**
|
||
Зробити Messenger повноцінно агентним:
|
||
- новий сервіс agent_filter, який вирішує, коли й який агент відповідає;
|
||
- розширити DAGI Router, щоб маршрутизувати події з Messenger до агентів;
|
||
- реалізувати agent-runtime-service, який читає історію каналів, викликає LLM і постить відповіді назад у Messenger.
|
||
|
||
**Existing:**
|
||
- Messenger Module (Matrix-aware, Full Stack) вже реалізований:
|
||
- messaging-service (FastAPI, 9 endpoints + WS)
|
||
- matrix-gateway API spec (services/matrix-gateway/API_SPEC.md)
|
||
- DB schema (channels, messages, channel_members, message_reactions, channel_events)
|
||
- frontend /messenger (ChannelList, MessageList, MessageComposer, WS)
|
||
- NATS, Synapse, matrix-gateway, messaging-service у docker-compose.messenger.yml
|
||
- Документація:
|
||
- docs/MESSENGER_MODULE_COMPLETE.md
|
||
- docs/MESSAGING_ARCHITECTURE.md
|
||
- docs/messaging-erd.dbml
|
||
- docs/MESSENGER_TESTING_GUIDE.md
|
||
|
||
**PHASE 2 складається з 3 підзадач:**
|
||
|
||
---
|
||
|
||
## 1) Сервіс agent_filter
|
||
|
||
**Create new service:** `services/agent-filter/`
|
||
|
||
**Files:**
|
||
- `services/agent-filter/main.py`
|
||
- `services/agent-filter/models.py`
|
||
- `services/agent-filter/rules.py`
|
||
- `services/agent-filter/config.yaml`
|
||
- `services/agent-filter/requirements.txt`
|
||
- `services/agent-filter/Dockerfile`
|
||
- `services/agent-filter/README.md`
|
||
|
||
**Tech:**
|
||
- Python + FastAPI
|
||
- NATS JetStream client (python-nats)
|
||
- Config з YAML
|
||
|
||
### 1.1 Models (models.py)
|
||
|
||
Define Pydantic models:
|
||
|
||
```python
|
||
from pydantic import BaseModel
|
||
from typing import Optional, Literal
|
||
from datetime import datetime
|
||
|
||
class MessageCreatedEvent(BaseModel):
|
||
channel_id: str
|
||
message_id: Optional[str] = None
|
||
matrix_event_id: str
|
||
sender_id: str
|
||
sender_type: Literal["human", "agent"]
|
||
microdao_id: str
|
||
created_at: datetime
|
||
|
||
class FilterDecision(BaseModel):
|
||
channel_id: str
|
||
message_id: Optional[str] = None
|
||
matrix_event_id: str
|
||
microdao_id: str
|
||
decision: Literal["allow", "deny", "modify"]
|
||
target_agent_id: Optional[str] = None
|
||
rewrite_prompt: Optional[str] = None
|
||
|
||
class ChannelContext(BaseModel):
|
||
microdao_id: str
|
||
visibility: Literal["public", "private", "microdao"]
|
||
allowed_agents: list[str] = []
|
||
disabled_agents: list[str] = []
|
||
|
||
class FilterContext(BaseModel):
|
||
channel: ChannelContext
|
||
sender_is_owner: bool = False
|
||
sender_is_admin: bool = False
|
||
sender_is_member: bool = True
|
||
local_time: Optional[datetime] = None
|
||
```
|
||
|
||
### 1.2 Rules (rules.py)
|
||
|
||
Implement:
|
||
|
||
```python
|
||
def decide(event: MessageCreatedEvent, ctx: FilterContext) -> FilterDecision:
|
||
"""
|
||
Baseline rules v1:
|
||
- Якщо event.sender_type == "agent" → decision = "deny" (щоб не було loop).
|
||
- Якщо channel.visibility == "microdao" і є default assistant агента для microDAO:
|
||
- target_agent_id = цей агент (поки можна жорстко прописати в config або заглушка).
|
||
- Якщо час у quiet_hours (23:00–07:00 з config.yaml):
|
||
- decision = "modify"
|
||
- rewrite_prompt = "Відповідай стисло і тільки якщо запит важливий. Не ініціюй розмову сам."
|
||
- Якщо агент заборонений у цьому каналі (agent_id у disabled_agents) → decision = "deny".
|
||
- Якщо немає жодного кандидата → decision = "deny".
|
||
"""
|
||
pass
|
||
```
|
||
|
||
**config.yaml:**
|
||
```yaml
|
||
nats:
|
||
servers: ["nats://nats:4222"]
|
||
messaging_subject: "messaging.message.created"
|
||
decision_subject: "agent.filter.decision"
|
||
rules:
|
||
quiet_hours:
|
||
start: "23:00"
|
||
end: "07:00"
|
||
default_agents:
|
||
"microdao:daarion": "agent:sofia"
|
||
```
|
||
|
||
### 1.3 main.py
|
||
|
||
- Підняти FastAPI:
|
||
- `GET /health` → `{ "status": "ok" }`
|
||
- `POST /internal/agent-filter/test` → приймає MessageCreatedEvent, викликає rules.decide(...), повертає FilterDecision.
|
||
|
||
- На startup:
|
||
- підʼєднатися до NATS
|
||
- підписатися на subject `messaging.message.created`
|
||
|
||
**Алгоритм обробки:**
|
||
1. Deserialize payload у MessageCreatedEvent.
|
||
2. Підібрати ChannelContext:
|
||
- `GET /internal/messaging/channels/{channel_id}/context` (потрібно додати цей endpoint у messaging-service, якщо ще нема).
|
||
- Очікуваний response:
|
||
```json
|
||
{
|
||
"microdao_id": "...",
|
||
"visibility": "microdao",
|
||
"allowed_agents": ["agent:sofia"],
|
||
"disabled_agents": []
|
||
}
|
||
```
|
||
3. Побудувати FilterContext.
|
||
4. Викликати rules.decide(event, ctx).
|
||
5. Опублікувати FilterDecision у NATS:
|
||
- subject: `agent.filter.decision`
|
||
- payload: `decision.json()`
|
||
|
||
### 1.4 Dockerfile + README
|
||
|
||
- Dockerfile подібний до messaging-service.
|
||
- README.md:
|
||
- як запускати локально,
|
||
- як тестувати `/internal/agent-filter/test`,
|
||
- приклад NATS payload.
|
||
|
||
---
|
||
|
||
## 2) Розширення DAGI Router під messaging.inbound
|
||
|
||
**Goal:**
|
||
- DAGI Router має слухати `agent.filter.decision` і на основі allow-рішень створювати AgentInvocation і штовхати в `router.invoke.agent`.
|
||
|
||
### 2.1 NATS subscription
|
||
|
||
У `services/router/` (або де реалізований DAGI Router):
|
||
|
||
- Підписка на subject: `agent.filter.decision`.
|
||
|
||
**Очікуваний payload:** FilterDecision (див. вище).
|
||
|
||
**Алгоритм:**
|
||
- Якщо `decision != "allow"` → ігноруємо.
|
||
- Якщо `decision == "allow"` і `target_agent_id` не заданий → логування + ігнор.
|
||
- Інакше: побудувати AgentInvocation:
|
||
|
||
```json
|
||
{
|
||
"agent_id": "<target_agent_id>",
|
||
"entrypoint": "channel_message",
|
||
"payload": {
|
||
"channel_id": "<channel_id>",
|
||
"message_id": "<message_id>",
|
||
"matrix_event_id": "<matrix_event_id>",
|
||
"microdao_id": "<microdao_id>",
|
||
"rewrite_prompt": "<rewrite_prompt>"
|
||
}
|
||
}
|
||
```
|
||
|
||
Опублікувати у NATS:
|
||
- subject: `router.invoke.agent`
|
||
- payload: AgentInvocation JSON.
|
||
|
||
### 2.2 Моделі
|
||
|
||
Додати в Router:
|
||
|
||
```python
|
||
from pydantic import BaseModel
|
||
from typing import Literal
|
||
|
||
class AgentInvocation(BaseModel):
|
||
agent_id: str
|
||
entrypoint: Literal["channel_message", "direct", "cron"] = "channel_message"
|
||
payload: dict
|
||
```
|
||
|
||
### 2.3 Конфіг
|
||
|
||
Файл `router_config.yaml` (або аналог):
|
||
|
||
```yaml
|
||
messaging_inbound:
|
||
enabled: true
|
||
source_subject: "agent.filter.decision"
|
||
target_subject: "router.invoke.agent"
|
||
```
|
||
|
||
### 2.4 HTTP debug endpoint
|
||
|
||
Додати в Router:
|
||
|
||
**POST /internal/router/test-messaging**
|
||
|
||
Body: FilterDecision
|
||
|
||
Behavior:
|
||
- прогнати той самий код, який обробляє NATS event,
|
||
- повернути AgentInvocation JSON без публікації у NATS.
|
||
|
||
---
|
||
|
||
## 3) Agent Runtime integration з Messenger
|
||
|
||
**Goal:**
|
||
- Реалізувати agent-runtime-service, який:
|
||
- читає контекст каналу (останні повідомлення),
|
||
- читає памʼять агента,
|
||
- викликає LLM через LLM Proxy,
|
||
- постить відповідь у канал через messaging-service.
|
||
|
||
**Create service:** `services/agent-runtime/`
|
||
|
||
**Files:**
|
||
- `services/agent-runtime/main.py`
|
||
- `services/agent-runtime/models.py`
|
||
- `services/agent-runtime/llm_client.py`
|
||
- `services/agent-runtime/messaging_client.py`
|
||
- `services/agent-runtime/memory_client.py`
|
||
- `services/agent-runtime/config.yaml`
|
||
- `services/agent-runtime/requirements.txt`
|
||
- `services/agent-runtime/Dockerfile`
|
||
- `services/agent-runtime/README.md`
|
||
|
||
### 3.1 Models (models.py)
|
||
|
||
```python
|
||
from pydantic import BaseModel
|
||
from typing import Literal
|
||
from datetime import datetime
|
||
|
||
class AgentInvocation(BaseModel):
|
||
agent_id: str
|
||
entrypoint: Literal["channel_message", "direct", "cron"] = "channel_message"
|
||
payload: dict
|
||
|
||
class ChannelContextMessage(BaseModel):
|
||
sender_id: str
|
||
sender_type: Literal["human", "agent"]
|
||
content: str
|
||
created_at: datetime
|
||
```
|
||
|
||
### 3.2 NATS subscription
|
||
|
||
**main.py:**
|
||
- Підʼєднатись до NATS.
|
||
- Підписатися на `router.invoke.agent`.
|
||
|
||
**Алгоритм:**
|
||
1. Deserialize AgentInvocation.
|
||
2. Якщо `entrypoint != "channel_message"` → поки що ігноруємо (або лог).
|
||
3. Витягти:
|
||
- `agent_id`
|
||
- `channel_id`, `message_id`, `matrix_event_id`, `microdao_id`, `rewrite_prompt` з payload.
|
||
4. Завантажити blueprint агента:
|
||
```http
|
||
GET /internal/agents/{agent_id}/blueprint
|
||
```
|
||
Очікуваний response:
|
||
```json
|
||
{
|
||
"id": "...",
|
||
"name": "Sofia-Prime",
|
||
"model": "gpt-4.1",
|
||
"instructions": "System prompt...",
|
||
"capabilities": {...}
|
||
}
|
||
```
|
||
5. Завантажити історію каналу:
|
||
```http
|
||
GET /internal/messaging/channels/{channel_id}/messages?limit=50
|
||
```
|
||
→ вернути список повідомлень у форматі ChannelContextMessage.
|
||
|
||
6. Витягти останнє human-повідомлення як user input.
|
||
|
||
7. Запитати памʼять:
|
||
```http
|
||
POST /internal/agent-memory/query
|
||
{
|
||
"agent_id": "<agent_id>",
|
||
"microdao_id": "<microdao_id>",
|
||
"channel_id": "<channel_id>",
|
||
"query": "<останній текст користувача>"
|
||
}
|
||
```
|
||
Очікуваний response: список релевантних фрагментів knowledge base.
|
||
|
||
8. Побудувати промпт для LLM (llm_client.py):
|
||
- system: інструкції з blueprint +, якщо є, rewrite_prompt
|
||
- context: останні N повідомлень (з імʼям, роллю, часом)
|
||
- memory: релевантні фрагменти
|
||
- user: останній текст користувача
|
||
|
||
9. Викликати LLM через LLM Proxy:
|
||
```http
|
||
POST /internal/llm/proxy
|
||
{
|
||
"model": "<з blueprint>",
|
||
"messages": [ {"role": "...", "content": "..."}, ... ]
|
||
}
|
||
```
|
||
Очікуваний response:
|
||
```json
|
||
{ "content": "<текст відповіді>" }
|
||
```
|
||
|
||
10. Надіслати відповідь у канал:
|
||
```http
|
||
POST /internal/agents/{agentId}/post-to-channel
|
||
{
|
||
"channel_id": "<channel_id>",
|
||
"text": "<llm response>"
|
||
}
|
||
```
|
||
Цей endpoint вже повинен існувати у messaging-service (як внутрішній).
|
||
|
||
11. (optional v1) Записати в памʼять:
|
||
```http
|
||
POST /internal/agent-memory/store
|
||
{
|
||
"agent_id": "<agent_id>",
|
||
"microdao_id": "<microdao_id>",
|
||
"channel_id": "<channel_id>",
|
||
"content": {
|
||
"user_message": "...",
|
||
"agent_reply": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 HTTP debug endpoint
|
||
|
||
**main.py:**
|
||
|
||
**POST /internal/agent-runtime/test-channel**
|
||
|
||
Body: AgentInvocation
|
||
|
||
Behavior:
|
||
- викликає ту саму логіку, що NATS handler,
|
||
- але замість реального POST до `/internal/agents/{agentId}/post-to-channel` просто повертає згенерований текст і зібраний prompt (обережно, без секретів у логах).
|
||
|
||
### 3.4 Docker + README
|
||
|
||
- Dockerfile за шаблоном інших сервісів.
|
||
- README.md:
|
||
- як запускати локально,
|
||
- як тестувати через `/internal/agent-runtime/test-channel`,
|
||
- як дивитись NATS events.
|
||
|
||
---
|
||
|
||
## 4) Інтеграція в docker-compose та документацію
|
||
|
||
### 4.1 docker-compose
|
||
|
||
- Додати `agent-filter`, `router` (якщо ще не доданий), `agent-runtime` у загальний docker-compose (або створити окремий `docker-compose.agents.yml`).
|
||
- Забезпечити доступ до:
|
||
- NATS
|
||
- messaging-service
|
||
- agent-memory-service (якщо вже існує) / stub
|
||
- agents-service (blueprints) / stub
|
||
- llm-proxy-service / stub
|
||
|
||
### 4.2 Документація
|
||
|
||
Оновити/додати:
|
||
- `docs/MESSAGING_ARCHITECTURE.md` — помітити, що PHASE 2 реалізовано.
|
||
- `docs/MESSENGER_COMPLETE_SPECIFICATION.md` — додати розділ "Agent Integration (PHASE 2)" з посиланнями на нові сервіси.
|
||
- При потребі: окремий `docs/AGENT_INTEGRATION_PHASE2.md` з коротким описом flow.
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
- ✅ Human → пише в канал → agent_filter приймає event → DAGI Router відправляє AgentInvocation → Agent Runtime читає історію й памʼять → агент відповідає в той самий канал → повідомлення відображається у /messenger і в Element.
|
||
- ✅ Є мінімум один робочий агент (наприклад, Sofia-Prime), який стабільно відповідає в одному каналі microDAO.
|
||
- ✅ Всі сервіси стартують через docker-compose, health-checkи зелені.
|
||
|
||
---
|
||
|
||
**Version:** 1.0.0
|
||
**Date:** 2025-11-24
|
||
**Priority:** High
|
||
**Estimated Time:** 4 weeks
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|