# Первый вертикальный проход · ZeroExternal

## Статус и область реализации

Реализована связка с **agent runtime**, а не только обмен комментариями доски: Kaska `start_agent_run` → собственный supervisor 0EX `/runs` → агентская Docker-ячейка → типизированные capability RPC → фиксированный Markov solver → отдельный verifier → атомарный HyperGraph commit → отзыв → подтверждённое уничтожение → callback Kaska.

Контракт прочитан из Kaska commit `b8b08294253348440d24a19b08b6be6410c9c4a6`. На 25 сентября 2026 публичный `github.com/e1berd/kaska` и API репозитория возвращают 404. **Совместимость с новым недоступным коммитом не подтверждена.** В этом исходнике есть `pending/running/succeeded/failed/stopped/timed_out`, но нет отдельного постоянного объекта sleeping agent. `AGENT_SLEEP` в 0EX означает отсутствие его вычислительных ячеек после подтверждённой очистки.

В среде разработки проверены реальный HTTP-обмен и настоящий двусторонний stdin/stdout с `scientist.py`; Docker и живой Kaska заменены явно обозначенными тестовыми службами. Docker daemon здесь отсутствует, namespace isolation недоступна. Это **не отчёт об успешном live Docker/Kaska E2E**. Команда `researchctl.py smoke` требует настоящий Docker и создаёт такой отчёт на вашем узле без тестового fallback.

## Что делает первый агент

Первый профиль — фиксированный специализированный Markov scientist, provider `fixed_scientific_agents`, без LLM. Он читает закреплённую гипотезу, выполняет разрешённую запись ParameterSet, запускает рецепт, читает артефакт и предлагает claim. По умолчанию менять разрешено только `steps` в вырожденном интервале текущего значения: это проверяет механизм полномочий без скрытого изменения постановки. Оператор может расширить точные интервалы при `prepare`.

Второй профиль — фиксированный verifier. Он строит матрицу B из закреплённых параметров независимо от результата worker: разложение квадрата модуля в косинусы вместо комплексной суммы solver. Затем с `math.fsum` пересчитывает траекторию, конечные вероятности и энтропию. Host проверяет результат своим независимым построением ядра. Допуски: матрица 1e-12, энтропия/вероятности 1e-10. Verifier не имеет `simulation.run` или `parameters.write`.

При подготовке оператор явно выбирает `prediction_id: markov.shannon_monotonicity.v1`: минимум приращения энтропии в конечной траектории, единица bit, порог -1e-12. Для absorbing двустохастическая предпосылка отсутствует: уменьшение H — ожидаемый отрицательный контроль, а не опровержение условной теоремы. Текст гипотезы автоматически не формализуется. Это численная проверка ограниченной модели, не экспериментальное подтверждение физической теории. Автономная LLM в этот профиль не включена; model broker остаётся отдельным.

## Подготовка узла

1. Нужны Python 3.11+, Docker и доступ к своей Kaska. Запустите `bash Start.command` (Mac) или `bash Start-server.sh` (Linux). Собирается `zeroexternal-science:1.6`; далее оба типа ячеек используют неизменяемый image ID. Gateway слушает только `127.0.0.1:8765`, supervisor — `127.0.0.1:8766`, локальное управление — `127.0.0.1:8767`. Главный ключ разрешён только на 8767. Первый build фиксирует digest базового образа; обновление выполняется явно с `--refresh-base`.
2. Сделайте рабочую копию `research-config.example.json`. Впишите фактические API/internal URL Kaska, project slug и UUID существующего агента проекта. Не помещайте секрет в этот JSON. URL должны быть достижимы с узла 0EX и разрешены точным hostname в `runtime.env`; удалённый прямой доступ — только HTTPS. Внутренний `/internal` Kaska не опубликован её стандартным Caddy.
3. Настройте **выделенную научную инсталляцию Kaska** для собственного supervisor. В изученном commit настройки глобальные, не per-agent. Не применяйте их поверх инсталляции с чужими агентами без отдельного решения владельца.

