# Callee + Prism — историческая справка по контрактам > [!WARNING] Версионная справка, 2026-09-29 > Контракты ниже относятся к исследованным тогда версиям. Их нельзя считать описанием установленного Broker без сверки закреплённых зависимостей, workflow и worker package. > Проверенные версии, текущие образы, реальные прогоны и оставшаяся работа: [[personal/projects/balda/execution-broker]]. > Старый [[personal/plans/balda-callee-prism-plan|план интеграции]] сохранён как архив. > Создан 2026-09-29. Опорная заметка к [[personal/plans/balda-callee-prism-plan]]. > Назначение: справочник по контрактам callee и prism, чтобы не перечитывать > доки апстрима при каждой правке ресурсов. --- ## 1. Что это | Инструмент | Репозиторий | Лицензия | Что делает | |---|---|---|---| | **callee** | `baldaworks/callee` | MIT | воркфлоу как версионируемые Markdown/YAML-ресурсы | | **prism** | `baldaworks/prism` | MIT | плагин в coding agent: жизненный цикл Story/Epic | | **prism-callee** | `baldaworks/prism-callee` | MIT | те же воркфлоу через callee-агентов | | **Beads** | `gastownhall/beads` | — | durable-состояние (`bd`), носитель состояния prism | 📌 Версия callee на 2026-09-29 — **`0.22.2`** (npm `@baldaworks/callee`, meta-пакет с optionalDependencies на платформенные бинарники: `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, `win32-x64`). --- ## 2. callee — контракты ### 2.1 Типы узлов (10 публичных kinds, `callee.metalagman.dev/v1alpha1`) | Группа | Kind | Поведение | |---|---|---| | Листья | `Role` | промпт + один или несколько ходов в одной свежей ACP-сессии | | | `DynamicRole` | то же, но конфиг провайдера рендерится из состояния визита | | | `Script` | детерминированный локальный шаг через `sh`/`bash` | | | `Human` | пауза, один непустой ответ оператора | | Типизированные оценки | `TypeSafeJev` | нативный оценщик System One/Jev | | | `OpenRouterDecision` | OpenRouter Decisions API | | Композиты | `Sequential` | дети один раз, в порядке исходника | | | `Parallel` | fan-out и join | | | `Loop` | повтор детей в границах до эскалации | | | `Router` | ровно один именованный или дефолтный ребёнок | ### 2.2 Исполнение ``` Markdown/YAML → decode + schema/semantic/template validation → registry + static graph resolution ⛔ отвергает: невалидные ресурсы, битые ссылки, циклы, дубли resource ID, дубли effective ID в resolved tree → resolved root tree + required parameter keys → runner + ОДИН shared ephemeral state-объект → ОДИН корневой артефакт в stdout (пишется ПОСЛЕ cleanup) ``` ⚠️ Диагностика идёт в **stderr**, артефакт — в **stdout**. Непустой stderr **не** означает провал. Успех определять по **exit status**. **Порядок визита узла:** счётчик → слияние `spec.state` со state входящего ребра → рендер строковых листьев по **одному** pre-node снапшоту → атомарный коммит state- модификатора → поведение по типу → `agent finished` со статусом и длительностью. 🔴 При провале рендера state: **ни одна часть модификатора не коммитится, провайдер не стартует.** ### 2.3 Провайдеры — ЗАКРЫТЫЙ список | `type` | Команда по умолчанию | Предусловие | |---|---|---| | `codex` | сам callee + `bridge codex` | авторизованный Codex CLI | | `claude` | `npx -y @zed-industries/claude-code-acp@latest` | Node + npx + креды адаптера | | `opencode` | `opencode acp` | OpenCode CLI | | `copilot` | `copilot --acp --stdio` | Copilot CLI | | `grok` | `grok agent stdio` | Grok CLI или `XAI_API_KEY` | | `cursor` | `agent acp` | Cursor CLI | | `generic_acp` | **нет — требует непустой `cmd`** | любой ACP-исполняемый файл | 🔴 **Gemini не является валидным типом.** DeepSeek — тоже нет → только через `generic_acp`. **Поля провайдера:** | Поле | Контракт | |---|---| | `type` | обязателен, из закрытого списка | | `cmd` | **одно имя исполняемого файла, НЕ shell-строка**; для `generic_acp` обязателен | | `model` / `reasoning` / `mode` | опциональны; пустые → дефолт бэкенда | | `extraArgs` | упорядоченный argv-список; каждый элемент непустой | | `timeout` | Go duration, **по умолчанию `15m`** | ⚠️ Конфиг провайдера обязан быть **вложенным** — плоские поля в `spec` не поддержаны. **Таймаут независим для:** старта процесса, создания и подготовки сессии, **каждого** хода. Корневой прогон целиком он **не** ограничивает. **Переиспользование процесса:** одинаковые effective `type` + полностью резолвленный `cmd` → один процесс. `model`/`mode`/`reasoning` на идентичность процесса **не влияют**. Сессия всегда создаётся свежая. ### 2.4 DynamicRole — провайдер из состояния Все поля провайдера — шаблоны: `type`, `cmd`, `model`, `reasoning`, `mode`, `timeout`, **каждый** элемент `extraArgs`. | Факт | Значение | |---|---| | Источники | `.Prompt`, текущий `.Input`, **один** согласованный post-node снапшот `.State`, `.Params` | | Недоступно | `.Output`, env, файловая система, сеть, часы, рандом, криптография, мутаторы | | Рендер | ровно **один раз на визит**; валидируется **до** поиска процесса | | Периметр | конфиг фиксирован на весь визит, включая REPL-ходы | | Повтор | новый визит Loop рендерит заново из актуального состояния | | Ошибка | рендер или валидация падают → визит падает, ничего не стартует | 📌 В `callee agent view` DynamicRole помечается `provider=runtime`; JSON-view сохраняет авторские шаблоны. Обычный `callee doctor` валидирует ресурс, шаблон и граф, но готовность провайдера **откладывает** — исполняемый файл проверяется только при достижении узла. 📌 После рендера в lifecycle-событиях появляются `role_model` и `role_reasoning`. **До** успешного рендера эти поля опускаются — их появление = рендер прошёл. 🔴 **Безопасность:** state-driven `cmd` и `extraArgs` могут выбирать исполняемый код. Не подавать туда недоверенный вывод модели без ограничения авторской шаблонной логикой. `cmd` остаётся одним исполняемым файлом, `extraArgs` — argv-списком; shell не участвует. ### 2.5 Script и Human **Script:** результат пишется в `State.scripts[effectiveId]` — `status` (`passed`/`failed`), `exitCode` (`-1` при таймауте), `stdout`, `stderr`, `timedOut`. 🔴 **Таймаут всегда валит воркфлоу.** Ненулевой exit валит, если не задан `onNonZero: continue`. **Human:** рендерит тело, показывает на **управляющем терминале**, просит один непустой ответ. Пишется в `State[responseKey]` и дублируется в `State.outputs[effectiveId]`. > 🔴 **Requires interactive mode** and an interactor backed by a controlling terminal. > Explicit non-interactive mode **rejects a Human anywhere in the resolved tree during > preflight, including beneath an unselected Router branch**. **Следствие:** воркфлоу с `Human` **не запускается** в non-interactive режиме вообще — и проверка идёт до всякой логики, по всему resolved tree. Запуск через `docker exec` без `-t` даёт non-interactive → `Human` невозможен. ### 2.6 Эскалация Принадлежит **рёбрам**, а не определениям ресурсов. `Role` может эскалировать в ближайший включающий `Loop` **только если каждое** ребро от Loop к этому вхождению имеет `canEscalate: true` (опущенное = `false`). Вложенный `Loop` открывает **новую границу авторизации** — потомки не наследуют полномочия внешнего. ### 2.7 Команды ```bash npx --yes @baldaworks/callee@latest setup callee agent list callee agent view [--json] callee agent run --message "..." [--repl-timeout D] callee agent import --path --prefix [--force] callee doctor ``` --- ## 3. Prism — таск-трекинг ### 3.1 Жизненные циклы **Story:** Specify → Design → Breakdown → **Human approval** → Apply → Verify. Фазы: `phase:story:specify` → `design` → `breakdown` → `human` → `apply` → `verify`. **Epic:** Frame → Architecture → Roadmap → **approval** → Deliver Stories → Validation. Фазы: `phase:epic:frame` → `architecture` → `roadmap` → `approval` → `delivery` → `validation`. ### 3.2 Правила | Правило | Значение | |---|---| | Дети Epic | только **Stories** | | Дети Story | только **Tasks** | | Вложенные Epic, Task напрямую в Epic | **невалидно** | | Фиксированный диапазон числа детей | **нет** — оценка качественная (покрытие, связность, ревьюимость, верифицируемость, ацикличность) | | `Apply` | 🔴 **каждый переход требует явной авторизации человека** | | Одобрение Epic → Story | **не переносится** | | Критерии приёмки | показываются **только** по явному запросу оператора | При паузах prism останавливается только на: необходимом уточнении, одобрении перед реализацией, блокерах. ### 3.3 Установка и импорт **В coding agent** (независимая установка, например Codex): ```bash codex plugin marketplace add baldaworks/prism codex plugin add prism@prism # обновление: codex plugin marketplace upgrade prism → повторить add → новый thread ``` **Callee-агенты** (отдельный репозиторий, требует `callee >= 0.19.0` + `bd`): ```bash callee agent import baldaworks/prism-callee \ --path pack/callee/prism \ --prefix prism # обновление/починка: тот же вызов + --force ``` **Проверка:** ```bash callee agent list | grep '^prism/' callee agent view prism/lifecycle --json # 🔴 kind ДОЛЖЕН быть Router callee agent view prism/story --json callee agent view prism/epic --json bd where ``` 🔴 **`Sequential` вместо `Router` = устаревший импорт** → обновлять `--force`. `--agent-root pack/callee` — только для поддержки самого репозитория. > «Each repository installs and validates independently — installing one plugin does > not install the other.» ### 3.4 Штатный и прямой запуск | Способ | Команда | Что даёт | |---|---|---| | Штатный (рекомендован) | `$prism-callee:lifecycle <запрос>` | резолвит Beads-цель, **персистит фазовые метки**, поддерживает resume | | Прямой Story | `callee agent run prism/story --message "..."` | исполняет граф, но **не** резолвит Beads-элемент и не персистит метки | | Прямой Epic | `callee agent run prism/epic --message "..."` | то же ограничение | 📌 «The coding agent owns durable state and approval. Callee executes the selected graph; **approval never transfers from an Epic to a Story.**» --- ## 3.5 Роли: что это и как ими управляют **Роль — 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/` | 🔴 **Ролей в рантайме не выдают.** Роль = наличие файла в каталоге. Рантайм-механизма «выдать роль на задачу» **не существует**. ### Что динамическое, а что нет | Сущность | Динамически? | Чем регулируется | |---|---|---| | Роль (файл графа) | ❌ статично | наличие файла в каталоге | | Провайдер (`type`, `cmd`) | ❌ или из `State` через `DynamicRole` | автор графа | | Файлы | ❌ | монтирование + рабочая копия | | SSH-ключ | ❌ | наличие файла у процесса | | **Разрешение модели внутри ACP-сессии** | ⚠️ **да** | `allow_all` / `ask` / `deny_all` | 🔴 **Единственное, что реально запрашивается в рантайме — разрешение модели внутри ACP-сессии** (`ask` → вопрос в чат). --- ## 4. Права: формула и границы **`права = права процесса × смонтированное × разрешённое в графе`** | Уровень | Кто задаёт | |---|---| | Чем запущен процесс | вы, при старте (контейнер / хост-юзер / отдельный юзер) | | Что смонтировано | compose | | Что разрешено в графе | автор воркфлоу | 🔴 **`permissions.mode: allow_all` — это про права модели ВНУТРИ ACP-сессии** (править файлы, запускать команды). Оно **не** расширяет, что процесс видит в файловой системе. Два разных ограничения. ### Где callee — там и движки, и Script'ы 🔴 **ACP-провайдер — дочерний процесс callee.** Движок на хосте + callee в контейнере технически невозможны. Следствие: «callee на хосте» = **всё исполнение на хосте целиком**. ### Гейт `Human` защищает действие, но не возможность Доступ у роли есть **всегда** — он определён тем, чем запущен callee и что смонтировано. Апрув **не выдаёт доступ**, он разрешает выполнить операцию, которая уже технически возможна. ``` Роль лежит в каталоге → доступ ЕСТЬ ▼ Узел Human («собрать Xcode в VM macos-27?») → пауза ├─ апрув → Script исполняется (права у процесса УЖЕ были) └─ отказ → граф останавливается ``` 🔴 **Изоляцию даёт только то, чем процесс запущен и что смонтировано — не граф.** ### Динамических маунтов не существует На macOS Docker маунты задаются **при создании контейнера**; live `mount --bind` контейнер не видит; симлинк на внешний путь битый. **«Как модель запросит доступ к папке» не решается через маунты.** | Подход | Оценка | |---|---| | A. Смонтировать всё заранее (`~/Developer:ro`) | запись нельзя | | B. Пересоздание контейнера (`up -d`, ~3 с) | нужен механизм вне контейнера | | C. Хост-агент ставит права сам | полный доступ, ноль изоляции | | **D. Проект = рабочая копия** ✅ | модель работает в `~/agents/<проект>`, результат в проект через git | **Решение: D** — доступ задаётся наличием репозитория, а не маунтом. ### Доступ выбирается маршрутом, а не запросом Модель **не просит** прав и **не может эскалировать**: нет доступа — задача падает. ``` Роутер в графе ├─ задача требует ~/agents → роль в контейнере ├─ задача требует Xcode/lume → роль на хост-исполнителе └─ задача требует хоста по ssh → роль со ssh-ключом (command=) ``` **Разграничение = статический роутинг по типам задач**, не динамические права. --- ## 5. DeepSeek как ACP-провайдер Провайдер callee — `generic_acp`, `cmd: dsh-acp`, пакет **`@openma/deepseek-harness-acp@0.4.33`** (Apache-2.0, Node `>= 22.15`, `bin: dsh-acp` → `dist/bin.js`). Обёртка вокруг официального DeepSeek Harness. | Способ передачи кред | Значение | |---|---| | Флаг | `api-key`, либо `api-key:` при нескольких провайдерах | | Env | **`DEEPSEEK_API_KEY`** | | Env | **`DEEPSEEK_BASE_URL`** — OpenAI-совместимый endpoint override | Приоритет: **флаги → переменные окружения → дефолты**. 📌 `DEEPSEEK_BASE_URL` — OpenAI-совместимый override → смена провайдера сводится к смене одной переменной, без правки графа. 📌 Официальный `@deepseek-ai/dsh-acp` **не подошёл**: pre-release (последние версии сплошь `-rc`/`-alpha`), `bin: null` — библиотека, а не запускаемый сервер. --- ## 6. Питфоллы 1. 🔴 **`Human` требует TTY** — non-interactive режим отвергает граф целиком, включая невыбранные ветки Router. Запуск через `docker exec` без `-t` = non-interactive. 2. 🔴 **`cmd` — одно имя файла.** Аргументы только в `extraArgs`, иначе провайдер не стартует. 3. 🔴 **Не подавать вывод модели в `cmd`/`extraArgs`** — исполнение произвольного кода. 4. 🔴 **ACP-провайдер — дочерний процесс callee** — движок и callee не могут быть на разных хостах. 5. 🔴 **Гейт `Human` не выдаёт доступ** — только разрешает уже возможное действие. 6. ⚠️ **`prism/lifecycle` обязан быть `Router`** — иначе устаревший импорт. 7. ⚠️ **Непустой stderr ≠ провал** — судить по exit status. 8. ⚠️ **Таймаут Script валит весь воркфлоу** — не «продолжает с ошибкой». 9. ⚠️ **Concurrency группы `Parallel`** используют один живой shared state. 10. ⚠️ **Поиск npm по `scope:normahq` / `scope:baldaworks` даёт пусто** — эти scoped-пакеты через search-API не индексируются. Искать прямыми URL `registry.npmjs.org/%2f`. 11. ⚠️ **Динамических маунтов на macOS Docker нет** — только пересоздание контейнера. --- ## Связанные заметки - [[personal/plans/balda-callee-prism-plan]] — план внедрения (топология: стек на Mac) - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план Фаз 1-5 - [[personal/plans/balda-mm-phase4-agents]] — Фаза 4 (мост, приёмка) - [[personal/tech/agents-bridge]] — мост :8790 (демонтируется в новой топологии) - [[personal/tech/balda-executor-mcp]] — MCP-сервер делегирования (демонтируется) - [[personal/tech/macos-docker-virtiofs-mount-limits]] — почему нет динамических маунтов