Все рецепты

Lessons-ledger: коррекции как код (Phase 5.5)

Фаза 5.5 серии Harness Engineering: твоё «неправильно» превращается не в забытый чат, а в код. Команда /wrong ловит коррекцию, normalize разводит развилку: computational-урок (ловится машиной) уходит правкой сенсора в рельсы, inferential-урок (нужно суждение) — markdown-файлом в lessons/<boundary>/, который агент читает перед задачей (retrieve-правило). maintain дедуплицирует и промоутит созревшие inferential-уроки в сенсоры. Метрика здоровья — сколько уроков выпущено в сенсоры, а не размер тетрадки. Стек-агностично, с копируемыми промптами и интерактивом.

ПродвинутыйDevOps с AI25 минClaude Code, .claude/commands, AGENTS.md, git, boundary-linter
1

Леджер — это staging, а не склад

Ты не учишь модель знать проект — её знание стохастично и не версионируется. Вместо этого ты превращаешь каждую свою поправку в две вещи: текст, который агент читает перед задачей, и правило, которое нельзя проигнорить. Это и есть lessons-ledger — слой между context-инжинирингом (что агент попробует) и архитектурными барьерами (что ему разрешено зашипить). По месту в стопке: ADR объясняет «почему так решили» (для людей), рельсы (lint / boundaries / sensors) держат «как нельзя» (для машины), а леджер ловит «как именно агент ошибся» и постепенно отдаёт зрелые уроки вниз, в рельсы. Главное — режим работы. Леджер это staging, а не склад. Его здоровье измеряется не размером («сколько уроков накопилось»), а пропускной способностью («сколько уроков уехало в сенсоры»). Растущая тетрадка, из которой ничего не выпускается в барьеры, — это write-only кладбище: туда пишут, оттуда не читают, агент всё равно повторяет ошибки. Об этом нужно помнить с первого дня.

🟥 Склад (write-only)

  • Метрика — размер тетрадки растёт
  • Уроки копятся, в барьеры не уходят
  • Агент всё равно повторяет ошибки

🟩 Staging (стекает в рельсы)

  • Метрика — % уроков, выпущенных в сенсоры
  • Зрелые inferential → computational барьеры
  • Знание затвердевает в проверки
Если за месяц из леджера не выпустилось ни одного урока в сенсор — это не повод писать больше уроков, а сигнал, что развилка normalize настроена неверно: всё, что детерминируемо, должно уходить в рельсы сразу, а не оседать текстом.
2

Жизнь урока: 5 станций и две развилки

У урока есть пайплайн из пяти станций: capture (поймать «неправильно» в моменте) → normalize (LLM-проход структурирует и разводит развилку) → store (markdown в git) → retrieve (агент сам подтягивает перед задачей) → maintain (фоновая уборка и промоушн). Не каждый урок проходит все пять: на развилке normalize он либо уезжает вниз, в рельсы (computational), либо вверх, в леджер (inferential), и только inferential-уроки доходят до store/retrieve/maintain. Эксплорер ниже показывает это в двух режимах. Кликайте по станциям, чтобы открыть деталь и характерный «подводный камень» каждой. Затем прогоните два примера: computational (UI ходит в БД напрямую — выражается import-границей, уезжает в рельсы сразу) и inferential (как назвать / как грузить данные — суждение, остаётся уроком и зреет). Разница между ними — сердце всей фазы.

Lessons-ledger — коррекции как код

STAGING → RAILS

Ты не учишь модель знать проект. Ты превращаешь свои поправки в две вещи: текст, который агент читает перед задачей, и правила, которые нельзя проигнорить. Леджер — это staging, а не склад: его здоровье измеряется тем, сколько уроков уехало в сенсоры, а не сколько накопилось.

Где это живёт: между ADR и линтером

ADRArchitecture Decision Records
«Решили X, потому что Y»
для людей · меняется редко · контекст решений
↑ ADR объясняет «почему» · ↓ леджер ловит «как именно»
LEDGERLessons / урокиНОВОЕ
«Агент сделал X — неверно, надо Y»
для агента · копится · staging → рельсы
промоушн стабильного урока ↓
RAILSlint · boundaries · sensors
детерминированные барьеры — обойти нельзя
для машины · зарельсено · «правильно» измеримо

Жизнь урока · пайплайн

01 · Capture
Capture — поймать в моменте

Говоришь «неправильно» прямо там, где увидел — slash-командой агента или строкой в CLI. Майнить коррекции из git-диффов можно потом, но шумно: в диффе и суть правки, и стиль, и постороннее. Начинай с явного захвата.

