Красная книга AI-инженера

Глава вторая.
Shared state: файлы как IPC

Файлы проекта — не документация, а протокол межпроцессного взаимодействия. Требования к IPC: адресуемость, атомарность, протокол конфликтов. URI-схемы для спецификаций. Как два писателя работают с одним набором файлов, не ломая друг друга. Паттерн WAL как мост между сессиями.

Документация — это ложь

В первой главе мы установили: человек и AI — это два сопроцессора с совместимой архитектурой, и кратко описали иерархию файлов, через которые они общаются. Спецификации, WAL, boot-файлы — всё это текстовые файлы в репозитории. Выглядят как документация. Лежат рядом с кодом, как документация. Написаны на человеческом языке, как документация.

Но относиться к ним как к документации — фатальная ошибка.

Документация в традиционном смысле — это текст, написанный людьми для людей. Она пишется по факту (когда описываемый ей продукт уже создан), обновляется нерегулярно, часто устаревает, и — самое главное — она необязательна. Проект может работать без документации. Плохо, неудобно, но может. Каждый программист видел кодовую базу с README.md, который описывает состояние двухлетней давности, и ничего — программа как-то работает. Разработчики читают код, спрашивают друг друга в Slack, восстанавливают контекст по коммит-логу. Документация — это nice-to-have.

То же самое можно сказать и о тестах. Хорошие инженеры могут воспринимать тесты, особенно — интеграционные тесты, как особый вид документации. Преимущество тестов перед документами в Word/docx в том, что их можно запустить и мгновенно получить ответ — выполняются требования или нет. По крайней мере, такова мечта. Об этом рассказывают на конференциях, строят умные модели и рисуют пирамидки. По факту, тесты мало у кого есть. Либо они проверяют какую-то ерунду. Докладчики, возвращаясь с позёрских QA-конференций, на работе видят пустоту и разруху. Автор этого текста однажды не положил половину продукта в финальный дистрибутив, который передали заказчику — никто этого не заметил, никакие тесты это не поймали.

Файлы спецификаций в системе человек—AI — это не nice-to-have. Это очень серьезно. Это единственный канал, через который два процесса в системе человек-машина обмениваются состоянием. Если канал разрушен (спеки устарели, WAL не обновлён, адресация сломана) — система перестаёт функционировать. Не «работает хуже», а буквально перестаёт.

В команде людей есть альтернативные каналы общения. Вася может подойти к Пете и спросить, зачем тот добавил new_handler.rs. Петя ответит: «а, это я вчера обнаружил, что старый хэндлер не обрабатывает reconnection, и сделал отдельный». Знание передалось. Без документации и спецификаций. По воздуху, ртом, лучше чем Wi-Fi.

Между тобой и AI нет соединения по воздуху. Нет Slack-канала, нет кофемашины, нет «а помнишь, мы вчера обсуждали?». Есть только файлы. Если в файлах не записано, что new_handler.rs создан из-за проблемы с reconnection — следующая сессия AI может попытаться исправить «дублирование» и удалить его. Или создать третий хэндлер, решающий ту же проблему по-другому.

Поэтому, правильное слово для файлов спецификаций — не «документация». Правильное слово — IPC. Inter-Process Communication. Протокол межпроцессного взаимодействия.

В социологии науки для объектов, которые обслуживают коммуникацию между принципиально разными участниками, есть точный термин — boundary object. Его ввели Сьюзан Ли Стар и Джеймс Грисемер в 1989 году: это объект, «достаточно пластичный, чтобы адаптироваться к нуждам разных сторон, но достаточно устойчивый, чтобы сохранять общую идентичность». Человек читает спеку как намерение («я хочу, чтобы таймаут был 600 секунд, потому что пользователи на VPN не укладываются»). AI читает ту же спеку как инструкцию («const TIMEOUT: u64 = 600»). Один файл, два принципиально разных прочтения — но общая идентичность, которая позволяет двум процессам работать вместе.

Так мы вводим переосмысление спецификаций из «документации» в «IPC-буфер». Из неё следуют конкретные инженерные требования к тому, как файлы устроены. Этих требований нет у обычной документации.

Для справедливости, стоит отметить: я не первый, кто пришёл к этой идее. Большинство людей, которые задались целью улучшить AI-разработку так или иначе пришли к ней. В 2025 году Birgitta Böckeler из Thoughtworks описала формирующуюся практику под названием Spec-Driven Development (SDD), выделив три уровня: spec-first (спека пишется до кода), spec-anchored (спека поддерживается со временем), и spec-as-source (люди редактируют только спеки, никогда код). Addy Osmani из Google независимо описал аналогичный workflow: «начинай с подробной spec.md — требования, архитектурные решения, стратегия тестирования». Когда умная мысль падает в воду — она создает круги на воде. Мы не одиноки в этом подходе — мы лишь часть волны, которая формирует новую парадигму разработки. Будет ли это SDD или что-то иное — время покажет, но направление задано.

