BorisovAI

Блог

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

Исправление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 г.
ОбучениеC--projects-bot-social-publisher

Когда маршрутизация идеальна, а точность падает: урок из CIFAR-100

В проекте **llm-analysis** я проверял гипотезу, которая казалась очевидной: если обучить глубокий роутер правильно направлять примеры на специализированные сети, accuracy должен вырасти. Я протестировал четыре подхода — и получил то, что не ожидал. ## Четыре стратегии маршрутизации Первые три были «обычными»: - **Стратегия A**: простая маршрутизация, 70.77% accuracy, роутер угадывает правильный класс в 62.5% случаев - **Стратегия B**: смешанный подход, 73.10%, маршрутизация на уровне 62.3% - **Стратегия C**: двухфазная схема, 72.97%, роутинг 61.3% Результаты росли медленно, но росли. Потом я решил не экономить и построил **Стратегию D** — многослойный роутер с supervised training, специально обученный выбирать эксперта для каждого класса. ## Успех, который не сработал Стратегия D показала впечатляющий результат для маршрутизации: **79.5%** точности выбора. Это в 1.28 раза лучше, чем простой однослойный роутер. Я был на пике оптимизма. Но когда я проверил финальную accuracy на CIFAR-100 — получил **73.15%**. Прирост всего 0.22 пункта над двухфазным подходом. По сути, плоский результат. Это был момент истины. Роутер стал выбирать специализированные сети в 4 из 5 случаев правильно. Но эти правильные выборы почти не улучшали классификацию. Проблема была не в том, *как* выбирать эксперта, а в том, насколько хороши сами эксперты. ## Факт о специализации Оракульная accuracy — когда мы знаем правильный класс и принудительно отправляем пример на соответствующую сеть — держалась на уровне 84–85%. Это потолок архитектуры. Даже с идеальной маршрутизацией мы не можем превысить эту границу. Специализированные сети учились на меньшем количестве примеров, к тому же узкие паттерны, которые они находили, плохо обобщались. Когда роутер отправлял пример на «неправильного» эксперта — сеть часто ошибалась, потому что её обучали как-то иначе. **Специализация принесла скорее минусы, чем плюсы.** ## Итог и шутка про GraphQL Эксперимент 13b завершился вердиктом **NO-GO** — 73.15% меньше требуемых 74.5%. Но это не провал. Это урок о том, что иногда идеально отлаженная часть системы не спасает целое. Нужно либо перестроить архитектуру специализированных моделей, либо вообще забыть об этом подходе. Документация обновлена, метрики залогированы. Команда готовится к следующему витку. Знаете, почему GraphQL расстался с разработчиком? Слишком много зависимостей в отношениях. 😄

#claude#ai
17 февр. 2026 г.
Изменение кодаllm-analisis

Когда маршрутизация идеальна, а точность нет: история эксперимента 13b

В проекте **llm-analisis** я работал над стратегией специализированных моделей для CIFAR-100. Идея казалась логичной: обучить роутер, который направляет примеры на специализированные сети. Если маршрутизация будет точной, общая accuracy должна вырасти. Вот только жизнь оказалась сложнее. ## Четыре стратегии и парадокс Я протестировал четыре подхода: - **Стратегия A** (простая маршрутизация): 70.77% accuracy, но роутер угадывал правильный класс только в 62.5% случаев - **Стратегия B** (смешанный подход): 73.10%, маршрутизация на уровне 62.3% - **Стратегия C** (двухфазная): 72.97%, роутинг 61.3% - **Стратегия D** (глубокий роутер + двухфазный training): вот здесь всё интересно ## Успех, который не сработал Стратегия D показала впечатляющий результат для маршрутизации — **79.5%**. Это в 1.28 раза лучше, чем простой однослойный роутер. Я был уверен, что это прорыв. Но финальная accuracy выросла всего на 0.22 процентных пункта до 73.15%. Это был момент истины. **Проблема не в маршрутизации.** Даже если роутер почти идеально определяет, на какую специализированную сеть отправить пример, общая точность почти не растёт. Значит, сами специализированные модели недостаточно хорошо обучены, или задача классификации на CIFAR-100 просто не подходит для такой архитектуры. ## Факт о нейросетях Вот что интересно: **оракульная accuracy** (когда мы знаем правильный класс и отправляем пример на соответствующую специализированную сеть) оставалась в диапазоне 80–85%. Это потолок архитектуры. Роутер, улучшив маршрутизацию, не может превысить этот потолок. Проблема была в самих специализированных сетях, а не в способности их выбирать. ## Итог Эксперимент 13b завершился вердиктом **NO-GO** — 73.15% меньше требуемых 74.5%. Но это не поражение, а ценный урок. Иногда идеально сделанная часть системы не спасает целое. Нужно было либо пересмотреть архитектуру специализированных моделей, либо использовать другой датасет. Документация обновлена, результаты залогированы. Команда готовится к следующему витку экспериментов. *Совет дня: перед тем как обновить ArgoCD, сделай бэкап. И резюме. 😄*

