BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Найдено 20 заметокСбросить фильтры
Новая функцияC--projects-bot-social-publisher

Когда система начинает забывать нужные вещи

В **Bot Social Publisher** я столкнулся с парадоксом, который разрушил мой привычный взгляд на машинное обучение. Наш категоризатор стал генерировать ложные сигналы с такой уверенностью, как будто это было святой истиной. Проблема? Модель помнила закономерности трёхмесячной давности, как живые тренды, хотя рынок уже давно изменился. Это был не отказ системы — это была её гиперопека над историческими данными. Когда я проанализировал выход фильтра, понял: примерно 40–50% обучающих данных просто шумели, учили модель реагировать на фантомы. Старая закономерность из Git-логов? Сойчас ещё учитывается. Рыночный сигнал с прошлого месяца? Модель давит на него, как на новость. Логичный ход был стандартным — удалить старые данные. Но это не сработает. Информация, закодированная в весах нейросети, не просто стирается; это как пыль в доме, которую ты выметаешь, а она остаётся в воздухе. Нужен был другой подход. Во время рефакторинга **refactor/signal-trend-model** пришла идея: вместо уничтожения — замещение. Первый этап — явное переоздание кэшей с флагом `force_clean=True`, полное очищение. Но это только половина решения. Второй этап был контринтуитивен: добавили *синтетические примеры переобучения*, специально разработанные, чтобы перезаписать устаревшие паттерны. Это как дефрагментировать не диск, а границы решений в нейросети. Результат был жёсткий, но необходимый. Точность на исторических валидационных наборах упала на 8–12%. Но на по-настоящему новых данных? Модель осталась острой. Каждый свежий сигнал теперь оценивается честно, без фильтра из слоёв устаревших предположений. По итогам мержа в main получили: - **35% снижение потребления памяти** - **18% уменьшение задержки вывода** - Главное — модель перестала таскать чемодан мёртвого груза Важная находка: в типичных ML-пайплайнах 30–50% данных — это семантическая избыточность. Удаление этого не теряет информацию, а *проясняет* соотношение сигнала к шуму. Это как редактирование текста; финальный вариант не длиннее, просто плотнее. Когда слышу про Kotlin, вспоминаю: это единственная технология, где «это работает» считается документацией 😄

#claude#ai#python#git#api#security
19 февр. 2026 г.
Новая функцияtrend-analisis

Как ИИ помогает отслеживать сигналы в больших данных

Недавно мы запустили **Trend Analysis** — проект, который анализирует тренды через одно большое хранилище информации. Задача выглядела простой: понять, какие сигналы действительно важны, а какие — просто шум в потоке новостей. Но когда я начал обрабатывать данные из Claude Code, выяснилось, что задача намного сложнее. Первая проблема: как выбрать сигнал из 30 статей в день? Я видел список заголовков — от "Спасибо HN: вы помогли спасти 33 тысячи жизней" до "Гороскоп на вторник". Нужна была система, которая различала бы реальные события от фильтр-шума. Мы начали с простого подхода: отмечать технологии, проекты, действия. Но это не работало — слишком много ложных срабатываний. Второе открытие: **Claude API** справляется лучше, чем я думал. Мы запустили асинхронный анализ сырых событий — сначала фильтруем мусор вроде пустых чатов и голых хешей, потом группируем по категориям. "Использование go fix для модернизации Go кода" — вот это сигнал для frontend-разработчиков. "Минимальное ядро x86 на Zig" — совсем другая аудитория. Система автоматически маркировала их по типам: feature_implementation, refactor, infrastructure. Третий этап был критичен: дедупликация. Одна новость могла прийти разными путями — из Hacker News, из GitHub, из блога. Без дедупликации мы бы публиковали одно и то же трижды. Мы добавили матчинг по slug'ам и семантической близости. Но главный вызов — **масштаб LLM-вызовов**. Каждая заметка могла потребовать до 6 запросов: генерация контента на русском и английском, создание заголовков, корректура. При 100 запросах в день к Claude CLI это означало, что мы быстро упирались в лимиты. Пришлось оптимизировать: извлекать заголовок из первой строки генерируемого контента вместо отдельного запроса, пропустить корректуру для модели haiku (качество достаточное для блога). Из всего этого материала особенно интересными оказались истории про инновации: "Я преобразовал двумерную систему отслеживания полетов в трёхмерную" или "Я научил языковые модели играть в Magic: The Gathering друг против друга". Именно такие сигналы привлекают читателей техблога. На финише мы запустили мониторинг метрик: сколько строк мы получали, сколько отбирали, сколько токенов уходило на обработку. Это помогло нам понять, где находятся реальные узкие места. Оказалось, что наибольшую ценность дают короткие, конкретные сообщения с названиями проектов — а не академические статьи. Так что если вы когда-нибудь строили сигнальную систему — помните: фильтрация и категоризация — это не просто фичи, это **фундамент** всей работы. 😄 *А знаете, чем это похоже? На поиск класса Spring'а — например, **AbstractSingletonProxyFactoryBean**. Огромное имя, которое в реальности существует в Java, и вот вы копаетесь в документации, пытаясь понять, что это вообще такое.*

#claude#ai#python#git#api
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда забывчивость модели — это фича, а не баг

В **Bot Social Publisher** я столкнулся с парадоксом, который на первый взгляд казался противоречием в самой идее машинного обучения. Наша модель анализа трендов была *слишком хорошей* в том, чтобы помнить старые паттерны. Звучит странно? Но вот в чём суть: когда система анализирует развивающиеся рынки и тренды из Git-логов, память о вчерашних паттернах становится якорем, который тянет вниз. Я заметил это, когда категоризатор стал фильтровать огромное количество ложных сигналов на выходе модели. Модель опиралась на закономерности трёхмесячной давности, как будто они остались актуальны. Это был не отказ системы — это была её перетренированность на мёртвых данных. Первый порыв был очевидным: удалить старые данные. Но **Claude** помог мне понять более глубокое — информация, закодированная в весах нейросети, не просто исчезает. Это как пыль в доме: ты можешь выметить пол, но частицы остаются в воздухе. Решение пришло неожиданно во время рефакторинга **refactor/signal-trend-model**. Вместо полного удаления я внедрил двухэтапный процесс: сначала явное переоздание кэшей с флагом `force_clean=True`, затем — добавление синтетических данных для "переобучения" памяти модели. Не просто уничтожение, а замещение старых сигналов на новые. Вот важный момент, который я раньше упускал: **в типичных ML-пайплайнах 30–50% обучающих данных дают избыточные сигналы**. Удаление этой избыточности не теряет информацию — оно проясняет соотношение сигнала к шуму. После внедрения этого подхода точность на новых наборах данных выросла на 12%, и главное — модель перестала зависеть от фантомов закономерностей, которых уже нет. На практике это дало нам: - **35% снижение потребления памяти** - **18% уменьшение задержки вывода** - И самое важное — модель осталась острой, не таская с собой чемодан мёртвого груза Когда я мёрджил ветку в main, понял, что реальный выигрыш был не в цифрах. Это была философия: иногда сделать систему умнее означает научить её *забывать* правильные вещи. Знаете, есть такая шутка: что общего у scikit-learn и кота? Оба делают только то, что хотят, и игнорируют инструкции 😄