```env
# Values must match your actual private/tunnel deployment.
AGENT_SUPERVISOR_URL=https://YOUR-SUPERVISOR-HOST
AGENT_SUPERVISOR_SECRET=<same-random-secret-entered-in-0EX>
AGENT_RUNTIME_IMAGE=zeroexternal-science:1.6
AGENT_RUNTIME_KASKA_API_URL=<0EX-reachable-base>/api/v1
AGENT_CALLBACK_BASE_URL=<0EX-reachable-base>/internal
```

Названия gateway/supervisor приведены как план размещения; эти сервисные домены и туннель автоматически не созданы. Если Kaska и supervisor работают нативно на одном узле, разрешён loopback URL. `localhost` контейнера Kaska не является loopback хоста. Не добавляйте Docker socket или домашние каталоги в агентскую ячейку, чтобы решить маршрутизацию.

```sh
.venv/bin/python researchctl.py configure research-config.json
.venv/bin/python researchctl.py kaska-login --url https://YOUR-KASKA-HOST --board physics --email YOUR-EMAIL
```

CLI запрашивает операторский токен runtime, shared supervisor secret и пароль скрытым вводом. Логин служит **локальной настройкой control plane**, а не инструментом агента и не формой публичного Journey. Сохранённая refresh-сессия Kaska остаётся в секретном файле runtime; пароль не сохраняется. Научные клетки не получают эти данные.

4. Выпустите одноразовый код для точного Origin интерфейса. Локальный пример:

```sh
.venv/bin/python researchctl.py pair --origin http://127.0.0.1:8765 --scopes read,research,review,export
```

Код действует 120 секунд, полученная сессия — 15 минут. В публичном UI укажите HTTPS gateway и код для **Origin сайта**, не Origin gateway. Главный ключ никогда не вставляйте в веб-форму. `read` видит состояние узла; `research` создаёт задачи и вычисления; `review` разрешает научные решения; `export` разрешает полный экспорт. Сессии не разделены по проектам: это среда одного оператора. Команда `revoke-session --session-id SESSION_ID` запрещает новые действия, но активные вычисления останавливаются отдельно.

5. Создайте Markov-гипотезу в существующей вкладке «Исследование». Затем «03 Kaska / Agent Runtime» может создать binding и отправить выбранную гипотезу. Для контролируемого терминального прохода:

```sh
.venv/bin/python researchctl.py smoke --task-id ACTUAL_LOCAL_TASK_ID --report research-e2e-report.json
```

Команда создаёт/связывает Kaska task, закрепляет immutable experiment, добавляет указанного агента к assignees, записывает паспорт эксперимента и вызывает нативное `start_agent_run`. Она ждёт наблюдаемых состояний и требует `environment.kind=docker`, commit, подтверждённое удаление контейнеров и terminal acknowledgement Kaska. JSON-отчёт создаётся с правами 600 и не перезаписывается. Не публикуйте его без проверки содержимого гипотезы/артефактов.

Ручное управление:

```sh
.venv/bin/python researchctl.py prepare --task-id ACTUAL_LOCAL_TASK_ID --bounds bounds.json
.venv/bin/python researchctl.py dispatch --experiment-id ACTUAL_EXPERIMENT_ID
.venv/bin/python researchctl.py status
.venv/bin/python researchctl.py stop --run-id ACTUAL_KASKA_RUN_UUID
.venv/bin/python researchctl.py abandon --experiment-id UNUSED_EXPERIMENT_ID
```

`bounds.json`, пример: `{"steps":[10,100],"mix":[0.1,0.5],"kernel":["quantum","permutation"]}`. Начальные параметры должны входить в разрешение. `parameters.write` меняет локальный versioned ParameterSet, не гипотезу человека. Изменение редакции гипотезы отзывает дальнейшие действия и commit. Изменение конфигурации supervisor делает старый binding непригодным: отзовите неиспользованный binding и создайте новый.

## Контракт ячейки

По stdin передаётся только `{"schema":"zeroexternal.cell.v1","role":"scientist"}` либо `verifier`. По stdout агент посылает JSON Lines:

```json
{"v":1,"seq":1,"method":"experiment.read","params":{}}
```

Ответ по stdin: `{"seq":1,"ok":true,"result":{...}}`. Никакого HTTP-сервера внутри ячейки. Право связано с pipe процесса и неизменяемым экспериментом; bearer-токена внутри нет.

