How Headroom Persistent Memory Lets LLMs Remember Across Conversations
Problem
Every time I start a new conversation with an LLM, it starts from zero. It does not remember that I prefer Python for backend work, that I use uv instead of pip, or that our project uses tabs instead of spaces. Carrying full conversation history quickly overflows context windows, and manually managing memory is fragile.
What Is Persistent Memory?
Headroom’s with_memory() wrapper adds persistent, hierarchical memory to any OpenAI-compatible client. It extracts key facts inline during chat completions, stores them with local embeddings, and injects relevant memories automatically in future conversations.
One-Line Setup
from openai import OpenAIfrom headroom import with_memory
# One lineclient = with_memory(OpenAI(), user_id="alice")That’s it. No external database. No complex infrastructure.
How It Works
When the LLM responds, with_memory() extracts <memory> blocks from the response inline. These are stored with embeddings for semantic search. Future queries automatically retrieve relevant memories.
# Memory extracted inline — zero extra latencyresponse = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "I prefer Python for backend work"}])
# Later, in a new conversation...response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "What language should I use?"}])# Response uses the Python preference from memoryThe model answers “Python” because the memory system injected the preference automatically.
Memory Scopes and Categories
Headroom organizes memory into four scope levels:
- User — all time, across all sessions
- Session — current session only
- Agent — current agent only
- Turn — single turn
Memory categories include:
- PREFERENCE
- FACT
- CONTEXT
- ENTITY
- DECISION
- INSIGHT
Temporal Versioning
When a fact changes — for example, a user changes jobs — Headroom creates a supersession chain rather than overwriting the old memory. Old facts are preserved for audit but excluded from current queries by default.
Searching and Managing Memories
# Search memories directlyresults = client.memory.search("python preferences", top_k=5)
# Add a memory manuallyclient.memory.add("User is a senior engineer", category="fact", importance=0.9)
# Check statsstats = client.memory.stats()Storage Backend
All storage is embedded:
- SQLite for CRUD operations
- HNSW for vector similarity search
- FTS5 for full-text search
No external services. Latency is under 50ms for memory injection and under 10ms for storage.
Why This Matters
Instead of sending 10,000 tokens of past conversation, you send ~100 tokens of summarized memories. This cuts costs and prevents context window overflow while preserving user preferences and facts.
Common Mistake
I initially treated memory as a simple key-value store. That breaks when facts change. Headroom’s hierarchical scoping and temporal versioning handle changing facts gracefully.
Summary
In this post, I showed how Headroom’s persistent memory turns ephemeral LLM conversations into stateful interactions. The key point is that with_memory() extracts facts inline, stores them with local embeddings, and injects relevant context automatically — all with embedded storage and no external dependencies.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments