[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

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 23:24:00 +06:00
1 parent 0c7b1f6466
commit aa5b66a442
3 files changed
+259 -49

No files matched your search

+162 -49
View File
@@ -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.<id>.mcp_servers` — серверы подключаются **через секцию провайдера**;
`balda.mcp_servers` лишь добавляет ID в сессию. Без провайдерской секции инструменты
не грузятся. Формула: `effective = bundled(balda) + runtime.providers.<p>.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.<id>.mcp_servers`. Формула:
```
effective = bundled(balda) + runtime.providers.<provider>.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 "<channel>"`,
проявляется **только** при `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-<ts>`,
`docker-compose.yml.bak-mm-classifier-<ts>`.
```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 (✅)
@@ -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.
+93
View File
@@ -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 `<name>`
не подключён к сессии», в логе `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.<id>.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` в