# 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..balda.provider`. Но у **одной сессии один провайдер** — переключается профилем, не внутри сессии. ### 0.4 GoalKeeper — уже готовая база для делегирования - `/goalkeeper `: цикл 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:///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]] — аналогичный опыт сравнения форка с апстримом