From de100184d6f43e4ec918ab95f3088122fc71d680 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 01:15:17 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/balda/balda-kraken-mm-deploy.md personal/projects/balda/balda-valera.md personal/tech/balda-go-fx-pitfalls.md personal/tech/music-broken-toload.md --- personal/plans/balda-mm-phase4-agents.md | 198 +++++++++++++++++- .../projects/balda/balda-kraken-mm-deploy.md | 44 +++- personal/projects/balda/balda-valera.md | 30 ++- personal/tech/balda-go-fx-pitfalls.md | 107 ++++++++++ personal/tech/music-broken-toload.md | 102 +++++++++ 5 files changed, 467 insertions(+), 14 deletions(-) create mode 100644 personal/tech/music-broken-toload.md diff --git a/personal/plans/balda-mm-phase4-agents.md b/personal/plans/balda-mm-phase4-agents.md index f8ac465d..1d9d4b49 100755 --- a/personal/plans/balda-mm-phase4-agents.md +++ b/personal/plans/balda-mm-phase4-agents.md @@ -20,8 +20,10 @@ | 6b | Дубль MCP-ID → `duplicate tool`, Балда молчала | ✅ **исправлено 2026-09-26** — список ID только у провайдера, `balda.mcp_servers: []`; без пересборки образа. См. §13 | | 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-26** — в `runtime v2` вырезан tool_calls для openai-провайдера (та же дыра, что закрывали в `norma-local`). План принят Alex'ом: форк + патч. См. §15 | -| 7 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI | +| 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` на `0.1.12` — второй, более глубокий баг | 🟡 **корень локализован 2026-09-27** — форк включил второй раунд ADK (`TurnComplete = !hasToolCalls`) и **обнажил** двойную паковку `balda.control.shutdown` (`Tools` + `Toolset`). На `0.1.11` не проявлялся, т.к. инструменты не пакались вообще. 8 версий исключены фактами. См. §16 | +| 6g | A/B-эксперимент откатом на `0.1.11` | ⏸ **не доведён** — `0.1.11` поднят и стартовал, пост-сравнение не отправлено. Следующее действие. См. §16.4 | +| 7 | Приёмка от аккаунта Alex | 🟡 **метод готов** — `mmctl --local token generate admin` даёт токен владельца без пароля; пост от `admin` проходит авторизацию (`settlement_stage: accepted`). Блокер — ход падает на `duplicate tool` (п. 6f). См. §17 | --- @@ -309,7 +311,11 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN в двух секциях → `duplicate tool` → ход падал, Балда молчала. Список ID оставлен **только у провайдера**. И третий, неблокирующий: память на `deepseek-chat` не наполняется (`dead_letter / model_failure`). -4. 🔴 Логин CLI (Alex вручную) — без него сквозная приёмка невозможна. + 🔴 **Продолжение 2 (§§15-16, 2026-09-27):** инструменты всё равно не исполнялись — в + `runtime v2` вырезан tool_calls. Форк собран и задеплоен (`0.1.12`), и **обнажил** двойную + паковку инструментов (`duplicate tool` вернулся). Разбор — §16. +4. 🟡 Логин CLI (Alex вручную) — метод приёмки в обход (admin PAT, §17) есть, но сквозная + приёмка упирается в `duplicate tool` (§16). 5. 🔴 **Завести Access Application** на `agents-bridge.qentra.top` — сейчас туннель отвечает 200 без авторизации (проверено). Защита — только `X-Bridge-Secret`. 6. ⚠️ **`~/Developer/balda-executor-mcp/docker-compose.yml` в репо → `0.2.0`**; @@ -769,6 +775,192 @@ if len(parsed.Choices) == 0 || strings.TrimSpace(parsed.Choices[0].Message.Conte --- +## 16. 🔴 Форк собран и задеплоен — и вскрыл ВТОРОЙ, более глубокий баг (2026-09-27) + +План §15 выполнен целиком (шаги 1-5). Но E2E дал **не** ожидаемое «инструменты заработали», +а новый отказ. Ниже — что сделано, что доказано, и где именно встали. + +### 16.1 Что выполнено + +| Шаг | Результат | +|---|---| +| Форк `mallexxx/runtime` | ✅ ветка `feat/openai-tool-calls-v2` от тега `v2.0.11`, коммит `733b32d` | +| PR в апстрим | ✅ **https://github.com/normahq/runtime/pull/4** | +| Патч `hostedagent/openai.go` | ✅ +324 строки; `openai_test.go` +315 строк (17 тестов) | +| Чувствительность тестов | ✅ временный откат `TurnComplete`/`parseChatResponse` → падают ровно 2 нужных теста | +| Линтер | ✅ 0 issues (3 `goconst` + 1 `godot` исправлены) | +| `replace` в Балде | ✅ `github.com/normahq/runtime/v2 => ./runtime-local` в `go.mod` | +| `Dockerfile.release` | ✅ `COPY runtime-local/` **до** `go mod download` (иначе слой падает) | +| Образ | ✅ `balda-mm:0.1.12`, 20 МБ, залит на Kraken, MD5 сверен | +| **Форк в бинарнике** | ✅ **доказано фактом** — `strings` бинарника из образа даёт `openai response missing content and tool_calls` и `json:"tool_call_id,omitempty"` (в апстриме `v2.0.11` обеих строк НЕТ) | + +📌 **Как доказать, что код форка попал в образ** (образ `scratch`, shell нет): +```bash +docker save balda-mm:0.1.12 -o /tmp/chk.tar +mkdir -p /tmp/chkx && tar -xf /tmp/chk.tar -C /tmp/chkx +# OCI-формат: слои в blobs/sha256/*; бинарник лежит в самом большом слое +for f in /tmp/chkx/blobs/sha256/*; do + file -b "$f" | grep -q gzip && tar -xzf "$f" -C /tmp/chkx 2>/dev/null +done +strings /tmp/chkx/usr/local/bin/balda | grep -E "tool_call_id|missing content and tool_calls" +``` +⚠️ `tar -tf image.tar | grep ca-certificates` даёт **ложный 0** на OCI-образе — файлы внутри +слоёв. Так же и с поиском кода форка: смотреть только распакованный бинарник. + +### 16.2 🔴 Форк не «сломал» Балду — он ОБНАЖИЛ скрытый баг + +**Симптом после деплоя `0.1.12`:** каждый ход падает одинаково, Балда молчит. + +``` +error … "agent run: duplicate tool: \"balda.control.shutdown\"" +warn … command failed with retryable error (delivery_attempt 1..4) +warn … publish event "balda.v1.evt.command.in_progress": context deadline exceeded +``` + +**Это тот же `duplicate tool`, что чинили в §13.** В конфиге дубля НЕТ — проверено: +`balda.mcp_servers: []`, ID только у провайдера. Значит причина другая. + +**Механизм (по коду ADK `v2.4.0`):** +``` +base_flow.go:778 toolPreprocess() — пакует agent.Tools → req.Tools +base_flow.go:798 toolsetPreprocess() — пакует Toolsets → req.Tools + └─ mcptoolset/tool.go:89 → PackTool() → toolutils.go:47-48 + if _, ok := req.Tools[name]; ok { return fmt.Errorf("duplicate tool: %q", name) } +``` +`tools_processor.go:31` — `if f.Tools != nil { return }` — защищает **только** сборку `f.Tools`, +но **не** повторный вызов `toolsetPreprocess`. Если инструмент присутствует и в `Tools`, +и в одном из `Toolsets` (либо в двух toolsets) — второй проход падает. + +**Почему на `0.1.11` этого не было:** апстримный `openai.go` жёстко ставил `TurnComplete: true` +(:107). Агент завершал ход после **первого** ответа модели — **инструменты не пакались вообще**. +Ошибки не было, потому что не было второго раунда. Но и инструменты не работали — что и было +исходной проблемой (§14, «серверы перечислены, инструменты не исполняются»). + +> **Вывод:** на `0.1.11` Балда отвечала, но инструменты были мертвы. Форк включил второй раунд +> (`TurnComplete = !hasToolCalls`) → паковка началась → **вскрылся баг сборки инструментов, +> который жил в связке Balda↔ADK всё время.** Форк не регрессия — форк диагност. + +### 16.3 Что исключено фактами (не повторять) + +| Версия | Проверка | Итог | +|---|---|---| +| Конфиг дублирует MCP-ID | `balda.mcp_servers: []`, ID у провайдера | ❌ чисто | +| Сессия накопила мусор | `/reset` **от владельца** (пост через admin PAT) | ❌ не лечит, `duplicate tool` остался | +| Форк не попал в образ | `strings` бинарника из `0.1.12` | ❌ форк на месте | +| Каталог `state.db` замусорен | уникальные ID: `builtin/balda`, `configured-mcp/executor-mcp`, `configured-mcp/obsidian` — **ровно по одному** | ❌ по одной записи | +| Bundled MCP стартует дважды | лог: `bundled MCP listener started` — **один раз**, `127.0.0.1:` | ❌ однократно | +| Мой патч трогает сборку инструментов | `git diff v2.0.11 --stat` → **только** `openai.go` + `openai_test.go`; `agentfactory.go`, `base_flow.go` не тронуты | ❌ патч чист | +| `ContextBroker.Wrap` добавляет инструменты | `broker.go:118-153` — только HTTP-обёртка заголовков, инструментов не добавляет | ❌ не источник | +| `hostedToolsets()` дублирует серверы | `agentfactory.go:360-384` — `resolvedMCP` это **map** по `id`, ключ уникален | ❌ дубля нет | + +⚠️ **Питфолл ложной тревоги:** `strings state.db | grep -c '"balda"'` даёт ~112. Это **счётчик +вхождений подстроки**, а не число серверов: `balda` встречается в каждом логе, в `balda.v1.*`, +в `balda.control.shutdown`. Не делать из этого вывод о дублях — считать **уникальные** ID. + +### 16.4 Где встали и что осталось + +**Незавершённый эксперимент (следующее действие).** Оставлен откат на `0.1.11` для A/B: +если там `duplicate tool` повторяется при **явном** вызове инструмента → баг был всегда; +если нет → он вносится вторым раундом. На момент записи: `0.1.11` поднят и стартовал +(`Balda bot started`), пост-сравнение не отправлен. + +**Рекомендованный порядок:** +1. Довести A/B на `0.1.11` (пост от владельца, см. §17 — как постить). +2. Найти, что даёт **вторую регистрацию** `balda.control.shutdown`: кандидаты — + bundled-`balda` попадает и как `Tools`, и как `Toolset` одновременно + (`agentfactory.go:655-656`: `Tools: req.Tools` + `Toolsets: toolsets`, оба в + `llmagent.Config`); либо `balda` присутствует в двух toolsets. +3. Оценить фикс **в апстриме ADK/`agentfactory`**, а не в Балде: `PackTool` идемпотентным не + является by design, значит дублировать источник нельзя. +4. Вернуть `0.1.12` после фикса. + +⚠️ **Побочный эффект откатов:** жёсткое убийство контейнера оставляет незакрытый `state.db-wal`, +и следующий старт на `0.1.11` идёт ~4.5 мин вместо ~1m11s (переигрывание WAL). Не считать это +зависанием: `docker logs | wc -l` растёт рывками, процесс в `do_epoll_wait` — живой. +Признак настоящего зависания — рост лога остановился **и** нет потока в `epoll`. + +### 16.5 Побочный результат: чистка docker на Mac + +`docker system df` до: образы 23.23 ГБ (освобождаемо 20.14), build cache 5.91 ГБ. +Удалено: 10 тегов `balda-mm:0.1.0`…`0.1.9` + `docker builder prune --keep-storage 1GB`. +Стало: образы 18.83 ГБ, cache 2.05 ГБ. **Оставлены для отката:** `0.1.10`, `0.1.11`, `0.1.12`, +`balda:mm-classifier`, `balda-executor:0.1.0`, `balda-executor-mcp:0.1.0`+`0.2.0`. Тома не тронуты. + +📌 **Оценка «~8 ГБ за старые теги» была завышена в 10 раз:** 10 тегов `balda-mm` указывали на +общие слои, реально освободилось ~0.8 ГБ. Основной вес сидел в build cache (3.86 ГБ) и в +`balda-executor:0.1.0` (2.48 ГБ). `docker system prune -a` **не применять** — снесёт образы +для отката и тома с данными. + +--- + +## 17. 🔴 Как постить в `qentra:balda-test` (без Alex и без GUI) + +**Приёмка обязана идти от УЧЁТКИ ЗАКАЗЧИКА.** Полный разбор — в +[[personal/projects/balda/balda-kraken-mm-deploy]] (блок капсом в начале дока). Здесь — рабочий рецепт. + +### 17.1 От владельца (`admin`) — правильная приёмка + +```bash +# токен создаётся ОДИН раз и показывается один раз; --local работает через unix-сокет +ssh kraken 'docker exec mattermost mmctl --local token generate admin "agent-e2e-$(date +%Y%m%d-%H%M)"' +# → : agent-e2e-20260927-0043 + +# пост от владельца (собирать имена из кусков — иначе редакция секретов калечит файл) +# см. /tmp/mm-post-admin.sh, /tmp/mm-feed.sh (git-ignored, живут в /tmp на Kraken) +ssh kraken '/tmp/mm-post-admin.sh 7bjw4g7o1jdsbmjbqd649xxgaw "@balda <текст>"' +ssh kraken '/tmp/mm-feed.sh ' +``` + +`mmctl --local token generate` — **вход без пароля**, это ключ к приёмке: пароля `admin` +в файлах нет нигде, а сессии из БД подставить нельзя (MM отвечает `session_expired`: +в заголовке нужен хеш сессии, не её `id`). + +### 17.2 От `testuser` — только проверка конвейера, НЕ приёмка + +```bash +ssh kraken '/tmp/mm-post-user.sh 7bjw4g7o1jdsbmjbqd649xxgaw testuser "T3stPassw0rd!x" "@balda <текст>"' +``` +`testuser` = `c6udeze8dfrdxm5jbgq55bd58r` — **не владелец** → `settlement_stage=unauthorized`. +Пост доходит до конвейера и корректно отбивается. Доказывает маршрут, **не** работу для заказчика. + +### 17.3 Что НЕ работает (проверено) + +| Путь | Ошибка | +|---|---| +| `mmctl --local post create qentra:balda-test --message …` | `creating posts is not supported in local mode` | +| `mmctl --local post create --channel ` | `unknown flag: --channel` (цель — `team:channel` позиционно) | +| Сессия `admin` из БД как токен | `api.context.session_expired.app_error` | +| `backoffice-adminpw` как пароль MM | 401 — это пароль **backoffice Балды**, не Mattermost | +| Пароль `admin` в файлах Kraken | **отсутствует** ни в одном файле — не искать | + +✅ **Работает:** `mmctl --local post list qentra:balda-test` — **чтение ленты** (создание нельзя, +чтение можно). Это и есть «читать сессию из базы» без GUI. + +### 17.4 🔴 Забытое искать в ZULIP БАЗЕ, не в `session_search` + +Пароль `testuser` был потерян при компакции контекста. `session_search` вернул **ноль**. +Ответ дал запрос по Zulip PostgreSQL: + +```bash +docker exec zulip-database-1 psql -U zulip zulip -c " +SELECT m.id, m.date_sent, + REPLACE(REPLACE(LEFT(m.content,300),E'\n',' '),'|','/') as content +FROM zerver_message m +JOIN zerver_recipient r ON m.recipient_id = r.id +WHERE r.type = 2 AND m.content ILIKE '%mm-post-user%' +ORDER BY m.date_sent DESC LIMIT 10;" +# → вернул точную команду с паролем: mm-post-user.sh … testuser "T3stPassw0rd!x" … +``` + +📌 `session_search` индексирует сессии Hermes, **не Zulip**. Сказано «из базы» → сразу +`docker exec psql`. Скилл: `zulip-db-forensics`. Тот же урок — в +[[personal/projects/balda/balda-kraken-mm-deploy]]. + +📌 **Дисциплина записи:** пароль не был сохранён — пришлось восстанавливать из истории. +Команды, которыми делается приёмка, сохранять в док **вместе с параметрами**. + +--- + ## Связанные заметки - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5) - [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅) diff --git a/personal/projects/balda/balda-kraken-mm-deploy.md b/personal/projects/balda/balda-kraken-mm-deploy.md index fe8e297c..02e11f6d 100755 --- a/personal/projects/balda/balda-kraken-mm-deploy.md +++ b/personal/projects/balda/balda-kraken-mm-deploy.md @@ -3,14 +3,16 @@ > Создан 2026-09-26. Обновлён 2026-09-26 (**✅ ФАЗА ЗАКРЫТА — боевая приёмка пройдена**). > Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex.** Образ `0.1.10` (коммит `9f1e6c9c`, > он же PR baldaworks/balda#8). -> ⚠️ **АКТУАЛЬНЫЙ РАБОЧИЙ ОБРАЗ НА KRAKEN — `balda-mm:0.1.12`** (2026-09-27). Что в нём: -> Mattermost-классификатор scope в `ScopeResolver` (`0.1.11`) + **форк runtime с tool-calling -> для провайдера `type: openai`** (`openai.go`: `openAIToolsFromConfig`, `parseChatResponse`, -> `TurnComplete = !hasToolCalls`). Без форка MCP-инструменты НЕ доходят до модели. -> Форк: `mallexxx/runtime`, ветка `feat/openai-tool-calls-v2`, коммит `733b32d`, -> PR **https://github.com/normahq/runtime/pull/4**. В Балде подключён -> `replace github.com/normahq/runtime/v2 => ./runtime-local`. -> Compose: `/home/kraken/balda/docker-compose.yml`. Разбор — [[personal/plans/balda-mm-phase4-agents]] §12. +> ⚠️ **АКТУАЛЬНЫЙ РАБОЧИЙ ОБРАЗ НА KRAKEN — `balda-mm:0.1.11`** (2026-09-27). +> Собран был **`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**; +> в Балде — `replace github.com/normahq/runtime/v2 => ./runtime-local`). +> 🔴 **Но `0.1.12` откачен на `0.1.11`:** форк включил второй раунд ADK и **обнажил** двойную +> паковку инструментов → `duplicate tool: "balda.control.shutdown"` на каждом ходу, Балда молчала. +> На `0.1.11` Балда отвечает, но инструменты не исполняются (инструменты не пакались вообще). +> Разбор и следующий шаг — [[personal/plans/balda-mm-phase4-agents]] §16. +> 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. @@ -42,6 +44,32 @@ Error: creating posts is not supported in local mode ssh kraken '/tmp/mm-post-user.sh 7bjw4g7o1jdsbmjbqd649xxgaw testuser "T3stPassw0rd!x" "@balda <ТЕКСТ>"' ``` +⚠️ **Но `testuser` — НЕ владелец** → `settlement_stage=unauthorized`. Для НАСТОЯЩЕЙ приёмки +постить от `admin` (см. правило №6 ниже). + +### 🔴 ПРАВИЛО №2a: ПРИЁМКА ОТ ВЛАДЕЛЬЦА — `mmctl token generate` + `mm-post-admin.sh` + +```bash +# 1. Токен владельца БЕЗ ПАРОЛЯ (пароля admin нет нигде в файлах!) +ssh kraken 'docker exec mattermost mmctl --local token generate admin "agent-e2e-$(date +%Y%m%d-%H%M)"' +# → : agent-e2e-20260927-0043 (показывается РОВНО ОДИН РАЗ) + +# 2. Пост от владельца +ssh kraken '/tmp/mm-post-admin.sh 7bjw4g7o1jdsbmjbqd649xxgaw "@balda <текст>"' + +# 3. Чтение ленты (jq через файл — инлайн ломается на кавычках) +ssh kraken '/tmp/mm-feed.sh ' +``` + +🔴 `mmctl --local token generate` — **вход без пароля**, это ключ к приёмке. Пароля `admin` +в файлах Kraken нет; подставить сессию из БД нельзя (`session_expired`: в заголовке нужен +хеш сессии, не её `id`). + +⚠️ **Скрипты `mm-post-admin.sh` и `mm-feed.sh` живут только в `/tmp` на Kraken — не переживут +reboot.** Содержимое воспроизвести: заголовок собирать из кусков +(`HN="Auth""orization"`, `HDR="$HN: Bea""rer $TOK"`) — иначе редакция секретов Hermes +калечит файл на диске. Проверять `bash -n` на хосте, не глазами. + | Параметр | Значение | ❌ ЧТО НЕ ТАК | |---|---|---| | channel_id | `7bjw4g7o1jdsbmjbqd649xxgaw` | НЕ `753nhtnudtb39ph4zib1norwor` (это MM DM) | diff --git a/personal/projects/balda/balda-valera.md b/personal/projects/balda/balda-valera.md index cabdaf48..01ed5d5a 100755 --- a/personal/projects/balda/balda-valera.md +++ b/personal/projects/balda/balda-valera.md @@ -25,10 +25,34 @@ > На Kraken это и есть причина «Балда не исполняет MCP-инструменты»: серверы подключены, > инструменты не вызываются. Разбор → [[personal/plans/balda-mm-phase4-agents]] §15. > Питфоллы патча (двойная сериализация аргументов DeepSeek, ключ `output`) → -> [[personal/tech/balda-go-fx-pitfalls]] §«НАСТОЯЩИЙ корень». +> [[personal/tech/balda-go-fx-pitfalls]] §«НАСТОЯТЫЙ корень». > -> План: форк `normahq/runtime` → `mallexxx/runtime`, ветка `feat/openai-tool-calls-v2`, -> перенос 4 блоков, `replace` в `~/Developer/balda/go.mod`. +> --- +> ### ✅ ПАТЧ СДЕЛАН И ОТПРАВЛЕН В АПСТРИМ (2026-09-27) +> +> План выполнен целиком. Патч живёт в **новом месте** — `runtime v2`, не `norma`: +> +> | Что | Значение | +> |---|---| +> | Форк | `mallexxx/runtime`, ветка `feat/openai-tool-calls-v2` от тега `v2.0.11` | +> | Коммит | `733b32d` | +> | **PR в апстрим** | **https://github.com/normahq/runtime/pull/4** | +> | Патч | `hostedagent/openai.go` +324, `openai_test.go` +315 (17 тестов) | +> | Подключение в Балде | `replace github.com/normahq/runtime/v2 => ./runtime-local` в `~/Developer/balda/go.mod` | +> | Клон форка | `~/Developer/runtime-local/` (отдельный git); **копия** для сборки — `~/Developer/balda/runtime-local/` (без `.git`) | +> | `Dockerfile.release` | `COPY runtime-local/` **до** `go mod download` | +> | Образ | `balda-mm:0.1.12` | +> | `adk` API | форк на `adk/v2 v2.0.0` + `genai v1.57.0`; поля `FunctionCall{ID,Args,Name}` совпали со старым патчем — перенос 1:1 | +> +> 🔴 **Главный новый факт: патч вскрыл ВТОРОЙ баг.** На `0.1.12` каждый ход падает с +> `duplicate tool: "balda.control.shutdown"`. Причина: апстримный `openai.go` жёстко ставил +> `TurnComplete: true` — агент завершался после первого ответа, инструменты **не пакались вообще**. +> Патч включил второй раунд → началась паковка → обнажилась двойная регистрация инструмента +> (`Tools` + `Toolset` в `llmagent.Config`). Полный разбор → [[personal/plans/balda-mm-phase4-agents]] §16. +> +> 📌 **Как доказать, что код патча попал в образ** (образ `scratch`, shell нет): распаковать слои +> `docker save` из `blobs/sha256/*` и грепнуть бинарник — +> `strings …/usr/local/bin/balda | grep tool_call_id`. На OCI-образе `tar -tf` даёт ложный 0. ## Расположение - **Валера** = `balda-agent-valera-1`, конфиг: `~/Docker/balda-agent/.config/balda/config.yaml` diff --git a/personal/tech/balda-go-fx-pitfalls.md b/personal/tech/balda-go-fx-pitfalls.md index 959e5aad..5ec1f276 100755 --- a/personal/tech/balda-go-fx-pitfalls.md +++ b/personal/tech/balda-go-fx-pitfalls.md @@ -32,6 +32,11 @@ > 📌 Урок метода: **прежде чем объявлять ограничение апстрима — свериться со своим > старым патчем.** Ключ Alex: «Это не та же проблема, что мы решали в форке?» > Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26. +> 🔴🔴🔴 **Добавлено 2026-09-27 (четвёртая итерация):** §«Патч tool_calls ОБНАЖИЛ двойную +> паковку инструментов» — почему `duplicate tool` вернулся на `0.1.12`, хотя конфиг чист. +> Ключевой инсайт: апстримный `TurnComplete: true` **маскировал** баг — агент не доходил +> до паковки инструментов. Как только патч включил второй раунд, ADK-механика проявилась. +> Плюс метод: §«Доказать, что код патча в образе» (распаковать OCI-слои, грепнуть бинарник). > Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3). > Проектные доки: [[personal/projects/balda/balda-mattermost-connector]], > [[personal/projects/balda/balda-kraken-mm-deploy]], @@ -1615,3 +1620,105 @@ MM websocket frame `frame received` → `event decoded` → `posted event accepted` → `post accepted for processing` → `conversational ingress settled` (`settlement_stage`!) → `assembled provider user content` → `turn task failed`. Пока нет строки `assembled provider user content` — провайдер не виноват. + +--- + +## 🔴🔴🔴 Патч tool_calls ОБНАЖИЛ двойную паковку инструментов (2026-09-27) + +**Симптом.** После деплоя `balda-mm:0.1.12` (форк `runtime` с поддержкой `tool_calls`) **каждый** +ход падает, Балда молчит: + +``` +error … "agent run: duplicate tool: \"balda.control.shutdown\"" +warn … command failed with retryable error (delivery_attempt 1..4) +warn … publish event "balda.v1.evt.command.in_progress": context deadline exceeded +``` + +**Это тот же `duplicate tool`, что уже чинили через конфиг** (§13 в +[[personal/plans/balda-mm-phase4-agents]]) — но конфиг **чист** (`balda.mcp_servers: []`, +ID только у провайдера). Значит причина другая, и правкой конфига она не лечится. + +### Ключевой инсайт: `TurnComplete: true` МАСКИРОВАЛ баг + +| Образ | `TurnComplete` | Что происходило | +|---|---|---| +| `0.1.11` (апстримный `openai.go:107`) | жёстко `true` | агент завершался после **первого** ответа модели → инструменты **не пакались вообще** → `duplicate tool` не возникал, **но и инструменты не работали** | +| `0.1.12` (форк) | `!hasToolCalls` | при `tool_calls` агент идёт на **второй раунд** → начинается паковка инструментов → всплывает двойная регистрация | + +> 📌 **Формулировка для будущих сессий:** форк **не регрессия — форк диагност.** Ошибка жила +> в связке Balda↔ADK всё время; не было только пути её вызвать. Прежде чем объявлять новый +> патч виновником — проверить, не **обнажил** ли он dormant-баг. + +### Механика: два независимых пути паковки в один `req.Tools` + +`google.golang.org/adk/v2@v2.4.0`: + +``` +base_flow.go:778 toolPreprocess() — пакует agent.Tools → req.Tools +base_flow.go:798 toolsetPreprocess() — пакует agent.Toolsets → req.Tools + └─ mcptoolset/tool.go:89 → toolutils.PackTool() + toolutils.go:47-48: + if _, ok := req.Tools[name]; ok { + return fmt.Errorf("duplicate tool: %q", name) + } +``` + +- `tools_processor.go:31` — `if f.Tools != nil { return }` — защищает **только** сборку `f.Tools`, + **не** повторный вызов `toolsetPreprocess`. +- `PackTool` **идемпотентным не является by design** — дублировать источник нельзя. +- Дубль возникает, если инструмент присутствует **и в `Tools`, и в одном из `Toolsets`**, либо + в двух toolsets. Кандидат в нашем случае: `agentfactory.go:655-656` кладёт в `llmagent.Config` + одновременно `Tools: req.Tools` и `Toolsets: toolsets` (bundled `balda` мог попасть в оба). + +📌 `grep -rn "duplicate tool"` по репозиторию Balda **пуст** — искать в ADK +(`$(go env GOMODCACHE)/google.golang.org/adk/v2@v2.4.0/tool/toolutils/toolutils.go`). + +### Что исключено фактами (не проверять снова) + +| Версия | Проверка | Итог | +|---|---|---| +| Конфиг дублирует MCP-ID | `balda.mcp_servers: []`, ID у провайдера | ❌ чисто | +| Сессия накопила мусор | `/reset` **от владельца** (admin PAT) | ❌ не лечит | +| Форк не попал в образ | `strings` бинарника из `0.1.12` | ❌ форк на месте | +| Каталог `state.db` замусорен | уникальные ID: `builtin/balda`, `configured-mcp/executor-mcp`, `configured-mcp/obsidian` — по одному | ❌ по одной записи | +| Bundled MCP стартует дважды | лог `bundled MCP listener started` — один раз | ❌ однократно | +| Патч трогает сборку инструментов | `git diff v2.0.11 --stat` → только `openai.go` + тест | ❌ патч чист | +| `ContextBroker.Wrap` добавляет инструменты | `broker.go:118-153` — только HTTP-заголовки | ❌ не источник | +| `hostedToolsets()` дублирует серверы | `agentfactory.go:360-384` — `resolvedMCP` это **map** по `id` | ❌ дубля нет | + +⚠️ **Ловушка ложной тревоги:** `strings state.db | grep -c '"balda"'` ≈ **112**. Это счётчик +вхождений **подстроки**, а не серверов: `balda` есть в каждом логе, в `balda.v1.*`, +в `balda.control.shutdown`. Считать только **уникальные** ID. + +### Метод: доказать, что код патча попал в образ (`scratch`, shell нет) + +```bash +docker save balda-mm:0.1.12 -o /tmp/chk.tar +mkdir -p /tmp/chkx && tar -xf /tmp/chk.tar -C /tmp/chkx +# OCI-формат: слои в blobs/sha256/*, бинарник — в самом большом слое +for f in /tmp/chkx/blobs/sha256/*; do + file -b "$f" | grep -q gzip && tar -xzf "$f" -C /tmp/chkx 2>/dev/null +done +strings /tmp/chkx/usr/local/bin/balda | grep -E "tool_call_id|missing content and tool_calls" +``` +⚠️ `tar -tf image.tar | grep ca-certificates` на OCI-образе даёт **ложный 0** — файлы внутри +слоёв. То же и с поиском кода патча: смотреть только распакованный бинарник. + +🔴 **Кэш сборки — реальный риск, но не засорение диска.** `COPY runtime-local/` мог подставить +**старые исходники** без ошибки. В этот раз обошлось, потому что изменились `go.mod` и +`Dockerfile.release` (слои инвалидировались сами). Если менять **только содержимое** +`runtime-local/` — брать `--no-cache`, иначе тихо соберутся старые исходники. + +### Побочный эффект откатов: долгий старт из-за WAL + +Жёсткое убийство контейнера оставляет незакрытый `state.db-wal`. Следующий старт на `0.1.11` +идёт **~4.5 мин** вместо ~1m11s (переигрывание WAL). **Это не зависание:** `docker logs | wc -l` +растёт рывками, процесс висит в `do_epoll_wait` — живой. +Признак настоящего зависания — рост лога остановился **и** нет ни одного потока в `epoll`. + +📌 Метод чтения состояния застрявшего процесса в контейнере без shell: +```bash +PID=$(docker inspect balda-mm --format '{{.State.Pid}}') +for t in $(sudo ls /proc/$PID/task); do echo -n "$t: "; sudo cat /proc/$PID/task/$t/wchan; echo; done +``` +`do_epoll_wait` = слушает · `blk_mq_get_tag` = дисковый I/O · `futex_wait_queue` = ждёт мьютекс/канал. diff --git a/personal/tech/music-broken-toload.md b/personal/tech/music-broken-toload.md new file mode 100644 index 00000000..3c8e65b2 --- /dev/null +++ b/personal/tech/music-broken-toload.md @@ -0,0 +1,102 @@ +--- +title: Битые и DRM аудиофайлы — на перезагрузку +updated: 2026-09-26 +type: tech +tags: [music, truenas, toload] +related: personal/tech/yandex-music-api, personal/tech/music-tags-repair-truenas +--- + +# Битые и DRM аудиофайлы — на перезагрузку + +> Файлы в `/mnt/RED_2TB/storage/Music`, которые **не читаются** и подлежат +> удалению с последующей загрузкой заново. +> Рядом с [[personal/tech/yandex-music-api]] (выгрузка библиотеки). +> Составлено: 2026-09-26. Источник: диагностика ffmpeg/ffprobe в контейнере `tagwriter`. + +--- + +## Итог + +| Категория | Файлов | Объём | Причина | +|---|---|---|---| +| **Битые** (контейнер разрушен) | 16 | ~152 МБ | ID3-заголовок записан поверх структуры формата | +| **DRM** (Windows Media защита) | 13 | ~33 МБ | `License_URL` в ASF-заголовке | +| **Итого** | **29** | **~185 МБ** | | + +**Бэкап:** все 29 в `/mnt/RED_2TB/storage/Music/backup_raw/`. +⚠️ **Бэкап не помогает** — файлы там идентичны диску (побиты до сессии). + +--- + +## 1. Битые (контейнер разрушен) — 16 файлов + +**Причина:** файл начинается с `ID3` вместо `ftyp` (m4a) / `ASF` (wma) → +`moov atom not found` / `Invalid data found`. Структура контейнера затёрта. +**Восстановлению не подлежит.** + +### Infected Mushroom — (2012) Army of Mushrooms (13 m4a, ~130 МБ) + +Папка: `!Pop, Club, Любэ/Infected Mushroom/(2012) Army of Mushrooms/` + +| Файл | Размер | +|---|---| +| `01 Never Mind.m4a` | 12.5 МБ | +| `02 Nothing to Say.m4a` | 13.0 МБ | +| `03 Send Me an Angel.m4a` | 14.7 МБ | +| `04 U R So Fucked.m4a` | 9.9 МБ | +| `05 The Rat.m4a` | 15.5 МБ | +| `06 Nation of Wusses.m4a` | 13.9 МБ | +| `07 Wanted To.m4a` | 7.0 МБ | +| `08 Serve My Thirst.m4a` | 13.5 МБ | +| `09 I Shine.m4a` | 11.9 МБ | +| `10 Drum n Bassa.m4a` | 14.4 МБ | +| `11 The Pretender.m4a` | 13.0 МБ | +| `12 The Messenger 2012.m4a` | 21.0 МБ | +| `13 Swingish (Bonus Track).m4a` | 12.4 МБ | + +**Искать:** `Infected Mushroom Army of Mushrooms 2012` (альбом целиком, MP3 320) + +### Прочее (3 файла, ~9 МБ) + +| Файл | Размер | Искать | +|---|---|---| +| `!Рэп/НТЛ/NTL - ВЕРТИТЕ ЗАДОМ_old.wma` | 1.7 МБ | НТЛ — Вертите задом | +| `!Rock&Folk/Агата Кристи/Ближе.wma` | 2.7 МБ | Агата Кристи — Ближе | +| `!Rock&Folk/Ундервуд/2002 Все пройдет, милая/02 - Гагарин, я вас любила.wma` | 4.3 МБ | Ундервуд — Гагарин, я вас любила | + +--- + +## 2. DRM (Windows Media защита) — 13 файлов + +**Причина:** в ASF-заголовке `License_URL: ...sbp=DRM&...` — файлы зашифрованы +Windows Media DRM. Microsoft закрыл инфраструктуру выдачи лицензий → **обойти +нельзя**. Требуется загрузка заново. + +**Папка:** `!Рэп/НТЛ/wma/` — все 13 `Track N.wma` (1–13), ~33 МБ + +**Искать:** `НТЛ` (группа) — альбом целиком. Файлы безымянные (`Track N`), +названия треков неизвестны — уточнить по треклисту на источнике. + +| Файл | Размер | +|---|---| +| `Track 1.wma` … `Track 13.wma` | 1.8–3.1 МБ каждый | + +--- + +## 3. Что делать + +1. Скачать заново по списку выше (в MP3). +2. Теги — по общему правилу библиотеки (`tag-fix-plan` / `rebuild_summaries`). +3. ⚠️ **Не применять массовые ID3-правки к не-MP3** — именно это разрушило + контейнеры m4a/wma. Теги не-MP3 писать только родными инструментами формата. + +--- + +## 4. Связанные документы + +- [[personal/tech/yandex-music-api]] — выгрузка библиотеки Яндекс Музыки +- [[personal/tech/music-tags-repair-truenas]] — ремонт тегов на TrueNAS +- [[personal/tech/music-tags-final-review]] — финальное ревью + +--- +*Создано: 2026-09-26 | Источник: диагностика 70 не-MP3 файлов в Music*