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

27 KiB
Raw Blame History

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: бота не создавать, вопросов не задавать, подтверждений не использовать, не останавливаться до результата.

Связанные заметки