惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
Schneier on Security
Schneier on Security
Attack and Defense Labs
Attack and Defense Labs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Heimdal Security Blog
C
Check Point Blog
Engineering at Meta
Engineering at Meta
美团技术团队
www.infosecurity-magazine.com
www.infosecurity-magazine.com
MongoDB | Blog
MongoDB | Blog
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Google Online Security Blog
Google Online Security Blog
H
Hacker News: Front Page
Webroot Blog
Webroot Blog
T
Troy Hunt's Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Fortinet All Blogs
雷峰网
雷峰网
O
OpenAI News
S
Security Archives - TechRepublic
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
F
Full Disclosure
S
Security Affairs
Latest news
Latest news
人人都是产品经理
人人都是产品经理
Microsoft Security Blog
Microsoft Security Blog
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
GbyAI
GbyAI
爱范儿
爱范儿
Recorded Future
Recorded Future
Cyberwarzone
Cyberwarzone
V
Vulnerabilities – Threatpost
N
Netflix TechBlog - Medium
小众软件
小众软件
G
Google Developers Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
Martin Fowler
Martin Fowler

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
9 AI-агентов делят одну API-квоту. Почему обычные ретраи только ломают систему
Ксения Мосеенкова · 2026-06-15 · via Все публикации подряд на Хабре

Средний

14 мин

8

Я уже пару недель запускаю Squad — мультиагентный AI-фреймворк. Он оркестрирует команду AI-агентов, которые занимаются ревью кода, архитектурными решениями, инфраструктурой, документацией и многим другим. Каждые 5 минут запускается цикл сверки состояния: он подхватывает работу и распределяет её между агентами. Большую часть времени всё работает отлично.

Когда я начал планировать запуск Squad в масштабе — на платформах вроде AKS, Azure VMs и похожих вариантах, — я понял, что ограничение скорости запросов в системе с несколькими агентами устроено принципиально иначе, чем ограничение скорости запросов для одного сервиса. Я нашёл тред на r/GithubCopilot, где люди описывали ровно ту же проблему, с которой столкнулся я. После этого я пошёл разбираться: почитал материалы, провёл стресс-тесты системы и спроектировал 6 паттернов, которые помогают с этим справиться.

Вот что стало отправной точкой для глубокого погружения. По мере того как я добавлял новые машины и процессы Ralph, всё начало ломаться.

Девять агентов стартовали одновременно. За 22 минуты они открыли 10 пулл-реквестов. Впечатляет — ровно до восьмой минуты, когда GitHub начал возвращать 429 Too Many Requests.

Все агенты одновременно ушли на повторную попытку. Волна повторных попыток вызвала вторую волну 429-х ответов. Та — третью. За 90 секунд я сжёг весь лимит GitHub в 5000 запросов в час и полностью потерял доступ. Тем временем Picard — мой ведущий агент, который принимает критически важные архитектурные решения, — застрял за Ralph, фоновым агентом периодического опроса, который съел оставшиеся автодополнения Copilot на низкоприоритетный разбор issue.

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

Главный вывод:

Ограничение скорости запросов в мультиагентных системах — это проблема координации, а не повторных попыток.

Все инструменты, которые я смотрел, — Azure API Management, Resilience4j, LangGraph — рассматривают ограничение частоты запросов как задачу, которую каждый вызывающий компонент решает самостоятельно. Но когда 9 агентов делят одни и те же API-квоты, независимая логика повторных попыток не просто ломается. Она активно ухудшает ситуацию.

Три режима отказа

Прежде чем что-то проектировать, мне нужно было понять, почему стандартная логика повторных попыток перестаёт работать. По логам я выделил три паттерна, которые проявлялись по мере масштабирования системы:

  1. Эффект «стада» при повторных запросах (Thundering Herd)

После 429 все агенты ждут одно и то же время из Retry-After и одновременно повторяют запрос. Снова сталкиваются, снова получают 429. В моих логах ralph-self-heal.log показывал больше 60 связанных отказов в рамках одного инцидента. Классическая проблема распределённых систем — только в роли «сервисов» здесь выступают AI-агенты, которые ничего не знают друг о друге.

