Files
knowledge-base/decisions/2026-06-08-swarmclaw-lxc135-deploy.md

26 KiB
Raw Blame History

date, type, tags
date type tags
2026-06-08 decision
dttb
swarmclaw
openclaw
orchestrator
lxc
docker

SwarmClaw — оркестратор-надстройка над openclaw (LXC 135)

Контекст

Олег искал «аналог openclaw — оркестратор + удобный + самообучение». По ходу выяснилось:

  1. У openclaw самообучение уже работает (Dreaming, embeddings ready, recall с concept-tagging + spaced repetition — см. правку openclaw). Менять движок не нужно.
  2. SwarmClaw (github.com/swarmclawai/swarmclaw) — это не замена, а control-plane НАД openclaw: рой агентов, делегирование, расписания, дашборд, и подключение существующих openclaw-gateway. Ровно «оркестратор» из запроса, без миграции стека.

Решение: поставить SwarmClaw на Proxmox (не Mac-app — тот умирает с ноутом) как постоянный дирижёр над Антошкой.

Где живёт

Параметр Значение
Proxmox LXC 135 (hostname swarmclaw)
IP 10.0.0.135/24, gw 10.0.0.1
ОС Debian 12, unprivileged + nesting=1,keyctl=1
Ресурсы 2 vCPU / 4 GB RAM / 16 GB (rootfs на work)
Рантайм Docker 29.5 + compose, образ ghcr.io/swarmclawai/swarmclaw:latest
Каталог /opt/swarmclaw (репо), данные volume ./data:/app/data
Доступ pct exec 135 -- bash; dashboard http://10.0.0.135:3456

Доступ к UI/API

  • URL: http://10.0.0.135:3456 (LAN/NetBird), слушает 0.0.0.0:3456-3457.
  • ACCESS_KEY: OL260380eg (сменён 2026-06-12 по просьбе Олега; первичный 4613e7d0… больше не действует) — он же cookie sc_auth для API. Хранится в /opt/swarmclaw/.env.local; смена = правка файла + docker compose up -d --force-recreate + repatch-ctxwin.sh.
  • Auth: POST /api/auth {"key":"..."} → cookie, либо header Cookie: sc_auth=<KEY>. getAccessKey()=process.env.ACCESS_KEY, validateAccessKey сравнивает строкой.

Что настроено

  1. Провайдер OmniRoute (custom, OpenAI-compatible):
    • baseUrl=http://10.0.0.179:20128/v1, requiresApiKey:false (внутренний, отдаёт каталог без ключа).
    • Каталог 58 моделей; инференс cc/claude-opus-4-8 подтверждён (stream chat.completions).
  2. Агент «Dirizhyor» (id 0d388a87) — provider omniroute, model cc/claude-opus-4-8.
  3. openclaw-gateway как control-plane:
    • credential cred_62caf5d0f0de (provider openclaw, зашифрованный gateway-token).
    • gateway profile gateway-0eaf65b0 «Antoshka (openclaw 137)», endpoint http://10.0.0.239:18789, isDefault:true.
    • openclaw gateway token: 20bfbdfed25b87b211a6faa60db3ab8fac75fb5100958ac8 (gateway.auth.mode=token на LXC 137).
    • device спарен и approved на openclaw (openclaw devices → 10.0.0.135, operator.admin).
    • connect ok:true.

