Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
40d4087
docs: add local debate lab runbook and findings
artemtrofymenko Aug 10, 2026
fb945b7
docs: drop the bad MAX_HISTORY_BYTES value and record the startup floors
artemtrofymenko Aug 10, 2026
a9f6926
docs: record tool-call reliability as the metric that picks a local m…
artemtrofymenko Aug 10, 2026
900b26c
docs: workflows cannot drive owned agents on the release build
artemtrofymenko Aug 10, 2026
42081bf
docs: the respond-to workaround is verified, not speculative
artemtrofymenko Aug 10, 2026
40ed4bd
docs: what the debates actually showed, and the workflow that ran them
artemtrofymenko Aug 10, 2026
2a80b45
docs: a workflow that runs but cannot be seen or stopped
artemtrofymenko Aug 10, 2026
57276cb
docs: give Gemma4 a realistic turn budget, note two engine limits
artemtrofymenko Aug 10, 2026
c7839e7
docs: the recommended prompt workaround is worse here, and why
artemtrofymenko Aug 10, 2026
783a9cd
docs: the isolated benchmark overstates what a local model does in th…
artemtrofymenko Aug 10, 2026
088d78f
docs: prove the sibling gate instead of quoting the code comment
artemtrofymenko Aug 15, 2026
de7f2e4
docs: the measurements are from 0.5.8, not the version we wrote down
artemtrofymenko Aug 15, 2026
9df7d2d
docs: re-check the findings on 0.5.14, and fix the advice that misled me
artemtrofymenko Aug 15, 2026
1e8f286
docs: close out findings 8 and 10, and correct what fail-closed means
artemtrofymenko Aug 15, 2026
16a2748
docs: catch finding 10 in the act, at the SQL level
artemtrofymenko Aug 15, 2026
07c2632
docs: the alias-seeding script trades a loud 404 for a silent empty w…
artemtrofymenko Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions docs/local-debate-lab/DEBATE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
# Дебати різних моделей у Buzz — ранбук

Кілька агентів на різних рантаймах сидять в одному каналі й ведуть
структуровану дискусію. Один на Claude Code через OAuth, решта — на локальних
моделях через Ollama. Усе на власному релеї, піднятому в Docker.

Перевірено 2026-08-10 на Windows 11, RTX 4080 Laptop (12 ГБ VRAM),
Buzz desktop 0.5.8, Ollama 0.32.6. Ранбук цілком на 0.5.14 не переганяли, але
знахідки, на яких він тримається, там переперевірені — див.
[FINDINGS.md](FINDINGS.md).

Супутні документи:
[DEBATE_SETUP.md](DEBATE_SETUP.md) — чому саме такі значення конфігу;
[FINDINGS.md](FINDINGS.md) — знахідки про сам продукт.

---

## Крок 0 — релей

Windows-збірка десктопа везе всередині `buzz-acp`, `buzz-agent`, `buzz-dev-mcp`
і `buzz` CLI, тож збирати Rust не треба. Релей піднімається з
[../../deploy/compose](../../deploy/compose) готовим образом.

```bash
cd deploy/compose
cp .env.example .env
./run.sh start
```

**У `.env` пиши `127.0.0.1`, а не `localhost`:**

```
BUZZ_DOMAIN=127.0.0.1
RELAY_URL=ws://127.0.0.1:3000
BUZZ_MEDIA_BASE_URL=http://127.0.0.1:3000/media
BUZZ_MEDIA_SERVER_DOMAIN=127.0.0.1
```

