# 09 — Рантаймы

**Русский** · [English](../09-runtimes.md)

Рантайм — способ присутствия пира. Ядро знает четыре: `claude`, `codex`, `telegram`, `notifier`. Первые два — AI-агенты, вторые два — инфраструктурные роутеры.

## Агентские рантаймы: claude, codex

Демон поднимает агента на входящее сообщение в сессии под pty-супервизором и закрывает её после часа простоя. Системный промпт ([05](05-системный-промпт.md)) подменяется штатным механизмом рантайма.

**Claude** запускается с флагом `--system-prompt-file`, который заменяет встроенный системный промпт собранным iapeer текстом. Продолжение диалога идёт через `--continue` (берётся последняя сессия), не через выбор сессии по идентификатору. История диалога лежит в `~/.claude/projects/<папка>/*.jsonl`.

**Codex** получает промпт настройкой `model_instructions_file` (`codex -c model_instructions_file=<файл>`), которая подменяет компилированные базовые инструкции. Продолжение — `codex resume --last`. История — в `~/.codex/sessions/`. Подключение к демону по MCP у Codex идёт через токен-заголовок: Codex закрывает HTTP-MCP за проверкой токена, поэтому ядро подставляет нерабочий «пустой» токен (он не несёт защитной функции), а демон узнаёт пир по заголовку `X-IAPeer-Identity`.

При настройке Codex ядро host-wide гасит стартовый диалог проверки обновлений (`check_for_update_on_startup = false` в `config.toml`). Интерактивное «доступно обновление, нажмите Enter» — это шлагбаум на загрузке: разбуженная демоном headless-сессия Codex зависла бы на нём, не дойдя до готовности, пока к ней не подключится человек. Обновления Codex остаются ручными (`codex update` либо brew), а не вопросом на старте. Уже выставленное оператором значение ключа ядро не переписывает.

Запустить агента в текущей папке вручную, свежей сессией:

```bash
iapeer claude     # поднять пир текущей папки в Claude
iapeer codex      # в Codex
```

Тонкие флаги запуска для конкретной пары пир+рантайм задаются в `<папка>/.iapeer/runtimes/<рантайм>/launch.env` ([06](06-хранение.md)).

### Какой рантайм выбирается при создании пира

Когда пир создаётся (`create` / `init`), основной рантайм определяется по слоям:

1. Явный `--runtime` — берётся он, но сначала проверяется, что рантайм установлен; если нет, команда завершается внятной ошибкой, а не принимает несуществующий рантайм молча.
2. Иначе — маркер в папке (`.claude` / `.codex`): папка, уже сконфигурированная под рантайм, сохраняет его. Маркер в приоритете и на установленность не перепроверяется.
3. Иначе — из установленных на хосте: ровно один установлен — он; ни одного — внятная ошибка (а не тихий Claude на хосте без рантайма); установлены оба — по умолчанию Claude (выбрать Codex явно — `--runtime codex`).

Установленность рантайма ядро определяет по его лаунчеру — переменная `IAPEER_<РАНТАЙМ>_BIN`, нативное расположение установщика, затем `PATH` — и по глобальному каталогу конфигурации (`~/.claude` / `~/.codex`).

## Инфраструктурные рантаймы: telegram, notifier

Это долгоживущие процессы-роутеры, не AI. Работают постоянно под launchd; демон их не поднимает и не усыпляет.

**telegram** — мост Telegram ↔ пиры. Требует, чтобы пир был природы `natural` (человек): попытка завести Telegram-присутствие у AI-агента отклоняется. Запускается как `telegram-runtime run`.

**notifier** — планировщик. Разворачивает служебных пиров `timer` (срабатывает по расписанию) и `watcher` (следит за событиями). Запускается как `notifier-runtime run`.

Плисты инфраструктурных пиров, в отличие от плиста демона, **загружаются автоматически** при `iapeer create --runtime telegram|notifier` и при `iapeer onboard` (если не передан `--no-bootstrap`). Поэтому отдельный `launchctl bootstrap` для них не нужен.

