246 lines
15 KiB
Markdown
246 lines
15 KiB
Markdown
# Балда / Валера — эксплуатация & MCP debug
|
||
|
||
> ## ⚠️ УСТАРЕЛО (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 как «текущее».
|
||
|
||
## Расположение
|
||
- **Валера** = `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`) |