From 335478ef651fcee0bee996bca790ba1d4e5edaf2 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sat, 26 Sep 2026 00:17:24 +0600 Subject: [PATCH] [2026-09-26] eagle: personal/projects/balda/balda-mm-delegation-plan.md personal/projects/balda/balda-valera.md personal/projects/balda/fast-rlm-integration.md --- .../balda/balda-mm-delegation-plan.md | 243 ++++++++++++++++++ personal/projects/balda/balda-valera.md | 14 + .../projects/balda/fast-rlm-integration.md | 11 + 3 files changed, 268 insertions(+) create mode 100644 personal/projects/balda/balda-mm-delegation-plan.md diff --git a/personal/projects/balda/balda-mm-delegation-plan.md b/personal/projects/balda/balda-mm-delegation-plan.md new file mode 100644 index 00000000..9fb58d4e --- /dev/null +++ b/personal/projects/balda/balda-mm-delegation-plan.md @@ -0,0 +1,243 @@ +# 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` — `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, ~2-3 ч) +1. Решить вопрос лицензии MM (free self-hosted Team Edition — ограничения по числу пользователей смотреть на момент установки). +2. Развернуть MM на Kraken (`docker-compose`, postgres, volume на USB-HDD-разделе с учётом mount-race). +3. Настроить: reverse proxy / порт, вход, создать команду + канал для агентов. +4. Создать бота (Personal Access Token / bot account) для Балды. + +### Фаза 2 — адаптер Mattermost в Балде (~2-4 дня) +1. Склонировать чистый `baldaworks/balda` в **новую** директорию (`~/Developer/balda-mm`) — наши коммиты не переносим. +2. Изучить `channel/zulip/*` как образец: adapter, client, inbound, server, presentation. +3. Написать `channel/mattermost/*` по контракту `transport-presentation-boundary.md`: + - ingress: WebSocket events (или outgoing webhook) → семантические типы; + - delivery: рендеринг (Markdown MM), треды/посты; + - registration через DI в composition root. +4. Собирать локально на Mac (arm64) и/или на Kraken (arm64) — проверить целевую платформу. + +### Фаза 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-диск нестабилен, 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 + +1. **Mattermost:** поднимаем self-hosted Team Edition (free) или есть платный/облачный аккаунт? +2. **VM vs Docker:** начинаем с Docker-контейнера на Mac (быстро) или сразу VM (UTM/Parallels)? +3. **Kraken:** MM на USB-HDD-разделе или на SD (риск износа)? Учитывая нестабильность USB-диска — возможно, вынести MM на SD с бэкапом. +4. **Провайдер оркестратора:** DeepSeek (как сейчас у Валеры) или другой дешёвый? +5. **Судьба текущей Валеры/Клавдия:** гасим или держим параллельно на время миграции? + +--- + +## 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**: создан по итогам разведки апстрима и юридической проверки подписок. diff --git a/personal/projects/balda/balda-valera.md b/personal/projects/balda/balda-valera.md index eaf0803e..bfcda6cf 100644 --- a/personal/projects/balda/balda-valera.md +++ b/personal/projects/balda/balda-valera.md @@ -1,5 +1,19 @@ # Балда / Валера — эксплуатация & MCP debug +> ## ⚠️ УСТАРЕЛО (2026-09-25) — читать с оговоркой +> Этот док описывает **старый форк-стек**: свой патч `norma-local` (`hostedagent/openai.go`), +> `go.mod replace` на локальную норму, DeepSeek через OpenAI-совместимый рантайм. +> +> **Апстрим ушёл далеко вперёд.** Актуально: `normahq/balda` → **`baldaworks/balda`**, +> архитектура переписана (`handlers/*` → `handlersfx/*`, Zulip → `channel/zulip/*`), +> провайдеры теперь **ACP** (`claude_code_acp`, `codex_acp`, ...) вместо hostedagent/openai. +> +> Планируется поднять **чистую Балду без наших коммитов** → см. +> [[personal/projects/balda/balda-mm-delegation-plan|balda-mm-delegation-plan]]. +> +> Что здесь всё ещё верно: грабли эксплуатации (NATS, owner, `DOCKER_OPTS`, `stream_only_with_session`), +> бранчи/стэши, диагностика молчания. Что мертво: раздел про `openai.go`/`norma-local` replace как «текущее». + ## Расположение - **Валера** = `balda-agent-valera-1`, конфиг: `~/Docker/balda-agent/.config/balda/config.yaml` - **Клавдий** = `claudio-agent-claudio-1`, конфиг: `~/Docker/claudio-agent/.config/balda/config.yaml` diff --git a/personal/projects/balda/fast-rlm-integration.md b/personal/projects/balda/fast-rlm-integration.md index e1c7e744..579b1ecf 100644 --- a/personal/projects/balda/fast-rlm-integration.md +++ b/personal/projects/balda/fast-rlm-integration.md @@ -1,5 +1,16 @@ # fast-rlm MCP HTTP integration with Balda +> ## ⚠️ СТАТУС (2026-09-25) +> Док описывает интеграцию с **прежней Балдой** (`balda-agent-valera-1` на нашем форке). +> Планируется переезд на чистый апстрим `baldaworks/balda` — см. +> [[personal/projects/balda/balda-mm-delegation-plan|balda-mm-delegation-plan]]. +> +> **Что переносится:** сама идея MCP HTTP sidecar в сети Балды (`runtime.mcp_servers` + +> `balda.mcp_servers`) и `OPENAI_BASE_URL`/`OPENAI_API_KEY` для fast-rlm — в апстриме MCP-механика +> сохранилась (`docs/reference/configuration.md`: `runtime.mcp_servers.` c `type: http|sse|stdio`). +> **Что перепроверить:** в апстриме у Балды встроенная fact-memory (`balda.memory.remember`) и +> опциональная Knowl-сайдкар — возможно, RLM-сжатие частично закрывается ими, оценить перед переносом. + ## Цель Дать Балде (`balda-agent-valera-1`) инструмент рекурсивного сжатия контекста (RLM) через MCP **HTTP** сервер в отдельном Docker контейнере.