Грабли (важно для будущего)

  1. ACCESS_KEY жил только в /app/.env.local внутри контейнера (не в volume). Пересоздание/обновление образа → новый ключ → запертый вход. Фикс: скопировал ACCESS_KEY (+CREDENTIAL_SECRET если будет) в хостовый /opt/swarmclaw/.env.local (он env_file в compose) → ключ постоянен через пересоздания. CREDENTIAL_SECRET дополнительно лежит в volume data/credential-secret.
  2. Auth rate-limit: 5 неудачных попыток с IP → лок 15 мин (authRateLimitMap, in-memory). Перебор форматов auth (Bearer/x-api-key — оба неверные) триггерит. Правильный auth — только cookie sc_auth. Сброс лока — docker compose up -d --force-recreate (in-memory обнуляется, ключ из env_file сохраняется).
  3. Подключение openclaw требует approve device-pairing. SwarmClaw manualConnect хардкодит useDeviceAuth=true (протокол v4, auth:{token} + device-signature). openclaw в token-mode создаёт pending pairing → пока не сделать openclaw devices approve <requestId> на LXC 137, connect молча {"ok":false}. Token-only handshake (proto4, auth.token, scopes operator.admin) проверен рабочим. После approve — connect ok:true, device в Paired.
  4. /api/openclaw/doctorspawn openclaw ENOENT — в контейнере нет bundled openclaw CLI; связь идёт по WS, не через CLI. Безвредно.
  5. DNS-FakeIP не словили (в отличие от LXC 137): github/docker.com резолвятся честно через 1.1.1.1/8.8.8.8 даже без NetBird на 135. NetBird не ставил.
  6. Локаль в LXC не настроена (locale: Cannot set LC_*) — косметика, при желании locale-gen en_US.UTF-8.
  7. Агент отвечал Error: Missing credentials … OPENAI_API_KEY хотя OmniRoute ключ не требует. Причина: OpenAI-совместимый клиент внутри SwarmClaw отказывается слать запрос без непустого apiKey. Фикс: создать dummy-credential (POST /api/credentials {provider:omniroute, apiKey:"omniroute-noauth-dummy"}cred_0d2feda42f7b) и привязать к провайдеру (requiresApiKey:true + credentialId) И к агенту (PUT /api/agents/<id> — PATCH даёт 405). OmniRoute ключ игнорирует. После — агент Dirizhyor отвечает на Opus 4.8 (проверено: «работает», + сам диагностировал второй агент через tools).
  8. UI «No agents yet» + циклический логин в Chrome = браузер держал старый JS-бандл (в логах Failed to find Server Action), ломались Server Actions (вход и загрузка). Сервер исправен (REST API через NPM HTTPS отдаёт auth+agents). Лечится чистым браузером (Yandex/инкогнито сработали сразу) или Clear site data в Chrome. SW/PWA нет, контейнер стабилен (0 рестартов).
  9. Дефолтный агент Assistant/«Ассистент» упорно падал Claude CLI not found. Корень в коде: agent-runtime-config.tsprovider = seed.provider || 'claude-cli' (хардкод-фоллбек). У default слетал/пустел provider → фоллбек на claude-cli (в контейнере нет). POST /api/agents не даёт задать фиксированный id, PUT /api/agents/default для пустого не помог. Фикс: прямой INSERT OR REPLACE INTO agents в data/swarmclaw.db — id=default, data = копия Dirizhyor (omniroute/opus/cred). App подхватывает без рестарта (loadAgents читает свежо). Теперь оба агента на OmniRoute/Opus.
  10. «Session not found» при тесте через curl — нельзя слать в произвольный agent-chat-* ID; сессия создаётся UI/отдельно. Не баг агента — артефакт диагностики. Реальные сессии (созданные в UI) работают (completed).
  11. Gateway health «не обновлялась 30 мин / статус неизвестно»у RPC /api/openclaw/gateway есть только connect/disconnect/reload-mode; health-поле профиля (status,lastCheckedAt) обновляет фоновый probe, не RPC. gateway.connect{profileId}ok:true (связь рабочая), но индикатор остаётся unknown. Косметика мониторинга, на работу не влияет.

База знаний (vault) подключена к агентам — 2026-06-08

Олег: «подключить базу данных так же, как у code-server и openclaw». SwarmClaw НЕ имеет folder-RAG как openclaw (memorySearch.extraPaths) — его memory это agent-memory (dream/graph). Поэтому путь = как у code-server: агент через файловые tools работает в каталоге vault.

  • Vault склонирован в volume: git clone https://oleg:***@git.dttb.ru/oleg/knowledge-base/opt/swarmclaw/data/knowledge-base (1286 md, 19M). Именно в data/ (volume), иначе контейнер /app/data его не видит.
  • Агенты настроены на vault: PUT /api/agents/<id>workspace=/app/data/knowledge-base (Dirizhyor + Ассистент). workspaceAccess через PUT не сохранился, но не нужен — агент читает через shell/files tools.
  • Проверено: на вопрос про openclaw агент сам сделал grep -ril openclaw /app/data/knowledge-base/projects/dttb → прочитал openclaw.md → ответил точно (LXC 137, 10.0.0.239, 100.70.167.54). RAG-доступ работает.
  • Автообновление: /root/kb-pull-swarm.sh (cron */15 на LXC 135) — git pull vault из Gitea. Как kb-pull у openclaw 137.

Встроенный Knowledge-раздел (доп. путь, по запросу Олега)

