# Deployment Architecture — Matrix Bot + Agents > Сформировано 2026-04-27 по итогам обсуждения с платформой. --- ## Топология ``` 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-бот, и в каждый агент-контейнер. Агент видит свой подкаталог как `/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[str, AgentApi] = {} def on_agent_disconnect(agent: AgentApi): del connected_agents[agent.id] async def on_message(matrix_user_id: str, text: str): agent_id = get_agent_id_by_user(matrix_user_id) # из user_agents конфига agent = connected_agents.get(agent_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=0, # default, один чат на агента ) await agent.connect() connected_agents[agent_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, # default — один чат на агента 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 пользователю **Ключевое:** поверхность видит `/agents/` целиком через shared volume. Прямой 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` — при нашей модели C1/C2/C3 каждый чат должен иметь отдельный `chat_id`. Нужно решить: один `AgentApi` на агента (chat_id=0) или по инстансу на чат (chat_id=1/2/3). Пока берём `chat_id=0` (один контекст на пользователя). - Composio `AGENT_ID` в `.env` для каждого агента — уточнить у платформы значения. - Что происходит с историей при рестарте агента — `MemorySaver` не персистентный.