51 KiB
Executable File
Балда → Callee + Prism: делегирование
Обновлён 2026-09-29. Статус: C0 закрыта. Топология пересмотрена: стек переезжает на Mac, на Kraken остаётся только MM-сервер. C1-C5 не начаты. Часть personal/projects/balda/balda-mm-delegation-plan. Опорная заметка по контрактам инструментов: personal/tech/callee-prism.
🔴 Открытая развилка (§6.7): как давать доступ к
xcodebuild/lume/docker build— узкий контейнер (три операции недоступны) или гибрид с хост-исполнителем. Определяет объём C1a.
1. Целевая архитектура
Принцип: стек — единое целое по признаку «работает с файлами на Mac». Всё, что трогает код, сборку, тесты, хосты, обязано быть рядом. Всё, что только принимает сообщения — может жить отдельно.
┌─ Mac ─────────────────────────────────────────────┐
│ Балда (оркестрация, планирование, крон) │
│ callee (движок воркфлоу) │
│ движки: codex / claude / dsh-acp │
│ bd (Beads) — состояние задач │
│ тулчейн: ~/Developer, xcodebuild, lume, docker │
└───────────────────────────────────────────────────┘
▲
│ Mattermost API — ИСХОДЯЩЕЕ соединение с Mac
│
┌─ Kraken ──────────────────────────────────────────┐
│ MM-сервер + postgres ← только это │
└───────────────────────────────────────────────────┘
Что это убирает
| Узел | Почему больше не нужен |
|---|---|
balda-executor-mcp (:8890) |
его работа — пропихнуть вызов к мосту. Балда на Mac → пропускать нечего |
мост agents-bridge (:8790) |
был границей хостов. Границы нет — нет моста |
| Cloudflare Tunnel для этого потока | нужен только для внешнего доступа к MM |
MCP-сервер obsidian-mcp на Kraken |
Obsidian рядом с Балдой |
📌 Соединение — исходящее с Mac к MM. Никаких входящих портов, туннеля внутрь Mac, «Mac спит → мост недоступен». При недоступности MM — переподключение.
Что остаётся на Kraken
Только MM-сервер + postgres. Доступность 24/7, один сетевой вход, ничего тяжёлого. Kraken разгружается: уходят Балда, MCP-сервер, obsidian-mcp (при load 8.03 на 4 ядрах это заметно).
Разделение расходов
| Роль | Где | Провайдер |
|---|---|---|
| Оркестратор | Mac | DeepSeek deepseek-chat |
| Исполнитель-роли callee | Mac | DeepSeek |
📌 «DeepSeek с обоих концов» = обе стороны на DeepSeek. Codex/Claude подключаются позже
заменой провайдера в DynamicRole — без правки графа.
1.1 Балда на Mac: контейнер и границы доступа
Где живёт
В Docker-контейнере. Причины те же, что были на Kraken: изоляция, воспроизводимость,
образ уже собран (balda-mm:0.1.13), откат одним тегом.
Проблема: контейнер изолирован, а нужен тулчейн
В контейнере нет и не должно быть по умолчанию:
| Чего нет | Почему это правильно |
|---|---|
~/.ssh |
ключи дают доступ ко всем хостам сразу |
docker.sock |
эквивалент root на хосте |
пароли, .env с токенами |
Балде они не нужны |
~/.aws, ~/.config/gh, прочие креды |
то же |
Но нужны: xcodebuild, lume, docker build, опционально ssh.
Решение: три класса доступа
| Класс | Что | Как | Права |
|---|---|---|---|
| 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=)
Разграничение = статический роутинг по типам задач, а не динамические права.
Гибрид «контейнер + узкий хост-исполнитель»
| Вариант | Права | Динамика | Оценка |
|---|---|---|---|
| callee в контейнере | узкие, контролируемые | только ~/agents |
безопасно; Xcode/lume/docker недоступны |
| callee на хосте | полный доступ пользователя | всё работает | ноль изоляции |
| гибрид ✅ | контейнер для основного + узкий хост-исполнитель для Xcode/lume/docker | маунты не нужны | рекомендован |
🟡 Хост-исполнитель — дыра в изоляции по определению, но узкая и явная: три операции (xcodebuild, lume, docker build), а не «что угодно».
1.1b Роли: жизненный цикл и контроль
Что такое роль
Роль — YAML/MD-файл в каталоге. Не «право», не «токен», а описание листа графа: какой провайдер, какой промпт, какие параметры, какой таймаут.
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/ |
Роли не выдаются динамически — они ЛЕЖАТ в каталоге. Кто положил файл, тот и «выдал» роль. Никакого рантайм-механизма выдачи прав нет.
Что раздаётся, а что нет
| Сущность | Динамически выдаётся? | Чем регулируется |
|---|---|---|
| Роль (файл графа) | ❌ статично | наличие файла в каталоге |
Провайдер (тип, cmd) |
❌ статично, или из State через DynamicRole |
автор графа |
| Файлы | ❌ | монтирование + рабочая копия (§1.1a) |
| SSH-ключ | ❌ | наличие файла у процесса |
| Разрешение модели внутри сессии | ⚠️ да — allow_all/ask/deny_all |
конфиг Балды |
🔴 Единственное, что реально запрашивается в рантайме — разрешение модели внутри ACP-сессии
(ask → вопрос в чат). Всё остальное статично.
Ваша гипотеза: проверяю по пунктам
«я ставлю задачу → приходит запрос "дать такую-то роль для такой-то задачи" → апрув → доступ появился»
| Часть гипотезы | Вердикт |
|---|---|
| Задача приходит в систему | ✅ да |
| Приходит запрос «выдать роль» | ❌ нет такого механизма. Роли не выдаются, они лежат в каталоге |
| Апрув → у агента появляется доступ | ⚠️ частично — не к инструментарию, а к действию |
| Без апрува доступа нет | ✅ верно, но иначе |
Что происходит на самом деле: доступ у роли есть всегда — он определяется тем, чем запущен callee и что смонтировано. Апрув не выдаёт доступ, апрув разрешает выполнить операцию, которая уже технически возможна.
Значит правильная формулировка
Роль лежит в каталоге (доступ есть по определению)
│
▼
Граф доходит до узла → Human («собрать Xcode в VM macos-27?») → пауза
│
├─ апрув → Script исполняется, потому что права у процесса УЖЕ были
└─ отказ → граф останавливается, ничего не исполняется
🔴 Разница принципиальная. Вы описали capability-модель (права выдаются на задачу). Callee работает по статической модели (права постоянны, гейты контролируют исполнение).
Что это значит для вашей безопасности: гейт Human защищает от действия, но не от
возможности. Если у процесса есть ssh-ключ — ключ есть всегда, вне зависимости от апрувов.
Изоляцию даёт только то, чем процесс запущен и что смонтировано — не граф.
Что можно сделать, чтобы приблизиться к вашей модели
| Способ | Как |
|---|---|
| Разные профили запуска | контейнер «узкий» (без ssh, без docker) и хост-исполнитель «широкий» — выбор профиля роутером |
| Роль на задачу | задача → своя роль с нужным cmd/таймаутом; доступ = какой профиль она выбрала |
| Апрув как гейт исполнения | Human перед дорогой/опасной операцией |
| Белый список команд в Script | Script не «любой shell», а конкретный скрипт с фиксированной командой |
1.2 Правила работы: Hermes-конфигурация в Балде
В Hermes у Alex накоплены: правила ведения документации (Obsidian — источник истины), правила работы (план → апрув → работа, бэкапы, запрет удаления), формат ответов (артефакт первой строкой, единица счёта, ADHD-friendly), правила приёмки (от учётки заказчика, а не подставной).
Эти правила — не код, а контент. Значит их надо переносить как контент, а не зашивать в Балду.
| Слой 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.
2. Провайдер DeepSeek для callee
2.1 ACP-адаптер — выбран @openma/deepseek-harness-acp@0.4.33
Свой адаптер писать не надо.
| Значение | |
|---|---|
| Пакет | @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 |
Установка (standalone-вариант, подходит для generic_acp):
npm i -g @openma/deepseek-harness-acp
# запуск: dsh-acp
2.2 Креды
Приоритет: флаги → переменные окружения → дефолты.
| Способ | Значение |
|---|---|
| Флаг | api-key, либо api-key:<provider> при нескольких провайдерах |
| 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 в нём отсутствует.
provider:
type: generic_acp
cmd: dsh-acp # одно имя исполняемого файла, НЕ shell-строка
model: deepseek-chat
timeout: 20m
| Поле | Контракт |
|---|---|
cmd |
одно имя исполняемого файла; для generic_acp обязателен |
extraArgs |
упорядоченный argv-список; каждый элемент непустой |
model / reasoning / mode |
опциональны; пустые → дефолт бэкенда |
timeout |
Go duration, по умолчанию 15m |
| Конфиг провайдера | обязан быть вложенным (spec.provider.*), плоские поля не поддержаны |
2.4 DynamicRole — провайдер из состояния
Все поля провайдера — шаблоны, рендерятся из состояния в момент визита:
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/extraArgsDynamicRole — исполнение произвольного кода мимо любого гейта внутри сессии (питфолл 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 не смонтирован |
🔴 Корень: шесть сценариев делятся на две группы, и текущая схема закрывает одну.
| Группа | Сценарии | Где |
|---|---|---|
| В контейнере | файлы Mac, git, Linux-сборки | balda-executor |
| Вне контейнера | TrueNAS/Kraken по ssh, Xcode, lume, docker build | нужен доступ к хосту |
Причина одна: контейнер изолирован. Нет docker.sock, ~/.ssh, lume, Xcode, ~/Developer.
Как закрывать: роли по исполнителям
| Вариант | Суть | Оценка |
|---|---|---|
| A. Роли по исполнителям ✅ | контейнер чистый для Linux-задач; для остального роли ходят через ssh на хост и узлы. Требует ~/.ssh:ro в контейнер + ssh admin@host.docker.internal |
изоляция сохраняется; роли явно описаны в графе: «linux-задача», «macOS-задача», «удалённый хост» |
| B. Дать контейнеру больше прав | смонтировать docker.sock, lume, ~/Developer |
проще, но контейнер теряет изоляцию — одну из причин своего существования |
Решение: A.
Чего нет в схеме (докрутить в C1)
| Нужно | Статус |
|---|---|
~/.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), интеграции нет |
| Разделение ролей по хостам | ❌ в графе не описано |
3. Prism — таск-трекинг
3.1 Callee и prism — независимые установки
Установка одного плагина не ставит другой.
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) |
Импорт callee-агентов:
callee agent import baldaworks/prism-callee --path pack/callee/prism --prefix prism
🔴 Проверка импорта: callee agent view prism/lifecycle --json → kind Router.
Sequential = устаревший импорт, обновлять --force.
3.2 Жизненный цикл
Story: Specify → Design → Breakdown → Human approval → Apply → Verify.
Фазы: phase:story:specify → design → breakdown → human → apply → verify.
Epic: Frame → Architecture → Roadmap → approval → Deliver Stories → Validation.
| Правило | Значение |
|---|---|
| Дети Epic | только Stories |
| Дети Story | только Tasks |
| Вложенные Epic / Task напрямую в Epic | невалидно |
| Фиксированного диапазона числа детей | нет — оценка качественная |
| Apply | каждый переход требует явной авторизации человека |
| Одобрение Epic → Story | не переносится |
4. Одобрения в Mattermost — работают
Одобрение проходит ответом в треде, как в Slack.
| Механизм | Статус |
|---|---|
| Вопрос с опциями (markdown-нумерация) | ✅ questionfmt + questioncmd + deliveryfmt, покрыто тестами |
| Приём ответа реплаем | ✅ BuildInboundReply через RootID (channel/mattermost/inbound.go) |
ask как режим разрешений |
✅ mattermost в списке каналов (permissions/service.go) |
📌 ask — это режим политики разрешений агента; механизм вопросов — отдельный слой.
Оба в MM работают.
⛔ Инлайн-кнопок и реакций в MM нет — только Telegram. Событие reaction
сознательно отбрасывается в channel/mattermost/ingress.go.
5. Фазы
⚠️ Переписано под топологию §1 (стек на Mac). Прежняя фаза C3 («переписать
PROVIDERSв мосте») отменена: мост в новой топологии не нужен.
C0 — ✅ ЗАКРЫТА 2026-09-29
Разведка npm: готовый ACP-адаптер к DeepSeek существует (§2.1). Свой адаптер не нужен.
Метод поиска: прямые URL registry.npmjs.org/<scope>%2f<name> и
/-/v1/search?text=.... ⛔ Поиск по scope:normahq и scope:baldaworks даёт пусто —
scoped-пакеты так не индексируются.
C1 — переезд стека на Mac (2-3 ч)
Объём вырос относительно исходного варианта: теперь это не «установка 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 ч)
bdинициализируется в рабочей области.- Прогон
prism:storyна реальной задаче. - Проверить 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 | оставить живым как запасной | откат без пересборки; ⚠️ требует отдельного MM-бота |
7. Питфоллы
- 🔴 Два инстанса Балды на один MM-бот = рвут WebSocket друг другу. Симптом —
bad_seq, события не приходят. Mac-инстансу нужен отдельный бот и токен. - 🔴
docker.sockв контейнер не монтировать никогда — эквивалент root на хосте (через сокет запускается контейнер с-v /:/host). - 🔴
~/.sshцеликом не монтировать — один ключ даёт доступ ко всем хостам. Только конкретные ключи +command=вauthorized_keys. - 🔴 ACP-провайдер — дочерний процесс callee. Движок на хосте + callee в контейнере невозможны: один процесс-тред. Где callee, там и движки, и Script'ы.
- 🔴 Гейт
Humanзащищает действие, но не возможность. Права определяет процесс и монтирование, не граф (§1.1a). - ⚠️
prism/lifecycleдолжен бытьRouter—Sequentialозначает устаревший импорт, лечится--force. - ⚠️ Provider
cmd— одно имя файла, не shell-строка. Аргументы — только вextraArgs. - ⚠️ Никогда не маршрутизировать вывод модели в
cmd/extraArgs. - ⚠️ Установки в контейнере не переживают пересборку — том или
Dockerfile. - ⚠️
docker-compose(дефис) на Mac,docker compose(пробел) на Kraken. - ⚠️ Node
>= 22.15требуется адаптером (в образе 24.21.0). - ⚠️ Динамических маунтов на macOS Docker нет — только
up -dс пересозданием. Доступ к проектам — через рабочую копию в~/agents+ git, не через маунт под задачу. - 🔴 Граф не видит команды внутри ACP-сессии. Узел
Human/Scriptзакрывает только то, что само вынесено в граф; обычныйbashмодели идёт мимо (§2.6). - 🔴 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-инстанс живым как запасной (§6.11) |
| Стоимость 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-инстанс Балды, образы для отката, диагностические контейнеры, старые теги.
Связанные заметки
- 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-сервер делегирования