Почему ваш AI-агент однажды просто перестанет отвечать
Вы сидите за терминалом, запускаете claude или cursor, а в ответ — тишина. Или, что еще хуже, — 429 Too Many Requests. Знакомо? Если вы активно используете AI-агентов для разработки (Claude Code, Cursor, Codex, Kimi), то рано или поздно упираетесь в блокировку аккаунта по IP или API-лимиту. Провайдеры видят, что с одного адреса идет подозрительно много запросов, и банально отрубают доступ.
В 2026 году эта проблема стала еще острее. Anthropic, OpenAI, Cursor, Moonshot (Kimi) ужесточили антифрод-политики. Обычные прокси и VPN не спасают — их IP-пулы тоже в черных списках. Нужна архитектура, которая:
- Разделяет трафик между несколькими аккаунтами.
- Позволяет переключать провайдера на лету.
- Работает на вашем VPS, а не через публичные сервера.
- Дает единый UI для управления агентами.
Я прошел через это. Собрал Planulix — мультиклиентский центр управления AI-агентами на Go (backend) и Flutter (frontend). Сегодня разберу архитектуру магистраль и покажу, как вы можете развернуть такой же за вечер. Кстати, если вы еще не знакомы с концепцией AI-агентов в UI, советую глянуть статью CopilotKit и AG-UI — там похожий протокол, но для веба.
Анатомия Planulix: три слоя, которые держат удар
Planulix состоит из трех компонентов:
- Go Gateway — высокопроизводительный шлюз, который маршрутизирует запросы к API разных провайдеров, балансирует нагрузку и ротирует ключи.
- Flutter Client — десктопное и мобильное приложение для управления агентами, просмотра логов и переключения профилей.
- CLI-обертка — агностичный интерфейс для запуска локальных агентов (Claude Code, Cursor CLI) через шлюз.
Все это живет на вашем VPS. Никаких зависимостей от облачных сервисов — только ваш сервер и ваши ключи. Вдохновлялся архитектурой из Plano 0.4.3, но адаптировал под мультиклиент.
Суть: вместо того чтобы каждый AI-агент сам ходил в API, все запросы идут через Planulix Gateway. Шлюз решает, какой провайдер ответит, и возвращает результат. Клиент (Flutter или CLI) при этом может переключать профили без перезапуска.
Строим Go Gateway: сердце Planulix
Go идеально подходит для такой задачи: легковесные горутины, быстрый JSON, встроенный HTTP/2, простое развертывание в один бинарник. Я использовал Go 1.23 (на момент 2026 года уже Go 1.25, но код обратно совместим).
1 Архитектура конфигурации
Конфигурация — это YAML-файл, где описаны провайдеры, их API-эндпоинты и ключи. Почему YAML, а не JSON? Потому что YAML с комментариями удобнее читать и править на сервере.
# /etc/planulix/gateway.yaml
providers:
claude:
type: anthropic
base_url: "https://api.anthropic.com/v1"
keys:
- "sk-ant-...1"
- "sk-ant-...2"
rate_limit: 10 # запросов в секунду
cursor:
type: cursor
base_url: "https://api.cursor.sh"
keys:
- "cur-...a"
rate_limit: 5
codex:
type: openai
base_url: "https://api.openai.com/v1"
keys:
- "sk-...x1"
- "sk-...x2"
rate_limit: 20
kimi:
type: moonshot
base_url: "https://api.moonshot.cn/v1"
keys:
- "mk-...k1"
rate_limit: 3
Шлюз читает конфиг при старте и может перезагружаться по сигналу SIGHUP. Никакого даунтайма.
2 Маршрутизация запросов
Каждый входящий запрос должен содержать заголовок X-Planulix-Provider (или дефолтный из профиля). Шлюз проверяет лимиты, выбирает ключ по round-robin или least-used и отправляет запрос к нужному провайдеру. Ответ проксируется обратно.
Вот фрагмент ядра на Go — не копируйте слепо, а вникайте в логику:
package gateway
import (
"context"
"net/http"
"sync"
"time"
)
type Provider struct {
Name string
BaseURL string
Keys []string
KeyIndex int
mu sync.Mutex
Limiter *RateLimiter
}
func (p *Provider) NextKey() string {
p.mu.Lock()
defer p.mu.Unlock()
p.KeyIndex = (p.KeyIndex + 1) % len(p.Keys)
return p.Keys[p.KeyIndex]
}
type RateLimiter struct {
tokens chan struct{}
closeCh chan struct{}
}
func NewRateLimiter(rps int) *RateLimiter {
rl := &RateLimiter{
tokens: make(chan struct{}, rps),
closeCh: make(chan struct{}),
}
go func() {
ticker := time.NewTicker(time.Second / time.Duration(rps))
defer ticker.Stop()
for {
select {
case <-ticker.C:
select {
case rl.tokens <- struct{}{}:
default:
}
case <-rl.closeCh:
return
}
}
}()
return rl
}
func (rl *RateLimiter) Wait(ctx context.Context) error {
select {
case <-rl.tokens:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// ProxyHandler - основной обработчик
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
providerName := r.Header.Get("X-Planulix-Provider")
if providerName == "" {
providerName = "claude" // default
}
provider := getProvider(providerName)
if provider == nil {
http.Error(w, "provider not found", http.StatusBadRequest)
return
}
if err := provider.Limiter.Wait(r.Context()); err != nil {
http.Error(w, "rate limit", http.StatusTooManyRequests)
return
}
key := provider.NextKey()
// ... клонируем запрос, подменяем Authorization, проксируем
}
Здесь два важных момента. Первый — RateLimiter работает на токенах с фиксированной скоростью, а не на окнах. Это дает равномерную нагрузку, что снижает риск блокировки. Второй — выбор ключа round-robin. Каждый ключ используется циклически, так что ни один аккаунт не перегревается.
Типичная ошибка: не добавлять контекстную таймауты. Если провайдер завис, ваша горутина будет ждать вечно, а клиенты получат 502. Всегда ставьте таймаут не более 30 секунд.
3 Health-чеки и автоотключение
Провайдеры иногда возвращают ошибки аутентификации (401) или превышение квоты (403). Шлюз должен это отслеживать и временно выводить ключ из ротации. Добавил счетчик ошибок: если за последние 5 минут больше 10 неудач — ключ в blacklist на 30 минут.
func (p *Provider) RecordFailure(keyIdx int) {
p.mu.Lock()
defer p.mu.Unlock()
// увеличиваем счетчик ошибок для конкретного ключа
}
func (p *Provider) IsKeyHealthy(keyIdx int) bool {
// проверяем, не превышен ли порог
}
Flutter Client: единое окно во все агенты
Зачем Flutter, если можно сделать веб-интерфейс? Потому что Flutter дает нативный опыт на десктопе (Windows, macOS, Linux) и мобилках. Вы можете мониторить агенты с телефона, а CLI-обертка работает на сервере. Planulix Client подключается к Go Gateway через gRPC или REST (я выбрал REST для простоты).
Архитектура Flutter-приложения
- Слой моделей: ProviderConfig, Session, LogEntry.
- Слой репозитория: абстракция над HTTP-клиентом (Dio).
- BLoC для управления состоянием: переключение провайдера, просмотр логов, редактирование профилей.
- UI: Material 3 с кастомными темами (адаптация под светлую тему, как у нас на сайте).
Ключевая особенность — пул соединений. Gateway поддерживает долгие WebSocket-соединения для стриминга ответов от AI-агентов. Flutter открывает WebSocket и получает чанки ответа в реальном времени, отображая их в терминальном окне.
4 Пример виджета для переключения провайдера
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ProviderSwitcher extends StatelessWidget {
const ProviderSwitcher({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GatewayCubit, GatewayState>(
builder: (context, state) {
return DropdownButton<String>(
value: state.currentProvider,
items: state.availableProviders.map((p) {
return DropdownMenuItem(value: p, child: Text(p));
}).toList(),
onChanged: (provider) {
if (provider != null) {
context.read<GatewayCubit>().switchProvider(provider);
}
},
);
},
);
}
}
CLI-обертка и интеграция с локальными агентами
Чтобы ваш локальный Claude Code или Cursor CLI общались через Planulix, нужно переопределить их API-эндпоинт. У Claude Code есть флаг --api-base или переменная окружения ANTHROPIC_BASE_URL. Мы создаем простой shell-wraper:
#!/bin/bash
# planulix-wraper.sh
# Запускает Claude Code через Planulix Gateway
export ANTHROPIC_BASE_URL="http://localhost:9090/anthropic"
export ANTHROPIC_API_KEY="dummy-key" # шлюз игнорирует, берет свой
# Опционально: указать провайдер через env
export PLANULIX_PROVIDER="claude"
claude "$@"
Для Cursor это сложнее — он использует WebSocket и свой протокол. Пришлось написать небольшой reverse-proxy в Go, который перехватывает handshake и добавляет заголовок X-Planulix-Provider. Детали выходят за рамки статьи, но код выложу в репозиторий.
Развертывание на VPS: от греха подальше
Я арендовал дешевый VPS на Hetzner (2 ядра, 4 ГБ RAM) с Ubuntu 24.04. Planulix Gateway и Flutter-бэкенд (веб-версия) упаковал в Docker. Но можно и без контейнеров — бинарник на Go не требует зависимостей.
5 Docker Compose
version: '3.8'
services:
gateway:
build: ./gateway
ports:
- "9090:9090"
volumes:
- ./config:/etc/planulix
environment:
- PLANULIX_CONFIG_PATH=/etc/planulix/gateway.yaml
restart: unless-stopped
web:
build: ./flutter_client/build/web
ports:
- "8080:80"
depends_on:
- gateway
После старта проверьте, что шлюз отвечает:
curl -H "X-Planulix-Provider: claude" http://your-vps:9090/v1/health
Если ответ {"status":"ok"} — все работает.
Важно: не забудьте настроить ufw, чтобы порты были открыты только для ваших IP. Иначе через день шлюз завалят боты. Я использую fail2ban с кастомной джиттер-задержкой.
Как НЕ надо: частые ошибки при сборке мультиклиента
- Отсутствие retry с экспоненциальной задержкой. Если провайдер вернул 429, вы должны подождать и повторить с другим ключом. Без этого клиент будет падать.
- Жесткая привязка к одному провайдеру. Я изначально захардкодил Claude Code. Когда Anthropic заблокировал мой ключ, пришлось экстренно добавлять Codex. Сделайте провайдеры плагинными с самого начала.
- Синхронная блокировка UI во Flutter. Никогда не делайте HTTP-вызовы в основном потоке. Используйте BLoC или Riverpod для асинхронной работы.
- Хранение ключей в репозитории. Ключи должны быть в переменных окружения или Vault, а не в коде. Хотя бы .gitignore.
Что дальше? План развития Planulix
Сегодня Planulix умеет маршрутизировать запросы, балансировать по ключам, показывать логи и статистику в Flutter UI. В планах на Q3 2026:
- Поддержка OpenRouter (как в DeepSeek TUI).
- Кастомные промпты-префиксы для каждого провайдера.
- Кэширование ответов на уровне шлюза для идентичных запросов.
Если тема AI-агентов на десктопе интересна, почитайте как превратить Ollama в персонального ассистента — это частично пересекается с мультиклиентской идеей.
Финальный совет: не верьте в «один ключ навсегда»
Мир AI-агентов меняется каждые полгода. Провайдеры банят аккаунты за превышение лимитов, за слишком частые запросы, за подозрительную активность. Единственный способ не остаться без инструмента — иметь запасные ключи и уметь мгновенно переключаться между ними. Planulix дает вам эту возможность.
Соберите свой центр управления сегодня. Начните с малого — поднимите шлюз на Go, добавьте пару ключей от Claude и Codex, настройте Flutter-клиент. Через неделю вы не сможете представить работу без него.
P.S. Весь код Planulix (пока в черновике) я выложу на GitHub к концу месяца. Если хотите получить уведомление — подпишитесь на блог.