[2026-09-27] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/balda/balda-kraken-mm-deploy.md personal/tech/adk-hostedagent-openai-mcp.md
This commit is contained in:
1 parent
0459fecd6b
commit
982ebb60e7
3 files changed
+244
-15
No files matched your search
@@ -23,8 +23,8 @@
|
||||
| 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) | ✅ **корень: 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 | Фикс дублирующегося тулсета | 🟡 **КОД НАПИСАН 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 |
|
||||
| 6h | Фикс дублирующегося тулсета | ✅ **ИСПРАВЛЕНО 2026-09-27, `balda-mm:0.1.13` задеплоен** — дедуп по эндпоинту в `hostedToolsets` (`runtime-local/agentfactory/agentfactory.go`) + 6 тестов. `go build`/`go vet`/`go test ./...` ✅. Приёмка от владельца: `@balda вызови executor_health` → ответ `**executor_health:** ok`, `turn_complete: true`, `duplicate tool:` = **0**, deadletter = **0**. См. §19.3-19.5 |
|
||||
| 7 | Приёмка от аккаунта Alex | ✅ **ПРОЙДЕНА 2026-09-27** — пост владельца `cbwsorzwoigbtrdnteugnx4aqo` → ответ бота `edxrgupoxiyig5gzmr79tjypbme`: `**executor_health:** ok`. `settlement_stage: accepted`, `turn_complete: true`, инструмент исполнился. См. §17, §19.5 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1205,7 +1205,37 @@ for _, id := range ids {
|
||||
| `TestMCPEndpointKey` | `?ctx=tok`, `?a=1&b=2` → ключ без query |
|
||||
| `TestMcpToolsetImplementsRequestProcessor` | фиксирует факт: `mcptoolset.set` **не** имеет `ProcessRequest` |
|
||||
|
||||
### 19.5 Состояние и остаток работ
|
||||
### 19.5 ✅ ПРИЁМКА ПРОЙДЕНА — `balda-mm:0.1.13` (2026-09-27)
|
||||
|
||||
Гейты: `go build ./...` ✅ · `go vet ./...` ✅ · `go test ./...` форка ✅ ·
|
||||
`go test ./agentfactory/ -run 'Toolset|MCPEndpoint|McpToolset' -v` → **6/6 PASS**.
|
||||
|
||||
Деплой: compose `image: balda-mm:0.1.13` (бэкап `docker-compose.yml.bak-013-<ts>`),
|
||||
`docker compose up -d --force-recreate`, старт OK (`Balda bot started`).
|
||||
|
||||
**Приёмка от владельца** (`@balda вызови executor_health и покажи результат`):
|
||||
|
||||
```
|
||||
1790454574509 | cbwsorzwoigb | @balda вызови executor_health и покажи результат
|
||||
1790454694404 | edxrgupoxiyg | **executor_health:** `ok`
|
||||
```
|
||||
|
||||
Метрики нового контейнера:
|
||||
|
||||
| Проверка | Значение |
|
||||
|---|---|
|
||||
| `duplicate tool:` (ошибка) | **0** ✅ |
|
||||
| `turn task failed` / `command deadlettered` | **0** ✅ |
|
||||
| `turn_complete` / `final_response` | `true` / `true` ✅ |
|
||||
| `function_response_part_count` | **1** — инструмент реально вызван ✅ |
|
||||
| `delivery settled` (`agent_reply`) | `success` ✅ |
|
||||
| Строка фикса в логе | `[mcp] skipping duplicate toolset "balda": endpoint already served by "balda-session-memory-<token>"` ✅ |
|
||||
|
||||
⚠️ **Питфолл своей же проверки:** `grep -c "duplicate tool"` даёт **1**, потому что моя строка
|
||||
лога — «skipping **duplicate tool**set». Искать ошибку надо строго как `grep -c "duplicate tool:"`
|
||||
(с двоеточием, как в формате `PackTool`). Иначе ложная тревога на ровном месте.
|
||||
|
||||
Остаток: §0 строка 6e (форк) — закрыта ранее; **6f/6g/6h/7 — ✅**.
|
||||
|
||||
Гейты (прогнано 2026-09-27): `go build ./...` → **OK**, `go vet ./...` → **OK**,
|
||||
`go test ./...` в форке → **все зелёные**. Целевой прогон:
|
||||
@@ -1213,6 +1243,21 @@ for _, id := range ids {
|
||||
видно требуемое поведение: `[mcp] skipping duplicate toolset "balda": endpoint already served
|
||||
by "balda-session-memory-abc123"` — выживает именно scoped-запись.
|
||||
|
||||
### 19.6 Остаток работ (незначительный)
|
||||
|
||||
| # | Пункт | Почему не срочно |
|
||||
|---|---|---|
|
||||
| 1 | Удалить с Kraken диагностический образ `balda-mm:0.1.13-diag` | Он уже не используется: compose указывает на `0.1.13`. Занимает ~79 МБ |
|
||||
| 2 | Почистить старые теги `balda-mm:0.1.0`…`0.1.9` | Оставлены ранее осознанно для отката; `0.1.10`-`0.1.13` трогать не нужно |
|
||||
| 3 | Завести Access Application на `agents-bridge.qentra.top` | Отдельный пункт §9.5, к этому дефекту не относится |
|
||||
|
||||
⛔ **Не удалять** без явного подтверждения Alex (правило: никаких удалений без апрува).
|
||||
|
||||
⚠️ **Урок про перегенерацию образа:** сборка, запущенная **до** последней правки кода, даёт
|
||||
устаревший бинарник. В этой сессии так и случилось: build стартовал, затем правки в
|
||||
`mcpEndpointKey` (добавлен `sse`), пришлось убивать процесс и пересобирать. Порядок:
|
||||
**сначала все правки → финальный `go build`/`go test` → и только потом docker build.**
|
||||
|
||||
🔴 **Проверка чувствительности теста:** отключение keeper-предпочтения (`if false && …`)
|
||||
тест **не ломает** — он проверял только количество. Поэтому добавлен `selectScopedOnly`:
|
||||
теперь тест падает, если выживает не scoped-запись. Слабый тест = ложная уверенность.
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
# Фаза 3 — Балда на Kraken + Mattermost (деплой)
|
||||
|
||||
> Создан 2026-09-26. Обновлён 2026-09-26 (**✅ ФАЗА ЗАКРЫТА — боевая приёмка пройдена**).
|
||||
> Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex.** Образ `0.1.10` (коммит `9f1e6c9c`,
|
||||
> он же PR baldaworks/balda#8).
|
||||
> 🔴 **ТЕКУЩЕЕ СОСТОЯНИЕ (2026-09-27): бот отвечает ТЕКСТОМ, инструменты вызвать НЕЛЬЗЯ.**
|
||||
> На 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, лог подтверждён). Обычные текстовые ответы работают — отсюда
|
||||
> ложное впечатление «рабочего образа».
|
||||
> Создан 2026-09-26. Обновлён 2026-09-27 (**✅ ФАЗА ЗАКРЫТА — боевая приёмка пройдена, включая
|
||||
> ВЫЗОВ ИНСТРУМЕНТОВ**).
|
||||
> Статус: **✅ РАБОТАЕТ. Бот отвечает аккаунту Alex и исполняет MCP-инструменты.**
|
||||
> Текущий образ на Kraken — **`balda-mm:0.1.13`** (см. блок ниже). Первая приёмка боевого
|
||||
> пути была на `0.1.10` (коммит `9f1e6c9c`, он же PR baldaworks/balda#8) — но тогда
|
||||
> инструменты не исполнялись.
|
||||
> 🔴 **КОРЕНЬ (исправлено 2026-09-27, проверено кодом):** Balda регистрирует bundled MCP-сервер
|
||||
> **ДВАЖДЫ на одном URL** — `registry.Set("balda", …)` (`internalmcp/manager.go:213`) и
|
||||
> session-scoped привязка `balda-session-memory-<token>` (`sessionmemorymcp/broker.go:111`,
|
||||
@@ -19,8 +16,12 @@
|
||||
> ОПРОВЕРГНУТА исполняемым тестом:** `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 того же дока.
|
||||
> ✅ **ФИКС ЗАДЕПЛОЕН И ПРИНЯТ (2026-09-27).** Образ **`balda-mm:0.1.13`** — дедуп тулсетов по
|
||||
> эндпоинту в `hostedToolsets` (запись со scoped-URL выживает: несёт контекст-токен).
|
||||
> Приёмка от владельца: `@balda вызови executor_health и покажи результат` →
|
||||
> `**executor_health:** ok`. `duplicate tool:` = **0**, deadletter = **0**,
|
||||
> `turn_complete`/`final_response` = true, `function_response_part_count` = 1
|
||||
> (инструмент реально исполнен). Детали — §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**;
|
||||
@@ -31,7 +32,9 @@
|
||||
> идёт на сборке запроса, **до** первого ответа модели, и от второго раунда ADK не зависит.
|
||||
> Баг жил в связке Balda↔ADK всегда.
|
||||
> Разбор и следующий шаг — [[personal/plans/balda-mm-phase4-agents]] §16.2 и §18.
|
||||
> Compose: `/home/kraken/balda/docker-compose.yml` (`image: balda-mm:0.1.11`).
|
||||
> ⛔ **§16.2 и §18 того дока — ОПРОВЕРГНУТЫ, читать только как историю.** Актуальный корень и
|
||||
> фикс — §19. Не повторять «это ADK / это форк».
|
||||
> Compose: `/home/kraken/balda/docker-compose.yml` (`image: balda-mm:0.1.13`).
|
||||
> **Доказательство (лента `balda-test`):** `cbwsorzwoigbtrdnteugnx4aqo` (Alex) —
|
||||
> `@balda скажи одно слово` → `edxrgupoxiyig5gzmr79tjypbme` (бот) — `слово`.
|
||||
> `settlement_stage=accepted`, `bad_seq`=0, `RestartCount`=0.
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# ADK hostedagent — OpenAI/MCP: `duplicate tool` и правда о `ProcessRequest`
|
||||
|
||||
> Создано 2026-09-27. Зеркало скилла [[software-development/adk-hostedagent-openai-mcp|`adk-hostedagent-openai-mcp`]].
|
||||
> Здесь — **исправление к скиллу** и разбор дефекта, найденного на Балде.
|
||||
> Проект: [[personal/plans/balda-mm-phase4-agents]] §19, [[personal/projects/balda/balda-kraken-mm-deploy]] §19.
|
||||
|
||||
---
|
||||
|
||||
## 1. ⚠️ Исправление к скиллу (было неверно до 2026-09-27)
|
||||
|
||||
Скилл (v3.2.0) утверждал:
|
||||
|
||||
> «**MCP tools implement `RequestProcessor`** → их `ProcessRequest()` вызывает
|
||||
> `toolutils.PackTool(req, tool)`, поэтому инструменты пакуются и в `toolPreprocess`, и в
|
||||
> `toolsetPreprocess`».
|
||||
|
||||
**Это ложь.** Проверено исполняемым тестом:
|
||||
|
||||
```
|
||||
go test ./agentfactory/ -run TestMcpToolsetImplementsRequestProcessor -v
|
||||
→ MCP toolset "mcp_tool_set" does NOT implement ProcessRequest
|
||||
```
|
||||
|
||||
В `adk/v2@v2.4.0`:
|
||||
|
||||
| Тип | Файл | `ProcessRequest` |
|
||||
|---|---|---|
|
||||
| `mcpTool` | `tool/mcptoolset/tool.go:88` | ✅ есть |
|
||||
| `set` (сам тулсет) | `tool/mcptoolset/set.go:144-185` | ❌ **НЕТ** (есть только `Tools()`) |
|
||||
|
||||
Поэтому `toolsetPreprocess` (`base_flow.go:798`) на MCP-тулсетах делает `if !ok { continue }` —
|
||||
**пропускает их**. Единственный паковщик для MCP-инструментов — `toolPreprocess` (`:778`)
|
||||
по плоскому `Flow.Tools`, который наполняет `toolProcessor` (`tools_processor.go:49`).
|
||||
|
||||
⛔ **Не диагностировать `duplicate tool` как «двойную паковку двух слоёв ADK» — этот путь
|
||||
физически невозможен.** Полчаса чтения `base_flow.go` вели именно к этой ложной версии.
|
||||
|
||||
---
|
||||
|
||||
## 2. Как выглядит дефект
|
||||
|
||||
```
|
||||
error … "agent run: duplicate tool: \"balda.control.shutdown\""
|
||||
warn … command failed with retryable error (delivery_attempt 1..5)
|
||||
warn … command deadlettered (terminal:unclassified)
|
||||
```
|
||||
|
||||
`PackTool` (`tool/toolutils/toolutils.go:47-48`):
|
||||
|
||||
```go
|
||||
if _, ok := req.Tools[name]; ok {
|
||||
return fmt.Errorf("duplicate tool: %q", name)
|
||||
}
|
||||
```
|
||||
|
||||
`PackTool` **не идемпотентен**, а `toolProcessor` схлопывает **все** тулсеты в один плоский
|
||||
`Flow.Tools`. Значит: **два MCP-сервера с одинаковыми именами инструментов = падение хода.**
|
||||
|
||||
🔴 **Ключевая сигнатура:** обычный текстовый ответ работает, падает **только** ход с явным
|
||||
запросом инструмента. Это указывает на момент паковки, а не на конфиг.
|
||||
|
||||
---
|
||||
|
||||
## 3. Настоящий корень (найден 2026-09-27)
|
||||
|
||||
Balda регистрировала bundled MCP-сервер **ДВАЖДЫ на один и тот же URL**:
|
||||
|
||||
```go
|
||||
// internal/apps/balda/internalmcp/manager.go
|
||||
// :206 — база для session-scoped брокера
|
||||
m.contextBroker.SetBaseURL("http://" + res.Addr + baldaRoute)
|
||||
// :213 — и тот же эндпоинт в реестр
|
||||
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-заголовки по токену
|
||||
|
||||
`agent/runtime_manager.go:344-345` добавляет привязку в набор серверов агента:
|
||||
|
||||
```go
|
||||
registry.Set(binding.ID, binding.Config)
|
||||
extraMCPServerIDs = append(extraMCPServerIDs, binding.ID)
|
||||
```
|
||||
|
||||
→ `hostedToolsets` строит **два тулсета на один сервер** → одинаковые имена → `PackTool` падает.
|
||||
|
||||
**Триггер:** `balda.session_memory.enabled: true` создаёт scoped-привязку. Без неё дефекта нет.
|
||||
|
||||
---
|
||||
|
||||
## 4. Фикс — дедуп по эндпоинту
|
||||
|
||||
**Файл:** `runtime-local/agentfactory/agentfactory.go`, функция `hostedToolsets`.
|
||||
|
||||
Два конфига, адресующие **один host+path**, — один сервер с точки зрения пространства имён
|
||||
инструментов. Различие в query (контекст-токен) не делает их разными.
|
||||
|
||||
Хелперы:
|
||||
- `mcpEndpointKey(cfg)` — `url.Parse`, обнуляет `RawQuery` и `Fragment`.
|
||||
Покрывает **`http` и `sse`**; `stdio` возвращает `""` (общего эндпоинта нет) → никогда не схлопывается.
|
||||
- `mcpURLIsScoped(cfg)` — есть ли query (признак session-scoped привязки Балды).
|
||||
|
||||
🔴 **Критично, какую запись оставлять.** Наивный «первый по алфавиту» оставляет `balda` и
|
||||
**выбрасывает scoped-привязку** — краш уходит, но **session memory ломается** (теряется
|
||||
контекст-токен). Поэтому keeper — запись **со** scoped-URL: она строгий супернабор
|
||||
(без токена `Wrap` просто пропускает запрос в `next.ServeHTTP`).
|
||||
|
||||
Результат в логе:
|
||||
|
||||
```
|
||||
[mcp] skipping duplicate toolset "balda": endpoint already served by "balda-session-memory-<token>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Проверка (что реально подтверждает фикс)
|
||||
|
||||
| Проверка | Команда | Ожидание |
|
||||
|---|---|---|
|
||||
| Тесты | `go test ./agentfactory/ -run 'Toolset\|MCPEndpoint\|McpToolset' -v` | 6/6 PASS |
|
||||
| Ошибка ушла | `docker logs balda-mm \| grep -c "duplicate tool:"` | **0** |
|
||||
| Инструмент вызван | лог: `function_response_part_count` | **1** |
|
||||
| Ход завершён | `turn_complete` / `final_response` | `true` / `true` |
|
||||
| Живая приёмка | пост владельца с явным вызовом инструмента | ответ с реальным результатом |
|
||||
|
||||
⚠️ **Питфолл своей же проверки:** `grep -c "duplicate tool"` даёт ложную **1**, потому что
|
||||
собственная строка лога фикса — «skipping **duplicate tool**set». Искать строго
|
||||
`grep -c "duplicate tool:"` (с двоеточием, как в формате `PackTool`).
|
||||
|
||||
⚠️ **Тест должен быть чувствительным.** Первая версия проверяла только *количество* тулсетов и
|
||||
**проходила даже со сломанным фиксом** (отключение keeper-предпочтения через `if false && …` её
|
||||
не ломало). Добавлен `selectScopedOnly` — теперь тест падает, если выживает не scoped-запись.
|
||||
Слабый тест = ложная уверенность.
|
||||
|
||||
---
|
||||
|
||||
## 6. Метод: исполняемый пробник вместо чтения
|
||||
|
||||
Час чтения исходников ADK дал **неверную** версию. Один тест на присутствие интерфейса убил её
|
||||
за секунду:
|
||||
|
||||
```go
|
||||
type requestProcessor interface {
|
||||
ProcessRequest(ctx any, req any) error
|
||||
}
|
||||
if _, ok := toolset.(requestProcessor); ok {
|
||||
t.Fatal("unexpected: premise changed")
|
||||
}
|
||||
```
|
||||
|
||||
📌 **Правило:** когда гипотеза касается **интерфейса или пути вызова** — писать исполняемый
|
||||
пробник, а не читать дальше. Чтение даёт правдоподобные версии; тест даёт факт.
|
||||
|
||||
📌 **Искать ошибку в БАЗОВОМ модуле, а не в своём репо:** `grep -rn "duplicate tool"` по
|
||||
репозиторию Balda **пуст** — текст живёт в `google.golang.org/adk` (`$GOMODCACHE`). Симптом
|
||||
«в моём коде такого нет» означает «ищи в зависимостях».
|
||||
|
||||
---
|
||||
|
||||
## 7. Порядок сборки образа (урок сессии)
|
||||
|
||||
Сборка, запущенная **до** последней правки кода, даёт устаревший бинарник. В этой сессии build
|
||||
стартовал, затем я доработал `mcpEndpointKey` (добавил `sse`), пришлось убивать процесс и
|
||||
пересобирать.
|
||||
|
||||
**Правильный порядок:** все правки → финальный `go build` / `go test` → и только затем docker build.
|
||||
|
||||
---
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
- [[personal/plans/balda-mm-phase4-agents]] — §19 (корень, фикс, тесты, приёмка), §16.2 и §18 опровергнуты
|
||||
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3, образ `balda-mm:0.1.13`
|
||||
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы Балды
|
||||
- скилл `adk-hostedagent-openai-mcp` — источник (исправлен 2026-09-27)
|
||||
Reference in new issue
Block a user