[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

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 01:15:17 +06:00
1 parent 586d9e8ffd
commit de100184d6
5 files changed
+467 -14

No files matched your search

+107
View File
@@ -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` = ждёт мьютекс/канал.