<div align="center">

<img src="https://github.com/openzebra/rlm.pi/blob/master/assets/hero.png?raw=true" alt="pi-rlm">

</div>

<div align="center">

<sub>
<a href="README.md">English</a> &nbsp;·&nbsp; <a href="README.zh-CN.md">中文</a> &nbsp;·&nbsp; <b>Русский</b>
</sub>

</div>

---

# pi-rlm — рекурсивные языковые модели для кодинг-агента [Pi](https://github.com/earendil-works)

<div align="center">

**Рекурсивные языковые модели (RLMs)**, реализованные нативно как расширение Pi —
ПОЛНОСТЬЮ ЛОКАЛЬНО.

</div>

---

**Рекурсивная языковая модель (RLM)** — это универсальная (task-agnostic) парадигма инференса, в которой корневая языковая модель управляет почти бесконечным контекстом, *программно* исследуя, декомпозируя и **рекурсивно вызывая саму себя** для обработки входных данных. RLM заменяют канонический вызов `llm.completion(prompt, model)` на вызов `rlm.completion(prompt, model)`: промпт/контекст передается как переменная в среде REPL, с которой взаимодействует модель, а модель может запускать вызовы sub-LLM и sub-RLM как обычные функции в коде.

Это ставка на архитектуру в стиле [CodeAct](https://arxiv.org/abs/2402.01030): каждая языковая модель получает доступ к среде выполнения кода, вызовы sub-(R)LM являются функциями, а контекст/промпты — объектами в коде, что является уходом от стандарта вызова инструментов через JSON. Система, построенная таким образом, *сама по себе* является языковой моделью, которая полагается на рекурсивные вызовы sub-LLM, отсюда и название.

`pi-rlm` переносит эту парадигму **нативно в Pi**:

- **Модель-оркестратор** управляет постоянным Python REPL пошагово.
- Работа с длинным контекстом **делегируется** дешевым worker-моделям через `llm_query` / `llm_query_batched`.
- Сложные подзадачи **рекурсивно** передаются в дочерние RLM через `rlm_query` (с ограничением глубины).
- Все работает **in-process** — единственным внешним процессом является локальный worker `python3`.

> This is a Pi-plugin reimplementation of the RLM method (see the [RLM paper](https://arxiv.org/abs/2512.24601)).
> It is **not** the Python library.

## Как это работает

```
pi process (TypeScript)
 ├─ /rlm  ──► движок управляет SMART (корневой) моделью пошагово (пишет ```repl``` Python)
 │             │  каждый шаг: парсинг repl-блоков ──► запуск в песочнице ──► возврат stdout
 │             ▼
 ├─ bridge ── llm_query / llm_query_batched ──► WORKER модель (serverless, in-process)
 │            rlm_query ──► рекурсивный дочерний RLM (с собственной песочницей), с ограничением глубины
 ├─ AgentTree ──► живое дерево агентов/субагентов над редактором (роли, глубина, стоимость, токены)
 └─ PythonSandbox ── `python3 worker.py` ──[JSONL over stdio, bidirectional]── постоянный REPL
```

- **Никаких серверов, сокетов или Docker.** Единственным внешним процессом является локальная песочница `python3`. Когда код в песочнице вызывает `llm_query`, worker пишет запрос в stdout и блокируется на stdin; Pi обрабатывает его внутри своего процесса и записывает ответ обратно. **API-ключи провайдеров никогда не попадают в песочницу.**
- Песочница предоставляет `context`, `llm_query`, `llm_query_batched`, `rlm_query`,
  `rlm_query_batched`, `SHOW_VARS()`, `todo()`, `ask_user_question()` и словарь `answer`.
  Модель отправляет окончательный результат, устанавливая `answer["ready"] = True`.

## Установка

`pi-rlm` — это пакет Pi. Pi предоставляет peer-зависимости `@earendil-works/pi-*` и `typebox`; **не** устанавливайте их отдельную копию в этот пакет. Требуется `python3` в `PATH` (только стандартная библиотека).

Рекомендуемая локальная установка при разработке:

```bash
pi install /path/to/this-repo/pi-plugin/rlm
```

Установка опубликованного npm-пакета:

```bash
npm publish                       # например, как @<you>/pi-rlm
pi install npm:@<you>/pi-rlm
```

> **Установка через Git** требует, чтобы манифест пакета находился в корне устанавливаемого репозитория. Для поддиректорий монорепозитория, таких как эта, предпочтительнее использовать локальный путь или npm, как указано выше.

Если вы ранее копировали папку расширения напрямую, удалите ее, чтобы она не перекрывала пакет:

```bash
rm -rf ~/.pi/agent/extensions/rlm
```

Затем выполните `/reload` или перезапустите Pi. Убедитесь с помощью `pi list`, что пакет появился в `settings.packages`, и проверьте, что `/rlm`, `/rlm-config` и `/rlm-stop` отображаются в разделе **[Extensions]**.

## Команды

| Команда | Горячая клавиша | Описание |
|---|---|---|
| `/rlm` | `Ctrl+Shift+R` | Переключить постоянный режим RLM (направлять обычные промпты через движок RLM) |
| `/rlm-stop` | | Прервать текущий запуск |
| `/rlm-config` | | Выбрать smart- и worker-модели и настроить параметры запуска |
| `/rlm-resume` | | Возобновить прерванный запуск (по умолчанию `@latest`) |
| `/rlm-runs` | | Список последних запусков |
| `/rlm-help` | | Показать руководство по запуску и шпаргалку |

Пока запуск активен, **живое дерево** отображает корневой оркестратор и каждый sub-LLM / рекурсивный дочерний элемент со статусом, моделью, стоимостью, токенами и длительностью. Окончательный ответ публикуется в чате в формате markdown; любые правки кода собираются в виде диффов и проверяются через всплывающее окно (если не включен `yolo`).

## Sandbox API

Эти функции внедряются в пространство имен Python модели внутри REPL:

| Функция | Сигнатура | Описание |
|---|---|---|
| `context` | `list[dict]` | Репозиторий, упакованный как `[{"path","content","tokens"}, ...]` — вся кодовая база |
| `llm_query` | `(prompt, model=None) -> str` | Одноразовый вызов sub-LLM (worker-модель) |
| `llm_query_batched` | `(prompts, model=None) -> list[str]` | Параллельные вызовы sub-LLM (с ограничением пула) |
| `rlm_query` | `(prompt, model=None) -> str` | Рекурсивный дочерний RLM со своей песочницей (с ограничением глубины) |
| `rlm_query_batched` | `(prompts, model=None) -> list[str]` | Параллельные рекурсивные дочерние RLM |
| `todo` | `(action, **kwargs) -> str` | Список задач: `create`/`update`/`list`/`get`/`delete`/`clear` |
| `ask_user_question` | `(questions) -> list[dict]` | Задать пользователю структурированные вопросы (только на глубине 0) |
| `SHOW_VARS` | `() -> str` | Список текущих переменных и их типов |
| `answer` | `dict` | Установите `answer["content"]=...; answer["ready"]=True` для завершения |

## Настройки (`/rlm-config`)

| Настройка | По умолчанию | Значение |
|---|---|---|
| Smart model | Активная модель Pi | корневой оркестратор |
| Worker model | Самая дешевая доступная | отвечает на `llm_query` |
| Max recursion depth | `4` | при превышении этой глубины `rlm_query` переключается на `llm_query` |
| Max iterations | `30` | количество шагов до завершения работы движка |
| Budget ceiling | нет | остановка всего дерева, когда затраты в USD превышают этот лимит |
| Max consecutive errors | `5` | остановка после N последовательных шагов с ошибками |
| REPL block timeout | `120s` | реальное время на один `repl`-блок (SIGALRM в worker) |
| Max concurrent sub-calls | `4` | размер пула для `*_batched` |
| Orchestrator addendum | вкл | инструкция «делегируй, а не решай сам» |
| Trajectory compaction | вкл (0.85) | суммаризация истории при приближении к лимиту окна контекста |
| `yolo` | выкл | применять предлагаемые правки немедленно, пропуская окно подтверждения |
| `askUserQuestion` | вкл | предоставить доступ к `ask_user_question()` для модели |
| `todo` | вкл | предоставить доступ к `todo()` для модели |

> **Примечание по параллелизму:** каждый дочерний `rlm_query` запускает собственного worker `python3` (~50–150 мс «холодного старта»). В худшем случае количество параллельных интерпретаторов ≈ `maxConcurrentSubcalls`^(depth−1); при настройках по умолчанию (глубина 4, параллелизм 4) это 4³ = 64 в патологическом случае. Лимиты бюджета и ошибок (см. выше) ограничивают общие затраты независимо от степени разветвления.

## Телеметрия и логи запусков

- **Логи запусков** (`runLog`): включены по умолчанию. Каждый запуск записывает след в формате JSONL в `.rlm/runs/` (по умолчанию) с ограничением `maxRuns` (50). Поддерживает **снимки** (`sandbox.pkl`) и **возобновление** прерванных запусков через `/rlm-resume`. Снимки защищены сессионным `nonce` для предотвращения повторов между сессиями.
- **Трассировка MLflow** (`telemetry`): опционально. Установите `MLFLOW_TRACKING_URI` или настройте `trackingUri` / `experimentId` в `/rlm-config`. Корневой запуск помечается как span MLflow для корреляции трасс при возобновлении. Bearer-токен берется из переменной окружения `MLFLOW_TRACKING_TOKEN` и **никогда не сохраняется** в `rlm.json`.

## Безопасность

- **Изоляция ключей**: ключи провайдеров хранятся только в TypeScript (`AuthStorage`); песочница получает промпты и возвращает текст, но никогда не получает ключи.
- **Очистка окружения**: чувствительные переменные окружения (API-ключи, токены) удаляются перед запуском worker. Worker не может прочитать учетные данные провайдеров из `os.environ`.
- **НЕ является защищенной песочницей**: Python-worker предоставляет доступ к `__import__` и `open`. Код, написанный моделью, может импортировать сетевые модули, читать/записывать локальные файлы и писать JSON-данные протокола в stdout. Этот уровень доверяет коду корневой модели; протокол stdio изолирует ключи провайдеров и жизненный цикл процесса, а **не** ограничивает вредоносный код. Более строгая песочница (Docker, seccomp) может быть добавлена позже через настройки без изменения протокола.
- **Ограниченные встроенные функции**: запрещены `eval`/`exec`/`compile`/`input`/`globals`/`locals`; тайм-аут SIGALRM для каждого блока + родительский watchdog (SIGKILL при зависании); лимиты по бюджету / токенам / времени / количеству последовательных ошибок.
- **Доверие**: локальная установка в проект требует доверия к проекту Pi.

## Структура проекта

```
src/
  sandbox/    worker.py + JSONL stdio driver (PythonSandbox) · protocol.ts · sandbox-manager.ts
  bridge/     model.ts (одноразовое завершение) · llm-query.ts · rlm-query.ts (рекурсия)
  core/       engine.ts (цикл) · iteration · limits · answer · compaction · pipeline · types
  prompts/    системные промпты и промпты для каждого шага (перенесены из Python-референса)
  text/       парсинг (repl-блоки) · токены · превью · правки
  state/      дерево-агентов · события · чтения/записи · возобновление · пути · строки
  tool/       repl-tool · rlm-events · агрегатор · предложение-правок · emitter-listener
  config/     значения по умолчанию · настройки (сохранение и валидация rlm.json)
  context/    упаковка репозитория на базе repomix + кеширование
  telemetry/  MLflow sink · диспетчер · mlflow-config
  ui/         виджет-дерева · статус · выбор-модели · панель-конфигурации · вступление · тема
  commands/   rlm · rlm-config
  mode/       rlm-mode (контроллер) · маршрутизатор-ввода
  patch/      применение · всплывающее-окно · индекс
  util/       ошибки · параллелизм
test/         фазы 1–9 · native-smoke · native-mode · помощники
```

## Тесты

Среда выполнения — **Bun** (`bun install`, `bun run …` — никогда не используйте npm/pnpm/yarn).

```bash
bun run test/phase1.ts                   # sandbox: exec, persistence, key isolation, timeout kill
bun run test/phase4.ts                   # recursion depth-cap logic (no tokens)
bun run test/phase5.ts                   # live agent tree rendering (no tokens)
RLM_TEST_LIVE=1 bun run test/phase2.ts   # real llm_query through the sandbox
RLM_TEST_LIVE=1 bun run test/phase3.ts   # real end-to-end /rlm over a file context
RLM_TEST_LIVE=1 bun run test/phase4.ts   # engine solves a 20-doc needle-in-haystack
```

## Общая информация

Реализовано на основе метода из [статьи RLM](https://arxiv.org/abs/2512.24601), с нативной переработкой для Pi.

Если вы используете этот проект в своих исследованиях, пожалуйста, сошлитесь на оригинальную работу RLM:

```bibtex
@misc{zhang2026recursivelanguagemodels,
      title={Recursive Language Models},
      author={Alex L. Zhang and Tim Kraska and Omar Khattab},
      year={2026},
      eprint={2512.24601},
      archivePrefix={arXiv},
      primaryClass={cs.AI},
      url={https://arxiv.org/abs/2512.24601},
}
```