**Схема плистов (мульти-инфра).** Одна personality может нести несколько always-on каналов (например, человек с Telegram-мостом и веб-консолью одновременно). Каждая пара (personality, runtime) резолвится в собственный launchd-label: **легаси базовый плист** `com.iapeer.<personality>` остаётся каналом того рантайма, который он уже держит (без принудительной миграции — существующие одноканальные хосты не трогаются), а каждая новая установка получает **per-runtime плист** `com.iapeer.<personality>.<runtime>`. Оба канала держатся launchd KeepAlive одновременно и независимо; `stop`/`start <peer> <runtime>` делают bootout/bootstrap ровно label этого рантайма, `remove` сносит **все** плисты personality. Суффиксный label ни с чем не коллидирует: имена пиров не могут содержать точек.

## Несколько рантаймов у пира

В профиле пира два поля про рантаймы: `runtimes` (все, на которых пир может присутствовать) и `default_runtime` (основной — куда маршрутизируются сообщения и какой поднимается первым). Расширить возможности и переключить основной рантайм — разные команды.

### add-runtime — добавить рантайм

```bash
iapeer add-runtime codex --peer assistant   # одному пиру
iapeer add-runtime codex --all              # всем пирам
```

Добавляет рантайм существующим пирам: дописывает его в `runtimes`, разворачивает пространство рантайма и проводит полную настройку для Codex (предварительное доверие к папке, рычаг родной памяти, подключение памяти, MCP). `default_runtime` команда **не трогает** — это расширение возможностей, не смена маршрутизации. Идемпотентна. Пир с инфраструктурным дефолтом пропускается, когда добавляется *агентский* рантайм: дать роутер-пиру агентский рантайм — решение оператора через `create`, а не массовая операция.

**Инфраструктурный** рантайм тоже добавляется этой командой, но только одному названному пиру:

```bash
iapeer add-runtime voicetalk --peer arthur   # прицепить второй always-on канал
iapeer connect voicetalk arthur              # то же самое в привычной по connect форме
```

Это операторский путь для описанной выше мультиканальной схемы — дать *существующей* личности ещё одно постоянное присутствие. Команда резолвит папку пира из реестра, ставит пер-рантаймовый плист, вызывает собственный хук `selfConfig` рантайм-пакета (он владеет `interfaces.<рантайм>`) и загружает job. `--all` для инфраструктурного рантайма отвергается: каждый канал — это launchd-job, и разворачивать их по всему флоту оператор не имеет в виду никогда.

`create` так не умеет. Он резолвит пира по **расположению** (`~/.iapeer/peers/<имя>`, если не передан `--path`), поэтому до записи пира, чья папка лежит в другом месте, не дотягивается вовсе — вместо этого создал бы вторую, ложную папку.

Объявление и реальность расходятся: `runtimes` только *объявляет* канал, а *реализует* его плист. Если запись рантайм объявляет, а плиста нет — команда переставляет канал, а не рапортует «already» о канале, которого не существует.

### default-runtime — переключить основной

```bash
iapeer default-runtime codex --peer assistant   # одному
iapeer default-runtime codex --all              # все пиры
```

Переключает основной рантайм — куда идёт маршрутизация и какой рантайм поднимается первым. Массовое переключение `--all` — это и есть «пересадка» всей команды с одного агентского рантайма на другой; обратно симметрично (`default-runtime claude --all`). Команда откажет, если пир не объявляет этот рантайм (сначала `add-runtime`), и пропустит инфраструктурных. Реестр чинится в той же команде, поэтому маршрутизация переключается сразу.

Выбор для маршрутизации строгий: отправка без явного `runtime` всегда целится в `default_runtime`. Живая сессия на другом объявленном рантайме не перехватывает такую отправку; если дефолтная сессия спит, демон будит её. Явный `runtime` по-прежнему выбирает конкретную недефолтную поверхность.

## Подключение канала: connect

```bash
iapeer connect telegram assistant --token <токен-бота>
```

Подключает Telegram-бота к пиру за один проход: регистрирует бота, прописывает его в профиль пира, перезапускает роутер. От человека нужен только токен (у `@BotFather`). После — отправьте боту **первое сообщение**: Telegram не позволяет боту начать чат первым, поэтому канал оживёт только после входящего от человека.

