[2026-09-27] eagle: family/plans/music-jellyfin-lidarr-taiga.md personal/plans/balda-mm-phase4-agents.md personal/projects/balda/balda-kraken-mm-deploy.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 02:15:59 +06:00
1 parent 74193b4d08
commit 51189c64f9
3 files changed
+467 -17

No files matched your search

+161 -8
View File
@@ -21,9 +21,9 @@
| 6c | Structured-output память на `deepseek-chat` | ⚠️ open: `dead_letter / model_failure` — память не наполняется, ответы работают. См. §13 |
| 6d | Живая приёмка: Балда отвечает | ✅ **подтверждено 2026-09-26** через `mmctl post list` — бот отвечает, `duplicate tool` ушёл. См. §14 |
| 6e | 🔴 MCP-инструменты НЕ исполняются | ✅ **патч готов 2026-09-27** — форк `mallexxx/runtime` + PR [#4](https://github.com/normahq/runtime/pull/4), `replace` в `go.mod`, образ `balda-mm:0.1.12`. См. §15-16 |
| 6f | 🔴 `duplicate tool` — **КОРЕНЬ НАЙДЕН И ДОКАЗАН** (2026-09-27) | ✅ **корень: двойная паковка в ADK `v2.4.0`, к форку отношения НЕ имеет** — `toolProcessor` расширяет инструменты toolset'ов в `f.Tools`, затем `toolPreprocess(f.Tools)` и `toolsetPreprocess(Toolsets)` пакуют их ДВАЖДЫ → `packTool` падает. См. §16.2 (исправлен) и §18 |
| 6f | 🔴 `duplicate tool` — **КОРЕНЬ НАЙДЕН И ДОКАЗАН** (2026-09-27) | ✅ **корень: Balda регистрирует bundled-сервер ДВАЖДЫ на одном URL** (`balda` + `balda-session-memory-<token>`), `hostedToolsets` строит 2 тулсета с одинаковыми инструментами → `PackTool` падает. ⚠️ Прежняя версия («двойная паковка в ADK через `toolPreprocess`+`toolsetPreprocess`») **ОПРОВЕРГНУТА исполняемым тестом** — `mcptoolset.set` не реализует `ProcessRequest`. См. §19 |
| 6g | A/B-эксперимент откатом на `0.1.11` | ✅ **доведён 2026-09-27** — `0.1.11` падает С ТЕМ ЖЕ `duplicate tool`. Гипотеза §16.2 («форк обнажил») **ОПРОВЕРГНУТА**: баг был всегда, независимо от раунда. См. §16.4 |
| 6h | Фикс двойной паковки | 🔴 **НЕ начат** — решение принято (Option A, §18.5), правка в `runtime-local/agentfactory/agentfactory.go`, ждёт go-ahead Alex. Целевой образ `balda-mm:0.1.13` |
| 6h | Фикс дублирующегося тулсета | 🟡 **КОД НАПИСАН 2026-09-27** — дедуп по эндпоинту в `hostedToolsets` (`runtime-local/agentfactory/agentfactory.go`) + 4 теста в `duplicate_toolset_test.go`. `go build`/`go vet` ✅, тесты **не прогнаны**. Остаётся: тесты → образ `balda-mm:0.1.13` → деплой → приёмка. См. §19.3-19.5 |
| 7 | Приёмка от аккаунта Alex | 🟡 **метод готов и проверен** — `mmctl --local token generate admin` даёт токен владельца без пароля; пост от `admin` проходит авторизацию (`settlement_stage: accepted`, лог подтверждает). Блокер — ход падает на `duplicate tool` (п. 6f/6h). См. §17 |
---
@@ -865,17 +865,25 @@ if _, ok := req.Tools[name]; ok {
кладёт его в `req.Tools` → `toolsetPreprocess` пытается положить его **второй раз** через
MCP-toolset → падение.
🔴 **Это дефект архитектуры ADK, а не Балды и не форка.** Два слоя (tools и toolsets) трактуются
как взаимоисключающие (`if f.Tools != nil { return }`), но `base_flow` вызывает их **безусловно
оба**. `PackTool` идемпотентным не является by design.
🔴 **Этот механизм ОПРОВЕРГНУТ (2026-09-27, см. §19).** `toolsetPreprocess` **не трогает**
MCP-toolsets: `mcptoolset.set` **не реализует** `ProcessRequest` — проверено исполняемым
тестом (`runtime-local/agentfactory/duplicate_toolset_test.go`,
`TestMcpToolsetImplementsRequestProcessor`). В `adk/v2@v2.4.0/tool/mcptoolset/set.go` метод
объявлен **только** у `mcpTool` (`tool.go:88`), а не у `set`. Значит
`if !ok { continue }` в `toolsetPreprocess` пропускает все MCP-тулсеты, и «двойная паковка
через два слоя» невозможна. Единственный паковщик — `toolPreprocess` по `f.Tools`.
Реальный корень — **два тулсета на один и тот же эндпоинт** (§19).
📌 **Формулировка «дефект архитектуры ADK» — тоже неверна.** Два слоя (`Tools` / `Toolsets`)
намеренно взаимоисключающие, и защита работает; ADK-контракт нарушила **регистрация** двух
MCP-серверов с идентичным набором инструментов.
📌 **Почему §16.4-кандидат «`Tools` + `Toolsets` в `llmagent.Config`» — тоже не источник:**
`agentfactory.go:644` строит `toolsets = hostedToolsets(req.Toolsets, resolvedMCP)`, которая
**копирует** `req.Toolsets` и добавляет по одному toolset'у на MCP-сервер. Балда
(`internal/apps/balda/agent/builder.go:296-303`) задаёт **только `MCPServerIDs`** — `Tools` и
`Toolsets` в `BuildRequest` **пустые**. Значит MCP-серверы приходят **исключительно** как
toolsets, и «дубль в двух полях» невозможен. Источник дубля — **`toolProcessor`**, не
`agentfactory`.
toolsets, и «дубль в двух полях» невозможен.
### 16.3 Что исключено фактами (не повторять)
@@ -1032,7 +1040,12 @@ ORDER BY m.date_sent DESC LIMIT 10;"
---
## 18. 🔴 Фикс двойной паковки — план (2026-09-27, ждёт go-ahead Alex)
## 18. 🔴 Фикс двойной паковки — план (2026-09-27)
> ⚠️ **Раздел сохранён как история решения. Диагноз здесь (Option A) ОШИБОЧЕН —
> см. §19 с настоящим корнем и настоящим фиксом.** Option A правил `openAIConstructor`
> под гипотезу «двойная паковка через два слоя», которая опровергнута исполняемым тестом.
> Фикс в итоге сделан в **`hostedToolsets`**, а не в конструкторах.
**Контекст:** корень доказан (§16.2), A/B проведён (§16.4). Фикс **не начат** — правка кода
в форке требует подтверждения Alex (правило: план → апрув → работа).
@@ -1089,6 +1102,146 @@ ORDER BY m.date_sent DESC LIMIT 10;"
---
## 19. ✅🔴 НАСТОЯЩИЙ КОРЕНЬ и ФИКС — два тулсета на один эндпоинт (2026-09-27)
> Сессия после §18. Alex: «Чё за тупые вопросы опять?! Чини блядь!!» → писать код без
> повторных апрувов. Диагноз §16.2/§18 **опровергнут**, найден истинный корень, фикс написан.
### 19.1 Корень — Balda регистрирует bundled-сервер ДВАЖДЫ на одном URL
`internal/apps/balda/internalmcp/manager.go` использует **один и тот же адрес** в двух местах:
```go
// :206 — база для session-scoped брокера (URL эндпоинта + ?token)
m.contextBroker.SetBaseURL("http://" + res.Addr + baldaRoute)
// :213 — регистрация bundled-сервера
m.registry.Set(bundledBaldaServerID, agentconfig.MCPServerConfig{
Type: agentconfig.MCPServerTypeHTTP,
URL: fmt.Sprintf("http://%s%s", res.Addr, baldaRoute),
})
```
`sessionmemorymcp/broker.go`:
- `Bind()` (`:111`) отдаёт `ID: "balda-session-memory-"+token`, а `URL` (через `contextURL`,
`:216-225`) — **тот же эндпоинт** плюс `?<contextQueryParameter>=<token>`.
- `Wrap()` (`:118-153`) — тот же handler, инжектит trusted-заголовки по токену.
`internal/apps/balda/agent/runtime_manager.go:344-345` регистрирует привязку и **добавляет её ID
в `extraMCPServerIDs`**:
```go
registry.Set(binding.ID, binding.Config)
extraMCPServerIDs = append(extraMCPServerIDs, binding.ID)
```
**Следствие:** `hostedToolsets` строит **два MCP-тулсета на один и тот же сервер**. Оба отдают
одинаковый список инструментов (включая `balda.control.shutdown`). `toolProcessor` складывает
оба набора в один плоский `f.Tools`, `toolPreprocess` пакует первый → OK, второй → `PackTool`
(`toolutils.go:47-48`) видит занятое имя → **`duplicate tool`**.
| Факт | Значение |
|---|---|
| Триггер | `session_memory.enabled: true` (создаёт привязку) **и** ход с явным вызовом инструмента |
| Почему на `0.1.11` «тихо» | Балда отвечала текстом → инструменты не пакались → баг не всплывал |
| Почему A/B дал падение | Пост содержал **явное требование вызвать инструмент** |
| Связь с форком | **Никакой** — дефект регистрации в Балде, существовал всегда |
### 19.2 Что опровергнуто исполняемым тестом (не возвращаться)
| Версия | Проверка | Итог |
|---|---|---|
| §16.2: `toolsetPreprocess` пакует MCP-тулсеты второй раз | `TestMcpToolsetImplementsRequestProcessor` → `mcptoolset.set` **не** реализует `ProcessRequest` | ❌ невозможно |
| §16.2: «дефект архитектуры ADK» | Защита `if f.Tools != nil { return }` работает as designed | ❌ контракт нарушила регистрация |
| §18 Option A: править `openAIConstructor`/`aistudioConstructor` | Гипотеза двойной паковки ложна | ❌ фикс не там |
| `hostedToolsets` дублирует `resolvedMCP` | `resolvedMCP` — map по `id`, ключ уникален | ❌ дубля нет (но два **разных** ключа на один URL — да) |
📌 **Метод, который дал ответ:** не чтение ADK, а **исполняемый тест** на присутствие
интерфейса. `go test ./agentfactory/ -run TestMcpToolsetImplementsRequestProcessor -v`
→ `MCP toolset "mcp_tool_set" does NOT implement ProcessRequest`. Час чтения исходников
не дал того, что дал один тест.
### 19.3 Фикс — дедуп тулсетов по эндпоинту (написан 2026-09-27)
**Файл:** `runtime-local/agentfactory/agentfactory.go`, функция `hostedToolsets`.
Идея: два конфига, адресующие **один host+path**, — это один и тот же сервер с точки зрения
пространства имён инструментов. Различие в query-строке (контекст-токен Балды) не должно делать
их разными.
```go
// схлопываем эндпоинты, отличающиеся только query
keeperOf := make(map[string]string, len(ids))
skip := make(map[string]bool, len(ids))
for _, id := range ids {
key := mcpEndpointKey(resolvedMCP[id])
if key == "" { continue }
keeper, ok := keeperOf[key]
if !ok { keeperOf[key] = id; continue }
// оставляем БОГАТУЮ запись: scoped-URL несёт контекст-токен → супернабор
if mcpURLIsScoped(resolvedMCP[id]) && !mcpURLIsScoped(resolvedMCP[keeper]) {
skip[keeper] = true; keeperOf[key] = id; continue
}
skip[id] = true
}
```
Новые хелперы:
- `mcpEndpointKey(cfg)` — `url.Parse`, схема+host+path, **`RawQuery` и `Fragment` обнуляются**.
- `mcpURLIsScoped(cfg)` — есть ли query (признак session-scoped привязки Балды).
🔴 **Критично, какую запись оставлять.** Наивный вариант «первый по алфавиту» оставляет `balda`
и **выбрасывает scoped-привязку** — фикс перестаёт падать, но **ломает session memory**
(теряется контекст-токен). Поэтому keeper — запись **со** scoped-URL: она строгий супернабор
(инжектит заголовки, а без токена `Wrap` просто пропускает запрос через `next.ServeHTTP`).
### 19.4 Тесты (написаны)
`runtime-local/agentfactory/duplicate_toolset_test.go`:
| Тест | Что проверяет |
|---|---|
| `TestHostedToolsetsCollapsesScopedEndpointDuplicate` | 2 записи на один URL → **1** тулсет |
| `TestHostedToolsetsKeepsDistinctEndpoints` | `balda` + `executor-mcp` + `obsidian` → **3** тулсета (защита от сверх-схлопывания) |
| `TestMCPEndpointKey` | `?ctx=tok`, `?a=1&b=2` → ключ без query |
| `TestMcpToolsetImplementsRequestProcessor` | фиксирует факт: `mcptoolset.set` **не** имеет `ProcessRequest` |
### 19.5 Состояние и остаток работ
Гейты на момент записи: `go build ./...` → **OK**, `go vet ./agentfactory/` → **OK**.
Тесты **не прогнаны** — команда ниже.
⚠️ **На Kraken сейчас стоит диагностический образ `0.1.13-diag`** (с временными `fmt.Printf`),
не `0.1.11`. Бэкап compose: `docker-compose.yml.bak-diag-<ts>`. Диагностические логи в
`hostedagent/openai.go` и `internal/apps/balda/agent/builder.go` **откачены** (в дереве чисто).
Остаток:
1. `cd ~/Developer/balda/runtime-local && go test ./agentfactory/ -run 'Toolset|MCPEndpoint' -v`
2. Собрать `balda-mm:0.1.13` (без `-diag`), залить, `docker compose up -d --force-recreate`.
3. Приёмка от владельца с **явным вызовом инструмента** → ждём ответ и **отсутствие**
`duplicate tool` в логе.
4. Вернуть compose на актуальный тег (сейчас `0.1.13-diag`).
5. §0: 6h → ✅; Фаза 3 в [[personal/projects/balda/balda-kraken-mm-deploy]].
### 19.6 🔴 Kraken перегружен — docker-операции идут минутами
Во время сессии `load average` поднимался до **8.03 при 4 ядрах** (нет одного CPU-хога — это
I/O-wait на HDD). Симптомы, которые **не** являются дефектом Балды:
| Симптом | Объяснение |
|---|---|
| `docker compose up -d` не отвечает >5 мин | хост thrashing, не зависание |
| `docker rm -f` → `removal … already in progress` | команда реально идёт, просто медленно |
| Контейнер в статусе `Dead` несколько минут | штатное промежуточное состояние при убитом recreate |
| Старт Балды ~4.5 мин вместо ~1м11с | переигрывание `state.db-wal` (§16.4) |
📌 **Правило:** на Kraken не повторять docker-команды «потому что долго» — это удваивает
нагрузку. Проверять `uptime` и `ps -eo pcpu,pid,comm --sort=-pcpu`.
⚠️ **Прерванный `docker compose up -d --force-recreate` оставляет мусор:** полу-созданный
контейнер `<hash>_balda-mm` + старый в `Dead`. Лечится `docker rm -f` на оба (медленно).
---
## Связанные заметки
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
@@ -4,17 +4,23 @@
> Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex.** Образ `0.1.10` (коммит `9f1e6c9c`,
> он же PR baldaworks/balda#8).
> 🔴 **ТЕКУЩЕЕ СОСТОЯНИЕ (2026-09-27): бот отвечает ТЕКСТОМ, инструменты вызвать НЕЛЬЗЯ.**
> На Kraken стоит `balda-mm:0.1.11`. Любой ход с **явным вызовом инструмента** падает с
> `duplicate tool: "balda.control.shutdown"` — **на `0.1.11` тоже**, не только на `0.1.12`
> На Kraken стоит **диагностический** `balda-mm:0.1.13-diag` (не `0.1.11` — оно подменено в
> ходе сессии). Любой ход с **явным вызовом инструмента** падает с
> `duplicate tool: "balda.control.shutdown"` — на `0.1.11` тоже, не только на `0.1.12`
> (A/B проведён 2026-09-27, лог подтверждён). Обычные текстовые ответы работают — отсюда
> ложное впечатление «рабочего образа».
> 🔴 **Корень (доказан по коду ADK `v2.4.0`):** двойная паковка инструментов —
> `toolProcessor` (`tools_processor.go:40-58`) расширяет инструменты toolset'ов в `f.Tools`,
> затем `base_flow.go` вызывает `toolPreprocess(f.Tools)` **и** `toolsetPreprocess(Toolsets)`
> без защиты между ними → `PackTool` (`toolutils.go:47-48`) видит имя второй раз → падение.
> **Это дефект ADK, не Балды и не форка.** Полный разбор — [[personal/plans/balda-mm-phase4-agents]] §16.2.
> Фикс (Option A, патч форка `runtime-local/agentfactory/agentfactory.go`) спланирован, но
> **не начат** — ждёт go-ahead Alex. Целевой образ `balda-mm:0.1.13`. См. §18 того же дока.
> 🔴 **КОРЕНЬ (исправлено 2026-09-27, проверено кодом):** Balda регистрирует bundled MCP-сервер
> **ДВАЖДЫ на одном URL** — `registry.Set("balda", …)` (`internalmcp/manager.go:213`) и
> session-scoped привязка `balda-session-memory-<token>` (`sessionmemorymcp/broker.go:111`,
> URL = тот же эндпоинт + `?token`), которую `runtime_manager.go:344-345` добавляет в
> `extraMCPServerIDs`. `hostedToolsets` строит 2 тулсета → одинаковые имена инструментов →
> `PackTool` (`toolutils.go:47-48`) падает.
> ⚠️ **Прежняя версия корня («двойная паковка в ADK», `toolPreprocess`+`toolsetPreprocess`)
> ОПРОВЕРГНУТА исполняемым тестом:** `mcptoolset.set` **не реализует** `ProcessRequest`
> (метод есть только у `mcpTool`), поэтому `toolsetPreprocess` пропускает MCP-тулсеты.
> Это дефект **регистрации в Балде**, не ADK и не форка. Разбор — [[personal/plans/balda-mm-phase4-agents]] §19.
> Фикс **написан** (дедуп тулсетов по эндпоинту в `hostedToolsets`), `go build`/`go vet` ✅,
> тесты не прогнаны. Целевой образ `balda-mm:0.1.13`. См. §19.3-19.5 того же дока.
> ⚠️ Собран был **`0.1.12`** (форк runtime с tool-calling для `type: openai`: `openAIToolsFromConfig`,
> `parseChatResponse`, `TurnComplete = !hasToolCalls`; форк `mallexxx/runtime`, ветка
> `feat/openai-tool-calls-v2`, коммит `733b32d`, PR **https://github.com/normahq/runtime/pull/4**;