2. Инверсия приоритетов (Priority Inversion)

Фоновый polling Ralph — проверка новых GitHub issue каждые 5 минут — съедал API-квоту, которая была нужна Picard для блокирующих архитектурных решений. У обоих агентов был одинаковый приоритет повторных попыток. Не было способа сказать: «Picard идёт первым», — поэтому критически важная работа ждала за фоновым шумом.

3. Каскадное усиление (Cascade Amplification)

Одно срабатывание GitHub secondary rate limit заставило несколько агентов поставить отложенную работу в очередь. Когда лимит снялся, они все одновременно сбросили свои очереди — и тут же снова упёрлись в лимит. Один 429 превратился в системный отказ, восстановление после которого занимало до 60 минут.

6 паттернов, которые я спроектировал

Опираясь на изученные материалы и собственные наблюдения, я спроектировал Rate Governor — координационный слой, с которым все агенты сверяются перед API-вызовами. Вот шесть паттернов внутри него: каждый напрямую отвечает на один из режимов отказа, который я наблюдал или ожидал увидеть по мере масштабирования системы.

Паттерн 1: троттлинг по принципу светофора

Что сломалось: агенты начинали реагировать только после того, как упирались в 429. К этому моменту всё окно квоты уже было исчерпано. Восстановление означало ожидание до 60 секунд, пока все агенты простаивали.

Что я понял: при прямых API-вызовах, например через gh api или REST-клиенты, каждый ответ содержит заголовки x-ratelimit-remaining и x-ratelimit-reset. Их просто никто не читал. Примечание: эти заголовки не доступны напрямую при использовании Copilot CLI с -p; этот паттерн применим, когда вы работаете с API напрямую.

Я добавил систему «светофора», которая после каждого API-вызова читает оставшуюся квоту и меняет поведение до того, как система упрётся в стену:

Зона

Когда

Что происходит

🟢 Зелёная

Осталось >40% квоты

Обычная работа

🟡 Жёлтая

Осталось 15–40%

Добавляются пропорциональные задержки: первыми замедляются фоновые агенты

🔴 Красная

Осталось <15%

Фоновые агенты паркуются. Обычные агенты замедляются до 1 запроса в секунду. Критические агенты проходят дальше.

Вот как выглядит разбор заголовков для GitHub REST API, который возвращает стандартные заголовки x-ratelimit-*:

# Read rate-limit state from API response headers
$remaining = [int]$response.Headers["x-ratelimit-remaining"]
$limit     = [int]$response.Headers["x-ratelimit-limit"]
$resetAt   = $response.Headers["x-ratelimit-reset"]

$ratio = $remaining / $limit

if ($ratio -ge 0.40) {
    # GREEN — no throttling
} elseif ($ratio -ge 0.15) {
    # AMBER — proportional delay for non-critical agents
    $delayMs = 2000 * (0.40 - $ratio) / 0.25
    Start-Sleep -Milliseconds $delayMs
} else {
    # RED — park background agents, slow standard agents
    if ($Priority -eq 2) { return "PARKED" }
    if ($Priority -eq 1) { Start-Sleep -Seconds 1 }
    # P0 passes through immediately
}

Ключевой вывод: не ждите 429, чтобы понять, что квота закончилась. Заголовки сообщают об этом за 10 вызовов до удара о стену. Читайте их.

Паттерн 2: общий пул токенов

Что сломалось: все агенты делят API-квоты — 80 автодополнений в час у Copilot, 5000 запросов в час у GitHub REST, — но учитывали расход независимо друг от друга. Когда Ralph простаивал, Picard не мог занять его неиспользованную долю. Когда Ralph был занят сортировкой issue, он вызывал голодание генерации кода у Data.

