Skip to main content

Human-like memory decay for AI agents

Project description

memory-decay-core

Python 3.10+ License: MIT FastAPI

Human-like memory decay for AI agents. A Python library that models how memories naturally fade, strengthen through recall, and compete for retrieval — giving agents realistic, bounded memory instead of perfect total recall.

Activation
  1.0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
      │ ■ high-impact fact (impact=0.9)
  0.8 ┤  ■■■■
      │      ■■■■■                                ← stability slows decay
  0.6 ┤  ●        ■■■■■■■
      │   ●●●           ■■■■■■■■■
  0.4 ┤      ●●●●              ■■■■■■■■■■■■■■■■■ ← floor: high-impact
      │          ●●●●●                              memories never fully
  0.2 ┤  ▴           ●●●●●●●                        vanish
      │   ▴▴▴▴            ●●●●●●●●●●●●●
  0.0 ┤       ▴▴▴▴▴▴▴▴▴▴▴▴▴▴▴                    ← low-impact episodes
      └─────────────────────────────────────────── Time (ticks)
        ■ high-impact fact    ● medium episode    ▴ low-impact episode

Key Ideas

Memory isn't a database. Humans don't store-and-retrieve — they encode, decay, interfere, and reconstruct. This library models that process with three measurable pillars:

Pillar What it measures Weight
Retrieval Can the system find the right memory? (recall + MRR) 40%
Forgetting Does it forget what it should? (non-target decay) 35%
Plausibility Does activation predict recallability? (correlation) 25%

Architecture

┌─────────────────────────────────────────────────────┐
│                    Your Agent                        │
│                                                      │
│  POST /store   POST /search   POST /auto-tick       │
└────────┬──────────┬──────────────┬──────────────────┘
         │          │              │
┌────────▼──────────▼──────────────▼──────────────────┐
│              FastAPI Server (server.py)               │
│  ┌──────────┐  ┌──────────┐  ┌────────────────────┐ │
│  │ Embedding │  │  Search  │  │ Retrieval          │ │
│  │ Provider  │  │  (vec +  │  │ Consolidation      │ │
│  │ (Gemini/  │  │  BM25    │  │ (testing effect)   │ │
│  │  OpenAI/  │  │  hybrid) │  │                    │ │
│  │  local)   │  │          │  │                    │ │
│  └────┬─────┘  └────┬─────┘  └────────┬───────────┘ │
│       │              │                 │             │
│  ┌────▼──────────────▼─────────────────▼───────────┐ │
│  │           MemoryStore (SQLite + sqlite-vec)      │ │
│  │  memories table │ vec_memories │ embedding_cache  │ │
│  └──────────────────────────────────────────────────┘ │
│                         │                             │
│  ┌──────────────────────▼───────────────────────────┐ │
│  │              DecayEngine                          │ │
│  │  exponential / power_law / custom soft-floor      │ │
│  │  stability-weighted rate scaling                  │ │
│  └───────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

Core Components

Module Class Role
graph.py MemoryGraph In-memory NetworkX graph for prototyping (from memory_decay.graph import MemoryGraph)
decay.py DecayEngine Time-step decay with exponential/power-law modes, stability modulation
memory_store.py MemoryStore SQLite + sqlite-vec persistence for production use
server.py FastAPI app HTTP API for store/search/tick/forget operations
embedding_provider.py EmbeddingProvider Pluggable embeddings: Gemini, OpenAI, local sentence-transformers

Installation

pip install -e .

# For development
pip install -e ".[dev]"

Dependencies

  • Python >= 3.10
  • NetworkX, NumPy
  • FastAPI + Uvicorn (server mode)
  • sqlite-vec (vector search persistence)
  • Optional: openai, google-genai (for API-based embeddings)
  • Optional: sentence-transformers (for local embeddings, install with pip install memory-decay[local])

Quick Start

As a Library

from memory_decay import MemoryStore, DecayEngine
from memory_decay.embedding_provider import create_embedding_provider

# 1. Create a memory store with Gemini embeddings
store = MemoryStore(
    db_path="./data/memories.db",
    embedding_provider=create_embedding_provider("gemini", api_key="your-api-key"),
)

# 2. Add memories
store.add_memory(
    memory_id="m1",
    mtype="fact",            # "fact" or "episode"
    content="Seoul is the capital of South Korea",
    impact=0.9,              # importance: 0.0-1.0
    created_tick=0,
    associations=[("m2", 0.7)],  # linked memories
)

# 3. Set up decay
engine = DecayEngine(store, decay_type="exponential")

# 4. Advance time — memories decay each tick
for _ in range(100):
    engine.tick()

# 5. Search with activation-weighted retrieval
results = store.search(
    query="What is the capital?",
    top_k=5,
    activation_weight=0.5,   # blend similarity with activation
    bm25_weight=0.3,         # hybrid semantic + lexical search
)

# 6. Reinforce recalled memories (testing effect)
store.re_activate("m1", boost_amount=0.1, source="direct", reinforce=True)

As an HTTP Server