| Capability | Граница |
|---|---|
| `experiment.read {}` | Закреплённый snapshot гипотезы и собственные параметры/артефакты |
| `parameters.write {expected_revision,patch}` | До 2 записей; только ключи и интервалы grant |
| `simulation.run {parameter_revision}` | 1 фиксированный Markov recipe; image/seed/ID выбирает host |
| `artifact.read {artifact_id}` | Только собственный выданный артефакт; пути и URL не принимаются |
| `claim.propose {text,evidence_ids}` | 1 непроверенный claim; не человеческая рецензия и не общая истина |
| `cell.done {}` | Только после обязательного claim/verification report |

Verifier получает только read/propose/done. Лимиты: 24 RPC на клетку, 16 KiB на строку запроса, 1 MiB ответов, 16 KiB stderr, до 180 секунд общего окна сессии, либо меньший лимит Kaska. Deadline отсчитывается с приёма запуска, включая создание ячейки и handshake. JSON с повторными ключами/NaN, перепутанный seq, параллельные RPC, чужой artifact и непрочитанный вывод приводят к отказу. Бюджет резервируется в журнале до побочного действия. Повторы после неопределённого исхода запрещены, автоматического replay нет.

Agent cells: network none, read-only, UID 65532, caps dropped, no-new-privileges, PID 32, 128 MB, 0.5 CPU, tmpfs 8 MB; без mounts и Docker socket. Общий потолок: 768 MB, 2 CPU, 128 PID. Из него каждой из двух ячеек отводится не более четверти, solver — не более половины; индивидуальные потолки 128 MB/0.5 CPU/32 PID и 512 MB/1 CPU/64 PID. Меньшие лимиты Kaska уменьшают все доли. Сумма одновременно разрешённых ресурсов не превышает бюджет. Ни task body, ни SYSTEM_PROMPT, ни присланный image/env не превращаются в команды. `KASKA_PAT` используется брокером в памяти, `LLM_*` из dispatch игнорируются и не сохраняются.

## Жизненный цикл и сохранение знаний

`TASK_CREATED → KASKA_DISPATCH → AGENT_WAKE → CONTAINER_CREATED → CAPABILITY → EXPERIMENT → OBSERVATION → VERIFIER_WAKE → VERIFICATION → HYPERGRAPH_COMMIT → REVOKE → DESTROY_CELL → RESULT_REPORTED → KASKA_EXIT_ACK → AGENT_SLEEP`.

Kaska сначала получает настоящий ID созданного, ещё не наделённого полномочиями контейнера. Локальный оператор заранее инициирует dispatch; возвращённый run обязан совпасть по agent/task/project/requester. Неизменяемый DispatchAuthorization связывает конкретный run и эксперимент. Ячейка не получает capability, пока этот ответ и active-run handshake не проверены. Только после ответа `/runs` она связывает PAT с active run; 0EX ограниченно ждёт этого handshake. Пока он не завершён, научный stdin не выдаётся. Host мониторит доступность lease через task REST; потеря доступа отзывает локальное исполнение. Короткое окно между удалённым отзывом и следующей проверкой ограничено мониторингом; уже записанные артефакты сохраняются как история.

Постоянны `Hypothesis`, `ParameterSet`, `SimulationArtifact`, `Claim`, `Evidence`, `Verification`, `CanonicalState`, Hyperedges и append-only lifecycle events. CanonicalState — паспорт **одного** вычислительного эксперимента со статусом `verified_computation_unreviewed_claim`, не глобальная научная истина. Автоматический commit не выдаёт себя за решение человека. Неудачные и прерванные попытки остаются историей, не становятся canonical evidence.

При timeout/stop/EOF сначала отзываются полномочия, затем останавливаются worker и agent cells. Удаление подтверждается Docker inspect; если проверить отсутствие не удалось, состояние `quarantined`, а не `sleeping`. После перезапуска исполнение не возобновляется автоматически; orphan-контейнеры этой instance очищаются до новых запусков, история отмечается interrupted. Если ответ Kaska `/exit` не подтверждён, это видно отдельно; нельзя считать цепочку завершённой только по локальному успешному расчёту.

