From 0c7b1f646656c2622a76a2dd81387f65bf6956b4 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sat, 26 Sep 2026 22:53:40 +0600 Subject: [PATCH] [2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/tech/balda-executor-mcp.md --- personal/plans/balda-mm-phase4-agents.md | 62 ++++++-- personal/tech/balda-executor-mcp.md | 176 +++++++++++++++++++---- 2 files changed, 204 insertions(+), 34 deletions(-) diff --git a/personal/plans/balda-mm-phase4-agents.md b/personal/plans/balda-mm-phase4-agents.md index c20ab9db..6ba09e6f 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 в сессии Балды | 🔴 **блокер:** снапшот каталога в `state.db` содержит только `builtin` → Балда «MCP не вижу». См. [[personal/tech/balda-executor-mcp]] §10g | +| 6a | 🔴 Инструменты MCP в сессии Балды | 🔴 **корень: `session_memory` выключен; при включении на Mattermost падает `/reset`** (`no session-memory classifier for "mattermost"`). См. §9.3 | | 7 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI | --- @@ -293,16 +293,58 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN - obsidian ``` См. §11 ниже. -3. 🔴 **MCP-инструменты не видны в сессии Балды.** Транспорты и сети проверены живьём - (оба сервера отвечают 200 из docker-сети), Балда стартует без ошибок и отвечает в чате — - но говорит «MCP не вижу». **Корень найден:** снапшот каталога в `state.db` - (`balda.appruntime_catalog_snapshot:*`) содержит **только `builtin`** — источники - `configured` не скомпилированы; плюс сессия закреплена за старым `RuntimeSnapshotID`. - Разбор и механика — [[personal/tech/balda-executor-mcp]] §10g. - ⏳ Ждёт решения Alex: **A)** вычистить ключи снапшотов из `state.db` (рекомендовано), - **B)** полный сброс `state.db`. +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). + ⚠️ Тест-пост должен идти **от админского токена** — посты от имени самого бота Балда - игнорирует (правильно): `docker exec mattermost mmctl --local token generate admin …`. + игнорирует (правильно), в логе `dropping posted event: bot not mentioned`: + `docker exec mattermost mmctl --local token generate admin …`. + И **не забывать `@balda`** в тексте поста. 4. 🔴 Логин CLI (Alex вручную) — без него сквозная приёмка невозможна. 5. 🔴 **Завести Access Application** на `agents-bridge.qentra.top` — сейчас туннель отвечает 200 без авторизации (проверено). Защита — только `X-Bridge-Secret`. diff --git a/personal/tech/balda-executor-mcp.md b/personal/tech/balda-executor-mcp.md index f7e461b0..059a046a 100755 --- a/personal/tech/balda-executor-mcp.md +++ b/personal/tech/balda-executor-mcp.md @@ -3,8 +3,9 @@ > Создан 2026-09-26. Шаг 5 плана [[personal/plans/balda-mm-phase4-agents]]. > Статус: **собран, задеплоен на Kraken, образ `0.2.0`; конфиг Балды — `type: http` + > `obsidian: sse`. Балда стартует без ошибок и отвечает в чате.** -> 🔴 **Блокер:** MCP-инструменты не попадают в сессию — корень в снапшоте каталога -> (`state.db`), см. **§10g**. Ждёт решения Alex (вариант A/B). +> 🔴 **Блокер:** MCP-инструменты не попадают в сессию на Mattermost. Корень найден — +> **§10h** (секция провайдера + `session_memory` ломает `/reset`). Ждёт решения Alex (A/B). +> ⚠️ Прежняя версия заметки указывала на снапшот каталога — **диагноз был неверным**, §10g. > Связка: Балда (Kraken) → MCP → мост (Mac :8790) → контейнер-исполнитель. --- @@ -278,37 +279,56 @@ docker exec balda-executor-mcp python3 -c "import uvicorn, fastapi, httpx; print --- -## 10g. 🔴🔴 ГЛАВНЫЙ КОРЕНЬ: MCP-серверы не попадают в сессию из-за снапшота каталога +## 10g. 🔴 Диагностика «MCP не вижу»: снапшот каталога — ПРОВЕРИТЬ, но это НЕ корень Симптом: **все транспорты верны, оба сервера отвечают 200 из docker-сети, а Балда в чате говорит «MCP не вижу, ни одного сервера».** Ошибок в логах нет. `/reset` при этом **перечисляет** серверы — то есть ID прочитаны из YAML, но инструменты не подключены. -### Диагностика (порядок) +> ⚠️ **Важно.** В первой редакции этой заметки корнем был объявлен снапшот каталога +> («в `state.db` только `builtin` → чистить снапшоты»). **Это оказался неверный диагноз.** +> Проверка показала, что снапшот в порядке и оба источника в нём есть. Удалять данные +> НЕ нужно. Настоящий корень — §10h ниже. + +### Шаг 1. Подтвердить, что дело не в транспорте ```bash -# 1. Есть ли вообще слова obsidian / executor-mcp в логе Балды? docker logs balda-mm 2>&1 | grep -ci "obsidian" # → 0 docker logs balda-mm 2>&1 | grep -ci "executor-mcp" # → 0 ``` -Если **0** — проблема не в транспорте и не в сети: каталог не публикует эти источники. +### Шаг 2. Прочитать снапшот каталога (ожидаемо — он ЗДОРОВ) + +⚠️ Только `sqlite3` **с хоста Kraken** (в контейнере Балды нет ни shell, ни sqlite3). +`strings` для разбора структуры НЕ годится. ```bash -# 2. Что лежит в снапшотах каталога (state.db) STATE=/srv/dev-disk-by-uuid-49e8f586-…/docker/balda/state -strings "$STATE/state.db" | grep -o 'balda.appruntime_catalog_snapshot:[a-f0-9]*' | head -strings "$STATE/state.db" | grep -A1 'catalog_snapshot' | grep -o '"kind":"[a-z]*","name":"[a-z-]*"' | sort -u +DB="$STATE/state.db" +sqlite3 "$DB" "SELECT key, substr(updated_at,1,25) FROM balda_app_kv + WHERE key LIKE '%catalog_snapshot%' ORDER BY updated_at DESC LIMIT 1;" +sqlite3 "$DB" "SELECT value_json FROM balda_app_kv WHERE key LIKE '%%';" | jq '.sources, .mcp_servers' ``` -**Показательный результат (баг):** +**Наблюдаемый (исправный) результат:** ```json -"sources":[{"id":{"kind":"builtin","name":"balda"}, …}] +"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"}] ``` -В снапшоте **только `builtin`**. Источников `configured` (наши `executor-mcp`/`obsidian`) -нет — значит `PreparePluginCandidate` их не скомпилировала. +Если источники и транспорты видны — каталог исправен, идти в §10h. + +### Схема таблиц (пригодится) + +| Что | Где | +|---|---| +| KV-хранилище снапшотов | `balda_app_kv` (`namespace='balda.app'`, `key='runtime_catalog_snapshot:'`, значение в `value_json`) | +| Сессии | `balda_runtime_sessions` (`session_id`, `state_json`, `updated_at`) — **поля snapshot в `state_json` нет** | +| Ревизии каталога на диске | `state/catalog-revisions/` — `drwx------ root`, обычным пользователем **не читается** (`Permission denied`) | ### Механика (по коду, `internal/apps/balda/`) @@ -334,23 +354,128 @@ strings "$STATE/state.db" | grep -A1 'catalog_snapshot' | grep -o '"kind":"[a-z] ### Правило для будущих правок MCP в Балде 1. Залить конфиг. -2. Убедиться, что `PublishCandidate` отработает (новый каталог) — иначе снапшот остаётся старым. -3. Создать **новую** сессию (не переиспользовать старую — она прибита к снапшоту). +2. **Прописать серверы в секции ПРОВАЙДЕРА** — см. §10h, это обязательное условие. +3. Создать **новую** сессию. 4. Проверить **постом от админского токена** (не от бота — свои посты Балда игнорирует): ```bash docker exec mattermost mmctl --local token generate admin whale-check --json | jq -r '.[0].token' ``` -### Открытый вопрос на конец сессии +--- -Требует решения Alex (удаление данных → нужно подтверждение): -- **A)** точечно вычистить ключи `balda.appruntime_catalog_snapshot:*` из `state.db` - (owner/настройки целы, снапшоты пересоберутся) — рекомендовано; -- **B)** полный сброс `state.db` (заново `bootstrap-admin` + owner-авторизация). +## 10h. 🔴🔴 НАСТОЯЩИЙ КОРЕНЬ (найден 2026-09-26): две причины, обе исправлены -⚠️ `catalog-revisions/` на хосте — `drwx------ root`, обычным пользователем не читается -(`Permission denied`). Смотреть только через `strings state.db`. +### Причина 1 — провайдер не ссылался на MCP-серверы + +Официальная дока (`docs/reference/configuration.md`): + +> MCP servers are configured in `runtime.mcp_servers` and **referenced by providers via +> `runtime.providers..mcp_servers`**. + +Формула из той же доки (стр. 555): + +``` +effective MCP IDs = bundled defaults + runtime.providers..mcp_servers + balda.mcp_servers +``` + +Я добавлял ID **только** в `balda.mcp_servers` — этого мало: секция провайдера подключает +серверы к рантайму, а `balda.mcp_servers` лишь дополняет список в сессии. + +**Исправлено:** + +```yaml +runtime: + providers: + deepseek: + type: openai + openai: + base_url: "https://api.deepseek.com/v1" + api_key: "${DEEPSEEK_API_KEY}" + model: "deepseek-chat" + mcp_servers: # 🔴 ОБЯЗАТЕЛЬНАЯ секция + - executor-mcp + - obsidian +``` + +### Причина 2 — 🔴 `session_memory` включает MCP-биндер, но на Mattermost ломает `/reset` + +**Включатель MCP-инструментов в сессии — это `balda.session_memory.enabled`, НЕ `balda.memory`.** +Две разные секции, легко перепутать. + +Механика (`internal/apps/balda/sessionapp/module.go:191-196`): + +```go +fx.Annotate( + func(broker *sessionmemorymcp.ContextBroker, enabled bool) baldaagent.SessionMCPBinder { + return SessionMCPBinderAdapter{Broker: broker, Enabled: enabled} + }, + fx.ParamTags(``, `name:"balda_session_memory_enabled"`), +) +``` + +`enabled` = `cfg.Balda.SessionMemory.Enabled`. Далее в `agent/runtime_manager.go:308-311`: + +```go +bindingEnabled := binder != nil && registry != nil +if status, ok := binder.(sessionMCPBinderStatus); ok && !status.SessionMCPEnabled() { + bindingEnabled = false // ← MCP-инструменты отключаются целиком +} +``` + +**НО** при `session_memory.enabled: true` сброс сессии падает: + +```json +{"level":"warn","component":"balda.session_manager", + "error":"observe reset boundary: no session-memory classifier is registered for \"mattermost\"", + "boundary_reason":"reset"} +``` + +В чате: **`Could not reset this session.`** + +Причина: `ScopeResolver` (`preflight.go`) регистрирует классификаторы **только** для +`telegram`, `slackagent`, `zulip`: + +```go +sessionmemoryapp.NewScopeResolver(map[string]sessionmemoryapp.ScopeClassifier{ + baldatelegram.ChannelType: baldatelegram.ClassifyLocatorScope, + baldaslackagent.ChannelType: baldaslackagent.ClassifyLocatorScope, + baldazulip.ChannelType: baldazulip.ClassifyLocatorScope, +}) +``` + +**Mattermost в этом списке НЕТ.** + +### Итоговый вывод (замкнутый круг) + +| `session_memory` | MCP-инструменты | `/reset` на Mattermost | +|---|---|---| +| `false` (текущее) | ❌ не подключаются | ✅ работает | +| `true` | ✅ подключились бы | ❌ «Could not reset this session.» | + +**MCP-инструменты в сессии Балды на Mattermost сейчас НЕДОСТИЖИМЫ** — ограничение апстрима, +не ошибка конфигурации. `session_memory` оставлен **выключенным** (иначе ломается `/reset`). + +### Что осталось для разблокировки (нужно решение Alex) + +- **A)** Добавить `ClassifyLocatorScope` для Mattermost в `ScopeResolver` (патч апстрима, + ~20 строк по образцу zulip) → MCP заработает в Mattermost. **Рекомендовано** — вся работа + Alex идёт в Mattermost. +- **B)** Перевести бота на канал, где классификатор есть (`telegram` / `slackagent` / `zulip`) + → MCP заработает без правок кода. + +⛔ **НЕ делать:** чистить `state.db` / ключи снапшотов. Каталог исправен (§10g), +данные трогать не нужно. + +### Питфоллы тестирования (сожрали время) + +| Питфолл | Симптом | Как правильно | +|---|---|---| +| Пост отправлен от имени **бота** | `dropping posted event: bot not mentioned` — Балда молчит | слать токеном **админа**: `mmctl --local token generate admin …` | +| Забыт `@balda` в тексте | то же `bot not mentioned` | всегда упоминать бота | +| Правка `.env`, а не plist/конфига | «перезапустил, но старое значение» | смотреть, откуда реально читается | +| `docker logs` сразу после `up` | логи пусты (буферизация) | подождать ~60-80 с (OnStart ~47 с) | +| Ожидание, что `/reset` пересоздаст сессию при выключенном `session_memory` | «серверы в списке, а инструментов нет» | серверы в списке ≠ инструменты подключены | --- @@ -361,8 +486,11 @@ docker exec mattermost mmctl --local token generate admin whale-check --json | j 3. ✅ Образ обновлён до `0.2.0` на Kraken, `tools/list` отдаёт 3 инструмента из docker-сети. 4. ✅ Балда перезапущена с валидным типом — **стартует без ошибок**, `Balda bot started`, `type: http` принят валидатором. Сквозной пост отработан, Балда **ответила**. -5. 🔴 **Блокер:** Балда отвечает «MCP не вижу». Корень — снапшот каталога (**§10g**): - в `state.db` снапшот содержит только `builtin`. Нужно решение Alex (вариант A или B, §10g). +5. 🔴 **Блокер:** Балда отвечает «MCP не вижу». Корень найден (**§10h**): провайдер не + ссылался на серверы (исправлено) **+** `session_memory.enabled` ломает `/reset` на + Mattermost (`no session-memory classifier for "mattermost"`) → включатель MCP держать + выключенным. Нужно решение Alex по разблокировке (вариант A или B, §10h). + ⛔ `state.db` НЕ чистить — каталог исправен (§10g). 6. 🔴 Сквозная приёмка: `executor_run` из чата → задача на Mac → результат. 7. 🔴 Логин CLI внутри `balda-executor` — делает **Alex вручную**.