[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/music-tags-review/music-tags-review.md personal/tech/agents-bridge.md personal/tech/music-tags-repair-truenas.md
This commit is contained in:
1 parent
d2f6a411f9
commit
dd9e081273
4 files changed
+492
-32
No files matched your search
@@ -0,0 +1,165 @@
|
||||
# agents-bridge — мост Mac ↔ Kraken (FastAPI :8790)
|
||||
|
||||
> Создан 2026-09-26. Компонент Фазы 4 [[personal/plans/balda-mm-phase4-agents]] (шаг 2 ✅).
|
||||
> Назначение: принять задачу от оркестратора Балды (Kraken) по HTTP, запустить
|
||||
> CLI-исполнитель в контейнере `balda-executor`, вернуть вывод.
|
||||
|
||||
---
|
||||
|
||||
## Расположение
|
||||
|
||||
| Что | Путь |
|
||||
|---|---|
|
||||
| Проект | `~/Developer/agents-bridge/` |
|
||||
| Python | `~/Developer/eagle-dash/.venv/bin/python` (3.14, fastapi 0.115.0, uvicorn 0.30.6) |
|
||||
| Конфиг + секрет | `~/Developer/agents-bridge/.env` (**0600, не коммитить**) |
|
||||
| Шаблон | `~/Developer/agents-bridge/.env.example` |
|
||||
| Состояние | `~/Developer/agents-bridge/state.sqlite3` (WAL) |
|
||||
| Лог (ручной запуск) | `/tmp/agents-bridge.log` |
|
||||
| Контейнер-исполнитель | `balda-executor` (`~/Docker/balda-executor/`) |
|
||||
|
||||
Своего venv у проекта **нет** — используется venv дашборда (там уже fastapi/uvicorn/httpx).
|
||||
|
||||
---
|
||||
|
||||
## Файлы
|
||||
|
||||
| Файл | Роль |
|
||||
|---|---|
|
||||
| `app.py` | роуты, проверка секрета, лимит параллельности, `ThreadPoolExecutor` |
|
||||
| `store.py` | SQLite: таблица `tasks`, UNIQUE-индекс по `idempotency_key`, `reap_stale()` |
|
||||
| `executor.py` | `docker exec` в контейнер; провайдеры `claude`/`codex`; таймаут; обрезка вывода |
|
||||
| `config.py` | чтение `.env` встроенным парсером + `Settings` (frozen dataclass) |
|
||||
| `requirements.txt` | fastapi==0.115.0, uvicorn==0.30.6, httpx, python-dotenv |
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```
|
||||
GET /healthz → статус моста и контейнера (без секрета)
|
||||
POST /task → 202 | 200 (идемпотентный повтор) | 401 | 429 | 400
|
||||
GET /task/{task_id} → статус + stdout/stderr/exit_code
|
||||
GET /tasks?limit=N → последние задачи (для дашборда/отладки)
|
||||
```
|
||||
|
||||
**Авторизация:** заголовок `X-Bridge-Secret` на всех `/task*`. Если `BRIDGE_SECRET` не
|
||||
настроен — `POST /task` возвращает **503**, мост не принимает задачи.
|
||||
|
||||
**Идемпотентность:** заголовок `Idempotency-Key`. Повтор возвращает **тот же** `task_id`
|
||||
с HTTP **200** (первый ответ — 202). Гарантия на уровне БД: UNIQUE-индекс + `IntegrityError`
|
||||
как обработка гонки. CLI запускается **ровно один раз** на задачу.
|
||||
|
||||
**Лимит параллельности:** `MAX_CONCURRENT_TASKS` (по умолчанию **1**). При исчерпании —
|
||||
**429**. Больше 2 задач под одним подписочным аккаунтом — риск бана за «unusual traffic».
|
||||
|
||||
---
|
||||
|
||||
## Провайдеры
|
||||
|
||||
`executor.PROVIDERS` — argv для headless-запуска:
|
||||
|
||||
| Провайдер | argv |
|
||||
|---|---|
|
||||
| `claude` | `claude -p --output-format text <prompt>` |
|
||||
| `codex` | `codex exec --skip-git-repo-check <prompt>` |
|
||||
|
||||
Дефолт — `EXECUTOR_PROVIDER=claude`. Команда собирается **списком аргументов** (без
|
||||
`shell=True`), `WORKDIR=/agents` внутри контейнера — наружу не выйти.
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Ключ | Дефолт | Смысл |
|
||||
|---|---|---|
|
||||
| `BRIDGE_HOST` | `127.0.0.1` | слушать только локально (наружу — через туннель) |
|
||||
| `BRIDGE_PORT` | `8790` | порт |
|
||||
| `BRIDGE_SECRET` | — | **обязателен**, 64 симв. `secrets.token_urlsafe(48)` |
|
||||
| `EXECUTOR_CONTAINER` | `balda-executor` | имя контейнера |
|
||||
| `EXECUTOR_PROVIDER` | `claude` | `claude` или `codex` |
|
||||
| `MAX_CONCURRENT_TASKS` | `1` | одновременно выполняемых |
|
||||
| `TASK_TIMEOUT_SECONDS` | `900` | таймаут задачи, процесс убивается |
|
||||
| `MAX_OUTPUT_BYTES` | `200000` | вывод обрезается |
|
||||
| `DB_PATH` | `state.sqlite3` | SQLite |
|
||||
|
||||
---
|
||||
|
||||
## Запуск
|
||||
|
||||
**Сейчас — вручную** (launchd ещё не сделан, см. открытый пункт 4 плана):
|
||||
|
||||
```bash
|
||||
cd ~/Developer/agents-bridge
|
||||
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
||||
```
|
||||
|
||||
Проверка:
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8790/healthz
|
||||
```
|
||||
|
||||
⚠️ На macOS `docker compose` **не работает** — только `docker-compose` (см. план, шаг 1).
|
||||
|
||||
---
|
||||
|
||||
## Питфоллы
|
||||
|
||||
### 1. `Not logged in` — это ответ CLI, а не ошибка моста
|
||||
|
||||
Сквозная проба до логина CLI вернула:
|
||||
```
|
||||
status=failed exit_code=1 stdout='Not logged in · Please run /login'
|
||||
```
|
||||
Это **признак рабочего конвейера**: docker поднял процесс, `claude` ответил. Если видишь
|
||||
эту строку — auth у исполнителя, а не мост. Логин делает **Alex сам**; копирование
|
||||
auth-файлов из `~/.claude`/`~/.codex` отклонено, не предлагать.
|
||||
|
||||
### 2. Редакция секретов Hermes искажает команды создания `.env`
|
||||
|
||||
`python -c` с f-строкой `BRIDGE_SECRET={s}` печатает в лог обрезанный фрагмент
|
||||
(`E...cret written`) — **редакция Hermes, файл на диске цел**. Не «чинить».
|
||||
|
||||
Проверять `.env` только так:
|
||||
```bash
|
||||
/usr/bin/awk -F= '{ if ($1 ~ /SECRET|TOKEN|KEY|PASSWORD/) print $1"=<redacted len "length($2)">"; else print }' .env
|
||||
```
|
||||
`read_file` на `.env` отказывает по определению (secret-bearing file) — это норма.
|
||||
|
||||
### 3. Зависшие задачи после рестарта
|
||||
|
||||
`store.reap_stale()` на старте помечает `running`-задачи старше `TASK_TIMEOUT_SECONDS`
|
||||
как `timeout` с причиной `bridge restarted while task was running`. Иначе они навсегда
|
||||
висят активными и блокируют слот лимита.
|
||||
|
||||
### 4. Мост не переживает перезагрузку
|
||||
|
||||
Пока нет launchd-плейта `com.agents.bridge`, процесс живёт только в текущей сессии.
|
||||
Это **блокер для шага 3** (туннель опубликует URL, ведущий в закрытый порт).
|
||||
|
||||
---
|
||||
|
||||
## Порядок ручной приёмки (сработавший)
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8790/task \
|
||||
-H 'Content-Type: application/json' -d '{"prompt":"hi"}' # → 401 (нет секрета)
|
||||
|
||||
# с секретом и одним Idempotency-Key дважды → один и тот же task_id, 202 затем 200
|
||||
# GET /task/{id} → status/stdout/exit_code
|
||||
```
|
||||
|
||||
Тестовый скрипт: `/tmp/test-bridge.sh` (читает секрет из `.env`, в командной строке
|
||||
секрет не светится).
|
||||
|
||||
---
|
||||
|
||||
## Связанное
|
||||
|
||||
- [[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]] — общий план делегирования
|
||||
|
||||
## История
|
||||
- **2026-09-26**: создан, поднят на :8790, протестирован (auth 401, идемпотентность,
|
||||
сквозной вызов CLI). Не в launchd. Логин CLI — за Alex.
|
||||
Reference in new issue
Block a user