[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/tech/balda-executor-mcp.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 22:53:40 +06:00
1 parent 2a8706d450
commit 0c7b1f6466
2 files changed
+204 -34

No files matched your search

+52 -10
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 в сессии Балды | 🔴 **блокер:** снапшот каталога в `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.<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).
⚠️ Тест-пост должен идти **от админского токена** — посты от имени самого бота Балда
игнорирует (правильно): `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`.
+152 -24
View File
@@ -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 '%<hash>%';" | 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:<hash>'`, значение в `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.<id>.mcp_servers`**.
Формула из той же доки (стр. 555):
```
effective MCP IDs = bundled defaults + runtime.providers.<provider_id>.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 вручную**.