Building a Chatbot
intermediateAdd persistent conversational memory to a chatbot. Store conversations, recall context, and let your bot remember across sessions.
What you'll build
A chatbot that remembers past conversations across sessions — it learns user preferences, recalls previous topics, and improves over time without re-training.
Prerequisites
| Requirement | Details |
|---|---|
| Account | Free tier or above |
| API key | From your dashboard |
| Libraries | Python requests (or any HTTP client) |
| Time | ~15 minutes |
The pattern
Most chatbots forget everything between sessions. With Engramma, every conversation turn becomes a memory that the bot can recall later:
User says something → Store it → Next turn, recall relevant memories → Use them as context
Steps
Set up your API key
Store conversation turns
Recall relevant context
Inject context into prompts
Let consolidation improve quality
Complete code
import os
import requests
API_BASE = "https://api.engramma-memory.com/v1"
API_KEY = os.environ["ENGRAMMA_API_KEY"]
HEADERS = {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
def store_conversation_turn(user_id: str, role: str, message: str):
"""Store a conversation turn as a memory."""
resp = requests.post(
f"{API_BASE}/memory/text/store",
headers=HEADERS,
json={
"text": f"[{role}] {message}",
"metadata": {
"user_id": user_id,
"role": role,
"type": "conversation"
}
}
)
resp.raise_for_status()
return resp.json() # {"success": true, "pattern_id": "pat_...", ...}
def get_relevant_context(user_id: str, current_message: str, top_k: int = 5):
"""Recall memories relevant to the current message using Active Inference."""
resp = requests.post(
f"{API_BASE}/memory/text/recall",
headers=HEADERS,
json={
"query": current_message,
"top_k": top_k
}
)
resp.raise_for_status()
data = resp.json()
# Filter by similarity threshold and user_id in metadata
return [
r["text"] for r in data["results"]
if r["similarity"] > 0.6
and r.get("metadata", {}).get("user_id") == user_id
]
def chat(user_id: str, user_message: str):
# 1. Get relevant past context
memories = get_relevant_context(user_id, user_message)
# 2. Build prompt with memory context
context = "\n".join(memories) if memories else "No previous context."
prompt = f"""You are a helpful assistant with memory of past conversations.
Relevant memories:
{context}
User: {user_message}
Assistant:"""
# 3. Generate response (replace with your LLM call)
response = call_your_llm(prompt)
# 4. Store both turns for future recall
store_conversation_turn(user_id, "user", user_message)
store_conversation_turn(user_id, "assistant", response)
return response
# Example usage
response = chat("user_123", "I prefer dark mode and concise answers")
print(response)
# Later session — the bot remembers
response = chat("user_123", "Can you adjust your style?")
# The bot recalls the dark mode + concise preference from memoryHow it works in practice
| Session | What happens |
|---|---|
| First visit | User mentions preferences → stored as memories |
| Second visit | Bot recalls past preferences → adjusts behavior |
| After consolidation | Repeated preferences strengthen, one-off mentions fade |
| Week later | Bot remembers core preferences, forgets noise |
Retrieve vs Recall
Engramma offers two endpoints for querying memories:
| Endpoint | Use case | Mechanism |
|---|---|---|
/v1/memory/text/retrieve | Fast semantic search | Embedding similarity only |
/v1/memory/text/recall | Deeper contextual recall | Active Inference + semantic re-ranking |
For a chatbot, recall is generally preferred — it finds contextually relevant memories even when the wording differs from what was stored. Use retrieve when you need lower latency and simple keyword/semantic matching is sufficient.
Filtering by user
Each memory includes a user_id in metadata. Since the retrieve and recall endpoints do not support metadata filtering directly, filter results client-side after retrieval:
import requests
resp = requests.post(
f"{API_BASE}/memory/text/recall",
headers=HEADERS,
json={"query": "What does the user prefer?", "top_k": 10}
)
data = resp.json()
# Filter client-side by user_id
user_memories = [
r for r in data["results"]
if r["metadata"].get("user_id") == "user_123"
]
# Filter by type for shared team knowledge
team_memories = [
r for r in data["results"]
if r["metadata"].get("type") == "team_knowledge"
]Since metadata filtering is not available at the API level, request a higher top_k value and filter results in your application code. This ensures you get enough relevant results after filtering.
Tips for production
Store with intention. Not every message needs to be stored — filter out greetings, acknowledgments, and filler. Focus on facts, preferences, and decisions.
Don't store sensitive data (passwords, tokens, PII) unless you have appropriate data handling policies in place. Use metadata to tag sensitivity levels if needed.
-
Similarity threshold — Only inject memories with
similarity> 0.6 into your prompt. Lower-similarity results add noise. -
Metadata strategy — Use metadata fields like
type,topic, andsession_idto organize memories and filter results client-side. -
Consolidation — Let automatic consolidation clean up. After hundreds of conversations, the memory space self-organizes.
-
Protected memories — Mark critical preferences as protected so they survive consolidation pruning:
# This preference will never be pruned during consolidation
resp = requests.post(
f"{API_BASE}/memory/text/store",
headers=HEADERS,
json={
"text": "User strongly prefers concise answers",
"metadata": {
"user_id": "user_123",
"protected": True
}
}
)
print(resp.json()["pattern_id"]) # pat_...Next steps
- Personal Assistant — Build an assistant that learns routines and habits
- Consolidation — How memories strengthen over time
- Causal Reasoning — Connect facts across conversations