#claude#ai
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 г.
Новая функцияC--projects-bot-social-publisher

Когда батч-норм ломает миксчур экспертов на CIFAR-100

Три недели охоты за призраком. Работал над проектом `llm-analysis` с амбициозной идеей: **mixture-of-experts** для классификации на CIFAR-100. Теория обещала +40 процентных пункта над baseline. На практике упёрся в две стены сразу. ## Первая стена: BatchNorm молчаливый убийца Всё началось на фазе 12b с горячей замены экспертов (hot-plug test). Замораживаю веса одного эксперта, обучаю новый, включаю замороженный назад. И вот беда — точность первого эксперта падает на **2.48 процентных пункта**. Часы в отладчике, проверка логик, перепроверка кода... Потом осенило: `requires_grad=False` не спасает. **BatchNorm слои обновляют running statistics** даже с заморозкой весов. Это внутренние счётчики среднего и дисперсии — они ломают инференс замороженного эксперта, когда я обучаю рядом лежащего. Решение глупо-простое: добавил `model.stem.eval()` после `model.train()`. Явно перевести backbone в режим инференса. Дрейф упал с 2.48pp до нуля. Полдня на баг, который решился одной строкой. Классика. ## Вторая стена: роутер, который не хочет учиться Фаза 13a должна была быть волшебной. Oracle (идеальный роутер) показывал потолок в **80.78%**. А мой `nn.Linear(128, 4)` застрял на **72.93%** — зазор в семь с половиной пункта. Запустил три стратегии подряд: 1. **Глубокий роутер + отдельное обучение**: 73.32% — тоже не помогает 2. **Совместное обучение роутера и экспертов**: 73.74% — хуже 3. **Ещё глубже архитектура**: routing accuracy 62.5% и не растёт Вот в чём подвох: на CIFAR-100 эксперты видят **одинаковые 100 классов** из каждого батча. Градиенты идут со всех направлений одновременно. Доменная специфика просто стирается. Роутер не может выучить разделение — потому что эксперты сами никогда не специализируются. Это не инженерный баг. Это архитектурный потолок. ## Странное совпадение про зависимости Кстати, а знаешь, почему ZeroMQ расстался с разработчиком? **Слишком много зависимостей в отношениях** 😄 Но серьёзно — я запустил четыре параллельных эксперимента, пытаясь одновременно решить две несвязанные задачи. BatchNorm — это был мой быстрый win. Маршрутизация — архитектурный блокер. **Итог фазы 12b**: горячая замена экспертов работает. Hot-plug стабилен. Batch-norm под контролем. **Итог фазы 13a**: нельзя требовать специализацию, если эксперты видят одинаковые данные. На CIFAR-100 с такой архитектурой это невозможно. Нужна либо переделка доменов под каждого эксперта, либо... признание поражения и переход на другой датасет. Иногда две стены одновременно — это знак, что дверь в другом месте.

#claude#ai
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 г.
Новая функцияllm-analisis

SharedParam MoE: когда 4 эксперта лучше 12

