Files
obsidian-vault/personal/tech/agents-bridge.md
T

7.7 KiB
Executable File
Raw Blame History

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 плана):

cd ~/Developer/agents-bridge
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790

Проверка:

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 только так:

/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, ведущий в закрытый порт).


Порядок ручной приёмки (сработавший)

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, в командной строке секрет не светится).


Связанное

История

  • 2026-09-26: создан, поднят на :8790, протестирован (auth 401, идемпотентность, сквозной вызов CLI). Не в launchd. Логин CLI — за Alex.