Skip to content

Building a Chatbot

intermediate

Add 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

RequirementDetails
AccountFree tier or above
API keyFrom your dashboard
LibrariesPython 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

1

Set up your API key

Configure the X-API-Key header for all requests to the Engramma API.
2

Store conversation turns

After each user message and bot response, store them as memories with metadata.
3

Recall relevant context

Before generating a response, use the recall endpoint to find memories related to the current message using Active Inference and semantic re-ranking.
4

Inject context into prompts

Add recalled memories to your LLM prompt so the bot has conversational history.
5

Let consolidation improve quality

Over time, frequently-accessed memories strengthen and noise fades away automatically.

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 memory

How it works in practice

SessionWhat happens
First visitUser mentions preferences → stored as memories
Second visitBot recalls past preferences → adjusts behavior
After consolidationRepeated preferences strengthen, one-off mentions fade
Week laterBot remembers core preferences, forgets noise

Retrieve vs Recall

Engramma offers two endpoints for querying memories:

EndpointUse caseMechanism
/v1/memory/text/retrieveFast semantic searchEmbedding similarity only
/v1/memory/text/recallDeeper contextual recallActive 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"
]
Info

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

Tip

Store with intention. Not every message needs to be stored — filter out greetings, acknowledgments, and filler. Focus on facts, preferences, and decisions.

Warning

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.

  1. Similarity threshold — Only inject memories with similarity > 0.6 into your prompt. Lower-similarity results add noise.

  2. Metadata strategy — Use metadata fields like type, topic, and session_id to organize memories and filter results client-side.

  3. Consolidation — Let automatic consolidation clean up. After hundreds of conversations, the memory space self-organizes.

  4. 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