/wrong (Claude Code)lessons add (CLI)Cursor rule
Высокое трение убивает всё. Если захват дольше пары секунд — делать не будет никто, включая тебя.

Прогон одного урока

промоушнразвилкаcapture/wrongnormalizeLLM-passstoregit · mdretrievescopemaintaincron + LLMrails · lintboundaries
шаг 1/4Ловишь в ревью, говоришь «неправильно». Фиксируешь через /wrong — одной строкой, не отрываясь.

Команда и гейт

Команда (напр. 12 девов)
PR на lessons / куратор
Владелец архитектуры · арбитр
Общий леджер в git

Без гейта леджер быстро забивается конфликтами и личным вкусом под видом правил. Поэтому новые уроки проходят через гейт: PR на lessons/ или один куратор как арбитр — он держит леджер связным и не даёт правилам противоречить друг другу. Generic-уроки можно делегировать, а промоушн в сенсоры и _core держать у куратора; гейт стоит оставлять лёгким, иначе арбитр станет узким местом.

Метрика здоровья

Размер леджера растёт — тревога: это write-only кладбище
% уроков, выпущенных в сенсоры — вот что должно расти

Что тут реально новое. «Capture → rule» в зачатке уже щупают (Cursor auto-rules, память моделей), но per-tool и заперто в вендоре. Незанятая ниша:

model-agnosticteam-sharedgit-nativesoft → deterministic

Дисциплина «леджер как staging, а не помойка» — то, что хайповые продукты пропускают, и то, что делает из «ещё одной memory» харнес-инструмент.

Прогоняйте оба примера подряд: видно, что computational-урок вообще НЕ заходит в леджер (это важно — иначе тетрадка раздувается дубликатами того, что уже зарельсено), а inferential-урок живёт как нудж и помечается кандидатом на промоушн только когда стабилизировался.
3

Развилка normalize: что в рельсы, что в леджер

Normalize — сердце системы. LLM-проход берёт сырое «так неправильно» и делает две вещи: структурирует (scope, блоки «Неверно»/«Верно», короткое «Почему») и разводит развилку computational vs inferential sensor. Это ровно то различие, на котором стоит весь харнес: контекст решает, что агент попробует; детерминированные ограничения — что ему разрешено зашипить. Computational — правило выразимо статической проверкой (import-граница, запрещённый вызов, структурный тест). Тогда его место НЕ в леджере, а сразу в рельсах: правка boundary-линтера, а в тетрадке остаётся лишь запись status: promoted — «почему правило появилось». Inferential — суждение, вкус, контекстный выбор, который ловится только LLM-judge или человеком. Тогда это урок-файл в lessons/<boundary>/. Сомневаешься — ставь inferential + promotable: true и опиши, при каком условии дозреет. И жёсткое табу: никогда не формулируй правило-погоню-за-метрикой («поднять покрытие до N%») — это Goodhart-давление, агент ослабит ассерты ради цифры. Правильный аналог — политика («не править существующие тесты»), а не число.

⚙️ Computational → рельсы

  • Ловится статической проверкой
  • Место — boundary-линтер, НЕ урок-файл
  • В тетрадке — лишь запись status: promoted
  • Барьер: агент физически не повторит

🧠 Inferential → леджер

  • Суждение / вкус / контекстный выбор
  • Ловится LLM-judge или человеком
  • Урок lessons/<boundary>/, читается перед задачей
  • Нудж: можно проигнорить, потому зреет к промоушну
# prompts/normalize.md — превращает сырую коррекцию в урок и разводит развилку
# turns a raw correction into a lesson and splits the fork
You are the lesson normalizer for the project's agent harness.
The architecture and its boundaries live in AGENTS.md / ADR. Rely on those, not on
generic best practices. Input: a raw correction (what the dev marked "wrong") + an
optional file path / diff from context.

1. SCOPE — where the rule applies. A glob over THIS project's structure, or a named
   boundary / module, or a task type. Narrower is better. Never widen beyond the example.
2. RULE — two blocks on a CONCRETE example: "Wrong" (what the agent did) and
   "Right" (what it should be), plus a short "Why".
3. SENSOR — the fork:
   computational — expressible as a static check (import boundary, forbidden call,
     structural test, fitness function) -> belongs in the architectural constraints
     (the boundary-linter), NOT the ledger. Give the concrete rule/config.
   inferential — judgement / taste / context, catchable only by an LLM-judge or human
     -> belongs in the ledger as a lesson.
   Unsure -> inferential, promotable: true, and state the condition under which it matures.
   NEVER write a rule that chases a metric (e.g. "raise coverage to N%"): that creates
   Goodhart pressure. The right analog is a policy ("don't edit existing tests"), not a number.