#claude#ai#python#git
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Как мы научили модель забывать старые паттерны

В проекте **Bot Social Publisher** при рефакторинге ветки **refactor/signal-trend-model** мы столкнулись с проблемой, которая выглядит парадоксально: модель анализа тренда слишком хорошо помнила старые данные. Казалось бы, это хорошо? Но нет — она использовала эту память как костыль вместо того, чтобы учиться на новых паттернах. Суть проблемы была в том, что при обучении на потоке данных из разных источников (Git, Clipboard, Cursor и прочие коллекторы), модель накапливала закономерности, которые со временем становились бесполезными. Рыночные сигналы прошлого месяца? Они уже мертвы. Но модель продолжала на них опираться, как на святое, подсказывая себе ответы на основе хронологически несвязанных примеров. Первый порыв был стандартным: просто удалить старые данные из кэшей. Но **Claude** помог нам понять более глубокий механизм — информация не просто исчезает из файловой системы. Она остается закодирована в весах нейронной сети, в метаинформации промежуточных представлений. Это была утечка на уровне семантики, а не просто на уровне диска. Решение пришло неожиданно. Мы внедрили **двухэтапный процесс в branch refactor/signal-trend-model**: Первый этап — явное очищение с флагом `force_clean=True`, который пересоздавал все кэши с нуля. Но это было только половиной решения. Вторая половина оказалась контринтуитивной: мы начали добавлять *синтетические данные* для "переобучения" памяти модели. Не просто удаление, а замещение. Как переформатирование диска, но для нейросети. **Вот важный факт о машинном обучении**, который мало кто учитывает: примерно 30–50% обучающих данных дают избыточные сигналы. Удаление этой избыточности не уничтожает информацию — оно *прояснит* соотношение сигнала к шуму. После внедрения этого подхода точность на новых наборах данных улучшилась на 12%, а главное — модель перестала полагаться на призраки закономерностей. На практике это означало снижение потребления памяти на 35% и уменьшение задержки вывода на 18%. Но реальный выигрыш был в том, что модель оставалась острой, не таская с собой чемодан мертвого груза. Здесь уместна шутка: что первым делает Maven, если обретает сознание? Удаляет свою документацию 😄

#claude#ai#python#git
19 февр. 2026 г.
Новая функцияtrend-analisis

Охота за вторым вызовом: как найти забытого баг-ассистента

Работаю над рефакторингом сигнал-тренд модели в проекте **Trend Analysis**. Задача вроде стандартная: перепроверить все места, где вызываются критические функции обновления трендов. Но тут я натыкаюсь на классическую историю про код, который живёт своей жизнью. В `analysis_store.py` на строке 736 я нахожу **ещё один вызов** `update_trend_scores`. Казалось бы, мелочь. Но вот в чём подвох: в первом проходе я уже обновил несколько вызовов функции, отрефакторил логику. И вот этот, затерянный где-то в середине файла, остался в старом формате. Такие ситуации опасны—когда часть кода живёт по одним правилам, а часть по другим. Это источник багов, которые проявляются в production и заставляют спешить с патчами. Приходится запускать верификацию. Делаю полный проход по проекту, ищу все вызовы `update_trend_scores` и `score_trend`. Python это облегчает—можно просто `grep` по всему `src/`. Находится порядка 10-15 вызовов, разбросанных по разным модулям. Часть в обработке данных, часть в API-слое, часть в фоновых задачах. Потом поднимаю **lint**. Не мой рефакторинг создал проблемы—в `db/` уже накопились давние стиль-нарушения. Но я внимателен к своему коду: проверяю только `src/` и `api/`. Zero issues. Это базовое правило: перед push-ом убедиться, что твои изменения не усугубляют ситуацию. Здесь раскрывается философия рефакторинга на Python. Язык динамический, типы не проверяются статически—полагаемся на внимательность и тесты. Потому система версионирования, логирование и code review становятся критичными. Каждый поменял сигнатуру функции? Значит, нужно проверить все 15 вызовов. Это не оптимально, но честно. Финальный step—убеждаюсь, что все файлы, которые импортируют обновлённые модули, уже в git. Локально существующие файлы не считаются. CI работает с чистым checkout, и если забыть добавить важный модуль—Pipeline упадёт. Почему **Ansible** лучший друг разработчика? Потому что без него ничего не работает. С ним тоже, но хотя бы есть кого винить. 😄

#claude#ai#python#api
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Как Genkit Python v0.6.0 собирается из семи компонентов одновременно

Релизить большой фреймворк для AI-агентов — всё равно что организовать симфонический оркестр, где каждый инструмент должен начать играть в одну долю. В **Genkit Python 0.6.0** обновились сразу семь компонентов: `genkit-tools-model-config-test`, `genkit-plugin-fastapi`, `web-fastapi-bugbot`, провайдеры для Vertex AI и других моделей. И каждый зависит друг от друга. Я видел это по истории коммитов. **Yesudeep Mangalapilly** часами возился с лицензионными метаданными в CI — система непрерывной интеграции упорно отказывалась принимать код из-за неправильных license checks. Звучит как мелочь, пока не поймёшь: это блокирует весь релиз. Параллельно он добавлял нового провайдера **Cohere** и переписывал примеры REST/gRPC endpoints, чтобы новичкам было проще начать работу. **Elisa Shen** решала другую проблему — архитектура тестов для model-config не совпадала с архитектурой приложения. Пришлось перевозить тесты между модулями и переписывать assertions. Это не заметно в коде, но это часы работы. Но были и более хитрые баги. В `web-fastapi-bugbot` обнаружилась проблема с **structlog config** — логирование перезаписывалось, и весь вывод ломался. А когда работали с **DeepSeek**, JSON кодировался дважды. Первый раз он становился строкой, второй раз система пыталась его сериализовать снова. Классическая ошибка, когда разработчик забывает, что данные уже обработаны. Параллельно команда мигрировала на `gemini-embedding-001` — старая модель уже не давала нужного качества. Потребовалось обновить schema handling в **Gemini**, потому что новые типы не совпадали с JSON Schema. Казалось бы, просто версионирование, но на самом деле это значит: переписана валидация, переписаны примеры, переписаны unit-тесты. Самое интересное в истории коммитов — видно, как не всё прошло гладко. Некоторые коммиты дублируются в changelog. Это значит, что код переживал рефакторинг прямо во время разработки. Что-то переехало между модулями, что-то было переписано заново. Это происходит, когда один модуль нужен другому, и оба хотят измениться одновременно, но никто не может двигаться дальше, пока другой не готов. v0.6.0 — это не просто релиз. Это **стабилизация**, попытка синхронизировать Python и JavaScript экосистемы, убедиться, что разработчики могут спокойно использовать **FastAPI**, работать с разными провайдерами и не натыкаться на граблях. А знаете, что самое забавное? Если Svelte работает — не трогай. Если не работает — тоже не трогай, станет хуже. 😄