# Start with local embeddings (no API key needed)
python -m memory_decay.server --port 8100

# Start with Gemini embeddings
python -m memory_decay.server \
    --port 8100 \
    --embedding-provider gemini \
    --embedding-api-key $GEMINI_API_KEY \
    --db-path ./data/memories.db

# Start with OpenAI embeddings
python -m memory_decay.server \
    --embedding-provider openai \
    --embedding-api-key $OPENAI_API_KEY \
    --embedding-model text-embedding-3-small

API Endpoints

Method Endpoint Description
POST /store Store a memory with text, importance, type, category, and associations
POST /store-batch Store multiple memories in one call
POST /search Semantic search with activation weighting + retrieval consolidation
POST /tick Advance decay by N ticks
POST /auto-tick Apply ticks based on elapsed real time
DELETE /forget/{id} Explicitly delete a memory
POST /reset Clear all memories
GET /health Health check
GET /stats Memory count and tick state

Example Requests

# Store a memory with category and calibrated importance
curl -X POST http://localhost:8100/store \
  -H "Content-Type: application/json" \
  -d '{"text": "User prefers dark mode", "importance": 0.9, "mtype": "fact", "category": "preference"}'

# Store a decision
curl -X POST http://localhost:8100/store \
  -H "Content-Type: application/json" \
  -d '{"text": "Chose SQLite over Postgres for single-node simplicity", "importance": 0.8, "mtype": "fact", "category": "decision"}'

# Store an episode (low importance — decays faster)
curl -X POST http://localhost:8100/store \
  -H "Content-Type: application/json" \
  -d '{"text": "Finished migrating auth middleware", "importance": 0.5, "mtype": "episode", "category": "episode"}'

# Search
curl -X POST http://localhost:8100/search \
  -H "Content-Type: application/json" \
  -d '{"query": "What theme does the user like?", "top_k": 5}'

# Advance time (apply decay)
curl -X POST http://localhost:8100/tick \
  -H "Content-Type: application/json" \
  -d '{"count": 10}'

Categories vs Types

Memories have two classification fields:

Field Values Purpose
mtype fact, episode Controls decay rate — facts decay slower (lambda_fact=0.02) than episodes (lambda_episode=0.035)
category fact, decision, preference, episode Semantic label for retrieval and filtering — returned in search results

If category is omitted, it defaults to the mtype value. The recommended mapping from plugins:

Category mtype Importance Use case
preference fact 0.8–1.0 User's role, style, habits, likes/dislikes
decision fact 0.8–0.9 Why X was chosen, tradeoffs, rejected alternatives
fact fact 0.7–0.9 Technical facts, API behaviors, architecture
episode episode 0.3–0.6 What was worked on, session context

Preferences and decisions use mtype: "fact" because they should decay slowly like facts, but carry a distinct category so agents can distinguish them in search results.

Core Concepts

Decay Functions

The engine supports two built-in decay modes plus custom functions:

Exponential decay (default):

A(t+1) = A(t) * exp(-λ_eff)
λ_eff  = λ / ((1 + α * impact) * (1 + ρ * stability))

Power law decay (longer tail):

A(t+1) = A(t) / (1 + β_eff)
β_eff  = β / ((1 + α * impact) * (1 + ρ * stability))

Soft-floor decay (custom, used in best config):

A(t+1) = floor(impact) + (A(t) - floor(impact)) * exp(-rate)

High-impact memories decay toward a non-zero floor rather than vanishing, controlled by a sigmoid gate for smooth consolidation transitions.

Stability & Consolidation

Memories have a stability score that modulates decay rate. Higher stability = slower decay.

  • Stability starts at 0 and increases when a memory is successfully recalled
  • Each tick, stability itself decays slowly (stability_decay=0.01), so reinforcement effects are long-lived but finite
  • Stability gain follows a saturation curve: gain * (1 - current/cap) — diminishing returns prevent runaway accumulation

Retrieval Consolidation (Testing Effect)

When a memory is successfully recalled during search, it gets strengthened — modeling the well-established testing effect from cognitive psychology:

  1. Memory is found in top-K search results
  2. Retrieval score gets boosted (immediate recall advantage)
  3. Storage score gets a fractional boost (long-term strengthening)
  4. Stability increases (slower future decay)

Multiple consolidation modes are available:

  • activation_and_stability — boost both scores + stability
  • retrieval_only — only boost retrieval score
  • stability_only_direct — only reinforce stability
  • retrieval_with_storage_fraction — retrieval gets full boost, storage gets 25%
  • retrieval_rank_scaled_fraction — boost scales inversely with rank position
  • retrieval_capped_fraction — boost capped at a ceiling value
  • retrieval_margin_bm25_fraction — requires both semantic margin and lexical agreement

Dual-Score Model

Each memory carries two activation scores:

Score Role Analogy
Storage score Can the memory be found at all? "Is it still in the filing cabinet?"
Retrieval score How easily can it be accessed? "Can I find it quickly?"

Search results are filtered by storage threshold, then ranked by retrieval score blended with similarity.

