surfaces/docs/deploy-architecture.md

9.2 KiB
Raw Blame History

Deployment Architecture — Matrix Bot + Agents

Сформировано 2026-04-27 по итогам обсуждения с платформой.


Compose Artifacts

  • Production deploy: docker-compose.prod.yml Bot-only handoff через published image (SURFACES_BOT_IMAGE). Поднимает только matrix-bot, монтирует shared volume в /agents. Платформа предоставляет 25-30 agent containers/services отдельно; бот подключается к ним через base_url из matrix-agents.yaml.
  • Internal full-stack E2E: docker-compose.fullstack.yml Внутренний harness для тестирования. Локально собирает matrix-bot через Dockerfile target development, поднимает один platform-agent, health-gated startup.

Production operators: docker-compose.prod.yml. Internal E2E: docker-compose.fullstack.yml.


Топология

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-бота обслуживает всех пользователей.
  • Один агент-контейнер на пользователя. Production scale target: 25-30 внешних агентов. Изоляция по agent_id, не через один общий agent instance.
  • Shared volume /agents/ смонтирован в Matrix-бот. В internal full-stack harness тот же volume mounted as /workspace inside platform-agent, чтобы bot-side absolute paths и agent workspace относились к одному и тому же хранилищу.

Конфиг (два словаря)

# 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: "http://lambda.coredump.ru:7000/agent_0/"
    workspace_path: "/agents/0"

  - id: agent-1
    label: "Agent 1"
    base_url: "http://lambda.coredump.ru:7000/agent_1/"
    workspace_path: "/agents/1"

  - id: agent-2
    label: "Agent 2"
    base_url: "http://lambda.coredump.ru:7000/agent_2/"
    workspace_path: "/agents/2"
  • user_agents — маппинг Matrix user_id → agent_id. Если пользователь не найден — используется первый агент из списка.
  • agents[].base_url — HTTP URL агент-эндпоинта. Бот подключается через AgentApi.
  • agents[].workspace_path — абсолютный путь к воркспейсу агента внутри контейнера бота (т.е. на shared volume). Бот сохраняет входящие файлы в {workspace_path}/incoming/, читает исходящие из {workspace_path}/.
  • Для 25-30 агентов продолжайте тот же паттерн до нужного номера: /agent_17/ + /agents/17, /agent_29/ + /agents/29.

Surface Image Build Contract

Production image содержит только Matrix surface. Он не содержит platform-agent и не требует локального external/ build context.

docker login
export SURFACES_BOT_IMAGE=mput1/surfaces-bot:latest

docker build --target production \
  --build-arg LAMBDA_AGENT_API_REF=master \
  -t "$SURFACES_BOT_IMAGE" .
docker push "$SURFACES_BOT_IMAGE"

Published image:

mput1/surfaces-bot:latest
sha256:26ba3a49290ab7c1cf0fa97f3de3fefdc70b59df7e6f1e0c2255728f8e2369be

SURFACES_BOT_IMAGE должен указывать на registry namespace, куда текущий Docker account может пушить. Ошибка insufficient_scope означает, что пользователь не залогинен в этот namespace, repository не создан, или у аккаунта нет push-доступа.

Production Dockerfile ставит platform/agent_api из Git по тому же принципу, что и platform-agent production image:

git+https://git.lambda.coredump.ru/platform/agent_api.git

Локальный docker-compose.fullstack.yml остаётся dev/E2E harness: он использует target development и additional_contexts.agent_api=./external/platform-agent_api, чтобы можно было тестировать surface вместе с локальным checkout SDK.


Agent API (используем master ветку platform/agent_api)

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):

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. Уточнить у платформы сроки мержа перед деплоем.
  • История при рестарте агента теряется — platform-agent использует MemorySaver (in-memory). Ограничение платформы.
  • AGENT_ID и COMPOSIO_API_KEY для каждого агент-контейнера — значения предоставляет платформа.