27 KiB
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—c619731dEvents 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 мин)
backup/pre-normahq-syncв~/Developer/baldaи~/Developer/norma-local.- Сохранить стэш
whale-20260623в отдельную ветку (не в stash — стэши теряются). - Снять текущий compose Валерии/Клавдия в бэкап (
~/Docker/balda-agent/,~/Docker/claudio-agent/).
Фаза 1 — Mattermost (Kraken) — 🔄 В РАБОТЕ
Детальный док: personal/projects/balda/mattermost-kraken-deploy.
- ✅ Лицензия — self-hosted Team Edition (free). Ограничения по числу пользователей смотреть на момент установки.
- 🔄 Развернуть 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 записаны; сборка запущена. - ✅ Порт выбран: 8065 (был свободен). Тома — на USB-HDD (
49e8f586…/docker/mattermost), НЕ на SD (иначе SD забьётся — инцидент 2026-06-24). - ⛔ Бота НЕ создавать — решение Alex 2026-09-26. Токен даст он сам.
- ⏳ Создать команду + канал для агентов после подъёма.
- ⏳ Cloudflare hostname
mm.qentra.top→ HTTPlocalhost:8065(делает Alex), затемMM_SERVICESETTINGS_SITEURL→https://mm.qentra.top.
Фаза 2 — адаптер Mattermost в Балде — 🔄 РАЗВЕДКА ЗАВЕРШЕНА
Детальный док: personal/projects/balda/balda-mattermost-connector.
- ✅ Вместо новой директории
~/Developer/balda-mm— работаем в~/Developer/baldaна веткеfeat/mattermost-channel, созданной от чистогоupstream/main(de273d8f). Это эквивалентно «чистому клону», но без второго рабочего дерева. - ✅ Изучен
channel/zulip/*целиком: adapter (331), client (289), server (316), inbound (192), locator_codec (229), presentation,zulipfx/(module/prompt/structured). - ⏳ Написать
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.
- ingress: WebSocket
- ⏳ Shared-слой:
ChannelTypeMattermost,TransportMattermost,NameMattermostMarkdown,turncmd.SourceMattermost, колонка вdocs/commands.md. ⚠️channel/router.goне правится — он map-based. - ⏳ Quality gates:
go test -race ./...+go tool golangci-lint run.
Фаза 3 — оркестратор на Kraken (~1-2 дня)
- Развернуть Балду с MM-адаптером на Kraken, провайдер — DeepSeek (через API-ключ, не подписка).
- Настроить
balda.mcp_servers(Obsidian MCP + Knowl при необходимости). - Проверить
/goalkeeper,/auto,/topic,/skillв MM. - Верификация Alex в UI MM (не по логам).
Фаза 4 — исполнитель в VM/Docker на Mac (~2-3 дня)
- Docker-контейнер (или UTM VM) с Node 24 +
codex+claude+git. - Авторизация:
claude setup-tokenна Mac →CLAUDE_CODE_OAUTH_TOKENв контейнер; для codex — свой flow. - Bind-mount проектной директории (не весь
~). - Мост: FastAPI-сервис, принимает задачу от Kraken, запускает CLI headless, возвращает diff/тесты.
- Идемпотентность + таймауты + лимит параллельных задач (не более 1-2, чтобы не ловить «unusual traffic patterns»).
Фаза 5 — связка и приёмка
- Сквозной тест: сообщение в MM → Балда → мост → CLI → результат обратно в MM.
- Проверить расходы: DeepSeek-токены оркестратора + подписочные лимиты исполнителя.
- Записать в 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, а не спрашивать. Пункты ниже оставлены как история; ответы даны инлайн.
- Mattermost: ✅ self-hosted Team Edition (free).
- VM vs Docker: ✅ начинаем с Docker-контейнера на Mac.
- Kraken: ✅ на USB-HDD-разделе. Диск не нестабилен — просто медленно монтируется (~130 с, mount-race закрыт drop-in'ом). Минус HDD — 5400rpm, ~95 МБ/с на чтение.
- Провайдер оркестратора: ✅ DeepSeek.
- Судьба текущей Валеры/Клавдия: ⏳ отложено — держим параллельно на время миграции.
- ✅ Бота в MM не создавать — токен даст Alex (2026-09-26).
6. Что понадобится прочитать перед реализацией
docs/architecture/transport-presentation-boundary.md— контракт транспортовdocs/architecture/runtime-contract.md— инварианты рантаймаdocs/reference/configuration.md— конфиг провайдеров и MCPdocs/reference/plugins.md— расширенияdocs/goal-workflow.md— контракт GoalKeeperdocs/architecture/auto-mode.md— автономное продолжениеdocs/reference/job-runtime.md— durable jobs, scheduler, webhooksinternal/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
- Фаза 1: найден и решён блокер arm64 (свой образ из официального бинаря), compose+
Связанные заметки
- 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 — аналогичный опыт сравнения форка с апстримом