Files
obsidian-vault/personal/projects/balda/balda-valera.md
T

24 KiB
Executable File
Raw Blame History

Балда / Валера — эксплуатация & MCP debug

⛔ СНЕСЁН 2026-09-29 — агент удалён на Mac

По команде Alex: «Валеру снеси на маке (он на старой балде работает, а мы нового готовим), только не папку с кодом, а агента».

Объект Действие
Контейнер balda-agent-valera-1 ✅ docker-compose -f docker-compose.yaml down → Removed
Образ balda-agent-valera:latest (640cb18aaeeb, 71.6 МБ) ✅ docker rmi → Deleted
Порт 8091 ✅ освобождён
Папка кода /Users/admin/Developer/balda ✅ СОХРАНЕНА (не тронута)
Compose+конфиг /Users/admin/Docker/balda-agent/ ✅ СОХРАНЕНЫ (.env, .config/balda/, docker-compose.yaml)
balda-executor (2.48 ГБ образ) ⚠️ НЕ тронут — это не Валера

🔴 Питфолл: restart: unless-stopped — Валера поднялся бы сам при старте Docker. Поэтому одного docker stop мало: нужен именно compose down.

🔴 Питфолл: на этом хосте НЕТ плагина docker compose. docker compose -f → unknown shorthand flag: 'f'. Работает только отдельный бинарь docker-compose (v5.1.3, /opt/homebrew/bin/docker-compose). Docker Engine 29.2.1.

📌 Как восстановить, если понадобится (образ придётся пересобрать — он удалён):

cd ~/Docker/claudio-agent
docker build --no-cache -f Dockerfile.claudio -t balda-agent-valera:latest /Users/admin/Developer
cd ~/Docker/balda-agent && docker-compose up -d

Раздел §«Сборка» ниже описывает полный цикл (сборка из ~/Developer/norma-local).

Замена: новый стек Балды — balda-mm:* на Kraken со стороны Mattermost, см. personal/projects/balda/balda-mm-delegation-plan.

⚠️ УСТАРЕЛО (2026-09-25) — читать с оговоркой

Этот док описывает старый форк-стек: свой патч norma-local (hostedagent/openai.go), go.mod replace на локальную норму, DeepSeek через OpenAI-совместимый рантайм.

