Как строить Иву: принцип колеса
Это метод проекта — что становится скиллом, что кодом и почему именно так. Файл
читают перед правкой и ещё раз при ревью: против него судится каждый дифф
(ADR-0004). Термины — по CONTEXT.md, принятые решения — в docs/adr/.
Одно правило держит остальные. Если проблему можно решить скиллом — решаем скиллом. Код пишем только там, где нужна детерминированность. Принцип колеса: чем меньше в системе трущихся деталей, тем дольше она едет.
1. Скилл или код
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Начинай новую задачу с вопроса «а не скилл ли это?» | Спрашивай до первой строки кода, на каждый issue и каждую фичу | Повторяющийся ответ в этой кодовой базе — скилл, то есть markdown-файл, который читает агент, а не новый механизм в TypeScript |
| Надо думать, адаптироваться, уточнять — скилл | Суждение, интерпретация, уточнение намерения и процедура живут в скилле | Суждение не раскладывается на ветвления; попытка даёт захардкоженную эвристику там, где надо было прочитать текст и подумать |
| Одинаковый вход всегда даёт одинаковый выход — код | Сюда же lookup, список и статус-чек | Модель рассадит восьмерых гостей за столом, а на восьмистах напишет правдоподобную чушь |
| Поведение зависит от контекста разговора — скилл | Ход модели этот контекст уже несёт | Коду тот же контекст пришлось бы передавать целиком и руками |
| Скилл описывает КАК, а не ЧТО | Конкретику подставляет вызов: один скилл с разными параметрами — разные способности | Markdown здесь не документация, а код: процедура записана на языке, на котором модель и так думает |
| Harness держи тонким | Harness — программа, которая гоняет модель: цикл, файлы, контекст, безопасность. У нас это eve плюс минимальный слой интеграции — Channel, Bridge, несколько корневых инструментов | Жирный harness с десятками tool-обвязок съедает половину контекстного окна: втрое больше токенов, втрое больше латентности, втрое больше отказов |
| Ценность держи в скиллах | Harness только доносит скилл до модели | Скилл — файл: его видно, его легко поправить, он не ломает чужие установки |
Примеры из своей же кодовой базы.
| Механизм | Сторона | Почему |
|---|---|---|
iva doctor | Код | Диагностика установки на одном и том же состоянии выводит одно и то же |
| Bridge | Код | Long-polling обязан работать, когда агент занят или завис |
| Очередь ходов, миграции юнитов | Код | Порядок и атомарность, причём на чужих машинах без человека рядом |
| Brain | Код | Бэкап, граф ссылок и Alert — один вход всегда даёт один результат |
| Rollup | Код + 4 коротких скилла | Модель судит о смысле дня, а код держит порядок, схемы, CAS, записи и Ceiling |
| Спавнер Schedule | Код | Запуск по времени обязан быть точным; он запускает единый кодовый memory-night |
2. Что живёт только в коде
Детерминированный код — территория доверия: одинаковый вход даёт одинаковый выход. Пять свойств markdown не гарантирует никогда.
| Свойство | Ключевой момент | Почему markdown не может |
|---|---|---|
| Идентичность события и дедупликация | Один и тот же апдейт обрабатывается ровно один раз | Текст не помнит, что уже видел, а сравнение ключей — операция, и её кто-то должен выполнить |
| Атомарность, локи, состояние | Запись либо целая, либо её нет; параллельные ходы не топчут друг друга | После краша нужен точный протокол восстановления, а не пересказ намерения |
| Точное время | Расписания, таймеры, таймауты, дроссели | Модель время оценивает, а часы его измеряют |
| Безопасность, лимиты, деньги | Allowlist, Gate, потолки расходов | Инструкцию можно переспорить словами, код нельзя, а цена промаха здесь чужая |
| Доставка | Наружу всё уходит через Outbox, внутрь — через inbound pipeline | Один шов оставляет одно место, где проверяется каждое сообщение |
Код зарабатывает своё место доверием: очереди, миграции, безопасность, деньги. Всё остальное — скилл.
3. Плагин — единица расширения
Цель владельца: один агент служит одному человеку, а расти умеет сам, чужими руками и без форка (ADR-0008).
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Расширяй Иву плагином | Не правкой Authored tree и не форком репозитория | Папку можно принести откуда угодно и унести куда угодно, а правка ядра остаётся у того, кто её сделал |
| Бери формат Agent Plugins | Папка с plugin.json, рядом skills/ и mcp.json | Готовый стандарт даёт плагины соседних экосистем даром, а свой стоил бы спеки, валидатора и изоляции от чужих плагинов |
| Код плагина — eve Extension или MCP-сервер | Чего плагинам не хватает в eve (решающие хуки, экраны без хода модели), дописываем сами — в eve или рядом с ней | eve даёт только хуки-наблюдатели; ждать upstream — оставить человека без функции (ADR-0023) |
| Ставь плагин в Custom layer | Harness при этом не меняется | Оттуда плагин переживает iva update, и его установка не может сломать install (ADR-0003) |
| Ставь из git-URL или локальной папки | Реестра нет и не планируется | Центральный список требует модерации, доверия и живого мейнтейнера, а даёт одно удобство поиска |
| При совпадении имён побеждает ближний слой | Решение предварительное, утвердим его вместе с реализацией | Так уже работает eve: одноимённый тул потребителя выигрывает у тула eve Extension |
| Skill — содержимое плагина, а не единица поставки | Ставят и удаляют плагин целиком; скилл внутри знает, как делать работу | Одно понятие — одно слово, поэтому слово «расширение» как термин запрещено обоим (CONTEXT.md) |
Принятый риск: bash внутри скилла плагина видит env процесса со всеми ключами инсталляции. Владелец отложил ступень 2 ADR-0005, поэтому чужой плагин получает токены вместе с папкой. Пиши это прямо в доке установки и в SECURITY.md.
4. eve правим сами: решает опыт человека
Мерило одно: у человека всё работает. Человек здесь — владелец Ивы, тот, кто её ставит и обслуживает, разработчик и сама модель за работой. Решение принимается по их опыту, а не по тому, чей это код (ADR-0019). eve мы пользуемся и правим её так, как нам нужно; upstream не ждём (ADR-0023).
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Сначала бери то, что eve уже делает | Проверь запуском, а не по памяти | Готовый механизм уже проверен и обновляется без нас; копия рядом — лишняя трущаяся деталь |
| eve мешает — правь eve | Правка — обычный коммит в eve/ вместе с кодом Ивы; upstream не ждём | Человеку всё равно, чей это дефект и чьи сроки; ему нужен ответ (ADR-0023) |
| Своё решение — один механизм в одном месте | Общий слой для всех провайдеров, не правка на вендора | Заплатка на случай возвращается следующим случаем |
| Свою правку eve держит тест | Тест называет поведение eve, которое мы поменяли | Новая версия eve, которая вернёт старое поведение, покажет красный тест, а не тихий двойной механизм |
| Новую версию eve бери, когда она что-то даёт | Обновление — наше решение, а не долг; issue в upstream — по желанию, ответа не ждём | Каждое обновление — перенос наших правок и прогон тестов границы; это цена свободы (ADR-0023) |
| eve пришла к нашему решению — сними своё | Условие снятия записано рядом с решением | Два механизма на одно поведение спорят друг с другом |
| Готовую гарантию systemd-таймера бери напрямую | Второй планировщик для неё не нужен | Гарантия уже есть и уже проверена; свой планировщик — та самая деталь, которая сотрётся |
5. Когда добавлять механизм
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Общий механизм заводи после двух реальных независимых потребителей | Один потребитель — это функция, а не механизм | Обобщение по одному случаю угадывает будущее и обычно угадывает мимо |
| Баг закрывай дизайном, а не одним тестом | Сначала измени дизайн так, чтобы весь класс таких ошибок стал невозможен. Тест подтверждает эту гарантию, а не заменяет её | Тест ловит один случай, дизайн закрывает всё семейство |
| Лимит, порог и настройку держи одной константой в одном файле | Все потребители — код, промпт, кламп, дока — читают одну точку; копий и хардкода нет, правка поведения системы = правка одной строки | Копии разъезжаются молча: продублированный лимит после первой правки живёт несколькими разными жизнями (CORE_CAP — эталон: один файл, три потребителя) |
| Считай новый механизм новой трущейся деталью | Её надо тестировать, ревьюить, мигрировать у сотен чужих инсталляций и когда-нибудь чинить | Каждая новая деталь в коде — та, что сотрётся: система должна оставаться простой настолько, чтобы её чинил обычный механик в поле (KISS Келли Джонсона) |
| Сначала спроси, что это уже даёт бесплатно: git, модель, существующий шов | Снимок, копия «на случай отката», сторож и проверка не заводятся, пока не проверено, что историю уже хранит git, решение примет модель по инструкции, а шов уже есть | Гениальное решение простое. Ночь держала CORE в памяти процесса и объявляла «после сбоя не восстановить», хотя каждая правка vault уже была коммитом: откат — git show, а модели достаточно строки «сломалось — смотри историю» (T96, 26.09.2026) |
Запрещено. Каждый пункт — механизм, который уже пробовали.
| Запрещено | Почему |
|---|---|
| SQLite ради одной карты | Движок ради одной записи придётся мигрировать, а файл справляется сам |
| Generic delivery-подсистема | Доставка уже есть, и это Outbox; вторая заводит второе место, где сообщение теряется |
| Локальный fallback провайдера | Тихая подмена модели прячет отказ вместо того, чтобы его показать |
| Парсинг внутренностей eve снаружи | Нужно поведение внутри eve — правь eve (ADR-0023); кто читает кишки снаружи, ломается на каждом обновлении |
| Удаление по mtime | Время файла врёт после копирования и восстановления, а платят за ошибку данными пользователя (ADR-0002) |
| Blacklist как security-граница | Список запрещённого закрывает только то, что автор вспомнил |
| Исправление дрейфа документации кодом | Доку чинит правка доки; код поверх неё лишь прячет расхождение |
| Снимок файла в памяти процесса ради отката | История vault — это git: каждая правка — коммит, откат — git show; копия в памяти умирает вместе с процессом и рождает ложный остаток «не восстановить» |
6. Тесты
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Парсер, валидатор, резолвер и инвариант состояния покрывай property-тестом | fast-check: генератор перебирает вход, свойство держит инвариант | Эджи живут там, куда фантазия автора не дошла: юникод-мусор, пустая строка, гонка, вырожденное состояние |
| Провал обязан печатать seed | Тот же seed воспроизводит провал | Случайный тест без реплея шумит вместо того, чтобы показать поломку |
| Example-тест оставляй якорем | Контракт, формат сообщения, известная регрессия | Якорь читается человеком и держит ровно то, что обещано наружу |
| Входных случаев больше десятка — бери генератор | Перечисление на этом объёме уже неполное | Список растёт линейно, пространство входов — нет |
7. Контекст
| Шаг | Ключевой момент | Почему |
|---|---|---|
| Считай узким местом контекст, а не интеллект модели | Модель ломается на незнании схемы данных, путей и наших слов, а не на сложности задачи | Вся работа вокруг агента — дать правильный контекст в правильный момент и не утопить модель в шуме |
| Держи инструкции указателями | AGENTS.md и памятки ссылаются на документы, а не содержат их | Двадцать тысяч строк накопленных заметок в системном промпте размывают внимание модели быстрее любого слабого чекпойнта |
| Правила владельца - отдельный живой файл | Встроенная инструкция - карта; правила пользователя лежат рядом и читаются с диска каждый ход | Правка встроенного файла замораживает его на дне правки и прячет всё, что приедет релизом |
| Роутинг контекста поручай description скилла | Написал хорошее description — построил резолвер | Модель сама сопоставляет запрос с описанием и подгружает процедуру тогда, когда она понадобилась |
| Память развивай как суждение, а не как поиск | Прочитать пятьдесят документов и написать одну страницу: что субъект говорит, что делает на самом деле, что изменилось и где противоречие | Такой страницы не даёт ни SQL-запрос, ни поисковая обвязка; Vault и ночной Rollup — ровно эта механика |
Способность модели прочитать много и написать страницу суждения мы называем диаризацией. Слово называет способность модели; ночной процесс зовётся Rollup.
8. Одноразовой работы нет
Задача, которая встретилась один раз, встретится снова.
| Шаг | Ключевой момент | Почему |
|---|---|---|
| 1. Сделай руками на нескольких примерах | Покажи результат владельцу | Процедуру не запишешь, пока не увидишь, как она ломается на живых данных |
| 2. Одобрено — оформи скиллом | Скилл держит процесс, конкретику подставит вызов | Файл переживает разговор, а разговор забывается |
| 3. Должно происходить само — повесь на Schedule или systemd-таймер | Ночной ход запускает тот же скилл | Работа без человека рядом — единственный способ накопить систему |
Тест: если об одном и том же пришлось попросить дважды — система провалилась.
Оформленный скилл — необратимый апгрейд: он не деградирует, не забывается и умнеет с каждой новой моделью. Латентные шаги умнеют вместе с моделью, детерминированные остаются надёжными. Система накапливается: собрал один раз — работает всегда.
9. Формальная проверка: TLA+
Решение владельца 26.09.2026. Тест проверяет примеры, спека TLA+ перебирает все порядки событий.
Там, где два процесса делят один файл, замок или очередь, тестам мало: гонку, которую автор не
вообразил, тест не поймает. Спеки и их инварианты лежат в specs/.
| Шаг | Ключевой момент | Почему |
|---|---|---|
| У модуля с гонкой есть спека и список инвариантов | Замок fs-atomic, напоминания reminder-tick, ночные задания schedule-runner: specs/<модуль>.tla и .cfg рядом с кодом | Инвариант «в критической секции один держатель» или «напоминание приходит один раз» доказывается перебором состояний, а не рассуждением в комментарии |
| Рассуждение о гонке живёт в спеке, не в комментарии | Комментарий «здесь поздний release не снимет чужой замок» становится строкой инварианта, которую проверяет TLC | Комментарий никто не перепроверяет после правки; спеку гейт перепроверяет при каждом изменении файла |
| Изменил охраняемый код — гейт гонит модель | Контрпример TLC красный: в отчёте порядок шагов, который ломает инвариант; код изменился, а спека нет — тоже красный | Та же логика, что у тестов: доказательство должно быть свежим относительно кода |
| Контрпример сначала становится тестом | По шагам контрпримера пишется падающий тест на этот порядок событий, только потом чинится код | Тест держит найденную гонку после того, как спека уже доказала исправление |
| Инвариант нельзя ослабить в одном коммите с кодом | Убрать или смягчить инвариант ради зелёного гейта — тот же обход, что удалить тест | Агент чинит код, а не проверку |
Из доклада Лорен Тан (2 500 PR за месяц) взяты правила, которые ложатся на этот раздел и на «Когда добавлять механизм»:
| Правило | Как применяем |
|---|---|
| Агент сам доказывает, что работа верна; доверие растёт только от этого | Приёмка требует доказательство запуском: смок, замер, контрпример или его отсутствие, а не «тесты зелёные» |
| Ошибку закрывать на самом сильном уровне: код → статический анализ → правила и скиллы → стайлгайд | Поправил агента дважды на одном и том же — пиши правило линтера или меняй код так, чтобы ошибка стала невозможной; строка в правилах — последнее средство |
| Садовник и один проторенный путь | Обходной путь с комментарием «временно так» расходится по коду как вирус; его останавливают правилом сразу, вычищают потом |
| Правильность и качество — разные вещи | Правильность даёт проверка (смок, спека), качество дают скиллы с опытом; одно другим не подменяется |
| Скилл проверки = готовая команда плюс карта фич | Команда запускает приложение и снимает замер одинаково каждый раз; карта фич говорит агенту, как дойти до экрана и что считать успехом |