## Публичный UI и исходящее соединение

Используйте собственный hostname UI и подтвердите DNS/TLS у выбранного хостинга. Mac, GPU и HyperGraph остаются на вашем узле. Для доступа из публичной страницы нужен фактически настроенный HTTPS gateway; публичная форма не подставляет localhost. Подробности — в [DOMAIN.md](DOMAIN.md) и [DEPLOYMENT.md](DEPLOYMENT.md).

Включён `cloudflared.example.yml` для native connector на том же узле. Cloudflared создаёт исходящее зашифрованное соединение, а origin остаётся loopback. Это не делает сервисные домены закрытыми: gateway принимает только короткую scoped сессию UI или ограниченный агентский токен, supervisor — отдельный shared secret на каждом запросе, независимо от source IP. Туннель не должен автоматически добавлять секреты посетителям. Порт admin 8767 **не добавляйте в ingress**. Браузерные сессии привязаны к Origin, однако Origin не удостоверяет устройство: защита токена и доверие к коду UI всё ещё необходимы.

Предпосылки: ваш Cloudflare account, домен в вашей зоне, реальный named tunnel и его credentials. Не меняйте nameservers существующего домена вслепую. Сохраните действующие записи своего UI; создавайте gateway/supervisor DNS только по фактическому tunnel ID. `httpHostHeader` в шаблоне сохраняет строгую Host-проверку origin. Внутренний HTTP leg идёт только по loopback того же узла; сеть до connector защищает Tunnel. Открывать входящие порты Mac не требуется. Туннель, GPU и Azure-ресурсы в этой поставке не активированы.

