[2026-09-26] eagle: family/how-to/truenas-infrastructure.md personal/plans/balda-mm-phase4-agents.md personal/tech/agents-bridge.md personal/tech/eagle-dashboard-agents-tab.md
This commit is contained in:
1 parent
31644d1010
commit
769bb4c21b
4 files changed
+288
-27
No files matched your search
@@ -87,18 +87,35 @@ GET /tasks?limit=N → последние задачи (для дашбор
|
||||
|
||||
## Запуск
|
||||
|
||||
**Сейчас — вручную** (launchd ещё не сделан, см. открытый пункт 4 плана):
|
||||
**Под launchd** (с 2026-09-26) — плейт `com.agents.bridge`:
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Label | `com.agents.bridge` |
|
||||
| Plist | `~/Library/LaunchAgents/com.agents.bridge.plist` |
|
||||
| Program | `~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790` |
|
||||
| WorkingDirectory | `/Users/admin/Developer/agents-bridge` |
|
||||
| `RunAtLoad` / `KeepAlive` | `true` / `true` |
|
||||
| Логи | `~/Library/Logs/agents-bridge.log` (+ `.err.log`) |
|
||||
| `PATH` в EnvironmentVariables | `/opt/homebrew/bin:...` — launchd не читает shell-PATH |
|
||||
|
||||
```bash
|
||||
cd ~/Developer/agents-bridge
|
||||
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
||||
```
|
||||
|
||||
Проверка:
|
||||
```bash
|
||||
# установка/переустановка
|
||||
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
|
||||
# перезапуск
|
||||
launchctl kickstart -k gui/$(id -u)/com.agents.bridge
|
||||
# статус (pid, last exit)
|
||||
launchctl list | grep agents.bridge
|
||||
# проверка
|
||||
curl -s http://127.0.0.1:8790/healthz
|
||||
```
|
||||
|
||||
Проверено 2026-09-26: `bootstrap` → exit 0, в `launchctl list` строка `3063 0 com.agents.bridge`,
|
||||
`/healthz` = `status: ok`. ⚠️ Процедура убийства pid для проверки `KeepAlive` **не выполнялась** —
|
||||
команда требует `kill -9` и была отклонена пользователем, не повторять без явной команды.
|
||||
|
||||
Ручной запуск (если launchd отключён) — тем же `uvicorn`, но это аварийный путь.
|
||||
|
||||
⚠️ На macOS `docker compose` **не работает** — только `docker-compose` (см. план, шаг 1).
|
||||
|
||||
---
|
||||
@@ -132,10 +149,27 @@ auth-файлов из `~/.claude`/`~/.codex` отклонено, не пред
|
||||
как `timeout` с причиной `bridge restarted while task was running`. Иначе они навсегда
|
||||
висят активными и блокируют слот лимита.
|
||||
|
||||
### 4. Мост не переживает перезагрузку
|
||||
### 4. Мост не переживает перезагрузку — ✅ ЗАКРЫТО
|
||||
|
||||
Пока нет launchd-плейта `com.agents.bridge`, процесс живёт только в текущей сессии.
|
||||
Это **блокер для шага 3** (туннель опубликует URL, ведущий в закрытый порт).
|
||||
Решено 2026-09-26: плейт `com.agents.bridge` (`RunAtLoad` + `KeepAlive`), поднят через
|
||||
`launchctl bootstrap`. Шаг 3 (туннель) больше не блокирован.
|
||||
|
||||
⚠️ Остаётся непроверенным поведение `KeepAlive` при падении процесса: тест требовал
|
||||
`kill -9` по pid моста и был отклонён. Проверить можно `launchctl kickstart -k`
|
||||
(штатный путь) либо перезагрузкой Mac.
|
||||
|
||||
### 5. launchd не видит `docker` — `PATH` задавать в плейте
|
||||
|
||||
`ProgramArguments` использует абсолютный путь к python, но `executor.py` вызывает
|
||||
`docker` и `docker-compose` из `PATH`. При запуске под launchd `PATH` = `/usr/bin:/bin`,
|
||||
поэтому `docker` (`/opt/homebrew/bin/docker`) не находится. В плейт добавлен блок
|
||||
`EnvironmentVariables.PATH` с `/opt/homebrew/bin` первым. Симптом без него:
|
||||
`docker не найден в PATH процесса моста` в `stderr` задачи.
|
||||
|
||||
### 6. Мост слушает только `127.0.0.1`
|
||||
|
||||
`BRIDGE_HOST=127.0.0.1` — намеренно. Наружу выставляется туннелем (шаг 3), не пробросом
|
||||
порта. Не менять на `0.0.0.0` без Access Application на стороне Cloudflare.
|
||||
|
||||
---
|
||||
|
||||
@@ -159,7 +193,11 @@ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8790/task \
|
||||
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4 (шаги 1-2 ✅)
|
||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — почему bind-mount вместо symlink
|
||||
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план делегирования
|
||||
- `~/Developer/agents-bridge` — код моста
|
||||
|
||||
## История
|
||||
- **2026-09-26**: создан, поднят на :8790, протестирован (auth 401, идемпотентность,
|
||||
сквозной вызов CLI). Не в launchd. Логин CLI — за Alex.
|
||||
сквозной вызов CLI). Логин CLI — за Alex.
|
||||
- **2026-09-26** (продолжение): переведён под launchd (`com.agents.bridge`,
|
||||
`RunAtLoad` + `KeepAlive`, pid 3063), добавлен `EnvironmentVariables.PATH`.
|
||||
Шаг 3 разблокирован; проверка `KeepAlive` при падении не выполнена (отклонён `kill -9`).
|
||||
@@ -0,0 +1,160 @@
|
||||
# Секция «Agents» в Eagle Dashboard — реализация
|
||||
|
||||
> Создан 2026-09-26. Шаг 4 Фазы 4 [[personal/plans/balda-mm-phase4-agents]].
|
||||
> Статус: **код написан, НЕ активирован** — дашборд работает на старом байткоде.
|
||||
> Активация = `__pycache__` + `launchctl kickstart -k` (апрув не получен).
|
||||
|
||||
---
|
||||
|
||||
## Расположение
|
||||
|
||||
| Что | Путь |
|
||||
|---|---|
|
||||
| Роутер | `~/Developer/eagle-dash/agents.py` (новый файл) |
|
||||
| Подключение | `~/Developer/eagle-dash/main.py` → `app.include_router(agents_router)` |
|
||||
| Реестр сервисов | `~/Developer/eagle-dash/services.yaml` → запись `agents_bridge` |
|
||||
| Бэкап реестра | `services.yaml.bak-agents-20260926-185641` |
|
||||
| Манифест монтирований | `~/Docker/balda-executor/agents-mounts.yaml` |
|
||||
| Генератор override | `~/Docker/balda-executor/tools/render-mounts.py` |
|
||||
|
||||
---
|
||||
|
||||
## API (`prefix=/api/agents`)
|
||||
|
||||
```
|
||||
GET /api/agents → список ~/agents + манифест + статус контейнера
|
||||
POST /api/agents {name} → mkdir ~/agents/<name>
|
||||
POST /api/agents/{name}/mount {host_path, mode} → запись в манифест + recreate
|
||||
DELETE /api/agents/{name}/mount → отвязать (файлы на диске целы)
|
||||
POST /api/agents/{name}/hardlink {file, link_name} → ln -F с проверкой st_dev
|
||||
GET /api/agents/{name}/usage → du -sk
|
||||
GET /api/agents/mounts → содержимое манифеста
|
||||
POST /api/agents/mounts/render → перегенерировать override без recreate
|
||||
GET /api/agents/bridge → состояние моста :8790 + `launchctl list`
|
||||
```
|
||||
|
||||
## Что секция НЕ делает (осознанно)
|
||||
|
||||
| Запрещено | Почему |
|
||||
|---|---|
|
||||
| Удалять файлы в `~/agents` | правило Alex: только отвязка от контейнера |
|
||||
| Создавать symlink на внешний путь | в Docker битый (virtiofs) — см. [[personal/tech/macos-docker-virtiofs-mount-limits]] |
|
||||
| Hardlink на каталог | ядро запрещает; вернётся ошибка с объяснением |
|
||||
| Hardlink через границу ФС | `st_dev` сравнивается до `ln`, при различии — 400 с текстом про `EXDEV` |
|
||||
| Живой `mount --bind` | на macOS не виден контейнеру — только `docker-compose up -d` |
|
||||
|
||||
---
|
||||
|
||||
## Ключевые решения реализации
|
||||
|
||||
### Роутер в отдельном файле, не в `main.py`
|
||||
|
||||
`main.py` — 889 строк; секция «Agents» вынесена в `agents.py` (~430 строк) как
|
||||
`APIRouter(prefix="/api/agents")`. Подключение одной строкой в `main.py` — минимизирует
|
||||
правку большого файла и не ломает существующие роуты.
|
||||
|
||||
### Валидация `host_path` — белый список запретов
|
||||
|
||||
Путь должен быть абсолютным, существующим каталогом, и НЕ одним из:
|
||||
`/`, `/Users`, `/Users/admin`, `/Users/admin/agents`. Имя агента — `^[a-z0-9][a-z0-9._-]{0,62}$`.
|
||||
Это защита от монтирования `~` целиком в контейнер.
|
||||
|
||||
### Откат манифеста при провале
|
||||
|
||||
`mount`/`unmount` пишут манифест → вызывают `render-mounts.py` → `docker-compose up -d`.
|
||||
Если любой шаг падает, **манифест возвращается к прежнему состоянию** (`_write_manifest(mounts)`),
|
||||
чтобы файл не расходился с реально смонтированным.
|
||||
|
||||
### Запись манифеста — атомарная
|
||||
|
||||
`tmp` → `replace()`. Заголовок с правилами перезаписывается константой `MANIFEST_HEADER`,
|
||||
чтобы пояснения не терялись при правках из UI.
|
||||
|
||||
### `du -sk` вместо обхода дерева
|
||||
|
||||
Список агентов считает размер через `rglob` (для UI), а `/{name}/usage` — через
|
||||
`du -sk`. Выбор `du` для usage: не блокирует event loop на больших каталогах дольше
|
||||
необходимого и не следует по монтированиям вглубь без нужды.
|
||||
|
||||
---
|
||||
|
||||
## Питфоллы
|
||||
|
||||
### 1. Дашборд кеширует байткод
|
||||
|
||||
Правки `.py` не видны до перезапуска. Порядок:
|
||||
|
||||
```bash
|
||||
rm -rf ~/Developer/eagle-dash/__pycache__
|
||||
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||
```
|
||||
|
||||
`kickstart` **без** `-k` переиспользует процесс и новый код не подхватывает.
|
||||
|
||||
### 2. `docker compose` vs `docker-compose`
|
||||
|
||||
На этой машине compose — отдельный бинарь `/opt/homebrew/bin/docker-compose` (v5.1.3).
|
||||
`docker compose` → `docker: unknown command`. В `agents.py` все вызовы через `docker-compose`.
|
||||
|
||||
### 3. launchd не читает shell `PATH`
|
||||
|
||||
Дашборд под launchd имеет `PATH=/usr/bin:/bin`. Поэтому:
|
||||
- `lsof` вызывается полным путём `/usr/sbin/lsof`;
|
||||
- `agents.py` из `PATH` дёргает `docker`, `docker-compose`, `du`, `launchctl` — на
|
||||
`/usr/bin` и `/bin` из них попадают только `du` и `launchctl`;
|
||||
`docker`/`docker-compose` из `/opt/homebrew/bin` **под launchd не найдутся**.
|
||||
|
||||
⚠️ **Не проверено живьём** (дашборд не перезапущен). Если mount/unmount будут возвращать
|
||||
500 с текстом «команда не найдена» — заменить в `agents.py` литералы на абсолютные пути
|
||||
`/opt/homebrew/bin/docker-compose` и `/opt/homebrew/bin/docker`.
|
||||
|
||||
### 4. `services.yaml` — запись `agents_bridge`
|
||||
|
||||
Добавлена в начало секции `services:` (файл отсортирован по `id`, `agents_bridge` первый):
|
||||
|
||||
```yaml
|
||||
agents_bridge:
|
||||
access: localhost
|
||||
category: orchestration
|
||||
critical: false
|
||||
health:
|
||||
type: http
|
||||
url: http://127.0.0.1:8790/healthz
|
||||
icon: 🌉
|
||||
label: com.agents.bridge
|
||||
log: /Users/admin/Library/Logs/agents-bridge.err.log
|
||||
name: Agents Bridge
|
||||
port: 8790
|
||||
type: daemon
|
||||
working_dir: /Users/admin/Developer/agents-bridge
|
||||
```
|
||||
|
||||
⚠️ `type: daemon` заставит **supervisor дашборда** взять сервис под управление и
|
||||
запустить свой процесс — а мост уже управляется собственным launchd (`com.agents.bridge`).
|
||||
Возможен конфликт за порт 8790. Ревизия при активации: либо `type: system` +
|
||||
`health.type: launchctl` (label `com.agents.bridge`), либо `type: none`.
|
||||
`health.type: http` на `:8790/healthz` работает в любом варианте.
|
||||
|
||||
---
|
||||
|
||||
## Активация (одна команда)
|
||||
|
||||
```bash
|
||||
rm -rf ~/Developer/eagle-dash/__pycache__ && \
|
||||
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||
```
|
||||
|
||||
Затем:
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8880/api/agents | /usr/bin/jq
|
||||
open "http://127.0.0.1:8880/?tab=agents"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Связанное
|
||||
|
||||
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4
|
||||
- [[personal/tech/agents-bridge]] — мост :8790
|
||||
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
|
||||
- скилл `eagle-dashboard` — архитектура дашборда, вкладки, кеш байткода
|
||||
Reference in new issue
Block a user