Files
obsidian-vault/personal/plans/balda-callee-prism-plan.md
T

342 lines
16 KiB
Markdown
Executable File

# Балда → Callee + Prism: делегирование
> Обновлён 2026-09-29. Статус: **C0 закрыта, C1-C5 не начаты.**
> Часть [[personal/projects/balda/balda-mm-delegation-plan]].
---
## 1. Целевая архитектура
```
Mattermost (Alex)
│ «добавь CSV-экспорт в отчёт»
▼
Балда-оркестратор (Kraken, DeepSeek, balda-mm:0.1.13)
│ приём сообщения, чтение Obsidian, контекст, ответ в чат
│ MCP-инструмент executor_run
▼
MCP-сервер balda-executor-mcp (Kraken, :8890, сеть mattermost_mm-net)
│ HTTP + X-Bridge-Secret
▼
Мост agents-bridge (Mac, :8790, launchd com.agents.bridge)
│ запускает `callee agent run <resource> --message <prompt>`
▼
callee CLI (контейнер balda-executor, Mac)
│ компилирует граф → валидирует ЦЕЛИКОМ до запуска
│ общий state-объект через прогон → ОДИН артефакт в stdout
├→ Role / DynamicRole → ACP-процесс → DeepSeek (generic_acp)
├→ Script → локальные детерминированные проверки
└→ Human → точка одобрения
▼
Beads (`bd`) — durable-состояние Story/Epic, переживает сессии
▼
артефакт → Балда → Mattermost
```
### Разделение ролей
| Слой | Владелец |
|---|---|
| Граф воркфлоу, валидация, state | **callee** |
| Durable-состояние + одобрение | **coding agent** |
| Исполнение модели | ACP-процесс |
### Разделение расходов
| Роль | Где | Провайдер |
|---|---|---|
| Оркестратор верхнего уровня | Kraken | DeepSeek `deepseek-chat` |
| Исполнитель-роли callee | Mac, контейнер | DeepSeek |
📌 «DeepSeek с обоих концов» = обе стороны на DeepSeek. Подписочный Codex подключается
позже заменой `State.provider` в `DynamicRole` — без правки графа.
---
## 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`):
```bash
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 в нём отсутствует.
```yaml
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 — провайдер из состояния
Все поля провайдера — шаблоны, рендерятся из состояния в момент визита:
```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` могут выбирать исполняемый код.
Не подавать туда вывод модели без ограничения через авторскую шаблонную логику.
---
## 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-агентов:
```bash
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. Фазы
### 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 — установка в контейнер `balda-executor` (1-2 ч)
Ни `callee`, ни `bd` нет ни в контейнере, ни на хосте.
| # | Шаг | Проверка |
|---|---|---|
| 1 | `npm i -g @baldaworks/callee` | `callee --version` |
| 2 | `npm i -g @openma/deepseek-harness-acp` | `dsh-acp --help` |
| 3 | `callee setup codex` → стартовые ресурсы | `callee agent list` содержит `workflows/investigate` |
| 4 | Установить Beads `bd` | `bd where` |
| 5 | Импорт prism-пака | `callee agent view prism/lifecycle --json` → `Router` |
| 6 | Прогон стартового `investigate` | артефакт в stdout, exit 0 |
📌 Персистентность: установки живут в слое контейнера. Чтобы переживали пересборку —
том или `Dockerfile`.
### C2 — Role к DeepSeek (1 ч)
Описать `Role` по §2.3 и проверить один визит вживую.
**Гейт:** в логе видны `role_model` и `role_reasoning`. До успешного рендера провайдера
эти поля опускаются — их появление означает, что рендер прошёл.
### C3 — подключение к мосту (2-3 ч)
1. Бэкап `~/Developer/agents-bridge/executor.py`.
2. Замена argv-таблицы `PROVIDERS`:
```python
PROVIDERS = {
"callee": ["callee", "agent", "run", "prism/lifecycle", "--message"],
}
```
3. `EXECUTOR_PROVIDER=callee` в `~/Docker/balda-executor/.env`.
4. Перезапуск моста: `launchctl bootout` + `bootstrap`.
5. Прогон задачи через мост, проверка `exit_code`.
### C4 — таск-трекинг (1-2 ч)
1. `bd` инициализируется в `/agents`.
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. Решения
| # | Вопрос | Решение | Почему |
|---|---|---|---|
| 1 | Провайдер обеих сторон | DeepSeek | команда Alex |
| 2 | Оркестрация | callee | документированная архитектура вместо самодельной |
| 3 | Таск-трекинг | prism + Beads | задание Alex |
| 4 | Host coding agent | callee CLI в контейнере `balda-executor` | Node 24 — родная среда; там смонтирован `~/agents`; мост уже умеет `docker exec` |
| 5 | Мост `:8790` | сохранить, сменить argv | идемпотентность, таймауты, лимит 1 задача уже проверены; выбрасывать работающий слой ради эстетики незачем |
| 6 | Тип провайдера | `generic_acp` | список типов закрытый, DeepSeek в нём нет |
---
## 7. Питфоллы
1. ⚠️ **`PROVIDERS` в мосте — argv-список.** Промпт должен остаться **одним**
последним элементом, иначе передача рвётся.
2. ⚠️ **`.env` не перебивает plist.** `--host` жёстко в `ProgramArguments`; смена
аргументов требует `bootout` + `bootstrap`. Проверка:
`lsof -nP -iTCP:8790 -sTCP:LISTEN` → должно быть `*:8790`.
3. ⚠️ **`prism/lifecycle` должен быть `Router`** — `Sequential` означает устаревший
импорт, лечится `--force`.
4. ⚠️ **Provider `cmd` — одно имя файла, не shell-строка.** Аргументы — только
в `extraArgs`.
5. ⚠️ **Никогда не маршрутизировать вывод модели в `cmd`/`extraArgs`.**
6. ⚠️ **Установки в контейнере не переживают пересборку** — том или `Dockerfile`.
7. ⚠️ **`docker-compose` (дефис) на Mac, `docker compose` (пробел) на Kraken.**
8. ⚠️ **Node `>= 22.15`** требуется адаптером (в образе 24.21.0).
---
## 8. Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Импорт `prism/*` устаревает при обновлении | средний | проверять `kind: Router` после каждого импорта |
| callee обновляется и меняет контракт | средний | пиннить версию в ресурсах |
| Мост теряет работоспособность после смены argv | низкий | бэкап `executor.py`, откат одной правкой |
| Стоимость DeepSeek растёт | низкий | исполнитель-роли — короткие визиты |
| Установка в контейнер не переживёт пересборку | низкий | том или `Dockerfile` |
---
## 9. Порядок и запреты
**Порядок:** C1 → C2 → C3 → C4 → C5.
**НЕ трогать в `~/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.
**НЕ начинать работу** без явного «поехали».
**Ничего не удалять** без отдельного подтверждения.
---
## Связанные заметки
- [[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-сервер делегирования