Что я понял: агентам нужен общий журнал учёта. Я создал rate-pool.json — единый файл с блокировкой, который отслеживает общую квоту, мягкие резервы на каждого агента и реестр пожертвований, куда простаивающие агенты возвращают неиспользованную ёмкость.

// rate-pool.json
{
  "github": {
    "window_completions_total": 80,
    "window_completions_remaining": 48,
    "agent_allocations": {
      "picard": { "reserved": 20, "used": 8 },
      "ralph":  { "reserved": 12, "used": 2 },
      "data":   { "reserved": 20, "used": 18 }
    },
    "donation_pool": 10
  }
}

Правила простые:

  • P0-агенты — Picard и Worf — всегда получают автодополнения, если хоть что-то осталось.

  • P1-агенты — Data и Seven — используют свой резерв, а затем берут ресурс из donation pool.

  • P2-агенты — Ralph — уступают, когда пул опускается ниже 30% ёмкости.

  • Простаивающие агенты автоматически возвращают неиспользованные резервы обратно в пул.

  • Защита от голодания: любой P2-агент, которому отказывали 5+ минут, повышается до P1.

  • Циклического ожидания нет: агент либо сразу получает автодополнения, либо уступает и пробует снова на следующем круге. Дедлоки невозможны.

Ключевой вывод: относитесь к API-квоте как к общему банковскому счёту, а не как к отдельным кошелькам. Простаивающие агенты должны делиться, критические — уходить в овердрафт.

Паттерн 3: предиктивный Circuit Breaker

Что сломалось: мой существующий Circuit Breaker открывался только после получения 429. Это как включать пожарную тревогу, когда здание уже горит. Квота уже закончилась, а восстановление означало ожидание всего окна cooldown.

Что я понял: исчерпание квоты можно предсказать заранее. Если вы сжигаете 1000 токенов в секунду, а осталось 2000, у вас есть 2 секунды — недостаточно, чтобы следующий запрос агента успел завершиться.

Я добавил в Circuit Breaker состояние PRE-EMPTIVE_OPEN:

Перед тем как полностью переключать модель, Circuit Breaker сначала пытается снизить нагрузку на той же модели: уменьшает max_tokens, сжимает промпты. И только если это не помогает, идёт вниз по цепочке fallback-моделей:

claude-sonnet-4.6 → gpt-5.4-mini → gpt-5-mini → gpt-4.1

Ключевой вывод: разница между «нас заблокировали на 10 минут» и «мы аккуратно деградировали на 30 секунд» — в прогнозировании. Если видите, что впереди стена, можно затормозить, а не врезаться.

Паттерн 4: детектор каскадов

Что сломалось. В Squad рабочие процессы последовательные: Picard принимает архитектурное решение, Data его реализует, Belanna выкатывает, Neelix объявляет. Попадание в лимит на любом этапе блокировало всё ниже по цепочке. Но ни один агент не знал о своих зависимостях.

Что я понял: нужен граф зависимостей. Когда один агент упирается в rate limit, все downstream-агенты должны узнать об этом до следующего вызова.

Когда 3+ агента попадают под rate limit в течение 30-секундного окна, детектор каскадов переключает систему в последовательный режим: агенты берут упорядоченную блокировку и идут по одному, а не все разом. Это мгновенно убивает thundering herd.

Workflow DAG я описываю в простом конфиге:

# backpressure.yaml
workflows:
  issue-to-deploy:
    - ralph      # triage
    - picard     # architecture
    - data       # implementation
    - belanna    # deployment
    - neelix     # announcement
  cascade_threshold: 3  # agents hit in 30s triggers sequential mode

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

Паттерн 5: очистка на основе lease

Что сломалось: когда агент падал в середине раунда, его резерв в общем пуле так и не освобождался. Даже за пару недель работы я заметил, что фантомные резервирования начали накапливаться: агентам отказывали в автодополнениях, хотя реальная API-квота ещё была доступна. В масштабе это стало бы намного хуже.

