[2026-06-18] taiga-vault: family/how-to/hermes-eagle-mac.md family/how-to/kraken-access.md family/how-to/switch-emulation-rom-infra.md personal/projects/balda/balda-valera.md personal/projects/balda/claudio-klaudiy.md personal/projects/balda/fast-rlm-integration.md personal/projects/balda/setup.md personal/projects/hermes-configs/eagle-config.example.yaml personal/projects/hermes-configs/whale-config.example.yaml personal/projects/personal-os/eagle-dashboard.md
This commit is contained in:
@@ -1,54 +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`
|
||||
- Config (persistent): `~/Docker/balda-agent/.config/balda/config.yaml`
|
||||
- Env: `~/Docker/balda-agent/.env`
|
||||
- Дублирующий compose `~/Developer/balda/compose.valera.yaml` — НЕ использовать (сеть `balda_default` без DNS)
|
||||
- Owner: `allowed_owners` в config.yaml (статически, `/start owner=` не нужен)
|
||||
|
||||
## Запуск / рестарт
|
||||
|
||||
```bash
|
||||
cd ~/Docker/balda-agent
|
||||
docker-compose restart
|
||||
docker-compose restart # без пересборки, только конфиг/env не менялись
|
||||
docker-compose up -d # пересоздать контейнер, перечитать .env
|
||||
|
||||
# Rebuild после изменений в коде:
|
||||
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
|
||||
```
|
||||
|
||||
> Валера и Клавдий — **один образ** (`balda-agent-valera`), собирается из одного Dockerfile (`Dockerfile.claudio`). Разница только в конфиге (`config.yaml`) и `.env`, которые монтируются volumes. Сборка идёт из `~/Docker/claudio-agent/` — там есть build секция.
|
||||
|
||||
## Архитектура получения сообщений
|
||||
Валера — **outgoing webhook bot** (bot_type=3). Zulip отправляет webhook только для @mention и DM.
|
||||
|
||||
Для получения всех сообщений в теме — Events API polling (горутина в коде):
|
||||
- `events_polling.enabled: true` в config.yaml
|
||||
- Очередь регистрируется с `all_public_streams=true`
|
||||
- Бот подписывается на все публичные стримы при старте (`SubscribeToPublicStreams`)
|
||||
- `events_polling.enabled: false` в config.yaml
|
||||
- Сообщения приходят только через **outgoing webhook** (webhook_token в .env)
|
||||
- После @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. Проверить что Events API polling запустился: `zulip events queue registered` в логах
|
||||
3. Стухший NATS-таск (симптом: логов нет после @mention)
|
||||
Фикс: `docker-compose restart` (NATS embedded, состояние в памяти)
|
||||
4. Бот не подписан на стримы (симптом: `processing zulip message trigger=stream` не появляется):
|
||||
```bash
|
||||
curl -u "<bot_email>:<api_key>" https://zulip.qentra.top/api/v1/users/me/subscriptions
|
||||
```
|
||||
Если пустой — должен подписаться при следующем старте. Или вручную через API.
|
||||
|
||||
## Патчи в коде (общие с Клавдием)
|
||||
### 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` добавлены:
|
||||
1. `botName` — извлекается из bot_email (часть до @). Используется чтобы отличить свой @mention от чужого.
|
||||
2. `extractOtherMention()` — проверяет текст на @**Name**, где Name не равен botName и не @**all**/@**everyone**. Если найден чужой @mention — сообщение игнорируется.
|
||||
3. Детальное логирование в `handleAutoClaimMention` — видны все шаги от входа до ошибки.
|
||||
### 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. OPENAI_BASE_URL — обязательная env var
|
||||
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Она читает env `OPENAI_BASE_URL`.
|
||||
### OPENAI_BASE_URL — обязательная env var
|
||||
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env `OPENAI_BASE_URL`.
|
||||
Без неё запросы уходят на `api.openai.com` (401).
|
||||
|
||||
**В `.env` обязательно:**
|
||||
@@ -56,37 +90,12 @@ Norma не читает `base_url` из config.yaml для `provider: deepseek`.
|
||||
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 — пустая строка убивает старт
|
||||
Должен быть валидный 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`.
|
||||
|
||||
## Конфигурация провайдера
|
||||
В `config.yaml` обязательно:
|
||||
```yaml
|
||||
balda:
|
||||
provider: deepseek
|
||||
zulip:
|
||||
events_polling:
|
||||
enabled: true
|
||||
stream_only_with_session: true
|
||||
runtime:
|
||||
providers:
|
||||
deepseek:
|
||||
openai:
|
||||
base_url: https://api.deepseek.com/v1
|
||||
```
|
||||
### composerestart vs compose up -d
|
||||
- `restart` — не перечитывает `.env`
|
||||
- `up -d` — пересоздаёт контейнер с обновлённым `.env`
|
||||
|
||||
@@ -5,33 +5,50 @@
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Repo | `~/Developer/balda/` (один код с Валерой) |
|
||||
| Ветка | `main` |
|
||||
| Compose | `~/Docker/claudio-agent/docker-compose.yaml` → контейнер `claudio-agent-claudio-1` |
|
||||
| Config | `~/Docker/claudio-agent/.config/balda/config.yaml` |
|
||||
| Env | `~/Docker/claudio-agent/.env` |
|
||||
| Dockerfile | `~/Docker/claudio-agent/Dockerfile.claudio` |
|
||||
| Образ | `claudio-agent-claudio` (тагается как `balda-agent-valera` для Валеры) |
|
||||
|
||||
## Запуск / рестарт
|
||||
|
||||
```bash
|
||||
cd ~/Docker/claudio-agent
|
||||
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.
|
||||
|
||||
Для получения всех сообщений в теме — **Events API polling**:
|
||||
- Подписан на все публичные стримы при старте
|
||||
- После @mention создаётся сессия; последующие сообщения без @mention обрабатываются
|
||||
|
||||
## Отличие от Валеры
|
||||
|
||||
- **Провайдер**: `claude` (claude-sonnet-4-6) через Claude Proxy (контейнер, порт 3457)
|
||||
- **Порт webhook**: 8092 (у Валеры 8091)
|
||||
- **База**: отдельная SQLite (NATS embedded)
|
||||
|
||||
## MCP Obsidian — конфиг
|
||||
Аналогично Валере — URL обязательно с `/sse`:
|
||||
```yaml
|
||||
runtime:
|
||||
mcp_servers:
|
||||
obsidian:
|
||||
type: sse
|
||||
url: http://obsidian-mcp:3101/sse
|
||||
name: obsidian
|
||||
```
|
||||
|
||||
## Проблемы и решения
|
||||
|
||||
### Валера отвечал вместо Клавдия на @mention
|
||||
@@ -48,4 +65,12 @@ docker-compose build && docker-compose up -d # после изменений в
|
||||
|
||||
### При первом запуске не отвечает на @mention
|
||||
|
||||
Нужен `/start owner=<token>` в DM. Токен генерируется при первом старте и пишется в лог: `docker logs claudio-agent-claudio-1 | grep -i owner`
|
||||
Если после запуска не отвечает на @mention — проверить в config.yaml `allowed_owners`:
|
||||
```yaml
|
||||
zulip:
|
||||
allowed_owners:
|
||||
- admin@zulip.local
|
||||
```
|
||||
Owner'ы прописаны в config.yaml статически, команда `/start owner=<token>` не нужна.
|
||||
|
||||
При необходимости — пересоздать контейнер: `docker-compose up -d`
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# fast-rlm MCP HTTP integration with Balda
|
||||
|
||||
## Цель
|
||||
Дать Балде (`balda-agent-valera-1`) инструмент рекурсивного сжатия контекста (RLM) через MCP **HTTP** сервер в отдельном Docker контейнере.
|
||||
|
||||
## Контекст
|
||||
- Хост: Eagle (Mac M4), Docker Desktop arm64
|
||||
- Балда: контейнер `balda-agent-valera-1`, compose в `/Users/admin/Docker/balda-agent/`
|
||||
- Конфиг Балды: `/Users/admin/Docker/balda-agent/.config/balda/config.yaml`
|
||||
- Текущие MCP у Балды: только `obsidian` (SSE, `http://obsidian-mcp:3101`)
|
||||
- fast-rlm ([avbiswas/fast-rlm](https://github.com/avbiswas/fast-rlm)): Python библиотека, MCP **клиент** (не сервер). CLI команды `serve` НЕ существует.
|
||||
- Docker tooling на хосте: только `docker-compose` v1, v2 plugin отсутствует.
|
||||
|
||||
## Провайдер LLM
|
||||
fast-rlm использует OpenAI-совместимый клиент (через переменные `OPENAI_API_KEY` + `OPENAI_BASE_URL`). Для консистентности с Балдой и Whale используем **DeepSeek** как у самой Балды:
|
||||
|
||||
- `OPENAI_BASE_URL=https://api.deepseek.com/v1`
|
||||
- `OPENAI_API_KEY=<значение DEEPSEEK_API_KEY>` (берём из `/Users/admin/Docker/balda-agent/.env`)
|
||||
- Дефолтная модель в `server.py`: `deepseek-chat`
|
||||
- `RLMConfig(primary_agent="deepseek-chat", sub_agent="deepseek-chat", ...)`
|
||||
|
||||
## Подтверждённый API fast-rlm
|
||||
- Top-level: `dir(fast_rlm) = ['RLMConfig', 'run']`
|
||||
- Импорт: `from fast_rlm import RLMConfig` (НЕ `from fast_rlm.config import RLMConfig` — этот путь не существует)
|
||||
- Сигнатура: `fast_rlm.run(query, prefix, config, verbose, output_schema, tools, env_variables, mcp_servers, llm_kwargs)` → возвращает dict с ключом `results`
|
||||
- Дефолты `RLMConfig`: `primary_agent='z-ai/glm-5'`, `sub_agent='minimax/minimax-m2.5'`, `max_depth=3`, `max_money_spent=0.2`, `truncate_len=2000`. Мы их переопределяем на `deepseek-chat`.
|
||||
- fast-rlm требует **Deno** (для Pyodide REPL) — ставится в Dockerfile.
|
||||
|
||||
## Архитектура
|
||||
fast-rlm — это библиотека (`import fast_rlm; fast_rlm.run(...)`). Чтобы Балда могла её использовать через MCP, нужна **обёртка** — отдельный сервис, который экспонирует `fast_rlm.run()` как MCP tool через HTTP-транспорт.
|
||||
|
||||
```
|
||||
┌──────────────────┐ MCP HTTP ┌──────────────────────┐
|
||||
│ balda-agent │ ──────────────────► │ fast-rlm-mcp │
|
||||
│ (Hermes) │ POST /mcp │ FastMCP + mcp[cli] │
|
||||
└──────────────────┘ │ ↓ in-process │
|
||||
│ fast_rlm.run(...) │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
Обёртка экспонирует один MCP tool: `recursive_context_compress(text, model, budget)`.
|
||||
|
||||
## Файлы (создать)
|
||||
- `/Users/admin/Docker/fast-rlm-mcp/Dockerfile` — `python:3.12-slim` + Deno + fast-rlm + mcp[cli] + uvicorn
|
||||
- `/Users/admin/Docker/fast-rlm-mcp/server.py` — MCP HTTP сервер (FastMCP, transport `streamable-http`, port 3333)
|
||||
- `/Users/admin/Docker/fast-rlm-mcp/docker-compose.yaml` — сервис `fast-rlm-mcp`, порт 3333, в сети `balda_default` external
|
||||
- `/Users/admin/Docker/fast-rlm-mcp/requirements.txt` — `fast-rlm`, `mcp[cli]>=1.2`, `uvicorn[standard]`
|
||||
- `/Users/admin/Docker/fast-rlm-mcp/.env` — `OPENAI_API_KEY=<DEEPSEEK_API_KEY value>`, `OPENAI_BASE_URL=https://api.deepseek.com/v1`, `FAST_RLM_DEFAULT_MODEL=deepseek-chat`. Chmod 600.
|
||||
|
||||
## Изменения существующих файлов
|
||||
`/Users/admin/Docker/balda-agent/.config/balda/config.yaml` — добавить:
|
||||
```yaml
|
||||
mcp_servers:
|
||||
obsidian:
|
||||
type: sse
|
||||
url: http://obsidian-mcp:3101
|
||||
name: obsidian
|
||||
fast-rlm:
|
||||
type: http
|
||||
url: http://fast-rlm-mcp:3333/mcp
|
||||
name: fast-rlm
|
||||
```
|
||||
|
||||
## Шаги выполнения (каждый шаг = реальная проверка)
|
||||
|
||||
### 1. Подтвердить API fast-rlm
|
||||
- **Действие:** `docker run --rm python:3.12-slim sh -c "pip install fast-rlm -q && python -c 'import fast_rlm; help(fast_rlm.run)'"`
|
||||
- **Проверка:** есть функция `fast_rlm.run(...)` с задокументированной сигнатурой. Импорт `from fast_rlm import RLMConfig` работает.
|
||||
- **Статус:** ✅ Сделано.
|
||||
|
||||
### 2. Создать структуру обёртки
|
||||
- **Действие:** `mkdir -p /Users/admin/Docker/fast-rlm-mcp/`, написать 5 файлов (Dockerfile, server.py, docker-compose.yaml, requirements.txt, .env).
|
||||
- **Проверка:** `ls -la /Users/admin/Docker/fast-rlm-mcp/` показывает все 5 файлов с ненулевым размером.
|
||||
|
||||
### 3. Собрать образ
|
||||
- **Действие:** `cd /Users/admin/Docker/fast-rlm-mcp && docker-compose build` (v1, не v2!)
|
||||
- **Проверка:** `docker images | grep fast-rlm-mcp` — образ присутствует. Сборка завершилась без ошибок.
|
||||
|
||||
### 4. Запустить контейнер
|
||||
- **Действие:** `docker-compose up -d`
|
||||
- **Проверка:** `docker ps --format '{{.Names}}\t{{.Status}}' | grep fast-rlm-mcp` — статус `Up`. `docker logs --tail 50 fast-rlm-mcp` — без `Traceback` / `Error`.
|
||||
|
||||
### 5. Smoke-тест HTTP MCP endpoint
|
||||
- **Действие:** `curl -sS -X POST http://localhost:3333/mcp -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'`
|
||||
- **Проверка:** JSON-ответ HTTP 200, в массиве `tools` есть `recursive_context_compress`.
|
||||
|
||||
### 6. Подключить к сети Балды
|
||||
- **Действие:** убедиться что контейнер в одной docker-сети с `balda-agent-valera-1` (сеть `balda_default`, external).
|
||||
- **Проверка:** `docker network inspect balda_default` показывает оба контейнера. Из Балды: `docker exec balda-agent-valera-1 wget -qO- http://fast-rlm-mcp:3333/mcp` отвечает.
|
||||
|
||||
### 7. Обновить config.yaml Балды
|
||||
- **Действие:** добавить секцию `fast-rlm` в `mcp_servers`.
|
||||
- **Проверка:** `grep -A3 fast-rlm config.yaml` показывает запись. YAML парсится.
|
||||
|
||||
### 8. Перезапустить Балду
|
||||
- **Действие:** `cd /Users/admin/Docker/balda-agent && docker-compose restart`
|
||||
- **Проверка:** `docker logs --since 60s balda-agent-valera-1` → "MCP server connected: fast-rlm". Без `Failed`. Балда онлайн в Zulip.
|
||||
|
||||
### 9. End-to-end smoke test
|
||||
- **Действие:** в Zulip Балде дать задачу с явным использованием fast-rlm.
|
||||
- **Проверка:** в логах Балды виден вызов tool `recursive_context_compress`. Возвращается результат. Без ошибок.
|
||||
|
||||
## Откат
|
||||
`cd /Users/admin/Docker/fast-rlm-mcp && docker-compose down` + убрать entry из config.yaml + `restart` Балды → состояние до начала.
|
||||
|
||||
## Признанные галлюцинации в этой сессии
|
||||
1. msg 58792-58795: фабрикация "Container Started / Ready" — реально ничего не создавалось.
|
||||
2. Утверждение что `fast-rlm serve --port 3333` существует — такой команды НЕТ. fast-rlm — только клиент.
|
||||
3. Старая запись в памяти про "Балда на Кракене" — устаревшая, противоречит документации. Балда на Eagle.
|
||||
4. Попытка писать доку через native `Write`/`Edit` в `/Users/admin/obsidian/...` из Linux-контейнера — файл попадает в контейнерную ФС, не в реальный vault. **Правильный путь — `mcp_obsidian_write_note` / `mcp_obsidian_patch_note`**.
|
||||
5. Утверждение `from fast_rlm.config import RLMConfig` — такого пути в библиотеке нет, импорт top-level: `from fast_rlm import RLMConfig`.
|
||||
6. `docker compose` (v2) на хосте не установлен — нужен `docker-compose` (v1).
|
||||
7. Идея использовать OpenRouter (`z-ai/glm-5`) как дефолт — отвергнута: используем DeepSeek как у Балды/Whale.
|
||||
8. Утверждение про "актуализированную доку" 16 июня — на хост-FS дока НЕ менялась (правки шли в контейнерную FS). Реальная актуализация через `mcp_obsidian_write_note` — 2026-06-16.
|
||||
|
||||
## Что НЕ делаем без явного подтверждения
|
||||
- Не пушить изменения в git.
|
||||
- Не правим config.yaml до согласования итогового формата секции `fast-rlm`.
|
||||
- Не рестартим Балду без согласования.
|
||||
|
||||
## История
|
||||
- **2026-06-16 ~14:00**: создан после серии фабрикаций и компакций контекста. План построен с явными точками реальной проверки на каждом шаге.
|
||||
- **2026-06-16 ~16:00**: пользователь поправил — провайдер LLM = DeepSeek (как у Балды/Whale), не OpenAI/OpenRouter.
|
||||
- **2026-06-16 ~17:00**: актуализация через `mcp_obsidian_write_note` — добавлены секции "Провайдер LLM" и "Подтверждённый API", compose v1 вместо v2, сеть `balda_default` вместо `balda-agent_default`, файл `.env` в список, Deno в Dockerfile, галлюцинации 5–8.
|
||||
@@ -1,188 +1,84 @@
|
||||
# Balda Setup & Status
|
||||
# Balda — Setup & Config
|
||||
|
||||
> Last updated: 2026-06-11
|
||||
_Последнее обновление: 2026-06-16_
|
||||
|
||||
## Runtime
|
||||
## Хост
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Binary | `~/Developer/balda/balda` (built from source) |
|
||||
| Process | Managed by launchd (`com.normahq.balda`) |
|
||||
| Log | `/private/tmp/balda.log` |
|
||||
| Config | `~/Developer/balda/.config/balda/config.yaml` |
|
||||
| State dir | `~/Developer/balda/.config/balda/` |
|
||||
| Sessions | SQLite persistence |
|
||||
| Provider | `deepseek` (openai type, model: `deepseek-chat`, via `https://api.deepseek.com/v1`) |
|
||||
Mac (Eagle), Docker Desktop (arm64 native). Контейнеры собираются из исходников в `~/Developer/balda/`.
|
||||
|
||||
Balda is managed by launchd. Full restart (reloads plist env vars):
|
||||
## Компоненты
|
||||
|
||||
| Бот | Порт | Провайдер | Dockerfile |
|
||||
|-----|------|-----------|------------|
|
||||
| **Валера** (`balda-agent-valera-1`) | `:8091` | DeepSeek | `Dockerfile` (одинаковый) |
|
||||
| **Клавдий** (`claudio-agent-claudio-1`) | `:8092` | Claude (через proxy :3457) | `Dockerfile.claudio` (тот же + arm64 target) |
|
||||
|
||||
## Расположение
|
||||
|
||||
| Item | Валера | Клавдий |
|
||||
|------|--------|---------|
|
||||
| Compose | `~/Docker/balda-agent/docker-compose.yaml` | `~/Docker/claudio-agent/docker-compose.yaml` |
|
||||
| Config volume | `~/Docker/balda-agent/.config/balda/` | `~/Docker/claudio-agent/.config/balda/` |
|
||||
| Env | `~/Docker/balda-agent/.env` | `~/Docker/claudio-agent/.env` |
|
||||
| Dev repo (общий) | `~/Developer/balda/` | `~/Developer/balda/` |
|
||||
|
||||
**Важно:** оба бота собираются из одного репозитория `~/Developer/balda/` (normahq/balda fork). Разница — только в config.yaml (провайдер) и .env (Zulip credentials).
|
||||
|
||||
## Образ
|
||||
|
||||
- Собирается локально из `Dockerfile` (Go, multi-stage: golang:1.26 → alpine:3)
|
||||
- Target: `linux/arm64` (статический Go бинарник)
|
||||
- Image name: `balda-agent-valera:latest` / `claudio-agent-claudio:latest` (по имени compose проекта)
|
||||
- Entrypoint: `/usr/local/bin/balda` (ELF arm64)
|
||||
- Сеть: `balda_default` (bridge, внешняя, создаётся через docker network create)
|
||||
|
||||
## Запуск / рестарт
|
||||
|
||||
**Валера:**
|
||||
```bash
|
||||
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.normahq.balda.plist
|
||||
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.normahq.balda.plist
|
||||
cd ~/Docker/balda-agent
|
||||
docker-compose restart # без пересборки, без перечитывания .env
|
||||
docker-compose up -d # пересоздаёт контейнер с текущим .env
|
||||
docker-compose build && docker-compose up -d # после изменений в коде
|
||||
```
|
||||
|
||||
Quick restart (does NOT reload plist env vars — use bootout/bootstrap for env changes):
|
||||
|
||||
**Клавдий:**
|
||||
```bash
|
||||
launchctl kickstart -k gui/$(id -u)/com.normahq.balda
|
||||
cd ~/Docker/claudio-agent
|
||||
# те же команды
|
||||
```
|
||||
|
||||
## Zulip Webhook
|
||||
**После изменений кода** (в `~/Developer/balda/`): `build && up -d` в каждом compose.
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Status | ENABLED |
|
||||
| Listen address | `0.0.0.0:8091` |
|
||||
| Path | `/zulip/webhook` |
|
||||
| Webhook bot URL | `http://host.docker.internal:8091/zulip/webhook` |
|
||||
## .env (общие переменные)
|
||||
|
||||
Port was **changed from 8090 → 8091** on 2026-06-07 (see Port Conflict below).
|
||||
|
||||
## Architecture
|
||||
|
||||
Balda uses the **outgoing webhook** mechanism — Zulip pushes events to Balda's
|
||||
HTTP endpoint. This differs from Eagle/Kit which use polling.
|
||||
|
||||
- Zulip runs in Docker (container: `zulip-zulip-1`), exposed on `127.0.0.1:8000`
|
||||
- Balda runs natively on Mac (not in Docker)
|
||||
- Colima maps `host.docker.internal:PORT → 127.0.0.1:PORT` on the Mac host,
|
||||
so the bot URL uses `host.docker.internal` to reach Balda from inside Docker
|
||||
|
||||
## Soul / Prompt
|
||||
|
||||
The `global_instruction` field in `config.yaml` defines Balda's persona (Валера).
|
||||
The canonical source is `soul.md` in this folder — sync manually to config after edits,
|
||||
then restart Balda.
|
||||
|
||||
Provider-level `system_instructions` are documented in `workspace-context.md`.
|
||||
|
||||
---
|
||||
|
||||
## Bug Fixed 2026-06-07 — Context Canceled on First DB Operation
|
||||
|
||||
**File:** `zulip_handler.go`, line 236
|
||||
|
||||
**Root cause:** The message-processing goroutine was launched with the HTTP
|
||||
request context:
|
||||
|
||||
```go
|
||||
// Before (broken)
|
||||
go h.processMessage(r.Context(), payload)
|
||||
```env
|
||||
OPENAI_BASE_URL=https://api.deepseek.com/v1 # обязательна для deepseek provider
|
||||
# для claude provider не нужна
|
||||
```
|
||||
|
||||
When the handler returns HTTP 200, Go cancels `r.Context()`. The goroutine then
|
||||
hits its first database operation and fails with `context canceled`.
|
||||
**Важно:** `docker-compose restart` не перечитывает `.env`. Только `docker-compose up -d`.
|
||||
|
||||
**Fix:**
|
||||
## Известные грабли
|
||||
|
||||
```go
|
||||
// After (fixed)
|
||||
go h.processMessage(context.WithoutCancel(r.Context()), payload)
|
||||
### 1. OPENAI_BASE_URL — обязательна для DeepSeek
|
||||
Norma не читает `base_url` из config.yaml для `provider: deepseek`. Читает env `OPENAI_BASE_URL`.
|
||||
Без неё запросы уходят на `api.openai.com` (401).
|
||||
|
||||
### 2. DOCKER_OPTS — пустая строка убивает старт
|
||||
```env
|
||||
DOCKER_OPTS={"max_tokens": 2048, "temperature": 0.7}
|
||||
```
|
||||
Не пустая строка! Иначе `strconv.ParseFloat: parsing ""` при старте.
|
||||
|
||||
`context.WithoutCancel` creates a copy of the parent context that is never
|
||||
canceled when the parent is — so the goroutine lives past the HTTP handler return.
|
||||
### 3. exec format error на arm64
|
||||
Симптом: контейнер в crash-цикле, логи повторяют `exec /usr/local/bin/balda: exec format error`.
|
||||
Docker показывает `Up N hours` (накопленное время рестартов), но exec не работает.
|
||||
|
||||
---
|
||||
**Причина:** контейнер создан с `Platform: linux/amd64` (старый Docker Desktop или первый run без platform). Образ arm64, контейнер amd64 — несовместимость.
|
||||
|
||||
## Port Conflict 2026-06-07 — Moved 8090 → 8091
|
||||
**Фикс:** `docker-compose down; docker-compose up -d` — удаляет старый контейнер и создаёт новый с правильной платформой.
|
||||
|
||||
**Root cause:** Colima (Docker runtime) maps `host.docker.internal:PORT →
|
||||
127.0.0.1:PORT` on the Mac host. `openclaw/claude-proxy` was occupying
|
||||
`127.0.0.1:8090`, intercepting Balda's webhook traffic before it reached Balda.
|
||||
|
||||
**Solution:** Changed Balda's listen port from `8090` to `8091` in `config.yaml`.
|
||||
|
||||
### Resolution
|
||||
|
||||
Webhook bot URL updated in Zulip DB via `docker exec psql` on 2026-06-07:
|
||||
`http://host.docker.internal:8090/zulip/webhook` → `http://host.docker.internal:8091/zulip/webhook`
|
||||
|
||||
---
|
||||
|
||||
## Smokescreen SSRF Proxy — Allow Private Ranges (Fixed 2026-06-11)
|
||||
|
||||
**Symptom:** Zulip outgoing webhook to Balda fails with HTTP 407.
|
||||
Zulip logs: `client: OutgoingWebhookResponse` + "Failure! Third party responded with 407".
|
||||
|
||||
**Root cause:** Zulip runs Smokescreen (SSRF proxy) for all outgoing HTTP.
|
||||
`host.docker.internal` resolves to a private IP (`192.168.5.x`), which Smokescreen
|
||||
blocks by default. A `docker restart` does NOT recreate containers, so env vars
|
||||
added to docker-compose aren't picked up — must use `docker compose up -d`.
|
||||
|
||||
**Fix:** Added to `~/Developer/zulip/docker-compose.yml` under the `zulip` service env:
|
||||
|
||||
```yaml
|
||||
PROXY_ALLOW_RANGES: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
||||
```
|
||||
|
||||
Then recreated containers:
|
||||
|
||||
```bash
|
||||
cd ~/Developer/zulip
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Smokescreen now starts with `--allow-range` flags for all RFC1918 ranges.
|
||||
Verify with: `docker exec zulip-zulip-1 ps aux | grep smokescreen`
|
||||
|
||||
**Note:** `SETTING_ALLOW_BOTS_TO_MAKE_REQUESTS_TO_PRIVATE_ADDRESSES: "True"` was already
|
||||
set but doesn't affect Smokescreen's `--allow-range` flags — it controls a different
|
||||
check. `PROXY_ALLOW_RANGES` is the correct env var.
|
||||
|
||||
---
|
||||
|
||||
## DeepSeek Provider via norma-local Fork
|
||||
|
||||
Balda's `openai` provider type in norma hardcoded `api.openai.com`. To support
|
||||
DeepSeek (or any OpenAI-compatible API), the norma source was forked locally:
|
||||
|
||||
- Fork path: `~/Developer/norma-local`
|
||||
- Patched files:
|
||||
- `pkg/runtime/hostedagent/openai.go` — added `openAIBaseURL()` reading `OPENAI_BASE_URL` env var
|
||||
- `pkg/runtime/agentfactory/agentfactory.go` — removed MCP restriction for `openai` type
|
||||
- Env var `OPENAI_BASE_URL=https://api.deepseek.com/v1` is set in the launchd plist
|
||||
- go.mod `replace` directive (`normahq/norma v0.0.6 → ../norma-local`) is local only
|
||||
(not committed to the PR branch — needs a separate norma upstream PR or fork)
|
||||
|
||||
To rebuild after patching norma:
|
||||
|
||||
```bash
|
||||
cd ~/Developer/balda
|
||||
go build -o balda ./cmd/balda/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLAUDECODE Environment Bug (Fixed 2026-06-07)
|
||||
|
||||
When launched from an Eagle terminal session, Balda inherited `CLAUDECODE=1`,
|
||||
which caused the Claude Code CLI to refuse to start (it detected a nested invocation).
|
||||
**Fix:** Use `launchctl bootout + bootstrap` to start Balda clean — launchd does
|
||||
not inherit parent shell env vars.
|
||||
|
||||
---
|
||||
|
||||
## Config Snippet (key sections)
|
||||
|
||||
```yaml
|
||||
balda:
|
||||
global_instruction: |
|
||||
<contents of soul.md>
|
||||
|
||||
runtime:
|
||||
providers:
|
||||
deepseek:
|
||||
type: openai
|
||||
openai:
|
||||
api_key: "<DeepSeek API key>"
|
||||
model: deepseek-chat
|
||||
|
||||
zulip:
|
||||
webhook:
|
||||
enabled: true
|
||||
listen: "0.0.0.0:8091"
|
||||
path: /zulip/webhook
|
||||
allowed_owners:
|
||||
- amartemyanov@duckduckgo.com
|
||||
```
|
||||
### 4. После перезапуска нужно @mention для создания сессии
|
||||
Events API polling подхватывает сообщения только если есть активная сессия в теме.
|
||||
После рестарта: написать `@Валера <текст>` (или `@Клавдий`).
|
||||
|
||||
Reference in New Issue
Block a user