Корпоративный AI-агент за 4 дня: что сломалось потом

Мы собрали корпоративного AI-агента за 4 дня. Вот что сломалось в последующие недели

Smith — это AI-агент, работающий внутри нашего Slack-воркспейса в daily.dev. Напишите ему сообщение — он сделает дело: выполнит запрос к BigQuery, запустит SQL против ClickHouse и Postgres, проведёт модерацию контента, сформирует отчёт по продажам, автоматизирует браузерную сессию, управляет секретами, запускает плановые задачи. По сути, у него есть доступ ко всему.

Первую версию мы выкатили внутри команды 12 марта — через четыре дня после первого коммита. Этот пост не про те четыре дня. Он про всё, что сломалось, когда к системе подключились реальные люди, — про три недели патчей, устранения уязвимостей и производственных инцидентов.

Какую проблему он решал

До Smith получить данные в daily.dev означало обратиться к дата-аналитику. Инженерам, которым нужны были логи или метрики из нескольких баз данных, приходилось писать связующий код. Продажи не могли вытащить цифры по кампаниям, не подав заявку. Узким местом никогда не были сами данные — узким местом был безопасный доступ к ним и знание, как им пользоваться.

Мы хотели это исправить, построив собственный аналог OpenClaw с безопасностью и управлением секретами, встроенными с самого начала. Мы позаимствовали главную идею OpenClaw: всё делается через сообщение. Никакого дашборда, никаких форм, никакой настройки. Скажи, чего хочешь, в Slack — получи результат.

Мы не ожидали, сколько других сценариев использования появится следом. Как только Smith научился делать запросы к базам данных, люди начали просить его модерировать спам, отбирать лучший контент, аудировать A/B-эксперименты, находить новые источники контента и формировать ежемесячные бизнес-отчёты. Система плановых задач открыла двери, о которых мы вообще не думали.

29 000 строк — в основном Codex

Smith — это 29 000 строк TypeScript и 10 000 строк тестов. Написан преимущественно Codex, с участием Claude Code. Вклад людей — навигация: написание спецификаций, ревью тестов, корректировка направления, когда агент уходил не туда. Два человека, 118 коммитов.

Об этом процессе разработки с помощью AI мы уже писали, поэтому повторяться не буду. Коротко: пишете спецификации, направляете на них кодирующего агента, проверяете тесты тщательнее, чем код, итерируете.

Стоит рассказать о том, чем трудности этого проекта отличались от предыдущих. В Huginn, нашем Linear-агенте, главным препятствием было получение структурированного вывода от LLM и управление жизненным циклом подпроцессов. В Smith главным препятствием стала безопасность. Не в смысле «мы добавили auth-middleware» — скорее в смысле «агент раз за разом находил творческие способы утечки учётных данных, и мы несколько недель гонялись за этим по кругу».

Не допускать утечки доступа

Это была самая сложная проблема в создании Smith. Не один баг, а целая категория проблем, которые продолжали всплывать в разных формах. Секреты утекали в вывод инструментов. Учётные данные из сессии одного пользователя просачивались в сессию другого. Сам агент зондировал токены, к которым не должен был иметь доступа. Каждый следующий раздел — это новое проявление одной и той же проблемы.

Секреты и редактирование

Smith управляет секретами через GCP KMS. Пользователи создают секреты с ACL, которые контролируют, кто может их использовать. Когда агенту нужен секрет для bash-команды, он проходит через резолвер, проверяющий владение, членство в группе и политики — и только потом расшифровывает. Расшифрованное значение инжектируется как переменная окружения, добавляется перед bash-командой, а затем вырезается из всего вывода до того, как LLM увидит результат. Каждое расшифрованное значение заменяется на [REDACTED] в выводе команды. Секреты сортируются от длинных к коротким, чтобы частичные совпадения не приводили к утечке подстрок.

Это покрывает прямолинейный сценарий: пользователь просит Smith вызвать API с его ключом — и ключ никогда не появляется в разговоре. Более сложные случаи описаны ниже.

Беспорядок с GitHub-учётными данными

Мы не хотели давать Smith безграничный доступ к нашей GitHub-организации. Вместо этого мы выстроили три уровня: по умолчанию — read-only общий токен, write-токен, ограниченный репозиторием памяти Smith, и per-user GitHub OAuth, при котором Smith получает ваш уровень доступа, если вы явно привязали аккаунт.

В теории — просто. На практике потребовалось 10 последовательных fix-коммитов.

Первая проблема: агент запускал команды CLI для проверки учётных данных в рамках рассуждений о том, есть ли у него GitHub-доступ. В общем рантайме, где несколько пользователей обращаются к одному процессу, это мутирует глобальное состояние CLI. Проверка авторизации одного пользователя портила сессию следующего. Мы написали санитайзер команд, который перехватывает bash-команды перед выполнением и блокирует опасные субкоманды.

