Files
obsidian-vault/personal/tech/callee-prism.md
T

389 lines
22 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Callee + Prism — инструменты оркестрации и таск-трекинга
> Создан 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 <codex|claude|grok|copilot|opencode|cursor>
callee agent list
callee agent view <id> [--json]
callee agent run <id> --message "..." [--repl-timeout D]
callee agent import <owner/repo> --path <pack> --prefix <pfx> [--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:<provider>` при нескольких провайдерах |
| 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/<scope>%2f<name>`.
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]] — почему нет динамических маунтов