[2026-09-29] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/balda-mm-pr-plan.md personal/plans/balda-runtime-rebuild-mm-validation.md personal/plans/runtime-pr4-openai-toolcalls-fixes.md personal/plans/runtime-pr5-toolset-dedup-review-fixes.md personal/plans/watchlist-tmdb-missing-marker.md personal/plans/zulip-decommission.md personal/projects/balda/balda-kraken-mm-deploy.md personal/projects/zont-config-compiler.md personal/tech/git-hygiene-post-merge.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-29 12:56:11 +06:00
1 parent 5f13ad0331
commit def88a5074
13 files changed
+528 -67

No files matched your search

+141
View File
@@ -0,0 +1,141 @@
# Git-гигиена после мержа PR (форк → апстрим)
> Создан 2026-09-29. Извлечено из уборки после мержа `baldaworks/balda#8` +
> `normahq/runtime#4/#5/#7`.
> Связано: [[personal/plans/balda-mm-pr-plan]], [[personal/plans/balda-runtime-rebuild-mm-validation]],
> [[personal/tech/balda-go-fx-pitfalls]].
---
## 1. Порядок операций (он обязателен)
```
worktree remove --force → worktree prune → branch -D → push origin --delete → remote prune
```
Перестановка шагов = падение. Подробности ниже.
```bash
cd <repo>
git checkout main && git pull --no-rebase <remote> main # merge, НЕ rebase
git worktree list # 🔴 СНАЧАЛА посмотреть
git worktree remove --force <path-with-gone-dir> && git worktree prune
git branch -D <merged-branches>
git push origin --delete <merged-branches>
git remote prune origin && git remote prune upstream
git status --short # ?? — untracked, разобрать отдельно
```
📌 `AGENTS.md` Балды: **синхронизация общих веток — через `merge` (`git pull --no-rebase`), не rebase.**
---
## 2. 🔴 Питфолл: git-регистрация worktree ≠ путь на диске
Самый дорогой сюрприз. `git worktree list` показывает **пути, которых уже нет**:
```
/private/tmp/balda-mm-pr8-review 06b2b8d0 (detached HEAD) ← каталог снесён из /tmp
```
`.git/worktrees/<name>` остаётся жить. Такая регистрация **держит ветку**, и удаление падает
даже при отсутствующем каталоге:
```
error: cannot delete branch 'fix/openai-tool-call-roundtrip-v2' used by worktree at
'/Users/admin/Developer/runtime-local' # ← это про ТЕКУЩИЙ worktree, не про мёртвый
```
Из двух провалов в одной сессии:
- `codex/mattermost-prod-fix` — держал мёртвый `/private/tmp/balda-mm-pr8-prod-fix`;
- `fix/openai-tool-call-roundtrip-v2` — держал **активный** worktree, где я стоял сам.
**Лечение:** `git worktree remove --force <path>` (работает и для несуществующего каталога —
просто убирает регистрацию) → `git worktree prune` → и только потом `git branch -D`.
⚠️ **Проверять `git worktree list` ДО удаления веток.** Иначе получишь список «неудаляемых» веток
и будешь думать, что они не смержены.
⚠️ `git worktree remove` на `/private/tmp/...` **уходит на апрув** — это удаление каталога, а не
безобидная мета-операция. Нормально, просто ожидаемо.
📌 Ещё: `git checkout main` **невозможен**, пока стоишь в worktree удаляемой ветки — отсюда
второй `cannot delete branch ... used by worktree` в логе.
---
## 3. ⚠️ `git pull origin main` → `Already up to date` — это НЕ «всё чисто»
После мержа PR в апстрим **ветка форка остаётся на старом коммите**, а мерж живёт только в
`upstream/main`. `origin` (форк) мержа не содержит вообще.
```bash
git pull --no-rebase origin main # → Already up to date ← вводит в заблуждение
git log --oneline -1 upstream/main # ← здесь правда
git merge-base --is-ancestor <pr-head> upstream/main && echo MERGED # ← доказательство
```
**Как доказывать факт мержа** (не по доке, а фактически):
```bash
gh api repos/<upstream>/pulls/<N> --jq '{merged, merge_commit_sha, head_sha, base_sha}'
git merge-base --is-ancestor <head_sha> upstream/main && echo YES || echo NO
```
Чтобы догнать апстрим локально — `git merge upstream/main` (не rebase).
---
## 4. `git branch -r --merged upstream/main` = список веток под снос
Один вызов отвечает «какие ветки уже в апстриме»:
```bash
git branch -r --merged upstream/main
```
⚠️ В списке появятся ветки, которые **не твои** (`upstream/feature/*`, `upstream/docs/*`) —
их не трогать. Смотреть на `origin/*` (свой форк).
---
## 5. Что не трогать
| Объект | Почему |
|---|---|
| `__dolt_remote_info__` | служебная ветка апстрима (Dolt), появляется в fetch, восстановится сама |
| untracked рабочие инструменты (`tools/*.py`, `deltas-*.json`) | не в git, значит и не «мусор». Проверять `git ls-files tools/` — пусто ≠ удалить |
| файлы вне PR, нужные для локальной сборки | `go.mod`, `Dockerfile.release`, `go.sum`, `app.go`, `preflight.go` — `checkout -- .`/`stash`/`reset --hard` их снесёт |
## 6. Stash
`git stash list` — перед любым решением выгружать содержимое, а не удалять вслепую:
```bash
git stash show --name-only stash@{N}
git stash show -p stash@{N} > /path/patch.diff # восстановимо после drop
```
`git stash drop` — **только с явного подтверждения**. Дата в сообщении stash (`whale-20260623`)
говорит, как давно он лежит; старый stash обычно надо сначала прочитать, потом решать.
---
## 7. 📌 Реакция Alex: бэкап не предлагать
Предложение «сделать бэкап веток перед сносом» получило резкий отказ. **Смерженный код
восстанавливается из апстрима — бэкапить нечего.** Бэкап в этом контексте = лишний шаг,
который читается как неуверенность.
| Предлагать бэкап | НЕ предлагать |
|---|---|
| Продовые конфиги, БД, данные без копии | Смерженные git-ветки |
| Хостовые файлы вне git | Worktree-регистрации в `/tmp` |
| `navidrome.db` (правило: согласовывать КАЖДЫЙ раз) | untracked-мусор на смерженных коммитах |
Формулировать так: «ветка смержена в `upstream/main` (`bf4a6ae4`), сношу» — без слова «бэкап».
---
## 8. Проверка результата
```bash
git worktree list # только рабочий каталог
git branch -a # main + remotes, без дев-веток
git status --short # пусто или объяснимые untracked
```
+115 -1
View File
@@ -109,11 +109,123 @@ if final_response and not interrupted and (_should_review_memory or _should_revi
## Потенциальные проблемы
- **Нет конфигурируемости для skills interval** — `_skill_nudge_interval` хардкожен в 10, не читается из config.yaml (в отличие от memory).
- **Skills interval — конфигурируемый** (исправлено 2026-09-29; ранее в доке ошибочно значилось «хардкожен в 10»).
Читается из `config.yaml → skills.creation_nudge_interval`, дефолт в коде `10`.
Проверено: `agent/agent_init.py:1224` → `int(skills_config.get("creation_nudge_interval", 10))`.
На этом профиле (`hermes-whale/config.yaml:280`) стоит **`0`** (отключено 2026-09-29, коммит `d2e1586`).
- **Tool whitelist был фиксирован** — теперь настраивается через `review.allowed_toolsets` в config.yaml (см. ниже).
- **Форк не имеет MCP-серверов** — MCP tools (obsidian-mcp, etc.) не регенерируются в форке.
- **max_iterations=16** — жёстко, не из конфига. Может не хватить на комплексное ревью многоскилловых сессий.
## 🔴 Как ВЫКЛЮЧИТЬ автогенерацию скиллов (2026-09-29)
Задача Alex: «отключи автогенерацию скиллов». Разобрано до уровня кода; **ВНЕСЕНО 2026-09-29**
(вариант A — см. §Развилка). Выбор Alex: «да. только hermes-whale. без бэкапов, у нас гит. потом коммит».
### Два независимых пути, которыми агент сам правит библиотеку скиллов
| # | Механизм | Что делает | Ключ конфига | Сейчас |
|---|---|---|---|---|
| 1 | **Skill nudge** | каждые N tool-итераций спавнит фоновый форк, который **создаёт/патчит скиллы** | `skills.creation_nudge_interval` | **15** → ставить `0` |
| 2 | **Review fork** | после сессии тот же форк, но с `allowed_toolsets` | `review.allowed_toolsets` | `[memory, skills, file]` |
### Код (проверено построчно)
```python
# agent/agent_init.py:1220-1226
agent._skill_nudge_interval = 10
skills_config = _agent_cfg.get("skills", {})
agent._skill_nudge_interval = int(skills_config.get("creation_nudge_interval", 10))
```
```python
# agent/conversation_loop.py:858-860 — инкремент внутри tool-цикла
if (agent._skill_nudge_interval > 0
and "skill_manage" in agent.valid_tool_names):
agent._iters_since_skill += 1
```
```python
# agent/conversation_loop.py:4779-4785 — финальная проверка в конце хода
_should_review_skills = False
if (agent._skill_nudge_interval > 0 # ← ГЛАВНЫЙ ВЫКЛЮЧАТЕЛЬ
and agent._iters_since_skill >= agent._skill_nudge_interval
and "skill_manage" in agent.valid_tool_names):
_should_review_skills = True
agent._iters_since_skill = 0
```
```python
# agent/conversation_loop.py:4797 — спавн только если что-то взведено
if final_response and not interrupted and (_should_review_memory or _should_review_skills):
agent._spawn_background_review(...)
```
**Вывод:** `creation_nudge_interval: 0` делает первое условие ложным **всегда** →
`_should_review_skills` никогда не станет `True` → скиллы от nudge-механизма не создаются.
Это чистый выключатель, other logic не затронута. Счётчик `_iters_since_skill` при `0`
даже не инкрементится (строка 858).
### Промпты — где живут
| Промпт | Что делает | Когда выбирается |
|---|---|---|
| `_MEMORY_REVIEW_PROMPT` | сохранить факты о юзере | `review_memory=True, review_skills=False` |
| `_SKILL_REVIEW_PROMPT` | **правит библиотеку скиллов** («Be ACTIVE — most sessions produce at least one skill update») | `review_skills=True` |
| `_COMBINED_REVIEW_PROMPT` | и память, и скиллы | оба `True` |
Выбор — `spawn_background_review_thread()` (`agent/background_review.py:583-588`):
`if review_memory and review_skills → COMBINED; elif review_memory → MEMORY; else → SKILL`.
То есть **скиллы правятся в ветках COMBINED и «else»**. Убрать `skills` из
`allowed_toolsets` — упадёт сам tool, но промпт останется.
### ✅ Внесено (2026-09-29) — вариант A
| Ключ | Было | Стало | Файл |
|---|---|---|---|
| `skills.creation_nudge_interval` | `15` | **`0`** (строка 280) | `~/.hermes/hermes-whale/config.yaml` |
Коммит **`d2e1586`** (`chore(whale): disable auto skill generation`), md5 `ac0c7af01d48fa24fb763efbe56668a2`.
`review.allowed_toolsets` **не тронут** (`[memory, skills, file]`) — память продолжает обновляться.
`~/.hermes/config.yaml` не тронут (Alex: «только hermes-whale»).
**Требуется перезапуск gateway**, чтобы правка вступила в силу:
`POST /api/services/hermes_whale/restart` — конфиг читается в `AIAgent.__init__`.
### 🔴 Питфолл: `hermes config set` перезаписывает ВЕСЬ YAML
`hermes config set <key> <val>` не делает точечную правку — он сериализует конфиг заново.
Последствия на этом профиле: **63 строки комментариев срезано**, `host: "0.0.0.0"` → `0.0.0.0`,
отступы списков переформатированы (` - memory` → ` - memory`).
Значения при этом не пострадали — сверено построчно через
`git diff --cached -U0 | grep -E "^[+-]" | grep -v "^[+-][+-]" | grep -vE "^[+-]\s*#"`.
**Всегда проверять дифф на смысловые изменения, а не только на свою строку.**
### 🔴 Питфолл: `patch`/`write_file` НЕ могут писать в config.yaml
Инструменты возвращают `Refusing to write to Hermes config file` — конфиг помечен
security-sensitive. Править **только** через `hermes config set`, руками (`hermes config edit`)
или с одобрения Alex. Это не баг, обходить не надо.
### 🔴 Питфолл: `hermes` не в PATH
`hermes: command not found` — бинарь лежит в venv:
`/Users/admin/.hermes/hermes-agent/venv/bin/hermes`.
`hermes config show` **не печатает** секцию `skills` — проверять значение грепом по файлу
(`grep -n creation_nudge_interval <config>`), а не через CLI. `config get` не существует
(есть только `show|edit|set|path|env-path|check|migrate`).
### Развилка (решение принято: A)
| Вариант | Что меняется | Последствие |
|---|---|---|
| **A. Только `creation_nudge_interval: 0`** ✅ | 1 ключ | скиллы не создаются сами; **память продолжает копиться** |
| B. A + убрать `skills` из `review.allowed_toolsets` | 2 ключа | глухо оба; но фоновый review перестанет писать и в **memory** — авто-обновление USER.md/MEMORY.md умрёт. **Не выбран.** |
Файлы (две копии, они **разные** — см. питфолл ниже):
`~/.hermes/hermes-whale/config.yaml` (основная) и `~/.hermes/config.yaml`.
## Реализованные расширения (2026-06-24)
Сделаны два изменения в исходном коде Hermes Agent для того, чтобы промпты и whitelist ревью были настраиваемы из `config.yaml`:
@@ -220,3 +332,5 @@ File tools работают напрямую с файловой системо
- 2026-06-24: Реализация настройки через config.yaml: промпты (через пути к .md) + allowed_toolsets. Созданы примеры промптов с Obsidian-инструкцией.
- 2026-06-24: Реализация thread-scoped memory + custom memory instruction (см. `personal/plans/thread-scoped-memory.md`).
- 2026-06-24: Тесты thread-scoped memory: 9 новых тестов (76/76 passed), закоммичено в `4ebad4f69`.
- 2026-09-29: Разбор механизма — интервал скиллов оказался **конфигурируемым** (ранее в доке значилось «хардкожен в 10»), проверено по `agent_init.py:1224`.
- 2026-09-29: **Автогенерация скиллов отключена** — `skills.creation_nudge_interval: 15 → 0`, коммит `d2e1586`. Память оставлена включённой. См. §«Как ВЫКЛЮЧИТЬ».
@@ -620,7 +620,8 @@ ORDER BY m.date_sent DESC LIMIT 25;"
```
**Этот запрос восстановил всю картину сессии** после того, как я потерял контекст
из-за собственного рестарта gateway. См. скилл `zulip-db-forensics`.
из-за собственного рестарта gateway. См. скилл `message-history-search` (ранее
`zulip-db-forensics`, снесён 2026-09-29 вместе с Zulip) → [[tech/zulip-message-history]].
## Связанные заметки
+25 -3
View File
@@ -11,7 +11,7 @@ _Создано: 2026-09-29_
| Параметр | Значение |
|---|---|
| **Файл** | `~/Documents/zulip_message_history.sqlite` |
| Размер | 78.1 MB |
| Размер | 81.9 MB на диске (78.1 MB логических данных) |
| Сообщений | **134 734** (134 688 в стримах + 46 личек) |
| Стримов | 11 |
| Тредов (`topics`) | 197 |
@@ -215,13 +215,35 @@ docker rmi zulip/docker-zulip:11.6-0 # ~3.84 GB
| Компонент | Что будет |
|---|---|
| `zulip-router` (Go, `~/Developer/zulip-router/`) | мёртвый — поллит Zulip Events API. Убрать из compose, иначе restart-loop |
| `zulip-bot-routing` (Еagle/Кит/Валера/Клавдий webhook) | маршрутизация в Zulip перестанет работать |
| Bot webhook-роутинг (Eagle/Кит/Валера/Клавдий, `host.docker.internal:8644-8645`) | маршрутизация в Zulip перестанет работать |
| Hermes `platforms: zulip` в `~/.hermes/config.yaml` | платформа отвалится; транспорт уже переехал на Mattermost |
ℹ️ Скиллы на Zulip больше не ссылаются — этап 3 закрыт, битых инструкций после сноса не будет
(кроме двух файлов `docker-balda-service`, см. §«Оставлено как есть»).
## Связанные документы
- [[plans/zulip-decommission]] — план и статус вывода Zulip из эксплуатации (этап 3 = этот файл)
- [[projects/zulip-router]] — архитектура event-роутера (Go), конфиг ботов
- [[tech/hermes-whale-mattermost-transport]] — текущий транспорт Hermes
- [[tech/hermes-self-improvement-review]] — фоновый review скиллов/памяти (в т.ч. как его выключить)
- [[how-to/hermes-eagle-mac]] — Hermes на Mac, Zulip Docker стек, RabbitMQ/Khepri pitfall
- [[tech/docker-mac-disk-issues]] — почему Zulip тормозил (переполнение диска VM)
- [[plans/zulip-decommission]] — план вывода Zulip из эксплуатации
## Как это используется агентами (скилл вместо скиллов)
Три скилла, работавшие с Zulip Postgres, **снесены** — их место занял один:
| Было | Стало |
|---|---|
| `zulip-db-forensics` | `message-history-search` |
| `zulip-thread-handoff` | ↑ |
| `zulip-bot-routing` | ↑ |
`~/.hermes/skills/note-taking/message-history-search/SKILL.md` — единственная точка входа
для «ищи в базе», «вспоминай», «читай историю». На неё ссылаются `SOUL.md` (оба файла!) и
`USER.md`, поэтому агент находит её без подсказки.
**Триггер в SOUL/USER:** «ищи в базе» / «чё сделано» → `sqlite3` СРАЗУ, без `session_search`
и без теории. `session_search` индексирует файлы сессий Hermes, **не** историю чата —
для старых тредов он пуст.