# Lambda Lab 3.0 — Surfaces

Matrix-бот для взаимодействия пользователя с AI-агентом Lambda.

## Интеграция для платформы

Бот — это один Docker-контейнер (`matrix-bot`), который вы добавляете в свою инфраструктуру рядом с агентами. Production target — один surface container на 25-30 внешних agent containers/services.

### Что бот ожидает от вас

**1. HTTP-эндпоинт агента**
Бот подключается к агенту через `lambda_agent_api.AgentApi` по адресу `AGENT_BASE_URL`.
Протокол — WebSocket поверх HTTP, контракт описан в `docs/deploy-architecture.md`.

**2. Shared volume с per-agent поддиректориями**
Shared volume монтируется в бот как `/agents`. Каждый агент видит свою поддиректорию.

```
Bot container                  Agent containers
  /agents/0/  ←── volume ──→  agent_0: /workspace/
  /agents/1/  ←── volume ──→  agent_1: /workspace/
  /agents/N/  ←── volume ──→  agent_N: /workspace/
```

- Бот сохраняет входящий файл прямо в `{workspace_path}/{file}` и передаёт агенту `attachments=["{file}"]`
- Если файл с таким именем уже есть, бот сохраняет следующий как `file (1).ext`, `file (2).ext`, как в Windows
- Агент пишет исходящий файл прямо в свой `/workspace/file`, бот читает его из `{workspace_path}/file`
- `workspace_path` для каждого агента задаётся в `config/matrix-agents.yaml`

**3. Конфиг агентов**
Файл `config/matrix-agents.yaml` — маппинг Matrix-пользователей на агентов. Вы заполняете его под свою инфраструктуру. Пример в `config/matrix-agents.example.yaml`.

### Что бот не делает

- Не управляет lifecycle агент-контейнеров (запуск/остановка — на вашей стороне)
- Не хранит историю разговоров (это в памяти агента)
- Не обрабатывает аутентификацию пользователей — любой Matrix-пользователь, который пишет боту, получает доступ

### Минимальный чеклист

- [ ] Взять опубликованный image `SURFACES_BOT_IMAGE` или собрать production image из этого репозитория
- [ ] Заполнить `config/matrix-agents.yaml` — ID агентов, `base_url` каждого, `workspace_path`, маппинг пользователей
- [ ] Задать переменные окружения (см. `.env.example`): Matrix credentials, `MATRIX_PLATFORM_BACKEND=real`, `MATRIX_AGENT_REGISTRY_PATH=/app/config/matrix-agents.yaml`
- [ ] Смонтировать в бот-контейнер shared volume как `/agents` — каждый агент должен видеть свою поддиректорию как `/workspace`
- [ ] Добавить bot-only service в свой compose; агенты в этот compose не входят и управляются платформой

---

## Статус

Matrix MVP готов к деплою. Telegram — в отдельном worktree, не входит в этот handoff.

---

## Архитектура

```
surfaces-bot/
  core/                  — общее ядро, не зависит от транспорта
    protocol.py          — унифицированные структуры (IncomingMessage, OutgoingUI, ...)
    handler.py           — EventDispatcher: IncomingEvent → OutgoingEvent
    store.py             — StateStore Protocol + InMemoryStore + SQLiteStore
    chat.py              — ChatManager
    auth.py              — AuthManager
    settings.py          — SettingsManager

  adapter/
    matrix/              — matrix-nio адаптер

  sdk/
    interface.py         — PlatformClient Protocol (контракт к SDK)
    real.py              — RealPlatformClient (через AgentApi)
    mock.py              — MockPlatformClient (заглушка для тестов)

  config/
    matrix-agents.yaml   — реестр агентов

  docs/                  — документация
```

Подробнее: [`docs/surface-protocol.md`](docs/surface-protocol.md)

---

## Деплой

### Переменные окружения

```bash
cp .env.example .env
```

| Переменная | Обязательна | Описание |
|---|---|---|
| `MATRIX_HOMESERVER` | ✓ | URL Matrix-сервера |
| `MATRIX_USER_ID` | ✓ | `@bot:example.org` |
| `MATRIX_PASSWORD` | ✓ | пароль (или `MATRIX_ACCESS_TOKEN`) |
| `MATRIX_PLATFORM_BACKEND` | ✓ | `real` для продакшна |
| `SURFACES_BOT_IMAGE` | ✓ | Docker image поверхности: `mput1/surfaces-bot:latest` |
| `AGENT_BASE_URL` | | Fallback URL агента если `base_url` не задан в `matrix-agents.yaml` |
| `MATRIX_AGENT_REGISTRY_PATH` | ✓ | путь к реестру внутри контейнера: `/app/config/matrix-agents.yaml` |
| `SURFACES_WORKSPACE_DIR` | | путь к shared volume в контейнере (по умолчанию `/agents`) |
| `SURFACES_SHARED_VOLUME` | | имя Docker volume (по умолчанию `surfaces-agents`) |

### Реестр агентов

`config/matrix-agents.yaml` — статический маппинг пользователей на агентов:

```yaml
user_agents:
  "@user0:matrix.lambda.coredump.ru": agent-0
  "@user1:matrix.lambda.coredump.ru": agent-1

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. Если пользователь не найден — используется первый агент.
- `base_url` — HTTP URL агент-эндпоинта (path-based routing через reverse proxy).
- `workspace_path` — путь к воркспейсу агента внутри бот-контейнера на shared volume.
  Бот сохраняет входящие файлы прямо в `{workspace_path}/`, агент пишет исходящие прямо в свой `/workspace/`.
- Для 25-30 агентов продолжайте тот же паттерн: `/agent_17/` + `/agents/17`, `/agent_29/` + `/agents/29`.

Полный пример с комментариями: `config/matrix-agents.example.yaml`

### Production (bot-only)

`docker-compose.prod.yml` — bot-only handoff через published image. Платформа добавляет этот сервис в свой compose рядом с agent containers, монтирует shared volume и задаёт переменные окружения. Этот compose не создаёт и не собирает агент-контейнеры.

Перед redeploy можно проверить реальные agent routes из той же сети, где будет работать бот:
```bash
PYTHONPATH=. uv run python -m tools.check_matrix_agents \
  --config config/matrix-agents.yaml \
  --timeout 5
