# Балда / Валера — эксплуатация ## Расположение - Repo: `~/Developer/balda/` - **Рабочий 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` - Дублирующий compose `~/Developer/balda/compose.valera.yaml` — НЕ использовать (сеть `balda_default` без DNS) ## Запуск / рестарт ```bash cd ~/Docker/balda-agent docker-compose restart # Rebuild после изменений в коде: cd ~/Docker/claudio-agent && docker-compose build && docker-compose up -d cd ~/Docker/balda-agent && docker-compose up -d ``` > Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes. > > **Сборка образа**: `cd ~/Docker/claudio-agent && docker-compose build && docker-compose up -d` > **Перезапуск Валеры**: `cd ~/Docker/balda-agent && docker-compose up -d` > > Сборка идёт из `~/Docker/claudio-agent/` — там есть build секция с Dockerfile. ## Архитектура получения сообщений Валера — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM. Для получения всех сообщений в теме — **Events API polling** (отключён, после деплоя zulip-router будет удалён из кода): - `events_polling.enabled: false` в config.yaml (текущее состояние) - Сообщения приходят только через **outgoing webhook** (webhook_token в .env) - После @mention создаётся сессия; последующие сообщения без @mention обрабатываются **После перезапуска**: нужно один раз написать `@Валера <текст>` чтобы создать сессию в теме. ## Диагностика молчания 1. Проверить логи: `docker logs balda-agent-valera-1 --tail 50` 2. Проверить что бот запущен и webhook слушает: в логах `zulip webhook server starting addr=0.0.0.0:8091` 3. Стухший NATS-таск (симптом: логов нет после @mention) Фикс: `docker-compose restart` (NATS embedded, состояние в памяти) 4. Проверить webhook со стороны: отправить POST с curl: ```bash curl -X POST http://localhost:8091/zulip/webhook \ -H "Content-Type: application/json" \ -d '{"type":"test"}' ``` ## Патчи в коде (общие с Клавдием) В `zulip_handler.go` добавлены: 1. `botName` — извлекается из bot_email (часть до @). Используется чтобы отличить свой @mention от чужого. 2. `extractOtherMention()` — проверяет текст на @**Name**, где Name не равен botName и не @**all**/@**everyone**. Если найден чужой @mention — сообщение игнорируется. 3. Детальное логирование в `handleAutoClaimMention` — видны все шаги от входа до ошибки. ## Ключевые грабли ### 1. 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 ``` **ВАЖНО:** `docker-compose restart` не перечитывает `.env`. Нужно `docker-compose up -d` (пересоздание контейнера). ### 2. DOCKER_OPTS — пустая строка убивает старт Если `DOCKER_OPTS=""` (или пустая строка в `.env`), norma падает на парсинге float: ``` strconv.ParseFloat: parsing "" ``` **Должен быть валидный JSON:** ``` DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7} ``` ### 3. composerestart vs compose up -d - `docker-compose restart` — не перечитывает `.env`, не пересоздаёт контейнер - `docker-compose up -d` — пересоздаёт контейнер с обновлённым `.env` При изменении `.env` всегда использовать `up -d`. ## Контекст (история сообщений) **Не работает** для OpenAI-совместимых провайдеров (DeepSeek, ChatGPT и т.д.). Причина: ADK Runner (`google.golang.org/adk`) inject'ит историю в мульти-тур только для Gemini API. Для OpenAI-compatible провайдеров (через `base_url` переопределение) ADK не inject'ит предыдущие сообщения в вызов модели — каждый turn получает только одно сообщение. `balda.sessions.persistent: true` — влияет на хранение balda-сессии между рестартами, но не на inject истории в API вызов. ### Возможные решения 1. **Inject'ить историю в RunSessionTurnPayload** — перед вызовом `r.Run()` загрузить предыдущие сообщения из ADK session store и передать как мульти-контент. Патч в `zulip_handler.go` и `balda.go`. 2. **Передать историю через global_instruction** — обогащать промпт предыдущими сообщениями из balda session хранилища. 3. **Использовать Gemini API** (нативный ADK inject истории). ## Owner token При старте генерируется owner token. Выводится в лог: ``` balda owner authentication required auth_command="/start owner=" ``` Для авторизации через Telegram (или Zulip с /start): ``` /start owner= ``` При старте с пустым `state.db` (после `rm`) — owner не зарегистрирован. Бот принимает сообщения но не отвечает до `/start`. `state.db` путь: контейнер `/workspace/.config/balda/state.db`, на хосте `~/Docker/balda-agent/.config/balda/state.db`. ## Диагностика: Валера не отвечает в Zulip ### Симптом: в логах `command running` → `command handled` за секунду, без `received provider event` Причины (проверять по порядку): 1. **Стухший embedded NATS** — swarm внутри процесса не может доставить команду до session/task actor'ов. Фикс: `docker-compose restart` (если не помогло → `up -d`) 2. **Удалён / пустой state.db** — после удаления balda не может зарегистрировать swarm акторы. Фикс: `rm state.db` и `up -d` (balda создаст заново). Owner токен сбросится — нужен `/start owner=...`. 3. **Нет owner** — `handleMessage` выходит при `getOwnerID() == 0`. Фикс: `/start owner=` в Telegram или Zulip. 4. **stream_only_with_session** — сообщение в теме без сессии молча дропается. Фикс: написать `@Валера <текст>` чтобы создать сессию. 5. **DeepSeek не отвечает (context loss)** — см. раздел «Контекст». ## Конфигурация провайдера В `config.yaml` обязательно: ```yaml balda: provider: deepseek sessions: persistent: true persistence: sqlite zulip: events_polling: enabled: false stream_only_with_session: true runtime: providers: deepseek: openai: base_url: https://api.deepseek.com/v1 ```