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

216 lines
12 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Фаза 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 вручную) — без него сквозная приёмка невозможна
## Связанные заметки
- [[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` — архитектура дашборда