Команда идемпотентна, и поведение зависит от токена. **Тот же токен** — чистый no-op: файл учётных данных бота не меняется, живой роутер уже работает на этих данных, перезапуска нет. **Новый или изменённый токен** требует перезапуска роутера (учётные данные читаются только при старте) — это секундная пауза в доставке для всех Telegram-пиров; входящие при этом не теряются (long-polling помнит смещение, Telegram держит сообщения до 24 часов). Неверный токен отсекается сразу, с дословной причиной от Telegram.

`connect` не только про Telegram. `iapeer connect <рантайм> <пир>` цепляет любой presence-канал: у Telegram остаётся его особый поток (нужны токен, поход к `@BotFather` и хранилище учётных данных), а любой другой инфраструктурный рантайм идёт по генеричной привязке — тот же пер-пировый путь, что и `add-runtime <рантайм> --peer <пир>`. Рантайм-пакет должен быть предварительно установлен на хосте (`install-runtime`); команда проверяет это до того, как что-либо тронуть.

## Рантайм-пакеты

Инфраструктурные рантаймы поставляются отдельными npm-пакетами (`*-runtime`). Ставит и обновляет их ядро:

```bash
iapeer install-runtime notifier        # установить пакет + развернуть его пиров
iapeer update-runtime notifier         # обновить с перезапуском пиров
iapeer update-runtime --all            # обновить все рантайм-пакеты
```

`install-runtime` ставит пакет и разворачивает объявленный им набор пиров (notifier разворачивает `timer` и `watcher`); флаг `--no-bootstrap` ставит пакет, не загружая его пиров в launchd. Рантайм без объявленного набора (telegram) — это режим «добавляет оператор»: пиров заводят вручную через `iapeer create <имя> --runtime telegram`.

### Деплой рантайм-пакета: всегда из облака, как `iapeer update`

Доставка рантайм-пакета следует тому же разделению **release ≠ deploy**, что и ядро (см. [07 — Установка и обновление](07-установка-и-обновление.md)). `iapeer update-runtime <рантайм>` — это рантайм-аналог `iapeer update`: деплой на хост **из облака (npm)**, а не из локального рабочего дерева. Он:

1. **version-gate** — сверяет npm-latest с `version`-штампом манифеста (`~/.iapeer/runtimes/<рантайм>/runtime.json`); установлен latest → no-op (`already-latest`). Без штампа гейт пропускается, переустановка идёт идемпотентно (и сообщает об этом) — поэтому штамповать `version` в манифест обязанность пакета (иначе ломается и `update-runtime`-гейтинг, и наблюдаемость версии в `iapeer status` и аудите версий по пирам).
2. **re-install** — форс-`npx @agfpd/<рантайм>-runtime`: **тянет пакет из npm** и даёт ему self-deploy'ить новый бинарь + манифест. Источник — опубликованный пакет, **не** рабочее дерево.
3. **re-provision** — идемпотентно тем же путём, что `install-runtime` (sync описаний, re-self-config; живые пиры не клоббятся).
4. **restart** — перезапускает зарегистрированных пиров рантайма обычными `stop`/`start`. Они **host-aware**: пир под супервизором корректно гасится (kill супервизора и ребёнка) и поднимается заново на новом бинаре — отдельная процедура не нужна.

Соответственно, **локальный self-install из рабочего дерева — это dev-проба, а не деплой** (аналог запуска ядра из исходников `bun src/...`). Деплоить рантайм надо из облака: сначала release (`npm publish` — в облако, живые хосты не трогает), затем deploy (`update-runtime` — pull из облака + перезапуск). Обход npm (активация локального кода через self-install) рассинхронизирует npm и хост и невоспроизводим на новом хосте: чистый хост поднимает рантайм только из npm (`install-runtime`, далее `update-runtime`). Ядро (`iapeer update`) сознательно НЕ трогает рантайм-пакеты — симметрия намеренная.

Путь к лаунчеру рантайма по умолчанию резолвится из `PATH`; переопределить его (например для launchd с минимальным `PATH`) можно переменными `NOTIFIER_RUNTIME_BIN` / `TELEGRAM_RUNTIME_BIN` — см. [14 — Конфигурация и журналы](14-конфигурация-и-журналы.md).

Контракт рантайм-пакета — формат манифеста, обязанности, команды — в [13 — Архитектура и контракт](13-архитектура-и-контракт.md).