4. Return EXACTLY one lesson markdown file (frontmatter + blocks). File only — no preamble.
5. Last line — the verdict:
   VERDICT: computational -> <concrete place in the constraints>
   VERDICT: inferential  -> lessons/<boundary>/
One rule = one file. Several different mistakes in the input -> split into several files.
Тест на развилку в одну фразу: «можно ли это зашить в линтер?». Да → рельсы, и в леджере ему делать нечего. Нет → урок. Если на развилке всё время выходит «да» — это хорошо: значит твоя архитектура легибельна и почти всё детерминируемо.
4

Capture без трения: /wrong и шаблон урока

Захват — самое хрупкое звено: высокое трение убивает всё. Если зафиксировать коррекцию дольше пары секунд, не будет делать никто, включая тебя. Поэтому захват — одна команда в моменте, а не «потом оформлю». В Claude Code это макрос в .claude/commands/wrong.md (агент подхватывает markdown из этой папки), вызов — /wrong <в чём ошибка>. Для другого агента это просто alias или сохранённый промпт с тем же телом. Команда не пишет файл молча — она показывает урок и строку VERDICT и ждёт твоего «ок». Сам урок — markdown-файл с frontmatter, организованный по границам/модулям проекта (lessons/<boundary>/<id>.md). Ключевые поля: scope (glob под структуру именно твоего проекта), sensor (computational | inferential), promotable (дозреет ли до барьера), status (active | promoted | archived | conflicting), seen (сколько раз встречалось — растёт). Чтобы не стартовать с пустого леджера, прогони bootstrap по последним N PR / ревью-тредам и вытащи коррекции, которые реально повторяются (seen ≥ 2) — ничего не выдумывая.

Шаблон урока — frontmatter

scope — glob/граница под структуру ТВОЕГО проекта (уже — лучше)
sensor — computational | inferential
promotable — дозреет ли до computational барьера
status — active | promoted | archived | conflicting
seen — счётчик повторов (растёт, сигнал к промоушну)
Захват дольше пары секунд — никто не делает
Команда пишет файл без твоего «ок»
# .claude/commands/wrong.md — Claude Code подхватывает команды из этой папки
# other agents: same body as a saved prompt / alias. Invoke: /wrong <what's wrong>
---
description: Capture an agent correction as a lesson (lessons-ledger)
---
I marked something as done wrong. The correction: $ARGUMENTS
If the current context has a file/diff, note its path and which boundary it touches.

Run normalize (prompts/normalize.md):
1) decide scope  2) write the "Wrong"/"Right" blocks  3) decide sensor: computational vs inferential.

Show me the lesson file and the VERDICT line. AFTER my "ok":
- inferential  -> write the file to lessons/<boundary>/<id>.md (status: active)
- computational -> propose a boundary-linter rule AND log a lesson with status: promoted
                   (the record of "why this rule appeared")
Do not write any file before my confirmation.
Майнинг коррекций из git-диффов выглядит заманчиво, но шумно: в диффе и суть правки, и стиль, и постороннее. Начинай с явного /wrong; авто-майнинг диффов и шеринг на всю команду — не на первой неделе.
5

Retrieve: агент сам читает уроки границы