Официальные инструкции: [создать named tunnel](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/create-local-tunnel/), [ingress](https://developers.cloudflare.com/tunnel/features/locally-managed-tunnels/configuration-file/), [Host/TLS origin parameters](https://developers.cloudflare.com/tunnel/reference/origin-parameters/). Для дополнительной защиты supervisor можно ограничить policy Cloudflare Access, согласовав service credentials с Kaska-клиентом; приложение не подставляет их автоматически.


## Локальные подключения моделей, агентов и лабораторий

Все команды выполняются в распакованном runtime через `.venv/bin/python researchctl.py`. CLI запрашивает главный ключ скрыто, обращается только к loopback admin. Не помещайте API keys в JSON-файлы: используйте `--with-key` и скрытый ввод.

`model.json`, локальная модель (замените model на установленный ID):

```json
{"name":"Local scientist","kind":"ollama","url":"http://127.0.0.1:11434","model":"YOUR_INSTALLED_MODEL","role":"proposer","execution_location":"this_machine","disclosure_consent":false}
```

```sh
.venv/bin/python researchctl.py endpoint --kind model model.json
```

Для `/v1/chat/completions` используйте kind `openai` и URL базы, заканчивающийся `/v1`. Для физической/молекулярной службы — `scientific_json`, полный URL, role `specialist`. Удалённая служба требует точного hostname в `runtime.env` и `disclosure_consent: true`. Модель, её веса и GPU должны уже работать; произвольный ответ модели не исполняется.

`agent.json`:

```json
{"name":"Independent agent","task_id":"ACTUAL_TASK_ID","minutes":30,"max_runs":1,"model_ids":[]}
```

```sh
.venv/bin/python researchctl.py endpoint --kind agent-grant agent.json
.venv/bin/python researchctl.py access --kind agent --id ACTUAL_GRANT_ID
```

Первый ответ содержит токен один раз: передавайте его только назначенному агенту. `agent-client.py` и контракт описаны в [PROTOCOLS.md](PROTOCOLS.md). Подключение независимого агента не предоставляет ему shell/Docker или права рецензента.

`peer.json`:

```json
{"name":"Independent laboratory","organization":"Operator-declared identity","url":"https://YOUR-LAB-HOST/v1/reproduce","recipes":["markov_entropy"],"disclosure_consent":true}
```

```sh
.venv/bin/python researchctl.py endpoint --kind peer peer.json --with-key
.venv/bin/python researchctl.py access --kind peer --id ACTUAL_PEER_ID
.venv/bin/python researchctl.py access --kind model --id ACTUAL_MODEL_ID --enabled false
```

`access` по умолчанию отзывает доступ; `--enabled true` включает модель или peer снова. Отозванный агентский grant заново не включается: выпустите новый. После настройки в UI остаются выбор моделей и просмотр/подтверждение конкретной передачи лаборатории.

## Научные решения и эволюция графа

Завершение вычисления создаёт evidence, но оставляет claim непроверенным человеком. Во вкладке Kaska с правом `review` доступны принятие, отклонение, отзыв, замена результата и связи. История сохраняется; последнее решение определяется цепочкой previous_decision_id, а не порядком строк SQL. Новая редакция гипотезы или отзыв принятого основания переводит зависимые выводы в `needs_review`. Циклические depends_on отвергаются. Contradicts записывает обоснованное разногласие; автоматически разрешать научный спор система не пытается.

Для CLI сохраните `decision.json` (при первом решении expected_decision_id равен null; дальше возьмите latest_decision_id из status):

```json
{"experiment_id":"ACTUAL_EXP_ID","decision":"accept","expected_decision_id":null,"note":"Принимаю только проверенное конечное вычисление с указанными допусками."}
```

```sh
.venv/bin/python researchctl.py decision decision.json
```

Решения: `accept`, `reject`, `retract`, `supersede`. Для supersede дополнительно требуется `replacement_id` другого завершённого и принятого эксперимента той же задачи. `relation.json`:

```json
{"from_experiment":"DERIVED_EXP_ID","to_experiment":"BASIS_EXP_ID","relation":"depends_on","note":"Вывод первого эксперимента использует второй как научное основание."}
```

```sh
.venv/bin/python researchctl.py relation relation.json
```

Для противоречия relation = `contradicts`. Для отзыва ошибочной связи: `unrelate.json` с полями `relation_id` и `note`, затем `researchctl.py unrelate unrelate.json`. Записи не удаляются.

## Подписанный пакет и воспроизведение

```sh
.venv/bin/python researchctl.py bundle --experiment-id ACTUAL_EXP_ID --output experiment.json
.venv/bin/python researchctl.py checkpoint --output checkpoint.json
```

Пакет содержит конечное научное предсказание, параметры, snapshot гипотезы, evidence, происхождение сборки, доступные исходники, допуски, решения графа и подписанный checkpoint. Подпись Ed25519 связана с хешем всего пакета. Для нового build используются проверенные снимки `build-sources/<image-sha>/`, а не изменившиеся файлы установки. Старые запуски без таких снимков получают явную метку `exporter_sources_only_execution_snapshot_unavailable`: файлы экспортера не выдаются за исполненные исходники.

Пакет содержит частную гипотезу и историю научных решений узла: перед отправкой проверьте содержимое. Сохраните checkpoint и fingerprint ключа вне вычислительного узла по доверенному каналу. Подпись сама по себе подтверждает владение ключом, не имя лаборатории и не научную истинность.

На другом узле с работающим Docker runtime:

```sh
.venv/bin/python replay.py experiment.json --trusted-key EXPECTED_KEY_SHA256 --report reproduction.json
```

`--trusted-key` — заранее проверенный `key_sha256` исходного узла. Без него проверяется математика подписи, но ключ не считается доверенным. CLI проверяет пакет и независимо пересчитывает исходный результат, затем запускает **установленный** фиксированный Markov recipe через локальный admin, проверяет `environment.kind=docker` и сравнивает траектории с допуском 1e-10. Вложенный код никогда не исполняется. Создаются новые task/run/evidence; научное принятие остаётся отдельной рецензией. Самостоятельность второй машины и личность лаборатории устанавливает оператор. Отчёт записывается с правами 600 без перезаписи.

## Проверки релиза

Unit/integration-проверки покрывают авторизацию, бюджеты, независимый kernel, гонки отзыва, journal failure, cleanup, историю рецензий, подписанные пакеты и replay validation. HTTP gateway проверяется с production Waitress; scientist/verifier используют настоящие subprocess pipes. Docker и Kaska в этих тестах — явно обозначенные doubles. Реальный Mac/Docker/Kaska smoke остаётся обязательной проверкой вашей установки; успешный протокольный тест не заменяет её.
