О чём это
Год назад я начал отдавать ИИ-агенту правки расширений боевой УТ 11.5. Не демо-базы, не копии — той самой, на которой в этот момент пробивают чеки в магазинах розничной сети. Торговля с тех пор не вставала ни разу по вине агента.
Статей про то, как ИИ пишет обработку за три минуты, сейчас много. Статей про то, что делать, чтобы эта обработка не остановила кассы, я не встречал ни одной. А вопрос ровно этот: пустить агента в песочницу не страшно, страшно пустить туда, где деньги.
Ниже — контур, который у меня выстроился за год. Он не про то, как заставить модель писать хороший код. Он про то, как сузить пространство ущерба настолько, чтобы плохой код не смог навредить.
Ключевая мысль, ради которой всё писалось: доверие к агенту — не тот параметр, которым надо управлять. Управлять надо тем, что он физически может сломать.
Почему обычные меры не работают
Первое, что приходит в голову — «пусть агент показывает изменения, я буду смотреть». Это ломается о два обстоятельства.
Объём. Агент за час выдаёт больше диффа, чем вы вдумчиво прочитаете за день. Через неделю вы начинаете просматривать по диагонали, а ещё через неделю — доверять отчёту «готово».
Отчёт врёт не по злому умыслу. Агент честно пишет «созданы, существуют» — и это правда. Просто объект создан без обязательных реквизитов, которые заполняет форма, и в интерфейсе его нет. Я ловил ровно это: мои программные проверки говорили «всё в порядке», а владелец бизнеса открыл список и не увидел записей. Проверка проверяла существование, а не пригодность.
Отсюда вывод, вокруг которого построено всё остальное: контур должен ловить не намерения, а последствия.
Слой 1: стоп-кран на необратимом
Первое и главное. Есть список операций, перед которыми агент обязан остановиться и спросить — независимо от того, насколько он уверен.
У меня список выглядит так:
DROP,TRUNCATE,DELETEбез жёсткогоWHERE;ALTERна боевой БД вне записанной миграции;- любая правка денег: балансы, начисления и списания бонусов руками, правка оплат;
- проведение, распроведение, удаление документов 1С;
LoadConfigFromFilesиUpdateDBCfgбез бэкапа расширения;git push --force,reset --hardна общей ветке;rm -rf, остановка контейнеров, пересборка образа без тега для отката;chmodиchownна системные каталоги,.env, ключи и сертификаты;- ротация любых секретов — это мгновенно ломает живые интеграции;
- выкладка мобильного приложения в стор или OTA — это прод для всех, у кого стоит приложение.
Формат вопроса зафиксирован жёстко, и это важнее самого списка:
Сейчас сделаю: <команда>
Сломает: <кто и что>
Откат: <бэкап / тег образа / migrate down>
Делать?
Три поля, и каждое работает. Команда — чтобы я видел ровно то, что выполнится, а не пересказ. Сломает — заставляет агента самому оценить радиус поражения, и на этом шаге он иногда сам передумывает. Откат — самое ценное: если агент не может назвать способ отката, значит операцию делать нельзя вообще, и разговор окончен.
И правило, без которого всё разваливается: молчание не равно согласию. Пока нет явного «да» — команда не запускается. Не «я подожду немного», не «пользователь наверняка не против».
Слой 2: изоляция контура
Если у вас одна база — пропустите. Если несколько — это второй по важности слой.
У меня на одном сервере живут базы разных организаций, плюс отдельная франшизная база, плюс несколько разных сайтов и бэкендов. Ошибка «применил правку не в ту базу» стоит дороже, чем плохой код: вы вносите чужую бизнес-логику в чужой учёт.
Правило простое: пока не назван контур — не трогать боевое. В первом же абзаце работы агент обязан написать: какая организация, какой хост, какая база, какой репозиторий.
Отдельно — список того, что нельзя переносить между контурами ни при каких обстоятельствах: константы бонусных программ, пороги, промо-механики, реферальные правила, пути публикаций, ключи подписи, секреты, SQL из чужой базы. Это выглядит очевидным ровно до того момента, когда агент «переиспользует» удачное решение из соседнего проекта.
Коварная деталь: один сервер обслуживает несколько продуктов. Имя процесса и каталог решают, чей он. Два процесса с похожими именами могут принадлежать разным организациям, и перепутать их легко.
Слой 3: песочница внутри боевой базы
Приём, который экономит больше всего нервов.
Агенту регулярно нужно что-то посмотреть в базе: содержимое регистра, структуру документа, результат функции. Единственный работающий способ выполнить произвольный код в боевой 1С на Linux-сервере без графической сессии — HTTP-метод в расширении. А это означает правку расширения и применение изменений к базе, то есть риск.
Решение: завести одно маленькое непрофильное расширение и держать все эксперименты только там.
Логика прямая. Если ошибиться в расширении, которое обслуживает кассу, встанет торговля. Если ошибиться в расширении, которое считает что-то третьестепенное и к кассам отношения не имеет, — цена ошибки близка к нулю.
У меня для этого выделено расширение с логикой одной второстепенной подсистемы: маленькое, редко меняется, ни на что критичное не влияет. Все временные диагностические методы живут там и только там. Расширение, которое кормит кассу, для экспериментов закрыто наглухо.
Дополнительное правило: токен временного метода — в константу, не в код. У меня до сих пор в боевом расширении живёт метод переотправки с зашитым в модуль токеном, потому что «уберу потом». Не повторяйте.
Слой 4: гейт компиляции
Это тот слой, который ловит собственно плохой код, и он ровно один.
/LoadConfigFromFiles не компилирует модули и возвращает нулевой код возврата на заведомо битом коде. Агент увидит успешное завершение, отчитается «применил», а ошибка вылезет либо при обновлении базы данных, либо у кассира.
Единственная настоящая проверка:
DESIGNER /CheckModules -Extension <имя> -Server
Она реально компилирует модули на сервере и выдаёт ошибки с именами модулей и номерами строк. Около минуты на среднее расширение.
Правило, зашитое в контур: между загрузкой и применением к базе всегда стоит гейт, и пайплайн останавливается, если гейт вернул ошибки. Без этого автоматизация опаснее ручной работы — конфигуратор хотя бы подсвечивает синтаксис при сохранении, а пакетный режим не подсвечивает ничего.
Отдельно проверьте на своей базе, покрывает ли гейт модули управляемых форм. У меня есть записи и о том, что покрывает, и о том, что нет. Проверяется за пять минут: подложите в модуль формы заведомую ошибку и прогоните. Знание избавляет от лишней работы на каждом выкате.
Слой 5: бэкап и путь отката
Правило: бэкап до правки, всегда, без исключений.
Технически это просто выгрузка расширения в файлы перед любыми изменениями:
DESIGNER /DumpConfigToFiles <каталог бэкапа> -Extension <имя>
Откат — обратная загрузка того же каталога. Занимает столько же, сколько выкат.
Два момента, которые делают бэкап осмысленным, а не ритуальным:
Бэкап снимается с боевой базы, а не берётся вчерашний. К расширению может иметь доступ подрядчик или второй разработчик. Свежий дамп против дампа прошлой сессии сразу показывает, менял ли расширение кто-то ещё, пока вас не было.
Каталог бэкапа именуется по задаче и времени. Через месяц вы не вспомните, что лежит в папке backup2.
Слой 6: сухой прогон по умолчанию
Любая массовая операция, которую агент делает через метод — выпуск карт, пересчёт, исправление данных — по умолчанию только считает и показывает. Реальная запись включается отдельным явным флагом.
# показать, что будет сделано
curl -X POST ".../Method" -d '{"params": {...}}'
# сделать
curl -X POST ".../Method" -d '{"params": {...}, "apply": true}'
Это дешёвая мера с огромной отдачей. Ошибиться в диапазоне номеров или в условии отбора очень легко, а разгребать сотню лишних элементов справочника — вечер работы. Сухой прогон превращает такую ошибку в строчку в выводе.
Чтение вообще — единственная категория, где агент работает без спроса: логи, SELECT, статусы, проверка конфигурации, компиляция. Всё, что не меняет состояние, разрешено по умолчанию.
Слой 7: проверка фактом, а не кодом возврата
Самый недооценённый слой. Успешное завершение команды не означает, что система в нужном состоянии.
Два случая из практики, оба стоили дорого.
Первый. Скрипт закрыл базу от пользователей, применил изменения и должен был открыть обратно. На последнем шаге оборвалось ssh-соединение. Команды «выполнились», процедура «завершилась», а база осталась закрытой — люди не могут зайти. Спасло только то, что я не поверил кодам возврата и перепроверил состояние руками.
Второй. После перезапуска сервера 1С умирает демон администрирования кластера. Скрипт восстановления дёргает его и молча ничего не делает. База остаётся закрытой, HTTP-сервисы отдают 500, а вы уверены, что всё вернули.
Отсюда правило: финальное состояние проверяется независимым способом.
# кластер считает базу открытой?
rac infobase info ... | grep sessions-deny
# база реально отвечает?
curl -u <логин> "http://127.0.0.1/<база>/hs/<корень>/status"
Ожидаем off и код 200 или 401. Код 500 означает, что база закрыта, что бы там ни говорила первая проверка.
И то же самое для кода: в базе действительно лежит то, что вы правили?
DESIGNER /DumpConfigToFiles <каталог после> -Extension <имя>
diff -r -x ConfigDumpInfo.xml <каталог патченный> <каталог после> && echo ИДЕНТИЧНО
ConfigDumpInfo.xml исключается обязательно — там служебные версии выгрузки, они различаются всегда. И сравнивайте с нормализацией переводов строк: платформа выгружает CRLF, а скрипт мог оставить LF, и вы получите «различаются» на содержательно одинаковых файлах.
Слой 8: ревью диффа, а не отчёта
Последний слой — человеческий, и обойти его нечем.
Не принимайте работу по отчёту «готово». Читайте дифф.
У меня есть фиксированный список того, что означает немедленный отказ, независимо от того, насколько красиво выглядит решение:
- расходится со спекой, планом или журналом проекта;
- секреты в коде или в логах;
- закупочные цены или внутренние данные утекли в клиентский API;
TODO, «потом доделаю», выкинутые тесты, отключённые проверки;- правки не в том репозитории или не на том хосте;
- финансовая операция не в одной транзакции;
- списание с баланса сделано чтением с последующей записью вместо атомарного условного обновления;
- нет идемпотентности там, где возможна гонка или повтор запроса.
Список короткий и проверяется быстро. Смысл в том, что он фиксированный: вы не оцениваете каждый раз заново «нормально ли это», а сверяетесь с правилами, которые написаны на трезвую голову заранее.
Что это даёт на практике
Складываем: агент может свободно читать что угодно, экспериментировать в изолированном расширении, но любая необратимая операция упирается в явный вопрос с планом отката, любой код проходит гейт компиляции, любое применение прикрыто свежим бэкапом, любое финальное состояние проверяется независимо, а дифф читает человек по фиксированному списку.
Реальный простой касс при штатном выкате — около двух минут: закрыть базу, применить, открыть. Всё остальное делается на тестовой базе и кассиров не касается.
За год работы в таком режиме — ни одной остановки торговли по вине агента. Ошибки при этом были, и немало: битый код на гейте ловился регулярно, объекты создавались без обязательных реквизитов, база один раз осталась закрытой. Просто каждая из них упёрлась в слой, который был для неё построен.
Выводы
1. Управляйте не доверием, а радиусом поражения. Вопрос «насколько хорош агент» бесполезен. Полезен вопрос «что он может сломать в худшем случае».
2. Необратимое требует явного «да» с планом отката. Если откат не назван — операция запрещена. Это отсекает большую часть катастроф ещё до запуска.
3. Гейт компиляции обязателен и он ровно один. Всё остальное молча пропустит битый код с нулевым кодом возврата.
4. Успешное завершение ничего не гарантирует. Проверяйте состояние независимым способом: дамп и diff для кода, живой запрос для доступности.
5. Заведите непрофильное расширение под эксперименты. Это самая дешёвая изоляция из возможных, и она снимает страх перед диагностикой в бою.
Ни один из этих слоёв не про искусственный интеллект. Все они — обычная инженерная дисциплина, которая была нужна и до агентов. Разница в том, что раньше её отсутствие компенсировалось медленной скоростью человека, а теперь не компенсируется ничем.
Платформа 8.3.27, УТ 11.5, сервер 1С на Linux. Контур обкатан на боевых базах розничной сети с действующими кассами.
Вступайте в нашу телеграмм-группу Инфостарт