Что я понял: у каждого резервирования должен быть lease со сроком действия. Я помечаю каждый резерв временной меткой и связываю его с heartbeat агента. Фоновая очистка каждые 30 секунд проверяет:

# Reclaim tokens from dead agents
$heartbeatFiles = Get-ChildItem "$env:SQUAD_DIR/heartbeats/*.json"
foreach ($hb in $heartbeatFiles) {
    $agent = $hb.BaseName
    $lastBeat = (Get-Content $hb.FullName | ConvertFrom-Json).timestamp
    $staleness = (Get-Date) - [datetime]$lastBeat

    if ($staleness.TotalMinutes -gt 2) {
        # Agent is dead — reclaim its tokens
        $pool = Get-Content "rate-pool.json" | ConvertFrom-Json
        $unused = $pool.github.agent_allocations.$agent.reserved -
                  $pool.github.agent_allocations.$agent.used
        $pool.github.donation_pool += [Math]::Max(0, $unused)
        $pool.github.agent_allocations.$agent.reserved = 0
        $pool | ConvertTo-Json -Depth 5 | Set-Content "rate-pool.json"
        Write-Host "♻️ Reclaimed $unused tokens from crashed agent: $agent"
    }
}

Это напрямую подключается к уже существующему в Squad ralph-heartbeat.ps1: heartbeat-файлы там уже были. Я просто начал их читать.

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

Паттерн 6: приоритетные окна повторных попыток

Что сломалось: стандартная формула AWS exponential backoff with jitter считает всех вызывающих равными. Когда Picard, отвечающий за критические архитектурные решения, и Ralph, занимающийся фоновым polling, одновременно получают 429, они оба повторяют запрос в одном и том же случайном окне. Ralph может просто удачно попасть в окно и забрать квоту раньше Picard. Это и есть priority inversion.

Что я понял: каждому уровню приоритета нужно дать своё непересекающееся окно повторных попыток. P0 повторяет первым. P1 — после того, как P0 закончил. P2 идёт последним.

Приоритет

Агенты

Окно повторной попытки

P0 Critical

Picard, Worf

0–0,5 с

P1 Standard

Data, Seven, Belanna, Troi, Neelix

0,5–3,5 с

P2 Background

Ralph, Scribe

3,5–9,5 с

Это гарантирует, что P0-агенты потребят доступную квоту ещё до того, как P1-агенты вообще начнут повторять запросы. Priority inversion становится структурно невозможной.

function Get-RetryDelay {
    param(
        [int]$RetryAfterSeconds,
        [int]$Attempt,
        [int]$Priority  # 0=critical, 1=standard, 2=background
    )

    # Base delay from Retry-After header (or exponential backoff)
    if (-not $RetryAfterSeconds) {
        $RetryAfterSeconds = [Math]::Min(60, [Math]::Pow(2, $Attempt))
    }

    # Non-overlapping priority windows
    switch ($Priority) {
        0 { $windowStart = 0;    $windowEnd = 0.5  }  # P0: first 500ms
        1 { $windowStart = 0.5;  $windowEnd = 3.5  }  # P1: 500ms–3.5s
        2 { $windowStart = 3.5;  $windowEnd = 9.5  }  # P2: 3.5s–9.5s
    }

    $jitter = Get-Random -Minimum 0 -Maximum (($windowEnd - $windowStart) * 1000)
    return $RetryAfterSeconds + $windowStart + ($jitter / 1000.0)
}

Ключевой вывод: стандартный jitter считает всех вызывающих равными. В мультиагентной системе это не так. Разведите окна повторных попыток по приоритетам — и проблема исчезнет.

Полная архитектура

Все шесть паттернов сходятся в общем Rate State Store — паре JSON-файлов, rate-pool.json и rate-state.json, с файловой блокировкой. Каждый агент читает состояние перед API-вызовом и записывает его после получения ответа. Центральный сервер не нужен — это кооперативная координация через файловую систему.

Важная оговорка: такой подход на файлах работает на одной машине или на общей файловой системе со строгой POSIX-семантикой. Для случая с несколькими узлами см. паттерн 7 ниже.