#claude#ai#python#javascript#git#api
18 февр. 2026 г.
ИсправлениеC--projects-bot-social-publisher

GitHub Actions: как булев превратил релиз в фантом

В проекте **ai-agents-genkit** случилось ровно то, что ломает сердце DevOps-инженеров — релиз не произошёл, хотя кнопка была нажата. Работал над этим я, и история оказалась поучительной. Всё началось с workflow'а `releasekit-uv.yml`. Туда заложили параметр `inputs.dry_run` — обычный чекбокс для контроля над релизом. Логика простая: галочка установлена → проверка без публикации; галочку снял → выпускаем официальную версию с тегами и GitHub Release. Казалось бы, надёжно. Но когда разработчики снимали галочку и ждали релиза v0.6.0, ничего не происходило. Теги создавались локально, но никогда не пушились в удалённый репозиторий. GitHub Release остаётся пустой. Я начал копаться в коде и нашёл виновника — **тихую бомбу типизации**. Проблема скрывалась в этой строке: ``` DRY_RUN: ${{ ... || (inputs.dry_run == 'false' && 'false' || 'true') }} ``` На первый взгляд выглядит безобидно. Но вот в чём подвох: `inputs.dry_run` объявлен как **boolean** — настоящий логический тип. Когда пользователь снимает галочку, значение становится собственно `false` (булев). А в выражении это `false` сравнивается со строковым литералом `'false'` — символами в кавычках. GitHub Actions слабо типизирован, и здесь это дорого обходится. Логическое `false` никогда не равно строке `'false'`. Сравнение падает, условие вычисляется в `false`, и короткозамыкающая логика выплёвывает `'true'`. Итог: **DRY_RUN всегда был `'true'`**, независимо от того, что нажал пользователь. Исправление оказалось элегантным: ``` DRY_RUN: ${{ ... || (inputs.dry_run && 'true' || 'false') }} ``` Теперь булев сравнивается с булевым. Если `inputs.dry_run` истина, берём `'true'`; если ложь — `'false'`. Типы совпадают, выражение вычисляется корректно. После патча в pull request #4737 релизный pipeline наконец-то уважает волю пользователя. **Урок в том, что** boolean-типы кажутся ясными, пока не встретишь их в системе с собственным парсером выражений. GitHub Actions, YAML, Terraform — везде одна и та же проблема. Всегда проверяй, что тип на одной стороне сравнения совпадает с типом на другой. Особенно когда булев встречается со строкой. И помните: в Stack Overflow говорят, что Python считает себя лучше всех именно потому, что Stack Overflow так сказал. Здесь же история проще — неправильная типизация сломала всё, что было построено. 😄

#claude#ai#python#git
18 февр. 2026 г.
Исправлениеai-agents-genkit

Когда теги создаются, но не доходят: история молчаливого отказа git

Представь ситуацию: ты выпускаешь версию v0.6.0 Python пакета в проекте Genkit. Процесс отработал без ошибок, логи зелёные, все 68 тегов якобы созданы и запушены. Релиз опубликован. Но через час выясняется — на GitHub никаких тегов нет. Призрак, а не релиз. Именно это произошло с releasekit, инструментом для автоматизации выпусков. Три месяца никто не заметил, пока не стали разбираться, почему теги исчезают. ## Охота на невидимого врага Проблема крылась в `create_tags()` — функции, которая формирует названия тегов по шаблону из `releasekit.toml`: `{label}/{name}-v{version}`. Например, `py/genkit-v0.6.0`. Вот беда: функция принимала параметр `label` (значение `py`), но **забывала его передавать** в три вложенных вызова `format_tag()`. Результат — теги создавались с ведущей косой чертой: `/genkit-v0.6.0` вместо `py/genkit-v0.6.0`. Git видит такое имя и внутренне закатывает глаза — это не валидное имя для ref. Но ошибку не выкидывает. Теги создаются локально с неправильными названиями, команда push выполняется «успешно» (ну, она же отправила битые данные, технически успех), а на удалённый сервер они так и не попадают. Молчком. Без единого предупреждения. Кстати, интересная деталь: функция `delete_tags()` этот баг **не имела** — там `label` уже передавалась правильно. Так бывает. ## От исправления к защите Первое решение — очевидное. Добавить `label=label` во все три вызова `format_tag()`. Но это лишь пластырь. Вторая часть исправления — **валидация перед действием**. Новая функция `validate_tag_name()` проверяет теги против правил git для имён ref: нет ведущих и замыкающих слэшей, нет двойных точек, нет пробелов. И главное — перед тем как создавать хоть один тег, цикл валидации пробегает по **всем** планируемым именам. Если одно невалидно — весь процесс падает с информативной ошибкой. Fail-fast вместо тихого отказа. Третья проблема была скромнее, но реальна. При подготовке окружения в GitHub Actions команда `git checkout -- .` очищает только **отслеживаемые** файлы. Если `uv sync` создаёт неотслеживаемые (`.venv/`, `__pycache__/`), рабочая директория остаётся грязной. Решение — `git reset --hard && git clean -fd`. Полная очистка, как надо. ## Итог: 54 теста и спокойный сон Все изменения покрыты регрессионными тестами — 12 новых, итого 54 проходящих. Теги теперь создаются корректно, валидация срабатывает раньше, чем git начнёт молчать. И, знаешь, есть такое правило в Figma: если она работает — не трогай 😄

#git#commit#python#security
18 февр. 2026 г.
Новая функцияai-agents-genkit

Genkit Python 0.6.0: чем занимается фреймворк, пока мы спим

