Skip to main content

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_name
  • project_synapse_status
  • preference_communication_style
  • mistake_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 context category 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")

Anti-Patterns to Avoid

Next Steps