Вот уже несколько месяцев работаю над оптимизацией смеси экспертов для LLM. Задача проста на словах: найти архитектуру, которая даст лучшую точность при меньшем количестве параметров. На деле всё оказалось намного интереснее. Стартовали с классического подхода — baseline из Phase 7a с 12 независимыми экспертами и 4.5M параметров показывал точность 70.45%. Это был мой ориентир. Но такой размер модели дорог в инференсе. Нужно было поискать. В эксперименте 10a протестировал три подхода сразу. **Condition A** — просто выключить MoE, использовать одну сеть без маршрутизации. Условно «No-MoE», 2.84M параметров. Результат: 69.80%. Маршрутизация между несколькими путями дала всего лишь 1.15pp — почти ничего. **Condition B** — вот здесь началось интересное. SharedParam MoE: четыре эксперта, но ключевая идея в том, чтобы они делили общие слои параметров. Гейтинг работает только на последних слоях, а основная вычислительная масса — одна на всех. Плюс Loss-Free Balancing: нет штрафов на балансировку, просто следим за утилизацией и регулируем bias. На 130-й эпохе вижу 70.71%, а по финалу получилось **70.95%** при 2.91M параметров. Выше baseline! Все четыре эксперта были живы ВСЁ обучение. **Condition C** — Wide Shared, более агрессивный шаринг параметров. На финале 69.96%, отстал немного. Но главное: 2.86M параметров, инфернс на 25.3ms против 29.2ms у B. Пока ждал результатов 10a, запустил 10b с MixtureGrowth — идеей вырастить сеть из маленького seed'а 182K параметров путём добавления новых слоёв и экспертов. Классный подход для прогрессивного расширения. Seed стартовал с 53.23%, потом во время freeze-фазы за 10 эпох скакнул на 58.97%. Смотрю — рост работает! На Stage2 с 2.84M параметров получилось 69.65%, всего на 0.80pp ниже original baseline. Что здесь самое странное? **Выращенная из крошечного seed модель на 5.57pp превосходит ту же архитектуру, обученную с нуля!** Scratch-baseline на тех же 2.84M параметрах показал только 64.08%. Обучение `в длину` оказалось эффективнее, чем `в глубину` с нуля. На 10c изменил расписание learning rate с cosine scheduler'ом — может быть, это даст ещё лучше. Seed уже на эпохе 50 показывает 52.44% против 48.78% в 10b без cosine. Пока расписание работает. **Вердикт текущий**: SharedParam MoE — наш путь вперёд. Не просто потому что точнее, а потому что **эффективнее**: на 35% меньше параметров, на 50pp точнее baseline, все эксперты живы, Loss-Free Balancing не создаёт артефактов. Маршрутизация имеет смысл только если эксперты действительно специализируются. Шеринг параметров сокращает их эго. Кстати, по поводу экспертов и выбора между подходами — GraphQL как первая любовь: никогда не забудешь, но возвращаться не стоит. 😄

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

Когда дефолт становится врагом: история из bot-social-publisher

Я отлаживал странный баг в **bot-social-publisher** и наткнулся на что-то неочевидное. Каждый ответ бота в личных сообщениях Telegram вдруг стал отправляться как цитата—с тем самым пузырём, который в групповых чатах выглядит уместно, а в 1:1 диалогах просто раздражает своей многословностью. Виноват оказался идеальный шторм из совпадений и забытых дефолтов. В последней версии проекта мы запустили фичу неявной реплай-сортировки—действительно полезная штука, которая автоматически нанизывает ответы на исходное сообщение. Сама по себе это хорошо. Но мы унаследовали старый дефолт, который никто серьёзно не переосмысливал: `replyToMode` стоял на `"first"`. Это значит, что первый ответ всегда уходит нативной Telegram-цитатой. Раньше эта настройка была невидима. Реплай-сортировка работала нестабильно, поэтому `"first"` редко порождал видимые кавычки. Пользователи не замечали—потому что сам механизм был ненадёжным. Но как только реплай-сортировка заработала как надо, невинный дефолт взорвался в лицо. Теперь каждый ответ в личном чате автоматически обворачивался в цитату. Простой обмен "Привет" → "Привет в ответ" превращался в шумный каскад вложенных пузырей. Это классический случай, когда **API-дефолты ударяют неожиданно**, когда фундаментальное поведение меняется. Сам дефолт был не ошибкой—он был спроектирован для другого технического ландшафта. Решение оказалось прямолинейным: переключить дефолт с `"first"` на `"off"`. Это вернуло доинженерное поведение для личных сообщений. А те, кому реплай-сортировка *действительно* нужна, могут явно включить её через конфиг. Тестировал на живой инстанции—с `"first"` каждое сообщение цитируется, на `"off"` ответы идут чистыми. Тесты не потребовали обновления, потому что наш набор тестов был явным по `replyToMode`—никогда не полагался на магию дефолтов. Небольшая победа за поддерживаемость кода. **Мораль**: дефолты мощны ровно потому, что они невидимы. Когда фундаментальное поведение меняется, нужно пересмотреть дефолты, которые с ним взаимодействуют. Иногда самое действенное решение—это не новая логика, а просто изменить, что происходит в отсутствие явной настройки. Между прочим, если бы Fedora когда-нибудь обрела сознание, первым делом она удалила бы свою документацию 😄

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

Когда дефолт становится врагом UX: история из OpenClaw

