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.

Python · 29 lines
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 AIMemoryManager call takes both user_id and tenant_id. Each tenant_id gets its own isolated ShortTermMemory, WorkingMemory, LongTermMemory (separate SQLite file), and SemanticMemory index — created lazily on first use.

Running maintenance

Long-term memory grows unbounded unless you periodically apply decay, eviction, and compression:

Python · 4 lines
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:

Python · 12 lines
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."
Python · 15 lines
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.

Python · 10 lines
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_interaction and build_context.
  • Configuration — every MemoryConfig field explained.
  • API Reference — full class-by-class documentation.

Related pages