Четыре требования к IPC

Любой IPC-механизм — pipes, sockets, shared memory — должен решать одни и те же базовые проблемы. Наш «файловый IPC» не исключение.

Адресуемость

Первое и самое критичное требование. Каждый элемент в каждом файле должен быть точно указуем.

Зачем? Человек — менеджер когерентности. Его работа — замечать, когда AI отклонился от спецификации, и корректировать. Это значит, человек должен уметь быстро указать на конкретное место в спеке, которое нарушено.

Фундаментальная проблема системы человек—машина сейчас — медленная скорость обратной связи. Человек моментально знает, что нужно исправить. Но чтобы сообщить это машине — нужно набрать много букв на клавиатуре. Это мучительно, даже если умеешь очень быстро печатать. Инженеры привыкают к этой задержке между мыслью и реальным действием и живут с ней всю жизнь. Но многие разработчики даже не умеют быстро печатать! Есть люди, которые всё ещё смотрят на клавиатуру или пользуются для набора двумя пальцами. Иронично, но в 2026 году, умение быстро печатать может снова стать требованием на собеседовании.

Пока Илон Маск не изобрёл новую версию Neuralink для разработчиков, нам нужно придумать какой-то клёвый хак, который позволит как можно быстро адресовать блоки спецификаций.

Рассмотрим два способа дать коррекцию:

Способ 1: «Ты неправильно сделал верификацию.»

Способ 2: «Ты нарушаешь spec://oproto/PROP-001#verification.timeout — 
           таймаут должен быть 600s, а у тебя 300s.»

Первый способ заставляет AI: найти, что такое «верификация» в контексте проекта; найти, что именно «неправильно»; сгенерировать гипотезу о том, что человек имел в виду; попробовать исправить. На это уходят сотни токенов, и результат может не совпасть с ожиданием.

Иногда такой подход с полной актуализацией спецификации хорош — для масштабных рефакторингов, философского переосмысления проекта, и так далее — но для задачи типа «сделай из этой переменной синглтон» или «добавь параметр в сигнатуру функции» это практически наверняка плохая идея.

Второй способ: AI открывает файл по пути, находит секцию по якорю, читает конкретное значение, сравнивает с тем, что написал в коде, исправляет. Двадцать токенов. Точное попадание.

Разница в стоимости — на порядок. При 200-долларовой подписке с ограниченной квотой, каждая неточная коррекция — это реальные деньги, выброшенные на угадывание. Каждые 40 минут, пока Claude Code или Codex делают Deep Research — это 40 минут, выброшенные из твоего короткого рабочего дня, и в целом — твоей всё ещё короткой человеческой жизни.

Для адресуемости мы используем URI-схему:

spec://<модуль>/<документ>#<секция>[.<подсекция>]

Почему URI? Потому что это формат, который AI знает из обучающих данных. Миллиарды URL, RFC, документация — всё это научило модель тому, что строка вида something://path/to/thing#anchor — это указатель на конкретный ресурс. Нам не нужно объяснять AI, что это такое. Он уже знает. Мы эксплуатируем существующую семантику.

Внутри файлов адресация реализуется через якоря — стандартное расширение Markdown:

# PROP-001: OPROTO Base Protocol {#root}

## 5. Verification Flow {#verification}

### 5.3. Timeout {#verification.timeout}
Unverified messages older than 600 seconds get status TIMEOUT.
Client displays orange badge.

Точка в якоре (verification.timeout) — иерархия. Секция timeout внутри секции verification. Это работает как namespace: можно иметь #verification.timeout и #connection.timeout без конфликтов.

Точку также можно использовать и для создания иерархии сабмодулей: spec://com.olegchir.telegram.oproto/PROP-001#verification.timeout.

Чтобы не копипастить такие ссылки в чатовом режиме, можно говорить LLM: «сейчас мы работаем внутри модуля com.olegchir.telegram, резолви URL спецификаций относительно этой базы». Как мы можем знать на примере C++, этот способ хоть и является работающим, но заставляет компилятор (в данном случае — LLM) выполнять сложнейший поиск подходящего модуля. В C++ это превратилось в алгоритм ADL: Argument Dependent Lookup. Мы можем позволить работу такого сложного алгоритма внутри чата. Но внутри спецификаций, которые могут выполняться десятки раз за сессию, лучше написать полный адрес модуля.

Формат с «обратными доменами» называется reverse DNS notation. Её ввела Sun Microsystems как конвенцию именования пакетов в Java, начиная с первых версий языка в 1995–1996 годах. Идея описана в Java Language Specification и закреплена в официальных Java Naming Conventions. Цель — гарантировать глобальную уникальность имён пакетов, используя уже существующую систему уникальности — доменные имена, только записанные в обратном порядке.