┌─────────────────────────────────────────────────┐
│              Squad Rate Governor                │
│                                                 │
│  ┌──────────┐ ┌──────────┐ ┌──────────────────┐│
│  │ Traffic  │ │ Shared   │ │ Lease-Based      ││
│  │ Light    │ │ Token    │ │ Cleanup          ││
│  │ Throttle │ │ Pool     │ │ (heartbeat-tied) ││
│  └────┬─────┘ └────┬─────┘ └────────┬─────────┘│
│       │             │                │          │
│       ▼             ▼                ▼          │
│  ┌─────────────────────────────────────────┐    │
│  │  Rate State Store                       │    │
│  │  rate-pool.json · rate-state.json       │    │
│  └────────────┬────────────────────────────┘    │
│               │                                 │
│       ┌───────┼───────┐                         │
│       ▼       ▼       ▼                         │
│  ┌────────┐ ┌──────┐ ┌───────────┐              │
│  │Cascade │ │Retry │ │Predictive │              │
│  │Detector│ │Window│ │Circuit    │              │
│  │        │ │      │ │Breaker    │              │
│  └────────┘ └──────┘ └───────────┘              │
└─────────────────────────────────────────────────┘
         │          │          │
         ▼          ▼          ▼
    GitHub API   GitHub Copilot  Azure OpenAI

Паттерн 7: когда одной машины уже мало

Здесь нужно быть честным: описанный выше файловый Rate State Store работает только на одном узле. Если вы запускаете Squad на своей dev-машине или на одной Azure VM, всё в порядке. Но как только вы масштабируетесь до нескольких pod’ов в AKS или отдельных VM, весь дизайн ломается.

Почему файловые блокировки не работают между узлами

Паттерны выше опираются на три вещи:

  1. POSIX-файловые блокировки, которые гарантируют взаимное исключение при доступе к rate-pool.json.

  2. Heartbeat-файлы, по которым можно понять, что агент упал, и вернуть его токены в пул.

  3. Немедленную согласованность: когда агент A записал состояние пула, агент B сразу читает обновлённое состояние.

На одной машине все три пункта работают. На нескольких машинах — ни один.

  • Файловые блокировки не распространяются надёжно через сетевые файловые системы. У NFS есть lockd и statd, но семантика блокировок ненадёжна при сетевых разделениях. Azure Files поддерживает SMB-блокировки, но там eventual consistency, а не атомарность.

  • Heartbeat-файлы локальны. Каждый pod пишет в свою файловую систему. Без сервиса координации нет общего представления о том, какие агенты всё ещё живы.

  • Нет fencing tokens. Если pod оказывается в сетевом разделении, он всё ещё может считать, что владеет токенами, и продолжать писать в общее состояние, портя пул устаревшими данными.

  • Eventual consistency на сетевой ФС означает устаревшие чтения. Агент A записывает, что потратил 10 токенов. Агент B через 2 секунды читает старое значение. Оба агента думают, что квота у них есть. Оба вызывают API. 429.

Что я бы использовал для multi-node Squad

Если бы мне понадобилось запустить Squad на нескольких pod’ах в AKS — пока не нужно, я всё ещё на одной машине, — я бы смотрел в сторону таких вариантов.

Вариант 1: Redis как Rate State Store

Почему это работает:

  • Атомарные операции INCR, DECR, GETSET гарантируют отсутствие race condition.

  • TTL на ключах даёт автоматическое истечение lease без ручной очистки heartbeat.

  • Pub/sub-каналы позволяют мгновенно распространять сигналы backpressure.

  • Redis уже давно обкатан для распределённого ограничения частоты запросов — см. реализации Stripe, GitHub, Shopify.

Что меняется:

  • Заменяем rate-pool.json на Redis-хеши: HSET rate:pool github:remaining 48.

  • Заменяем файловые блокировки Redis-транзакциями: MULTI/EXEC.

  • Heartbeat превращаются в Redis-ключи с TTL: SET heartbeat:picard alive EX 30.

  • Детектор каскадов использует Redis pub/sub: PUBLISH backpressure:github "429 detected".