SwarmClaw имеет раздел Knowledge (/api/knowledge), и агент использует его автоматически: prompt-builder + selectKnowledgeCitations инжектят релевантные куски в контекст с цитатами (отдельного tool нет; knowledge_search/knowledge_store как tools удалены). Поиск — текстовое сходство (jaccard, не embeddings) — для фактов норм.

  • Импортировано 189 entries (projects, decisions, snippets, claude-memory, templates — без daily/notes шума): скрипт POST /api/knowledge {title,content,sourcePath} по каждому .md. 0 ошибок.
  • Ре-синк: /root/kb-knowledge-sync.sh (cron 30 3 * * *) — git pull + удалить наши entries (по sourcePath префиксу) + импорт заново. Иначе POST плодит дубли (нет upsert).
  • Итог — два механизма: (1) Knowledge entries → авто-контекст с цитатами (UI-раздел); (2) файловый workspace → агент grep'ает всю базу (свежее, */15). Дополняют друг друга.

OmniRoute «out of usage» = баг версии (2026-06-09)

Агенты на cc/claude-opus-4-8 начали падать 400 You're out of extra usage. Оказалось — НЕ лимит Max, а баг устаревшего OmniRoute (стоял 3.8.7, актуально 3.8.16). Обновление вылечило. Грабля апдейта: entry-point переехал app/server.jsdist/server.js → поправлен ExecStart в omniroute.service (иначе crash-loop). Подробно — ../claude-memory/omniroute + memory feedback_omniroute_update. Агенты SwarmClaw остаются на Opus 4.8 (Sonnet Олега не устроил).

iPad/iPhone Safari — чаты не отвечали (2026-06-09)

На Mac (Chromium/Yandex) чаты работали, на iPad Safari открывалось, но ответы агента не приходили. Причина — чат отдаёт ответ через SSE (text/event-stream из POST /api/chats/[id]/chat), а NPM (nginx) по умолчанию буферизирует proxy → Safari iOS/iPadOS строг к буферизированному стриму. Фикс — advanced_config у NPM-хоста #32:

proxy_buffering off;
proxy_request_buffering off;
proxy_set_header X-Accel-Buffering no;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

После reload — чаты на iPad заработали. Покрывает и iPhone. (Применять к любому SSE/стрим-сервису за NPM.)

NetBird-доступ для агентов (2026-06-09)

Чтобы агенты SwarmClaw дотягивались до хостов в NetBird-сети (100.70.x: openclaw, серверы клиентов и т.д.).

  • NetBird-клиент на LXC 135 (host): curl -fsSL https://pkgs.netbird.io/install.sh | sh (v0.72.2). Работает в unprivileged-LXC через nesting=1 + kernel WireGuard (wt0; /dev/net/tun НЕ нужен, как на 137). Autostart (systemctl enable).
  • setup-key создан через API (PAT из этого же файла, группа Claude-Diag d7jra32fadhs73dmqv5g): netbird up --setup-key <KEY>. IP 100.70.95.183, swarmclaw.netbird.cloud.
  • Docker-контейнер дотягивается до 100.70.x БЕЗ доп. настройки — контейнер → default gw (host) → host route 100.70.0.0/16 dev wt0 + Docker bridge MASQUERADE. Проверено: контейнер → 100.70.167.54:18789 (openclaw) → http 200. Интернет (github/docker для kb-pull) работает одновременно, DNS не сломан (resolv.conf остался 1.1.1.1/8.8.8.8).
  • ГРАБЛЯ: только по IP, не по именам. Magic-DNS *.netbird.cloud в контейнере НЕ резолвится — search dttb.ru + wildcard *.dttb.ru превращают openclaw.netbird.cloudopenclaw.netbird.cloud.dttb.ru10.0.0.195 (NPM) → connect fail. Агент, ходивший по имени, «не мог работать с NetBird». Фикс: в systemPrompt обоих агентов добавлена инструкция ходить по IP 100.70.x (адреса в базе), не по *.netbird.cloud. По IP — http 200 (проверено агентом через shell). Если нужен доступ по именам — настраивать NetBird magic-DNS в контейнере (пока не делали, по IP достаточно).

Управление Антошкой агентом — что реально может (2026-06-10)