Hybrid Search

Retrieval combines three signals:

  1. Vector similarity — cosine similarity between query and memory embeddings
  2. Activation weightingsimilarity * retrieval_score^weight (faded memories rank lower)
  3. BM25 re-ranking — lexical matching for exact term overlap (optional, configurable weight)

Spreading activation through graph edges also boosts memories whose neighbors are active.

Configuration

Decay Parameters

Parameter Default Description
lambda_fact 0.02 Exponential decay rate for facts
lambda_episode 0.035 Exponential decay rate for episodes
alpha 0.5 Impact scaling factor
stability_weight 0.8 How much stability slows decay
stability_decay 0.01 Per-tick stability erosion
reinforcement_gain_direct 0.2 Stability boost on direct recall
reinforcement_gain_assoc 0.05 Stability boost on associated recall
stability_cap 1.0 Maximum stability value

Server Parameters

Flag Default Description
--host 127.0.0.1 Bind address
--port 8100 Port number
--db-path data/memories.db SQLite database path
--tick-interval 3600 Real seconds per tick
--embedding-provider local local, gemini, or openai
--embedding-model auto Model name (provider-specific)
--embedding-dim auto Embedding dimension (auto-detected)
--experiment-dir experiments/best Custom decay function directory

Custom Decay Functions

Place a decay_fn.py in your experiment directory with a compute_decay function:

# experiments/my_experiment/decay_fn.py
def compute_decay(activation, impact, stability, mtype, params):
    """Custom decay: must return float in [0, 1]."""
    # Your decay math here
    return new_activation

The server auto-loads from experiments/best/ on startup. Override with --experiment-dir.

Benchmarks

Evaluated on the full LongMemBench benchmark (500 questions) using GPT-4o as judge, testing the complete pipeline: memory storage → decay → retrieval → answer generation.

Metric Score
Accuracy 81%

OpenClaw Plugin Integration

memory-decay-core is designed to back the openclaw-memory-decay TypeScript plugin. The plugin connects to the server's HTTP API and provides AI agents with decaying, searchable memory.

Setup

  1. Start the memory-decay server:
python -m memory_decay.server --port 8100 --db-path ./data/agent_memories.db
  1. Configure the plugin to point at http://localhost:8100

  2. The plugin calls:

    • POST /store when the agent forms new memories
    • POST /search when the agent needs to recall (triggers retrieval consolidation automatically)
    • POST /auto-tick periodically to advance decay based on real elapsed time
    • DELETE /forget/{id} for explicit forgetting

Auto-Tick

The /auto-tick endpoint maps real time to simulation ticks:

ticks_due = floor(elapsed_seconds / tick_interval)

With the default tick_interval=3600, one tick equals one hour. This means:

  • Recent memories (< 1 hour) are at full activation
  • Day-old memories have decayed through ~24 ticks
  • Week-old memories have been through ~168 ticks

Adjust --tick-interval to control how aggressively memories fade.

Running Tests

pip install -e ".[dev]"
pytest tests/ -v

Project Structure

memory-decay-core/
├── src/memory_decay/
│   ├── __init__.py           # Public API: MemoryGraph, DecayEngine, MemoryStore
│   ├── graph.py              # Graph memory store + hybrid search
│   ├── decay.py              # Decay math (exponential, power law, soft-floor)
│   ├── bm25.py               # Shared BM25 tokenizer + scorer
│   ├── memory_store.py       # SQLite + sqlite-vec persistence
│   ├── server.py             # FastAPI HTTP server
│   └── embedding_provider.py # Pluggable embedding backends
├── tests/
├── data/                     # Default SQLite DB location
└── pyproject.toml

References

  • Ebbinghaus, H. (1885). Memory: A Contribution to Experimental Psychology
  • Roediger, H. L., & Butler, A. C. (2011). The critical role of retrieval practice in long-term retention
  • Wixted, J. T. (2004). On Common Ground: Jost's (1897) law of forgetting and Ribot's (1881) law of retrograde amnesia

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

memory_decay-0.1.0.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

memory_decay-0.1.0-py3-none-any.whl (30.7 kB view details)

Uploaded Python 3

File details

Details for the file memory_decay-0.1.0.tar.gz.

File metadata

  • Download URL: memory_decay-0.1.0.tar.gz
  • Upload date:
  • Size: 42.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for memory_decay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a735b0ae2fdcf7737241714c8f262cf1e109135d3402083980c624c0cafa56d6
MD5 35bae3f495fe39d3654994b325542078
BLAKE2b-256 85565765d902b61d75bcfdbb0af3caeeb9abff433af110913a03847546ab147c

See more details on using hashes here.

File details

Details for the file memory_decay-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: memory_decay-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for memory_decay-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d95f922eb3acc30483e5fc3d46f53e2caae940f3cf427544f39365b6d39b98d5
MD5 d898907217dbe5d2544bf5f6fcfbea54
BLAKE2b-256 29fb3db49fe6ef0213be65463172bd80eee0aa7886f67e66688f961536af5339

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page