12 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 | Сеть (Cloudflare Tunnel) | ⏳ на Alex — CF-панель |
| 4 | Секция «Agents» в Dashboard | ✅ API + вкладка |
| 5 | Связка Балда → мост | ⏳ не начато |
| 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 | Cloudflare Tunnel (существующий cloudflared на Mac) |
| 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
▼
Cloudflare Tunnel: agents-bridge.qentra.top → localhost:8790 ⏳
│ (существующий cloudflared на Mac, PID + LaunchAgent)
▼
Мост (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
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 |
~/Developer/eagle-dash/agents.py |
роутер секции «Agents» |
Известные питфоллы (пройдены):
docker composeв этом окружении отсутствует — толькоdocker-compose(v5.1.3);compose.override.yamlобязателен при старте, иначеupпадает на монтировании;- компайл:
node:24-bookwormвместоscratch(CA-сертификаты — иначе TLS-фейл); psutilне установлен в venv дашборда → supervisor не может трекать мост по PID; health для моста —type: http.
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. Сеть (шаг 3) — на Alex
Cloudflare Tunnel: добавить published route agents-bridge.qentra.top → HTTP localhost:8790 +
Access Application. Токен и процесс cloudflared не трогаются.
⚠️ hostname — одноуровневый (двухуровневые дают TLS handshake failure).
Проверка после настройки: curl https://agents-bridge.qentra.top/healthz.
8. Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Мост опубликован в интернет | высокий | Access Application + shared secret |
Полный доступ агента к ~/agents |
средний | бэкапы; git rm-семантика вместо rm |
| Бан подписки за «unusual traffic» | средний | 1 параллельная задача, официальный бинарь, без прокси токена |
Hardlink через ФС → EXDEV |
высокий | проверка st_dev в API |
| Mac спит → туннель и мост недоступны | средний | задачи в durable-очереди Балды, идемпотентность |
9. Открыто
- Cloudflare Tunnel (шаг 3) — нужен доступ к CF-панели
- Связка Балда → мост (шаг 5) — MCP-инструмент
- Логин CLI (Alex вручную) — без него сквозная приёмка невозможна
Связанные заметки
- 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/macos-docker-virtiofs-mount-limits — ограничения macOS Docker
- personal/tech/balda-go-fx-pitfalls — fx-питфоллы, дельта-апплаер
- family/how-to/kraken-portainer-access — cloudflared, routes
- скилл
eagle-dashboard— архитектура дашборда