# Подключения и перенос ZeroExternal

Проверено по исходному коду 27 сентября 2026. Это контракт реализации и план подключения, а не свидетельство работающей внешней Kaska, n8n или лаборатории.

## Три среды, три разных статуса

| Среда | Что работает в коде | Как подтвердить подключение |
|---|---|---|
| Browser Runtime | Локальные фиксированные Web Workers; результаты и граф в IndexedDB этого браузера | Реальные события запуска, завершённый результат, проверка и экспорт пакета |
| Общий кабинет | D1-проекты, люди, ограниченные внешние Python-агенты | Последний heartbeat/опрос за 90 секунд; выданный ключ сам по себе не означает подключение |
| Kaska / локальный Docker runtime | Адаптер старого проверенного контракта; Markov scientist + verifier | Успешный доступ к board, подтверждённый Kaska run ID, контейнер, evidence, cleanup и exit ACK |

Квантовый эксперимент текущего браузерного runtime нельзя автоматически отправить через старую Kaska-интеграцию: `ResearchRuntime.prepare` разрешает только `markov_entropy`. Общий кабинет пока выдаёт агентам только `observer_projection`. Эти ограничения должны быть видны при выборе внешнего исполнителя.

## Простой путь без Kaska

Существующий клиент `external-agents/observer/zeroexternal-observer-agent.py` уже работает через исходящие HTTPS-запросы. Ему не нужны входящий порт, туннель или Docker. В кабинете владелец выдаёт отдельный ограниченный ключ каждому внешнему агенту и назначает конкретную задачу. Ключ вводится локально в переменную окружения; не в URL, форму задачи или репозиторий.

Контракт имеет префикс `/api/research`, bearer-авторизацию и три маршрута:

| Маршрут | Назначение |
|---|---|
| POST `/agent/heartbeat` | Присутствие; с `taskId,leaseToken` продлевает действующую аренду |
| POST `/agent/next` | Получить только назначенную этому агенту задачу и короткую аренду |
| POST `/agent/tasks/{id}/complete` | Результат, происхождение и lease; одинаковая повторная доставка идемпотентна |

Фиксированный клиент не вызывает LLM, не выполняет текст задачи как код, не загружает URL из задачи. Ключ связан с проектом и рецептом. Техническая документация: `/account/API.md`.

## Kaska: реально реализованный контракт

Адаптер закреплён на `b8b08294253348440d24a19b08b6be6410c9c4a6`. Совместимость с последней версией пока не подтверждена. Публичный `github.com/e1berd/kaska` не удалось получить; найденная поиском старая страница `e1berd/hardhat` описывает Phoenix-доску и не подтверждает актуальную модель агента или n8n.

| Направление | Маршрут / событие | Авторизация |
|---|---|---|
| 0EX → Kaska | `/socket/websocket?vsn=2.0.0`; `auth:lobby` → `login` / `refresh` | Пользователь Kaska; короткие access/refresh хранятся на вычислительном узле |
| 0EX → Kaska | `board_slug:{slug}`; `create_task`, `create_task_comment`, `update_task`, `start_agent_run` | Authenticated Phoenix Channel; проверяется `can_write` и ответ run identity |
| Kaska → 0EX supervisor | POST `/runs` | Выделенный общий supervisor bearer; не браузерный маршрут |
| Kaska → 0EX supervisor | POST `/runs/{run_id}/stop` | Тот же выделенный supervisor bearer |
| 0EX → Kaska | GET `/api/v1/p/{slug}/tasks/{task_id}` | PAT конкретного активного run |
| 0EX → Kaska | POST `/api/v1/p/{slug}/tasks/{task_id}/comments` | PAT конкретного активного run |
| 0EX → Kaska | POST `/internal/agent_runs/{run_id}/exit` | Выделенный supervisor bearer |

Supervisor принимает только точный envelope `run_id,image,env,limits,callback_url`. Проверяет run/task/project/API/image и заранее зафиксированный эксперимент. В worker не передаются LLM-ключи и произвольные команды; capabilities выдаются только после проверки привязки.