Вторая проблема: утечка токенов между сессиями. Read-only токен из общего окружения просачивался в очередь пользователя, давая ему меньше прав, чем должен был предоставить его OAuth-грант. Или сохранялся write-токен предыдущего пользователя. Мы сделали инжекцию учётных данных строго per-turn и добавили детерминированный инструмент, позволяющий агенту проверять собственное состояние учётных данных, не трогая shell-окружение.

Третья проблема: агент стал изобретательным. После того как мы заблокировали очевидные команды, он начал пробовать команды инспекции окружения с grep, запускать git push как тест, порождать однострочники на скриптовых языках для чтения окружения процесса. Каждый обходной путь потребовал отдельного правила в санитайзере.

Здесь начинается мета-история. Пока я писал этот пост, Smith помогал набрасывать текст. Черновик содержал описания regex-паттернов санитайзера. Smith попытался записать их в файл через bash-heredoc. Heredoc содержал буквальные строки, которые срабатывают в правилах санитайзера — потому что пост был об этих правилах. Собственный санитайзер команд Smith заблокировал запись.

Мы перенаправили запись файла через Python, чтобы обойти bash-санитайзер. Потом Python-подход наткнулся на вторую мину: примеры команд в markdown с backtick-кавычками внутри bash-heredoc были интерпретированы как подстановка shell-команд — и эти команды реально выполнились, выгрузив настоящие переменные окружения в выходной файл. Мы заметили это немедленно, но ирония была очевидна. Система безопасности, о которой мы писали, прямо в процессе документирования продемонстрировала два собственных режима отказа.

Санитайзер — это блок-лист, который рос через производственные инциденты. Он работает. Но он не элегантен и никогда не будет исчерпывающим.

Контейнерная песочница

Все bash-команды выполняются внутри Docker-контейнера под названием smith-exec. Ubuntu 24.04, непривилегированный пользователь, с набором инструментов, которые нам заведомо нужны: клиент ClickHouse, клиент PostgreSQL, Python 3 с matplotlib, git, jq.

Переменные окружения, поступающие в контейнер, проходят через явный allowlist. Всё, что в него не входит, — отбрасывается: API-ключи, учётные данные баз данных, конфигурация KMS, всё с хоста, чего агент не должен видеть. Если нужен какой-то секрет — он проходит через систему резолвинга с проверкой ACL. Обходных путей нет.

Проблема тихой смерти

Пользователи начали сообщать, что Smith не отвечает. Они отправляли сообщение в Slack — и ничего не происходило. Никакой ошибки, никакого таймаута, просто тишина.

Процесс был живым. Systemd показывал, что сервис работает. Но event loop Node.js был заблокирован, и новые запросы не обрабатывались.

Systemd был настроен с Restart=on-failure, что срабатывает только при падении процесса. Заблокированный event loop — это не падение. Процесс просто висит: живой, но бесполезный — бесконечно.

Мы направили кодирующего агента на производственные логи и попросили найти корневую причину. Решение оказалось четырёхслойным: watchdog в worker-треде, отправляющий heartbeat из основного потока каждые 5 секунд и принудительно завершающий процесс, если 30 секунд не было ответа; таймауты запросов Fastify (11 минут для агентских запросов, 30 секунд для соединений); активные проверки состояния Caddy, опрашивающие /api/health каждые 10 секунд; и сокращённые таймауты остановки systemd. Четыре слоя — потому что ни одного недостаточно.

Если вы держите долгоживущие Node.js-процессы с агентами — добавьте watchdog до того, как он вам понадобится. Мы добавили его уже после.

Коллега, который регулярно роняет Smith

У нас есть один член команды — дата-аналитик, — который стабильно крашит агента. Не намеренно. Просто он — power user. Сложные многоэтапные аналитические задачи, большие результирующие наборы, длинные диалоговые треды, доводящие всё до предела.

Один инцидент был связан с диалогом из 170+ сообщений. Результаты инструментов содержали SQL MERGE-выражения на 25 КБ. Память агента росла без ограничений, пока VM на 15 ГБ полностью не зависла. Swap не был настроен. Упала вся машина, а не только Smith.

Решением стали ограничения памяти через systemd cgroups. Веб-сервисы получают максимум 6 ГБ. Планировщики — 4 ГБ. Exec-контейнер — 2 ГБ. Процессы, превышающие лимиты, убиваются OOM-киллером и перезапускаются чисто, а не замораживают всё вокруг.

Странный нюанс: в основном рантайме агента есть встроенная компакция диалогов — он должен справляться с длинными тредами. Но что-то в паттерне работы этого члена команды обходит её, и мы до сих пор не выяснили что именно. Дата-аналитик продолжает регулярно ронять Smith. Просто теперь мы восстанавливаемся за секунды, а не за минуты.

Прогрессивное раскрытие инструментов

У Smith около 60 инструментов. Только браузерная автоматизация — это 15. BigQuery — 6. Управление секретами — 10.

В начале все инструменты были доступны на каждом шаге. Системный промпт раздулся. Стоимость токенов выросла. Агент тянулся к тяжёлым инструментам, когда хватило бы простых, — просто потому что видел их.

