---
date: 2026-06-08
type: decision
tags: [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](https://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/doctor` → `spawn 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.ts` → `provider = 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.js`→`dist/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.cloud` → `openclaw.netbird.cloud.dttb.ru` → `10.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-status` → `contextWindow: 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 build` — `npm 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) они не нужны.

## Обновление версии образа (2026-06-14: 1.9.38 → 1.9.39)
Процедура на LXC 135 (через `pct exec 135`): бэкап БД `cp /opt/swarmclaw/data/swarmclaw.db{,.bak-preXXXX}` → `cd /opt/swarmclaw && docker compose pull && docker compose up -d` → **`bash repatch-ctxwin.sh` ОБЯЗАТЕЛЬНО** (патч `omniroute:2e5`=200K живёт в `/app/.next/server/chunks` контейнера, слетает при recreate; без него `getContextWindowSize` fallback = 8192 → агенты на Opus режутся до 8K). Проверка: `docker exec swarmclaw-swarmclaw-1 grep version /app/package.json`, `auth HTTP=200` ключом из `.env.local` (`OL260380eg`, **не** первичный `4613e7d0…`), `/api/agents` отдаёт список.
- 1.9.39 — packaging-релиз (npm publish pending, Docker готов, macOS desktop zip/нотаризация); функциональных изменений для сервера нет. На ghcr `:latest` == `:v1.9.39`.
- Откат: старый образ 1.9.38 (`sha256:d1d102a4…`) остаётся локально; восстановить из `.bak-pre1939` + запустить прежний образ.

## Схема 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.ru` → `10.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.

## Связано
- [[openclaw]] — основной бот (LXC 137), теперь под управлением SwarmClaw
- [[2026-05-06-openclaw-opus-4-7-via-max-cliproxy]]