```

Проверка открывает фактический WebSocket URL каждого агента (`.../v1/agent_ws/{chat_id}/`) и ждёт первый `STATUS`. Для проверки полного запроса к агенту добавьте `--message "ping"`.

Для запуска опубликованного image:
```bash
export SURFACES_BOT_IMAGE=mput1/surfaces-bot:latest
docker compose --env-file .env -f docker-compose.prod.yml up -d
```

Опубликованный image:

```text
mput1/surfaces-bot:latest
sha256:2f135f3535f7765d4377b440cdabe41195ad2efbc3e175def159ae4689ef90bd
```

Для сборки и публикации surface image:
```bash
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"
```

Если push возвращает `insufficient_scope`, текущий Docker login не имеет доступа к namespace/repository. Создайте repository в нужном Docker Hub namespace или перетегируйте image в namespace пользователя, под которым выполнен `docker login`.

### Fullstack E2E (bot + agent)

```bash
docker compose --env-file .env -f docker-compose.fullstack.yml up --build
```

Поднимает `matrix-bot` вместе с локальным `platform-agent`. Это internal harness, не production topology на 25-30 агентов. `matrix-bot` собирается через Dockerfile target `development` и локальный `external/platform-agent_api` context, как в dev-сборке `platform-agent`. `AGENT_BASE_URL` перекрывается на `http://platform-agent:8000`. Shared volume виден как `/agents` в боте и `/workspace` в агенте.

### Сброс состояния (локально)

```bash
rm -f lambda_matrix.db && rm -rf matrix_store
```

---

## Shared volume: передача файлов

```
Bot (/agents)              Agent (/workspace = /agents/N/)
  /agents/0/report.pdf  ←──── одно и то же хранилище ────→  /workspace/report.pdf
  /agents/0/result.txt  ←────────────────────────────────→  /workspace/result.txt
```

- **Входящий файл** (пользователь → агент): бот сохраняет в `{workspace_path}/{file}`, например `/agents/17/report.pdf`, и передаёт агенту `attachments=["report.pdf"]`
- **Коллизии имён**: если `/agents/17/report.pdf` уже существует, бот сохранит следующий файл как `/agents/17/report (1).pdf`, затем `/agents/17/report (2).pdf`
- **Исходящий файл** (агент → пользователь): агент пишет в `/workspace/file`, бот читает из `{workspace_path}/file`, например `/agents/17/result.txt`, и отправляет пользователю как Matrix file message
- `workspace_path` для каждого агента задаётся в `config/matrix-agents.yaml`

---

## Онбординг пользователя

1. Пользователь приглашает бота в личные сообщения (DM) на Matrix-сервере
2. Бот создаёт private Space `Lambda — {display_name}` и комнату `Чат 1`
3. Дальнейшее общение — в рабочих комнатах, не в DM

**Требование:** незашифрованные комнаты. E2EE не поддержан.

---

## Команды Matrix

### Работающие

| Команда | Действие |
|---|---|
| *(любое сообщение)* | Диалог с агентом, стриминг ответа |
| `!new [название]` | Создать новый чат |
| `!chats` | Список активных чатов |
| `!rename <название>` | Переименовать текущую комнату |
| `!archive` | Архивировать чат |
| `!clear` | Сбросить контекст текущего чата |
| `!yes` / `!no` | Подтвердить / отменить действие агента |
| `!list` | Файлы в очереди вложений |
| `!remove <n>` / `!remove all` | Удалить вложение из очереди |
| `!help` | Справка |

### Не работают / заглушки

| Команда | Статус |
|---|---|
| `!save` / `!load` / `!context` | Нестабильны: зависят от агента, сессии теряются при рестарте |
| `!settings` и подкоманды | Заглушки MVP, требуют готового SDK платформы |

---

## Отправка файлов агенту

Matrix-клиент отправляет файлы и текст отдельными событиями. Файл без текстовой инструкции ставится в очередь.

```
[отправил файл]
!list
  1. report.pdf

прочитай и сделай summary   ← файл уйдёт агенту вместе с этим текстом
```

---

## Известные ограничения

| Проблема | Причина |
|---|---|
| История теряется при рестарте агента | `platform-agent` использует `MemorySaver` (in-memory) |
| E2EE | `python-olm` не собирается на macOS/ARM |

---

## Разработка

```bash
uv sync
pytest tests/ -v
pytest tests/adapter/matrix/ -v   # только Matrix
```

## Документация

| Файл | Содержание |
|---|---|
| [`docs/deploy-architecture.md`](docs/deploy-architecture.md) | **Читать первым.** Топология деплоя, volume-контракт, AgentApi, конфигурация |
| [`docs/matrix-prototype.md`](docs/matrix-prototype.md) | Команды бота, UX, передача файлов |
| [`docs/known-limitations.md`](docs/known-limitations.md) | Известные ограничения и обходные пути |
| [`docs/surface-protocol.md`](docs/surface-protocol.md) | Внутренний протокол событий (для расширения) |
| [`docs/new-surface-guide.md`](docs/new-surface-guide.md) | Руководство по созданию новой поверхности (Discord, Slack и др.) |
