Back to Theory
Theory9 min read · July 6, 2026

AI Memory for LLM Agents: How to Give Your Agent Persistent Memory

LLM agents need four types of memory — episodic, semantic, procedural, and working — to reason across sessions. This guide explains each type and shows how to implement all four with Feather DB in Python.

F
Feather DB
Engineering

LLM agents have four types of memory: episodic (specific past events), semantic (general knowledge and facts), procedural (how to do things), and working (current context in the active window). Persistent memory for agents means storing episodic and semantic memories between sessions so the agent does not start from zero each time. Feather DB implements all four layers in a single embedded library — pip install feather-db — with sub-millisecond retrieval and adaptive decay so memories age naturally rather than accumulating without limit.

The Four Types of Agent Memory Explained

Before implementing memory, it helps to understand what you are actually storing. The cognitive science literature distinguishes four memory types, and each maps to a specific engineering decision in your agent stack.

1. Working Memory

Working memory is what the agent holds right now: the current conversation turn, the active task, immediate context. In LLM terms, this is the context window. It is bounded by the model's token limit (typically 8K–200K tokens depending on the model), and it does not persist between sessions. Working memory requires no external system — it lives in the prompt.

The bottleneck: working memory is expensive. Filling a 128K context window with GPT-4o costs roughly $0.26 per session. Across 1,000 sessions, that is $260. Context engines like Feather DB replace large working memories with targeted retrieval — fetch the 10 most relevant memories, not all 10,000.

2. Episodic Memory

Episodic memory stores specific events: "The user mentioned their dog's name was Archie on June 3rd," "The user complained about the payment flow in session #47." These are time-stamped, specific, and tied to particular interactions.

In agent architecture, episodic memory is what most people mean when they say "memory" — the agent remembering what happened before. Feather DB stores episodic memories as nodes with timestamps, importance scores, and session metadata:

import feather_db as fdb

db = fdb.FeatherDB("agent_memory.feather")

# Store an episodic memory
db.add(
    text="User mentioned their dog's name is Archie during onboarding",
    metadata={
        "memory_type": "episodic",
        "session_id": "session_047",
        "timestamp": "2026-06-03T14:23:00Z",
        "entity": "user_123"
    },
    importance=0.7
)

3. Semantic Memory

Semantic memory is general knowledge and facts about the world, the user, or the domain. It is not tied to a specific event: "The user prefers concise responses," "The user's tech stack is Python + FastAPI," "The user's company has a no-meeting Fridays policy."

Semantic memories are more durable than episodic ones. A user preference expressed once and reinforced across ten sessions should survive longer than a one-off request. Feather DB's stickiness model handles this automatically:

# Store a semantic memory with high importance
db.add(
    text="User prefers concise, direct responses without preamble",
    metadata={
        "memory_type": "semantic",
        "entity": "user_123",
        "confidence": 0.9
    },
    importance=0.9  # High importance = decays slower
)

4. Procedural Memory

Procedural memory stores how-to knowledge: "To deploy this user's app, use make deploy-prod," "This user's API key rotation procedure requires filing a ticket first." These are stable facts about processes.

Procedural memories should have high importance scores and long half-lives — the deployment process for a production app does not change often, and forgetting it would be catastrophic:

# Store procedural memory with maximum importance
db.add(
    text="Deployment procedure: run make deploy-prod, then verify at status.company.com",
    metadata={
        "memory_type": "procedural",
        "entity": "user_123",
        "half_life_days": 365  # Very slow decay
    },
    importance=1.0
)

How Feather DB's Adaptive Decay Handles Memory Types

Different memory types need different decay rates. Working memory disappears immediately. Episodic memories should fade unless reinforced. Semantic memories fade slowly. Procedural memories are nearly permanent.

Feather DB's scoring formula implements this through the importance parameter and half_life_days:

stickiness    = 1 + log(1 + recall_count)
effective_age = age_in_days / stickiness
recency       = 0.5 ^ (effective_age / half_life_days)
final_score   = ((1 - time_weight) * similarity + time_weight * recency) * importance

A semantic memory with importance=0.9 and half_life_days=90 will still score higher than an episodic memory with importance=0.3 and half_life_days=7 after 30 days, even if the episodic memory is slightly more semantically similar to the query. This is the correct behavior — durable, high-importance facts should beat recent but low-importance ones.

Complete Implementation: A Stateful Agent with All Four Memory Types

import feather_db as fdb
from datetime import datetime

