Files
obsidian-vault/personal/projects/balda/balda-mm-delegation-plan.md
T

304 lines
27 KiB
Markdown
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.
# Balda 2.0 — Mattermost + распределённое делегирование (план)
> Создан 2026-09-25. Статус: **в работе с 2026-09-26** (Alex дал команду исполнять, без вопросов).
> Задача: поднять свежую Балду из `baldaworks/balda` (без наших коммитов), завязать на Mattermost,
> построить распределённое делегирование (дешёвый оркестратор + подписочные CLI в VM).
>
> **Оценка объёма (проверено 2026-09-25):** это ~10-14 рабочих дней, а не «пара вечеров».
> Апстрим переписан полностью (900 файлов), Mattermost-адаптера в нём нет вообще.
> Реалистичная разбивка: MM на Kraken 2-3 ч · MM-адаптер 2-4 дня · оркестратор на Kraken 1-2 дня ·
> исполнитель + мост на Mac 2-3 дня · связка и приёмка 1 день.
>
> **Развилки, решённые Alex 2026-09-26:** бота в MM **не создавать** (токен даст сам);
> вопросов не задавать — решать самостоятельно; команд, требующих подтверждения, не использовать;
> не останавливаться до исполнения. Детали по фазам вынесены в отдельные доки:
> [[personal/projects/balda/mattermost-kraken-deploy]] (Фаза 1) ·
> [[personal/projects/balda/balda-mattermost-connector]] (Фаза 2).
---
## 0. Ключевые находки разведки (факты, проверено в коде и доках)
### 0.1 Апстрим Балды
- Апстрим переехал: `normahq/balda` → **`baldaworks/balda`**. Релиз: `ghcr.io/baldaworks/balda:latest`, npm `@baldaworks/balda`.
- Наш форк `~/Developer/balda` отстал на **900 файлов** (+135k/−28k строк).
- Архитектура переписана полностью: `handlers/*` → `handlersfx/*`, Zulip вынесен в `internal/apps/balda/channel/zulip/*`, добавлены backoffice (встроенный web UI + админ/логин/аудит), Slack Agent, unified user security state, sessionmemory на Badger.
- Наши коммиты (промежуточные статусы Zulip) в апстрим **не влиты** — `05159d15` есть только у нас.
### 0.2 Mattermost — в апстриме НЕТ
- `grep -i mattermost` по всему `upstream/main` → **0 совпадений**. Ни кода, ни доков.
- Каналы в апстриме: `telegram`, `zulip`, `slackagent`, `webhook` + `channel/router.go`.
- Значит MM = **новый транспортный адаптер**, писать самим.
- Хорошая новость: есть явный контракт для этого — `docs/architecture/transport-presentation-boundary.md`.
Требование: shared-пакеты владеют семантикой, транспортные пакеты владеют рендерингом/ingress/delivery,
регистрация через DI в composition root. Образец для копирования: `channel/zulip/adapter.go` (331 стр.) + `client.go` (289 стр.) — по объёму это 2-4 дня работы.
#### MM — это НОВЫЙ chat surface, а не просто «ещё один webhook»
В README апстрима заявлены ровно три chat surface: **Telegram, Zulip, Slack Agent**. MM не входит.
Значит адаптер затрагивает не только `channel/mattermost/*`, но и весь слой, где перечисляются транспорты.
Найденные touchpoints (проверять при реализации):
- `channel/router.go` — регистрация/диспетчеризация транспорта
- `handlersfx/module.go` — composition root (DI-вайринг)
- `internal/apps/balda/state/` — `channel_type` в каноническом locator (`channel_type:address_key`)
- `docs/commands.md` — таблица доступности команд по транспортам (Telegram/Zulip/Slack — колонки)
- `docs/reference/messaging.md`, `docs/architecture/multi-channel-token-auth.md` — контракты auth/доставки
**Следствие:** в MM-адаптере сразу закладывать полный набор: `/start owner=`, `/start invite=`,
`/topic`, `/goalkeeper`, `/auto`, `/reset`, `/skill`, `/cancel`, `/locator`, `/user`.
Scheduler/webhook-таргеты (`balda.scheduler.jobs` → `target: locator`) должны резолвиться в MM-локатор.
### 0.2.1 Что именно есть только у нас (инвентаризация перед «начать с чистого»)
Решение «поднять чистую Балду без наших коммитов» — обосновано. Инвентаризация:
**`~/Developer/balda`** (наши уникальные коммиты):
- `05159d15` — промежуточные статусы Zulip (правит `internal/apps/balda/handlers/balda.go` +25). В `main` нашего форка.
- `bc5fd03c` — то же + Telegram + `messageID` для threading. Лежит на `backup/our-main-before-upstream` и `feat/zulip-transport-intermediate`, в `main` не попала.
- `5ad65955` — **не фича**, а `replace normahq/norma => ./norma-local` в `go.mod`. При переезде удалить.
- `feat/zulip-events-polling` — `c619731d` Events API long-poll + stream_only_with_session фильтр; плюс два коммита про valera Dockerfile/compose.
**`~/Developer/norma-local`** (наши уникальные коммиты):
- `56a6e1e` = `91c2c0a` (дубли) — DeepSeek tool_calls: `pkg/runtime/hostedagent/openai.go` (+286),
`pkg/runtime/agentfactory/agentfactory.go` (+3), `openai_test.go` (+312).
- **Критично:** в `norma v2` (`f8a7336`) эти файлы **удалены целиком**; модуль стал
`github.com/normahq/norma/v2`, апстрим использует `github.com/normahq/runtime/v2 v2.0.11`.
Наш патч не переносится — либо он уже есть в `normahq/runtime/v2`, либо пишется заново.
- `norma-local` отстал от `origin/master` на **26 коммитов** (включая теги `v0.0.11`–`v0.0.13`, `v2.0.0`, `v2.0.1`).
**Проверить перед началом:** есть ли в `normahq/runtime/v2` поддержка tool_calls для OpenAI-совместимых
провайдеров. Если да — наш DeepSeek-фикс не нужен вообще, и потерь нет. Если нет — фикс надо
переписать в новый модуль (см. скилл `adk-hostedagent-openai-mcp`).
### 0.3 Провайдеры Балды (ACP)
Реестр в `cmd/balda/init.go`:
| id | type | model | binary |
|---|---|---|---|
| codex | `codex_acp` | gpt-5-codex | `codex` |
| opencode | `opencode_acp` | opencode/big-pickle | `opencode` |
| copilot | `copilot_acp` | gpt-5-codex | `copilot` |
| gemini | `gemini_acp` | gemini-3-flash-preview | `gemini` |
| claude_code | `claude_code_acp` | claude-sonnet-4-6 | `claude` |
- Балда говорит с CLI через **ACP (Agent Client Protocol)** — через `github.com/normahq/go-adk-acpagent/v2 v2.2.6`.
- Это ровно то, что нужно для «подписочных CLI»: Балда запускает **настоящий** `claude`/`codex` бинарь, а не дёргает API.
- **Мультипровайдерность есть:** несколько провайдеров в `runtime.providers`, переключение через `profiles.<name>.balda.provider`. Но у **одной сессии один провайдер** — переключается профилем, не внутри сессии.
### 0.4 GoalKeeper — уже готовая база для делегирования
- `/goalkeeper <objective>`: цикл work → validate до `verdict: pass` или `max_iterations`.
- Отдельные ADK-сессии для worker и validator (изоляция контекста).
- При workspace mode: изолированный worktree, автогенерация Conventional Commit, squash-merge в `base_branch`, удаление workspace.
- Артефакты в финале: изменённые файлы, ветка, коммит, вывод валидации, next step.
- **Вывод:** это уже «оркестратор + исполнитель», просто оба на одном провайдере.
### 0.5 Auto mode — автономное продолжение
- `/auto on|off` — сессионный режим: после каждого turn планируется внутренний synthetic turn со стирингом.
- Сентинелы `AUTO_DECISION:DONE` / `AUTO_DECISION:WAIT_FOR_USER`.
- Наш прошлый кейс (агент сам не продолжал) — в апстриме закрыт этой фичей.
### 0.6 Юридическая граница по подпискам — КРИТИЧНО
Проверено по официальным докам Anthropic (`code.claude.com/docs/en/legal-and-compliance`) и разбору Zylos (2026-07-13):
**Разрешено:**
- Запуск **немодифицированного** официального бинаря `claude`, залогиненного **под своим** аккаунтом, для своих задач и автоматизации. Claude Code сам является «explicit permission» для автоматизации.
- `claude setup-token` → `CLAUDE_CODE_OAUTH_TOKEN` на headless-машине (Pro/Max). Это документированный путь, не дыра.
- Provisioning своих API-ключей для своих авторизованных пользователей.
**Запрещено:**
- Извлекать OAuth-токен подписки и **переиспользовать его в другом клиенте** (прокси, ре-серв как OpenAI-совместимый API). Anthropic реализовал блокировки на сервере и банит аккаунты. Февраль 2026: OAuth-токены consumer-планов «in any other product, tool, or service — including the Agent SDK» не разрешены.
- Спуфить идентичность клиента Claude Code.
**Следствие для архитектуры:** Балда через ACP = хорошо (запускает настоящий бинарь). Прокси-мост, отдающий подписочный токен наружу как API = бан.
Дополнительно: лимиты Pro/Max рассчитаны на «ordinary, individual usage» — параллельные агенты под одним аккаунтом это серая зона даже с официальным бинарём. Codex CLI от OpenAI в этом смысле либеральнее (инженер OpenAI публично поддержал использование подписочного токена в сторонних харнессах); Gemini CLI — как Anthropic, вплоть до закрытия аккаунта.
---
## 1. Где разворачивать — решение
Требование Alex: постоянная доступность + сборка проектов + подписочные CLI без токенов.
Вывод: **не «или-или», а разделение ролей между двумя контейнерами.**
| Роль | Где | Почему |
|---|---|---|
| Mattermost + оркестратор Балда (DeepSeek) | **Kraken** | 24/7, уже есть docker stack, Zulip-подобный топиковый UX, дешёвый по CPU/RAM |
| Исполнитель (codex/claude CLI по подписке) | **Mac (Eagle)**, в VM | нужен доступ к `~/Developer`, git, тяжёлые сборки; подписка залогинена на маке |
### Почему не всё на Mac
Постоянная доступность Mac не гарантирована (сон, ребуты), а оркестратор должен жить в чате 24/7.
### Почему не всё на Kraken
- RPi5 ARM64: тяжёлые сборки/macOS-таргеты невозможны, `~/Developer` не там.
- Подписочный CLI на Kraken = логин подписного аккаунта на чужом хосте, вне зоны контроля → лишний риск бана.
### Почему VM, а не голый Mac
Изоляция: агент-исполнитель получает полный доступ к файлам и сети. VM даёт откат, снапшоты, ограничение диска. Плюс чистая авторизация (`claude setup-token` внутри VM, вне основного профиля Mac).
### Вариант, который стоит рассмотреть вместо VM
**Одноразовый Docker-контейнер на Mac** (не VM): том с проектом, `CLAUDE_CODE_OAUTH_TOKEN` из env. Плюсы: запуск за минуты, изоляция не хуже, легко убить. Минус: на macOS Docker работает через VM — доступ к хостовому диску только через bind-mount, производительность FS ниже.
**Рекомендация: начать с Docker-контейнера; если понадобится полный desktop/gui или сложный toolchain — переехать в UTM/Parallels VM.** Это на порядок быстрее в запуске.
### Транспорт между оркестратором и исполнителем
- Вариант A: **общий Mattermost-канал** — Балда на Kraken и исполнитель в VM оба боты. Просто, но грязно (лишний шум, конфликты сессий).
- Вариант B: **Мост на Mac**: небольшой сервис (FastAPI), который принимает задачи от Kraken по HTTP/Webhook и запускает CLI внутри. Идемпотентность, очередь, статусы.
- Вариант C: **вторая Балда в VM** и связка через webhooks Балды (в апстриме есть `balda.webhooks.*`).
- **Рекомендация: начать с B**, потому что он не требует ни форка Балды, ни второго канала. Позже можно перейти на C.
---
## 2. Сценарий делегирования (как это работает для Alex)
```
Mattermost (Alex)
│ «почини баг X в проекте Y»
▼
Балда-оркестратор (Kraken, DeepSeek — дёшево)
│ 1. собирает контекст: читает Obsidian, ищет в web, формирует ТЗ
│ 2. /goalkeeper → цикл work→validate
│ 3. на этапе work вызывает мост → задание в VM
▼
Мост (Mac, FastAPI) ──► codex/claude CLI (VM, подписка)
│ делает: код, сборка, тесты
│◄──── возврат: diff, вывод тестов, коммит
▼
Балда-оркестратор
│ 4. валидирует результат, обновляет доку в Obsidian,
│ трекает исполнение, отвечает в Mattermost
▼
Mattermost (Alex) — итог + артефакты
```
Ключевое разделение расходов:
- **оркестратор** — DeepSeek (дёшево) — контекст, поиск, документирование, трекинг, коммуникация.
- **исполнитель** — подписочный CLI (фикс-цена) — код, сборка, тесты.
---
## 3. Фазы
### Фаза 0 — подготовка (бэкапы, ~30 мин)
1. `backup/pre-normahq-sync` в `~/Developer/balda` и `~/Developer/norma-local`.
2. Сохранить стэш `whale-20260623` в отдельную ветку (не в stash — стэши теряются).
3. Снять текущий compose Валерии/Клавдия в бэкап (`~/Docker/balda-agent/`, `~/Docker/claudio-agent/`).
### Фаза 1 — Mattermost (Kraken) — 🔄 В РАБОТЕ
Детальный док: [[personal/projects/balda/mattermost-kraken-deploy]].
1. ✅ Лицензия — self-hosted **Team Edition** (free). Ограничения по числу пользователей
смотреть на момент установки.
2. 🔄 Развернуть MM на Kraken — **БЛОКЕР НАЙДЕН И РЕШЁН**: официальный образ
`mattermost/mattermost-team-edition` существует **только для amd64**, Kraken = arm64.
Официальной поддержки arm64 у Mattermost нет (KB: «not officially supported for production»).
**Решение:** собираем свой образ из **официального arm64-бинаря**
`releases.mattermost.com/11.11.1/mattermost-team-11.11.1-linux-arm64.tar.gz`.
Compose и `.env` на Kraken записаны; сборка запущена.
3. ✅ Порт выбран: **8065** (был свободен). Тома — на USB-HDD (`49e8f586…/docker/mattermost`),
НЕ на SD (иначе SD забьётся — инцидент 2026-06-24).
4. ⛔ **Бота НЕ создавать** — решение Alex 2026-09-26. Токен даст он сам.
5. ⏳ Создать команду + канал для агентов после подъёма.
6. ⏳ Cloudflare hostname **`mm.qentra.top` → HTTP `localhost:8065`** (делает Alex),
затем `MM_SERVICESETTINGS_SITEURL` → `https://mm.qentra.top`.
### Фаза 2 — адаптер Mattermost в Балде — 🔄 РАЗВЕДКА ЗАВЕРШЕНА
Детальный док: [[personal/projects/balda/balda-mattermost-connector]].
1. ✅ Вместо новой директории `~/Developer/balda-mm` — работаем в `~/Developer/balda`
на ветке **`feat/mattermost-channel`**, созданной от **чистого `upstream/main`**
(`de273d8f`). Это эквивалентно «чистому клону», но без второго рабочего дерева.
2. ✅ Изучен `channel/zulip/*` целиком: adapter (331), client (289), server (316),
inbound (192), locator_codec (229), presentation, `zulipfx/` (module/prompt/structured).
3. ⏳ Написать `channel/mattermost/*` по контракту `transport-presentation-boundary.md`:
- ingress: **WebSocket** `wss://<host>/api/v4/websocket` (не outgoing webhook!);
- delivery: рендеринг Markdown MM, треды через `root_id`, файлы через `file_ids`,
edit-in-place через `PUT /posts/{id}` (в отличие от Zulip, где draft — no-op);
- 7 групп DI, как в `zulipfx/module.go`.
4. ⏳ Shared-слой: `ChannelTypeMattermost`, `TransportMattermost`, `NameMattermostMarkdown`,
`turncmd.SourceMattermost`, колонка в `docs/commands.md`.
⚠️ `channel/router.go` **не** правится — он map-based.
5. ⏳ Quality gates: `go test -race ./...` + `go tool golangci-lint run`.
### Фаза 3 — оркестратор на Kraken (~1-2 дня)
1. Развернуть Балду с MM-адаптером на Kraken, провайдер — **DeepSeek** (через API-ключ, не подписка).
2. Настроить `balda.mcp_servers` (Obsidian MCP + Knowl при необходимости).
3. Проверить `/goalkeeper`, `/auto`, `/topic`, `/skill` в MM.
4. Верификация Alex в UI MM (не по логам).
### Фаза 4 — исполнитель в VM/Docker на Mac (~2-3 дня)
1. Docker-контейнер (или UTM VM) с Node 24 + `codex` + `claude` + `git`.
2. Авторизация: `claude setup-token` на Mac → `CLAUDE_CODE_OAUTH_TOKEN` в контейнер; для codex — свой flow.
3. Bind-mount проектной директории (не весь `~`).
4. Мост: FastAPI-сервис, принимает задачу от Kraken, запускает CLI headless, возвращает diff/тесты.
5. Идемпотентность + таймауты + лимит параллельных задач (не более 1-2, чтобы не ловить «unusual traffic patterns»).
### Фаза 5 — связка и приёмка
1. Сквозной тест: сообщение в MM → Балда → мост → CLI → результат обратно в MM.
2. Проверить расходы: DeepSeek-токены оркестратора + подписочные лимиты исполнителя.
3. Записать в Obsidian: как работает, где логи, как откатить.
---
## 4. Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Бан подписочного аккаунта за «unusual traffic» | средний | только официальный бинарь; не более 1-2 параллельных задач; не проксировать токен; мониторить письма Anthropic |
| MM-адаптер разъедется с апстримом | высокий | держать как отдельный fork-патч, ребейзить на каждый релиз; либо предложить в апстрим PR |
| Kraken: USB-HDD монтируется ~130 с, есть mount-race | известный | drop-in `RequiresMountsFor` уже применён и проверен reboot'ом; volume не на data-root |
| RPi5 не тянет сборку Балды | средний | собирать образ на Mac arm64, на Kraken только `pull`/`up` |
| Утечка секретов в MM-канал | средний | `balda.permissions.mode: ask`; redaction в job-events уже есть в апстриме |
| Mattermost free-лицензия ограничивает нужное | низкий | проверить лимиты до установки |
| 🔴 **Официального arm64-образа MM НЕ существует** | **подтверждён** | свой образ из официального arm64-бинаря; сторонние community-образы не брать. См. [[personal/projects/balda/mattermost-kraken-deploy]] |
| 🔴 **Hermes маскирует секреты в tool-выводе** | **подтверждён** | `write_file` не редактируется; значения не писать литералами — собирать на хосте. См. [[personal/tech/hermes-secret-redaction-bypass]] |
---
## 5. Открытые вопросы к Alex
> ⚠️ **2026-09-26: Alex запретил задавать вопросы** — решения принимать самостоятельно,
> искать в Obsidian, а не спрашивать. Пункты ниже оставлены как история; ответы даны инлайн.
1. **Mattermost:** ✅ self-hosted **Team Edition** (free).
2. **VM vs Docker:** ✅ начинаем с **Docker-контейнера** на Mac.
3. **Kraken:** ✅ на **USB-HDD-разделе**. Диск **не нестабилен** — просто медленно монтируется
(~130 с, mount-race закрыт drop-in'ом). Минус HDD — 5400rpm, ~95 МБ/с на чтение.
4. **Провайдер оркестратора:** ✅ **DeepSeek**.
5. **Судьба текущей Валеры/Клавдия:** ⏳ отложено — держим параллельно на время миграции.
6. ✅ **Бота в MM не создавать** — токен даст Alex (2026-09-26).
---
## 6. Что понадобится прочитать перед реализацией
- `docs/architecture/transport-presentation-boundary.md` — контракт транспортов
- `docs/architecture/runtime-contract.md` — инварианты рантайма
- `docs/reference/configuration.md` — конфиг провайдеров и MCP
- `docs/reference/plugins.md` — расширения
- `docs/goal-workflow.md` — контракт GoalKeeper
- `docs/architecture/auto-mode.md` — автономное продолжение
- `docs/reference/job-runtime.md` — durable jobs, scheduler, webhooks
- `internal/apps/balda/channel/zulip/*` — образец адаптера
## История
- **2026-09-25**: создан по итогам разведки апстрима и юридической проверки подписок.
- **2026-09-25**: добавлена оценка объёма (10-14 дней); уточнено, что диск Kraken НЕ нестабилен
(только медленный mount ~130 с) — формулировку «диск нестабилен» Alex потребовал удалить из
доков и памяти. См. `personal/tech/docker-data-root-mount-race.md` §«Диск НЕ нестабилен».
- **2026-09-26**: **старт исполнения.** Решения Alex: бота не создавать, вопросов не задавать,
подтверждений не использовать, не останавливаться до результата.
- Фаза 1: найден и решён блокер arm64 (свой образ из официального бинаря), compose+`.env`
записаны на Kraken, сборка запущена. → [[personal/projects/balda/mattermost-kraken-deploy]]
- Фаза 2: ветка `feat/mattermost-channel` от чистого `upstream/main`, разведка `channel/zulip/*`
завершена, архитектура коннектора зафиксирована.
→ [[personal/projects/balda/balda-mattermost-connector]]
- Побочно: разобран механизм маскировки секретов Hermes и найден обход.
→ [[personal/tech/hermes-secret-redaction-bypass]]
## Связанные заметки
- [[personal/projects/balda/mattermost-kraken-deploy]] — Фаза 1: MM на Kraken (arm64-блокер, деплой)
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2: архитектура MM-коннектора
- [[personal/tech/hermes-secret-redaction-bypass]] — обход маскировки секретов Hermes
- [[personal/tech/docker-data-root-mount-race]] — mount-race на Kraken (закрыт, reboot-безопасен)
- [[personal/projects/balda/balda-valera]] — текущая эксплуатация Балды/Валеры (форк, старая архитектура)
- [[personal/projects/balda/fast-rlm-integration]] — fast-rlm MCP sidecar (наша ветка)
- [[personal/tech/hermes-fork-vs-upstream]] — аналогичный опыт сравнения форка с апстримом