21 KiB
Balda 2.0 — Mattermost + распределённое делегирование (план)
Создан 2026-09-25. Статус: черновик, ждёт апрува Alex. Задача: поднять свежую Балду из
normahq/balda(без наших коммитов), завязать на Mattermost, построить распределённое делегирование (дешёвый оркестратор + подписочные CLI в VM).
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, ~2-3 ч)
- Решить вопрос лицензии MM (free self-hosted Team Edition — ограничения по числу пользователей смотреть на момент установки).
- Развернуть MM на Kraken (
docker-compose, postgres, volume на USB-HDD-разделе с учётом mount-race). - Настроить: reverse proxy / порт, вход, создать команду + канал для агентов.
- Создать бота (Personal Access Token / bot account) для Балды.
Фаза 2 — адаптер Mattermost в Балде (~2-4 дня)
- Склонировать чистый
baldaworks/baldaв новую директорию (~/Developer/balda-mm) — наши коммиты не переносим. - Изучить
channel/zulip/*как образец: adapter, client, inbound, server, presentation. - Написать
channel/mattermost/*по контрактуtransport-presentation-boundary.md:- ingress: WebSocket events (или outgoing webhook) → семантические типы;
- delivery: рендеринг (Markdown MM), треды/посты;
- registration через DI в composition root.
- Собирать локально на Mac (arm64) и/или на Kraken (arm64) — проверить целевую платформу.
Фаза 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-диск нестабилен, mount-race | известный | учесть в compose (см. docker-data-root-mount-race), volume не на data-root |
| RPi5 не тянет сборку Балды | средний | собирать образ на Mac arm64, на Kraken только pull/up |
| Утечка секретов в MM-канал | средний | balda.permissions.mode: ask; redaction в job-events уже есть в апстриме |
| Mattermost free-лицензия ограничивает нужное | низкий | проверить лимиты до установки |
5. Открытые вопросы к Alex
- Mattermost: поднимаем self-hosted Team Edition (free) или есть платный/облачный аккаунт?
- VM vs Docker: начинаем с Docker-контейнера на Mac (быстро) или сразу VM (UTM/Parallels)?
- Kraken: MM на USB-HDD-разделе или на SD (риск износа)? Учитывая нестабильность USB-диска — возможно, вынести MM на SD с бэкапом.
- Провайдер оркестратора: DeepSeek (как сейчас у Валеры) или другой дешёвый?
- Судьба текущей Валеры/Клавдия: гасим или держим параллельно на время миграции?
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: создан по итогам разведки апстрима и юридической проверки подписок.