Набросок кода:

# Резервирование токенов осуществляется атомарно.
redis-cli --eval reserve-tokens.lua github picard 10
# Скрипт Lua гарантирует, что операции INCR и HSET выполняются как одна атомарная операция.

Скорее всего, в Azure я бы использовал Valkey, форк Redis: он OSS и хорошо поддерживается.

Вариант 2: etcd для распределённых блокировок

Почему это работает:

  • Он уже работает в AKS-кластерах: на нём держится сам Kubernetes.

  • Строгие гарантии согласованности через Raft consensus.

  • Lease-блокировки с автоматическим истечением.

  • Watch API для распространения изменений состояния.

Что меняется:

  • Заменяем rate-pool.json на key-value store в etcd.

  • Используем механизм lease в etcd для heartbeat и резервирования токенов.

  • Следим за изменениями /rate-pool/github/remaining, чтобы обнаруживать исчерпание квоты.

  • Используем транзакции etcd для атомарного compare-and-swap при выделении токенов.

Компромисс: etcd тяжелее Redis и оптимизирован под конфигурацию, а не под высоконагруженные счётчики. Но если я уже в AKS, он там есть, и мне не нужен ещё один сервис.

Вариант 3: Sidecar / DaemonSet Pattern

Почему это работает:

  • Запускаем по одному «rate governor» на каждый узел AKS как DaemonSet.

  • Все агенты на этом узле обращаются к локальному governor: быстро, без сетевых походов.

  • Governors координируются централизованно через Redis или etcd, но агрегируют запросы локально.

  • Это снижает накладные расходы на координацию: общаются только N governors, а не N×M агентов.

Что меняется:

  • Каждый агент вызывает http://localhost:8080/reserve-tokens, то есть локальный sidecar.

  • Sidecar держит локальный мягкий резерв, например 20 токенов на узел.

  • Когда локальный пул заканчивается, sidecar запрашивает ещё из центрального Redis-пула.

  • Heartbeat — это живость процесса sidecar, с этим уже работает Kubernetes.

Компромисс: больше сложности — ещё один сервис для деплоя, — но гораздо лучше производительность в масштабе. Так работают крупные API Gateway, например Envoy и Istio.

Что я реально делаю сейчас

Сейчас я запускаю Squad на одной машине. Подход на файлах работает отлично и намного проще, чем поднимать Redis или etcd только ради координации ограничения частоты запросов. Когда я дойду до точки, где понадобится multi-node Squad — скорее всего, когда начну запускать несколько customer-инстансов или крупномасштабное нагрузочное тестирование, — я мигрирую на вариант 1: Valkey в Azure. Это самый естественный вариант для частых обновлений счётчиков, и у него уже есть проверенные паттерны multi-tenant rate limiting от GitHub и Stripe.

Вывод: начните с простого. Выпустите файловую версию. Когда перерастёте одну машину, мигрируйте на распределённое состояние. Не стройте распределённую инфраструктуру раньше, чем она действительно понадобится.


5 вещей, которые можно сделать уже сегодня