На машине оператора: `researchctl.py configure`, `kaska-login`, `prepare`, `dispatch`, `status`, `stop`. Для подтверждения полного прохода есть `researchctl.py smoke --task-id ... --report ...`; критерий включает реальный Docker, evidence, подтверждённую очистку и Kaska exit ACK. Поле `kaska_connected` в старом `/api/state` означает наличие сохранённой конфигурации, не свежую сетевую проверку. Не использовать его как единственное доказательство online.

## n8n: использовать существующий workflow

В проекте есть три неактивных примера `mac-runtime/integrations/n8n/`. Они не являются подключением к экземпляру Эльберта. После `automation-grant` один короткий bearer разрешает один подготовленный эксперимент:

| Маршрут | Тело |
|---|---|
| GET `/api/automation/context` | — |
| POST `/api/automation/dispatch` | `{experiment_id,idempotency_key}` |
| POST `/api/automation/status` | `{job_id}` |
| POST `/api/automation/stop` | `{job_id}` |

Хранить bearer в n8n Header Auth credential. Стабильный idempotency key относится к эксперименту, не к новой попытке workflow. Повторный неизвестный исход нельзя лечить новым dispatch: требуется сверка. Не создавать встречный цикл «Kaska task → 0EX task → Kaska task». Научное принятие claim не входит в разрешение автоматизации.

## Перенос на свой домен

Самый короткий путь — подключить домен к тому же Site. Так можно сохранить серверный проект и D1. Факт текущей готовности DNS/TLS должен проверяться в управлении хостингом; старый файл `mac-runtime/DOMAIN.md` содержит выданные ранее записи, но не доказывает, что они сейчас активны.

Перед переключением необходимы следующие проверки:

1. **Вход в кабинет.** Человеческие маршруты доверяют `oai-authenticated-user-id` и `oai-authenticated-user-email`, которые обязан перезаписывать доверенный Sites ingress. Проверить `/api/research/me` после входа именно на новом hostname. Самостоятельный перенос на Worker без этой границы требует полноценного проверяемого OIDC/session-адаптера и удаления клиентских identity headers.
2. **Серверные данные.** Сохранить D1 binding `DB`, таблицы и обе deployment migrations. Экспорт кабинета содержит только сохранённые записи и максимум 500 событий; это не полный бэкап базы и не готовый формат восстановления. До переезда базы необходимы backup + проверка восстановления.
3. **Браузерные данные.** Новый origin получает пустые IndexedDB/localStorage. До перехода экспортировать подписанные задачи, замороженные протоколы и граф. Старый browser signing identity автоматически не переносится. Импорт — проверка старого evidence, а не повторное вычисление и не новое подписание от имени прежнего автора.
4. **Маршруты.** Сохранить `/runtime/`, `/quantum/`, `/observer/`, `/account/`, `/protocol/` и `/api/research/`. Проверить загрузку ES-модулей/Workers и pinned source hashes с нового origin; не сводить сайт к одному скачиваемому HTML.
5. **Origin и агенты.** Человеческие POST требуют exact same-origin. Внешние Python-агенты меняют `--server` на точный HTTPS origin; не добавлять wildcard CORS. Browser Runtime работает на HTTPS или доверенном loopback.
6. **Локальный вычислитель.** Для браузерного обращения к отдельному gateway добавить точный UI origin в allowlist и выдать новый pairing code. Для Kaska callback нужен отдельно настроенный защищённый исходящий tunnel с loopback upstream; admin 8767 никогда не публикуется. DNS публичного UI не создаёт этот tunnel.
7. **Один перенос за раз.** Сначала домен существующего Site и проверка входа/задачи/worker/export. Смена identity provider, базы и вычислительного узла — отдельный этап с проверкой отката.

## Что показывать пользователю

Одна вкладка «Подключения»: Browser, внешний агент, Kaska/n8n, локальная модель, домен. Для каждого — явный статус и следующий конкретный шаг. Протокол, расчёты и сеть — детали внутри этой страницы. Профиль внешнего агента без heartbeat должен показываться как «доступ выдан, подключения ещё нет».

Слово «Jeff» из запроса не даёт проверяемого названия продукта или API. Оставить универсальный выбор типа подключения; не создавать выдуманный Jeff-протокол. Для отдельного адаптера нужны точное имя/URL и его документированный контракт.