class StatefulAgent:
    def __init__(self, user_id: str):
        self.user_id = user_id
        self.db = fdb.FeatherDB(f"memory_{user_id}.feather")
        self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"

    def remember(self, text: str, memory_type: str, importance: float = 0.5):
        """Store a memory with appropriate decay settings."""
        half_life_map = {
            "episodic": 30,
            "semantic": 180,
            "procedural": 365
        }
        self.db.add(
            text=text,
            metadata={
                "memory_type": memory_type,
                "session_id": self.session_id,
                "entity": self.user_id,
                "timestamp": datetime.now().isoformat()
            },
            importance=importance,
            half_life_days=half_life_map.get(memory_type, 60)
        )

    def recall(self, query: str, top_k: int = 10) -> list:
        """Retrieve the most relevant memories for a query."""
        return self.db.search(
            query=query,
            top_k=top_k,
            filters={"entity": self.user_id}
        )

    def recall_with_context(self, query: str, hops: int = 2) -> list:
        """Retrieve memories plus related context via graph traversal."""
        return self.db.context_chain(
            query=query,
            hops=hops,
            filters={"entity": self.user_id}
        )

    def build_system_prompt(self, current_query: str) -> str:
        """Build a context-rich system prompt from retrieved memories."""
        memories = self.recall(current_query, top_k=8)
        memory_text = "\n".join([f"- {m['text']}" for m in memories])
        return f"""You are a helpful assistant with memory of past interactions.

Relevant memories:
{memory_text}

Respond to the user's message using this context where relevant."""

Connecting Memory to an LLM

The agent's memory feeds into the system prompt before each turn. Here is how to wire it to an OpenAI-compatible API:

from openai import OpenAI

client = OpenAI()
agent = StatefulAgent(user_id="user_123")

def chat(user_message: str) -> str:
    # Build context-aware system prompt
    system_prompt = agent.build_system_prompt(user_message)

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_message}
        ]
    )

    assistant_reply = response.choices[0].message.content

    # Store this interaction as episodic memory
    agent.remember(
        text=f"User asked: {user_message[:200]}",
        memory_type="episodic",
        importance=0.5
    )

    return assistant_reply

Memory Retrieval vs. Full Context: The Cost Comparison

The naive approach to agent memory is to stuff all previous interactions into the context window. At scale, this fails on cost. With Feather DB's retrieval approach, you inject only the top 8–10 relevant memories (~500 tokens) instead of the full history (potentially 50,000+ tokens). The per-session cost difference at GPT-4o pricing ($5/1M input tokens):

Approach Tokens per session Cost per 1,000 sessions
Full context stuffing ~57,000 $288
Feather DB retrieval (top 10) ~1,500 $7.50

The retrieval approach is not just cheaper — it scores higher on LongMemEval (0.693 vs 0.640) because selective retrieval surfaces the most relevant memories rather than overwhelming the model with undifferentiated history.

Common Memory Implementation Mistakes

Storing raw conversation turns instead of extracted facts

Storing "User: my dog is named Archie" is less useful than storing "User's dog is named Archie." The extracted fact retrieves better and takes less token space when injected into future prompts. Use a lightweight LLM call to extract facts before storing.

Using the same importance score for everything

Not all memories are equally important. A user's name deserves importance=0.95. A passing comment about the weather deserves importance=0.2. Importance affects both retrieval ranking and decay rate — high-importance memories live longer.

Ignoring namespace isolation in multi-user systems

Always scope memories to a user or entity ID. Feather DB's namespace system prevents user A's memories from appearing in user B's context.

FAQ

What is the difference between episodic and semantic memory in AI agents?

Episodic memory stores specific past events tied to a time and place ("the user mentioned X in session 5"). Semantic memory stores general facts and preferences that are not tied to a specific event ("the user prefers concise answers").

How many memories should I inject into the context window per turn?

8–12 memories is the practical ceiling for most use cases. Beyond that, the injected memories start competing with each other and the quality of context degrades. Feather DB's ranking ensures the top 10 results are the most temporally and semantically relevant.

Does Feather DB handle memory deduplication?

Not automatically in the current version. The recommended pattern is to check for highly similar existing memories (cosine similarity > 0.95) before adding a new one, and update the importance score of the existing memory instead of creating a duplicate.

How do I implement memory for a multi-turn conversation within a single session?

Within a session, keep the recent turns in the context window directly (working memory). Store memories to Feather DB at the end of the session or at logical checkpoints — not after every single turn — to avoid over-storing low-value conversational fragments.

Can Feather DB run on mobile or edge devices?

Yes. Feather DB is an embedded binary with no server dependency. It runs anywhere Python runs, including resource-constrained environments. The .feather file is portable — the same memory store can move between development and production environments.