21 KiB
Executable File
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) 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 - Сброс: при вызове
memorytool (строка 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_managetool (строки 209, 778 в tool_executor.py) - Условия:
"skill_manage" in valid_tool_names
Финальная проверка
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)
-
Создаёт форк 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(чтобы не рекурсил)
-
Устанавливает tool whitelist (строки 459–472):
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: ..." -
Запускает
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(жёстко, не из конфига)
- Выбранным промптом (см. ниже) + суффикс
-
После завершения — собирает успешные tool-результаты через
summarize_background_review_actions(), фильтруя те, что уже были вmessages_snapshot(чтобы не показывать старые как новые). -
Выводит пользователю:
f" 💾 Self-improvement review: {summary}" # Например: "💾 Self-improvement review: Memory updated · Skill 'xxx' patched"Через
agent._safe_print()и черезagent.background_review_callback()(для TUI/gateway). -
На ошибках — пишет в лог
"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 — конфигурируемый (исправлено 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] |
Код (проверено построчно)
# 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))
# 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
# 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
# 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:
1. agent_init.py — чтение секции review: из config.yaml
Место: agent/agent_init.py (после блока skills config).
Читает секцию:
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
Что делает код:
- Выставляет
agent._review_allowed_toolsets(по дефолту["memory", "skills"]) - Для каждого из трёх путей (
skill_prompt_path,memory_prompt_path,combined_prompt_path) — пытается прочитать файл и записать содержимое вagent._SKILL_REVIEW_PROMPT/agent._MEMORY_REVIEW_PROMPT/agent._COMBINED_REVIEW_PROMPTсоответственно. - Если путь не указан, файл не найден или не читается — тихо игнорируется, остаётся встроенный хардкод.
2. background_review.py — использование _review_allowed_toolsets с инстанса
Место: agent/background_review.py, функция _run_review_in_thread, whitelist (строка ~459).
Было:
review_whitelist = {
t["function"]["name"]
for t in get_tool_definitions(
enabled_toolsets=["memory", "skills"],
quiet_mode=True,
)
}
Стало:
_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-обновление
- Раскомментировать в
~/.hermes/config.yaml:review: skill_prompt_path: ~/.hermes/review/skill_prompt.md memory_prompt_path: ~/.hermes/review/memory_prompt.md allowed_toolsets: - memory - skills - file - Перезапустить Hermes (новый сессионный конфиг прочитается при старте
AIAgent.__init__). - После каждого десятого 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.yamlagent/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. - 2026-09-29: Разбор механизма — интервал скиллов оказался конфигурируемым (ранее в доке значилось «хардкожен в 10»), проверено по
agent_init.py:1224. - 2026-09-29: Автогенерация скиллов отключена —
skills.creation_nudge_interval: 15 → 0, коммитd2e1586. Память оставлена включённой. См. §«Как ВЫКЛЮЧИТЬ».