223 lines
14 KiB
Markdown
223 lines
14 KiB
Markdown
# Hermes Self-Improvement Review (Background Memory/Skill Review)
|
||
|
||
Source: codebase analysis of `hermes-agent/agent/background_review.py`, `run_agent.py`, `agent/conversation_loop.py`, `agent/agent_init.py`.
|
||
|
||
## Что это
|
||
|
||
После каждого юзерского сообщения ([run_conversation](https://github.com/NousResearch/hermes-agent)) AIAgent может запустить **фоновый daemon-thread**, который форкает ещё один AIAgent, даёт ему снимок всего разговора и просит оценить — нужно ли сохранить что-то в память (Memory) или обновить/создать скилл (Skill).
|
||
|
||
Основной поток продолжается немедленно. Ревью не блокирует пользователя.
|
||
|
||
## Where it lives
|
||
|
||
| File | Lines | Role |
|
||
|------|-------|------|
|
||
| `agent/background_review.py` | 1–597 | Центральный модуль — промпты, `_run_review_in_thread`, `spawn_background_review_thread`, `summarize_background_review_actions` |
|
||
| `run_agent.py` | 1360–1400 | Форвардеры: `_spawn_background_review`, `_summarize_background_review_actions`, импорт констант-промптов |
|
||
| `agent/conversation_loop.py` | 4779–4805 | Место вызова — после завершения ответа, перед возвратом `result` |
|
||
| `agent/codex_runtime.py` | 150–162 | Аналогичный вызов для Codex Response API режима |
|
||
| `agent/agent_init.py` | 1067–1075, 1187–1190 | Инициализация счётчиков и интервалов |
|
||
| `agent/tool_executor.py` | 207–209, 776–778 | Сброс счётчиков при вызове `memory` или `skill_manage` |
|
||
| `tui_gateway/server.py` | 2452–2464 | Подключение `background_review_callback` для TUI (Ink) |
|
||
| `gateway/run.py` | 17849–17893 | Подключение `background_review_callback` для gateway-сообщений |
|
||
|
||
## Как триггерится
|
||
|
||
Два независимых счётчика:
|
||
|
||
### Memory review (turn-based)
|
||
|
||
- Счётчик: `_turns_since_memory`
|
||
- Интервал: `_memory_nudge_interval` (default = **10**)
|
||
- Задаётся из `config.yaml: memory.nudge_interval`
|
||
- Инкрементится **в начале** каждого `run_conversation` (строка 559)
|
||
- Проверка: `if _turns_since_memory >= _memory_nudge_interval` → `_should_review_memory = True`
|
||
- Сброс: при вызове `memory` tool (строка 207 в tool_executor.py)
|
||
- Условия: `"memory" in valid_tool_names` и `_memory_store != None`
|
||
|
||
Если `memory_enabled: false` → ревью не триггерится.
|
||
|
||
### Skills review (iteration-based)
|
||
|
||
- Счётчик: `_iters_since_skill`
|
||
- Интервал: `_skill_nudge_interval` (default = **10**)
|
||
- Инкрементится **внутри tool-call цикла** после каждого батча (строка 860)
|
||
- Проверка: `if _iters_since_skill >= _skill_nudge_interval` → `_should_review_skills = True`
|
||
- Сброс: при вызове `skill_manage` tool (строки 209, 778 в tool_executor.py)
|
||
- Условия: `"skill_manage" in valid_tool_names`
|
||
|
||
### Финальная проверка
|
||
|
||
```python
|
||
if final_response and not interrupted and (_should_review_memory or _should_review_skills):
|
||
agent._spawn_background_review(
|
||
messages_snapshot=list(messages),
|
||
review_memory=_should_review_memory,
|
||
review_skills=_should_review_skills,
|
||
)
|
||
```
|
||
|
||
## Что происходит внутри
|
||
|
||
### `_run_review_in_thread` (background_review.py:327–559)
|
||
|
||
1. **Создаёт форк AIAgent**, наследуя:
|
||
- `model`, `provider`, `base_url`, `api_key`, `credential_pool` от родителя (чтобы попасть в тот же prefix cache у Anthropic/OpenRouter)
|
||
- `_cached_system_prompt` — byte-идентичный, чтобы не разогревать кеш заново
|
||
- `session_id`, `session_start` — те же
|
||
- `_memory_store`, `_memory_enabled`, `_user_profile_enabled` — те же
|
||
- Но `skip_memory=True` (чтобы форк не создавал свой MemoryManager)
|
||
- `_memory_nudge_interval = 0`, `_skill_nudge_interval = 0` (чтобы не рекурсил)
|
||
|
||
2. **Устанавливает tool whitelist** (строки 459–472):
|
||
```python
|
||
review_whitelist = {
|
||
t["function"]["name"]
|
||
for t in get_tool_definitions(
|
||
enabled_toolsets=["memory", "skills"],
|
||
quiet_mode=True,
|
||
)
|
||
}
|
||
```
|
||
Вне whitelist — всё блокируется с сообщением `"Background review denied non-whitelisted tool: ..."`
|
||
|
||
3. **Запускает** `review_agent.run_conversation(...)` с:
|
||
- Выбранным промптом (см. ниже) + суффикс `"You can only call memory and skill management tools. Other tools will be denied at runtime — do not attempt them."`
|
||
- `conversation_history=messages_snapshot` — весь разговор до этого момента
|
||
- `max_iterations=16` (жёстко, не из конфига)
|
||
|
||
4. **После завершения** — собирает успешные tool-результаты через `summarize_background_review_actions()`, фильтруя те, что уже были в `messages_snapshot` (чтобы не показывать старые как новые).
|
||
|
||
5. **Выводит** пользователю:
|
||
```python
|
||
f" 💾 Self-improvement review: {summary}"
|
||
# Например: "💾 Self-improvement review: Memory updated · Skill 'xxx' patched"
|
||
```
|
||
Через `agent._safe_print()` и через `agent.background_review_callback()` (для TUI/gateway).
|
||
|
||
6. **На ошибках** — пишет в лог `"Background memory/skill review failed: ..."` и шлёт `_emit_auxiliary_failure`. Не ломает основной разговор.
|
||
|
||
## 3 варианта промпта
|
||
|
||
Выбираются в `spawn_background_review_thread()` по комбинации флагов:
|
||
|
||
| `review_memory` | `review_skills` | Промпт | Суть |
|
||
|---|---|---|---|
|
||
| ✅ | ❌ | `_MEMORY_REVIEW_PROMPT` (~10 строк) | Сохранить факты о пользователе (persona, preferences, style). Если нечего — 'Nothing to save.' |
|
||
| ❌ | ✅ | `_SKILL_REVIEW_PROMPT` (~120 строк) | Самая большая инструкция. Искать сигналы: коррекции, frustration, новые техники, устаревшие скиллы. Приоритет: loaded skill → existing umbrella → support file → new umbrella. Запрещено: env-зависимости, негативные фиксации, session-specific. |
|
||
| ✅ | ✅ | `_COMBINED_REVIEW_PROMPT` (~80 строк) | Смесь обоих — сначала memory, потом skills с той же логикой. |
|
||
|
||
## Потенциальные проблемы
|
||
|
||
- **Нет конфигурируемости для skills interval** — `_skill_nudge_interval` хардкожен в 10, не читается из config.yaml (в отличие от memory).
|
||
- **Tool whitelist был фиксирован** — теперь настраивается через `review.allowed_toolsets` в config.yaml (см. ниже).
|
||
- **Форк не имеет MCP-серверов** — MCP tools (obsidian-mcp, etc.) не регенерируются в форке.
|
||
- **max_iterations=16** — жёстко, не из конфига. Может не хватить на комплексное ревью многоскилловых сессий.
|
||
|
||
## Реализованные расширения (2026-06-24)
|
||
|
||
Сделаны два изменения в исходном коде Hermes Agent для того, чтобы промпты и whitelist ревью были настраиваемы из `config.yaml`:
|
||
|
||
### 1. agent_init.py — чтение секции `review:` из config.yaml
|
||
|
||
Место: `agent/agent_init.py` (после блока skills config).
|
||
|
||
Читает секцию:
|
||
```yaml
|
||
review:
|
||
# Пути к .md файлам с кастомными промптами.
|
||
# Если файл не найден/не читается — тихо падает на встроенный хардкод.
|
||
skill_prompt_path: ~/.hermes/review/skill_prompt.md
|
||
memory_prompt_path: ~/.hermes/review/memory_prompt.md
|
||
combined_prompt_path: ~/.hermes/review/combined_prompt.md
|
||
|
||
# Какие toolsets доступны форку ревью.
|
||
# По дефолту: ["memory", "skills"].
|
||
# Чтобы форк мог читать/писать .md файлы Obsidian — добавить "file".
|
||
allowed_toolsets:
|
||
- memory
|
||
- skills
|
||
# - file
|
||
```
|
||
|
||
Что делает код:
|
||
|
||
1. Выставляет `agent._review_allowed_toolsets` (по дефолту `["memory", "skills"]`)
|
||
2. Для каждого из трёх путей (`skill_prompt_path`, `memory_prompt_path`, `combined_prompt_path`) — пытается прочитать файл и записать содержимое в `agent._SKILL_REVIEW_PROMPT` / `agent._MEMORY_REVIEW_PROMPT` / `agent._COMBINED_REVIEW_PROMPT` соответственно.
|
||
3. Если путь не указан, файл не найден или не читается — тихо игнорируется, остаётся встроенный хардкод.
|
||
|
||
### 2. background_review.py — использование `_review_allowed_toolsets` с инстанса
|
||
|
||
Место: `agent/background_review.py`, функция `_run_review_in_thread`, whitelist (строка ~459).
|
||
|
||
**Было:**
|
||
```python
|
||
review_whitelist = {
|
||
t["function"]["name"]
|
||
for t in get_tool_definitions(
|
||
enabled_toolsets=["memory", "skills"],
|
||
quiet_mode=True,
|
||
)
|
||
}
|
||
```
|
||
|
||
**Стало:**
|
||
```python
|
||
_allowed_sets = getattr(agent, "_review_allowed_toolsets", ["memory", "skills"])
|
||
review_whitelist = {
|
||
t["function"]["name"]
|
||
for t in get_tool_definitions(
|
||
enabled_toolsets=_allowed_sets,
|
||
quiet_mode=True,
|
||
)
|
||
}
|
||
```
|
||
|
||
А также сообщение для форка динамически подставляет имена toolsets вместо хардкода `"memory and skill"`.
|
||
|
||
### 3. Примеры промптов
|
||
|
||
Созданы в `~/.hermes/review/`:
|
||
|
||
- **`skill_prompt.md`** — копия встроенного `_SKILL_REVIEW_PROMPT` + секция `--- Obsidian documentation update ---`, которая инструктирует форк после каждого обновления скилла читать и патчить соответствующий `.md` в `/Users/admin/obsidian/`.
|
||
- **`memory_prompt.md`** — копия `_MEMORY_REVIEW_PROMPT` + инструкция обновлять `personal/profiles/Alex.md` при сохранении user preference.
|
||
|
||
### Как включить Obsidian-обновление
|
||
|
||
1. Раскомментировать в `~/.hermes/config.yaml`:
|
||
```yaml
|
||
review:
|
||
skill_prompt_path: ~/.hermes/review/skill_prompt.md
|
||
memory_prompt_path: ~/.hermes/review/memory_prompt.md
|
||
allowed_toolsets:
|
||
- memory
|
||
- skills
|
||
- file
|
||
```
|
||
2. Перезапустить Hermes (новый сессионный конфиг прочитается при старте `AIAgent.__init__`).
|
||
3. После каждого десятого tool-батча (или любого вызова `skill_manage` раньше) форк будет: создать/обновить скилл **и** прочитать/пропатчить соответствующий Obsidian-документ через `read_file`/`patch`.
|
||
|
||
### Замечание по архитектуре
|
||
|
||
File tools работают напрямую с файловой системой, в обход obsidian-mcp. Это значит:
|
||
- **Линки** `[[wiki]]` в Obsidian не резолвятся автоматически — форк видит сырой `.md`.
|
||
- **Frontmatter** (YAML заголовки) — видит как часть текста, может патчить.
|
||
- **Vault search** недоступен — только прямой путь к файлу.
|
||
|
||
Для большинства задач (пропатчить существующий doc, создать новый .md с frontmatter) file tools достаточно. Если нужен полный Obsidian API — надо дорабатывать подключение MCP в форке (Вариант B из первоначального исследования, пока не реализован).
|
||
|
||
## Related files
|
||
|
||
- `~/.hermes/hermes-whale/review/skill_prompt.md` — кастомный skills промпт с Obsidian-инструкцией (для всех артефактов: планы, конфиги, этапы проектов)
|
||
- `~/.hermes/hermes-whale/review/memory_prompt.md` — кастомный memory промпт с Obsidian-инструкцией
|
||
- `agent/agent_init.py` — чтение секции `review:` из config.yaml
|
||
- `agent/background_review.py` — использование `_review_allowed_toolsets` и кастомных промптов
|
||
- Config: `config.yaml → review.*` (см. секцию выше)
|
||
|
||
## Хронология
|
||
|
||
- 2026-06-24: Исследование и документирование механизма self-improvement review (Whale/Alex).
|
||
- 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`.
|