From 6ea574a07bfae3debaf7936137dcd24e417cf626 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 29 Sep 2026 17:50:51 +0600 Subject: [PATCH] [2026-09-29] eagle: personal/plans/balda-callee-prism-plan.md --- personal/plans/balda-callee-prism-plan.md | 1131 +++++++-------------- 1 file changed, 342 insertions(+), 789 deletions(-) diff --git a/personal/plans/balda-callee-prism-plan.md b/personal/plans/balda-callee-prism-plan.md index 3ff1c0c9..aeccdd0a 100755 --- a/personal/plans/balda-callee-prism-plan.md +++ b/personal/plans/balda-callee-prism-plan.md @@ -1,873 +1,426 @@ -# Балда → Callee + Prism: делегирование +# Balda + Prism/Beads + Execution Broker + Callee + Codex -> Обновлён 2026-09-29. Статус: **C0 закрыта. Топология пересмотрена: стек переезжает -> на Mac, на Kraken остаётся только MM-сервер.** -> ✅ **2026-09-29: БАЛДА СОБРАНА И РАБОТАЕТ НА MAC** — контейнер `balda-mm-mac`, -> образ `balda-mm:0.1.16-mac` из `bf4a6ae4`, сквозная приёмка пройдена (бот ответил -> в `qentra:balda-test`). Осталось: `callee` + `dsh-acp` + `bd` + prism (шаги 3-7, 9 C1). -> ⛔ **2026-09-29: старый Balda-стек на Kraken СНЕСЁН** (контейнеры `balda-mm`, -> `balda-executor-mcp`, `obsidian-mcp` + все образы `balda-mm:*`). Mattermost жив. -> Решение §6.11 «оставить Kraken-инстанс живым как запасной» — **ОТМЕНЕНО Alex'ом**. -> C1 закрыта **частично**: Балда собрана и работает, callee/prism не начаты. -> Опорная заметка по контрактам инструментов: [[personal/tech/callee-prism]]. -> -> 🔴 **Открытая развилка (§6.7):** как давать доступ к `xcodebuild` / `lume` / `docker build` — -> узкий контейнер (три операции недоступны) или гибрид с хост-исполнителем. Определяет объём C1a. +> Обновлено: 2026-09-29 +> Статус: целевая архитектура зафиксирована; Broker и интеграции ещё не реализованы. ---- +## 1. Цель -## 1. Целевая архитектура +Balda принимает задачу в Mattermost, ведёт её состояние в Prism/Beads и запускает изолированное выполнение в одноразовой macOS VM. -**Принцип: стек — единое целое по признаку «работает с файлами на Mac».** Всё, что трогает -код, сборку, тесты, хосты, обязано быть рядом. Всё, что только принимает сообщения — может -жить отдельно. +Код, сборка, тесты и Xcode выполняются только внутри VM. Хост Mac управляет VM через Virfield, но не исполняет команды агента в основной системе. -``` -┌─ Mac ─────────────────────────────────────────────┐ -│ Балда (оркестрация, планирование, крон) │ -│ callee (движок воркфлоу) │ -│ движки: codex / claude / dsh-acp │ -│ bd (Beads) — состояние задач │ -│ тулчейн: ~/Developer, xcodebuild, lume, docker │ -└───────────────────────────────────────────────────┘ - ▲ - │ Mattermost API — ИСХОДЯЩЕЕ соединение с Mac - │ -┌─ Kraken ──────────────────────────────────────────┐ -│ MM-сервер + postgres ← только это │ -└───────────────────────────────────────────────────┘ +Единственная целевая цепочка исполнения: + +```text +Mattermost + → Balda + → Execution Broker + → Virfield на Mac + → macOS VM + → Callee workflow + → Codex ACP sessions ``` -### Что это убирает +Контур состояния задачи: -| Узел | Почему больше не нужен | -|---|---| -| `balda-executor-mcp` (:8890) | его работа — пропихнуть вызов к мосту. Балда на Mac → пропускать нечего | -| мост `agents-bridge` (:8790) | был границей хостов. Границы нет — нет моста | -| Cloudflare Tunnel для этого потока | нужен только для внешнего доступа к MM | -| MCP-сервер `obsidian-mcp` на Kraken | Obsidian рядом с Балдой | +```text +Balda + → Prism adapter + → bd + → Dolt/Beads database +``` -📌 **Соединение — исходящее с Mac к MM.** Никаких входящих портов, туннеля внутрь Mac, -«Mac спит → мост недоступен». При недоступности MM — переподключение. +Обратный поток: -### Что остаётся на Kraken +```text +Codex + → ACP result + → Callee event + → Broker event log + → Balda + → Prism/Beads + Mattermost +``` -Только **MM-сервер + postgres**. Доступность 24/7, один сетевой вход, ничего тяжёлого. -Kraken разгружается: уходят Балда, MCP-сервер, obsidian-mcp (при load 8.03 на 4 ядрах это -заметно). +## 2. Что уже есть -### Разделение расходов +### Подтверждено -| Роль | Где | Провайдер | -|---|---|---| -| Оркестратор | Mac | DeepSeek `deepseek-chat` | -| Исполнитель-роли callee | Mac | DeepSeek | +1. Balda работает в Docker на Mac и отвечает в Mattermost. +2. Mattermost-сервер остаётся на Kraken; Balda подключается к нему с Mac. +3. Obsidian MCP доступен Balda по `http://host.docker.internal:3101/sse`. +4. Virfield MCP работает на хостовом Mac и умеет подготовить VM: clone golden image, start, wait for IP/SSH, create tunnel. +5. Host Codex и Claude уже подключены к Virfield MCP. -📌 «DeepSeek с обоих концов» = обе стороны на DeepSeek. Codex/Claude подключаются позже -заменой провайдера в `DynamicRole` — без правки графа. +### Текущая конфигурация Balda ---- +- основной provider: DeepSeek через OpenAI-compatible API; +- `permissions.mode: allow_all`; +- `workspace.mode: off`; +- Virfield в Balda не подключён; +- Callee CLI и описания агентов находятся в окружении Balda по текущему сетапу, но целевой запуск Callee должен происходить внутри VM. -## 1.1 Балда на Mac: контейнер и границы доступа +### Чего пока нет + +- Execution Broker; +- сетевого API управления Virfield для Broker; +- VM runner для управляемого запуска Callee; +- обратного журнала событий Callee → Balda; +- адаптера Balda → Prism/Beads; +- подтверждённого сквозного запуска Story → VM → build/test → отчёт. + +## 3. Владельцы ответственности + +### Balda — оркестратор верхнего уровня + +Balda: + +- принимает задачу и уточнения из Mattermost; +- создаёт и обновляет Story в Prism/Beads; +- выбирает разрешённый workflow по его идентификатору; +- запускает выполнение через Broker; +- показывает запросы Human и итог пользователю. + +Balda не управляет процессами внутри VM напрямую. Она работает только через контракты Prism adapter и Execution Broker. + +### Prism adapter + Beads — долговременное состояние + +Prism adapter принадлежит контуру Balda и физически может быть отдельным sidecar-контейнером. + +Он: + +- предоставляет Balda узкие операции Story/Task/phase; +- вызывает `bd`; +- подключается к Dolt/Beads database в соседнем контейнере; +- обеспечивает идемпотентную запись событий Broker; +- остаётся единственным писателем предметного состояния задачи. + +Broker, Callee и Codex не пишут в Beads напрямую. Они возвращают факты выполнения, а Balda решает, как отразить их в Story. + +### Execution Broker — инфраструктурный исполнитель + +Broker живёт отдельным sidecar-контейнером рядом с Balda. + +Он: + +- получает от Balda команду запуска; +- арендует VM через Virfield; +- запускает VM runner и Callee; +- хранит техническое состояние run и журнал событий; +- передаёт ввод, отмену и ссылки на артефакты. + +Broker не выбирает workflow, не исполняет его граф и не меняет Prism/Beads. + +### Virfield — граница управления хостом + +Virfield работает на Mac-хосте и владеет жизненным циклом VM: + +- clone/start/stop/delete; +- IP и SSH readiness; +- сетевые туннели; +- lease и принудительная уборка VM. + +Для Broker нужен узкий аутентифицированный HTTP API или HTTP MCP-обёртка над этими операциями. Произвольного host shell в этом API нет. + +### Callee и Codex — выполнение внутри VM + +Callee исполняет workflow: фазы, ветвления, циклы, Human-паузы и запуск ролей. + +`implementer`, `reviewer`, `verifier` — роли Callee. Каждая роль запускает новую Codex ACP-сессию с отдельным prompt и контекстом. + +Codex изменяет код, запускает `xcodebuild`, тесты и другие команды внутри VM. Codex не запускает Callee и не управляет Story. + +## 4. Prism и prism-callee + +Официальный `prism-callee` — плагин к coding-agent host. В штатной схеме он делает coding agent владельцем Beads и запускателем Callee: + +```text +Codex → prism-callee skill → Callee → дочерние Codex sessions +``` + +Эта штатная топология не является целевой, потому что верхним оркестратором должен оставаться Balda. + +В целевой схеме используются отдельно: + +- lifecycle и правила Prism — как основа skill/адаптера Balda; +- Callee workflow pack — как версионируемые workflow-файлы внутри VM; +- Beads — через отдельный Prism adapter; +- Codex — только как исполнитель ролей Callee. + +До реализации требуется проверить, какие части официального плагина можно импортировать без изменений, а какие нужно адаптировать для Balda. + +## 5. Прямой data flow + +1. Пользователь ставит задачу Balda в Mattermost. +2. Balda через Prism adapter создаёт Story и получает `story_id`. +3. Balda выбирает `workflow_id` из статического allowlist и вызывает `execution_start`. +4. Broker просит Virfield создать VM, затем запускает внутри неё VM runner и Callee. +5. Callee выполняет граф и для каждой Role запускает локальный Codex через ACP stdio. + +Минимальный запрос запуска: + +```json +{ + "idempotency_key": "story:bd-42:attempt:1", + "story_id": "bd-42", + "workflow_id": "prism/story", + "workflow_version": "pinned-git-sha", + "repository": "git@host:org/repo.git", + "revision": "main", + "vm_template": "macos-27-golden", + "task": "Добавить CSV export" +} +``` + +Broker возвращает `run_id` сразу после надёжной записи команды. Подготовка VM продолжается асинхронно. + +## 6. Обратный data flow + +1. Codex возвращает результат роли в Callee по ACP. +2. Callee переводит workflow в следующее состояние и отдаёт структурированное событие VM runner. +3. VM runner передаёт событие Broker; Broker сохраняет его до ответа Balda. +4. Balda читает события через `execution_events(run_id, after_event_id)` и подтверждает курсор. +5. Balda обновляет Story через Prism adapter и публикует значимые изменения в Mattermost. + +События доставляются как минимум один раз. Повторная доставка безопасна благодаря уникальному `(run_id, event_id)`. + +Минимальное событие: + +```json +{ + "event_id": 17, + "run_id": "run-123", + "story_id": "bd-42", + "type": "role_completed", + "phase": "review", + "role": "reviewer", + "status": "passed", + "summary": "Tests pass", + "artifacts": [ + {"kind": "git_commit", "ref": "abc123"}, + {"kind": "xcresult", "ref": "artifact://run-123/tests"} + ] +} +``` + +Тяжёлые логи и `.xcresult` не помещаются в журнал. Broker хранит только ссылки, checksum, размер и срок жизни. + +## 7. Human approval + +1. Callee доходит до Human node и приостанавливает workflow. +2. VM runner отправляет `human_required` с вопросом и вариантами. +3. Broker фиксирует состояние `waiting_human`. +4. Balda показывает вопрос в Mattermost и получает ответ в том же session/topic. +5. Balda вызывает `execution_respond`; Broker доставляет ответ в конкретный paused run. + +Связка обязательна: `chat_id/topic_id ↔ story_id ↔ run_id ↔ human_request_id`. + +Ответ должен быть идемпотентным: повторный `human_request_id` не возобновляет workflow дважды. + +## 8. Execution Broker: контракт ### Где живёт -**В Docker-контейнере.** Причины те же, что были на Kraken: изоляция, воспроизводимость, -образ уже собран (`balda-mm:0.1.13`), откат одним тегом. +Отдельный контейнер в одном Docker network с Balda. У Broker собственный persistent volume с SQLite. -### Проблема: контейнер изолирован, а нужен тулчейн +Northbound: HTTP MCP для Balda. +Southbound: узкий HTTP API Virfield и SSH к выданной VM. +Внутри VM: VM runner → Callee → Codex ACP stdio. -В контейнере нет и не должно быть по умолчанию: +### MCP tools -| Чего нет | Почему это правильно | -|---|---| -| `~/.ssh` | ключи дают доступ ко всем хостам сразу | -| `docker.sock` | **эквивалент root на хосте** | -| пароли, `.env` с токенами | Балде они не нужны | -| `~/.aws`, `~/.config/gh`, прочие креды | то же | +1. `execution_start(request) → run_id` +2. `execution_status(run_id) → state, phase, last_event_id` +3. `execution_events(run_id, after_event_id) → events, next_cursor` +4. `execution_respond(run_id, human_request_id, answer)` +5. `execution_cancel(run_id, reason)` и `execution_artifacts(run_id)` -Но нужны: `xcodebuild`, `lume`, `docker build`, опционально `ssh`. +### Состояния run -### Решение: три класса доступа - -| Класс | Что | Как | Права | -|---|---|---|---| -| **1. Прямой** | файлы проектов, git | bind-mount `~/Developer` (только нужные проекты, не весь `~`) | чтение-запись в проекте | -| **2. Через шлюз** | `docker build`, `lume` | **не давать `docker.sock` и бинарь** — вместо этого узкий сокет/скрипт-обёртка на хосте с белым списком команд | только разрешённые операции | -| **3. Через ssh** | доступ к хосту, TrueNAS, Kraken | `ssh` с ключом **ограниченным** `command=` в `authorized_keys` | одноразовые команды, без шелла | - -🔴 **`docker.sock` в контейнер не монтировать никогда.** Это тождественно выдаче root на -хосте: через сокет можно запустить контейнер с `-v /:/host` и получить всё. Если нужны -сборки — либо `docker build` через ограниченную обёртку на хосте, либо **вынести сборку в -отдельный Script-узел, исполняемый на хосте**, а не в контейнере. - -### Xcode и lume: где физически - -| Задача | Где исполняется | Почему | -|---|---|---| -| `xcodebuild` | **не в Linux-контейнере** — в lume VM с macOS | контейнер Linux, Xcode только на macOS | -| `lume` | **на хосте Mac** | бинарь на хосте, в контейнере недоступен | -| `docker build` | **на хосте Mac** | `docker.sock` в контейнер не даём | - -📌 **Это означает: часть ролей исполняется НЕ в контейнере Балды.** Значит нужен явный -механизм «роль → хост-исполнитель». Вариант — callee запускается не только в контейнере, -но и на хосте (для ролей, требующих Xcode/lume/docker); маршрутизация роли по хосту -описывается в графе. - -### Границы: что Балда НЕ должна получить - -| Ресурс | Статус | -|---|---| -| пароли, токены в открытом виде | ❌ только через env-файл с ограниченным набором (DeepSeek-ключ) | -| `~/.ssh` целиком | ❌ только конкретные ключи | -| `docker.sock` | ❌ никогда | -| весь `~` | ❌ только выбранные проекты | -| root на хосте | ❌ | -| host-шелл | ❌ только белый список команд | - ---- - -## 1.1a Формула прав - -**`права = права процесса × смонтированное × разрешённое в графе`** - -| Уровень | Кто задаёт | Пример | -|---|---|---| -| Чем запущен процесс | вы, при старте | контейнер / хост-юзер / отдельный системный юзер | -| Что смонтировано | compose | `~/agents:/agents` — и ничего больше | -| Что разрешено в графе | автор воркфлоу | какие роли и скрипты вообще есть в каталоге | - -🔴 **`permissions.mode: allow_all` у роли — это про права модели ВНУТРИ ACP-сессии** -(править файлы, запускать команды). Оно **не** расширяет, что процесс видит в файловой -системе. Это два разных ограничения, не путать. - -### Где callee — там и движки, и Script'ы - -🔴 **ACP-провайдер — дочерний процесс callee.** Движок на хосте + callee в контейнере -технически невозможны: это один процесс-тред. - -| Исполнитель | Что исполняет | Права | -|---|---|---| -| callee | сам граф: циклы, роли, state | права процесса, которыми запущен | -| движок (codex/claude/dsh) | модель: думает, правит файлы | наследует + своя песочница | -| Script | shell: сборки, тесты, ssh | наследует права процесса | - -**Следствие:** «callee на хосте» = **всё исполнение на хосте целиком**. Промежуточного -варианта нет. - -### Динамических маунтов не существует - -На macOS Docker маунты задаются **при создании контейнера**; live `mount --bind` контейнер -не видит; симлинк на внешний путь битый. **Вопрос «как модель запросит доступ к папке» -не имеет решения через маунты.** - -| Подход | Как | Оценка | -|---|---|---| -| A. Смонтировать всё заранее | `~/Developer:ro` | запись нельзя | -| B. Пересоздание контейнера | правка compose + `up -d` (~3 с) | нужен механизм вне контейнера | -| C. Хост-агент ставит права сам | callee на хосте | полный доступ, ноль изоляции | -| **D. Проект = рабочая копия** ✅ | модель работает в `~/agents/<проект>`, результат уходит в `~/Developer` через git | доступ задаётся наличием репозитория, а не маунтом | - -**Решение: D.** Модель всегда работает в `~/agents/<проект>` (уже смонтировано), результат -попадает в проект через `git`. Право выдаётся точечно. - -### Доступ не запрашивается — он выбирается маршрутом - -Модель **не просит** прав: она не может эскалировать. Если у роли нет доступа, задача -**падает**, а не спрашивает. Доступ задаёт автор графа. - -``` -Роутер в графе - ├─ задача требует ~/agents → роль в контейнере - ├─ задача требует Xcode/lume → роль на хост-исполнителе - └─ задача требует хоста по ssh → роль со ssh-ключом (command=) +```text +accepted +→ provisioning +→ booting +→ running +→ waiting_human +→ running +→ succeeded | failed | canceled | expired ``` -**Разграничение = статический роутинг по типам задач**, а не динамические права. - -### Гибрид «контейнер + узкий хост-исполнитель» - -| Вариант | Права | Динамика | Оценка | -|---|---|---|---| -| callee в контейнере | узкие, контролируемые | только `~/agents` | безопасно; Xcode/lume/docker недоступны | -| callee на хосте | **полный доступ пользователя** | всё работает | ноль изоляции | -| **гибрид** ✅ | контейнер для основного + узкий хост-исполнитель для Xcode/lume/docker | маунты не нужны | рекомендован | +Каждый переход записывается транзакционно вместе с событием. После рестарта Broker продолжает незавершённые run или переводит их в явно диагностируемое состояние. -🟡 Хост-исполнитель — дыра в изоляции **по определению**, но **узкая и явная**: три операции -(xcodebuild, lume, docker build), а не «что угодно». +### Внутреннее состояние Broker ---- +Broker хранит только технические данные: -## 1.1b Роли: жизненный цикл и контроль +- `run_id`, `story_id`, idempotency key; +- VM id, IP, lease deadline; +- workflow id/version и process/session id; +- event cursor, terminal status и error code; +- artifact references и cleanup status. -### Что такое роль +### VM runner -**Роль — YAML/MD-файл в каталоге.** Не «право», не «токен», а **описание листа графа**: -какой провайдер, какой промпт, какие параметры, какой таймаут. - -```yaml -apiVersion: callee.metalagman.dev/v1alpha1 -kind: Role -spec: - description: Собирает Xcode-проект - provider: - type: generic_acp - cmd: dsh-acp - model: deepseek-chat - timeout: 20m ---- -Собери проект {{ .Params.project }} и верни отчёт об ошибках. -``` - -### Кто их раздаёт и откуда они берутся - -| Источник | Что даёт | -|---|---| -| Каталог по умолчанию | 6 стартовых ресурсов (`callee setup`) | -| Импорт из git | `callee agent import baldaworks/prism-callee --prefix prism` | -| **Наши собственные** | файлы в рабочем каталоге проекта / `~/.callee/` | +Нужен небольшой процесс внутри образа VM. Он отделяет Broker от консольного формата Callee. -**Роли не выдаются динамически — они ЛЕЖАТ в каталоге.** Кто положил файл, тот и «выдал» -роль. Никакого рантайм-механизма выдачи прав нет. +Runner: -### Что раздаётся, а что нет +- проверяет входной manifest и allowlist workflow; +- запускает Callee в рабочей директории репозитория; +- преобразует lifecycle/output в стабильные JSON events; +- принимает cancel и Human response; +- возвращает terminal result и artifact manifest. -| Сущность | Динамически выдаётся? | Чем регулируется | -|---|---|---| -| Роль (файл графа) | ❌ статично | наличие файла в каталоге | -| Провайдер (тип, `cmd`) | ❌ статично, или из `State` через DynamicRole | автор графа | -| **Файлы** | ❌ | монтирование + рабочая копия (§1.1a) | -| **SSH-ключ** | ❌ | наличие файла у процесса | -| **Разрешение модели внутри сессии** | ⚠️ **да** — `allow_all`/`ask`/`deny_all` | конфиг Балды | +Broker не должен парсить человекочитаемый ANSI/TTY-вывод Callee. Если Callee уже имеет стабильный machine-readable API, runner становится тонким адаптером. -🔴 **Единственное, что реально запрашивается в рантайме — разрешение модели внутри ACP-сессии** -(`ask` → вопрос в чат). Всё остальное статично. +## 9. Изоляция и секреты -### Ваша гипотеза: проверяю по пунктам +Основная граница безопасности — одноразовая macOS VM. -> «я ставлю задачу → приходит запрос "дать такую-то роль для такой-то задачи" → апрув → доступ появился» +- исходный проект клонируется в VM без host bind mounts; +- Codex, Callee, Xcode и тесты работают внутри VM; +- Broker получает только scoped SSH key для конкретной VM; +- Git credentials выдаются на один репозиторий и ограниченный срок; +- VM удаляется после выгрузки результатов или по lease timeout. -| Часть гипотезы | Вердикт | -|---|---| -| Задача приходит в систему | ✅ да | -| **Приходит запрос «выдать роль»** | ❌ **нет такого механизма.** Роли не выдаются, они лежат в каталоге | -| **Апрув → у агента появляется доступ** | ⚠️ **частично — не к инструментарию, а к действию** | -| **Без апрува доступа нет** | ✅ **верно, но иначе** | +Хостовый Virfield API разрешает только операции жизненного цикла VM. Balda и Broker не получают `docker.sock`, пользовательский `~/.ssh` или произвольный shell на Mac. -**Что происходит на самом деле:** доступ у роли **есть всегда** — он определяется тем, чем -запущен callee и что смонтировано. Апрув не выдаёт доступ, апрув **разрешает выполнить -операцию**, которая уже технически возможна. +## 10. Неподтверждённое -### Значит правильная формулировка +### Блокирует прототип -``` -Роль лежит в каталоге (доступ есть по определению) - │ - ▼ -Граф доходит до узла → Human («собрать Xcode в VM macos-27?») → пауза - │ - ├─ апрув → Script исполняется, потому что права у процесса УЖЕ были - └─ отказ → граф останавливается, ничего не исполняется -``` +1. Есть ли у Callee стабильный machine-readable event stream и программный resume Human node. +2. Можно ли запустить workflow pack `prism/story` отдельно от stock plugin host. +3. Как Codex ACP получает workspace/cwd и сохраняет его между Role visits. +4. Как передавать cancel в Callee без повреждения terminal result. +5. Какой минимальный Virfield API можно безопасно открыть Broker. -🔴 **Разница принципиальная.** Вы описали **capability-модель** (права выдаются на задачу). -Callee работает по **статической модели** (права постоянны, гейты контролируют исполнение). +### Требует проверки до production -**Что это значит для вашей безопасности:** гейт `Human` защищает от **действия**, но не от -**возможности**. Если у процесса есть ssh-ключ — ключ есть всегда, вне зависимости от апрувов. -Изоляцию даёт **только то, чем процесс запущен и что смонтировано** — не граф. +- установка и авторизация Codex внутри golden VM; +- доступность Dolt/Beads из Prism adapter после рестартов; +- формат и размер Xcode артефактов; +- восстановление Broker после падения во время `waiting_human`; +- параллельные Story и лимиты VM/CPU/RAM на Mac. -### Что можно сделать, чтобы приблизиться к вашей модели +## 11. Варианты -| Способ | Как | -|---|---| -| **Разные профили запуска** | контейнер «узкий» (без ssh, без docker) и хост-исполнитель «широкий» — выбор профиля роутером | -| **Роль на задачу** | задача → своя роль с нужным `cmd`/таймаутом; доступ = какой профиль она выбрала | -| **Апрув как гейт исполнения** | `Human` перед дорогой/опасной операцией | -| **Белый список команд в Script** | `Script` не «любой shell», а конкретный скрипт с фиксированной командой | - ---- - -## 1.2 Правила работы: Hermes-конфигурация в Балде +### A. Sidecar Broker + VM runner — выбран -В Hermes у Alex накоплены: правила ведения документации (Obsidian — источник истины), -правила работы (план → апрув → работа, бэкапы, запрет удаления), формат ответов -(артефакт первой строкой, единица счёта, ADHD-friendly), правила приёмки (от учётки -заказчика, а не подставной). +Balda остаётся оркестратором, Broker изолирует инфраструктуру, Callee владеет workflow, Codex работает в VM. Это целевая схема. -**Эти правила — не код, а контент.** Значит их надо переносить как контент, а не -зашивать в Балду. - -| Слой Hermes | Аналог в Балде | Как переносится | -|---|---|---| -| System prompt / SOUL | `balda.global_instruction` | текст в конфиг | -| Память (`memory`) | session memory Балды + Obsidian | Obsidian MCP | -| Скиллы (процедуры) | **callee-ресурсы** (`Role`/`Sequential`) | правила работы как воркфлоу | -| Правила документирования | `global_instruction` + Role, обновляющий Obsidian | текст | -| Формат ответов | `deliveryfmt` / `questionfmt` | рендеринг, не промпт | -| Правила приёмки | `Script` + `Human` в графе | исполняемый граф | - -🔴 **Ключевое различие:** в Hermes правила лежат в промпте и памяти — это **мягкое** -принуждение (модель может нарушить). В callee часть правил становится **жёсткой**: -валидация графа, `Human`-гейт, `Script`-проверка. Например «план → апрув → работа» -превращается в узел `Human` между фазами, а не в просьбу в промпте. - -**Что это значит на практике:** правила, которые важны как инвариант (апрув, бэкап, -приёмка) — переносятся в граф как узлы. Правила, которые про стиль (формат ответа, -тон) — остаются в `global_instruction`. - -### Obsidian - -Балда ходит в Obsidian по MCP. На Mac поднимается `obsidian-mcp` рядом (по образцу -Kraken — контейнер `mcp-proxy --port 3101 -- mcpvault /vault`, `type: sse`), том -`~/obsidian:/vault`. - ---- +### B. Broker как host daemon -## 2. Провайдер DeepSeek для callee +Меньше сетевых переходов к Virfield и SSH, но Broker получает более широкую поверхность хоста. Использовать только если контейнерный sidecar не может надёжно управлять VM. -### 2.1 ACP-адаптер — выбран `@openma/deepseek-harness-acp@0.4.33` - -Свой адаптер писать не надо. +### C. Stock prism-callee plugin -| | Значение | -|---|---| -| Пакет | `@openma/deepseek-harness-acp` | -| Версия | **`0.4.33`** | -| `bin` | **`dsh-acp`** → `dist/bin.js` — запускаемый ACP-сервер | -| Родословная | обёртка вокруг официального DeepSeek Harness | -| Node | **`>= 22.15`** (в контейнере 24.21.0 ✅) | -| Лицензия | Apache-2.0 | -| Зависимости | `@agentclientprotocol/sdk 1.4.0`, `tar`, `zod` | +`Codex → plugin → Callee → Codex`. Быстрее для проверки Prism, но Balda перестаёт быть владельцем lifecycle. Подходит как временный reference test, не как production topology. -Установка (standalone-вариант, подходит для `generic_acp`): - -```bash -npm i -g @openma/deepseek-harness-acp -# запуск: dsh-acp -``` - -### 2.2 Креды - -Приоритет: **флаги → переменные окружения → дефолты**. - -| Способ | Значение | -|---|---| -| Флаг | `api-key`, либо `api-key:` при нескольких провайдерах | -| Env | **`DEEPSEEK_API_KEY`** | -| Env | **`DEEPSEEK_BASE_URL`** — OpenAI-совместимый endpoint override | - -🔴 `DEEPSEEK_BASE_URL` — это OpenAI-совместимый override, значит переезд на Colex -сводится к смене одной переменной, без правки графа. - -📌 Ключ `DEEPSEEK_API_KEY` лежит в `~/.hermes/hermes-whale/.env` (длина 35). - -📌 Креды не попадают в конфиг клиента: адаптер переиспользует ключ из credential store -(`dsh web` / `dsh-acp login`), либо берёт `DEEPSEEK_API_KEY` из окружения процесса. - -### 2.3 Конфигурация Role - -Тип провайдера — **`generic_acp`**, потому что список поддерживаемых типов закрытый: -`codex`, `claude`, `opencode`, `copilot`, `grok`, `cursor`, `generic_acp`. -DeepSeek в нём отсутствует. - -```yaml -provider: - type: generic_acp - cmd: dsh-acp # одно имя исполняемого файла, НЕ shell-строка - model: deepseek-chat - timeout: 20m -``` +### D. Native remote ACP -| Поле | Контракт | -|---|---| -| `cmd` | одно имя исполняемого файла; для `generic_acp` обязателен | -| `extraArgs` | упорядоченный argv-список; каждый элемент непустой | -| `model` / `reasoning` / `mode` | опциональны; пустые → дефолт бэкенда | -| `timeout` | Go duration, **по умолчанию `15m`** | -| Конфиг провайдера | обязан быть **вложенным** (`spec.provider.*`), плоские поля не поддержаны | - -### 2.4 DynamicRole — провайдер из состояния - -Все поля провайдера — шаблоны, рендерятся из состояния в момент визита: - -```yaml -kind: DynamicRole -spec: - provider: - type: '{{ .State.provider }}' - model: '{{ .State.model }}' - timeout: '{{ .State.timeout }}' - state: - provider: generic_acp - model: deepseek-chat - timeout: 20m -``` - -| Факт | Значение | -|---|---| -| Что рендерится | `type`, `cmd`, `model`, `reasoning`, `mode`, `timeout`, каждый `extraArgs` | -| Из чего | `.Prompt`, текущий `.Input`, один согласованный post-node снапшот `.State`, `.Params` | -| Чего нельзя | `.Output`, env, файловая система, сеть, часы, рандом, криптография, мутаторы | -| Когда валидируется | до поиска и старта процесса; ошибка → визит падает, провайдер не стартует | -| Периметр визита | конфиг фиксирован на весь визит, включая REPL-ходы; новый визит Loop рендерит заново | -| Переиспользование процесса | одинаковые effective type+cmd → один процесс; сессия всегда новая | - -🔴 **Безопасность:** state-driven `cmd` и `extraArgs` могут выбирать исполняемый код. -Не подавать туда вывод модели без ограничения через авторскую шаблонную логику. - ---- - -### 2.6 Отлов опасных команд: возможные механизмы - -> ⚠️ Исследование 2026-09-29. **Решения нет** — зафиксированы варианты и границы, выбор не сделан. - -**Где физически можно перехватить команду модели:** - -| Место перехвата | Что видит | Кто решает | -|---|---|---| -| Внутри ACP-сессии (адаптер `dsh-acp`) | **каждую** команду модели | адаптер / обёртка вокруг него | -| `Script`-узел в графе | **только** команды сборок/деплоев, вынесенные в граф | граф callee | - -🔴 **Ограничение охвата:** узел `Human` в графе защищает **только** `Script`-шаги. Команды, которые -модель запускает внутри своей ACP-сессии (обычный `bash` харнесса), граф **не видит вообще**. -Значит «отловить все опасные команды» и «отловить только свои сборки» — принципиально разные -объёмы работы: первое требует правки адаптера, второе делается чисто в графе. - -**Механизм 1 — regexp / белый список.** -Детерминированно, ~0 мс, не обходится вероятностной ошибкой. Ловит только известные шаблоны -(`rm -rf`, обращение к `docker.sock`, `~/.ssh`, `curl | sh`); обфускация обходит. - -**Механизм 2 — Jev (`TypeSafeJev` / типизированная оценка).** -Возвращает **числа**, а не решение: `choice` (вероятности по опциям), `score` (0–3 по рубрике), -`noul` (0–1 для утверждения). Не LLM, не вызывает инструменты, текст не генерирует. -Порог и действие пишет **ваш код**: - -``` -risk(0–3) / injection(noul) / exfiltration(noul) - ├─ if risk >= 1.5 || injection >= 0.8 → deny - ├─ if risk >= 0.5 || exfiltration >= 0.5 → ask - └─ иначе → allow -``` - -🔴 **Если `if` не написан — тул выполнится как обычно.** Jev сам не запрещает и не спрашивает; -без обвязки он даёт «команда опасная» и ничего не меняет. Латентность — сотни мс на каждый -тул-колл. Вендор позиционирует его как один слой defense-in-depth, не замену sandboxing. - -**Механизм 3 — Script + Human в графе.** -Апрув только для того, что само вынесено в `Script`. Внутри ACP-сессии не работает. - -**Граница, которая важнее гейта:** любой из трёх механизмов защищает **действие**, но не -**возможность** (§1.1a). Права определяет процесс и монтирование; гейт лишь разрешает -выполнить уже технически возможное. Если ключ есть у процесса — он есть всегда. - -**Сопутствующие факты:** - -- `TypeSafeJev` — один из 10 публичных kinds callee ([personal/tech/callee-prism]] §2.1), то есть - нативный узел графа; схема порогов выше — из вендорской доки tool-approval. -- `permissions.mode` (`allow_all`/`ask`/`deny_all`) — режим разрешений **внутри** ACP-сессии. - В Балде `ask` работает для MM, вопрос уходит реплаем в тред (§4). -- 🔴 **Не подавать вывод модели в `cmd`/`extraArgs` DynamicRole** — исполнение произвольного кода - мимо любого гейта внутри сессии (питфолл 8). -- Разбор слоёв привратника: [[Research/Gatekeeper Architecture for Agent Tool Calls]] — подходы 3 - (OPA), 4 (Cedar), 5 (MCP-прокси), 8 (Jev). - ---- - -## 2.5 Карта стека: кто кого дёргает - -> ⚠️ Обновлено под топологию §1 (стек на Mac). Прежний вид — «Балда на Kraken + MCP + -> мост → callee» — **устарел**, оставлен ниже как справка по демонтированным узлам. - -### Актуально (топология «стек на Mac») - -``` -ВЫ (Mattermost) - │ задача словами - ▼ MM API — исходящее соединение с Mac -Балда-оркестратор — Mac, balda-mm:0.1.13, DeepSeek - │ понимает задачу, читает Obsidian, планирует, держит крон, отвечает - │ НЕ пишет код, НЕ собирает, НЕ ходит по хостам - ▼ -callee — Mac - │ ДВИЖОК ВОРКФЛОУ: компилирует граф, валидирует целиком до запуска - │ МОДЕЛИ НЕ СОДЕРЖИТ: дирижёр — раздаёт роли, гоняет циклы, собирает артефакт - ├─ Role ────► ACP-процесс ───► МОДЕЛЬ (DeepSeek / позже Codex) - ├─ Script ──► shell на Mac: git, docker build, xcodebuild, pytest - └─ Human ───► пауза, ответ реплаем в треде MM - ▼ -bd (Beads) — трекинг Story → Tasks, живёт между сессиями - ▼ -артефакт → Балда → Mattermost -``` - -### Демонтированные узлы (не нужны в новой топологии) - -| Узел | Почему больше не нужен | -|---|---| -| `balda-executor-mcp` (:8890) | его работа — пропихнуть вызов к мосту; Балда на Mac → пропускать нечего | -| мост `agents-bridge` (:8790) | был границей хостов; границы нет | -| Cloudflare Tunnel (для этого потока) | остаётся только для внешнего доступа к MM | -| `obsidian-mcp` на Kraken | Obsidian рядом с Балдой | - -### Кто есть кто - -| Компонент | Где | Что это | -|---|---|---| -| Балда | **Mac** | Оркестратор верхнего уровня: понимает смысл задачи, планирует, крон | -| **callee** | Mac | Движок воркфлоу. Граф, роли, циклы. **Не модель** | -| Role / DynamicRole | Mac | Узел, который зовёт модель | -| **ACP** | Mac | Протокол «редактор ↔ агент». Транспорт, не модель | -| **dsh / dsh-acp** | Mac | Адаптер: отдаёт DeepSeek по протоколу ACP | -| **Codex / Claude CLI** | Mac | Агент-харнесс: модель + умение править файлы и запускать команды | -| **bd (Beads)** | Mac | Память задач: что сделано, что нет | -| **prism** | Mac | Воркфлоу: specify → design → breakdown → human → apply → verify | -| **lume** | Mac (хост) | macOS-виртуалки: `macos-15-golden`, `macos-27-golden`, `uitest-26.4.1-golden`, `pdf-hud-macos27` | -| MM-сервер | **Kraken** | Только чат-сервер 24/7. Больше ничего | - -📌 **`callee` не умеет ничего сам** — ни думать, ни писать код. Он берёт описание графа, -проверяет его и по шагам вызывает: роль → модель, скрипт → shell, human → пауза. -Вся содержательная работа — либо в модели через Role, либо в shell через Script. - -📌 **Модель ≠ агент-харнесс.** `dsh-acp` только отдаёт DeepSeek. `codex` — харнесс: -модель плюс умение править файлы, запускать команды, искать. Через ACP callee зовёт и то, -и другое. - -### Разбор сценариев - -| Сценарий | Механизм | Где исполняется | Статус | -|---|---|---|---| -| Настроить на **Mac** | `Script` → локальный shell | контейнер, только `~/agents` | ⚠️ `~/Developer` не смонтирован | -| Настроить на **TrueNAS** | `Script` → `ssh truenas_admin@mallexxx.duckdns.org` | контейнер | ❌ `~/.ssh` не смонтирован | -| Настроить на **Kraken** | `Script` → `ssh kraken` | контейнер | ❌ ssh не настроен; 🔴 риск потерять канал управления | -| Собрать **Xcode** | `Script` → `xcodebuild` | **lume VM с macOS** | ❌ в Linux-контейнере невозможно | -| Тесты в **lume** | `Script` → `lume` | **хост Mac** | ❌ `lume` стоит на хосте, не в контейнере | -| Собрать **докер-образ** | `Script` → `docker build` | **хост Mac** | ❌ `docker.sock` не смонтирован | +Будущая transport abstraction в `runtime + acpagent` позволит Balda или Callee подключаться к ACP по WebSocket/Streamable HTTP. Она может заменить SSH-транспорт, но не заменяет Broker, VM lifecycle, durable events и cleanup. -🔴 **Корень:** шесть сценариев делятся на две группы, и текущая схема закрывает одну. +## 12. Pitfalls -| Группа | Сценарии | Где | -|---|---|---| -| В контейнере | файлы Mac, git, Linux-сборки | `balda-executor` | -| **Вне контейнера** | TrueNAS/Kraken по ssh, Xcode, lume, docker build | нужен доступ к хосту | +### Архитектура -**Причина одна:** контейнер изолирован. Нет `docker.sock`, `~/.ssh`, `lume`, Xcode, `~/Developer`. +1. Смешивание двух топологий: stock `Codex → Callee` и целевой `Balda → Broker → Callee`. +2. Перенос workflow-логики в Broker превращает его во второй оркестратор. +3. Прямая запись Broker/Callee/Codex в Beads создаёт несколько владельцев состояния. +4. Запуск Callee вне VM переносит Script nodes и часть команд на хост. +5. Парсинг консольного вывода Callee вместо стабильного event protocol ломает resume и диагностику. -### Как закрывать: роли по исполнителям +### Надёжность -| Вариант | Суть | Оценка | -|---|---|---| -| **A. Роли по исполнителям** ✅ | контейнер чистый для Linux-задач; для остального роли ходят через `ssh` на хост и узлы. Требует `~/.ssh:ro` в контейнер + `ssh admin@host.docker.internal` | изоляция сохраняется; роли явно описаны в графе: «linux-задача», «macOS-задача», «удалённый хост» | -| B. Дать контейнеру больше прав | смонтировать `docker.sock`, `lume`, `~/Developer` | проще, но контейнер теряет изоляцию — одну из причин своего существования | +1. Потеря события между Callee и Balda без durable event log. +2. Двойной запуск VM без idempotency key. +3. Повторный Human response возобновляет workflow дважды. +4. Падение Broker оставляет VM без lease и cleanup. +5. Большие build-логи забивают SQLite или MCP-ответы. -**Решение: A.** +### Безопасность -### Чего нет в схеме (докрутить в C1) +1. Непроверенный `workflow_id`, repository URL или revision превращается в выполнение произвольного ввода. +2. Общий SSH/Git credential между VM расширяет последствия компрометации. +3. Секреты в prompt, events или build logs попадают в Mattermost/Beads. +4. Host path, переданный как ACP cwd, не существует или указывает не туда внутри VM. +5. VM с доступом в локальную сеть может атаковать хостовые сервисы; egress и host API нужно ограничить. -| Нужно | Статус | -|---|---| -| `~/.ssh` в контейнере | ❌ | -| `docker.sock` | ❌ | -| `lume` в контейнере | ❌ (стоит на хосте, v0.5.3) | -| ssh с контейнера на Kraken/TrueNAS | ❌ | -| lume VM как исполнитель | ❌ VM есть (`macos-15-golden`, `macos-27-golden`, `uitest-26.4.1-golden`, `pdf-hud-macos27`), интеграции нет | -| Разделение ролей по хостам | ❌ в графе не описано | +## 13. План реализации Broker ---- +### Этап 1 — spike контрактов, 4–8 часов -## 3. Prism — таск-трекинг +1. Запустить один Callee workflow вручную внутри тестовой VM. +2. Зафиксировать фактический stdout/API, Human pause/resume и cancel. +3. Запустить Codex Role через локальный ACP stdio. +4. Проверить workspace path, build и terminal result. +5. Принять формат VM runner protocol на основании фактов. -### 3.1 Callee и prism — независимые установки +Результат: подтверждённый протокол Callee/runner без предположений. -Установка одного плагина не ставит другой. +### Этап 2 — Virfield control API, 4–8 часов -| | `baldaworks/prism` | `baldaworks/prism-callee` | -|---|---|---| -| Что это | плагин в coding agent (Codex/Claude/Grok/Copilot/Cursor) | callee-агенты для тех же воркфлоу | -| Требования | coding agent + `bd` (Beads) | coding agent + `bd` + callee ≥ 0.19.0 | -| Команды | `$prism:story`, `$prism:epic`, `$prism:lifecycle` | `$prism-callee:lifecycle` | -| Состояние | Beads (`bd`) | Beads (`bd`) | +1. Обернуть существующий `vm_prepare_session` в узкий authenticated HTTP API. +2. Добавить операции lease status, stop и delete. +3. Ограничить templates, TTL и число параллельных VM. +4. Добавить idempotency key и audit log. +5. Проверить cleanup после таймаута и падения клиента. -Импорт callee-агентов: +Результат: Broker может безопасно получить и освободить VM без host shell. -```bash -callee agent import baldaworks/prism-callee --path pack/callee/prism --prefix prism -``` +### Этап 3 — VM runner, 1–2 дня -🔴 **Проверка импорта:** `callee agent view prism/lifecycle --json` → **kind `Router`**. -`Sequential` = устаревший импорт, обновлять `--force`. +1. Описать versioned JSON protocol command/event. +2. Реализовать start, events, Human response и cancel. +3. Подключить Callee workflow pack и Codex ACP. +4. Сформировать artifact manifest для git diff, commit, logs и `.xcresult`. +5. Добавить terminal result и гарантированное завершение дочерних процессов. -### 3.2 Жизненный цикл +Результат: одна команда запускает workflow в VM и возвращает структурированные события. -**Story:** Specify → Design → Breakdown → **Human approval** → Apply → Verify. -Фазы: `phase:story:specify` → `design` → `breakdown` → `human` → `apply` → `verify`. +### Этап 4 — Broker MVP, 1–2 дня -**Epic:** Frame → Architecture → Roadmap → **approval** → Deliver Stories → Validation. +1. Реализовать SQLite schema для runs, events, responses и artifacts. +2. Реализовать state machine и транзакционный event journal. +3. Подключить Virfield API и SSH/runner transport. +4. Опубликовать MCP tools из §8. +5. Добавить restart recovery, lease renewal и cleanup worker. -| Правило | Значение | -|---|---| -| Дети Epic | только Stories | -| Дети Story | только Tasks | -| Вложенные Epic / Task напрямую в Epic | невалидно | -| Фиксированного диапазона числа детей | нет — оценка качественная | -| Apply | каждый переход требует явной авторизации человека | -| Одобрение Epic → Story | не переносится | +Результат: Balda управляет долговечным run через HTTP MCP. ---- +### Этап 5 — Balda + Prism, 1–2 дня -## 4. Одобрения в Mattermost — работают +1. Подключить Broker MCP к runtime Balda. +2. Развернуть Prism adapter и Dolt/Beads container. +3. Реализовать отображение events → Story phases. +4. Связать Human request с Mattermost session/topic. +5. Сформировать финальный отчёт Result/Artifacts/Confidence/Next action. -Одобрение проходит **ответом в треде**, как в Slack. +Результат: Story ведётся в Beads, выполнение и approvals видны в Mattermost. -| Механизм | Статус | -|---|---| -| Вопрос с опциями (markdown-нумерация) | ✅ `questionfmt` + `questioncmd` + `deliveryfmt`, покрыто тестами | -| Приём ответа реплаем | ✅ `BuildInboundReply` через `RootID` (`channel/mattermost/inbound.go`) | -| `ask` как режим разрешений | ✅ `mattermost` в списке каналов (`permissions/service.go`) | +### Этап 6 — сквозная приёмка, 4–8 часов -📌 `ask` — это режим политики разрешений агента; механизм вопросов — отдельный слой. -Оба в MM работают. +1. Создать Story из реального сообщения Mattermost. +2. Пройти specify/design/approval/implement/review/verify. +3. Собрать Xcode-проект и запустить тесты внутри VM. +4. Перезапустить Broker во время run и проверить восстановление. +5. Проверить terminal cleanup VM и доступность артефактов. -⛔ **Инлайн-кнопок и реакций в MM нет** — только Telegram. Событие `reaction` -сознательно отбрасывается в `channel/mattermost/ingress.go`. +Результат: воспроизводимый end-to-end прогон без исполнения агентских команд на хосте. ---- +Оценка MVP после spike: 4–7 рабочих дней. Production hardening зависит от возможностей Callee API и обычно добавит 3–7 дней. -## 5. Фазы +## 14. Критерии готовности -> ⚠️ Переписано под топологию §1 (стек на Mac). Прежняя фаза C3 («переписать `PROVIDERS` -> в мосте») **отменена**: мост в новой топологии не нужен. - -### C0 — ✅ ЗАКРЫТА 2026-09-29 - -Разведка npm: готовый ACP-адаптер к DeepSeek существует (§2.1). Свой адаптер не нужен. - -**Метод поиска:** прямые URL `registry.npmjs.org/%2f` и -`/-/v1/search?text=...`. ⛔ Поиск по `scope:normahq` и `scope:baldaworks` даёт пусто — -scoped-пакеты так не индексируются. - -### C1 — ✅ БАЛДА СОБРАНА И РАБОТАЕТ НА MAC (2026-09-29). Шаги 3-9 (callee/prism) не начаты. - -#### Что сделано и проверено - -| # | Шаг | Статус | Доказательство | -|---|---|---|---| -| 1 | Образ `balda-mm:0.1.16-mac` | ✅ | собран из `bf4a6ae4` = **head of upstream**, linux/arm64, 78.8 МБ | -| 2 | MM-бот | ✅ | **тот же `@balda`** — решение Alex, второго бота не заводили | -| 3-7 | callee / dsh-acp / bd / prism | ❌ | не начато | -| 8 | `obsidian-mcp` рядом | ✅ | контейнер на Mac, `http://host.docker.internal:3101/sse` | -| 9 | Сквозная приёмка | ✅ | **лента канала**: `cbwsorzwoi` (Alex) → `@balda ответь одним словом: работает`; `edxrgupoxi` (бот) → **`работает`** (1.7 с) | - -Контейнер: `balda-mm-mac`, Up, `RestartCount=0`, `restart: unless-stopped`, -WebSocket `wss://mm.qentra.top/api/v4/websocket` — `connected`, errors=0. - -#### Файлы на Mac - -| Путь | Что | -|---|---| -| `/Users/admin/Developer/balda` | исходники, `main` = `bf4a6ae4` (head of upstream) | -| `/Users/admin/Docker/balda-mm-mac/config/config.yaml` | конфиг | -| `/Users/admin/Docker/balda-mm-mac/.env` | `0600`: DEEPSEEK_API_KEY, MM-токен, bot_user_id, server_url | -| `/Users/admin/Docker/balda-mm-mac/docker-compose.yml` | `image: balda-mm:0.1.16-mac`, `extra_hosts: host.docker.internal` | -| `/Users/admin/Docker/balda-mm-mac/state/` | sqlite `state.db` + nats + session-memory | -| `/Users/admin/Docker/balda-mm-mac/secrets/backoffice-adminpw` | `0600`, пароль backoffice | - -**Отличия конфига Mac от Kraken:** -- `server_url`: `http://mattermost:8065` → **`https://mm.qentra.top`** (Cloudflare API пропускает, 200) -- `executor-mcp` **убран** (контейнер снесён); `obsidian` → `host.docker.internal:3101` -- `BRIDGE_URL` / `BRIDGE_SECRET` не нужны - -#### Четыре дефекта при запуске (все закрыты) - -| # | Дефект | Симптом | Фикс | -|---|---|---|---| -| 1 | **Backoffice-админ не забутстрапен** | crash-loop: `administrator bootstrap is required` | `docker run --rm … balda-mm:0.1.16-mac backoffice bootstrap-admin --username admin` (образ `scratch` — `docker exec` невозможен, только `docker run` с тем же state-томом) | -| 2 | 🔴 **`/home/kraken/balda/secrets/mm-bot-token-id` ВРЁТ** | `bot user id mismatch: configured "ejquewpbq…", token belongs to "edxrgupoxi…"` | истинный id — **из API**: `/api/v4/users/me`. Файл на Kraken содержит чужой id, **не доверять** | -| 3 | 🔴 **`balda_user_bindings` пуст на чистой базе** | посты приняты (`post accepted for processing`), но **нет ни одной строки `settlement`/`turn_dispatcher`** — молчаливое гашение | `INSERT` binding для аккаунта Alex. На Kraken круг был замкнут (`testuser` уже владелец), на чистой базе — нет; штатный путь `/start owner=` тоже доступен | -| 4 | 🔴 **Hermes-редакция секретов калечит подстановки** | `sqlite3 < file` из heredoc с `$VAR` → 0 строк, exit 0 (тихий провал); в `.env` значения `***` | писать **литералы прямо в аргумент** `sqlite3`, БД при **остановленном** контейнере; секреты — через файлы-носители | - -**Рабочая форма записи binding** (единственная сработавшая; контейнер остановлен): -```bash -cd /Users/admin/Docker/balda-mm-mac && docker-compose stop -docker run --rm -v /Users/admin/Docker/balda-mm-mac/state:/state alpine:latest \ - sh -c 'apk add -q sqlite 2>/dev/null; sqlite3 /state/state.db "DELETE FROM balda_user_bindings; INSERT INTO balda_user_bindings (binding_id,user_id,channel_type,principal,display_name,provenance,created_at,updated_at) VALUES (…);"' -docker-compose up -d -``` -⚠️ **НЕ работают** (проверено, 0 строк, exit 0): `sqlite3 < /q.sql` с heredoc-подстановками; -многострочный INSERT через `-v /q.sql:/q.sql:ro`. - -#### Приёмка (воспроизводимо, без GUI) - -```bash -ssh kraken "docker exec mattermost mmctl --local token generate admin 'acc-$(date +%s)'" \ - | sed -n 's/^\([a-z0-9]*\):.*/\1/p' > /tmp/tok -ssh kraken "bash /tmp/mm-post-admin.sh $(cat /tmp/tok) 7bjw4g7o1jdsbmjbqd649xxgaw '@balda ответь одним словом: работает'" -ssh kraken "bash /tmp/mm-feed.sh $(cat /tmp/tok) 7bjw4g7o1jdsbmjbqd649xxgaw 12" -``` -⚠️ `mmctl --local token generate` печатает `токен: имя` — парсить **только первую часть**, иначе получите `api.context.session_expired.app_error`. -⚠️ `mm-feed.sh` — **позиционные** аргументы; при неверном числе `jq: Cannot iterate over null`. - -#### ⚠️ Остаточный дефект (неблокирующий, тот же, что на Kraken) - -``` -session_memory_worker: dead_letter, error_code: model_failure -output does not match schema: (root): output is required -``` -`deepseek-chat` не отдаёт structured output нужной схемы → **память сессий не наполняется**, -ответы работают. Отдельная задача. Разбор — [[personal/plans/balda-mm-phase4-agents]] §13. - -### C1 (исходный чеклист — для шагов 3-7) - -Объём вырос относительно исходного варианта: теперь это не «установка callee в контейнер», -а **переезд Балды на Mac + установка инструментов рядом**. - -| # | Шаг | Проверка | -|---|---|---| -| 1 | Запустить второй инстанс `balda-mm:0.1.13` на Mac | контейнер Up, лог `Balda bot started` | -| 2 | 🔴 **Отдельный MM-бот и токен для Mac-инстанса** | два процесса на один WebSocket MM рвут события (`bad_seq`) | -| 3 | `npm i -g @baldaworks/callee` на Mac | `callee --version` | -| 4 | `npm i -g @openma/deepseek-harness-acp` | `dsh-acp --help` | -| 5 | `callee setup codex` → стартовые ресурсы | `callee agent list` содержит `workflows/investigate` | -| 6 | Установить Beads `bd` | `bd where` | -| 7 | Импорт prism-пака | `callee agent view prism/lifecycle --json` → `Router` | -| 8 | `obsidian-mcp` рядом с Балдой (том `~/obsidian:/vault`) | `/sse` отвечает | -| 9 | Прогон стартового `investigate` | артефакт в stdout, exit 0 | - -**Решения по хранению (принять на C1):** установки должны переживать пересборку образа — -том или `Dockerfile`. - -### C1a — доступы и исполнители (объём зависит от развилки) - -Закрывает шесть сценариев из §2.5. **Объём определяется выбором в §6.7:** - -| Что нужно | Как | -|---|---| -| `~/.ssh` в контейнере | монтирование **конкретных ключей** с `command=` в `authorized_keys`, не каталога целиком | -| `docker build` | ❌ **не через `docker.sock`**; либо обёртка на хосте, либо хост-исполнитель | -| `lume` | ❌ в контейнер не пробрасывается; хост-исполнитель | -| `xcodebuild` | ❌ в Linux-контейнере невозможно; **lume VM с macOS** | -| `~/Developer` | рабочая копия в `~/agents/<проект>` + git (см. §1.1a) | -| Доступ к хостам (TrueNAS, Kraken) | ssh-ключ с ограничением `command=` | - -### C2 — Role к DeepSeek (1 ч) - -Описать `Role` по §2.3 и проверить один визит вживую. - -**Гейт:** в логе видны `role_model` и `role_reasoning`. До успешного рендера провайдера -эти поля опускаются — их появление означает, что рендер прошёл. - -### C3 — ~~подключение к мосту~~ ОТМЕНЕНА - -Мост `agents-bridge` в топологии «стек на Mac» не участвует. Фаза сохраняется только как -**план отката** к топологии «Kraken-оркестратор», если переезд не оправдается. - -### C4 — таск-трекинг (1-2 ч) - -1. `bd` инициализируется в рабочей области. -2. Прогон `prism:story` на реальной задаче. -3. Проверить durable: прервать сессию, возобновить Story — состояние в Beads на месте. - -### C5 — сквозная приёмка (1 ч) - -От **учётки Alex**, не подставным аккаунтом: - -``` -@balda добавь CSV-экспорт в отчёт -→ Specify → Design → Breakdown → Human approval → Apply → Verify -→ ответ в Mattermost + артефакт -``` - -Доказательство — лента `qentra:balda-test`, автор поста `cbwsorzwoigbtrdnteugnx4aqo`. - ---- - -## 6. Решения - -> ⚠️ Решения по §2.6 (отлов опасных команд) **не приняты** — это зафиксированное исследование, -> не выбор. Развилки ниже помечены явно. - -| # | Вопрос | Решение | Почему | -|---|---|---|---| -| 1 | Провайдер обеих сторон | DeepSeek | команда Alex | -| 2 | Оркестрация | callee | документированная архитектура вместо самодельной | -| 3 | Таск-трекинг | prism + Beads | задание Alex | -| 4 | Где живёт стек | **Mac целиком** | исполнение работает с файлами Mac → обязано быть рядом | -| 5 | Где MM-сервер | **Kraken** | единственный, кому нужна доступность 24/7 | -| 6 | Тип провайдера | `generic_acp` | список типов закрытый, DeepSeek в нём нет | -| 7 | 🔴 **Открыто: доступ к Xcode/lume/docker** | **не решено** | узкий контейнер (три операции недоступны) vs гибрид с хост-исполнителем | -| 8 | Роли: где живут | статично, файлами в каталоге | рантайм-выдачи ролей в callee не существует (§1.1b) | -| 9 | Права | `права процесса × монтирование × граф` | гейт `Human` защищает действие, но не возможность (§1.1a) | -| 10 | Мост `:8790` / MCP `:8890` | **демонтируются** | лишние посредники без содержательной работы | -| 11 | Старый инстанс на Kraken | ⛔ **ОТМЕНЕНО 2026-09-29 — СНЕСЁН** | Alex скомандовал снос: контейнеры + все образы `balda-mm:*`. Откат без пересборки невозможен; при возврате — собирать заново из `upstream/main` | - ---- - -## 7. Питфоллы - -1. 🔴 **Два инстанса Балды на один MM-бот = рвут WebSocket друг другу.** Симптом — - `bad_seq`, события не приходят. Mac-инстансу нужен **отдельный бот и токен**. -2. 🔴 **`docker.sock` в контейнер не монтировать никогда** — эквивалент root на хосте - (через сокет запускается контейнер с `-v /:/host`). -3. 🔴 **`~/.ssh` целиком не монтировать** — один ключ даёт доступ ко всем хостам. - Только конкретные ключи + `command=` в `authorized_keys`. -4. 🔴 **ACP-провайдер — дочерний процесс callee.** Движок на хосте + callee в контейнере - невозможны: один процесс-тред. Где callee, там и движки, и Script'ы. -5. 🔴 **Гейт `Human` защищает действие, но не возможность.** Права определяет процесс и - монтирование, не граф (§1.1a). -6. ⚠️ **`prism/lifecycle` должен быть `Router`** — `Sequential` означает устаревший - импорт, лечится `--force`. -7. ⚠️ **Provider `cmd` — одно имя файла, не shell-строка.** Аргументы — только - в `extraArgs`. -8. ⚠️ **Никогда не маршрутизировать вывод модели в `cmd`/`extraArgs`.** -9. ⚠️ **Установки в контейнере не переживают пересборку** — том или `Dockerfile`. -10. ⚠️ **`docker-compose` (дефис) на Mac, `docker compose` (пробел) на Kraken.** -11. ⚠️ **Node `>= 22.15`** требуется адаптером (в образе 24.21.0). -12. ⚠️ **Динамических маунтов на macOS Docker нет** — только `up -d` с пересозданием. - Доступ к проектам — через рабочую копию в `~/agents` + git, не через маунт под задачу. -13. 🔴 **Граф не видит команды внутри ACP-сессии.** Узел `Human`/`Script` закрывает только то, - что само вынесено в граф; обычный `bash` модели идёт мимо (§2.6). -14. 🔴 **Jev не запрещает и не спрашивает — он возвращает числа.** Без вашего `if` по порогам - тул выполнится как обычно (§2.6). - ---- - -## 8. Риски - -| Риск | Оценка | Митигация | -|---|---|---| -| 🔴 **Mac спит → бот не отвечает** | **высокий** | `caffeinate` / Energy Saver; MM-сервер на Kraken жив всегда, Mac переподключается | -| 🔴 **Развилка доступа к Xcode/lume/docker не решена** | **высокий** | решить до C1a (§6.7) | -| Хост-исполнитель = дыра в изоляции | средний | узкий набор операций (3 шт.), явный и названный | -| Два инстанса Балды конфликтуют по MM | средний | отдельный бот/токен для Mac-инстанса | -| Импорт `prism/*` устаревает при обновлении | средний | проверять `kind: Router` после каждого импорта | -| callee обновляется и меняет контракт | средний | пиннить версию в ресурсах | -| Переезд стека с Kraken не оправдается | средний | ⛔ **Kraken-инстанс снесён 2026-09-29** — откат только пересборкой из `upstream/main`. Образы удалены | -| Стоимость DeepSeek растёт | низкий | исполнитель-роли — короткие визиты | -| Установка в контейнер не переживёт пересборку | низкий | том или `Dockerfile` | - ---- - -## 9. Порядок и запреты - -**Порядок:** C1 → C1a → C2 → C4 → C5. (C3 отменена.) - -**НЕ трогать в `~/Developer/balda`:** `go.mod`, `Dockerfile.release`, `go.sum`, -`internal/apps/balda/app.go`, `internal/apps/balda/preflight.go` — вне PR-дерева, -нужны для сборки стенда. `git checkout -- .` / `stash` / `reset --hard` их снесёт. -См. [[personal/plans/balda-mm-pr-plan]] §5. - -**НЕ начинать работу** без явного «поехали». -**Ничего не удалять** без отдельного подтверждения. В частности: Kraken-инстанс Балды, -образы для отката, диагностические контейнеры, старые теги. - ---- +1. Balda создаёт одну Story и один idempotent run из сообщения Mattermost. +2. Все команды Codex, Xcode build и тесты исполняются внутри leased VM. +3. Callee управляет ролями и циклами; Broker не содержит workflow policy. +4. После рестарта Balda или Broker прогресс восстанавливается без двойного запуска. +5. Terminal run обновляет Beads, публикует артефакты и удаляет VM по lease policy. ## Связанные заметки -- [[personal/tech/callee-prism]] — **справочник по контрактам callee и prism** -- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план Фаз 1-5 -- [[personal/plans/balda-mm-phase4-agents]] — Фаза 4 (мост :8790, приёмка) -- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8) -- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 -- [[personal/tech/agents-bridge]] — мост :8790 -- [[personal/tech/balda-executor-mcp]] — MCP-сервер делегирования +- [[personal/tech/callee-prism]] — контракты Callee, ACP и Prism. +- [[personal/tech/agents-bridge]] — предыдущий мост; не часть целевой схемы. +- [[personal/tech/balda-executor-mcp]] — предыдущий исполнитель; не часть целевой схемы. +- [[personal/plans/balda-mm-phase4-agents]] — история предыдущей интеграции.