surfaces/docs/deploy-architecture.md
Mikhail Putilovskij 22a3a2b60a docs(05-04): document split deployment artifacts
- document prod vs fullstack compose usage
- align operator docs with shared /agents contract
2026-04-28 01:15:41 +03:00

158 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` не персистентный.