Compare commits
1 Commits
main
...
burn/20260
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
511eacb573 |
335
docs/memory-architecture.md
Normal file
335
docs/memory-architecture.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Memory Architecture Guide
|
||||
|
||||
How Hermes Agent remembers things across sessions — the stores, the tools, the data flow, and how to configure it all.
|
||||
|
||||
## Overview
|
||||
|
||||
Hermes has a multi-layered memory system. It is not one thing — it is several independent systems that complement each other:
|
||||
|
||||
1. **Persistent Memory** (MEMORY.md / USER.md) — bounded, curated notes injected into every system prompt
|
||||
2. **Session Search** — full-text search across all past conversation transcripts
|
||||
3. **Skills** — procedural memory: reusable workflows stored as SKILL.md files
|
||||
4. **External Memory Providers** — optional plugins (Honcho, Holographic, Mem0, etc.) for deeper recall
|
||||
|
||||
All built-in memory lives on disk under `~/.hermes/` (or `$HERMES_HOME`). No memory data leaves the machine unless you explicitly configure an external cloud provider.
|
||||
|
||||
## Memory Types in Detail
|
||||
|
||||
### 1. Persistent Memory (MEMORY.md and USER.md)
|
||||
|
||||
The core memory system. Two files in `~/.hermes/memories/`:
|
||||
|
||||
| File | Purpose | Default Char Limit |
|
||||
|------|---------|--------------------|
|
||||
| `MEMORY.md` | Agent's personal notes — environment facts, project conventions, tool quirks, lessons learned | 2,200 chars (~800 tokens) |
|
||||
| `USER.md` | User profile — name, preferences, communication style, pet peeves | 1,375 chars (~500 tokens) |
|
||||
|
||||
**How it works:**
|
||||
|
||||
- Loaded from disk at session start and injected into the system prompt as a frozen snapshot
|
||||
- The agent uses the `memory` tool to add, replace, or remove entries during a session
|
||||
- Mid-session writes go to disk immediately (durable) but do NOT update the system prompt — this preserves the LLM's prefix cache for performance
|
||||
- The snapshot refreshes on the next session start
|
||||
- Entries are delimited by `§` (section sign) and can be multiline
|
||||
|
||||
**System prompt appearance:**
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════
|
||||
MEMORY (your personal notes) [67% — 1,474/2,200 chars]
|
||||
══════════════════════════════════════════════
|
||||
User's project is a Rust web service at ~/code/myapi using Axum + SQLx
|
||||
§
|
||||
This machine runs Ubuntu 22.04, has Docker and Podman installed
|
||||
§
|
||||
User prefers concise responses, dislikes verbose explanations
|
||||
```
|
||||
|
||||
**Memory tool actions:**
|
||||
|
||||
- `add` — append a new entry (rejected if it would exceed the char limit)
|
||||
- `replace` — find an entry by substring match and replace it
|
||||
- `remove` — find an entry by substring match and delete it
|
||||
|
||||
Substring matching means you only need a unique fragment of the entry, not the full text. If the fragment matches multiple entries, the tool returns an error asking for a more specific match.
|
||||
|
||||
### 2. Session Search
|
||||
|
||||
Cross-session conversation recall via SQLite FTS5 full-text search.
|
||||
|
||||
- All CLI and messaging sessions are stored in `~/.hermes/state.db`
|
||||
- The `session_search` tool finds relevant past conversations by keyword
|
||||
- Top matching sessions are summarized by Gemini Flash (cheap, fast) before being returned to the main model
|
||||
- Returns focused summaries, not raw transcripts
|
||||
|
||||
**When to use session_search vs. memory:**
|
||||
|
||||
| Feature | Persistent Memory | Session Search |
|
||||
|---------|------------------|----------------|
|
||||
| Capacity | ~3,575 chars total | Unlimited (all sessions) |
|
||||
| Speed | Instant (in system prompt) | Requires search + LLM summarization |
|
||||
| Use case | Key facts always in context | "What did we discuss about X last week?" |
|
||||
| Management | Manually curated by the agent | Automatic — all sessions stored |
|
||||
| Token cost | Fixed per session (~1,300 tokens) | On-demand (searched when needed) |
|
||||
|
||||
**Rule of thumb:** Memory is for facts that should *always* be available. Session search is for recalling specific past conversations on demand. Don't save task progress or session outcomes to memory — use session_search to find those.
|
||||
|
||||
### 3. Skills (Procedural Memory)
|
||||
|
||||
Skills are reusable workflows stored as `SKILL.md` files in `~/.hermes/skills/` (and optionally external skill directories).
|
||||
|
||||
- Organized by category: `skills/github/github-pr-workflow/SKILL.md`
|
||||
- YAML frontmatter with name, description, version, platform restrictions
|
||||
- Progressive disclosure: metadata shown in skill list, full content loaded on demand via `skill_view`
|
||||
- The agent creates skills proactively after complex tasks (5+ tool calls) using the `skill_manage` tool
|
||||
- Skills can be patched when found outdated — stale skills are a liability
|
||||
|
||||
Skills are *not* injected into the system prompt by default. The agent sees a compact index of available skills and loads them on demand. This keeps the prompt lean while giving access to deep procedural knowledge.
|
||||
|
||||
**Skills vs. Memory:**
|
||||
|
||||
- **Memory:** compact facts ("User's project uses Go 1.22 with chi router")
|
||||
- **Skills:** detailed procedures ("How to deploy the staging server: step 1, step 2, ...")
|
||||
|
||||
### 4. External Memory Providers
|
||||
|
||||
Optional plugins that add deeper, structured memory alongside the built-in system. Only one external provider can be active at a time.
|
||||
|
||||
| Provider | Storage | Key Feature |
|
||||
|----------|---------|-------------|
|
||||
| Honcho | Cloud | Dialectic user modeling with semantic search |
|
||||
| OpenViking | Self-hosted | Filesystem-style knowledge hierarchy |
|
||||
| Mem0 | Cloud | Server-side LLM fact extraction |
|
||||
| Hindsight | Cloud/Local | Knowledge graph with entity resolution |
|
||||
| Holographic | Local SQLite | HRR algebraic reasoning + trust scoring |
|
||||
| RetainDB | Cloud | Hybrid search with delta compression |
|
||||
| ByteRover | Local/Cloud | Hierarchical knowledge tree with CLI |
|
||||
| Supermemory | Cloud | Context fencing + session graph ingest |
|
||||
|
||||
External providers run **alongside** built-in memory (never replacing it). They receive hooks for:
|
||||
- System prompt injection (provider context)
|
||||
- Pre-turn memory prefetch
|
||||
- Post-turn conversation sync
|
||||
- Session-end extraction
|
||||
- Built-in memory write mirroring
|
||||
|
||||
Setup: `hermes memory setup` or set `memory.provider` in `~/.hermes/config.yaml`.
|
||||
|
||||
See `website/docs/user-guide/features/memory-providers.md` for full provider details.
|
||||
|
||||
## How the Systems Interact
|
||||
|
||||
```
|
||||
Session Start
|
||||
|
|
||||
+--> Load MEMORY.md + USER.md from disk --> frozen snapshot into system prompt
|
||||
+--> Provider: system_prompt_block() --> injected into system prompt
|
||||
+--> Skills index --> injected into system prompt (compact metadata only)
|
||||
|
|
||||
v
|
||||
Each Turn
|
||||
|
|
||||
+--> Provider: prefetch(query) --> relevant recalled context
|
||||
+--> Agent sees: system prompt (memory + provider context + skills index)
|
||||
+--> Agent can call: memory tool, session_search tool, skill tools, provider tools
|
||||
|
|
||||
v
|
||||
After Each Response
|
||||
|
|
||||
+--> Provider: sync_turn(user, assistant) --> persist conversation
|
||||
|
|
||||
v
|
||||
Periodic (every N turns, default 10)
|
||||
|
|
||||
+--> Memory nudge: agent prompted to review and update memory
|
||||
|
|
||||
v
|
||||
Session End / Compression
|
||||
|
|
||||
+--> Memory flush: agent saves important facts before context is discarded
|
||||
+--> Provider: on_session_end(messages) --> final extraction
|
||||
+--> Provider: on_pre_compress(messages) --> save insights before compression
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### What to Save
|
||||
|
||||
Save proactively — don't wait for the user to ask:
|
||||
|
||||
- **User preferences:** "I prefer TypeScript over JavaScript" → `user` target
|
||||
- **Corrections:** "Don't use sudo for Docker, I'm in the docker group" → `memory` target
|
||||
- **Environment facts:** "This server runs Debian 12 with PostgreSQL 16" → `memory` target
|
||||
- **Conventions:** "Project uses tabs, 120-char lines, Google docstrings" → `memory` target
|
||||
- **Explicit requests:** "Remember that my API key rotation is monthly" → `memory` target
|
||||
|
||||
### What NOT to Save
|
||||
|
||||
- **Task progress or session outcomes** — use session_search to recall these
|
||||
- **Trivially re-discoverable facts** — "Python 3.12 supports f-strings" (web search this)
|
||||
- **Raw data dumps** — large code blocks, log files, data tables
|
||||
- **Session-specific ephemera** — temporary file paths, one-off debugging context
|
||||
- **Content already in SOUL.md or AGENTS.md** — those are already in context
|
||||
|
||||
### Writing Good Entries
|
||||
|
||||
Compact, information-dense entries work best:
|
||||
|
||||
```
|
||||
# Good — packs multiple related facts
|
||||
User runs macOS 14 Sonoma, uses Homebrew, has Docker Desktop and Podman. Shell: zsh. Editor: VS Code with Vim bindings.
|
||||
|
||||
# Good — specific, actionable convention
|
||||
Project ~/code/api uses Go 1.22, sqlc for DB, chi router. Tests: make test. CI: GitHub Actions.
|
||||
|
||||
# Bad — too vague
|
||||
User has a project.
|
||||
|
||||
# Bad — too verbose
|
||||
On January 5th, 2026, the user asked me to look at their project which is
|
||||
located at ~/code/api. I discovered it uses Go version 1.22 and...
|
||||
```
|
||||
|
||||
### Capacity Management
|
||||
|
||||
When memory is above 80% capacity (visible in the system prompt header), consolidate before adding. Merge related entries into shorter, denser versions. The tool will reject additions that would exceed the limit — use `replace` to consolidate first.
|
||||
|
||||
Priority order for what stays in memory:
|
||||
1. User preferences and corrections (highest — prevents repeated steering)
|
||||
2. Environment facts and project conventions
|
||||
3. Tool quirks and workarounds
|
||||
4. Lessons learned (lowest — can often be rediscovered)
|
||||
|
||||
### Memory Nudge
|
||||
|
||||
Every N turns (default: 10), the agent receives a nudge prompting it to review and update its memory. This is a lightweight prompt injected into the conversation — not a separate API call. The agent can choose to update memory or skip if nothing has changed.
|
||||
|
||||
## Privacy and Data Locality
|
||||
|
||||
**Built-in memory is fully local.** MEMORY.md and USER.md are plain text files in `~/.hermes/memories/`. No network calls are made in the memory read/write path. The memory tool scans entries for prompt injection and exfiltration patterns before accepting them.
|
||||
|
||||
**Session search is local.** The SQLite database (`~/.hermes/state.db`) stays on disk. FTS5 search is a local operation. However, the summarization step uses Gemini Flash (via the auxiliary LLM client) — conversation snippets are sent to Google's API for summarization. If this is a concern, session_search can be disabled.
|
||||
|
||||
**External providers may send data off-machine.** Cloud providers (Honcho, Mem0, RetainDB, Supermemory) send data to their respective APIs. Self-hosted providers (OpenViking, Hindsight local mode, Holographic, ByteRover local mode) keep everything on your machine. Check the provider's documentation for specifics.
|
||||
|
||||
**Security scanning.** All content written to memory (via the `memory` tool) is scanned for:
|
||||
- Prompt injection patterns ("ignore previous instructions", role hijacking, etc.)
|
||||
- Credential exfiltration attempts (curl/wget with secrets, reading .env files)
|
||||
- SSH backdoor patterns
|
||||
- Invisible unicode characters (used for steganographic injection)
|
||||
|
||||
Blocked content is rejected with a descriptive error message.
|
||||
|
||||
## Configuration
|
||||
|
||||
In `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
# Enable/disable the two built-in memory stores
|
||||
memory_enabled: true # MEMORY.md
|
||||
user_profile_enabled: true # USER.md
|
||||
|
||||
# Character limits (not tokens — model-independent)
|
||||
memory_char_limit: 2200 # ~800 tokens at 2.75 chars/token
|
||||
user_char_limit: 1375 # ~500 tokens at 2.75 chars/token
|
||||
|
||||
# External memory provider (empty string = built-in only)
|
||||
# Options: "honcho", "openviking", "mem0", "hindsight",
|
||||
# "holographic", "retaindb", "byterover", "supermemory"
|
||||
provider: ""
|
||||
```
|
||||
|
||||
Additional settings are read from `run_agent.py` defaults:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `nudge_interval` | 10 | Turns between memory review nudges (0 = disabled) |
|
||||
| `flush_min_turns` | 6 | Minimum user turns before memory flush on session end/compression (0 = never flush) |
|
||||
|
||||
These are set under the `memory` key in config.yaml:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
```
|
||||
|
||||
### Disabling Memory
|
||||
|
||||
To disable memory entirely, set both to false:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
memory_enabled: false
|
||||
user_profile_enabled: false
|
||||
```
|
||||
|
||||
The `memory` tool will not appear in the tool list, and no memory blocks are injected into the system prompt.
|
||||
|
||||
You can also disable memory per-invocation with `skip_memory=True` in the AIAgent constructor (used by cron jobs and flush agents).
|
||||
|
||||
## File Locations
|
||||
|
||||
```
|
||||
~/.hermes/
|
||||
├── memories/
|
||||
│ ├── MEMORY.md # Agent's persistent notes
|
||||
│ ├── USER.md # User profile
|
||||
│ ├── MEMORY.md.lock # File lock (auto-created)
|
||||
│ └── USER.md.lock # File lock (auto-created)
|
||||
├── state.db # SQLite session store (FTS5)
|
||||
├── config.yaml # Memory config + provider selection
|
||||
└── .env # API keys for external providers
|
||||
```
|
||||
|
||||
All paths respect `$HERMES_HOME` — if you use Hermes profiles, each profile has its own isolated memory directory.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Memory full" errors
|
||||
|
||||
The tool returns an error when adding would exceed the character limit. The response includes current entries so the agent can consolidate. Fix by:
|
||||
1. Replacing multiple related entries with one denser entry
|
||||
2. Removing entries that are no longer relevant
|
||||
3. Increasing `memory_char_limit` in config (at the cost of larger system prompts)
|
||||
|
||||
### Stale memory entries
|
||||
|
||||
If the agent seems to have outdated information:
|
||||
- Check `~/.hermes/memories/MEMORY.md` directly — you can edit it by hand
|
||||
- The frozen snapshot pattern means changes only take effect on the next session start
|
||||
- If the agent wrote something wrong mid-session, it persists on disk but won't affect the current session's system prompt
|
||||
|
||||
### Memory not appearing in system prompt
|
||||
|
||||
- Verify `memory_enabled: true` in config.yaml
|
||||
- Check that `~/.hermes/memories/MEMORY.md` exists and has content
|
||||
- The file might be empty if all entries were removed — add entries with the `memory` tool
|
||||
|
||||
### Session search returns no results
|
||||
|
||||
- Session search requires sessions to be stored in `state.db` — new installations have no history
|
||||
- FTS5 indexes are built automatically but may lag behind on very large databases
|
||||
- The summarization step requires the auxiliary LLM client to be configured (API key for Gemini Flash)
|
||||
|
||||
### Skill drift
|
||||
|
||||
Skills that haven't been updated can become wrong or incomplete. The agent is prompted to patch skills when it finds them outdated during use (`skill_manage(action='patch')`). If you notice stale skills:
|
||||
- Use `/skills` to browse and review installed skills
|
||||
- Delete or update skills in `~/.hermes/skills/` directly
|
||||
- The agent creates skills after complex tasks — review and prune periodically
|
||||
|
||||
### Provider not activating
|
||||
|
||||
- Run `hermes memory status` to check provider state
|
||||
- Verify the provider plugin is installed in `~/.hermes/plugins/memory/`
|
||||
- Check that required API keys are set in `~/.hermes/.env`
|
||||
- Start a new session after changing provider config — existing sessions use the old provider
|
||||
|
||||
### Concurrent write conflicts
|
||||
|
||||
The memory tool uses file locking (`fcntl.flock`) and atomic file replacement (`os.replace`) to handle concurrent writes from multiple sessions. If you see corrupted memory files:
|
||||
- Check for stale `.lock` files in `~/.hermes/memories/`
|
||||
- Restart any hung Hermes processes
|
||||
- The atomic write pattern means readers always see either the old or new file — never a partial write
|
||||
Reference in New Issue
Block a user