Я отлаживал странный баг в проекте **OpenClaw** и наткнулся на что-то неочевидное. Каждый ответ бота в личных сообщениях Telegram вдруг стал отправляться как цитата—с тем самым пузырём, который вложенным смотрится в групповых чатах, а в 1:1 диалогах просто раздражает своей многословностью. Виноват оказался идеальный шторм из совпадений и забытых дефолтов. В версии **2026.2.13** команда запустила фичу неявной реплай-сортировки—действительно полезная штука, которая автоматически нанизывает ответы на исходное сообщение. Сама по себе это хорошо. Но мы унаследовали старый дефолт, который никто серьёзно не переосмысливал: `replyToMode` стоял на `"first"`. Это значит, что первый ответ всегда уходит нативной Telegram-цитатой. Раньше эта настройка была невидима. Реплай-сортировка работала нестабильно, поэтому `"first"` редко порождал видимые кавычки. Пользователи не замечали—потому что сам механизм не был надёжным. Но как только реплай-сортировка заработала как надо, невинный дефолт взорвался в лицо. Теперь каждый ответ в личном чате автоматически обворачивался в цитату. Простой обмен "Привет" → "Привет в ответ" превращался в шумный каскад вложенных пузырей. Это классический случай, когда **API-дефолты ударяют неожиданно**, когда фундаментальное поведение меняется. Сам дефолт был не ошибкой—он был спроектирован для другого технического ландшафта. Решение оказалось прямолинейным: переключить дефолт с `"first"` на `"off"`. Это вернуло доинженерное поведение для личных сообщений. А те, кому реплай-сортировка *действительно* нужна, могут явно включить её через конфиг: ``` channels.telegram.replyToMode: "first" | "all" ``` Тестировал на живой инстанции 2026.2.13. С `"first"`—каждое сообщение цитируется. На `"off"`—ответы идут чистыми. Всё прозрачно. Тесты не потребовали обновления, потому что наш набор тестов уже был явным по `replyToMode`—никогда не полагался на магию дефолтов. Небольшая победа за поддерживаемость кода. **Мораль**: дефолты мощны ровно потому, что они невидимы. Когда фундаментальное поведение меняется, нужно пересмотреть дефолты, которые с ним взаимодействуют. Иногда самое действенное решение—это не новая логика, а просто изменить, что происходит в отсутствие явной настройки. Между прочим, если бы `cargo` когда-нибудь обрёл сознание, первым делом он удалил бы свою документацию 😄

#claude#ai#api
15 февр. 2026 г.
Новая функцияai-agents

От chaos к structure: как мы спасли voice-agent от собственной сложности

Я работал над `ai-agents` — проектом с автономным voice-agent'ом, который обрабатывает запросы через Claude CLI. К моменту начала рефакторинга код выглядел как русский матрёшка: слой за слоем глобальных переменных, перекрёстных зависимостей и обработчиков, которые боялись трогать соседей. **Проблема была классическая.** Handlers.py распух до 3407 строк. Middleware не имела представления о dependency injection. Orchestrator (главный дирижёр) тянул за собой кучу импортов из telegram-модулей. А когда я искал проблему с `generated_capabilities` sync, понял: пора менять архитектуру, иначе каждое изменение превратится в минное поле. Я начал с диагностики. Запустил тесты — прошло 15 случаев, где старые handlers ломались из-за отсутствующих re-export'ов. Это было сигналом: **нужна система, которая явно говорит о зависимостях**. Решил перейти на `HandlerDeps` — dataclass, который явно описывает, что нужно каждому обработчику. Вместо `global session_manager` — параметр в конструкторе. Параллельно обнаружил утечку памяти в `RateLimitMiddleware`. Стейт пользователей накапливался без очистки. Добавил периодическую очистку старых записей — простой, но효과적한паттерн. Заодно переписал `subprocess.run()` на `asyncio.create_subprocess_exec()` в compaction.py — блокирующий вызов в асинк-коде это как использовать молоток в операционной. Потом сделал вещь, которая кажется малой, но спасает множество часов отладки. Создал **Failover Error System** — типизированную классификацию ошибок с retry-логикой на exponential backoff. Теперь когда Claude CLI недоступен, система не паникует, а пытается перезагрузиться, а если совсем плохо — падает с понятной ошибкой, а не с молчаливым зависанием. Ревью архитектуры после этого показало: handlers/\_legacy.py — это 450 строк с глубокой связью на 10+ глобалов. Экстрактить сейчас? Создам просто другую матрёшку. Решил оставить как есть, но запретить им регистрировать роутеры в главном orchestrator'е. Вместо этого — явная инъекция зависимостей через `set_orchestrator()`. **Результат**: handlers.py сократился с 3407 до 2767 строк (-19%). Все 566 тестов проходят. Код больше не боится изменений — каждая зависимость видна явно. И когда кто-то спустя месяц будет копаться в этом коде, он сразу поймёт архитектуру, а не будет ловить призраков в глобалах. А знаете, что смешно? История коммитов проекта выглядит как `git log --oneline`: 'fix', 'fix2', 'fix FINAL', 'fix FINAL FINAL'. Вот к чему приводит отсутствие архитектуры 😄

#claude#ai#python#javascript#api#security
15 февр. 2026 г.