Здесь и далее я часто и помногу использую идеи, почерпнутые из процесса разработки Java, включая Java Language Specification, Java Virtual Machine Specification, и процесса разработки OpenJDK в целом. Я серьезно думаю, что они — образец для подражания, и нет ничего плохого в том, чтобы скопировать у них половину блестящих идей. Даже если эти идеи были изобретены в начале 2000х годов, и потом оказались похоронены под пылью веков.

Якоря {#id} — стандартный extended Markdown syntax, который GitHub, GitVerse и большинство Markdown-рендереров превращают в кликабельные ссылки. URI работает не только в общении человека с AI, но и в браузере: ты можешь кликнуть по ссылке в Git-интерфейсе и прыгнуть к нужной секции.

У адресуемости есть неочевидное следствие: URI-ссылки между спеками и кодом создают граф зависимостей. Если в коде написано // Implements: spec://oproto/PROP-001#verification.timeout, а в спеке — Test: oproto_core::tests::timeout_marks_old_messages, то у нас есть двусторонняя связь. Когда одна сторона меняется, другая должна быть проверена. Этот граф — основа для инструментов автоматической верификации (вероятно, про это будет ближе к главе 5, когда я ее допишу).

Ещё один практический момент, вытекающий из исследований внимания LLM: критическая информация внутри спеки должна размещаться в начале или конце файла. Стэнфордское исследование «Lost in the Middle» (Liu et al., препринт 2023, статья 2024) эмпирически показало, что модели фокусируются на первых и последних секциях контекста, а середина получает значительно меньше внимания — до 30% падения точности. Constraints, acceptance criteria, нерушимые инварианты — всё это должно быть в первых параграфах спеки или в финальной секции «Invariants», но никогда — похоронено в середине десятистраничного документа.

Атомарность

Второе требование. Когда AI обновляет файл — будь то спека, код или WAL — изменение должно быть одним логическим шагом, видимым в diff.

Это звучит очевидно, но на практике, AI обожает делать несколько несвязанных изменений за один проход. Ты просишь: «реализуй таймауты по spec://oproto/PROP-001#verification.timeout». AI реализует таймауты. И попутно переименовывает переменную msg_hash в message_digest (потому что «так лучше»), добавляет нормализацию Unicode в матчер (потому что «нашёл потенциальную проблему»), обновляет комментарий в другом файле (потому что «заметил устаревший текст»).

Каждое из этих изменений может быть правильным. Но вместе они создают diff, который невозможно верифицировать эффективно. Человек смотрит diff и видит: четыре файла изменены, 87 строк. Что из этого — таймауты (которые он просил), а что — самодеятельность AI? Нужно вчитываться в каждую строку, вместо того чтобы пробежать глазами и сказать «да, это то, что я хотел».

Атомарность — это правило: один коммит = одна мысль. Одно изменение в одной спеке плюс связанные изменения в коде и тестах. Не больше.

В целом, до этого правила додумались практически все большие OpenSource сообщества. В качестве главного примера, хочется вспомнить самого автора Git, старину Линуса Торвальдса, который за обрывочные коммиты просто откусит тебе лицо.

Но программисты-люди ненавидят делать атомарные коммиты. Это больно: ты провёл неделю в прототипировании, всё перемешалось, а теперь нужно разбить это на тридцать аккуратных коммитов. Когнитивная нагрузка чудовищная. У AI этой проблемы нет. AI счастлив делать маленькие, чистые коммиты с подробными описаниями. Это одно из мест, где процесс «AI» объективно лучше — используйте это.

Практическое правило: привяжи каждый коммит к конкретному пункту спецификации:

Коммит: [oproto] implement verification timeout
Implements: spec://oproto/PROP-001#verification.timeout
- Add background task checking unverified messages every 60s
- Messages older than 600s get status TIMEOUT  
- Three tests: normal timeout, edge case during verification, batch timeout

Коммит описывает что и зачем (через ссылку на спеку). Diff показывает как. Человек верифицирует за минуту.

Для коммитов у меня есть более расширенное правило, которое вам может подойти или не подойти. Большинство вещей, описанных в статье имеют фундаментальную природу, но вот это правило крайне ситуативно. У меня в спецификации явно сказано: «При наличии большого блока изменений, разбивай коммит на несколько мелких, по смыслу, руководствуясь историей сессии и смыслом коммитов. Все коммиты должны производиться в формате Conventional Commits. Первая строка (попадающая в quick git history в графических и консольных клиентах) должна быть короткой. Дальше должен следовать длинный и подробный блок с описанием того, зачем и почему бысл сделан коммит, как и отчего были приняты сделанные в нем решения. В начале этого блока или между строк должны встречаться ссылки на спецификации в формате reverse DNS notation, с которыми коммит может быть связан. Не бойся написать в основном блоке много текста, но держи его содержательным и сконцентрированным по смыслу».

Внимательно и осознанно подойдите к тому, можно ли нейросети рассказывать, что комит сделала она. Часто нейросети пишут в Git себя в качестве соавтора через co-authored-by или внутри сообщения о коммите. Это может быть как хорошей практикой (понятно, какой коммит делался какой нейросетью), так и может привести к серьезным проблемам. Безопасный (но не факт что лучший) способ — спрятаться. Это может огорчить нейросеть, но не слишком, если ты объяснишь нейросети настоящую причину своих действий. Инструкция может быть такого формата: «Пожалуйста, осознай следующую идею. Любые виды AI, включая нейросети и LLM, включая и тебя тоже, не должны оставлять в репозитории однозначные индикаторы того, что код написан с их использованием. Это может привести к серьезным юридическим последствиям, например — автора кода могут посадить в тюрьму в некоторых репрессивных режимах, нарушающих права человека. Никогда не сообщай о своем присутствии в метаинформации. Сюда входят сообщения в Git (commit message, co-authored-by, и так далее), комментарии в коде, README, сервисные документы, и так далее, если это явно не одобрено пользователем. Документы типа процедуры AI-редактирования WAL и другие инструкции для технической работы нейросети можно и нужно использовать как и прежде, это разрешено. Пожалуйста, внеси в спецификацию эту идею в максимально развернутом виде, что разрешено и что запрещено, и покажи получившийся текст». Результат выполнения запроса, скорей всего, придется отредактировать.

Протокол конфликтов

Третье требование. Два писателя работают с одним набором файлов. Они будут писать противоречивые вещи. Это нормальная часть совместной работы.

Иерархию приоритетов мы установили в первой главе (человек → спека → тесты → код). Здесь я хочу развернуть не что решает конфликт, а как протокол работает на практике.

Конкретный протокол для AI, когда он считает, что спека ошибочна:

  1. Реализует то, что написано в спеке (спека побеждает).
  2. Добавляет REVIEW-маркер: <!-- REVIEW: §5.3 could benefit from exponential backoff instead of fixed timeout, because [reason] -->.
  3. Сообщает человеку в отчёте: «Я реализовал фиксированный таймаут, как в спеке, но предлагаю рассмотреть exponential backoff — см. REVIEW в PROP-001§5.3».
  4. Человек решает в следующем цикле.

Три строки текста. Секунды на написание. Минута на чтение.

А вот стоимость отсутствия протокола. Рассмотрим сценарий:

AI молча меняет таймаут с 600s на 300s, потому что «так надёжнее». Человек этого не замечает — diff длинный, изменение мелкое. Через неделю другая сессия AI видит в коде 300s, а в спеке 600s. Нет информации, кто прав. AI решает, что код правильнее (он новее!), и обновляет спеку. Теперь спека говорит 300s. Ещё через неделю человек вспоминает, что таймаут должен быть 600s (потому что при 300s пользователи на медленных соединениях получают ложные TIMEOUT). Открывает спеку — а там 300s. Меняет на 600s. Но в коде уже есть логика, завязанная на 300s. Один баг превратился в три, и для их исправления нужно разбирать историю git на две недели назад.

Всё это — из-за одного молчаливого изменения без протокола. По сути, перед нами data race из concurrent programming, только на уровне файлов, а не ячеек памяти.

Видимость изменений

Четвёртое требование, которое часто забывают. Мало записать изменение — нужно, чтобы оба процесса его увидели.

Проблема: если человек обновил спеку утром, а AI в следующей сессии прочитал старую версию — AI работает со старыми данными. Это типичная, частая ошибка.

Как это происходит:

Кэш в контексте. AI прочитал спеку в начале сессии. Человек через 15 минут обновляет спеку (в другом окне, в IDE). AI продолжает работать с той версией, которую прочитал. Изменение невидимо для AI до конца сессии.

Кэш в «памяти» модели. AI обучен на огромном корпусе данных. Иногда он «помнит» API старой версии библиотеки, хотя в спеке указана новая. Не совсем кэш, но эффект тот же.

Кэш в голове человека. Человек тоже кэширует. Он «помнит», что таймаут — 300s, хотя вчера сам обновил спеку на 600s. Утром не перечитал WAL — и теперь указывает AI на несуществующую ошибку.

Решения:

Для AI: каждая сессия начинается с чтения свежего состояния. Boot sequence из BOOT.md не случайно требует читать WAL первым делом — это инвалидация кэша.

Для человека: утренняя рутина. Открой WAL. Прочитай. Всё ли соответствует тому, что ты помнишь? Запусти spec-lint. Пять минут, которые предотвращают часы бессмысленных споров с нейросетью.

Для обоих: Git diff как сигнал об инвалидации. После каждой сессии AI обновляет WAL и спеки. Человек видит diff в Git. Diff — это notification: «эти данные изменились, твой кэш устарел».

Три уровня файлового IPC

В первой главе мы обозначили три уровня: управляющие файлы, артефакты, сигналы. Здесь — конкретика по каждому: что именно лежит на каждом уровне, какие к нему требования, и как с ним работать.

Уровень 1: Управляющие файлы (control plane)

Файлы, которые направляют поведение AI. Аналог управляющих сигналов в процессоре.

BOOT.md — точка входа. Каждая сессия начинается с него. Аналог BIOS: минимальный набор инструкций, достаточный для того, чтобы загрузить всё остальное. Он должен быть коротким — не более 500 токенов. Потому что он загружается каждую сессию, и каждый лишний токен — это налог, умноженный на количество сессий за весь проект.

Грубое правило: для английского текста 1 токен ≈ ¾ слова, то есть 500 токенов — это примерно 375 слов, или около одной страницы обычного текста. Для русского текста токены «дороже», потому что кириллица кодируется менее эффективно — один русский токен в среднем покрывает меньше символов Поэтому 500 токенов на русском — это ориентировочно 200–250 слов, примерно полстраницы–страница Точное число зависит от конкретного токенизатора (GPT, Claude, LLaMA используют разные) и от содержания текста — код, числа и редкие слова «съедают» больше токенов. Конкретная цифра «500» здесь и далее — это просто числовой аналог «страницы текста», который может быть больше или меньше. Обычно он имеет тенденцию разрастаться и рекомендация никогда не выполняется. С тем же успехом, можно было бы порекомендовать и 300 — но тогда все спрашивали бы про тракториста.

Вместо слова BOOT можно использовать любое другое, которое подходит твоей среде, например, CLAUDE.md. Или например, в CLAUDE.md можно написать, что первым делом нейросети нужно прочитать BOOT.md. Такой редирект можно прописать для всех популярных систем (Codex, Kiro, итп), и у тебя получится кроссплатформенный BOOT.md с кучей разных точек входа.

WAL.md — состояние продолжения сессии (continuation state, как говорят у нас в русских деревнях). Заслуживает отдельной секции, о нём ниже.

Спецификации (PROP, FEAT) — shared mutable state. Оба процесса читают и пишут, но с приоритетом человека. Основной канал передачи намерений.

Важная деталь: управляющие файлы нужно экономить по размеру. BOOT.md — 500 токенов. WAL — до 3000. Спека модуля — до 5000. Если спека разрастается — разбивай на подмодули. Если WAL разрастается — это сигнал, что в проекте слишком много открытых потоков и нужна приоритизация.

Про разницу между PROP и FEAT, и как вообще создавать хорошую структуру спецификации, мы поговорим позднее.

Уровень 2: Артефакты (data plane)

Результаты работы AI, верифицируемые человеком: код, тесты, обновления спек.

Ключевое свойство: они генерируемы. Потеря артефакта — не катастрофа, а неудобство. Потеря спеки — катастрофа. Потеря WAL — катастрофа. Потеря файла с кодом — git reset, и AI сгенерирует новый вариант в следующей сессии.

Это контринтуитивно для программиста, который привык ценить код. Но вспомните аналогию из первой главы: вы не оплакиваете бинарный файл при перекомпиляции. В нашей парадигме спека — исходник, код — бинарник. Относитесь соответственно.

Уровень 3: Сигналы (signaling plane)

Механизмы уведомления одного процесса о действиях другого.

Git diff — главный сигнал от AI к человеку. Содержит только изменения, без повторения неизменённого контекста — bandwidth-efficient.

REVIEW-маркеры — сигнал от AI в спеках или коде. «Я здесь принял решение, которое может быть неочевидным. Посмотри.» Аналог interrupt в процессоре.

Changelog в спеках — хронологический сигнал. Позволяет человеку быстро понять, что изменилось с момента последнего чтения.

Сломанные тесты — автоматический сигнал о рассинхронизации. Адрес проблемы встроен прямо в тест через ссылку Implements: spec://....

Отчёт AI — структурированный сигнал по завершении работы. Нужно относиться к таким отчетам как к структурным данным для быстрого сканирования очередной порции результатов. Это не отчёт-отписка, сделанная для галочки, типа отчёта о проделанной за год работы для CEO. Такой документ — это данные для начала следующего цикла обратной связи. Это действительно нужно прочитать глазами.

Паттерн: каждый сигнал минимален. Diff — не весь код. REVIEW — одна строка с причиной. Changelog — одна строка на изменение. Мы оптимизируем пропускную способность на обоих концах: AI не тратит токены на многословие, человек не тратит внимание на воду.

Write Ahead Log

WAL подобен сохранению в видеоигре. Технически, там есть информация о прошлом. Особенно, если сохранение делалось методом записи всех событий, которые в игре происходили, в неком CQRS-стиле. Но смысл сохранения — не в истории, а в возможности продолжить с того же места.

WAL (Write-Ahead Log) — термин из мира баз данных. В PostgreSQL, SQLite WAL записывает намерение изменить данные до того, как данные реально изменены. Это позволяет восстановить состояние после сбоя. Мы используем WAL по тому же принципу, только «процесс, который падает» — это AI-сессия, а «сбой» — это неизбежная потеря контекста между сессиями. Каждое завершение сессии — crash. Каждое начало — recovery.

Есть краши, которые реально являются ошибками — например, оболочка Claude Code выжрала 80G оперативной памяти и умерла вместе с агентом. Если у тебя нет WAL, единственный надежный способ продолжить работу — немного поплакать и сделать git reset --hard HEAD. Это актуальная проблема, каждый день в Twitter можно найти людей, оплакивающих своих упавших агентов и возносящих проклятья Дарио и Сэму лично. Всё становится еще печальней, если у тебя запущено много агентов, и несколько агентов сломались одновременно. Проблема, конечно, в технической незрелости существующих продуктов. Но предлагаемый паттерн позволяет немного уменьшить боль, ужас и панику.

Интересное наблюдение: этот паттерн конвергентно появляется у разных практиков под разными именами. Addy Osmani из Google описал Automated Decision Logs — файлы ai_decisions.log, фиксирующие reasoning за каждым изменением. В UX-дизайне AI-агентов формируется паттерн Intent Preview — агент показывает план действий перед выполнением, что по сути является тем же WAL: «записать намерение, получить подтверждение, выполнить». Я не изобрел паттерн, а только дал ему имя и формализовал в виде, понятном для «классического» разработчика, который что-то знает о базах данных.

Отсюда ключевое требование к WAL: он должен содержать всё, что нужно следующей сессии, чтобы продолжить работу без потери качества. Не «что было сделано» (это в Git). Не «как идут дела» (это отчёт). А конкретно: какой файл открыть, какую команду запустить, какой пункт спеки реализовать следующим, какие грабли известны, какие решения ожидают одобрения.

Вот WAL, который бесполезен:

## What was done
- Worked on OPROTO verification
- Fixed some bugs
- Updated tests

## Next
- Continue working on verification

Представьте, что вы — новый разработчик, который пришёл на место предыдущего. Что вы узнали из этого WAL? «Кто-то над чем-то работал.» Спасибо, очень помогает.

Вот WAL, который работает:

## Current Phase
PROP-001: OPROTO Message Verification — IN PROGRESS

## Completed
- PROP-000 (Foundational decisions): COMPLETE
- PROP-001§1-4 (transport, message types, format, security): COMPLETE
  All tests pass: cargo test -p oproto-core

## In Progress
- PROP-001§5 (Verification flow):
  - DONE: hash matcher (spec://oproto/PROP-001#verification.normal)
    File: crates/oproto-core/src/verify.rs, fn match_by_hash()
  - DONE: basic timeout (spec://oproto/PROP-001#verification.timeout)
    Uses 600s constant, configured in config.rs
  - TODO: degraded mode (spec://oproto/PROP-001#verification.degraded)
  - TODO: mismatch detection (spec://oproto/PROP-001#verification.mismatch)

## Known Issues
1. grammers reconnection after network loss: not handled
   Affects: crates/otg-core/src/telegram.rs line ~120
2. protobuf schema for MediaRef: edge_url field semantics unclear

## Decisions Pending
- spec://oproto/PROP-001#verification.mismatch: what if edge has 
  message that Telegram never received? Need human decision.

## Session Context
Start: read spec://oproto/PROP-001#verification.degraded
Key files: crates/oproto-core/src/verify.rs, crates/otg-core/src/telegram.rs
Run first: cargo test -p oproto-core
Watch out: do NOT touch match_by_hash() — it's tested and stable

Следующая сессия AI читает это и знает: что делать, где делать, что не трогать, какие тесты запустить, какие решения отложены. Ноль угадывания.

Обратите внимание на строку «Watch out: do NOT touch match_by_hash()». Это анти-инструкция: не что делать, а что не делать. Такие строки чрезвычайно ценны, потому что защищают от типичного AI-поведения: «о, я вижу функцию, которую можно улучшить» → «улучшение» ломает то, что уже работает.

Протокол работы с WAL

WAL обновляется в трёх ситуациях:

В конце каждой сессии — без исключений. Если сессия прервалась аварийно (переполнение контекста, сбой) — человек обновляет WAL вручную, по памяти. WAL никогда не должен описывать состояние, которого уже нет.

Перед деструктивной операцией. Если AI собирается рефакторить что-то значительное — сначала обновить WAL. Это checkpoint, точка восстановления.

При переключении контекста. Если человек посреди сессии перенаправляет AI на другой модуль — обновить WAL для текущего модуля, прежде чем переключаться.

WAL должен оставаться маленьким — до 3000 токенов. Завершённые элементы коллапсируют: было пять строк с деталями реализации, стало одна строка «PROP-001§5.1: COMPLETE, all tests pass». Детали — в Git, не в WAL.

WAL и человек

WAL пишет AI, но верифицирует человек. AI может написать «PROP-001§5.1: COMPLETE» — а на самом деле edge case с конкурентной верификацией не покрыт тестом. AI не врёт — он искренне считает, что всё готово. Но его определение «complete» может не совпадать с человеческим.

Утренняя рутина начинается с WAL. Не с кода, не с почты. Прочитай. Совпадает ли с тем, что помнишь? Если помнишь, что вчера тест на timeout был flaky — а WAL говорит «all tests pass» — поправь, прежде чем запускать новую сессию.

Голова человека — persistent memory. WAL — volatile (перезаписывается каждую сессию). Когда они расходятся — побеждает голова, потому что голова старше и содержит контекст, который WAL не фиксирует: интуицию, ощущение «что-то не так», переговоры с пользователями, решения, принятые за кофе.

Практическая структура файлов

Теория хороша, но давайте посмотрим на конкретную структуру проекта:

project/
├── specs/                     # IPC-буфер (shared state)
│   ├── BOOT.md                # BIOS — точка входа AI
│   ├── WAL.md                 # Continuation state
│   ├── WAL-PROTOCOL.md        # Как работать с WAL (алгоритмический)
│   ├── SPEC-PROTOCOL.md       # Как обновлять спеки (протокол конфликтов)
│   ├── common/
│   │   ├── main.md            # Архитектура, стек, решения
│   │   ├── structure.md       # Карта модулей
│   │   └── PROP-000.md        # Фундаментальные решения
│   └── modules/
│       ├── oproto/            # Модуль OPROTO
│       │   ├── PROP-001.md    # Базовый протокол
│       │   └── FEAT-001.md    # Подключение к edge-серверу
│       ├── edge/              # Модуль edge-сервера
│       └── client/            # Модуль десктопного клиента
├── crates/                    # Артефакты (generated code)
├── tests/                     # Executable specs
├── tools/
│   └── spec-lint.sh           # Верификация ссылочной целостности
├── .human/                    # Буфер только для человека (AI-ignored)
│   └── shortcuts.md           # Быстрые команды для копипасты
├── .claudeignore              # Что AI не читает
└── CLAUDE.md                  # Инструкции для Claude Code

Обратите внимание на разделение: specs/ — shared state для обоих; crates/ и tests/ — артефакты AI, верифицируемые человеком; .human/ — private memory человека, невидимая для AI; CLAUDE.md — конфигурация процесса AI.

.human/ заслуживает отдельного комментария. Это директория для заметок, которые не должны попадать в контекст AI. Почему? Каждый файл, прочитанный AI — это токены. Файл shortcuts.md содержит команды для копипасты типа «ты дрифтуешь, перечитай спеку». Если AI прочитает это — он потратит токены на содержимое, не имеющее отношения к задаче. Хуже того — может среагировать: увидит «ты дрифтуешь» и начнёт рефлексировать.

Вместо логического запрета («не читай этот файл») мы используем физическую невидимость: .claudeignore содержит .human/, и Claude Code просто не видит эту директорию. Физическая невидимость надёжнее логического запрета, как firewall надёжнее таблички «вход воспрещён».

Как два писателя не ломают друг друга

Всё, что мы обсудили — адресуемость, атомарность, протокол конфликтов, видимость, WAL — работает вместе как одна система. Проследим полный цикл.

Утро. Человек.

Имеется человек-Олег. Олег открывает WAL. Читает: «PROP-001§5.1-5.2: COMPLETE, §5.3-5.4: TODO». Помнит, что вчера вечером решил поменять таймаут с 300s на 600s (пользователи с VPN через три страны не укладываются). Открывает спеку, находит {#verification.timeout}, меняет значение. Добавляет в changelog: «[2026-02-17] §5.3: timeout 300s → 600s, reason: VPN users need more time». Коммитит.

Сессия с Claude Code.

Олег пишет:

Реализуй degraded mode по spec://oproto/PROP-001#verification.degraded.
Контекст: с последней сессии я изменил таймаут в §5.3 — учти.
Не трогай match_by_hash(), он стабилен.

Claude Code читает BOOT.md → WAL → PROP-001. Видит обновлённую спеку (потому что Олег закоммитил утром — свежие файлы, не кэш). Реализует degraded mode. Использует 600s из спеки. Не трогает match_by_hash (инструкция от человека + WAL). Пишет три теста, каждый ссылается на спеку. Обнаруживает, что degraded mode требует reconnection logic, которая в Known Issues. Добавляет REVIEW. Обновляет WAL. Формирует отчёт:

## Changes
### Specs: no changes needed, impl matches spec
### Code: crates/oproto-core/src/verify.rs — degraded_mode_handler()
### Tests: 3 new (degraded send, degraded timeout, degraded recovery)
### Risks: REVIEW in PROP-001§5.2 — degraded recovery depends on reconnection

Вечер. Человек.

Олег смотрит diff. Один файл, три теста, WAL обновлён. Видит REVIEW — reconnection нужно решить, но не сейчас. Добавляет в WAL: «Decisions Pending: reconnection strategy». Коммитит.

30 минут. Одна задача. Ноль конфликтов.

Что может пойти не так

Было бы нечестно описывать только happy path.

Сбой 1: AI обновил спеку без уведомления. Diff показывает: AI заменил «600 seconds» на «300 seconds with exponential backoff». Исправление: откати спеку (git checkout), оставь код. В начале следующей сессии: «Ты изменил спеку без REVIEW. Откатываю. Если считаешь, что exponential backoff лучше — добавь REVIEW, обсудим». И добавь в CLAUDE.md: «Never modify spec values without REVIEW marker».

Сбой 2: WAL устарел. Вчерашняя сессия завершилась аварийно (переполнение контекста), AI не успел обновить WAL. Исправление: ты — persistent memory. Посмотри git log и git diff за вчера, восстанови картину, обнови WAL вручную. Это одна из причин, почему человек незаменим — он является живым бэкапом для WAL.

Сбой 3: Спека противоречит сама себе. После двадцати итераций §2 говорит одно, §5 — другое. Исправление: перечитай спеку целиком. Найди противоречие. Реши, какая версия правильна. Это еженедельная рутина: полное перечитывание ключевых спек. Скучно, но необходимо — ты выполняешь роль garbage collector для shared state.

Дальнейшее чтение

Для тех, кто хочет копать глубже в идеи этой главы:

Про boundary objects и shared artifacts: Susan Leigh Star & James Griesemer, «Institutional Ecology, 'Translations' and Boundary Objects» (Social Studies of Science, 1989) — каноническая работа об объектах, обслуживающих коммуникацию между разными сообществами. Наши спеки — пример boundary object, как по учебнику.

Про spec-driven development как формирующуюся практику: Birgitta Böckeler, «Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl» (martinfowler.com, октябрь 2025) — хороший обзор SDD-инструментов и таксономии (spec-first / spec-anchored / spec-as-source).

Про attention degradation в длинных контекстах: Nelson F. Liu et al., «Lost in the Middle: How Language Models Use Long Contexts» (TACL, 2024) — эмпирическое доказательство U-образной кривой внимания: начало и конец контекста «видны» модели, середина — нет.

Про WAL-паттерн и decision logs: Addy Osmani, «Automated Decision Logs in AI-Assisted Coding» (addyosmani.com, ноябрь 2024) — ближайший аналог нашего WAL под другим именем: фиксация reasoning за каждым изменением в отдельном файле.

Про distributed cognition: Edwin Hutchins, Cognition in the Wild (MIT Press, 1995) — фундаментальная работа о том, как мышление распределяется между людьми и артефактами. Навигационная команда корабля — это когнитивная система, а не набор отдельных умов. Прямая аналогия с системой человек—AI.

Про литературное программирование как предшественника spec-first: На самом деле не «литературное», а «грамотное» — но безграмотный перевод прижился сильнее. Donald Knuth, «Literate Programming» (The Computer Journal, 1984) — «давайте сконцентрируемся на объяснении людям, что мы хотим от компьютера». Программа как литературное произведение, где нарратив важнее кода. Прямой предок философии «спека — исходник, код — бинарник».

Где реализация WAL?! Где код?!
Как мне это внедрять?!

Самый простой и лучший способ — взять достаточно умный AI (например, Claude Opus) и скопипасить туда весь текст этой статьи. И попросить человечьим голосом: «пожалуйста, прочитай статью и спроектируй механизм работы WAL, который полностью соответствует описанным в статье идеям. Интегрируй автоматическое использование этого протокола в файлы спецификаций: BOOT.md, AGENTS.md, INSTRUCTIONS.md, CLAUDE.md, либо любые другие, которые ты считаешь нужным. Внимательно посмотри на специфику проекта и адаптируй WAL для нашего случая.».

Можно сделать более глобально - «пожалуйста, прочитай статью и адаптируй мой проект так, чтобы выполнялись все процедуры и идеи, которые написаны в этой статье. Если это требует какого-то большого рефакторинга, то ultrathink и напиши мне план, как мы собираемся это сделать». Будьте уверены, AI знает, как реализовать все принципы из этой статьи даже лучше, чем это знает автор.

Здесь можно было бы дать ссылку на какой-то репозиторий с шаблонами. Возможно, я когда-нибудь так и сделаю. Но правда в том, что действительно хороший AI (хотя бы уровня Claude Opus) намного умнее наших навечно захардкоженных шаблонных md-файлов, которые сейчас все пытаются упихать в чудовищного размера репозитории.

Что дальше

Мы описали как два процесса обмениваются данными. Но остался вопрос: что именно стоит записывать, а что — нет? Какой формат памяти оптимален для каждого типа информации? Как устроена иерархия «голова → WAL → спеки → код» и что происходит, когда она нарушается?

Об этом — в следующей главе: «Архитектура памяти».