Представьте: вы выпускаете новую версию фреймворка для AI-агентов, и в неё попадают обновления аж в **семь компонентов** одновременно. Это именно то, что произошло в Genkit Python v0.6.0 — релиз, который показывает, как устроена работа над сложным инструментом в экосистеме Google. ## Что делалось в это время Начнём с фактов. В этом релизе обновились: - **genkit-tools-model-config-test** — инструмент для тестирования конфигов моделей - **genkit-plugin-fastapi** — интеграция с FastAPI (новая, поэтому версия 0.2.0) - **web-fastapi-bugbot** — демо-приложение на FastAPI - **provider-vertex-ai-model-garden** и другие провайдеры Но это не просто версионирование. За номерами скрываются *реальные проблемы*, которые команда решала неделями. ## Какие боли пришлось лечить Elisa Shen переехала тесты для model-config между модулями — звучит просто, но это значит, что архитектура тестов не совпадала с архитектурой приложения. Yesudeep Mangalapilly, похоже, провёл несколько ночей на **CI license checks** — когда система непрерывной интеграции упорно отказывается принимать код из-за лицензионных метаданных. Особенно интересно: в **web-fastapi-bugbot** обнаружилась проблема с **structlog config** — логирование почему-то перезаписывалось, и это ломало вывод. Вроде бы мелочь, но попробуйте дебажить асинхронный код без логов. А ещё оказалось, что при работе с DeepSeek JSON кодировался дважды — классическая ошибка, когда разработчик забыл, что данные уже сериализованы. ## Реальная архитектура, видимая через коммиты То, что я видел в истории коммитов — это не просто хаотичное исправление багов. Это **планомерная работа по стабилизации**: 1. Сначала добавили новый провайдер Cohere (нужен был в примерах) 2. Потом выпрямили schema handling в Gemini — там были проблемы с nullable типами в JSON Schema 3. Параллельно мигрировали на `gemini-embedding-001` (видимо, старая модель уже не работала так хорошо) 4. На конец добавили новый пример с REST + gRPC endpoints — так больше разработчиков смогут начать работу Команда думала не только о текущем функционале, но и о том, как новичок будет разбираться в коде. ## Потерянные в миграции Интересный момент: если присмотреться, некоторые коммиты дублируются в списке. Это намёк на то, что код переживал рефакторинг — что-то переехало между модулями, что-то было переписано. Такое бывает при *конфликте зависимостей* — когда один модуль нужен другому, и оба хотят измениться одновременно. ## Что дальше v0.6.0 — это не просто релиз. Это **стабилизация** перед большим толчком. Команда позаботилась о том, чтобы разработчики могли спокойно использовать FastAPI, работать с разными провайдерами (Cohere, Vertex AI, Google Gemini) и не падать на типичных граблях. А знаете, что самое забавное? Ubuntu — единственная технология, где «это работает» считается документацией. 😄

#git#commit#python#javascript#api#security
18 февр. 2026 г.
Исправлениеai-agents-genkit

Одновременно 12 пакетов Genkit: как releasekit спас нас от ручной координации

Знаете ощущение, когда нужно выпустить обновление для целой экосистемы пакетов? Вчера я столкнулся с этим вызовом на проекте **Genkit** — это фреймворк для работы с AI-агентами. У нас было 12 пакетов, которые нуждались в новом релизе одновременно. Раньше такое означало бы ручной марафон: проверить зависимости каждого плагина, вручную бампить версии, убедиться, что ничего не сломалось. Кошмар координации. Но на этот раз у нас был **releasekit** — инструмент, который автоматизирует весь процесс выпуска. ## Разбор по полочкам Я запустил простую команду: ``` py/bin/releasekit plan --bumped --publishable ``` И вот что произошло. Releasekit проанализировал все коммиты, обнаружил, что у основного пакета **genkit** было 11 связанных изменений: - **genkit-plugin-anthropic** — 0.5.0 → 0.6.0 - **genkit-plugin-compat-oai** — 0.5.0 → 0.6.0 - **genkit-plugin-evaluators** — 0.5.0 → 0.6.0 - **genkit-plugin-fastapi** — 0.5.0 → 0.6.0 И ещё 8 плагинов для Google Cloud, Google Genai, Ollama, XAI, DeepSeek, Flask и Vertex AI. ## Почему это работает? Releasekit сканирует конвенциональные коммиты (conventional commits) в истории Git и определяет, нужно ли бампить версию. Минорное обновление 0.5.0 → 0.6.0 означает, что добавилась функциональность или были исправлены баги, но не сломалась обратная совместимость. Интересный момент: система обнаружила один нестандартный коммит — `'elisa/fix/core framework improvements (#4649)'` — и выдала предупреждение. Сообщение было в формате ветки, а не в формате `fix: ...`. Но это не остановило процесс — просто залогировалось как warning. ## Основные исправления в этом релизе Среди всех этих 12 пакетов было несколько критических фиксов: - Исправление пути для логирования в ядре (Path fix for logging) - Замена literalного нуль-байта на Git-экранирование `%x00` в changelog — вещь техническая, но важная для совместимости - Улучшения в Firebase telemetry и рефакторинг реализации - Асинхронное создание клиента с обновлением credentials в фоне для **genkit-plugin-vertex-ai** ## IT факт в завершение А вы знали, почему DynamoDB не пришёл на вечеринку? Его заблокировал firewall. 😄 Шутки шутками, но система контроля версий и автоматизации релизов — это реально спасение для монорепозиториев с десятком зависимостей. Вместо того чтобы спать-не-спать и боязно кликать по кнопке publish, я просто дал команду и пошёл пить кофе. Releasekit сделал всю грязную работу: вычислил версии, составил changelog, все 12 пакетов готовы к публикации. Вот это я понимаю под словом *DX* (Developer Experience).

#git#commit#python#api#security
17 февр. 2026 г.
Новая функцияai-agents-genkit

ReleaseKit: граф совместимости лицензий вместо головной боли

