Files
obsidian-vault/personal/plans/balda-mm-phase4-agents.md
T

12 KiB
Executable File
Raw Blame History

Фаза 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. Открыто

  1. Cloudflare Tunnel (шаг 3) — нужен доступ к CF-панели
  2. Связка Балда → мост (шаг 5) — MCP-инструмент
  3. Логин CLI (Alex вручную) — без него сквозная приёмка невозможна

Связанные заметки