From aa5b66a442898aff77fabb33a1418413578a7d3c Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sat, 26 Sep 2026 23:24:00 +0600 Subject: [PATCH] [2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/balda/balda-kraken-mm-deploy.md personal/tech/balda-go-fx-pitfalls.md --- personal/plans/balda-mm-phase4-agents.md | 211 ++++++++++++++---- .../projects/balda/balda-kraken-mm-deploy.md | 4 + personal/tech/balda-go-fx-pitfalls.md | 93 ++++++++ 3 files changed, 259 insertions(+), 49 deletions(-) diff --git a/personal/plans/balda-mm-phase4-agents.md b/personal/plans/balda-mm-phase4-agents.md index 6ba09e6f..b010708c 100755 --- a/personal/plans/balda-mm-phase4-agents.md +++ b/personal/plans/balda-mm-phase4-agents.md @@ -16,7 +16,7 @@ | 4 | Секция «Agents» в Dashboard | ✅ активирована и проверена | | 5 | Связка Балда → мост | ✅ конфиг `type: http`, образ 0.2.0, `tools/list` из docker-сети | | 6 | Obsidian MCP для Балды | ✅ контейнер `obsidian-mcp` на Kraken + `type: sse` в конфиге (транспорт проверен) | -| 6a | 🔴 Инструменты MCP в сессии Балды | 🔴 **корень: `session_memory` выключен; при включении на Mattermost падает `/reset`** (`no session-memory classifier for "mattermost"`). См. §9.3 | +| 6a | Инструменты MCP в сессии Балды | ✅ **исправлено 2026-09-26** — Mattermost-классификатор добавлен в `ScopeResolver` (`app.go` + `preflight.go`), образ `balda-mm:0.1.11`, `session_memory.enabled: true`. См. §12 | | 7 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI | --- @@ -293,54 +293,10 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN - obsidian ``` См. §11 ниже. -3. 🔴 **MCP-инструменты не видны в сессии Балды. КОРЕНЬ НАЙДЕН (2026-09-26).** - Оказалось **не** снапшот каталога — снапшот в порядке. Самый свежий (`scope: effective`, - `runtime_catalog_snapshot:895d7905…`) содержит всё как надо: - ```json - "sources":[{"kind":"builtin","name":"balda"}, - {"kind":"configured-mcp","name":"executor-mcp"}, - {"kind":"configured-mcp","name":"obsidian"}] - "mcp_servers":[{"name":"executor-mcp","transport":"streamable-http"}, - {"name":"obsidian","transport":"sse"}] - ``` - - **Две настоящие причины (обе исправлены в конфиге):** - - **3a. Провайдер не ссылался на MCP-серверы.** Доки Balda: - `runtime.providers..mcp_servers` — серверы подключаются **через секцию провайдера**; - `balda.mcp_servers` лишь добавляет ID в сессию. Без провайдерской секции инструменты - не грузятся. Формула: `effective = bundled(balda) + runtime.providers.

.mcp_servers + balda.mcp_servers`. - ```yaml - runtime: - providers: - deepseek: - type: openai - openai: {…} - mcp_servers: # 🔴 обязательная секция - - executor-mcp - - obsidian - ``` - - **3b. 🔴 `balda.session_memory.enabled` — выключатель MCP-биндера, но на Mattermost - он ломает `/reset`.** Биндер создаётся как - `SessionMCPBinderAdapter{Enabled: cfg.Balda.SessionMemory.Enabled}` - (`sessionapp/module.go:191-196`). Значит MCP-инструменты в сессии включаются именно - `session_memory`, а не `balda.memory` (это разные секции!). - - НО: при `session_memory.enabled: true` сброс сессии падает — - ``` - warn … "observe reset boundary: no session-memory classifier is registered for \"mattermost\"" - → в чате: "Could not reset this session." - ``` - `ScopeResolver` регистрирует классификаторы только для **telegram / slackagent / zulip** - (`preflight.go`), для Mattermost — нет. Итог: сессия не пересоздаётся, MCP не подключаются. - - **Вывод:** MCP-инструменты в сессии Балды на Mattermost сейчас **недостижимы** — - упирается в отсутствие Mattermost-классификатора в `ScopeResolver`. - `session_memory` оставлен **выключенным** (иначе `/reset` не работает). - Для разблокировки нужно либо добавить `ClassifyLocatorScope` для Mattermost в апстриме, - либо перейти на канал с поддержкой (telegram/slackagent/zulip). - +3. ✅ **MCP-инструменты не видны в сессии Балды — ИСПРАВЛЕНО (2026-09-26).** Полный разбор в §12. + Кратко: причина была **не** в снапшоте каталога (он в порядке) и не только в провайдерском + `mcp_servers`. Настоящий выключатель — `balda.session_memory.enabled`, а он на Mattermost + падал из-за отсутствующего классификатора scope. Классификатор добавлен — см. §12. ⚠️ Тест-пост должен идти **от админского токена** — посты от имени самого бота Балда игнорирует (правильно), в логе `dropping posted event: bot not mentioned`: `docker exec mattermost mmctl --local token generate admin …`. @@ -422,6 +378,163 @@ docker exec mattermost sh -c 'curl -sS -m 8 -H "Accept: text/event-stream" http: 📌 У `mcp-proxy` **нет флага `--host`** — в отличие от Python-моста. Передавать только `--port` + `--`. Лишний `--host` роняет контейнер. +## 12. ✅ MCP-инструменты в сессии Балды — корень и фикс (2026-09-26) + +**Симптом:** `/reset` перечисляет `MCP: balda, executor-mcp, obsidian`, но Балда отвечает +«namespace `obsidian` не подключён к сессии» и **не вызывает инструменты** +(`function_call_part_count: 0`). + +### Три ложных следа (пройдены, чтобы не повторять) + +| След | Почему ложный | +|---|---| +| Снапшот каталога `appruntime_catalog_snapshot` пуст | **Не пуст.** Самый свежий (`scope: effective`) содержит `builtin` + `configured-mcp: executor-mcp` + `configured-mcp: obsidian`, транспорты `streamable-http` / `sse` — всё верно. Я предлагал чистить `state.db` — **правка не нужна**, данные целы | +| Провайдер `type: openai` не умеет MCP | Умеет. Ходит через agent runtime, ограничения по типу провайдера нет | +| `balda.mcp_servers` достаточно | **Недостаточно, но и не корень.** Это лишь список ID для сессии | + +### Настоящий корень — цепочка из двух звеньев + +**Звено 1: серверы подключаются через секцию ПРОВАЙДЕРА.** +Доки Balda: MCP-серверы описываются в `runtime.mcp_servers` и **подключаются провайдером** +через `runtime.providers..mcp_servers`. Формула: +``` +effective = bundled(balda) + runtime.providers..mcp_servers + balda.mcp_servers +``` +Без провайдерской секции `balda.mcp_servers` игнорируется. + +```yaml +runtime: + providers: + deepseek: + type: openai + openai: {…} + mcp_servers: # 🔴 обязательная секция + - executor-mcp + - obsidian +``` + +**Звено 2 (главное): 🔴 `balda.session_memory.enabled` — выключатель MCP-биндера сессии.** +Биндер собирается в composition root: +```go +// internal/apps/balda/app.go:275-278 +fx.Annotate( + func() bool { return cfg.Balda.SessionMemory.Enabled }, + fx.ResultTags(`name:"balda_session_memory_enabled"`), +), +// internal/apps/balda/sessionapp/module.go:191-196 +fx.Annotate( + func(broker *sessionmemorymcp.ContextBroker, enabled bool) baldaagent.SessionMCPBinder { + return SessionMCPBinderAdapter{Broker: broker, Enabled: enabled} + }, + fx.ParamTags(``, `name:"balda_session_memory_enabled"`), +), +``` +⚠️ **`balda.memory` и `balda.session_memory` — РАЗНЫЕ секции.** Первая включает память, +вторая — MCP-биндер сессии. В конфиге была только `memory`. + +При включении `session_memory` сессия на Mattermost падала: +``` +warn … "observe reset boundary: no session-memory classifier is registered for \"mattermost\"" +→ в чате: "Could not reset this session." +``` + +### Фикс — классификатор Mattermost в `ScopeResolver` + +**Ключевое:** классификатор для Mattermost **уже был написан** (в составе MM-коннектора, +`channel/mattermost/locator_codec.go:141-156` — `dm`→personal, `channel`→group). +Не хватало ровно **регистрации** в двух картах резолвера. + +| Файл | Строки | Правка | +|---|---|---| +| `internal/apps/balda/app.go` | 28 | импорт → алиас `baldamattermost` | +| `internal/apps/balda/app.go` | 284 | `baldamattermost.ChannelType: baldamattermost.ClassifyLocatorScope,` | +| `internal/apps/balda/app.go` | 991 | `mattermost.ValidateConfig` → `baldamattermost.ValidateConfig` (следствие алиаса) | +| `internal/apps/balda/preflight.go` | 13 | импорт алиаса | +| `internal/apps/balda/preflight.go` | 169 | строка регистрации в карте | + +🔴 **Карт резолвера ДВЕ** — `app.go` (рантайм) и `preflight.go` (валидация). +Править **обе**: если поправить одну, preflight разойдётся с рантаймом. + +```go +// app.go:280-285 и preflight.go:165-170 — одинаковый список +sessionmemoryapp.NewScopeResolver(map[string]sessionmemoryapp.ScopeClassifier{ + baldatelegram.ChannelType: baldatelegram.ClassifyLocatorScope, + baldaslackagent.ChannelType: baldaslackagent.ClassifyLocatorScope, + baldazulip.ChannelType: baldazulip.ClassifyLocatorScope, + baldamattermost.ChannelType: baldamattermost.ClassifyLocatorScope, +}) +``` + +📌 **Урок:** при добавлении нового транспорта в Балду классификатор scope надо +**не только написать, но и зарегистрировать в двух картах**. Симптом отсутствия +регистрации — `no session-memory classifier is registered for ""`, +проявляется **только** при `session_memory.enabled: true`. + +### Проверка (гейты) + +```bash +cd ~/Developer/balda +go build ./... # ✅ +go tool go-arch-lint check --project-path . # ✅ OK +gofmt -l internal/apps/balda/app.go internal/apps/balda/preflight.go # ✅ пусто +go test -race ./internal/apps/balda/sessionmemoryapp/... \ + ./internal/apps/balda/channel/mattermost/... # ✅ ok +``` + +⚠️ **Полный `go test -race ./...` = 7 предсуществующих падений** (не регрессия от правок): +`cmd/balda` (1), `internal/apps/balda` (2), `mcpruntime` (1), `session` (2), `sessionmemory` +layout-тесты. Проверено `git stash` — **те же падения на чистом дереве**. +📌 Метод: прежде чем объявлять падения регрессией, снять правки `git stash` и прогнать те же +пакеты на базовой ревизии. + +### Деплой + +```bash +# сборка (Mac, arm64 builder) — Dockerfile.release, НЕ Dockerfile +docker buildx build --builder arm64builder --platform linux/arm64 \ + -f Dockerfile.release --provenance=false --load -t balda:mm-classifier . +docker save balda:mm-classifier -o /tmp/balda-mm-classifier.tar # 20 МБ +scp /tmp/balda-mm-classifier.tar kraken:/tmp/ +ssh kraken 'docker load -i /tmp/balda-mm-classifier.tar && docker tag balda:mm-classifier balda-mm:0.1.11' +``` + +На Kraken: `docker-compose.yml` → `image: balda-mm:0.1.11`; `config/config.yaml` → +`session_memory: enabled: true`; оба файла править **локально**, заливать `scp` +(не sed/heredoc). Бэкапы: `config.yaml.bak-mm-classifier-`, +`docker-compose.yml.bak-mm-classifier-`. + +```bash +ssh kraken 'cd /home/kraken/balda && docker compose up -d --force-recreate' +``` + +✅ **Логи подтверждают фикс:** +``` +SessionMCPBinder … provided +session-memory runtime … started +session memory … started +session manager ready · balda_provider=deepseek +bundled MCP listener started · 127.0.0.1:33703 · routes /mcp/balda +Balda bot started. Press Ctrl+C to stop. +``` +⚠️ Побочное: `failed to load bot identity for startup log: getMe response missing result` — +**не дефект**, только косметика лога идентичности бота. + +### Что проверяет Alex (живая приёмка) + +``` +@balda /reset +→ сессия сбрасывается БЕЗ «Could not reset this session» +→ в стартовом сообщении MCP: balda, executor-mcp, obsidian + +@balda какие mcp серверы тебе доступны? +→ перечисляет obsidian и executor-mcp как ИНСТРУМЕНТЫ +``` + +⚠️ На момент записи **живой ответ Балды ещё не подтверждён** — проверка за Alex'ом. +Статус шага 6a: код+деплой ✅, приёмка ⏳. + +--- + ## Связанные заметки - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5) - [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅) diff --git a/personal/projects/balda/balda-kraken-mm-deploy.md b/personal/projects/balda/balda-kraken-mm-deploy.md index 6f3e63af..577b45e8 100755 --- a/personal/projects/balda/balda-kraken-mm-deploy.md +++ b/personal/projects/balda/balda-kraken-mm-deploy.md @@ -3,6 +3,10 @@ > Создан 2026-09-26. Обновлён 2026-09-26 (**✅ ФАЗА ЗАКРЫТА — боевая приёмка пройдена**). > Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex.** Образ `0.1.10` (коммит `9f1e6c9c`, > он же PR baldaworks/balda#8). +> ⚠️ **Актуальный рабочий образ на Kraken — `balda-mm:0.1.11`** (2026-09-26, позже этой фазы): +> добавлен Mattermost-классификатор scope в `ScopeResolver` + включён +> `balda.session_memory` (MCP-инструменты в сессии). Разбор — [[personal/plans/balda-mm-phase4-agents]] §12. +> Compose: `/home/kraken/balda/docker-compose.yml` (`image: balda-mm:0.1.11`). > **Доказательство (лента `balda-test`):** `cbwsorzwoigbtrdnteugnx4aqo` (Alex) — > `@balda скажи одно слово` → `edxrgupoxiyig5gzmr79tjypbme` (бот) — `слово`. > `settlement_stage=accepted`, `bad_seq`=0, `RestartCount`=0. diff --git a/personal/tech/balda-go-fx-pitfalls.md b/personal/tech/balda-go-fx-pitfalls.md index 8d335373..4837de77 100755 --- a/personal/tech/balda-go-fx-pitfalls.md +++ b/personal/tech/balda-go-fx-pitfalls.md @@ -15,6 +15,11 @@ > **🔴 замкнутый круг привязки при занятом binding** (§«Дефект 8»). > 🔴 **Новое в этой сессии:** §«Mattermost Team Edition: что недоступно» — push невозможен. > Уточнён §«авторизация — ОДИН механизм»: челлендж НЕ был причиной №1, им был `bad_seq`. +> 🔴 **Добавлено 2026-09-26 (сессия MCP-фикса):** §«Scope-классификатор нового транспорта» — +> написать мало, надо зарегистрировать в ДВУХ картах (`app.go` + `preflight.go`); смежное — +> `balda.session_memory` vs `balda.memory`, MCP через секцию провайдера. +> §«Сброс падающих тестов» — сначала `git stash`, потом слово «регрессия»; +> `buildx` без `--load` не кладёт образ в `docker images`. > Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26. > Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3). > Проектные доки: [[personal/projects/balda/balda-mattermost-connector]], @@ -114,6 +119,94 @@ case transportMattermost: --- +## 🔴🔴 Scope-классификатор нового транспорта: написать мало — надо зарегистрировать в ДВУХ картах + +Найдено 2026-09-26 (Балда на Kraken, MCP-инструменты не подключались). Класс ошибки: +**функция есть, регистрации нет** — код компилируется, тесты зелёные, отказ виден только в рантайме. + +**Симптом:** MCP-инструменты не подключаются к сессии. В чате — «namespace `` +не подключён к сессии», в логе `function_call_part_count: 0` (модель не вызвала инструмент). +При включённом `session_memory` сброс сессии падает: + +``` +warn … "observe reset boundary: no session-memory classifier is registered for \"mattermost\"" +→ в чате: "Could not reset this session." +``` + +**Причина:** `sessionmemoryapp.ScopeResolver` собирается из карты классификаторов по +`channelType`. Новый транспорт (для Mattermost — коннектор писал Alex) имел +`ClassifyLocatorScope` в `channel/mattermost/locator_codec.go`, но **не был добавлен +в карту**. Резолвер fail-closed: `classifier == nil` → `CodeUnsupportedScope` +(`sessionmemoryapp/capture.go:68-75`). + +🔴 **Карт ДВЕ, править обе:** +- `internal/apps/balda/app.go` — рантайм-граф +- `internal/apps/balda/preflight.go` — граф валидации + +Расхождение между ними даёт «конфиг прошёл preflight, но упал в рантайме» (и наоборот). + +```go +sessionmemoryapp.NewScopeResolver(map[string]sessionmemoryapp.ScopeClassifier{ + baldatelegram.ChannelType: baldatelegram.ClassifyLocatorScope, + baldaslackagent.ChannelType: baldaslackagent.ClassifyLocatorScope, + baldazulip.ChannelType: baldazulip.ClassifyLocatorScope, + baldamattermost.ChannelType: baldamattermost.ClassifyLocatorScope, // ← добавить +}) +``` + +⚠️ Побочный эффект импорта с алиасом: если пакет уже импортировался без алиаса +(`"…/channel/mattermost"`), все обращения вида `mattermost.X` в файле надо перевести +на `baldamattermost.X` — иначе `undefined: mattermost` (как в §«Дефект 3»). + +📌 **Добавлять в чек-лист нового транспорта** (продолжение списка из 12 пунктов выше): +13. 🔴 `ClassifyLocatorScope` зарегистрирован в карте `ScopeResolver` **в обоих** файлах — + `app.go` и `preflight.go`. + +### Смежное: `session_memory` — выключатель MCP-биндера, и это НЕ `memory` + +```go +// app.go:275-278 — выключатель +fx.Annotate(func() bool { return cfg.Balda.SessionMemory.Enabled }, + fx.ResultTags(`name:"balda_session_memory_enabled"`)) +// sessionapp/module.go:191-196 — биндер MCP +func(broker *sessionmemorymcp.ContextBroker, enabled bool) baldaagent.SessionMCPBinder { + return SessionMCPBinderAdapter{Broker: broker, Enabled: enabled} +} +``` + +⚠️ **`balda.memory` и `balda.session_memory` — разные секции.** Первая включает память, +вторая — MCP-биндер сессии. С `memory`-only инструменты в сессию не подключаются. + +📌 **MCP-серверы подключаются через секцию ПРОВАЙДЕРА**, не через `balda.mcp_servers`: +``` +effective = bundled(balda) + runtime.providers..mcp_servers + balda.mcp_servers +``` + +--- + +## 🔴 Сброс падающих тестов: сначала `git stash`, потом слово «регрессия» + +Метод 2026-09-26. `go test -race ./...` на ветке `feat/mattermost-channel` даёт 7 падений +(`cmd/balda`, `internal/apps/balda`, `mcpruntime`, `session`, тесты layout `sessionmemory`). +**Ни одно не было регрессией** от правки — те же падения на чистом дереве. + +```bash +git stash +go test -race ./internal/apps/balda/ ./internal/apps/balda/session/ \ + ./internal/apps/balda/mcpruntime/ ./cmd/balda/ > /tmp/baseline.log 2>&1 +git stash pop +# сравнить списки `^--- FAIL` — совпали? значит не регрессия +``` + +📌 Объявлять регрессией только то, чего **нет** в baseline. Иначе доклад «я сломал 7 тестов» +при нетронутом коде. + +⚠️ Побочное наблюдение: фоновая сборка `docker buildx build` **без `--load`** кладёт результат +только в кеш («No output specified with docker-container driver») — образ не появится в +`docker images`, и последующий `docker save` провалится. Собирать с `--load`. + +--- + ## 🔴 Прогресс-политика должна совпадать на command- и turn-пути одного транспорта Найдено 2026-09-26 (сессия PR-подготовки). `handlersfx/mattermost_ingress.go` в