Device SwarmClaw (10.0.0.135) спарен на openclaw с operator.admin (approve сделан при настройке gateway, pending больше нет — агент Дирижёр советовал повторный approve зря).

  • Агентские openclaw-tools: openclaw_nodes (список openclaw-узлов + exec-команды; НЕ cron — action cron → «Unknown nodes action») и openclaw_workspace (backup/rollback/history конфига). Их надо явно включить в agent.tools — по умолчанию у агента их нет (поэтому Дирижёр сначала не мог рулить Антошкой). Включены для Dirizhyor + Ассистента.
  • Грабля: agent.gatewayProfileId переключает chat-модель агента на gateway-провайдера (openclaw model «default») → MODEL_NOT_FOUND 404, агент перестаёт отвечать. НЕ ставить gatewayProfileId на агенте; openclaw_nodes сам берёт дефолтный gateway-профиль. Оставлять gatewayProfileId: null, provider omniroute.
  • Полное управление (cron, agent-files SOUL/IDENTITY/.., models, deploy) — только через UI (Providers → профиль «Antoshka») или SwarmClaw API напрямую (/api/openclaw/{cron,agent-files,models,...}?profileId=gateway-0eaf65b0 с cookie sc_auth). Через агентский чат cron/файлы НЕ редактируются.

Окно контекста агента = 8192 (баг!) → патч на 200K (2026-06-12)