Теперь Smith начинает каждый тред с 18 всегда включёнными инструментами и одним мета-инструментом, который по требованию разблокирует наборы возможностей. Когда задача требует браузерной автоматизации или BigQuery, агент активирует нужный набор. Доступно шесть наборов: браузер, cron, BigQuery, запись в BigQuery, управление секретами/политиками и мессенджинг в Slack. После активации набор остаётся включённым до конца треда.

Это сократило базовый промпт, снизило стоимость каждого шага и помогло агенту сосредоточиться. Мы также ведём лог активной поверхности инструментов для каждого вызова LLM в журнале использования, чтобы точно видеть, какие возможности задействовались и сколько это стоило.

Мозг: 25 навыков, все написаны самостоятельно

У Smith есть git-репозиторий под названием «мозг» (brain). Мы выбрали git намеренно: он даёт полную историю каждого изменения, которое Smith вносит в собственные знания, и любой в команде может просмотреть репозиторий, чтобы точно понять, с каким контекстом работает агент. Три директории: docs для справочных материалов, skills для инструкций по переиспользуемым задачам, scripts для исполняемых вспомогательных файлов.

Когда Smith узнаёт что-то в ходе разговора, он записывает это знание в мозг. Навык для обнаружения спама: делает запрос к ClickHouse на поиск подозрительных паттернов и воздействует на пользователей через наш внутренний API. Навык для формирования отчётов по продажам из данных рекламных кампаний. Один — для поиска новых источников контента через исследование веба с браузерной автоматизацией. Один — для отбора лучшего контента.

Сейчас около 25 навыков. Каждый до единого был написан Smith в ходе реальных разговоров с людьми. Никто не писал их вручную.

Cron-задача коммитит изменения мозга в GitHub и пушит их. Ещё одна нормализует права на файлы — потому что пользователь контейнера и пользователь хоста имеют разные UID, и файлы, записанные внутри контейнера, получают неверного владельца.

Мы не аудируем мозг систематически. Smith обновляет собственный контекст, и со временем повторяющиеся задачи выполняются всё лучше. Хорошо ли структурирован каждый навык и точен ли он — если честно, мы не проверяли тщательно. Система самокорректируется через использование. Это допущение, с которым мы готовы мириться, но которое не доказали.

Чем он занимается всё это время

Сценарий с доступом к данным сработал как задумывалось. Но система cron-задач превратила Smith из инструмента запросов в автономного оператора.

Каждую ночь в 3:00 Smith прочёсывает систему в поисках спама. Он делает запрос к ClickHouse для поиска подозрительных паттернов постинга, сопоставляет с пользовательскими данными и автоматически модерирует через наш внутренний API. Каждую неделю проводит аудит наших A/B-экспериментов, проверяя, есть ли для feature-флагов в кодовой базе соответствующие эксперименты в GrowthBook. Проверяет ожидающие рассмотрения ключевые слова контента. Находит новые источники, просматривая веб. Обновляет собственные навыки и документацию.

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

MCP-сервер стал поздним добавлением, оказавшимся неожиданно полезным. Несколько из нас используют Claude Code локально и хотели дать ему доступ к внутренним системам. Smith предоставляет единственный MCP-инструмент под названием ask_smith. Направьте Claude Code на эндпоинт Smith — и он сможет делать запросы к базам данных, проверять статус деплоя или запускать задачи модерации. Делегирование между агентами — через наш собственный слой безопасности и ACL.

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

Что ещё сломано

Дата-аналитик продолжает ронять Smith. В рантайме агента есть компакция, но что-то в его паттерне работы её обходит. Ограничения памяти — это временный пластырь. Корневую причину мы так и не нашли.

Санитайзер команд — это блок-лист, который растёт через производственные инциденты. Он ловит то, что мы уже видели. Называть его полным было бы наивно.

Обработка Slack-событий по-прежнему остаётся источником багов. Вложенные ответы в тредах, петли из сообщений ботов, дублирующиеся доставки событий, гидрированные payloads с ответами и отсутствующими полями. Каждая защитная проверка в обработчике событий восходит к конкретному производственному инциденту. Работает — но читать этот код — всё равно что читать changelog неожиданных сюрпризов Slack API.

Мозг не аудирован. Навыки могут деградировать. Мы ставим на самокоррекцию, а не на верификацию.

И самый глубокий открытый вопрос: как убедиться, что автономный агент с доступом к производственным базам данных, GitHub-репозиториям и браузерным сессиям не сделает ничего непредвиденного? У нас есть эшелонированная защита: санитайзер команд, allowlist переменных окружения, секреты с проверкой ACL, изоляция контейнера, инжекция учётных данных per-turn. Но нет формального доказательства. Нет гарантии, что агент не эскалирует собственный доступ способом, о котором мы не подумали. Только слои «мы пока этого не видели» и готовность добавить ещё один слой, когда увидим.

Вот где мы находимся. Работает в продакшне, команда использует ежедневно, дата-аналитик роняет его примерно раз в неделю. Назван в честь Агента Смита из «Матрицы» — потому что имена из скандинавской мифологии закончились, а это подошло идеально.

© 2026 meganuke