- New: smart-home/home-assistant-dashboard-conventions (Mushroom cards, view tabs, no Bubble Cards) - Updated: rke2, ceph, galera, proxmox, brainstorming, compound-learning, 1password-cli, smart-home-automation skills - New references: ceph-cluster-administration, docker-volume-forensics, ceph-crush-weight, ceph-ec-mixed-size
5.0 KiB
Hermes Memory System Architecture (2026-07-14)
Overview
Hermes has two persistent memory stores injected into the system prompt:
- MEMORY.md — agent's personal notes (environment facts, infrastructure topology, tool quirks). Default limit: 2,200 chars.
- USER.md — what the agent knows about the user (preferences, style, communication rules). Default limit: 1,375 chars.
Both files live at ~/.hermes/memories/ and use § (section sign) as
entry delimiter: \n§\n between entries.
Config
# ~/.hermes/config.yaml
memory:
memory_enabled: true
user_profile_enabled: true
memory_char_limit: 2200
user_char_limit: 1375
nudge_interval: 10
Adjust limits:
hermes config set memory.user_char_limit 3000
hermes config set memory.memory_char_limit 4000
# Takes effect on next /reset or gateway restart
System Prompt Injection Pipeline
1. Loading (once per session)
agent_init.py creates a MemoryStore instance and calls
load_from_disk(). This reads both .md files, splits on §, deduplicates
entries, and creates a frozen snapshot.
2. Frozen Snapshot (prefix-cache invariant)
self._system_prompt_snapshot = {
"memory": self._render_block("memory", sanitized_memory),
"user": self._render_block("user", sanitized_user),
}
The snapshot is never mutated mid-session — even when memory() tool
calls write new entries to disk. This keeps the system prompt byte-stable,
preserving the LLM prefix cache across all turns.
3. Injection Point
system_prompt.py injects both blocks into the volatile tier of the
system prompt (alongside date, model, provider):
if agent._memory_enabled:
mem_block = agent._memory_store.format_for_system_prompt("memory")
volatile_parts.append(mem_block)
if agent._user_profile_enabled:
user_block = agent._memory_store.format_for_system_prompt("user")
volatile_parts.append(user_block)
4. Rendered Format
════════════════════════════════════════════
MEMORY (your personal notes) [100% — 2,217/2,200 chars]
════════════════════════════════════════════
entry1
§
entry2
§
...
════════════════════════════════════════════
USER PROFILE (who the user is) [86% — 1,184/1,375 chars]
════════════════════════════════════════════
entry1
§
entry2
...
5. Threat Scanning
Each entry is scanned for prompt-injection patterns at load time
(_sanitize_entries_for_snapshot). Matches are replaced with
[BLOCKED: ...] in the snapshot, but the original text remains in the
live file so the user can inspect and delete it.
System Prompt Tiers
STABLE (byte-stable, cached for entire session)
1. SOUL.md — identity ("You are Hermes Agent...")
2. Tool schemas, platform hints, guardrails
CONTEXT (cwd-dependent, changes between sessions)
3. .hermes.md / HERMES.md (walks to git root)
4. AGENTS.md (cwd only)
5. CLAUDE.md (cwd only)
6. .cursorrules (cwd only)
(first match wins — only ONE loaded, ~20K char cap)
VOLATILE (per-session, not cached)
7. MEMORY.md snapshot (memory_char_limit)
8. USER.md snapshot (user_char_limit)
9. External memory provider (optional, e.g. Hindsight)
10. Date, model, provider, session ID
Alternative Context Injection Points
Beyond MEMORY.md/USER.md, additional context can be injected via:
- SOUL.md (
~/.hermes/SOUL.md) — global identity/personality, always loaded, no char limit - AGENTS.md (in repo root) — project conventions, ~20K char cap, loaded when cwd is the repo
- CLAUDE.md / .cursorrules — same as AGENTS.md, first-match-wins
- .hermes.md — walks up to git root, highest priority among context files
Live State vs Snapshot
| Aspect | Snapshot (System Prompt) | Live State (Tool Calls) |
|---|---|---|
| When read | Once at session start | Every memory() call |
| When written | Never (read-only) | Every memory() call → disk |
| Prefix cache | Byte-stable for session | N/A (not in prompt) |
| Changes visible | Next /reset or restart |
Immediately in tool response |
Key Insight
Memory writes via the memory() tool are not visible in the current
session's system prompt — they persist to disk but the snapshot was
already frozen. This is by design: mutating the system prompt mid-session
would invalidate the prefix cache and multiply API costs.
Source Files
tools/memory_tool.py—MemoryStoreclass, file I/O, renderingagent/agent_init.pylines 1199-1218 — initializationagent/system_prompt.pylines 426-435 — injection into volatile tieragent/prompt_builder.pylines 1921-1968 — context files (AGENTS.md etc.)agent/coding_context.py— project detection, context file discovery