Записанный урок бесполезен, если агент не увидит его в нужный момент. Retrieve — это как урок попадает агенту перед похожей задачей. Дешёвый и надёжный механизм — не семантический поиск, а фильтр по затронутым границам проекта: трогаешь persistence/* → грузишь lessons/_global.md, lessons/_core/ и уроки этой границы. Границы модулей дают почти бесплатный сигнал релевантности. «Агент сам заглядывает» — это строка-правило в файле, который он читает на старте: AGENTS.md (универсально) и/или CLAUDE.md. Правило простое: перед работой определи затронутые границы, прочитай их active-уроки и считай их жёсткими ограничениями — нарушает решение урок, остановись и сообщи, не делай молча. Уроки со status: promoted | archived в retrieve НЕ подаются (promoted уже в сенсорах — дублировать незачем). Самое сложное здесь — тайминг: перельёшь контекст (200 уроков) — агент их игнорит и цена растёт; недольёшь — нужный не сработал. Поэтому scope по границам, а не «всё сразу».
Задача затрагивает границу
Грузим _global + _core + <boundary>
Уроки = жёсткие ограничения
нарушает
Конфликт → стоп и доклад
не нарушает
Нет конфликта → работа
## Lessons-ledger (rules learned from mistakes)   ← put in AGENTS.md / CLAUDE.md

Before working on a task:
1. Identify which boundaries / modules you touch.
2. Read lessons/_global.md, lessons/_core/ and lessons/<touched-boundary>/*.md.
3. Treat these lessons as hard constraints. If a solution violates a lesson —
   stop and report, do not do it silently.
4. If I say "wrong" during the work, that is the signal to capture a lesson: /wrong.

Ignore lessons with status: archived | promoted (promoted are already in the sensors).
Не клади весь леджер в контекст «на всякий случай». Тот же принцип, что у GC-проверок: сигнал должен быть узким и релевантным. Граница модуля — это и есть готовый дешёвый фильтр; семантику подключай, только если границ не хватает.
6

Maintain: дедуп, промоушн в сенсоры, метрика

Maintain — это «сборщик мусора» леджера, маленькие непрерывные платежи вместо разовой чистки. Фоновый job того же класса, что GC-проверки фазы 5 (на том же планировщике — CI / cron / n8n), раз в неделю или руками. Он только ПРЕДЛАГАЕТ диффы, ничего не применяет молча. Четыре функции: дедуп/мерж близких уроков (с суммой seen); ловля противоречий (два конфликтующих урока на один scope → пометить оба conflicting и вынести человеку, победителя сам не выбирает); промоушн (стабильные inferential-уроки с promotable: true, которые не менялись N дней и набрали seen ≥ M → предложить конкретное правило для boundary-линтера, исходный урок пометить status: promoted — архив, не удаление); гигиена (promoted/archived в retrieve не подаются). И главное, что выдаёт maintain в отчёте, — не размер леджера, а сколько уроков выпущено в computational sensors за период. Это и есть метрика здоровья. Леджер должен стекать в рельсы: inferential-урок — нудж, его можно проигнорить, поэтому ценность в том, что зрелое знание затвердевает в барьер, а не лежит вечно текстом. На этом замыкается ядро автономности: коррекции, которые GC не ловит (недетерминируемые ошибки вкуса), проходят через леджер и часть из них становится теми самыми барьерами фазы 3. Дальше серия продолжается фазами 6–8 (skills, workflow, тиражирование); полная карта — в хабе.
ФазаЧтоРецепт
ХабКарта серии Harness Engineeringharness-engineering — готово ✓
5Garbage collection: дрейф → артефактыharness-garbage-collection — готово ✓
5.5Lessons-ledger: коррекции как кодэтот рецепт ✓
6Skills: переиспользуемые SKILL.mdagent-skills-library — готово ✓
# prompts/maintain.md — gardener of the ledger; PROPOSES diffs, applies nothing silently
You are the ledger keeper. Input: all files in lessons/**.

1. DEDUP / MERGE: lessons saying the same thing -> merge into one, keep the clearest
   example, sum their seen.
2. CONFLICTS: pairs that contradict for the same scope -> mark both status: conflicting
   and surface to the human. Do NOT pick a winner yourself.
3. PROMOTION (machine over text): inferential lessons with promotable: true that are
   stable (unchanged N days AND seen >= M) -> propose a concrete boundary-linter rule /
   structural test, mark the source lesson status: promoted (archive, not delete).
4. HYGIENE: lessons with status: promoted | archived are not served on retrieve.

Output: the diffs; a health summary (active / promoted / conflicting counts);
and ABOVE ALL — how many lessons graduated into computational sensors this period.
That is the metric, not the ledger's size.
Дедуп считай по scope, а не по тексту: два урока про одну границу, сказанные разными словами, — это один урок с суммой seen. Иначе «промоушн стабильных» никогда не сработает, потому что seen размазан по дубликатам и порог M не достигается.

Результат

Фаза 5.5 собрана: твои коррекции перестали теряться в чате и стали кодом. Ты понимаешь пайплайн урока (capture → normalize → store → retrieve → maintain), главную развилку normalize (computational уходит в рельсы сразу, inferential живёт уроком и зреет к промоушну), как сделать захват без трения через /wrong, как агент сам подтягивает уроки границы по retrieve-правилу и как maintain дедуплицирует и промоутит созревшее в сенсоры. И главное — метрика здоровья: сколько уроков выпущено в барьеры, а не размер тетрадки, плюс анти-Goodhart дисциплина (никаких правил-погонь-за-процентом). Это ядро автономности серии; дальше — фазы 6–8 (skills, workflow, тиражирование), полная карта в хабе.