# 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`.