313 lines
15 KiB
Markdown
313 lines
15 KiB
Markdown
# Руководство по созданию новой поверхности
|
||
|
||
Этот документ описывает, как написать новую новую поверхность (например, Discord, Slack или Custom Web) по образцу текущей Matrix-поверхности в ветке `main`.
|
||
|
||
Он основан на актуальной реализации Matrix surface в репозитории и отражает текущую продакшн-логику, а не устаревший легаси.
|
||
|
||
---
|
||
|
||
## 1. Общая архитектура
|
||
|
||
### 1.1. Что такое поверхность
|
||
|
||
Поверхность — это тонкий адаптер между конкретной платформой (Платформа) и общим ядром бота.
|
||
|
||
В репозитории есть разделение:
|
||
|
||
- `core/` — общее ядро и бизнес-логика
|
||
- `adapter/<platform>/` — реализация конкретной поверхности
|
||
- `sdk/real.py` — работа с реальной платформой / агентом
|
||
- `config/` — статическая конфигурация агентов
|
||
- `docs/surface-protocol.md` — общий контракт поверхностей
|
||
|
||
### 1.2. Как это работает
|
||
|
||
Поверхность должна:
|
||
|
||
- принимать нативные события от Платформа
|
||
- преобразовывать их в единый внутренний контракт (`IncomingMessage`, `IncomingCommand`, `IncomingCallback`)
|
||
- передавать их в `core`
|
||
- получать ответы из `core` (`OutgoingMessage`, `OutgoingUI`, `OutgoingTyping`, `OutgoingNotification`)
|
||
- преобразовывать ответы обратно в нативные нативные сообщения
|
||
|
||
Поверхность не должна:
|
||
|
||
- управлять жизненным циклом агентских контейнеров
|
||
- хранить долгую историю бесед вне `core`/платформы
|
||
- аутентифицировать пользователей сама (если это не часть Платформа API)
|
||
|
||
---
|
||
|
||
## 2. Структура новой поверхности
|
||
|
||
### 2.1. Основные каталоги
|
||
|
||
Рекомендуемая структура для новой платформы:
|
||
|
||
```
|
||
adapter/<platform>/
|
||
bot.py
|
||
converter.py
|
||
agent_registry.py
|
||
files.py
|
||
handlers/
|
||
store.py
|
||
```
|
||
|
||
### 2.2. Принцип reuse
|
||
|
||
По примеру Matrix surface, New surface должен переиспользовать общий `core` и общий `sdk`.
|
||
|
||
Не дублируйте бизнес-логику, а реализуйте только адаптер:
|
||
|
||
- `adapter/<platform>/converter.py` — конвертация событий платформы ⇄ внутренние структуры
|
||
- `adapter/<platform>/bot.py` — основной runtime, старт Платформа client, loop, отправка/прием
|
||
- `adapter/<platform>/agent_registry.py` — загрузка `config/<platform>-agents.yaml`
|
||
- `adapter/<platform>/files.py` — хранение входящих/исходящих вложений
|
||
|
||
---
|
||
|
||
## 3. Контракт входящих/исходящих событий
|
||
|
||
### 3.1. Внутренний формат
|
||
|
||
Смотрите `core/protocol.py`. Основные типы:
|
||
|
||
- `IncomingMessage` — обычное текстовое сообщение + вложения
|
||
- `IncomingCommand` — управляющая команда
|
||
- `IncomingCallback` — подтверждение / интерактивные действия
|
||
- `OutgoingMessage` — ответ пользователю
|
||
- `OutgoingUI` — интерфейсные элементы (кнопки и т.п.)
|
||
- `OutgoingTyping` — индикатор печати
|
||
- `OutgoingNotification` — системное уведомление
|
||
|
||
### 3.2. Пример конверсии Matrix
|
||
|
||
В Matrix-реализации `adapter/matrix/converter.py`:
|
||
|
||
- текст `!yes` / `!no` превращается в `IncomingCallback` с `action: confirm/cancel`
|
||
- `!list`/`!remove` говорят не агенту, а surface-процессу
|
||
- вложения `m.file`, `m.image`, `m.audio`, `m.video` нормализуются в `Attachment`
|
||
|
||
Для Платформа реализуйте аналогичную логику для native команд вашего клиента.
|
||
|
||
---
|
||
|
||
## 4. Реестр агентов и маршрутизация
|
||
|
||
### 4.1. Что хранит реестр
|
||
|
||
В текущей Matrix реализации есть `config/matrix-agents.yaml` и `adapter/matrix/agent_registry.py`.
|
||
|
||
Структура:
|
||
|
||
```yaml
|
||
user_agents:
|
||
"@user0:matrix.example.org": agent-0
|
||
"@user1:matrix.example.org": agent-1
|
||
|
||
agents:
|
||
- id: agent-0
|
||
label: "Agent 0"
|
||
base_url: "http://lambda.coredump.ru:7000/agent_0/"
|
||
workspace_path: "/agents/0"
|
||
```
|
||
|
||
### 4.2. Логика выбора агента
|
||
|
||
- `user_agents` маппит конкретного пользователя на `agent_id`
|
||
- если user_id не найден, используется первый агент из списка
|
||
- `agents[].base_url` определяет URL агента
|
||
- `agents[].workspace_path` определяет путь внутри surface-контейнера для этого агента
|
||
|
||
Это важно: именно на этом контракте строится разделение агентов по рабочим каталогам.
|
||
|
||
### 4.3. Рекомендуемая Версия для новой платформы
|
||
|
||
Создайте `config/<platform>-agents.yaml` с тем же смыслом.
|
||
|
||
- `user_agents` — маппинг external user_id → agent_id
|
||
- `agents` — список агентов
|
||
- `workspace_path` для каждого агента должен быть абсолютным путем внутри surface-контейнера, например `/agents/0`
|
||
|
||
---
|
||
|
||
## 5. Файловый контракт
|
||
|
||
### 5.1. Shared volume
|
||
|
||
Текущее Matrix-решение использует shared volume:
|
||
|
||
- surface монтирует общий том как `/agents`
|
||
- каждый агент видит свою поддиректорию как `/workspace`
|
||
|
||
Топология:
|
||
|
||
```
|
||
Bot (/agents) Agent (/workspace = /agents/N/)
|
||
/agents/0/report.pdf ←──→ /workspace/report.pdf
|
||
```
|
||
|
||
### 5.2. Правила записи файлов
|
||
|
||
В `adapter/matrix/files.py` реализовано:
|
||
|
||
- входящий файл сохраняется прямо в `{workspace_root}/{filename}`
|
||
- возвращается путь `workspace_path` относительный внутри рабочего каталога агента
|
||
- при коллизии имен создаётся `file (1).ext`, `file (2).ext`
|
||
- `Attachment.workspace_path` передаётся агенту
|
||
|
||
Для исходящих файлов:
|
||
|
||
- surface читает файл из `workspace_root / workspace_path`
|
||
- загружает его в платформу
|
||
|
||
### 5.3. Пример поведения
|
||
|
||
- Пользователь отправляет файл → surface скачивает файл и кладёт его в agent workspace
|
||
- Агент получает `attachments=["report.pdf"]` и работает с относительным `workspace_path`
|
||
- Агент пишет результат в `/workspace/result.txt`
|
||
- surface читает `/agents/{N}/result.txt` и отправляет файл пользователю
|
||
|
||
---
|
||
|
||
## 6. Чат-менеджмент и контекст
|
||
|
||
### 6.1. `platform_chat_id`
|
||
|
||
Matrix-реализация использует `platform_chat_id` как стабильный идентификатор чата на стороне агента.
|
||
|
||
- `room_meta.platform_chat_id` определяется и сохраняется в `adapter/matrix/store.py`
|
||
- `reconcile_startup_state()` восстанавливает отсутствующие `platform_chat_id` при рестарте
|
||
- `RoutedPlatformClient` перенаправляет запросы агенту по `agent_id` + `platform_chat_id`
|
||
|
||
Для New surface тот же принцип:
|
||
|
||
- каждая внешняя беседа должна привязываться к одному внутреннему `chat_id`
|
||
- этот `chat_id` используется для вызовов агента
|
||
- если в Платформа есть несколько комнат/топиков, каждая должна иметь свой `surface_ref`
|
||
|
||
### 6.2. Команды управления чатами
|
||
|
||
Matrix поддерживает следующие команды, которые нужно сохранить в Платформа:
|
||
|
||
- `!new [название]` — создать новый чат
|
||
- `!chats` — список активных чатов
|
||
- `!rename <название>` — переименовать текущий чат
|
||
- `!archive` — архивировать чат
|
||
- `!clear` / `!reset` — сбросить контекст текущего чата
|
||
- `!yes` / `!no` — подтвердить или отменить действие агента
|
||
- `!list` — показать очередь вложений
|
||
- `!remove <n>` / `!remove all` — удалить вложение из очереди
|
||
- `!help` — справка
|
||
|
||
Эти команды реализованы в Matrix через `adapter/matrix/handlers/`.
|
||
|
||
### 6.3. Очередь вложений
|
||
|
||
Matrix surface поддерживает staged attachments:
|
||
|
||
- файл может быть отправлен без текста
|
||
- surface сохраняет файл в `staged_attachments` для конкретного room_id + user_id
|
||
- следующий текст отправляется агенту вместе со всеми файлами из очереди
|
||
|
||
В Платформа можно реализовать ту же модель:
|
||
|
||
- `!list` показывает текущую очередь
|
||
- `!remove` удаляет файл из очереди
|
||
- команда-индикатор или следующее текстовое сообщение отправляет queued attachments агенту
|
||
|
||
---
|
||
|
||
## 7. Runtime и окружение
|
||
|
||
### 7.1. Переменные среды
|
||
|
||
Для Matrix surface текущий runtime ожидает:
|
||
|
||
- `MATRIX_HOMESERVER` — URL Matrix-сервера
|
||
- `MATRIX_USER_ID` — `@bot:example.org`
|
||
- `MATRIX_PASSWORD` или `MATRIX_ACCESS_TOKEN`
|
||
- `MATRIX_PLATFORM_BACKEND` — должно быть `real` для продакшна
|
||
- `MATRIX_AGENT_REGISTRY_PATH` — путь к `config/matrix-agents.yaml`
|
||
- `AGENT_BASE_URL` — fallback URL агента
|
||
- `SURFACES_WORKSPACE_DIR` — путь к shared volume внутри контейнера (по умолчанию `/workspace` в коде, но в docs рекомендуют `/agents`)
|
||
|
||
Для New surface используйте аналогичные переменные:
|
||
|
||
- `PLATFORM_PLATFORM_BACKEND=real`
|
||
- `PLATFORM_AGENT_REGISTRY_PATH=/app/config/<platform>-agents.yaml`
|
||
- `SURFACES_WORKSPACE_DIR=/agents`
|
||
- `AGENT_BASE_URL` — если хотите общий fallback
|
||
|
||
### 7.2. Environment contract
|
||
|
||
В коде `adapter/matrix/bot.py`:
|
||
|
||
- `_agent_base_url_from_env()` читает `AGENT_BASE_URL` или `AGENT_WS_URL`
|
||
- `_load_agent_registry_from_env()` читает `MATRIX_AGENT_REGISTRY_PATH`
|
||
- `_build_platform_from_env()` выбирает `RealPlatformClient` при `MATRIX_PLATFORM_BACKEND=real`
|
||
|
||
В New surface реализуйте ту же логику, заменив префиксы на `PLATFORM_`.
|
||
|
||
---
|
||
|
||
## 8. Локальное тестирование
|
||
|
||
Для тестирования новой поверхности вместе с одним локальным агентом используйте паттерн `docker-compose.fullstack.yml`.
|
||
В этом режиме:
|
||
- Запускается 1 контейнер вашей поверхности
|
||
- Запускается 1 контейнер `platform-agent`
|
||
- Поднимается локальный shared volume (`surfaces-agents`)
|
||
- Поверхность настроена маршрутизировать запросы на `http://platform-agent:8000` (через `AGENT_BASE_URL`)
|
||
- Пользователь общается с ботом, а бот напрямую общается с локальным агентом, разделяя с ним общую папку для файлов.
|
||
|
||
Это самый быстрый способ проверить интеграцию новой платформы без внешнего бэкенда.
|
||
|
||
---
|
||
|
||
## 9. Реализация шаг за шагом
|
||
|
||
1. Скопировать `adapter/matrix/` как шаблон для `adapter/<platform>/`.
|
||
2. Сделать `adapter/<platform>/converter.py`:
|
||
- превратить native нативные сообщения в `IncomingMessage`
|
||
- превратить команды в `IncomingCommand`
|
||
- превратить yes/no-подтверждения в `IncomingCallback`
|
||
3. Сделать `adapter/<platform>/agent_registry.py` на основе `adapter/matrix/agent_registry.py`.
|
||
4. Сделать `adapter/<platform>/files.py` на основе `adapter/matrix/files.py`.
|
||
5. Сделать `adapter/<platform>/bot.py`:
|
||
- инстанцировать runtime
|
||
- читать env vars `PLATFORM_*`
|
||
- загружать реестр агентов
|
||
- обрабатывать входящие события
|
||
- отправлять `Outgoing*` обратно в Платформа
|
||
6. Реализовать команды управления чатами и очередь вложений.
|
||
7. Прописать `config/<platform>-agents.yaml`.
|
||
8. Прописать `docker-compose.platform.yml` или аналог, чтобы surface монтировал `/agents`.
|
||
9. Написать тесты по аналогии с `tests/adapter/matrix/`.
|
||
10. Проверить, что все env vars читаются из окружения и не зависят от устаревших Matrix-переменных.
|
||
|
||
---
|
||
|
||
## 10. Важные замечания
|
||
|
||
- Текущий Matrix surface на ветке `main` — активная реализация, а не устаревший легаси.
|
||
- Документация и код согласованы: `agent_registry`, `files`, `routed_platform`, `reconciliation` работают вместе.
|
||
- Обязательно явно задавайте `SURFACES_WORKSPACE_DIR=/agents` в production, если `workspace_path` в реестре указывает на `/agents/*`.
|
||
- Для New surface сохраните ту же архитектуру: surface = thin adapter, агенты = внешние сервисы.
|
||
- Не пытайтесь в surface реализовывать логику запуска/стопа агент-контейнеров.
|
||
|
||
---
|
||
|
||
## 11. Полезные ссылки внутри репозитория
|
||
|
||
- `README.md`
|
||
- `docs/deploy-architecture.md`
|
||
- `docs/surface-protocol.md`
|
||
- `adapter/matrix/bot.py`
|
||
- `adapter/matrix/converter.py`
|
||
- `adapter/matrix/agent_registry.py`
|
||
- `adapter/matrix/files.py`
|
||
- `adapter/matrix/routed_platform.py`
|
||
- `adapter/matrix/reconciliation.py`
|
||
- `tests/adapter/matrix/`
|