[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:
@@ -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) — не трогали
|
||||
Reference in New Issue
Block a user