[2026-06-25] taiga-vault: family/how-to/htpc-emulators-setup.md family/how-to/htpc-gaming-plans.md family/how-to/kraken-access.md family/how-to/openmediavault-rpi5.md family/how-to/time-machine.md family/how-to/wireguard-vpn.md personal/documents/todo-list.md personal/plans/extract-stable-prompt-blocks.md personal/plans/hermes-whale-system-prompt.md personal/plans/thread-scoped-memory.md

This commit is contained in:
Taiga
2026-06-25 05:23:46 +00:00
parent ac0d753ec0
commit 16987d69f3
26 changed files with 2656 additions and 457 deletions
+160 -172
View File
@@ -1,197 +1,185 @@
# Балда / Валера — эксплуатация
# Балда / Валера — эксплуатация & MCP debug
## Расположение
- Repo: `~/Developer/balda/` (ветка `main`)
- Code: upstream `normahq/balda` + `feat/zulip-transport` влит, `normahq/norma` v0.0.10
- **Рабочий compose**: `~/Docker/balda-agent/docker-compose.yaml` → контейнер `balda-agent-valera-1`
- Config (persistent): `~/Docker/balda-agent/.config/balda/config.yaml`
- Env: `~/Docker/balda-agent/.env`
- Owner: `allowed_owners` в config.yaml (статически, `/start owner=` не нужен)
- **Валера** = `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 ~/Docker/balda-agent
docker-compose restart # без пересборки, только конфиг/env не менялись
docker-compose up -d # пересоздать контейнер, перечитать .env
# Rebuild после изменений в коде:
cd ~/Docker/claudio-agent && docker-compose build
docker tag claudio-agent-claudio:latest balda-agent-valera:latest
cd ~/Docker/claudio-agent && docker-compose up -d
cd ~/Docker/balda-agent && docker-compose up -d
cd ~/Developer/balda && git stash pop stash@{0}
```
> Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes. Сборка идёт из `~/Docker/claudio-agent/` — там есть build секция.
**norma-local** (`~/Developer/norma-local`):
- OPENAI-DEBUG стэш дропнут — 9x fmt.Fprintf(os.Stderr, "OPENAI-DEBUG:...") в рабочей копии openai.go
## Архитектура получения сообщений
Валера — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
## Бранчи (23.06.2026)
- `events_polling.enabled: false` в config.yaml
- Сообщения приходят только через **outgoing webhook** (webhook_token в .env)
- После @mention создаётся сессия; последующие сообщения без @mention обрабатываются
**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`
## MCP Obsidian — конфиг
Balda через SSE подключается к obsidian-mcp. **URL обязательно с `/sse`**:
## Сборка
```yaml
runtime:
mcp_servers:
obsidian:
type: sse
url: http://obsidian-mcp:3101/sse # <-- без /sse → 404 Not Found
name: obsidian
```
## Контекст (история сообщений)
**Работает** (на upstream `normahq/balda` с поддержкой hosted LLM agent sessions). ADK inject'ит историю корректно для OpenAI-совместимых провайдеров.
Раньше не работало — исправлено в upstream `32e33a8 fix: support hosted MCP toolsets` / `d774c78 fix: use hosted llmagent sessions`.
## Проблема: MCP не работают у Валеры (provider: type=openai)
### Коренная причина
hostedagent (`pkg/runtime/hostedagent/`) не поддерживает MCP инструменты.
`openAIConstructor` в `agentfactory.go` получает `resolvedMCP map[string]agentconfig.MCPServerConfig`,
но не передаёт их в `hostedagent.Config` — у Config нет поля для MCP.
`openAIConstructor` вызывает `newHostedAgent(hostedagent.Config{...})` без MCPServers.
Фикс: в `hostedagent.Config` добавить `MCPServers map[string]agentconfig.MCPServerConfig`,
а в `openAIConstructor` передавать `toRuntimeMCPServers(resolvedMCP)`.
Важно: hostedagent использует OpenAI-compatible API (не ACP). MCP инструменты нужно
интегрировать через OpenAI tool_calls — модель шлёт tool_call, код выполняет MCP вызов.
## Проблема: MCP не работают у Валеры — попытка фикса
### Что было сделано (2026-06-19)
#### 1. go.mod
Добавлен `replace github.com/normahq/norma => ../norma-local` — для локальной разработки.
#### 2. hostedagent — добавлена поддержка MCP
- `pkg/runtime/hostedagent/agent.go`:
- В Config добавлено `MCPServers map[string]acpagent.MCPServerConfig`
- Сохранено в `runtimeAgent`
- В `run()`: после создания модели вызывается `model.SetTools(openAIToolsFromMCPServers(r.MCPServers))`
- `pkg/runtime/hostedagent/openai.go`:
- В `openAIChatRequest` добавлено `Tools []openAIToolDefinition`
- В `OpenAIModel` добавлены `tools []openAIToolDefinition + `SetTools()`
- Добавлен `tool_calls` в парсинг ответа (`tool_calls` → content string)
#### 3. agentfactory — передача MCP
- В `openAIConstructor` добавлено `MCPServers: toRuntimeMCPServers(resolvedMCP)` в вызов `newHostedAgent()`
#### 4. Dockerfile
В `Dockerfile.claudio` добавлен `COPY norma-local/ ./norma-local/` — чтобы `replace` работал в контейнере.
#### 5. Сборка
Образ собран и запущен как `balda-agent-valera:latest`.
#### 6. Диагностика
- `mcpServerIDs` приходят корректно: `[balda obsidian fast-rlm]`
- Добавлен stderr-лог в `agentfactory.go` в `Build()`:
```go
fmt.Fprintf(os.Stderr, "BALDA-DEBUG: mcpServerIDs=%v resolvedMCP=%v\n", mcpServerIDs, resolvedMCP)
```
- Для просмотра логов нужен `docker logs balda-agent-valera-1 2>&1 | grep BALDA-DEBUG`
### Почему не заработало
Точная причина не установлена — нужно увидеть `resolvedMCP` в логах контейнера. Возможные варианты:
- `resolveMCPServers` возвращает пустой map
- `toRuntimeMCPServers` неправильно конвертирует
- `SetTools` не влияет на уже созданные сообщения в сессии
### Что осталось
1. Прочитать stderr из контейнера: `docker logs balda-agent-valera-1 2>&1 | grep BALDA-DEBUG`
2. Если `resolvedMCP` пустой — исправлять цепочку resolveMCPServers
3. Если не пустой — тестировать response с tool_calls в DeepSeek ответе
## Диагностика молчания
### 1. MCP ошибка: `failed to list MCP tools: failed to connect: Not Found`
**Причина**: в config.yaml url без `/sse` на конце.
**Фикс**: `url: http://obsidian-mcp:3101/sse` → `docker-compose restart`.
### 2. В логах `command running → command handled` за секунду, без `received provider event`
**Причина**: стухший embedded NATS. Swarm внутри процесса не доставляет команду до session/task actor'ов.
**Фикс**: `docker-compose restart` (если не помогло → `up -d`).
### 3. Удалён / пустой state.db
После удаления balda не может зарегистрировать swarm акторы.
**Фикс**: `rm state.db` и `up -d` (balda создаст заново).
### 4. Нет owner
`handleMessage` выходит при `getOwnerID() == 0`.
**Фикс**: прописать `allowed_owners` в config.yaml (не нужен `/start owner=`).
### 5. stream_only_with_session
Сообщение в теме без сессии молча дропается.
**Фикс**: написать `@Валера <текст>` чтобы создать сессию.
### 6. Проверить webhook со стороны
### Полный цикл сборки
```bash
curl -X POST http://localhost:8091/zulip/webhook \
-H "Content-Type: application/json" \
-d '{"type":"test"}'
# 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
Фикс: написать `@Валера <текст>` чтобы создать сессию.
## Известные грабли
### OPENAI_BASE_URL — обязательная env var
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env `OPENAI_BASE_URL`.
Без неё запросы уходят на `api.openai.com` (401).
**В `.env` обязательно:**
```
OPENAI_BASE_URL=https://api.deepseek.com/v1
```
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env.
### DOCKER_OPTS — пустая строка убивает старт
Должен быть валидный JSON:
```
DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7}
```
Должен быть валидный JSON: `DOCKER_OPTS={"max_tokens": 2048}`
### composerestart vs compose up -d
- `restart` — не перечитывает `.env`
- `up -d` — пересоздаёт контейнер с обновлённым `.env`
### 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/`.
## Диагноз: MCP не работают — инструменты не передаются модели
### 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 \"\""`.
**Статус (2026-06-21):** MCP резолвятся, но не вызываются.
**Корень:** В `zulip-router/config.go` структура `Message` не имела поля `Type`. Zulip Events API присылает `message.type` ("stream"/"private"), но роутер его не парсил — в вебхук приходил пустой `""`. Balda-agent валидирует message.type.
**Установлено через BALDA-DEBUG:**
```
BALDA-DEBUG: mcpServerIDs=[balda obsidian fast-rlm]
resolvedMCP=map[balda:{http http://127.0.0.1:33545/mcp}
fast-rlm:{http http://fast-rlm-mcp:3333/mcp}
obsidian:{sse http://obsidian-mcp:3101/sse}]
```
MCP серверы зарезолвлены, тулсеты переданы в `hostedagent.Config`.
**Почему не работают:**
`OpenAIModel.generate()` → `buildChatRequest()` не читает `req.Config.Tools` (поле `genai.GenerateContentConfig`). ADK кладёт инструменты туда, но `buildChatRequest` сериализует только `model`, `messages`, `temperature`, `top_p`, `max_tokens`, `stop`. DeepSeek получает запрос без `tools` → отвечает текстом.
**Логи подтверждают:** `function_call_part_count=0` в ответе DeepSeek.
**Фикс:** добавить передачу tools в OpenAI запрос и парсинг tool_calls из ответа.
**Сделано (2026-06-21):** в `openai.go` добавлены:
- `openAIToolDefinition`, `openAIFunction`, `openAIToolCall` — структуры для API
- Поле `Tools` в `openAIChatRequest`
- Функция `openAIToolsFromConfig()` — конвертирует `genai.Tool[]` → OpenAI tool definitions
- Функция `contentToOpenAI()` — обрабатывает FunctionCall/FunctionResponse из ADK
- Парсинг `tool_calls` в `parseChatResponse()`
- **Тесты:** 17 тестов на new функциональность + 3 существующих = все проходят
1. Добавить поле `Tools` в `openAIChatRequest` + `openAIToolDefinition`
2. В `buildChatRequest` читать `req.Config.Tools` → конвертировать в OpenAI tool definitions
3. В `parseChatResponse` парсить `tool_calls` из ответа (ADK сам выполнит MCP)
**Важно:** upstream `norma` v0.0.10 (`bf2b25d`) уже корректно передаёт MCP тулсеты через `agentfactory.hostedToolsets()`. Проблема только в том, что `OpenAIModel` — кастомная реализация, не умеющая прокидывать tools. Если бы использовался Gemini/ACP provider — MCP бы работали.
**Фикс:** Добавлен `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`)
+4 -2
View File
@@ -1,10 +1,12 @@
# Balda — Setup & Config
_Последнее обновление: 2026-06-16_
_Последнее обновление: 2026-06-24_
## Хост
Mac (Eagle), Docker Desktop (arm64 native). Контейнеры собираются из исходников в `~/Developer/balda/`.
Mac (Eagle), **Colima** (arm64, 8 CPU, 24 GB RAM, 100 GB sparse disk). Контейнеры собираются из исходников в `~/Developer/balda/`.
**Важно:** `buildx_buildkit_arm64builder0_state` volume — основная причина зависаний Zulip при сборке. См. [[tech/docker-mac-disk-issues]].
## Компоненты