[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:23:17 +06:00
1 parent c3ced58ee1
commit 218165c8c7
2 files changed
+118 -12

No files matched your search

+105 -7
View File
@@ -1,8 +1,10 @@
# balda-executor-mcp — MCP-сервер делегирования задач (шаг 5 Фазы 4)
> Создан 2026-09-26. Шаг 5 плана [[personal/plans/balda-mm-phase4-agents]].
> Статус: **собран, задеплоен на Kraken, образ обновлён до 0.2.0; конфиг Балды
> переведён на `type: http`, Obsidian MCP прописан рядом** — ждёт сквозной проверки в чате.
> Статус: **собран, задеплоен на Kraken, образ `0.2.0`; конфиг Балды — `type: http` +
> `obsidian: sse`. Балда стартует без ошибок и отвечает в чате.**
> 🔴 **Блокер:** MCP-инструменты не попадают в сессию — корень в снапшоте каталога
> (`state.db`), см. **§10g**. Ждёт решения Alex (вариант A/B).
> Связка: Балда (Kraken) → MCP → мост (Mac :8790) → контейнер-исполнитель.
---
@@ -276,16 +278,112 @@ docker exec balda-executor-mcp python3 -c "import uvicorn, fastapi, httpx; print
---
## 10g. 🔴🔴 ГЛАВНЫЙ КОРЕНЬ: MCP-серверы не попадают в сессию из-за снапшота каталога
Симптом: **все транспорты верны, оба сервера отвечают 200 из docker-сети, а Балда в чате
говорит «MCP не вижу, ни одного сервера».** Ошибок в логах нет. `/reset` при этом
**перечисляет** серверы — то есть ID прочитаны из YAML, но инструменты не подключены.
### Диагностика (порядок)
```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** — проблема не в транспорте и не в сети: каталог не публикует эти источники.
```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
```
**Показательный результат (баг):**
```json
"sources":[{"id":{"kind":"builtin","name":"balda"}, …}]
```
В снапшоте **только `builtin`**. Источников `configured` (наши `executor-mcp`/`obsidian`)
нет — значит `PreparePluginCandidate` их не скомпилировала.
### Механика (по коду, `internal/apps/balda/`)
| Звено | Файл | Что делает |
|---|---|---|
| параметры модуля | `catalogapp/module.go` | `params.Norma.MCPServers` ← `runtime.mcp_servers` |
| сборка | `catalogapp/runtime.go:81` | `configuredMCP: configuredMCPSources(configured)` |
| компиляция | `runtime.go:128` `PreparePluginCandidate` | `sources := [builtin] + r.configuredMCP + skills + plugins` |
| публикация | `runtime.go:168` `PublishCandidate` | `persistSnapshot` → `store.PublishApplication` → **`r.mcp.Reconcile(ctx, retained)`** |
| привязка к сессии | `runtime.go:188` `ResolveEffectiveSnapshot` | если у сессии есть `record.RuntimeSnapshotID` → берётся **именно он** |
| выдача ID | `module.go:57` `AcquireMCPServerIDs(ctx, snapshot.ID)` | ID MCP берутся из **снапшота**, не из конфига |
🔴 **Два независимых условия, оба обязаны выполниться:**
1. `PublishCandidate` должен быть **вызван** — он единственный, кто дёргает
`mcp.Reconcile`. Вызывается при публикации кандидата (плагины/скиллы/старт).
2. Сессия **не должна** быть закреплена за старым `RuntimeSnapshotID`.
Привязка вечная: строка `record.RuntimeSnapshotID != ""` → старый снапшот навсегда.
📌 **Из этого следует:** правка `runtime.mcp_servers` в YAML **не влияет** на уже
существующие сессии. Нужно новое состояние каталога + новые сессии.
### Правило для будущих правок MCP в Балде
1. Залить конфиг.
2. Убедиться, что `PublishCandidate` отработает (новый каталог) — иначе снапшот остаётся старым.
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-авторизация).
⚠️ `catalog-revisions/` на хосте — `drwx------ root`, обычным пользователем не читается
(`Permission denied`). Смотреть только через `strings state.db`.
---
## 11. Что осталось
1. ✅ **`runtime.mcp_servers.executor-mcp` → `type: http`** — сделано, залито на Kraken.
2. ✅ **`obsidian` прописан рядом** (`type: sse` → `http://obsidian-mcp:3101/sse`).
3. ✅ Образ обновлён до `0.2.0` на Kraken, `tools/list` отдаёт 3 инструмента из docker-сети.
4. 🟡 Перезапустить Балду и убедиться, что инструменты обоих серверов видны в сессии
(последняя правка `type: http` в конфиге **залита в `/tmp/balda-cfg/config.yaml`**, но
на момент окончания сессии перезапуск с валидным типом ещё не подтверждён сквозным тестом).
5. 🔴 Сквозная приёмка: `executor_run` из чата → задача на Mac → результат.
6. 🔴 Логин CLI внутри `balda-executor` — делает **Alex вручную**.
4. ✅ Балда перезапущена с валидным типом — **стартует без ошибок**, `Balda bot started`,
`type: http` принят валидатором. Сквозной пост отработан, Балда **ответила**.
5. 🔴 **Блокер:** Балда отвечает «MCP не вижу». Корень — снапшот каталога (**§10g**):
в `state.db` снапшот содержит только `builtin`. Нужно решение Alex (вариант A или B, §10g).
6. 🔴 Сквозная приёмка: `executor_run` из чата → задача на Mac → результат.
7. 🔴 Логин CLI внутри `balda-executor` — делает **Alex вручную**.
### Что проверено живьём (2026-09-26, конец сессии)
| Проверка | Результат |
|---|---|
| `executor-mcp:8890/mcp` из docker-сети | HTTP 200 + `mcp-session-id` ✅ |
| `obsidian-mcp:3101/sse` из docker-сети | 200 + `text/event-stream` + `event: endpoint` ✅ |
| `executor-mcp:8890/sse` (не тот эндпоинт) | 404 — правильно ✅ |
| `obsidian-mcp:3101/mcp` (не тот эндпоинт) | 400 — правильно ✅ |
| туннель `agents-bridge.qentra.top/healthz` из `mattermost` | 200 через Cloudflare ✅ |
| Балда после правки `type: http` | старт без ошибок, ответ в чате ✅ |
| слова `obsidian`/`executor-mcp` в логе Балды | **0** ❌ → корень в снапшоте (§10g) |
📌 **Метод проверки транспорта:** у каждого сервера ровно один рабочий эндпоинт.
Быстрый тест из `mattermost` (там есть `curl`, в образе Балды — нет):
```bash
docker exec mattermost sh -c 'curl -sS -m 8 -o /dev/null -w "HTTP %{http_code}\n" -H "Accept: text/event-stream" http://obsidian-mcp:3101/sse'
```
## 12. Версии образа