Memory Best Practices
How to structure memories for effective recall — categories, keys, tags, priorities.
Memory Best Practices
How you structure memories determines how useful they are. This guide covers patterns for categorizing, tagging, and prioritizing memories so the LLM can recall the right information at the right time.
Categories: Pick the Most Specific
| Category | Use For | Example |
|---|---|---|
identity |
User name, role, contact info | "user_name": "Michael Schäfer" |
preference |
Likes, dislikes, working style | "communication": "Prefers concise responses" |
fact |
Verifiable facts | "office_location": "Berlin, Germany" |
project |
Project status, decisions | "project_synapse": "v1.5.0 deployed" |
skill |
User's skills | "skill_python": "Advanced, 10+ years" |
mistake |
Past errors to avoid | "mistake_npm_version": "Always bump version" |
context |
Session-relevant context | "current_focus": "Working on docs system" |
note |
Misc notes | "note_idea": "Try Redis for caching" |
When in doubt, use `fact` for verifiable info and `note` for everything else.
Don't over-categorize — better to have a clear `fact` than a confusing `context`.
Keys: Meaningful Identifiers
The key field is the memory's identifier. Use meaningful, stable keys:
Good keys:
user_nameproject_synapse_statuspreference_communication_stylemistake_npm_version_bump
Bad keys:
mem_001(not meaningful)temp(not descriptive)2026-06-27-note(date doesn't help recall)
Key naming conventions
snake_case(lowercase with underscores)- Prefix with category:
preference_*,project_*,mistake_* - Use descriptive nouns, not verbs
- Keep under 50 characters
Tags: For Search and Filtering
Tags enable fast filtering and search. Add 2-5 tags per memory:
{
"category": "project",
"key": "project_synapse_status",
"content": "Synapse v1.5.0 deployed. Next: v1.6.0 with docs system.",
"tags": ["synapse", "deployment", "status", "v1.5.0"]
}Tag patterns
- Project names:
synapse,synapse-mcp,synapse-chat - Topics:
deployment,ci,database,auth - Status:
active,completed,blocked - Priority indicators:
urgent,long-term
Tags are case-insensitive. Use lowercase for consistency.
Priorities: Be Realistic
| Priority | Use For | % of Memories |
|---|---|---|
critical |
User identity, legal info, irreversible decisions | ~5% |
high |
Active projects, important preferences | ~20% |
normal |
Most facts, notes, context | ~65% |
low |
Ephemeral, nice-to-know | ~10% |
Don't mark everything `critical`. If everything is critical, nothing is.
Reserve `critical` for things that would cause real harm if forgotten.
When to Store vs Not Store
Always store
- User identity (name, email, role)
- Long-term preferences
- Project decisions and rationale
- Past mistakes and lessons learned
- Commitments made to the user
Don't store
- Transient state (use variables instead)
- Verbatim conversation history (chat system handles this)
- Sensitive data (passwords, API keys)
- Easily derivable facts (current date, file contents)
- Ephemeral context (use
contextcategory with low priority)
Updating Memories
POST /memory with the same category + key updates the existing memory:
# Initial store
store("project", "project_synapse_status", "v1.4.0 deployed", priority="high")
# Later: update with same key
store("project", "project_synapse_status", "v1.5.0 deployed. CI green.", priority="high")Use stable keys so you can update without creating duplicates. The LLM
should re-POST the same key as info changes, not create new memories.
Memory Lifecycle
Create → Active → Stale → Archive → Delete- Create: POST /memory with full context
- Active: Recall frequently, update as needed
- Stale: Still relevant but not actively used (lower priority?)
- Archive: Set priority to
low, keep for historical reference - Delete: DELETE /memory/:id when no longer relevant
Periodic cleanup
# Find memories not updated in 90 days
old_memories = requests.get(
f"{URL}/memory/search?q=*",
headers={"Authorization": f"Bearer {KEY}"}
)
for mem in old_memories["results"]:
if is_stale(mem, days=90):
# Either delete or lower priority
if is_obsolete(mem):
delete_memory(mem["id"])
else:
update_memory(mem["id"], priority="low")Pattern: Memory Inheritance
For hierarchical context (project → subproject → task):
# Parent project
store("project", "project_synapse", "Main Synapse project",
tags=["synapse", "parent"], priority="high")
# Sub-project (tags link to parent)
store("project", "project_synapse_docs", "Docs system for Synapse",
tags=["synapse", "docs", "synapse-parent"], priority="high")
# Specific task (tags link to sub-project)
store("project", "task_docs_loader", "Implement docs-loader.ts",
tags=["synapse", "docs", "task"], priority="normal")The LLM can then search q=synapse+docs to find all related memories.
Pattern: Decision Log
Store decisions with rationale so the LLM doesn't re-litigate them:
store("fact", "decision_postgres_over_sqlite",
"Chose PostgreSQL over SQLite for production. Reason: concurrent writes, "
"FTS5 native support, better backup story. Date: 2026-06-15. Decided by: Michael.",
tags=["decision", "database", "postgres", "sqlite"],
priority="high")Pattern: Mistake Avoidance
Store mistakes with specific avoidance instructions:
store("mistake", "mistake_forget_version_bump",
"Forgot to bump package.json version after changes. npm publish failed. "
"FIX: Always run `npm version patch` before pushing. "
"CI fails with 'version already exists' if you forget.",
tags=["npm", "ci", "publish", "version"],
priority="high")