[2026-06-25] taiga-vault: family/how-to/htpc-emulators-setup.md family/how-to/htpc-gaming-plans.md family/how-to/kraken-access.md family/how-to/openmediavault-rpi5.md family/how-to/time-machine.md family/how-to/wireguard-vpn.md personal/documents/todo-list.md personal/plans/extract-stable-prompt-blocks.md personal/plans/hermes-whale-system-prompt.md personal/plans/thread-scoped-memory.md

This commit is contained in:
Taiga
2026-06-25 05:23:46 +00:00
parent ac0d753ec0
commit 16987d69f3
26 changed files with 2656 additions and 457 deletions
+122
View File
@@ -0,0 +1,122 @@
# Реализация: Thread-scoped memory + custom memory prompt
**Статус:** Реализовано ✅
## Изменённые файлы
### 1. `tools/memory_tool.py` — MemoryStore с thread_key
**`__init__`** — новый параметр `thread_key: Optional[str] = None`, сохраняется как `self.thread_key`.
**`_path_for(target)`** — теперь instance method (был static):
- `target == "user"` → всегда `memories/USER.md`
- `target == "memory"` и `self.thread_key` задан → `memories/threads/<key>/MEMORY.md`
- `target == "memory"` без thread_key → `memories/MEMORY.md` (глобальный fallback)
**`load_from_disk()`** — использует `self._path_for("memory")` и `self._path_for("user")` вместо хардкода.
### 2. `agent/agent_init.py` — конфиг + проброс
Новые поля на агенте:
- `_memory_thread_scoped` — читается из `config.yaml: memory.thread_scoped`
- `_memory_instruction` — читается из `config.yaml: memory.prompt_path` (.md файл)
MemoryStore создаётся с `thread_key=_gateway_session_key` если `thread_scoped: true`.
Кастомная memory instruction логируется: `Loaded custom memory instruction from ...`.
### 3. `agent/system_prompt.py` — вставка в system prompt
В блок MEMORY добавляется:
- Заголовок `MEMORY for thread: <gateway_session_key>` (вместо `MEMORY (your personal notes)`) когда thread_scoped включён и есть ключ.
После memory блока вставляется отдельный блок `MEMORY INSTRUCTION` (с опциональным `for thread: <key>`), содержащий кастомную инструкцию из .md файла.
### 4. `gateway/run.py` — уже пробрасывает
`gateway_session_key` уже передаётся в `AIAgent.__init__` на строке ~17828. Никаких изменений не потребовалось.
### 5. `run_agent.py` — уже принимает
Параметр `gateway_session_key` уже есть в `AIAgent.__init__`. Пробрасывается в `init_agent()` где записывается как `agent._gateway_session_key`.
## Конфиг (Whale)
```yaml
memory:
...
thread_scoped: true
prompt_path: ~/.hermes/hermes-whale/review/memory_prompt.md
```
## Файлы
- **`~/.hermes/hermes-whale/review/memory_prompt.md`** — инструкция что запоминать (документы, команды, конфиги, статус проекта, решения).
- После добавления нового пункта в список patch() не перенумеровывает — нужен второй clean patch.
- **2026-06-24:** Добавлен пункт 2 — после загрузки Obsidian docs (skill_view, mcp_obsidian_read_note) извлекать ключевые факты в memory.
## Файловая структура на диске
```
~/.hermes/hermes-whale/memories/
├── MEMORY.md # глобальная (fallback для CLI/старых сессий)
├── USER.md # глобальная (всегда)
└── threads/
├── agent:main:webhook:webhook:webhook:whale/
│ └── MEMORY.md # память Whale
├── agent:main:zulip:stream:general:thread:123/
│ └── MEMORY.md # память конкретного треда
└── ...
```
## Коммиты
- `hermes-agent`: `98cb69b50` — feat: thread-scoped memory + configurable memory instruction
- `hermes-agent`: `4ebad4f69` — test: thread-scoped memory persistence, drift guard, snapshot, sanitization (+9 тестов, 142 строки)
- `hermes-whale`: `736f8f3` — whale: enable thread-scoped memory and custom memory instruction
## Тесты
9 тестов в `tests/tools/test_memory_tool.py` (всего 76 в файле, 76/76 passed):
**Persistence:**
- `test_thread_scoped_memory_writes_separate_file` — global и thread пишутся в разные файлы
- `test_user_stays_global_with_thread_key` — USER.md всегда глобальный, не залезает в `threads/`
- `test_thread_and_global_are_independent_on_load` — загрузка thread не видит global entries и vice versa
- `test_thread_key_none_falls_back_to_global` — backward compat: без thread_key пишет в `memories/MEMORY.md`
**Snapshot:**
- `test_snapshot_reflects_thread_scoped_path``format_for_system_prompt` берёт данные из thread-файла
- `test_snapshot_from_thread_and_global_are_independent` — thread snapshot изолирован от global
**Drift guard:**
- `test_drift_guard_with_thread_key``_detect_external_drift` работает на thread-scoped MEMORY.md
**Sanitization:**
- `test_load_time_sanitization_with_thread_key` — poisoned entry в thread блокируется на уровне snapshot
**Pitfalls:**
- `pytest-timeout` плагин не установлен, но `pyproject.toml` содержит `addopts = "--timeout=30"`. Запуск падает с `unrecognized arguments`. Используй `-o "addopts="` для override.
- Drift guard на thread: нужен блок > `memory_char_limit` (дефолт 2200), иначе `_detect_external_drift` не находит entry-size overflow. В тесте `"x" * 2300`.
## Тесты
- **76/76 passed** (из них 9 новых для thread_key, добавлены в `4ebad4f69`)
- **9 новых тестов:**
- `test_thread_scoped_memory_writes_separate_file` — разные файлы для global/thread
- `test_user_stays_global_with_thread_key` — USER.md не уходит в threads/
- `test_thread_and_global_are_independent_on_load` — не пересекаются при чтении
- `test_thread_key_none_falls_back_to_global` — backward compat
- `test_snapshot_reflects_thread_scoped_path` — форматирует snapshot из thread файла
- `test_snapshot_from_thread_and_global_are_independent` — не подхватывает global entry
- `test_drift_guard_with_thread_key` — детекция внешней модификации на thread файле
- `test_load_time_sanitization_with_thread_key` — poisoned entry блокируется в thread snapshot
- `test_already_blocked_entry_passes_through` — no double-wrap (расширен)
- Запуск: `cd ~/.hermes/hermes-agent && source venv/bin/activate && python -m pytest tests/tools/test_memory_tool.py -v -o "addopts="`
## Неизменённое
- `run_conversation` / `conversation_loop.py` — не трогали
- `background_review.py` — наследует `_memory_store` от родителя, thread_key приходит автоматически
- `tools/memory_tool.py` schema/MEMORY_SCHEMA — не меняли, кастомная инструкция в system prompt
- External memory providers (honcho/mem0) — не трогали