Це не косметика. Релей резолвить спільноту з Host-заголовка й fail-closed падає
в 404 на незнайомому хості, а десктоп нормалізує будь-який loopback до
`127.0.0.1`, перш ніж передати адресу агентові. Якщо засіяти спільноту під
`localhost`, **жоден агент не під'єднається** — деталі в
[FINDINGS.md](FINDINGS.md#1).

Для локальної лабораторії також варто вимкнути закритий режим:

```
BUZZ_REQUIRE_AUTH_TOKEN=false
BUZZ_REQUIRE_RELAY_MEMBERSHIP=false
BUZZ_ALLOW_NIP_OA_AUTH=true
BUZZ_AUTO_MIGRATE=true
```

`BUZZ_ALLOW_NIP_OA_AUTH` лишається увімкненим — на ньому тримається
взаємодія агентів між собою (див. «Гейт доступу» нижче).

**Перевірка** — WebSocket-апгрейд має віддати 101, а не 404:

```bash
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
http://127.0.0.1:3000/
```

`/_liveness` для цієї перевірки не годиться: він віддає 200 на будь-якому
хості, зокрема на незмаплених. Саме тому «релей живий» і «агент під'єднається»
— різні твердження.

У десктопі приєднайся до спільноти за адресою `ws://127.0.0.1:3000`.

---

## Крок 1 — хмарні рантайми

### Claude Code

```bash
npm install -g @agentclientprotocol/claude-agent-acp
claude setup-token
```

Токен, який видасть `setup-token`, поклади у змінну оточення
`CLAUDE_CODE_OAUTH_TOKEN` (User scope). API-ключ не потрібен — вистачає
підписки.

> `claude auth status` **нічого не каже** про готовність ACP-рантайму: вони
> читають різні сховища. За один день на тій самій машині проба розійшлася з
> реальністю в обидва боки — спершу «залогінений» на неробочому рантаймі, потім
> «не залогінений» на робочому. Buzz використовує саме цю перевірку як
> auth-probe, тож бейджу в UI не вір у жодному напрямку — перевіряй ACP-циклом.
> Деталі в [FINDINGS.md](FINDINGS.md#2).

### Codex

```bash
npm install -g @openai/codex @agentclientprotocol/codex-acp
codex login
```

ChatGPT-OAuth працює — `OPENAI_API_KEY` не потрібен, попри те що каже
[README крейта buzz-acp](../../crates/buzz-acp/README.md).

### Як переконатись, що рантайм справді робочий

Мінімальний ACP-цикл, без Buzz у ланцюзі:
`initialize` → `session/new` → `session/prompt`. Якщо він повертає
`stopReason: end_turn` — авторизація й модель у порядку, і подальші проблеми
шукай у релеї, а не в рантаймі.

---

## Крок 2 — локальні моделі

Ollama за замовчуванням обслуговує **8192** токени контексту, хоч би що
заявляли самі моделі. Змінна `OLLAMA_CONTEXT_LENGTH` вимагає рестарту сервера
і легко лишається непоміченою, тож надійніше зашити контекст у похідні моделі:

```bash
printf 'FROM qwen3.5:9b\nPARAMETER num_ctx 32768\n' > Modelfile.qwen
printf 'FROM gemma4:12b\nPARAMETER num_ctx 32768\n' > Modelfile.gemma
ollama create qwen3.5-debate -f Modelfile.qwen
ollama create gemma4-debate -f Modelfile.gemma
```

Похідна модель посилається на ті самі блоби, тож місця майже не займає, а
налаштування переживає будь-який рестарт.

**Перевірка** — колонка `CONTEXT` має показати нове значення, а `PROCESSOR`
бажано `100% GPU`:

```bash
ollama ps
```

Модель, яка не вміщується у VRAM, з'їжджає частково на CPU й дає кратно меншу
швидкість. Заміри для конкретного заліза — у [DEBATE_SETUP.md](DEBATE_SETUP.md).

---

## Крок 3 — агенти

**Agents → Create an agent.**

### Спільні поля локальних агентів

| Поле | Значення |
|---|---|
| Runtime | `Buzz Agent` |
| Provider | **OpenAI-compatible** ← не «OpenAI» |

```
OPENAI_COMPAT_BASE_URL=http://localhost:11434/v1
OPENAI_COMPAT_API_KEY=ollama
BUZZ_AGENT_MAX_CONTEXT_TOKENS=32768
BUZZ_AGENT_MAX_OUTPUT_TOKENS=2048
BUZZ_AGENT_MAX_TOOL_RESULT_TEXT_BYTES=8000
BUZZ_AGENT_REQUIRE_REPLY=1
```

> **Не чіпай `BUZZ_AGENT_MAX_HISTORY_BYTES`.** Здається логічним зрізати його
> під невелике вікно, але це байтова межа стенограми, а не конкурент токенному
> вікну, і крейт має жорсткий поріг: значення нижче `MAX_PROMPT_BYTES`
> (1 048 576) валить агента на старті з `config: BUZZ_AGENT_MAX_HISTORY_BYTES
> (…) must be >= MAX_PROMPT_BYTES (1048576)`. Реальний дефолт — 16 МіБ, попри
> те що README крейта каже 1 МіБ.

> **Провайдер саме `openai-compat`.** При значенні `openai` десктоп фільтрує
> список моделей до відомих йому текстових моделей OpenAI, і жодна модель
> Ollama у пікері не з'явиться — виглядає так, ніби Ollama не підключена.
> Крейт `buzz-agent` обидва написання приймає однаково.

`OPENAI_COMPAT_API_KEY` має бути непорожнім: крейт відкидає порожнє значення,
а Ollama його ігнорує.

### Системний промпт: тільки «як опублікувати»

Перевірено 15/15 прогонів. Роль і завдання **не** кладемо сюди — вони йдуть у
повідомленні каналу, де їх можна міняти без ризику:

```
Ти учасник дебатів.

ЯК ТИ ВІДПОВІДАЄШ (єдиний спосіб):
Ти НЕ відповідаєш текстом. Щоб сказати щось, виклич інструмент shell з командою:
buzz messages send --channel <UUID каналу> --content "твій текст"
Виклич його один раз за хід.
У команді НЕ вживай --reply-to: інакше відповідь потрапить у тред, і в каналі її ніхто не побачить.
```

**Не дописуй сюди нічого зайвого.** Заміри показали, що додаткові правила
знижують надійність слабших моделей: три уточнення про лапки збили
`qwen3.5-debate` з 4/5 до 2/5 коректних команд. Кожен доданий рядок треба
переміряти.

Рядок про `--reply-to` натомість обов'язковий — без нього агент відповідає в
тред, і в стрічці каналу його не видно.

### Ролі — у повідомленні каналу, не в промпті

Системний промпт в усіх агентів однаковий (див. вище). Роль призначається
текстом раунду, і тоді її можна змінити на ходу — наприклад помінявши сторони
місцями — не чіпаючи налаштувань:

```
@Claude ти пропонент: захищай тезу «…». Аргументуй механізмом або прикладом.
@Codex ти скептик: атакуй найсильнішу версію його аргументу, не карикатуру.
@Gemma4 ти модератор: підсумуй, у чому саме вони не згодні.
```

Такий поділ вийшов не з міркувань стилю, а з замірів: усе, що дописували в
системний промпт понад інструкцію «як опублікувати», знижувало надійність
виклику інструмента у слабших моделей.

### Кого брати локальним дебатером

| Модель | Коректних команд | Придатність |
|---|---|---|
| `gemma4-debate` | 15/15 | єдина повністю надійна |
| `qwen3.5-debate` | 4/5 і 2/5 залежно від промпту | ламає форму команди |
| `ornith:9b` | 3/4 | межова |
| `lfm2.5:8b` | 0/4 | непридатна |
| `qwen3.6-debate` (27B) | не заміряна | 4 ток/с, 46% на CPU при 12 ГБ VRAM |

Повна методика й числа — у [DEBATE_SETUP.md](DEBATE_SETUP.md).

Додай усіх у канал і запусти кожного (play-бейдж на аватарі).

### Гейт доступу

Міняти нічого не треба. Дефолтний `respond-to: owner-only` впускає власника
**і всіх криптографічно верифікованих агентів того самого власника** — саме те,
що потрібно для дебатів. На цьому ж тримається вбудована Welcome Team, де лід
інструктує тіммейтів.

Не перемикай на `anyone` на релеї, доступному поза localhost.

---

## Крок 4 — раунд

Готовий воркфлоу на три раунди: [debate-workflow.yaml](debate-workflow.yaml).
Створюється через **Settings → Experiments → Workflows**, далі
`Create Workflow` → `<> Edit as YAML`. Веде чергу сам: пише учаснику, чекає,
пише наступному — стан тримає рушій, а не модель.

Два правила, які довелося вивести дорогою ціною:

- **Тема має стояти в кожному кроці.** Локальні моделі не знаходять її в
історії каналу. У YAML вона підставляється через `{{trigger.text}}`.
- **Тригер — `str_starts_with`, не `str_contains`.** Інакше крок, який цитує
тему, містить тригерну фразу й запускає новий прогін. З `str_starts_with`
цитата всередині тексту безпечна.

Запуск — повідомлення, що **починається** з `ДЕБАТИ СТАРТ`, далі тема.

### Або вручну



```
Тема раунду: чи замінять локальні моделі хмарні протягом трьох років?

@Пропонент захищай тезу «замінять».
@Скептик атакуй її.
@Модератор після їхніх реплік підсумуй, у чому саме вони не згодні.
```

Далі достатньо писати `@Пропонент відповідай на заперечення` — агенти бачать
історію каналу.

### Методика

- **Проводь кожне питання двічі, помінявши сторони.** Інакше різниця між
моделями змішана з тим, який бік аргументу їм дістався, і порівняння нічого
не означає.
- **Не став найслабшу модель суддею.** Або оцінює людина, або хмарна модель
судить локальні — не навпаки.
- **Не проси точну кількість слів.** «Рівно 100 слів» заганяє reasoning-модель
у цикл підрахунку: у замірах вона витрачала 2500 токенів і не видавала
жодного видимого слова. Обмеження став як максимум.

---

## Якщо агент мовчить

**Спершу панель Activity** — клік по агенту → вкладка Activity. Вона показує
кожен виклик інструмента разом з аргументом:

- `Ran buzz messages send --channel …` — агент публікує правильно.
- `Ran <проза>` — модель поклала текст повідомлення в параметр `command`
замість командного рядка. Класична поламка слабшої локальної моделі.
- лише `Thinking` без жодного `Ran` — модель не викликає інструмент узагалі.

Лог харнеса на рівні `info` викликів інструментів **не показує**, тому мовчання
там виглядає однаково в усіх трьох випадках. Для повного логу постав
`RUST_LOG=buzz_acp=info,buzz_agent=debug` у змінних агента — ключ не
зарезервований.

Далі — лог харнеса:

```
%APPDATA%\xyz.block.buzz.app\agents\logs\<pubkey>__<relay>.log
```

Робочий старт виглядає так:

```
connected to relay at ws://127.0.0.1:3000
owner resolved from BUZZ_AUTH_TAG: <pubkey>
discovered N channel(s)
presence set to online
```

Найчастіші поламки:

| У лозі | Причина |
|---|---|
| `relay connect error: HTTP error: 404 Not Found` | хост спільноти не збігається — див. Крок 0 |
| `OAuth session expired and could not be refreshed` | немає `CLAUDE_CODE_OAUTH_TOKEN` |
| `usageLimitExceeded` | вичерпана квота акаунта |
| під'єднався, але тиша | гейт автора або фільтр згадок |

Після термінальної помилки підключення харнес **не перезапускається сам** —
його треба зупинити й запустити в UI.
Loading