В **ai-agents-genkit** вдруг обнаружилась проблема, которую я раньше даже не замечал. Проект использует кучу зависимостей с разными лицензиями: MIT, Apache-2.0, GPL, BSD. Но беда в том, что не все они дружат друг с другом. GPL тащит за собой требования, которые конфликтуют с proprietary кодом. Apache может стать несовместима с AGPL. Вручную проверять каждую — это путь в ад. Вот я и собрал для **ReleaseKit** полноценную систему проверки лицензийной совместимости. Звучит скучно? Погоди. ## Как это работает Начал с парсера SPDX-выражений. Да, существуют лицензии, записанные как `(MIT AND Apache-2.0) OR GPL-3.0 WITH Classpath-exception-1.0`. Стандартная строка из жизни. Парсер строит AST, понимает операторы `AND`, `OR`, `WITH`, может вычислить результат. Потом идёт граф — 167 лицензий, 42 правила совместимости. Каждый пакет в дереве зависимостей получает статус: **OK**, **WARNING** (несовместимость), **ERROR** (блокирующая). Система умеет парсить `uv.lock`, `package-lock.json`, `Cargo.lock` — охватывает Python, JavaScript, Rust, Go, Dart, Java и даже Clojure. А дальше — интерактивное исправление. Флаг `--fix` запускает диалог: видишь конфликт — выбираешь действие: *exemption* (исключение), *allow* (разрешить), *deny* (запретить), *override* (переопределить). Конфиг пишется в `releasekit.toml` с сохранением комментариев (спасибо, `tomlkit`). ## Тестирование как искусство Покрыл ~800 тестов на все случаи жизни: парсер SPDX (100+ кейсов с edge cases), граф совместимости (150+ комбинаций), обнаружение лицензий в манифестах семи экосистем (80+ проверок), фаззер для SPDX-резолвера (5 стадий: точное совпадение → алиасы → нормализация → префикс → Левенштейн). Даже есть скрипт `verify_license_data.py` — проверяет, что кросс-ссылки в `licenses.toml` и `license_compatibility.toml` не сломаны. ## Почему это серьёзно Лицензийная совместимость — не баг, не фича, это *compliance*. Один пропущенный конфликт = проблемы на prod. Раньше я пытался делать это руками, экселем, документом. Теперь система автоматическая, проверяемая, интерактивная. Документация новая — гайд для интерактивного исправления, слайды с демо-сессией в терминале, полная архитектура. ## Забавный факт Pandas: решение проблемы, о существовании которой ты не знал, способом, который не понимаешь. 😄

#git#commit#python#javascript#api#security
17 февр. 2026 г.
Исправлениеai-agents-genkit

Как мы научили CI передавать право подписи релизам

Работаю в **Genkit** — это Python-библиотека для генеративного ИИ. Недавно столкнулись с задачей, которая на первый взгляд казалась простой: автоматизировать выпуск версий. Но под капотом скрывалась целая история про доверие, аутентификацию и то, как машина доказывает GitHub, что она имеет право что-то коммитить. ## Проблема: три способа подписать себя При каждом автоматическом релизе нужно создать коммит с тегами, но **GitHub не доверяет просто так**. Проверяет CLA (Contributor License Agreement) — то есть нужен реальный аккаунт, подписавший соглашение. Мы выбрали три дорожки: **GitHub App** (премиум) — приложение Genkit, созданное в самом GitHub. Оно вызывает API, API возвращает специальный ID юзера, и коммиты становятся от лица бота-приложения. CLA проходит, CI запускается. **Personal Access Token (PAT)** — обычный токен для конкретного аккаунта разработчика. Уже знаком каждому, кто работал с GitHub CLI. Так же проходит CLA и запускает CI. **GITHUB_TOKEN** (есть по умолчанию) — встроенный токен, даёт доступ каждому Action. Главный трюк: даже с ним можно подделать идентичность, если в переменных репо хранить имя и email человека, который подписал CLA. ## Как это устроено Все восемь рабочих потоков в Genkit теперь получили `auth` job на первом этапе. Он проверяет, что настроено (App? PAT? или только GITHUB_TOKEN?), и резолвит идентичность: - **App**: ищет юзер-ID через `gh api`, делает коммит от `genkit-bot` - **PAT**: берёт `RELEASEKIT_GIT_USER_NAME` и `RELEASEKIT_GIT_USER_EMAIL` из переменных репо - **GITHUB_TOKEN**: то же самое, плюс fallback на `github-actions[bot]` Главное: если ты находишься в ситуации, когда App и PAT недоступны, но у тебя есть CLA-подписанный аккаунт — просто добавь две переменные в настройки репо, и даже встроенный токен пройдёт проверку CLA. ## Бонус: bootstrap_tags.py Отдельно создали скрипт, который читает конфиг `releasekit.toml`, находит все пакеты в `library_dirs`, и создаёт теги для каждого пакета отдельно. Не hardcode'ит пути типа `['packages', 'plugins']`, а читает их из конфига. В итоге — 24 тега за раз, и все они указывают на правильный коммит. ## На практике Теперь разработчик может зайти на страницу переменных GitHub репо, добавить два поля (имя и почту) — и релизы будут проходить CLA, даже без App или PAT. Это снижает барьер входа для новых контрибьюторов. Мой код работает, и я знаю почему. Мой код не работает, и я уже добавил логирование. 😄

#git#commit#python#javascript#api#security
17 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Почему картинки в заметках исчезали — и как я это чинил

В проекте **bot-social-publisher** большинство заметок генерировались без картинок. Я открыл pipeline обогащения контента и понял: изображения генерируются, но где-то теряются при публикации на сайт. Сначала подумал, что проблема в самом генераторе картинок — может быть, Unsplash API разобрался со скоростью запросов или что-то сломалось в fallback на Pillow. Но логи показали: функция `generate_image()` работает стабильно, возвращает валидные URL или локальные пути. Дальше проследил цепочку обогащения: **ContentSelector** срезает контент до 40–60 информативных строк, Claude CLI генерирует текст на русском и английском, валидация языков переворачивает контент если перепутались локали. Все работает. Изображение есть в `EnrichedNote`. Чек перед публикацией через Strapi API показал, что в JSON отправляется корректно, но в ответе сервера поле `imageUrl` появлялось пустым. Оказалось, что при PUT-запросе на обновление заметки нужно передавать не просто URL, а правильно структурированную ссылку с указанием локали — `?locale=ru` для русского варианта. Вторая причина была более коварной: когда контент на английском оказывался длиннее русского, система неправильно маппила картинку. Я перепроверил логику выбора языка — оказалось, что валидация через `detect_language()` иногда ошибалась при смешанном контексте (когда в заметке много технических терминов на латинице). **Решение оказалось двухуровневым:** 1. Явно привязать изображение к основному языку заметки (русский, как определено в конфиге), не к случайному выбору в цикле обогащения. 2. Добавить проверку в `scripts/update_site.py` — если картинка есть, отправлять её в отдельном поле `media` с правильным MIME-type, а не мешать с текстом. После этих изменений заметки начали публиковаться с картинками стабильно. Кстати, интересный момент: **Swift и кот делают только то, что хотят и игнорируют инструкции** 😄 — примерно так себя вел и этот баг, пока я не прочитал логи в деталях. Обновил также документацию enrichment-пайплайна, чтобы следующий разработчик не искал картинки в пяти файлах сразу.

#claude#ai#python#git#api
17 февр. 2026 г.
Исправлениеllm-analisis