Если вы запускаете несколько AI-агентов с общей API-квотой, вот практический чеклист:

  1. Читайте заголовки rate limit

    Каждый ответ от GitHub REST API и Azure OpenAI содержит x-ratelimit-remaining. Парсите его. Логируйте. Реагируйте до того, как получите 429. Это бесплатно и реализуется за 20 минут. Примечание: это относится к прямым API-вызовам, а не к Copilot CLI с -p, где заголовки напрямую не доступны.

  2. Назначьте агентам уровни приоритета

    Не все агенты равны. Мой агент, принимающий архитектурные решения, не должен конкурировать с фоновым poller’ом. Я определил уровни P0 — критические, P1 — обычные, P2 — фоновые — и развёл окна повторных попыток соответственно.

  3. Разделите состояние квоты между агентами

    Если агенты отслеживают расход независимо, они будут превышать квоту. Общего JSON-файла с файловой блокировкой достаточно для старта. В первый день мне не понадобились ни Redis, ни отдельный сервис-координатор.

  4. Добавьте срок действия для резервирований

    Если агенты могут падать — а они будут, — у каждого резервирования токенов должен быть TTL. Мёртвые агенты не должны держать квоту в заложниках. Привяжите это к heartbeat-файлу: heartbeat остановился — возвращайте токены.

  5. Опишите цепочку зависимостей между агентами

    Какие агенты зависят от результата работы других агентов? Я записал это явно. Когда один агент упирается в rate limit, я передаю сигнал backpressure всему, что находится ниже по цепочке, прежде чем эти агенты зря потратят собственные API-вызовы.

Ссылки

Эти паттерны не появились из воздуха — это адаптации хорошо известных концепций из распределённых систем к контексту мультиагентного AI:

  • Circuit Breaker Pattern — Michael Nygard, Release It! (2007/2018). Оригинальная формулировка паттерна. Также реализован в Resilience4j для Java и Polly для .NET.

  • Token Bucket / Leaky Bucket — классические алгоритмы rate limiting (ограничение частоты запросов). Для хорошего практического введения см. пост Stripe про rate limiter.

  • Thundering Herd — хорошо описанная проблема в литературе по распределённым системам. Статья AWS про exponential backoff and jitter — стандартная отправная точка.

  • Priority Inversion — изначально концепция из ОС реального времени, см. баг Mars Pathfinder. Я адаптировал её к планированию API-квот.

  • Backpressure — концепция из реактивных систем и Reactive Manifesto. Также центральная тема в Reactive Extensions (Rx), о которых я писал в Rx.NET in Action.

  • Lease-Based Resource Management — вдохновлено Chubby, распределённым lock-сервисом Google, и механизмами lease в etcd.

  • GitHub API Rate Limiting — документация GitHub REST API по rate limits. Заголовки x-ratelimit-remaining описаны именно там.

  • Обсуждение на Reddit — тред на r/GithubCopilot, где другие пользователи сообщали о похожих проблемах с rate limit в мультиагентных системах.

  • Adaptive Rate Limiting with Deep RL — arXiv 2511.03279. Исследование multi-objective adaptive rate limiting в микросервисах с помощью deep reinforcement learning. Показывает прирост пропускной способности на 15–30% по сравнению со статическими алгоритмами.

  • Lamport, L. (1978) — “Time, Clocks, and the Ordering of Events in a Distributed System”, Communications of the ACM. Фундаментальная работа: координация мультиагентных систем — по сути та же проблема, только с узлами в форме LLM.

  • Resilient Microservices: A Systematic Review of Recovery Patterns — arXiv 2512.16959. Большой обзор паттернов восстановления в распределённых системах.

  • Patterns of Distributed Systems — каталог Martin Fowler и книга Unmesh Joshi, Addison-Wesley, 2024.

Сейчас я экспериментирую с этими паттернами на нескольких DevBox и AKS-кластерах. Squad управляет 8–12 автономными AI-агентами, которые занимаются ревью кода, архитектурными решениями, деплоем инфраструктуры, исследованиями и коммуникациями. Паттерны, описанные здесь, — это то, к чему я веду систему по мере масштабирования.

Тему координации AI-агентов и устойчивости распределённых систем можно продолжить на бесплатных уроках. Преподаватели-практики покажут свои подходы, а участники смогут познакомиться с форматом обучения и задать свои вопросы:

  • 15 июня, 20:00. «Интеграция ИИ-агентов в рабочую разработку: обвязка агента навыками и MCP». Записаться

  • 24 июня, 20:00. «Инцидент-менеджмент в SRE. Как быстро находить, устранять и предотвращать сбои в системе». Записаться

Полный список бесплатных уроков июня смотрите в дайджесте.