Quickstart
On this page
There are two ways to use memory: pick individual memory primitives directly, or use AIMemoryManager as a single orchestrating facade. Most new projects should start with the facade.
Option A — AIMemoryManager (recommended)
from zerda.memory import AIMemoryManager, MemoryConfig
# 1. Create a manager. A minimal LLM wrapper only needs a `.generate(prompt, max_tokens)` method.
class MyLLM:
def generate(self, prompt: str, max_tokens: int = 256) -> str:
... # call your actual model here
return "..."
mgr = AIMemoryManager(config=MemoryConfig(), llm=MyLLM())
# 2. Save a conversation turn (writes through STM, SemanticMemory, and conditionally LTM)
mgr.save_interaction(
user_id="alice",
tenant_id="acme-corp",
user_input="How do I use Python decorators?",
assistant_output="A decorator wraps a function to extend its behavior without modifying it...",
importance=0.6,
topics=["python", "decorators"],
)
# 3. Build a token-budgeted context for the next turn
ctx = mgr.build_context(
user_id="alice",
tenant_id="acme-corp",
query="Show me a decorator example with arguments",
)
print(ctx.full_text) # ready to prepend to your LLM prompt
print(ctx.token_estimate) # rough token count
[!TIP] Multi-tenancy is built in Every
AIMemoryManagercall takes bothuser_idandtenant_id. Eachtenant_idgets its own isolatedShortTermMemory,WorkingMemory,LongTermMemory(separate SQLite file), andSemanticMemoryindex — created lazily on first use.
Running maintenance
Long-term memory grows unbounded unless you periodically apply decay, eviction, and compression:
report = mgr.run_maintenance(tenant_id="acme-corp")
print(report)
# {'tenant_id': 'acme-corp', 'timestamp': ..., 'stm_evicted': 0,
# 'ltm_decay_updated': 12, 'ltm_deleted': 0, 'working_forgotten': 0}
Call this on a schedule (e.g. a cron job or background task) — AIMemoryManager does not do this automatically.
Option B — Individual primitives
If you don't need the full orchestration layer, compose the pieces yourself:
from zerda.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=5)
memory.save_context(
{"input": "What's the capital of France?"},
{"output": "Paris."},
)
variables = memory.load_memory_variables({})
print(variables["history"])
# "user: What's the capital of France?\nassistant: Paris."
from zerda.memory import SemanticMemory, MemoryEntry, MemoryType
import time
sm = SemanticMemory() # uses stub embeddings unless sentence-transformers is installed
entry = MemoryEntry(
content={"input": "My favorite language is Rust", "output": "Noted!"},
timestamp=time.time(),
memory_type=MemoryType.SEMANTIC,
importance=0.7,
)
sm.add(entry)
results = sm.search("What language does the user like?", k=3)
for matched_entry, score in results:
print(score, matched_entry.content)
Async usage
AIMemoryManager and every built-in ConversationXMemory class expose async equivalents (asave_interaction/abuild_context on the manager; asave_context/aload_memory_variables on the memory classes). These are thin wrappers that run the synchronous method in a thread pool (asyncio.to_thread or a ThreadPoolExecutor) — there is no genuinely async I/O anywhere in this package. Note that BaseMemory itself does not declare these async methods (only BaseRetriever does); each concrete memory class re-implements them individually — see Architecture → Concurrency Model.
import asyncio
from zerda.memory import ConversationBufferMemory
async def main():
mem = ConversationBufferMemory()
await mem.asave_context({"input": "hi"}, {"output": "hello"})
vars = await mem.aload_memory_variables({})
print(vars)
asyncio.run(main())
Next steps
- Architecture — how the tiers fit together, sequence diagrams for
save_interactionandbuild_context. - Configuration — every
MemoryConfigfield explained. - API Reference — full class-by-class documentation.