18 KiB
Executable File
Фаза 4 — исполнитель на Mac + ~/agents + секция «Agents» в Eagle Dashboard
План + статус. Старт 2026-09-26. Часть personal/projects/balda/balda-mm-delegation-plan (Фазы 4-5). Предыдущее: personal/projects/balda/balda-kraken-mm-deploy (Фаза 3 ✅ — бот отвечает Alex).
0. Статус шагов
| # | Шаг | Статус |
|---|---|---|
| 1 | ~/agents + ~/cli-auth + контейнер |
✅ собрано, запущено |
| 2 | Мост FastAPI :8790 | ✅ работает под launchd |
| 3 | Сеть Kraken → Mac | ✅ LAN напрямую, туннель не нужен |
| 4 | Секция «Agents» в Dashboard | ✅ активирована и проверена |
| 5 | Связка Балда → мост | 🟡 связка работает, конфиг Балды ждёт переключения на type: http |
| 6 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI |
1. Решения
| # | Вопрос | Решение |
|---|---|---|
| 1 | Папка для проектов исполнителя | ~/agents |
| 2 | Подтверждения | Full access → permissions.mode: allow_all |
| 3 | Управление линками в ~/agents |
Секция «Agents» в Eagle Dashboard |
| 4 | Auth CLI (claude/codex) | на хосте, ~/cli-auth, вне ~/agents; логинит Alex вручную |
| 5 | Сеть Kraken → Mac | ✅ LAN напрямую (http://192.168.1.57:8790); Cloudflare Tunnel — резерв |
| 6 | Провайдер исполнителя | claude (дефолт в .env); codex собирается в том же образе, переключается переменной |
2. Ограничения macOS Docker (проверено тестами)
| Ограничение | Итог |
|---|---|
| Симлинк на внешний путь | битый в контейнере — цель резолвится от корня контейнера |
Live mount --bind внутри VM |
контейнер не видит (virtiofs строит маппинг на старте) |
| Сетевая шара SMB/NFS | в контейнер не протянуть (тот же virtiofs) |
| Hardlink на каталог | физически невозможен (ядро запрещает) |
| Hardlink через границу ФС | EXDEV |
Следствие: внешние проекты подключаются только bind-mount'ом через -v +
docker-compose up -d (2-3 сек). Hardlink — только для файлов внутри одного тома.
Детали механизма: personal/tech/macos-docker-virtiofs-mount-limits.
3. Архитектура (собрано)
Mattermost (Alex)
│ «почини баг X в project-y»
▼
Балда-оркестратор (Kraken, DeepSeek, allow_all)
│ читает Obsidian, формирует ТЗ, /goalkeeper → work → validate
▼
MCP-сервер balda-executor-mcp (Kraken, контейнер в mattermost_mm-net) :8890
│ инструменты executor_run / executor_status / executor_health
│ HTTP + X-Bridge-Secret
▼
LAN: http://192.168.1.57:8790 ← Kraken и Mac в одной сети, туннель не нужен
▼
Мост (Mac, FastAPI :8790) — под launchd com.agents.bridge
│ idempotency key, таймаут 900 с, лимит 1 задача, shared secret
▼
Контейнер-исполнитель balda-executor (Mac, Docker)
│ -v ~/agents:/agents:rw
│ -v ~/Developer/<proj>:/agents/<name>:rw ← внешние проекты (override)
│ -v ~/cli-auth/claude:/root/.claude:rw ← auth на хосте
│ -v ~/cli-auth/codex:/root/.codex:rw ← auth на хосте
│ Node 24 + claude 2.1.283 + codex 0.157.1 + git
▼
diff + тесты + коммит → Балда → Mattermost
Детали MCP-сервера: personal/tech/balda-executor-mcp.
Auth CLI на хосте
| Данные | Хост | В контейнере | Права |
|---|---|---|---|
| Claude auth | ~/cli-auth/claude |
/root/.claude |
0700 |
| Codex auth | ~/cli-auth/codex |
/root/.codex |
0700 |
Пересборка образа и обновление CLI auth не сбрасывают. Токены вне /agents → агент их не видит.
4. Что собрано на диске
| Путь | Что |
|---|---|
~/agents/ |
рабочая область исполнителя |
~/cli-auth/{claude,codex} |
0700, монтируются в контейнер |
~/Docker/balda-executor/Dockerfile |
Node 24 + claude + codex + git + tini + CA |
~/Docker/balda-executor/docker-compose.yml |
4 тома, сеть executor-net, build-секция |
~/Docker/balda-executor/.env |
EXECUTOR_PROVIDER=claude, 0600 |
~/Docker/balda-executor/agents-mounts.yaml |
манифест внешних проектов |
~/Docker/balda-executor/tools/render-mounts.py |
манифест → compose.override.yaml |
~/Developer/agents-bridge/ |
мост: app.py, store.py, executor.py, config.py |
~/Library/LaunchAgents/com.agents.bridge.plist |
автозапуск + KeepAlive, --host 0.0.0.0 |
~/Developer/eagle-dash/agents.py |
роутер секции «Agents» |
~/Developer/balda-executor-mcp/ |
MCP-сервер: server.py, Dockerfile, docker-compose.yml |
/home/kraken/mcp/docker-compose.yml (Kraken) |
запуск контейнера MCP |
Известные питфоллы (пройдены):
docker composeна Mac отсутствует — толькоdocker-compose(v5.1.3); на Kraken наоборот:docker compose, аdocker-compose→command not found;compose.override.yamlобязателен при старте, иначеupпадает на монтировании;- образ исполнителя:
node:24-bookwormвместоscratch(CA-сертификаты — иначе TLS-фейл); psutilне установлен в venv дашборда → supervisor не может трекать мост по PID; health для моста —type: http;--hostберётся изProgramArgumentsplist,.envего не перебивает; смена аргументов требуетbootout+bootstrap, неkickstart;- образ Балды из
scratch: нет shell,docker exec … shне работает;docker.sockв контейнер Балды не смонтирован →docker runизнутри невозможен; - на Kraken в sshd
AllowTcpForwarding no→ обратный SSH-туннель поставить нельзя.
5. Секция «Agents» в Eagle Dashboard
Правки: main.py (include_router) · agents.py (новый роутер) · templates/index.html (вкладка ?tab=agents) · services.yaml (сервис agents_bridge).
| Функция | Что делает |
|---|---|
| Список | содержимое ~/agents: имя, размер, mtime, статус линка |
| Создать | mkdir ~/agents/<name> |
| Подключить внешний | запись в agents-mounts.yaml → render-mounts.py → up -d |
| Hardlink | ln -F для файлов, проверка st_dev |
| Отключить | убрать из манифеста + up -d; файлы на диске целы |
| Статус моста | /api/agents/bridge — provider, container, active, limit, pid, secret |
| Терминал | ссылка на вкладку files |
API: GET/POST /api/agents · POST|DELETE /api/agents/{name}/mount ·
POST /api/agents/{name}/hardlink · GET /api/agents/{name}/usage ·
GET /api/agents/mounts · POST /api/agents/mounts/render · GET /api/agents/bridge.
Не делает: не удаляет файлы, не убивает процессы, не создаёт symlink на внешние цели.
Приёмка секции (2026-09-26) — 13 проверок, все прошли
| Проверка | Результат |
|---|---|
| создание агента | 201 ✅ |
| повторное создание | 409 ✅ |
| подключение внешнего проекта | 200 + контейнер Recreated ✅ |
| проект виден внутри контейнера | да, ls /agents/testproj → файлы ✅ |
| отвязка | 200; внутри контейнера пусто, на хосте файлы целы ✅ |
| hardlink на каталог | 400 (ядро запрещает) ✅ |
| hardlink на файл в том же томе | 200, links=2, одинаковый inode ✅ |
слишком широкий host_path |
400 ✅ |
| размер каталога | OK ✅ |
?tab=agents рендерится |
200, 75 КБ ✅ |
| JS-целостность шаблона | скобки парны, 8 методов на месте ✅ |
| Alpine в логе дашборда | ошибок нет ✅ |
6. Мост agents-bridge
~/Developer/agents-bridge/. Запуск под launchd: com.agents.bridge, лог ~/Library/Logs/agents-bridge.err.log.
| Эндпоинт | Назначение |
|---|---|
GET /healthz |
живость моста + контейнера |
POST /task |
поставить задачу {prompt, provider?} |
GET /task/{id} |
статус + stdout/stderr/exit_code |
GET /tasks |
последние 20 задач |
Безопасность: заголовок X-Bridge-Secret (иначе 401), Idempotency-Key (повтор возвращает тот же task_id),
лимит 1 одновременная задача (иначе 429), таймаут 900 с, обрезка вывода 200 КБ.
Приёмка моста (2026-09-26) — 6 проверок, все прошли
| Проверка | Результат |
|---|---|
| без секрета / неверный секрет | 401 / 401 ✅ |
два POST с одним Idempotency-Key |
одинаковый task_id ✅ |
| задача дошла до CLI в контейнере | да ✅ |
ответ CLI вернулся (Not logged in · Please run /login) |
да — это ответ Claude Code, конвейер рабочий ✅ |
| состояние в SQLite (WAL) | ✅ |
/healthz видит контейнер |
status: ok ✅ |
Блокер приёмки: CLI внутри контейнера не авторизован. Логинит Alex вручную:
claude / codex внутри контейнера, credentials кладутся в ~/cli-auth/{claude,codex}.
7. Сеть Kraken → Mac — ✅ РЕШЕНО, туннель НЕ нужен
Итог: обычный HTTP по локальной сети
Kraken и Mac стоят в одной LAN и видят друг друга напрямую — проверено
ping с Kraken на 192.168.1.57 (RTT ~2.4 мс, 0% потерь). Никакой туннель,
никакой проброс и никакая Access Application не нужны.
BRIDGE_URL=http://192.168.1.57:8790
Проверки, подтвердившие связь:
| Проверка | Результат |
|---|---|
ping Kraken → 192.168.1.57 |
2/2, RTT 2.4 мс ✅ |
curl с Kraken на :8790/healthz |
status: ok ✅ |
curl из контейнера в mattermost_mm-net на :8790/healthz |
status: ok ✅ |
Что НЕ сработало и почему (чтобы не повторять)
| Путь | Почему отпал |
|---|---|
Cloudflare Tunnel (agents-bridge.qentra.top) |
Требует правки маршрута в CF-панели — ручная работа Alex, а LAN работала сразу. Остаётся резервным вариантом, если Mac уедет из этой сети |
Обратный SSH-туннель (ssh -R) |
🔴 На Kraken в sshd стоит AllowTcpForwarding no. Симптом: Error: remote port forwarding failed for listen port 18790, в verbose — debug1: Remote: Server has disabled port forwarding. Не диагностировать как «порт занят»: порт свободен, форвард выключен администратором |
📌 Если понадобится туннель в будущем — сначала проверить AllowTcpForwarding на Kraken,
потом уже ставить форвард. Cloudflare Tunnel этот запрет обходит (исходящее соединение,
не SSH-форвард).
Мост теперь слушает всю сеть — это осознанное изменение
| Было | Стало | Почему |
|---|---|---|
BRIDGE_HOST=127.0.0.1 |
BRIDGE_HOST=0.0.0.0 |
иначе Kraken не достучится |
⚠️ Порт 8790 теперь открыт в локальной сети. Защита — X-Bridge-Secret (обязателен,
иначе POST /task отдаёт 503). При уходе Mac в недоверенную сеть — вернуть 127.0.0.1
и поднимать туннель.
🔴 Питфолл: .env не перебивает plist. Я поменял BRIDGE_HOST в .env, но мост
продолжал слушать 127.0.0.1 — потому что --host жёстко задан в ProgramArguments
plist. Симптом: lsof показывает 127.0.0.1:8790 после «перезапуска с новым конфигом».
Исправлять надо в plist, а не в .env.
# применить: правка plist → перезагрузка сервиса (не kickstart!)
launchctl bootout gui/$(id -u)/com.agents.bridge
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
# проверка: должно быть *:8790, а не 127.0.0.1:8790
lsof -nP -iTCP:8790 -sTCP:LISTEN
kickstart перечитывает только программу, но не изменённый ProgramArguments —
для смены аргументов нужен bootout + bootstrap.
8. Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Мост опубликован в интернет | высокий | Access Application + shared secret |
Полный доступ агента к ~/agents |
средний | бэкапы; git rm-семантика вместо rm |
| Бан подписки за «unusual traffic» | средний | 1 параллельная задача, официальный бинарь, без прокси токена |
Hardlink через ФС → EXDEV |
высокий | проверка st_dev в API |
| Mac спит → туннель и мост недоступны | средний | задачи в durable-очереди Балды, идемпотентность |
9. Открыто
- Переключить конфиг Балды на HTTP-MCP — сейчас там stdio-вариант с
docker run, который не может работать (нетdocker.sockи нет shell в образе Балды). Нуженtype: http→http://balda-executor-mcp:8890/mcp. См. personal/tech/balda-executor-mcp §7. - Проверить, что инструменты видны в сессии Балды (после перезапуска).
- Логин CLI (Alex вручную) — без него сквозная приёмка невозможна.
- Cloudflare Tunnel — не нужен на текущей схеме; держать как резерв, если Mac уедет из LAN.
10. Шаг 5 — связка Балда → мост (что сделано 2026-09-26)
| Компонент | Где | Статус |
|---|---|---|
MCP-сервер balda-executor-mcp |
~/Developer/balda-executor-mcp/ |
✅ код + образ |
| Образ | balda-executor-mcp:0.1.0, загружен на Kraken |
✅ |
| Контейнер MCP | Kraken, /home/kraken/mcp/docker-compose.yml |
✅ Up |
| Конфиг Балды | runtime.mcp_servers.executor-mcp + balda.mcp_servers |
🟡 ждёт type: http |
.env Балды |
+ BRIDGE_URL, + BRIDGE_SECRET |
✅ |
Бэкапы на Kraken: config.yaml.bak-mcp-20260926-194313, .env.bak-mcp-*, .env.bak-url-*.
Проверено живьём: MCP-handshake (stdio и HTTP), tools/list с 3 инструментами,
executor_health из docker-сети Kraken вернул живые данные моста на Mac,
Балда после правки конфига стартовала без ошибок.
📌 Ключевой вывод по архитектуре: MCP-сервер обязан быть отдельным контейнером
с HTTP-транспортом. Вариант «stdio + docker run изнутри Балды» нерабочий:
в образе Балды нет shell и не смонтирован docker.sock.
Связанные заметки
- personal/projects/balda/balda-mm-delegation-plan — общий план (Фазы 4-5)
- personal/projects/balda/balda-kraken-mm-deploy — Фаза 3 (✅)
- personal/plans/balda-mm-pr-plan — Фаза 2 (PR #8)
- personal/tech/balda-executor-mcp — MCP-сервер делегирования (шаг 5)
- personal/tech/agents-bridge — мост :8790 (шаг 2)
- personal/tech/eagle-dashboard-agents-tab — секция «Agents» в дашборде (шаг 4)
- personal/tech/macos-docker-virtiofs-mount-limits — ограничения macOS Docker
- personal/tech/balda-go-fx-pitfalls — fx-питфоллы, дельта-апплаер
- family/how-to/kraken-portainer-access — cloudflared, routes
- скилл
eagle-dashboard— архитектура дашборда