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

383 lines
25 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> [!WARNING] Историческая схема Balda
> Не использовать пути, контейнеры, provider или MCP transport из этой заметки для текущего запуска.
> Канонический текущий runbook: [[personal/projects/balda/setup|Balda — актуальный локальный запуск]].
> Актуально: `balda-mm-mac`, `/Users/admin/Docker/balda-mm-mac/config/config.yaml`, Mattermost, Codex ACP, Obsidian `type: http` → `http://host.docker.internal:3101/mcp`.
# Балда / Валера — эксплуатация & 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.
>
> 📌 **Как восстановить, если понадобится** (образ придётся пересобрать — он удалён):
> ```bash
> 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|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`:
```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 стэша:**
```bash
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`
## Сборка
### Полный цикл сборки
```bash
# 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)
Если снова встанет вопрос «что у нас есть, чего нет в апстриме» — этот порядок даёт полную картину
без догадок:
```bash
# 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`)