Files
hermes-skills/devops/rke2-cluster-administration/references/hermes-memory-system-architecture.md
T
Debian 01bd921ced feat: add home-assistant-dashboard-conventions skill + update multiple skills
- 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
2026-07-14 18:35:16 +00:00

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.pyMemoryStore class, file I/O, rendering
  • agent/agent_init.py lines 1199-1218 — initialization
  • agent/system_prompt.py lines 426-435 — injection into volatile tier
  • agent/prompt_builder.py lines 1921-1968 — context files (AGENTS.md etc.)
  • agent/coding_context.py — project detection, context file discovery