Апстрим ушёл далеко вперёд. Актуально: normahq/balda → baldaworks/balda, архитектура переписана (handlers/* → handlersfx/*, Zulip → channel/zulip/*), провайдеры теперь ACP (claude_code_acp, codex_acp, ...) вместо hostedagent/openai.

Планируется поднять чистую Балду без наших коммитов → см. personal/projects/balda/balda-mm-delegation-plan.

Что здесь всё ещё верно: грабли эксплуатации (NATS, owner, DOCKER_OPTS, stream_only_with_session), бранчи/стэши, диагностика молчания. Что мертво: раздел про openai.go/norma-local replace как «текущее».


🔴 ВАЖНАЯ ПОПРАВКА (2026-09-26): раздел про openai.go снова АКТУАЛЕН

Вывод «патч openai.go мёртв» опровергнут. Апстрим переписал hostedagent/openai.go под runtime v2 и потерял фикс tool_calls целиком (246 строк против наших 490) — то есть та же дыра снова открыта, и закрыть её можно только тем же патчем.

На Kraken это и есть причина «Балда не исполняет MCP-инструменты»: серверы подключены, инструменты не вызываются. Разбор → personal/plans/balda-mm-phase4-agents §15. Питфоллы патча (двойная сериализация аргументов DeepSeek, ключ output) → personal/tech/balda-go-fx-pitfalls §«НАСТОЯТЫЙ корень».


✅ ПАТЧ СДЕЛАН И ОТПРАВЛЕН В АПСТРИМ (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.


✅ ВТОРОЙ PR — ФИКС MCP-ТУЛИНГА ОТПРАВЛЕН (2026-09-27)

Чтобы закрыть обнажившийся баг duplicate tool, потребовалась правка в runtime, а не в Балде. Отправлено отдельным PR — тема независима от #4.

Что Значение
Форк mallexxx/runtime, ветка fix/hosted-toolsets-duplicate-endpoint
PR в апстрим https://github.com/normahq/runtime/pull/5
База main (12f1210), 0 позади / 1 впереди — rebase не нужен
Коммит b01bdc6
Объём 2 файла, +263 / −0
Патч agentfactory/agentfactory.go +74; agentfactory/duplicate_toolset_test.go new (6 тестов)

Суть фикса. hostedToolsets схлопывает MCP-конфиги, различающиеся только query/fragment. Балда регистрирует bundled-сервер дважды: balda (…/mcp/balda) и balda-session-memory-<token> (…/mcp/balda?ctx=<token>). Оба отдают один список инструментов, ADK пакует всё в одну карту → duplicate tool.

🔴 Ключевое решение: выживает SCOPED-запись, не первая по алфавиту. Её URL несёт контекст-токен сессии — это строгое надмножество plain-эндпоинта. Наивный дедуп «первый победил» отбросил бы токен и сломал session-scoped MCP-вызовы. Новые функции: mcpEndpointKey (stdlib net/url, срезает RawQuery + Fragment) и mcpURLIsScoped. stdio-транспорты возвращают "" → никогда не схлопываются.

Скип логируется — видно в проде: [mcp] skipping duplicate toolset "balda": endpoint already served by "balda-session-memory-<token>"

Гейты (локально, все зелёные на закоммиченном дереве):

go build ./...                    → exit 0
go vet ./...                      → exit 0
go tool golangci-lint run ./...   → 0 issues
go test -race ./...               → все пакеты ok

⚠️ Питфолл линтеров на тесте (duplicate_toolset_test.go:187): staticcheck ST1023 требует убрать явный тип (var x tool.Toolset = ts), а unconvert — убрать конверсию (tool.Toolset(ts)). Оба правы → правильное решение — не вводить переменную вообще: if _, ok := any(ts).(requestProcessor); ok {. После этого импорт adk/v2/tool стал неиспользуемым — его тоже надо убрать.

🔴 CI на PR НЕ ЗАПУСТИЛСЯ — mergeStateStatus: UNSTABLE, check-runs: total_count 0. Причина: PR из форка, GitHub требует ручного подтверждения workflows владельцем репо («Approve and run workflows»). Это НЕ поломка PR. Локальные гейты пройдены полностью. Требуется действие Alex: открыть PR#5 и подтвердить запуск CI.

📌 Развилка репозиториев (важно не путать): фикс MCP-тулинга → normahq/runtime (PR#5), Mattermost-транспорт → baldaworks/balda (PR#8). Это разные репозитории, у них разные форки (mallexxx/runtime / mallexxx/balda) и разные клоны на диске (~/Developer/runtime-local/ — git-клон; ~/Developer/balda/ — репо Балды).

⚠️ runtime-local существует в ДВУХ местах и их легко перепутать:

Путь Что это
~/Developer/runtime-local/ git-клон форка mallexxx/runtime — здесь делаются PR
~/Developer/balda/runtime-local/ копия без .git для сборки образа Балды

Расположение

  • Валера = balda-agent-valera-1, конфиг: ~/Docker/balda-agent/.config/balda/config.yaml
  • Клавдий = claudio-agent-claudio-1, конфиг: ~/Docker/claudio-agent/.config/balda/config.yaml
  • Общий образ: собирается из ~/Docker/claudio-agent/Dockerfile.claudio, контекст /Users/admin/Developer
  • Norma-local (форк с MCP tools): ~/Developer/norma-local/
    • go.mod replace: github.com/normahq/norma => ./norma-local (в ~/Developer/balda/go.mod)
    • Модифицирован pkg/runtime/hostedagent/openai.go + pkg/runtime/agentfactory/agentfactory.go
  • Dockerfile: ~/Docker/claudio-agent/Dockerfile.claudio
    • Копирует norma-local/ в /src/norma-local/
    • go mod edit -replace github.com/normahq/norma=./norma-local перед go mod download

Архитектура

  • Валера = DeepSeek (provider: deepseek, модель deepseek-chat)
  • Клавдий = Claude через прокси (provider: claude, claude-sonnet-4-6)
  • Валера использует Balda runtime — OpenAIModel из openai.go вызывается (hostedagent провайдер).
  • agentfactory.go используется обоими — там лог версии на старте.

Версионный лог (norma-tools)

При старте Валеры в логах:

norma-tools version=v0.0.10 build=whale-YYYYMMDD-N

Где:

  • version — номер версии нормы (из openAIVersion)
  • build — тег сборки (из buildTag)

Перед каждым билдом апать buildTag в openai.go:

const openAIVersion = "v0.0.10"
const buildTag      = "whale-YYYYMMDD-N"   // ← менять!

Файлы где апать:

  • ~/Developer/norma-local/pkg/runtime/hostedagent/openai.go — buildTag константа

Статус: DeepSeek MCP tools — РАБОТАЕТ

Что сделано (openai.go)

Добавлена полная поддержка OpenAI tool_calls:

  • openAIToolDefinition, openAIFunction, openAIToolCall — структуры
  • Tools []openAIToolDefinition в openAIChatRequest
  • openAIToolsFromConfig() — конвертация genai.Tool[] → OpenAI definitions
  • parseChatResponse() — парсинг tool_calls из ответа DeepSeek (ID сохраняется)
  • contentToOpenAI() — конвертация FunctionCall/FunctionResponse в историю
  • generate() — TurnComplete=false при tool_calls (ADK делает второй раунд)

Исправленные проблемы

1. DeepSeek возвращает аргументы с двойной сериализацией ✓ 22.06.2026

Симптом: read_note/write_note падали — Obsidian MCP возвращал "Cannot read properties of undefined (reading 'replace')".

Корень: DeepSeek возвращает function.arguments как JSON-строку (экранированную), а не как JSON-объект.

Фикс в parseChatResponse: сперва пробуем распарсить Arguments как строку (json.Unmarshal(&argsStr)), потом эту строку как объект (json.Unmarshal([]byte(argsStr), &args)).

2. Obsidian MCP: get_vault_stats работает, read/write/delete нет ✓ 22.06.2026

Корень: двойная сериализация аргументов (см. проблему 1). После её фикса всё работает.

3. Key output не проверялся в contentToOpenAI ✓ 22.06.2026

Obsidian MCP возвращает response как {"output":"..."} — добавлена проверка на ключ output.

Текущий билд

  • buildTag: whale-20260622-7
  • Все фиксы закоммичены в master (норма-локаль)
  • OPENAI-DEBUG стэш дропнут — логи в рабочей копии (не коммитятся)
  • Replace на норму работает через go.mod + Dockerfile
  • norma-tools лог подтверждён: time=2026-06-22T13:00:37.170Z level=INFO msg=norma-tools version=v0.0.10 build=whale-20260622-7

Промежуточные статусы — ПОЧИНЕНО (23.06.2026)

Статус: работает.

  • RunSessionTurnPayload (zulip_handler.go) — публикует промежуточный текст и ⚙️ function call статусы для !ev.TurnComplete ивентов через sendPlain.
  • balda.go (Telegram) — те же промежуточные публикации.
  • handleAutoClaimMention / handleMessage / enqueueTurn — передают messageID для точного Zulip threading.
  • Коммит: 05159d1 (main), запушен.

Стэши debug-логов (23.06.2026)

Дебаг логи не коммитятся, хранятся в стэшах.

balda (~/Developer/balda):

  • stash@{0}: debug: whale-20260623 — TASK-ACTOR logging in swarm_task_actor, event count & response_len debug in zulip_handler
    • internal/apps/balda/actors/swarm_task_actor.go — TASK-ACTOR: dispatching/dispatch OK/FAILED
    • internal/apps/balda/handlers/zulip_handler.go — eventCount, response_len, running session turn log
  • stash@{1}: WIP on feat/zulip-events-polling

Восстановление balda стэша:

cd ~/Developer/balda && git stash pop stash@{0}

norma-local (~/Developer/norma-local):

  • OPENAI-DEBUG стэш дропнут — 9x fmt.Fprintf(os.Stderr, "OPENAI-DEBUG:...") в рабочей копии openai.go

Бранчи (23.06.2026)

norma-local:

  • feat/hostedagent-mcp-tools — коммит 56a6e1e (DeepSeek tool_calls фикс), чистая бранча от origin/master
  • master — коммит 56a6e1e (тот же, DeepSeek фиксы)

balda:

  • main — 05159d1 (intermediate status фикс запушен)
  • backup/our-main-before-upstream — старый main
  • feat/zulip-transport-intermediate — от коммита bc5fd03

Сборка

Полный цикл сборки

# 1. Инкремент buildTag в openai.go (строка buildTag)
# ~/Developer/norma-local/pkg/runtime/hostedagent/openai.go
# buildTag = "whale-YYYYMMDD-N"

# 2. Применить OPENAI-DEBUG стэш (если нужно дебажить)
cd ~/Developer/norma-local && git stash pop stash@{0}

# 3. Сборка
docker build --no-cache \
  -f ~/Docker/claudio-agent/Dockerfile.claudio \
  -t balda-agent-valera:latest \
  /Users/admin/Developer

# 4. Деплой
cd ~/Docker/balda-agent && docker-compose up -d --force-recreate

# 5. Проверка версии в логах
docker logs balda-agent-valera-1 2>&1 | grep "norma-tools"

Особенности сборки

  • Dockerfile копирует norma-local/ в /src/norma-local/
  • go.mod replace: github.com/normahq/norma => ./norma-local
  • go mod edit -replace применяется до go mod download
  • --no-cache обязателен при изменении norma-local
  • Образ надо таргетировать под нужное имя: balda-agent-valera для Валеры, claudio-agent для Клавдия
  • После сборки docker-compose up -d --force-recreate чтобы подхватить новый образ

Диагностика молчания / проблем

NORMA-TOOLS не появляется в логах

Причина: агент использует не hostedagent провайдер. openai.go задействован только при provider=deepseek через ADK.

MCP ошибка: failed to list MCP tools: failed to connect: Not Found

Фикс: в config.yaml url должен заканчиваться на /sse: url: http://obsidian-mcp:3101/sse

В логах command running → command handled за секунду, без received provider event

Причина: стухший embedded NATS. Фикс: docker-compose restart или up -d.

Удалён / пустой state.db

Фикс: rm state.db и up -d (balda создаст заново).

Нет owner

Фикс: прописать allowed_owners в config.yaml.

stream_only_with_session

Фикс: написать @Валера <текст> чтобы создать сессию.

Как инвентаризировать форк перед сверкой с апстримом (проверено 2026-09-25)

Если снова встанет вопрос «что у нас есть, чего нет в апстриме» — этот порядок даёт полную картину без догадок:

# 1. Подтянуть метаданные апстрима (не менять рабочую копию!)
cd ~/Developer/balda
git fetch --all --tags --prune

# 2. Насколько мы отстали / опередили
git rev-list --left-right --count HEAD...upstream/main     # ожидаем "N  M"

# 3. Наши уникальные коммиты по КАЖДОЙ ветке (главное)
for ref in origin/main backup/our-main-before-upstream feat/zulip-transport feat/zulip-events-polling; do
  echo "--- $ref ---"; git log --oneline upstream/main.."$ref"
done

# 4. Размер расхождения по файлам
git diff --stat $(git merge-base upstream/main origin/main)..upstream/main | tail -5

# 5. Что апстрим сделал с файлами, которые мы правили
git log --oneline --all -- pkg/runtime/hostedagent/openai.go

Результат для нашего форка (2026-09-25):

Репо Отставание Наши уникальные коммиты
~/Developer/balda 900 файлов 05159d15 (промежуточные статусы Zulip, handlers/balda.go), bc5fd03c (то же + Telegram + messageID), feat/zulip-events-polling (Events API long-poll)
~/Developer/norma-local 26 коммитов 56a6e1e=91c2c0a (DeepSeek tool_calls в hostedagent/openai.go)

Важно про 5ad65955 в balda — это НЕ фича, а replace normahq/norma => ./norma-local в go.mod. При переезде на чистый апстрим его нужно удалить, иначе сборка не пройдёт (файлов norma-local там нет).

Что апстрим сделал с нашими файлами:

  • norma v2 (f8a7336) удалила целиком pkg/runtime/hostedagent/ и pkg/runtime/agentfactory/. Наш DeepSeek-фикс не переносится патчем. Перед переносом проверить, есть ли поддержка tool_calls для OpenAI-совместимых провайдеров в normahq/runtime/v2 v2.0.11 — если да, фикс не нужен вовсе.
  • В balda наши handlers/* заменены на handlersfx/*; Zulip переехал в channel/zulip/*. Наш фикс промежуточных статусов надо переписывать под handlersfx/zulip_ingress.go.

Питфоллы при сверке:

  • origin у norma-local — это normahq/norma (реальный апстрим), а у balda origin — наш mallexxx/balda, апстрим живёт в upstream. Не путать: «тянуть до апстрима» для balda ≠ git pull origin main.
  • Стэши теряются (не переживают git gc/клоны) — выносить в ветку до любых операций сброса.

Известные грабли

OPENAI_BASE_URL — обязательная env var

Norma не читает base_url из config.yaml для provider: deepseek. Читает env.

DOCKER_OPTS — пустая строка убивает старт

Должен быть валидный JSON: DOCKER_OPTS={"max_tokens": 2048}

docker compose up -d не работает

На этом хосте docker compose (без дефиса) не принимает -d. Использовать docker-compose up -d.

Docker кеш COPY norma-local/

Слой копирования кешируется. --no-cache обязателен при изменении norma-local.

go.mod replace

replace github.com/normahq/norma => ./norma-local — путь относительно /src в контейнере, куда копируется norma-local/.

zulip-router owner-форвард падал с 400 (23.06.2026)

Симптом: Валера не отвечал на сообщения в закреплённых тредах без @mention. Router лог: forward failed (owner) error="post ... status 400 body=bad request". Валера лог: invalid zulip webhook payload error="unsupported message.type \"\"".

Корень: В zulip-router/config.go структура Message не имела поля Type. Zulip Events API присылает message.type ("stream"/"private"), но роутер его не парсил — в вебхук приходил пустой "". Balda-agent валидирует message.type.

Фикс: Добавлен Type string \json:"type,omitempty"`вMessage struct. Коммит 803ae68(локальный, без remote). docker-compose up -d --build. Фикс: messageID int в сигнатуру handleAutoClaimMention, передаётся payload.Message.ID из processMessage. Файл:~/Developer/balda/internal/apps/balda/handlers/zulip_handler.go(в коммите05159d1`)