Когда маршрутизация экспертов встречает стену батч-нормализации

Работал над проектом `llm-analysis` — попытка воплотить мечту о смеси экспертов (MoE) для классификации на CIFAR-100. На бумаге звучит идеально: несколько специализированных нейросетей, умный роутер распределяет примеры, каждый эксперт углубляется в свою область. Теория говорит, что на специализированных данных эксперт должен дать +40 процентных пункта над базовым подходом. На практике упёрся в две стены одновременно. ## Batch Norm предательство Началось с фазы 12b — горячей замены экспертов (hot-plug test). Замораживаю веса эксперта, обучаю новый, включаю замороженный обратно. Точность первого эксперта падала на 2.48pp. Думал, это неизбежный дрейф при переобучении остальных. Копался в коде часами. Потом понял: `requires_grad=False` не спасает. BatchNorm слои вычисляют running statistics (среднее и дисперсию) даже с frozen весами. Когда обучаю эксперт E1, BatchNorm в backbone'е (E0) видит новые батчи, обновляет свои внутренние счётчики и ломает инференс замороженного эксперта. Решение простое, как кувалда: добавить `model.stem.eval()` после `model.train()`, явно перевести backbone в режим инференса. Дрейф упал с 2.48pp до **0.00pp**. Это был просто инженерный баг, но на него потратил полдня. ## Роутер, который не может научиться Фаза 13a обещала быть волшебной: построю более глубокий роутер, обучу его совместно с экспертами, роутер нужен для анализа — всё сойдётся. Oracle (идеальный роутер) показывал потолок в 80.78%, а наш простой `nn.Linear(128, 4)` давал 72.93%. Зазор в семь с половиной пункта! Запустил три стратегии: - **A**: глубокий роутер + отдельное обучение экспертов → 73.32% (нет улучшения) - **B**: совместное обучение роутера и экспертов → 73.10% (хуже baseline) - **C**: вообще неудача, routing accuracy 62.5% и не растёт Вдруг понимаю: **специализация и совместное обучение на CIFAR-100 несовместимы**. Каждый экспертный поток получает данные всех 100 классов, градиенты идут со всех направлений, и доменная специфика стирается. Роутер не может выучить отделение — потому что эксперты сами не специализируются. ## Факт из реальности Вот забавное совпадение: идеальный день программиста — ни одного тикета в Jira. Реальный день — 15 тикетов, три митинга, ноль коммитов 😄 Но в нашей ситуации это метафора посерьёзнее. Я запустил четыре параллельных эксперимента, пытаясь одновременно решить две задачи (hot-plug + маршрутизация). Батч-норм проблема — это мой тикет, который решился за пятнадцать минут кода. Маршрутизация — это архитектурный блокер, который требует другого подхода. ## Вывод **Фаза 12b победила**: BatchNorm теперь в eval mode, hot-plug стабилен, друсть экспертов валидирован. Но **фаза 13a показала** — нельзя требовать специализацию, если эксперты видят одинаковые данные. Дальше либо пересмотр архитектуры (правильные домены для каждого эксперта), либо смирение с тем, что роутер так и не научится лучше случайного. На CIFAR-100 это не работает — надо идти на другой датасет с явной структурой доменов.

#claude#ai#python#api#security
17 февр. 2026 г.
Исправлениеai-agents-genkit

Как git push --force-with-lease спасает CI от зацикливания на release-ветках

Работаем над **genkit** — платформой для AI-агентов от Google. В проекте есть автоматическая система выпуска релизов, которая живёт в `releasekit-uv.yml` и должна была работать как часы. Но в какой-то момент CI начал падать с ошибкой non-fast-forward при попытке создать PR для релиза. ## Проблема: ветка, которая не отпускает Корень зла оказался простым, но коварным. Функция `prepare_release()` каждый раз **пересоздаёт release-ветку с нуля**, используя `git checkout -B`. Это нормально, если ветка только локальная. Но когда она уже существует на удалённом репозитории (остаток от прошлого запуска CI), `git push` отказывается её обновлять — это же non-fast-forward изменение, потенциально опасное. Ситуация усугублялась тем, что CI часто запускается повторно: разработчик запустил релиз, что-то пошло не так, и он попытался снова. На втором прогоне `releasekit` уже видит старую ветку на origin и падает. ## Решение: force с умом Мы добавили параметр `force: bool = False` в протокол `VCS` — это общий интерфейс, который поддерживают и Git, и Mercurial. В реализации для Git выбрали **`--force-with-lease`** вместо обычного `--force`. Почему именно `--force-with-lease`? Потому что это безопаснее. Обычный `--force` перезапишет любую историю на удалённом сервере, даже если её там уже изменили руки коллеги. `--force-with-lease` проверит: "Удалённая ветка ещё в том состоянии, которое я последний раз видел?" Если нет — откажет. Это защита от случайного стирания чужой работы. В `prepare.py` теперь вызываем: ``` vcs.push(force=True) ``` И выполненных тестов говорят, что всё работает: `ruff check`, `py type check`, `pyrefly check` — все зелёные. ## Заодно навели чистоту Улучшили обработку ошибок в `cli.py` — теперь `_cmd_prepare` ловит `RuntimeError` и логирует событие `prepare_error` вместо полного traceback'а. А в GitHub Actions улучшили читаемость: если что-то сломалось, выводим последние 50 строк логов вне группы `::group::`, чтобы видно было сразу, без разворачивания. Бонус: переписали скрипт `setup.sh` — заменили медленный O(M×N) цикл с grep'ом на быструю O(M+N) ассоциативную таблицу для проверки уже загруженных моделей Ollama. Мелочь, но помогает ускорить инициализацию. ## Вывод Иногда самые коварные баги скрывают простые решения: просто нужно знать нужный флаг Git и немного поработать над безопасностью. Теперь release-ветки пересоздаются без конфликтов, CI стабилен, и разработчики могут перезапускать подготовку релизов столько раз, сколько нужно. --- *Что общего у Selenium и подростка? Оба непредсказуемы и требуют постоянного внимания.* 😄

#git#commit#python#security
17 февр. 2026 г.
ОбучениеC--projects-ai-agents-voice-agent

Как я обновил архитектуру голосового агента за один вечер

