[2026-06-17] update balda docs — context fix, MCP /sse, single-image build flow, current status
This commit is contained in:
@@ -1,63 +1,88 @@
|
|||||||
# Балда / Валера — эксплуатация
|
# Балда / Валера — эксплуатация
|
||||||
|
|
||||||
## Расположение
|
## Расположение
|
||||||
- Repo: `~/Developer/balda/`
|
- 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`
|
- **Рабочий compose**: `~/Docker/balda-agent/docker-compose.yaml` → контейнер `balda-agent-valera-1`
|
||||||
- Config (persistent): `~/Docker/balda-agent/.config/balda/config.yaml`
|
- Config (persistent): `~/Docker/balda-agent/.config/balda/config.yaml`
|
||||||
- Env: `~/Docker/balda-agent/.env`
|
- Env: `~/Docker/balda-agent/.env`
|
||||||
- Дублирующий compose `~/Developer/balda/compose.valera.yaml` — НЕ использовать (сеть `balda_default` без DNS)
|
- Owner: `allowed_owners` в config.yaml (статически, `/start owner=` не нужен)
|
||||||
|
|
||||||
## Запуск / рестарт
|
## Запуск / рестарт
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/Docker/balda-agent
|
cd ~/Docker/balda-agent
|
||||||
docker-compose restart
|
docker-compose restart # без пересборки, только конфиг/env не менялись
|
||||||
|
docker-compose up -d # пересоздать контейнер, перечитать .env
|
||||||
|
|
||||||
# Rebuild после изменений в коде:
|
# Rebuild после изменений в коде:
|
||||||
cd ~/Docker/claudio-agent && docker-compose build && docker-compose up -d
|
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 ~/Docker/balda-agent && docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
> Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes.
|
> Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes. Сборка идёт из `~/Docker/claudio-agent/` — там есть build секция.
|
||||||
>
|
|
||||||
> **Сборка образа**: `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.
|
Валера — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
|
||||||
|
|
||||||
Для получения всех сообщений в теме — **Events API polling** (отключён, после деплоя zulip-router будет удалён из кода):
|
- `events_polling.enabled: false` в config.yaml
|
||||||
- `events_polling.enabled: false` в config.yaml (текущее состояние)
|
|
||||||
- Сообщения приходят только через **outgoing webhook** (webhook_token в .env)
|
- Сообщения приходят только через **outgoing webhook** (webhook_token в .env)
|
||||||
- После @mention создаётся сессия; последующие сообщения без @mention обрабатываются
|
- После @mention создаётся сессия; последующие сообщения без @mention обрабатываются
|
||||||
|
|
||||||
**После перезапуска**: нужно один раз написать `@Валера <текст>` чтобы создать сессию в теме.
|
**После перезапуска**: написать `@Валера <текст>` чтобы создать сессию.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
## Диагностика молчания
|
## Диагностика молчания
|
||||||
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"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Патчи в коде (общие с Клавдием)
|
### 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`.
|
||||||
|
|
||||||
В `zulip_handler.go` добавлены:
|
### 2. В логах `command running → command handled` за секунду, без `received provider event`
|
||||||
1. `botName` — извлекается из bot_email (часть до @). Используется чтобы отличить свой @mention от чужого.
|
**Причина**: стухший embedded NATS. Swarm внутри процесса не доставляет команду до session/task actor'ов.
|
||||||
2. `extractOtherMention()` — проверяет текст на @**Name**, где Name не равен botName и не @**all**/@**everyone**. Если найден чужой @mention — сообщение игнорируется.
|
**Фикс**: `docker-compose restart` (если не помогло → `up -d`).
|
||||||
3. Детальное логирование в `handleAutoClaimMention` — видны все шаги от входа до ошибки.
|
|
||||||
|
### 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. OPENAI_BASE_URL — обязательная env var
|
### OPENAI_BASE_URL — обязательная env var
|
||||||
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Она читает env `OPENAI_BASE_URL`.
|
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env `OPENAI_BASE_URL`.
|
||||||
Без неё запросы уходят на `api.openai.com` (401).
|
Без неё запросы уходят на `api.openai.com` (401).
|
||||||
|
|
||||||
**В `.env` обязательно:**
|
**В `.env` обязательно:**
|
||||||
@@ -65,89 +90,12 @@ Norma не читает `base_url` из config.yaml для `provider: deepseek`.
|
|||||||
OPENAI_BASE_URL=https://api.deepseek.com/v1
|
OPENAI_BASE_URL=https://api.deepseek.com/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
**ВАЖНО:** `docker-compose restart` не перечитывает `.env`. Нужно `docker-compose up -d` (пересоздание контейнера).
|
### DOCKER_OPTS — пустая строка убивает старт
|
||||||
|
Должен быть валидный JSON:
|
||||||
### 2. DOCKER_OPTS — пустая строка убивает старт
|
|
||||||
Если `DOCKER_OPTS=""` (или пустая строка в `.env`), norma падает на парсинге float:
|
|
||||||
```
|
|
||||||
strconv.ParseFloat: parsing ""
|
|
||||||
```
|
|
||||||
|
|
||||||
**Должен быть валидный JSON:**
|
|
||||||
```
|
```
|
||||||
DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7}
|
DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. composerestart vs compose up -d
|
### composerestart vs compose up -d
|
||||||
- `docker-compose restart` — не перечитывает `.env`, не пересоздаёт контейнер
|
- `restart` — не перечитывает `.env`
|
||||||
- `docker-compose up -d` — пересоздаёт контейнер с обновлённым `.env`
|
- `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=<TOKEN>"
|
|
||||||
```
|
|
||||||
|
|
||||||
Для авторизации через Telegram (или Zulip с /start):
|
|
||||||
```
|
|
||||||
/start owner=<TOKEN>
|
|
||||||
```
|
|
||||||
|
|
||||||
При старте с пустым `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=<TOKEN>` в 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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -5,33 +5,50 @@
|
|||||||
| Item | Value |
|
| Item | Value |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| Repo | `~/Developer/balda/` (один код с Валерой) |
|
| Repo | `~/Developer/balda/` (один код с Валерой) |
|
||||||
|
| Ветка | `main` |
|
||||||
| Compose | `~/Docker/claudio-agent/docker-compose.yaml` → контейнер `claudio-agent-claudio-1` |
|
| Compose | `~/Docker/claudio-agent/docker-compose.yaml` → контейнер `claudio-agent-claudio-1` |
|
||||||
| Config | `~/Docker/claudio-agent/.config/balda/config.yaml` |
|
| Config | `~/Docker/claudio-agent/.config/balda/config.yaml` |
|
||||||
| Env | `~/Docker/claudio-agent/.env` |
|
| Env | `~/Docker/claudio-agent/.env` |
|
||||||
| Dockerfile | `~/Docker/claudio-agent/Dockerfile.claudio` |
|
| Dockerfile | `~/Docker/claudio-agent/Dockerfile.claudio` |
|
||||||
|
| Образ | `claudio-agent-claudio` (тагается как `balda-agent-valera` для Валеры) |
|
||||||
|
|
||||||
## Запуск / рестарт
|
## Запуск / рестарт
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/Docker/claudio-agent
|
cd ~/Docker/claudio-agent
|
||||||
docker-compose restart # без пересборки
|
docker-compose restart # без пересборки
|
||||||
docker-compose build && docker-compose up -d # после изменений в коде
|
docker-compose up -d # пересоздать контейнер
|
||||||
|
|
||||||
|
# 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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Валера и Клавдий — **один код и один образ** (`claudio-agent-claudio` → `balda-agent-valera`). Разница только в конфиге, `.env` и порте webhook (8091 vs 8092). Сборка всегда из `~/Docker/claudio-agent/`.
|
||||||
|
|
||||||
## Архитектура получения сообщений
|
## Архитектура получения сообщений
|
||||||
|
|
||||||
Клавдий — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
|
Клавдий — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
|
||||||
|
|
||||||
Для получения всех сообщений в теме — **Events API polling** (отключён, после деплоя zulip-router будет удалён из кода):
|
|
||||||
- `events_polling.enabled: false` в config.yaml (текущее состояние)
|
|
||||||
- Сообщения приходят только через **outgoing webhook**
|
|
||||||
|
|
||||||
## Отличие от Валеры
|
## Отличие от Валеры
|
||||||
|
|
||||||
- **Провайдер**: `claude` (claude-sonnet-4-6) через Claude Proxy (контейнер, порт 3457)
|
- **Провайдер**: `claude` (claude-sonnet-4-6) через Claude Proxy (контейнер, порт 3457)
|
||||||
- **Порт webhook**: 8092 (у Валеры 8091)
|
- **Порт webhook**: 8092 (у Валеры 8091)
|
||||||
- **База**: отдельная SQLite (NATS embedded)
|
- **База**: отдельная SQLite (NATS embedded)
|
||||||
|
|
||||||
|
## MCP Obsidian — конфиг
|
||||||
|
Аналогично Валере — URL обязательно с `/sse`:
|
||||||
|
```yaml
|
||||||
|
runtime:
|
||||||
|
mcp_servers:
|
||||||
|
obsidian:
|
||||||
|
type: sse
|
||||||
|
url: http://obsidian-mcp:3101/sse
|
||||||
|
name: obsidian
|
||||||
|
```
|
||||||
|
|
||||||
## Проблемы и решения
|
## Проблемы и решения
|
||||||
|
|
||||||
### Валера отвечал вместо Клавдия на @mention
|
### Валера отвечал вместо Клавдия на @mention
|
||||||
|
|||||||
Reference in New Issue
Block a user