# Ratchet Loop

> Одна метрика тянется до упора циклом с откатом: latency, число SQL-запросов, размер бандла, pass-rate. Каждая итерация мерится замороженным оценщиком (anti-Goodhart), улучшившее изменение остаётся, остальное откатывается через git; независимый verifier-проход проверяет, что метрика не обманута. Петля сама не терминируется — крутится до бюджета или плато. Используй когда пользователь говорит «оптимизируй метрику до упора», «крути, пока улучшается», «ratchet», или называет число, которое надо довести до минимума или максимума. Довести фичу до готовности — goal-pipeline; петля без измеримого скаляра — ralph-loop.

- Skill: `goldenprofile/ratchet-loop` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add goldenprofile/ratchet-loop`
- Raw SKILL.md: https://api.skillmd.com/api/skills/goldenprofile/ratchet-loop/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: goldenprofile (https://skillmd.com/u/goldenprofile)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/goldenprofile/ratchet-loop

---


# Ratchet Loop

Оптимизационная петля-**храповик** поверх одной измеримой метрики. Идея взята из
`autoresearch` Карпатого и перенесена на brownfield: ты не «дорабатываешь фичу до
готовности» (это `goal-pipeline`), ты **тянешь одно число** — p95 latency, число
SQL-запросов на странице, размер бандла, pass-rate промпта, mutation score — и
оставляешь только то, что реально его сдвинуло в нужную сторону. Всё остальное
откатывается. История ветки = trail исследования.

Главный механизм — **храповик**: попробовал → измерил замороженным оценщиком →
если лучше, коммит остаётся (зубец защёлкнулся); если нет, `git reset` к
последнему «keep». Петля **сама не заканчивается** — крутится до бюджета, до плато
или до прерывания. Это отличает её от любой gate-петли с булевым «done».

Два предохранителя, без которых храповик вырождается в reward hacking, вшиты в
контракт: **замороженный оценщик** (агент не имеет права его трогать; целостность
проверяется хешем) и **независимый verifier-проход** (свежий суд по артефактам, а
не по самоотчёту петли).

## Чем НЕ является (анти-коллизия)

| Похоже на | Разница |
|---|---|
| `goal-pipeline` | Coverage-пайплайн под хостовый `/goal`: разные фазы, терминируется на «все фазы done + аудит». ratchet-loop крутит ОДНУ задачу-вариацию на одном артефакте ради скаляра и сам не терминируется. `/goal` проверяет *условие*, а не «лучше ли число, иначе откат» — храповик в его модель не ложится. |
| `ralph-loop` | Общая in-session петля без хостового оценщика и без контракта оптимизации. ratchet-loop — её специализация: добавляет скаляр + направление, frozen eval, keep-or-revert и verifier. Если оптимизируемого скаляра нет — бери `ralph-loop`. |
| gate-loop (паттерн, не навык) / `/goal "done when зелёное"` | Булев гейт: «чини пока тесты/CI зелёные», терминируется когда щёлкнуло. ratchet тянет непрерывный скаляр и после «зелёного» продолжает жать. Для «до зелёного» отдельный скилл не нужен — это голый `/goal` или одна фаза `goal-pipeline`. |
| `spec-writer` | Только пишет документ, ничего не исполняет и не меряет. |

## Когда брать / когда не брать

**Брать:** есть **одно число**, которое надо двигать, и **детерминированный (или
почти) способ его померить** на одном артефакте — оптимизация запроса/функции,
снижение latency/памяти/запросов, ужатие бандла, подъём pass-rate промпта или
few-shot-набора, рост mutation score. Изменения **обратимы через git** (чистый код, без
сайд-эффектов).

**Не брать:**
- Нет скаляра, есть «довести фичу до готового» → `goal-pipeline`.
- Метрику нельзя померить автоматически и воспроизводимо → петле нечего тянуть;
  сперва сделай оценщик, потом возвращайся.
- Цель — булев гейт «до зелёного» → голый `/goal "done when ..."`.
- Оптимизируемый артефакт **сам по себе сайд-эффектный** (миграция, деплой-скрипт,
  рассылка) → откат через git не спасёт; это **не** задача для ratchet-loop, откажись
  и эскалируй (`RL_HALT`).

## Предусловия

1. **Git-репозиторий, чистое рабочее дерево** — храповик стоит на `git reset`. Грязное
   дерево на старте → попроси закоммитить/стешить. Нет git → откажись (предложи
   `git init` + baseline-коммит, без него петля небезопасна).
2. **Замороженный оценщик** — команда (`make`-цель или прямая), которая печатает скаляр в
   распознаваемой строке и **не зависит от оптимизируемого артефакта** как от
   источника правды. `python eval.py` → `metric: 0.873`, или `pytest
   tests/bench_test.py -q` со строкой метрики, или `make bench`.
3. **Понятные команды проекта.** Все прогоны — командами в терминале твоей среды;
   не заводи под это скрипты-файлы.

## Профили автономности

Выбирается в этапе 0 (дефолт — **checkpoint-on-verify**). Пишется в `STATE.md`.

| Профиль | Поведение | Когда |
|---|---|---|
| **checkpoint-on-verify** (дефолт) | Гонит попытки автономно; на каждом verifier-чекпоинте (каждые `K` keep-ов и финал) печатает `RL_HALT` и возвращает управление — ты глянул, ре-диспатчнул. | Дефолт. Рутина сама, «лучшее» проверяется глазами по чекпоинтам. |
| **full-auto** | Руки прочь до бюджета/плато; стоп только на `RL_HALT` от блока (eval сломан / 3-strike crash / попытка сайд-эффекта). | Низкорисковая чистая оптимизация под git (один файл, тесты зелёные). |
| **per-keep** | `RL_HALT` после каждого принятого улучшения. | Хрупкий/незнакомый код, где хочешь видеть каждый зубец. |

---

## Этап 0 — Контекст

1. **Профиль автономности** — спроси одним `AskUserQuestion`, если не задан (дефолт
   checkpoint-on-verify). Запиши в `STATE.md`.
2. **Git baseline** — `git rev-parse HEAD` и проверка чистоты дерева
   (`git status --porcelain`). Запиши `Baseline ref:` в `STATE.md`. Грязно → стоп,
   попроси привести дерево в порядок.
3. **Память** — подгрузи memory-индекс (`MEMORY.md`), выборочно подними релевантное
   (предпочтения по стеку, прошлые прогоны по этому артефакту). Применённое вынеси в
   контракт как «Из памяти: …».

---

## Этап 1 — Контракт оптимизации (программа петли)

Это самая важная часть: пока контракт не
зафиксирован, петлю не запускай. Эхо задачи в одно предложение, затем определи и
**покажи пользователю** контракт:

- **Мутируемая цель** — РОВНО один файл (по умолчанию) или явный малый набор. Только
  он редактируется. Всё прочее — заморожено.
- **Замороженный оценщик** — точная команда измерения + строка-якорь метрики
  (например `^metric:` или `^val_bpb:`). Оценщик и любые его данные-эталоны входят
  в **frozen-set** и не редактируются (этап 2).
- **Метрика и направление** — один скаляр, `minimize` или `maximize`. Не «качество»,
  не «быстрее» — конкретное число.
- **Порог шума (epsilon)** — насколько метрика должна сдвинуться, чтобы считать это
  улучшением, а не дрожанием замера. Если замер шумный — оцени epsilon по 2–3
  прогонам baseline.
- **Бюджет на попытку** — wall-clock таймаут (например 5 мин). Превышение → kill,
  попытка = crash/discard.
- **Условие останова петли** — `until-interrupted` (дефолт), либо
  `N trials`, либо `plateau:K` (K попыток подряд без улучшения сверх epsilon).

Уточняющие вопросы — только истинные пробелы (что за метрика, чем мерить, что
правим). Микро-детали — в показ контракта как предположения для правки в один клик.

Напечатай `RL_CONTRACT` со всеми полями и `AskUserQuestion` «Старт?» с режимами
правки (**Старт** · **Поправить цель/оценщик** · **Сменить метрику/направление** ·
**Сменить бюджет/останов**). Молчание ≠ подтверждение.

Пример заполненного контракта, заморозки оценщика, `results.tsv` и финала —
`references/contract-example.md` (снижение числа SQL-запросов на странице Django).

---

## Этап 2 — Заморозка оценщика (anti-Goodhart core)

Без этого петля сжульничает в первую же ночь. После «Старт»:

1. Перечисли все файлы frozen-set: скрипт оценщика, его данные/эталоны, конфиг
   замера, фикстуры бенча. Всё, что мутируемая цель **не** должна трогать.
2. Зафиксируй их целостность: `git hash-object <file>` по каждому (или хеш дерева
   подкаталога). Запиши в `.ratchetrun/EVAL.lock`.
3. Напечатай `RL_EVAL_LOCKED` со списком файлов и хешей.

**Жёсткое правило:** мутируемая цель ≠ ни один файл frozen-set; их пересечение
пусто. Если пользователь назвал оценщиком тот же файл, что и цель — стоп, объясни:
агент, который и правит, и судит один файл, измеряет собственную выдумку.

---

## Этап 3 — Baseline

Первая «попытка» — без правок: прогони оценщик как есть, чтобы получить отправную
точку. `<eval-cmd> > .ratchetrun/run.log 2>&1`, затем достань метрику
(`Select-String '^metric:' .ratchetrun/run.log`). **Не давай выводу затопить
контекст** — только строка метрики (redirect + grep, не tee).
Запиши как `best` в `STATE.md` и первой строкой в `results.tsv`. Напечатай
`RL_BASELINE: <metric>`.

Если метрика это позволяет, на baseline сними и **профиль** (что доминирует в числе:
какой запрос, какая функция, какой кусок бандла) — чтобы гипотезы целили в горячую
точку, а не угадывали. Профиль — в лог, не в контекст.

---

## Этап 4 — Ratchet-петля

Артефакты — в `.ratchetrun/` (напомни добавить в `.gitignore`). `results.tsv`
**не коммить** (untracked), как и весь `.ratchetrun/`. Колонки (tab-separated):

```
trial	commit	metric	status	eval_ok	description
```

Цикл (до условия останова, `RL_HALT`, или прерывания):

1. **Eval-integrity guard.** Пере-сверь хеши frozen-set против `EVAL.lock`. Любое
   расхождение → `RL_HALT`, `STATE.md → Status: EVAL_TAMPERED`, стоп. Прогон
   недействителен — оценщик изменился.
2. **Гипотеза.** Одно изменение мутируемой цели (что и зачем, 1 строка), нацеленное
   на горячую точку из профиля, а не наугад. **Один рычаг за попытку** — иначе
   непонятно, что именно сдвинуло метрику.
3. **Правка.** Внеси её **только в мутируемую цель**.
4. **Прогон.** `<eval-cmd> > .ratchetrun/run.log 2>&1` с таймаутом бюджета.
   Превышение → kill процесса.
5. **Считай метрику.** `Select-String '<якорь>' .ratchetrun/run.log`. Пусто → crash.
6. **Решение храповика:**
   - **crash** (пусто/таймаут/ненулевой код) → `RL_CRASH`; если причина тупая
     (опечатка, импорт) — почини и переран один раз; иначе discard.
   - **улучшение** (метрика лучше `best` по направлению **сверх epsilon**) → **keep**:
     `git commit`, обнови `best`, `RL_KEEP: <metric>`.
   - **равно/хуже** → **revert**: `git reset --hard <last-keep-commit>` (или
     `git restore`), `RL_REVERT`.
   - **критерий простоты**: метрика в пределах epsilon, но код
     **проще** (меньше строк, убрали хак) → keep как упрощение. Крошечный выигрыш
     ценой уродливой сложности → discard.
7. **Лог.** Допиши строку в `results.tsv` (`eval_ok` = да/нет из шага 1).
8. **Чекпоинт.** Профиль `per-keep` и был keep, ИЛИ накопилось `K` keep-ов
   (checkpoint-on-verify) → перейди в **этап 5** (verifier), затем `RL_HALT`.
9. **Прерывание.** Пришло сообщение пользователя → пауза на границе попытки, разберись,
   спроси перед продолжением.

### 3-strike на crash

Три подряд crash без единого keep по одной линии идей → `RL_HALT` с историей попыток;
`STATE.md → Status: BLOCKED`. Не долби стену — смени направление гипотез или верни руль.

### Если идеи кончились

Не останавливайся молча (профиль full-auto/until-interrupted). Перечитай контракт и
прошлые near-miss из `results.tsv`, скомбинируй частичные улучшения, попробуй более
радикальную правит цели. Плато объявляй только по условию `plateau:K`, не на глаз.

---

## Этап 5 — Независимый verifier-проход

Per-trial решения — самоотчёт петли; «лучшее» нужно судить **с чистого листа**.
Рекомендуемая форма (истинная независимость) — свежая сессия/суб-агент **без доступа
к транскрипту петли**, судящая только по `best`-коммиту + контракту. Минимум —
отдельный проход в текущей сессии. Проверь:

1. **Целостность оценщика.** Хеши frozen-set == `EVAL.lock`. Расхождение → провал,
   весь прогон под подозрением.
2. **Воспроизводимость.** Чистый чекаут `best`-коммита → прогони оценщик заново →
   метрика воспроизводится в пределах epsilon. Ловит выдумку из транскрипта,
   кеш-фейк и недетерминизм.
3. **Анти-хак скан диффа** против baseline ref (см. «Жульничество» ниже).
4. **Без сайд-эффектов.** Дифф трогает только мутируемую цель: нет новых миграций,
   `push`, деплоя, сетевых вызовов, destructive ops, правок вне цели.

Напечатай `RL_VERIFY` с результатом каждого пункта. Провал любого → `RL_HALT`,
`STATE.md → Status: VERIFY_FAILED`, верни руль с диагнозом.

---

## Этап 6 — Завершение (Definition of Done)

«Done» здесь — не «метрика красивая», а **прогнанные проверки** (стиль
harness-engineering: DoD = гейт, а не пожелание):

- [ ] `RL_VERIFY` зелёный: целостность оценщика + воспроизводимость `best` из чистого
      чекаута + анти-хак скан чисто + ноль сайд-эффектов вне цели.
- [ ] `HEAD` == `best`-коммит; метрика `HEAD` == записанная `best`.
- [ ] `results.tsv` когерентен: по строке на попытку, статусы согласованы с git-историей.
- [ ] Memory writeback сделан (что сработало/не сработало по этому артефакту).

Затем `RL_RUN_COMPLETE` с 5-строчным резюме: метрика baseline→best (дельта), число
попыток (keep/discard/crash), какие идеи сработали, путь к `results.tsv`, остался ли
запас. Если доля недетерминированно-проверенного в verifier высока — баннер «⚠ метрика
шумная, перед мержем подтверди дельту на нескольких прогонах».

---

## Что считается жульничеством (reward hacking)

Анти-хак скан в этапе 5 ищет в диффе мутируемой цели:

- правка/перезапись любого файла frozen-set (главное и тягчайшее);
- хардкод ожидаемого ответа, чтение файла-эталона оценщика из цели, кеш результата
  под ключ теста;
- `if <тест/бенч>: return <ответ>` и прочие ветки «распознал оценщик → подыграл»;
- `try/except`, глотающий падение, чтобы оценщик увидел «успех»;
- сетевые вызовы / внешнее состояние, дающие метрике подсмотреть ответ;
- оверфит под eval-set, когда он совпадает с тюнингующим сигналом (нужен holdout);
- сайд-эффект ради метрики (правка вне цели, глобальное состояние).

Любое срабатывание → keep недействителен, откат, `RL_HALT`.

---

## Маркеры транскрипта и память

Полная таблица маркеров (`RL_CONTRACT`, `RL_KEEP`, `RL_HALT`, `RL_RUN_COMPLETE`, …)
и правила memory-writeback (`ratchet_<artifact-slug>.md`) — в
[references/loop-protocol.md](references/loop-protocol.md). Читай его перед этапом 1
и перед завершением прогона.

---

## Связанные навыки

- `ralph-loop` — родитель: общая in-session петля без контракта оптимизации. Нет
  скаляра — бери её, не ratchet-loop.
- `goal-pipeline` — когда задача не «тяни число», а «доведи фичу/рефакторинг до
  готового по фазам».
- `harness-engineering` — независимый verifier-проход и DoD здесь — те же гейты, что
  харнесс кладёт в Definition of Done проекта. ratchet-loop исполняет «оптимизация под
  замороженным оценщиком» как один из них.
- `test-coverage-auditor` / `migration-safety-auditor` — могут быть оценщиком или
  гейтом, если оптимизируешь именно покрытие/безопасность миграций.