Дирижёр жаловался на «ограничение окна» и просил пересадить себя на claude-cli + Anthropic-ключ. Корень — НЕ модель и НЕ Max-лимит: getContextWindowSize(provider, model) (src/lib/server/context-manager.ts) для незнакомого custom-провайдера omniroute не находит его ни в PROVIDER_CONTEXT_WINDOWS[model] (там claude-opus-4-6, нет cc/claude-opus-4-8), ни в PROVIDER_DEFAULT_WINDOWS[provider] (есть anthropic/openclaw/google, нет omniroute) → fallback 8_192. При 4.8k токенов агент уже «на 58%» и паникует. OmniRoute при этом реально отдаёт context_length: 1000000 для cc/claude-opus-4-8.

  • Чистого API-пути нет — окна зашиты в бандл, model_overrides про подмену модели (heartbeat), provider-config contextWindow нигде не читается.
  • Фикс — патч каталога окон в бандле: добавлен omniroute:2e5 в PROVIDER_DEFAULT_WINDOWS (чанк /app/.next/server/chunks/src_lib_server_06.*.js, паттерн goose:2e5,openclaw:128e3...,omniroute:2e5,openclaw:128e3). Проверено: context-statuscontextWindow: 200000. Бэкап чанка .bak-ctxwin.
  • 200K, не 1M: консервативно (гарантированно для Opus). 1M ставить только после проверки, что Max-тракт cc/* реально принимает >200K (иначе переполнение → ошибка).
  • Устойчивость: патч в бандле образа — слетает при --force-recreate/обновлении. Скрипт /opt/swarmclaw/repatch-ctxwin.sh переприменяет (idempotent). Запускать после каждого docker compose pull/update SwarmClaw.
  • Вывод про claude-cli: не нужен. Ключ Anthropic не давать.

НАСТОЯЩИЙ корень «контекст кончается за 2-3 запроса» (2026-06-12)

Размер окна оказался вторичен. Олег: «было 200, всё равно за 2-3 запроса кончается». Замер по OmniRoute call_logs (/root/.omniroute/storage.sqlite, поле tokens_in): простой запрос «привет» = ~33-36K tokens_in (из них cache_read ~33K — кэшируется, но занимает окно). А SwarmClaw context-status показывал лишь 5.6K — он НЕ учитывает в индикаторе системный промпт + схемы инструментов + knowledge-инжект. Отсюда иллюзия «3%» при реальных 16-18% на пустой запрос, и окно тает в ~6× быстрее.

  • Главный пожиратель — схемы 24 инструментов в каждом запросе (browser, replicate, image_gen, google_workspace, swarmdock, manage_* — жирные JSON-схемы). Урезал tools 24→9 (ядро: shell, execute, files, edit_file, web, memory, delegate, openclaw_nodes, schedule_wake) → tokens_in упал 33K → 3K (×11). Проверено замером.
  • Knowledge (189 записей, ~270K ток суммарно) инжектится чанками по релевантности (CHUNK_TARGET_CHARS=2200), на «привет» не грузится — не постоянный оверхед, не трогал.
  • Как мерить: sqlite3 /root/.omniroute/storage.sqlite "SELECT tokens_in,tokens_cache_read FROM call_logs WHERE model LIKE '%opus-4-8%' ORDER BY timestamp DESC LIMIT 5" на LXC 132.
  • Урезаны Dirizhyor (0d388a87) + Ассистент (default, был сломан 0 tools — восстановлен через БД-INSERT). Прочие агенты Олега (Pochtalion/Бухгалтер/Бенелюкс/НИИКН-Ассистент/Nastavnik) — по 7 tools, не трогал.

Картинки/скриншоты в чат (2026-06-12)

  • Канал vision работает — проверено end-to-end: upload PNG (POST /api/upload, header x-filename, → data/uploads/, resolveImagePath читает файл → base64 → image_url формат → OmniRoute → cc/claude-opus-4-8 (input_modalities text+image) → агент описал картинку.
  • Из коробки работают: вставка Cmd+V (handlePaste ловит image/* из clipboard) + кнопка «Add image». Vision-гейта по провайдеру НЕТ (не блокирует custom omniroute).
  • Drag&drop НЕ был реализованchat-input.tsx не было onDrop). Допатчил: добавил handleDrop/handleDragOver/handleDragLeave + state isDragging + onDrop на корневой div (переиспользует uploadAndAdd). Патч сохранён: /opt/swarmclaw/dragdrop.patch (git apply).
  • Требует пересборки образа (docker compose buildnpm ci из кэша, переедет next build ~5-10 мин, затем docker compose up -d).
  • ОТКАЧЕНО 2026-06-12: сборка next build шла слишком долго/молча, Олег не дождался, drag&drop так и не доехал до контейнера → git checkout -- src/components/input/chat-input.tsx, сборку убил. В src и контейнере чисто (0 handleDrop). Патч-файл /opt/swarmclaw/dragdrop.patch оставлен — при желании доделать: git apply dragdrop.patch && docker compose build && docker compose up -d. Вставка Cmd+V и кнопка «Add image» работают и без него.

Грабля: урезка tools сломала создание агентов (2026-06-12)

После урезки Дирижёра 24→9 tools (ради контекста) он перестал создавать агентов — был убран manage_platform (именно он управляет агентами: create/assign; «не та ветка» = путаница агента про параллельные branches суб-агентов в subagent.ts, не git). Фикс: вернул Дирижёру manage_platform + spawn_subagent (он оркестратор роя) → tools=11, создание агентов работает (проверено: создал TestBot99). Урок: при урезке tools у агента-оркестратора НЕ убирать manage_platform/spawn_subagent/delegate_to_agent — это его рабочие инструменты. Рядовым агентам (7 tools) они не нужны.

Схема API (для будущих правок headless)

  • POST /api/providers{id,name,baseUrl,models[],requiresApiKey,isEnabled} (type всегда custom), хранит JSON в таблице provider_configs.
  • POST /api/agents → zod AgentCreateSchema; обяз. name,provider; ollamaMode только local|cloud|null (не off).
  • POST /api/credentials{provider,name,apiKey}.
  • POST /api/gateways{name,endpoint,credentialId,isDefault}.
  • POST /api/openclaw/gateway → RPC {method:"gateway.connect",params:{url,token}|{profileId}}.
  • БД: /opt/swarmclaw/data/swarmclaw.db (sqlite, таблицы *_configs/profiles/agents/credentials как id+data JSON), memory.db, logs.db.

Домен swarm.dttb.ru (2026-06-08)

  • NPM proxy host #32 (LXC 103, 10.0.0.195): swarm.dttb.ru10.0.0.135:3456, WSS on, block_exploits.
  • DNS: wildcard *.dttb.ru есть только во внутреннем DNS (роутер) — http в LAN/NetBird заработал сразу. Публично wildcard НЕТ (каждый поддомен = отдельная A-запись в SpaceWeb). Добавил swarm A 176.62.183.186 через ../snippets/spaceweb-dns-api (add-a, один вызов — зона сверена до/после, 23→24 записи, ничего не задето; бэкап /tmp/dttb_zone_before.txt).
  • HTTPS/LE — ГОТОВО: DNS пропагировался за ~90 сек, LE cert выпущен (NPM cert id 118, до 2026-09-06), ssl_forced on, проверено https://swarm.dttb.ru/api/healthz → 200. Грабля NPM API: LE cert принимает только meta:{} (с полями → "additional properties"); email/agree NPM подставляет сам. Challenge падал, пока DNS не пропагировался — не из-за meta.
  • Итог доступа: https://swarm.dttb.ru (публично, за NPM+auth) и http://swarm.dttb.ru (LAN/NetBird). Вход — cookie sc_auth / ACCESS_KEY.

TODO / опционально

  • Снести Mac-app SwarmClaw.app (или оставить как клиент).
  • Маппинг openclaw starter-агентов на gateway (роли/теги) — через UI.
  • Самообучение SwarmClaw (conversation-to-skill) — полуручное (draft→approve), не автономное; для настоящего автообучения остаётся openclaw Dreaming.

Связано