389 lines
22 KiB
Markdown
Executable File
389 lines
22 KiB
Markdown
Executable File
# 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]] — почему нет динамических маунтов
|