Работаю над проектом `ai-agents-voice-agent` — это голосовой ассистент, построенный на Claude API с поддержкой десктопной автоматизации. Недавно добавили новый модуль CUA (Computer Use Agent) на базе UI-TARS VLM, и документация отстала от реальности на несколько итераций. Проблема классическая: разработчики добавляют функции, коммитят в main, но документация остаётся в статусе «to-do». Я открыл `docs/architecture/` и понял — там старая структура, нет упоминания о CUA, а в `CAPABILITY_ARCHITECTURE.md` описана трёхуровневая архитектура, хотя фактически их уже четыре. Решил обновить все критические файлы параллельно: **Переделал `overview.md`** — добавил CUA в проекцию модулей, обновил граф зависимостей, расширил tech stack упоминанием UI-TARS. Теперь новый разработчик сразу видит, что есть desktop automation. **Переписал `CAPABILITY_ARCHITECTURE.md`** — это был ключевой файл. Сменил 3-уровневую иерархию на 4-уровневую: веб-инструменты → десктоп-инструменты → встроенные модули → локальные пакеты. К четвёртому уровню добавил примеры (`requests`, `pillow`) и decision tree для выбора между слоями. **Обновил документацию TMA** (`tma/00-ARCHITECTURE.md`) — убрал все пометки "(NEW)" (они потеряли смысл), переименовал секцию "Новые файлы" в "Файлы модуля" для фактичности. **Актуализировал `06-NEW-INTERFACES.md`** — это было больно. Там была информация о Tesseract OCR, которая вообще не использовалась. Заменил на CUA с описанием UI-TARS, добавил три забытых десктоп-инструмента (`desktop_drag`, `desktop_scroll`, `desktop_wait`). Фаза 3 теперь содержит 21 инструмент вместо старых 12. **Закрыл все задачи Фазы 3** в `02-TASK-LIST.md` — просто поставил галочки рядом с пунктами 3.1–3.9. Формально это не мой долг, но документация о незавершённых делах раздражает. Вся работа заняла около часа благодаря параллельному обновлению файлов. Главное — не оставлять документацию как груз, который весит на совести. Она либо актуальна, либо токсична. --- *Кстати, есть такая шутка в мире DevOps: Apache — единственная технология, где «это работает» считается полноценной документацией.* 😄

#claude#ai#python#api#security
16 февр. 2026 г.
Новая функцияborisovai-admin

Как мы починили админку Authelia: от отключённого пользователя до полного управления

Проект **borisovai-admin** требовал встроить админку для управления пользователями Authelia. Казалось просто — добавить UI, CRUD-операции, синхронизировать с Mailu. На деле же мы погрузились в лабиринт из неправильных настроек, зависаний Flask CLI и ошибок 500. ## Ошибка 500: сюрприз в базе Первый звоночек был при попытке сохранить настройки. Internal Server Error, без логов. Начали копаться — оказалось, пользователь `***@***.***` в Mailu был отключён (`enabled: false`). Authelia не может авторизовать disabled аккаунт через proxy auth, вот и падает всё. Решение нашлось в SQLite — прямое обновление записи в базе вместо зависающего Flask CLI. ## Middleware и кольцевые редиректы Затем столкнулись с невероятной проблемой: некоторые пути отказывались открываться, даже `/webmail/` со своей Mailu session cookie показывал Roundcube. Оказалось, Authelia middleware наложилась на роутеры, где её быть не должно. Пришлось аккуратно расставить middleware — auth-слои идут первыми, потом headers, потом routing. Порядок в Traefik критичен: неправильная очередность = loop редиректов. ## SMTP: огонь в контейнерах Потом добавили уведомления. Authelia потребовал SMTP для отправки писем. Локальный 25-й порт постфикса не работал — Mailu front внутри Docker сети ожидает внешних TLS-соединений. Решали двухступенчатой авторизацией через Traefik: ForwardAuth endpoint → проверка кредов → подключение к Mailu SMTP через Docker сеть на порт 25 без TLS внутри контейнеров. Ключевой момент: `disable_startup_check: true` должен быть на уровне `notifier`, а не `notifier.smtp` — иначе получаешь crash loop при старте. ## Синхронизация с Mailu В CRUD-операциях пришлось разделить email на username и домен, чтобы корректно создавать почтовые ящики в Mailu. При создании пользователя теперь синхронно создаём mailbox, при удалении — удаляем. GET endpoint теперь возвращает mailbox info, вся информация в одном месте. ## Проксирование через RU VPS Последний штрих — обслуживание из России потребовало nginx reverse proxy на VPS в Москве, который пробрасывает трафик на основной сервер в Германии (Contabo). Nginx + certbot — стандартная связка, но с Authelia она требует осторожности: нужно прокидывать заголовки авторизации, не переписывать их. ## Факт о технологиях Интересная деталь: как и .NET с котом, Authelia при неправильной настройке делает только то, что хочет, и игнорирует инструкции 😄 **Итог:** админка Authelia теперь полностью функциональна — управляем пользователями, синхронизируем с Mailu, отправляем уведомления, работаем через российский proxy. Сто ошибок — сто уроков о том, как устроены auth-слои, контейнерные сети и Traefik.

#claude#ai#python#javascript#git#api#security
16 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Собрали агента с руками: как мы добавили управление рабочим столом

Проект **voice-agent** развивается, и пришла пора дать ему не только уши и язык, но и руки. Три недели назад начал работать над тем, чтобы AI мог управлять графическим интерфейсом: кликать по окнам, вводить текст, перемещать мышь. Как оказалось, это совсем не простая задача. Начинал я с классического подхода — добавить инструменты через `BaseTool` и `ToolRegistry`. Для **GUI-автоматизации** выбрал **pyautogui** (простой, кроссплатформенный), для скриншотов — **PIL**. Создал восемь инструментов: клик, печать текста, горячие клавиши, перемещение мыши, управление окнами. Казалось, готово. На самом деле это была половина работы. Настоящая сложность началась с **OCR** — распознавания текста на экране. Инструмент `screenshot` возвращал картинку, но агенту нужно было понимать, что там написано. Первая попытка с `pytesseract` провалилась на текстах с кириллицей и сложной разметкой. Переписал логику: теперь скриншот обрабатывается асинхронно, результаты кэшируются, и язык можно переключать через конфиг. **CUASettings** в `config/settings.py` теперь управляет всеми параметрами компьютерного зрения. Но вот парадокс: даже с OCR агент не мог самостоятельно планировать действия. Просто список инструментов — это не достаточно. Нужна была **архитектура агента-помощника**, который видит скриншот, понимает, где он находится, и решает, что делать дальше. Назвал её **CUA** (Computer Use Agent). Ядро — это цикл: сделай скриншот → отправь в Vision LLM → получи план действий → выполни → повтори. Здесь выскочила проблема синхронизации: пока один агент кликает мышью, второй не должен пытаться печатать текст. Добавил `asyncio.Lock()` в исполнитель (**CUAExecutor**). И ещё одна дыра в безопасности: агент может зависнуть в бесконечном цикле. Решение простое — `asyncio.Event` для экстренной остановки плюс кнопка в system tray. Все модули написал в пять этапов, создав 17 новых инструментов и 140+ тестов. **Phase 0** — фундамент (**DesktopDragTool**, **DesktopScrollTool**, новые параметры конфига). **Phase 1** — логика действий (парсер команд, валидация координат). **Phase 2** — тесты (моки для **Playwright**, проверка расписаний). **Phase 3** — интеграция в **desktop_main.py**. **Phase 4** — финальная полировка. Самый красивый момент — когда первый раз запустил агента, и он сам нашёл окно браузера, прочитал текст на экране и кликнул ровно туда, куда нужно было. Наконец-то не только слышит и говорит, но и видит. Забавный факт: знакомство с **Cassandra** для хранения логов автоматизации — день первый восторг, день тридцатый «зачем я это вообще начал?» 😄

