- document prod vs fullstack compose usage - align operator docs with shared /agents contract
158 lines
7.3 KiB
Markdown
158 lines
7.3 KiB
Markdown
# Deployment Architecture — Matrix Bot + Agents
|
||
|
||
> Сформировано 2026-04-27 по итогам обсуждения с платформой.
|
||
|
||
---
|
||
|
||
## Compose Artifacts
|
||
|
||
- **Production deploy:** `docker-compose.prod.yml`
|
||
Bot-only handoff. Поднимает только `matrix-bot`, монтирует shared volume в `/agents`, требует внешний `AGENT_BASE_URL`.
|
||
- **Internal full-stack E2E:** `docker-compose.fullstack.yml`
|
||
Внутренний harness. Поднимает `matrix-bot` и `platform-agent`, использует тот же volume name и health-gated startup через `condition: service_healthy`.
|
||
|
||
Production operators should run the bot with `docker-compose.prod.yml`; internal verification should use `docker-compose.fullstack.yml`.
|
||
Старый root compose harness больше не является primary runtime contract для Phase 05.
|
||
|
||
---
|
||
|
||
## Топология
|
||
|
||
```
|
||
lambda.coredump.ru
|
||
├── :7000 (reverse proxy, path-based routing)
|
||
│ ├── /agent_0/ → agent_0 container
|
||
│ ├── /agent_1/ → agent_1 container
|
||
│ └── /agent_N/ → agent_N container
|
||
│
|
||
└── Matrix bot instance (один инстанс на всех)
|
||
└── volume /agents/ (shared с агентами)
|
||
├── /agents/0/ ← workspace agent_0
|
||
├── /agents/1/ ← workspace agent_1
|
||
└── /agents/N/
|
||
```
|
||
|
||
- **Один инстанс Matrix-бота** обслуживает всех пользователей.
|
||
- **Один агент-контейнер на пользователя.** Изоляция по agent_id, не через chat_id внутри одного инстанса.
|
||
- **Shared volume** `/agents/` смонтирован в Matrix-бот. В internal full-stack harness тот же volume mounted as `/workspace` inside `platform-agent`, чтобы bot-side absolute paths и agent workspace относились к одному и тому же хранилищу.
|
||
|
||
---
|
||
|
||
## Конфиг (два словаря)
|
||
|
||
```yaml
|
||
# config/matrix-agents.yaml
|
||
|
||
user_agents:
|
||
"@user0:matrix.lambda.coredump.ru": agent-0
|
||
"@user1:matrix.lambda.coredump.ru": agent-1
|
||
"@user2:matrix.lambda.coredump.ru": agent-2
|
||
|
||
agents:
|
||
- id: agent-0
|
||
label: "Agent 0"
|
||
base_url: "ws://lambda.coredump.ru:7000/agent_0/"
|
||
workspace_path: "/agents/0/"
|
||
|
||
- id: agent-1
|
||
label: "Agent 1"
|
||
base_url: "ws://lambda.coredump.ru:7000/agent_1/"
|
||
workspace_path: "/agents/1/"
|
||
```
|
||
|
||
- `user_agents` — маппинг Matrix user_id → agent_id (статический, выдаётся платформой)
|
||
- `agents` — маппинг agent_id → URL агента и путь к его workspace на shared volume
|
||
|
||
---
|
||
|
||
## Agent API (используем master ветку `platform/agent_api`)
|
||
|
||
```python
|
||
from lambda_agent_api.agent_api import AgentApi
|
||
|
||
connected_agents: dict[tuple[str, int], AgentApi] = {}
|
||
|
||
def on_agent_disconnect(agent: AgentApi):
|
||
connected_agents.pop((agent.id, agent.chat_id), None)
|
||
|
||
async def on_message(matrix_user_id: str, matrix_room_id: str, text: str):
|
||
agent_id = get_agent_id_by_user(matrix_user_id) # из user_agents конфига
|
||
platform_chat_id = get_room_platform_chat_id(matrix_room_id)
|
||
|
||
agent = connected_agents.get((agent_id, platform_chat_id))
|
||
if not agent:
|
||
agent = AgentApi(
|
||
agent_id,
|
||
get_agent_base_url(agent_id), # ws://lambda.coredump.ru:7000/agent_0/
|
||
on_disconnect=on_agent_disconnect,
|
||
chat_id=platform_chat_id, # отдельный thread на Matrix room
|
||
)
|
||
await agent.connect()
|
||
connected_agents[(agent_id, platform_chat_id)] = agent
|
||
|
||
async for event in agent.send_message(text):
|
||
...
|
||
```
|
||
|
||
**Параметры конструктора (master):**
|
||
```python
|
||
AgentApi(
|
||
agent_id: str,
|
||
base_url: str, # ws://host:port/agent_N/
|
||
chat_id: int = 0, # surfaces must supply per-room platform_chat_id
|
||
on_disconnect: callable,
|
||
)
|
||
```
|
||
|
||
**Lifecycle:** агент автоматически отключается после нескольких минут бездействия.
|
||
`on_disconnect` удаляет из пула → следующее сообщение создаёт новое соединение.
|
||
|
||
---
|
||
|
||
## Передача файлов
|
||
|
||
### Пользователь → Агент (входящий файл)
|
||
|
||
1. Matrix-бот получает файл от пользователя
|
||
2. Сохраняет в workspace агента: `/agents/{N}/incoming/{filename}`
|
||
3. Вызывает `agent.send_message(text, attachments=["incoming/filename"])`
|
||
— путь относительно `/workspace` агента
|
||
|
||
### Агент → Пользователь (исходящий файл)
|
||
|
||
1. Агент эмитит `MsgEventSendFile(path="output/report.pdf")`
|
||
2. Matrix-бот читает файл: `/agents/{N}/output/report.pdf`
|
||
3. Отправляет как Matrix file message пользователю
|
||
|
||
**Ключевое:** production handoff через `docker-compose.prod.yml` и internal E2E через `docker-compose.fullstack.yml` используют один и тот же `/agents` contract на стороне поверхности. Прямой HTTP-доступ к файлам не нужен.
|
||
|
||
---
|
||
|
||
## Текущее состояние platform-agent (main)
|
||
|
||
- Composio интегрирован в main (`#9-интеграция-composIO`)
|
||
- Агент требует в `.env`: `AGENT_ID`, `COMPOSIO_API_KEY`
|
||
- Backend: `IsolatedShellBackend` (main) / `CompositeBackend` (ветка `#19`, не merged)
|
||
- Memory: `MemorySaver` — история слетает при рестарте контейнера (known limitation)
|
||
|
||
---
|
||
|
||
## platform-master (будущее, пока не используем)
|
||
|
||
Ветка `feat/storage` реализует реальный Master-сервис:
|
||
- `POST /api/v1/create {chat_id}` → поднимает/переиспользует sandbox-контейнер
|
||
- TTL-based lifecycle (300с default, конфигурируемо)
|
||
- `ChatStorage` — API для upload/download файлов через Master
|
||
- Auth + p2p lease — вне текущего scope MVP
|
||
|
||
**Для деплоя MVP используем статический конфиг без Master.**
|
||
При готовности Master: `get_agent_url()` будет вызывать `POST /api/v1/create`, URL возвращается в ответе.
|
||
|
||
---
|
||
|
||
## Что НЕ решено / открытые вопросы
|
||
|
||
- Ветка `platform-agent_api #9-clientside-tool-call` убирает `attachments` и `MsgEventSendFile` — пока игнорируем, используем master. Уточнить у Азамата сроки мержа перед деплоем.
|
||
- `chat_id` — каждый Matrix chat room должен иметь собственный `platform_chat_id`. `!clear` должен ротировать `platform_chat_id` только для текущей комнаты, чтобы получить новый thread и чистый контекст без смены Matrix room.
|
||
- Composio `AGENT_ID` в `.env` для каждого агента — уточнить у платформы значения.
|
||
- Что происходит с историей при рестарте агента — `MemorySaver` не персистентный.
|