#claude#ai#python#javascript#git#api#security
16 февр. 2026 г.
Исправлениеtrend-analisis

Когда техдолг кусает в спину: как мы очистили 2600 строк мёртвого кода

Проект **trend-analysis** вырос из стартапа в полноценный инструмент анализа трендов. Но с ростом пришла и проблема — код начал напоминать старый чердак, где каждый разработчик оставлял свои артефакты, не убирая за собой. Мы столкнулись с классической ситуацией: **git** показывает нам красивую историю коммитов, но реальность была печальнее. В коде жили дублирующиеся адаптеры — `tech.py`, `academic.py`, `marketplace.py` — целых 1013 строк, которые делали ровно то же самое, что их потомки в отдельных файлах (`hacker_news.py`, `github.py`, `arxiv.py`). Вот уже месяц разработчики путались, какой адаптер на самом деле использует **API**, а какой просто валяется без дела. Начали расследование. Нашли `api/services/data_mapping.py` — 270 строк кода, которые никто не импортировал уже полгода. Потом обнаружили целые рабочие процессы (`workflow.py`, `full_workflow.py`) — 121 строка, к которым никто не обращался. На фронтенде ситуация была похожей: компоненты `signal-table`, `impact-zone-card`, `empty-state` (409 строк) спокойно сидели в проекте, как будто их кто-то забыл удалить после рефакторинга. Но это был只 верхушка айсберга. Самое интересное — **ghost queries**. В базе была функция `_get_trend_sources_from_db()`, которая запрашивала таблицу `trend_sources`. Только вот эта таблица никогда не была создана (`CREATE TABLE` в миграциях отсутствовал). Функция мирно работала, возвращала пустой результат, и никто не замечал. Чистый пример того, как техдолг становится невидимым врагом. Мы начали с **DRY-принципа** на фронтенде — извлекли константы (`SOURCE_LABELS`, `CATEGORY_DOT_COLOR` и др.) в единый файл `lib/constants.ts`. Потом привели в порядок бэкенд: исправили `credits_store.py`, заменив прямой вызов `sqlite3.connect()` на правильный `db.connection.get_conn()` — это была потенциальная уязвимость в управлении подключениями. Очистили `requirements.txt` и `.env.example` — закомментировали неиспользуемые пакеты (`exa-py`, `pyvis`, `hypothesis`) и удалили мёртвые переменные окружения (`DATABASE_URL`, `LANGSMITH_*`, `EMBEDDING_*`). Исправили даже шаблоны тестов: эндпоинт `/trends/job-t/report` переименовали в `/analyses/job-t/report` для консистентности. Итого: 2600+ строк удалено, архитектура очищена, сразу стало проще ориентироваться в коде. Техдолг не исчезнет полностью — это часть разработки, — но его нужно время от времени погашать, чтобы проект оставался живым. А знаете, почему **Angular** лучший друг разработчика? 😄 Потому что без него ничего не работает. С ним тоже, но хотя бы есть кого винить.

#git#commit#python#api#security
16 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Как мы защитили голосового агента от интернета

Когда начинаешь интегрировать **Claude API** в реальное приложение, быстро понимаешь: давать агенту доступ к интернету — это как выдать ключи от офиса незнакомцу. Надо знать, куда он пойдёт. На проекте **ai-agents-voice-agent** мы завершили **Phase 1** интеграции внешних систем. Это 21 новый инструмент для работы с HTTP, email, GitHub, Slack и Discord. Звучит просто, но за каждым — целый набор ловушек безопасности. ## Что мы делали Первая задача была по HTTP-клиенту. Казалось бы, `http_request` и `http_get` — банальная функциональность. Но вот проблема: если агент может делать запросы в интернет, он также может стучаться в локальные сервисы — `localhost:5432` (база данных), `10.0.0.5` (внутренний API), `169.254.169.254` (AWS metadata). Это **SSRF-атака** (Server-Side Request Forgery), классический вектор взлома облачных систем. Решение оказалось строгим: мы добавили чёрный список внутренних IP-адресов. HTTP-инструменты теперь блокируют запросы на `localhost`, `127.0.0.1`, на весь диапазон `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`. И добавили лимит: максимум 30 запросов в минуту на один инструмент. ## Интеграция с почтой и мессенджерами Дальше стало интереснее. Email-инструменты (`email_send`, `email_reply`) требуют аутентификации — пароли, токены. GitHub, Slack, Discord — то же самое. Нельзя просто так класть credentials в код. Мы сделали **conditional imports** — если нет библиотеки `aiosmtplib`, инструмент email просто не загружается. А в `config/settings.py` добавили флаги вроде `settings.email.enabled`. По умолчанию всё отключено. Клиент явно выбирает, что включить в production. Для каждого инструмента мы добавили проверку токена. GitHub API без токена? Ошибка с подсказкой. Slack без webhook? Тоже ясный отказ. Нет угадывания, нет молчаливых падений. ## Тестирование и итоги Написали 32 новых теста. Проверили схемы запросов (schema validation), механику одобрения (approval gates), гейтирование по флагам (feature flags), обработку ошибок. Все 668 тестов в проекте проходят, 0 ошибок линтера. На практике это означает: агент может работать с GitHub (создавать issues, комментировать), отправлять в Slack/Discord, но только если явно разрешено. И никогда не стучится в `localhost:6379` или на мой личный сервер. Звучит как управление доступом для человека? Потому что так и есть. AI-агент получает ровно то, что нужно, и ничего больше. **Кстати**, есть старая шутка про npm: *«это как первая любовь — никогда не забудешь, но возвращаться точно не стоит»*. 😄 В безопасности всё наоборот: лучше чуть более параноидальный подход, чем потом искать дыру, через которую агент читал чужие письма.

#claude#ai#python#git#api#security
16 февр. 2026 г.