memory-decay 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 memory-decay contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,439 @@
1
+ Metadata-Version: 2.4
2
+ Name: memory-decay
3
+ Version: 0.1.0
4
+ Summary: Human-like memory decay for AI agents
5
+ Author: memory-decay contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/memory-decay/memory-decay-core
8
+ Project-URL: Repository, https://github.com/memory-decay/memory-decay-core
9
+ Keywords: memory,decay,ai,agent,forgetting,retrieval
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: networkx>=3.0
22
+ Requires-Dist: numpy>=1.24
23
+ Requires-Dist: openai>=1.0
24
+ Requires-Dist: google-genai>=1.0
25
+ Requires-Dist: fastapi>=0.115.0
26
+ Requires-Dist: uvicorn>=0.34.0
27
+ Requires-Dist: sqlite-vec>=0.1.7
28
+ Provides-Extra: local
29
+ Requires-Dist: sentence-transformers>=2.2; extra == "local"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: httpx>=0.28.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # memory-decay-core
36
+
37
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
38
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
39
+ [![FastAPI](https://img.shields.io/badge/server-FastAPI-009688.svg)](https://fastapi.tiangolo.com)
40
+
41
+ **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.
42
+
43
+ ```
44
+ Activation
45
+ 1.0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
46
+ │ ■ high-impact fact (impact=0.9)
47
+ 0.8 ┤ ■■■■
48
+ │ ■■■■■ ← stability slows decay
49
+ 0.6 ┤ ● ■■■■■■■
50
+ │ ●●● ■■■■■■■■■
51
+ 0.4 ┤ ●●●● ■■■■■■■■■■■■■■■■■ ← floor: high-impact
52
+ │ ●●●●● memories never fully
53
+ 0.2 ┤ ▴ ●●●●●●● vanish
54
+ │ ▴▴▴▴ ●●●●●●●●●●●●●
55
+ 0.0 ┤ ▴▴▴▴▴▴▴▴▴▴▴▴▴▴▴ ← low-impact episodes
56
+ └─────────────────────────────────────────── Time (ticks)
57
+ ■ high-impact fact ● medium episode ▴ low-impact episode
58
+ ```
59
+
60
+ ## Key Ideas
61
+
62
+ **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:
63
+
64
+ | Pillar | What it measures | Weight |
65
+ |--------|-----------------|--------|
66
+ | **Retrieval** | Can the system find the right memory? (recall + MRR) | 40% |
67
+ | **Forgetting** | Does it forget what it should? (non-target decay) | 35% |
68
+ | **Plausibility** | Does activation predict recallability? (correlation) | 25% |
69
+
70
+ ## Architecture
71
+
72
+ ```
73
+ ┌─────────────────────────────────────────────────────┐
74
+ │ Your Agent │
75
+ │ │
76
+ │ POST /store POST /search POST /auto-tick │
77
+ └────────┬──────────┬──────────────┬──────────────────┘
78
+ │ │ │
79
+ ┌────────▼──────────▼──────────────▼──────────────────┐
80
+ │ FastAPI Server (server.py) │
81
+ │ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
82
+ │ │ Embedding │ │ Search │ │ Retrieval │ │
83
+ │ │ Provider │ │ (vec + │ │ Consolidation │ │
84
+ │ │ (Gemini/ │ │ BM25 │ │ (testing effect) │ │
85
+ │ │ OpenAI/ │ │ hybrid) │ │ │ │
86
+ │ │ local) │ │ │ │ │ │
87
+ │ └────┬─────┘ └────┬─────┘ └────────┬───────────┘ │
88
+ │ │ │ │ │
89
+ │ ┌────▼──────────────▼─────────────────▼───────────┐ │
90
+ │ │ MemoryStore (SQLite + sqlite-vec) │ │
91
+ │ │ memories table │ vec_memories │ embedding_cache │ │
92
+ │ └──────────────────────────────────────────────────┘ │
93
+ │ │ │
94
+ │ ┌──────────────────────▼───────────────────────────┐ │
95
+ │ │ DecayEngine │ │
96
+ │ │ exponential / power_law / custom soft-floor │ │
97
+ │ │ stability-weighted rate scaling │ │
98
+ │ └───────────────────────────────────────────────────┘ │
99
+ └──────────────────────────────────────────────────────┘
100
+ ```
101
+
102
+ ### Core Components
103
+
104
+ | Module | Class | Role |
105
+ |--------|-------|------|
106
+ | `graph.py` | `MemoryGraph` | In-memory NetworkX graph for prototyping (`from memory_decay.graph import MemoryGraph`) |
107
+ | `decay.py` | `DecayEngine` | Time-step decay with exponential/power-law modes, stability modulation |
108
+ | `memory_store.py` | `MemoryStore` | SQLite + sqlite-vec persistence for production use |
109
+ | `server.py` | FastAPI app | HTTP API for store/search/tick/forget operations |
110
+ | `embedding_provider.py` | `EmbeddingProvider` | Pluggable embeddings: Gemini, OpenAI, local sentence-transformers |
111
+
112
+ ## Installation
113
+
114
+ ```bash
115
+ pip install -e .
116
+
117
+ # For development
118
+ pip install -e ".[dev]"
119
+ ```
120
+
121
+ ### Dependencies
122
+
123
+ - Python >= 3.10
124
+ - NetworkX, NumPy
125
+ - FastAPI + Uvicorn (server mode)
126
+ - sqlite-vec (vector search persistence)
127
+ - Optional: `openai`, `google-genai` (for API-based embeddings)
128
+ - Optional: `sentence-transformers` (for local embeddings, install with `pip install memory-decay[local]`)
129
+
130
+ ## Quick Start
131
+
132
+ ### As a Library
133
+
134
+ ```python
135
+ from memory_decay import MemoryStore, DecayEngine
136
+ from memory_decay.embedding_provider import create_embedding_provider
137
+
138
+ # 1. Create a memory store with Gemini embeddings
139
+ store = MemoryStore(
140
+ db_path="./data/memories.db",
141
+ embedding_provider=create_embedding_provider("gemini", api_key="your-api-key"),
142
+ )
143
+
144
+ # 2. Add memories
145
+ store.add_memory(
146
+ memory_id="m1",
147
+ mtype="fact", # "fact" or "episode"
148
+ content="Seoul is the capital of South Korea",
149
+ impact=0.9, # importance: 0.0-1.0
150
+ created_tick=0,
151
+ associations=[("m2", 0.7)], # linked memories
152
+ )
153
+
154
+ # 3. Set up decay
155
+ engine = DecayEngine(store, decay_type="exponential")
156
+
157
+ # 4. Advance time — memories decay each tick
158
+ for _ in range(100):
159
+ engine.tick()
160
+
161
+ # 5. Search with activation-weighted retrieval
162
+ results = store.search(
163
+ query="What is the capital?",
164
+ top_k=5,
165
+ activation_weight=0.5, # blend similarity with activation
166
+ bm25_weight=0.3, # hybrid semantic + lexical search
167
+ )
168
+
169
+ # 6. Reinforce recalled memories (testing effect)
170
+ store.re_activate("m1", boost_amount=0.1, source="direct", reinforce=True)
171
+ ```
172
+
173
+ ### As an HTTP Server
174
+
175
+ ```bash
176
+ # Start with local embeddings (no API key needed)
177
+ python -m memory_decay.server --port 8100
178
+
179
+ # Start with Gemini embeddings
180
+ python -m memory_decay.server \
181
+ --port 8100 \
182
+ --embedding-provider gemini \
183
+ --embedding-api-key $GEMINI_API_KEY \
184
+ --db-path ./data/memories.db
185
+
186
+ # Start with OpenAI embeddings
187
+ python -m memory_decay.server \
188
+ --embedding-provider openai \
189
+ --embedding-api-key $OPENAI_API_KEY \
190
+ --embedding-model text-embedding-3-small
191
+ ```
192
+
193
+ #### API Endpoints
194
+
195
+ | Method | Endpoint | Description |
196
+ |--------|----------|-------------|
197
+ | `POST` | `/store` | Store a memory with text, importance, type, category, and associations |
198
+ | `POST` | `/store-batch` | Store multiple memories in one call |
199
+ | `POST` | `/search` | Semantic search with activation weighting + retrieval consolidation |
200
+ | `POST` | `/tick` | Advance decay by N ticks |
201
+ | `POST` | `/auto-tick` | Apply ticks based on elapsed real time |
202
+ | `DELETE` | `/forget/{id}` | Explicitly delete a memory |
203
+ | `POST` | `/reset` | Clear all memories |
204
+ | `GET` | `/health` | Health check |
205
+ | `GET` | `/stats` | Memory count and tick state |
206
+
207
+ #### Example Requests
208
+
209
+ ```bash
210
+ # Store a memory with category and calibrated importance
211
+ curl -X POST http://localhost:8100/store \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"text": "User prefers dark mode", "importance": 0.9, "mtype": "fact", "category": "preference"}'
214
+
215
+ # Store a decision
216
+ curl -X POST http://localhost:8100/store \
217
+ -H "Content-Type: application/json" \
218
+ -d '{"text": "Chose SQLite over Postgres for single-node simplicity", "importance": 0.8, "mtype": "fact", "category": "decision"}'
219
+
220
+ # Store an episode (low importance — decays faster)
221
+ curl -X POST http://localhost:8100/store \
222
+ -H "Content-Type: application/json" \
223
+ -d '{"text": "Finished migrating auth middleware", "importance": 0.5, "mtype": "episode", "category": "episode"}'
224
+
225
+ # Search
226
+ curl -X POST http://localhost:8100/search \
227
+ -H "Content-Type: application/json" \
228
+ -d '{"query": "What theme does the user like?", "top_k": 5}'
229
+
230
+ # Advance time (apply decay)
231
+ curl -X POST http://localhost:8100/tick \
232
+ -H "Content-Type: application/json" \
233
+ -d '{"count": 10}'
234
+ ```
235
+
236
+ ## Categories vs Types
237
+
238
+ Memories have two classification fields:
239
+
240
+ | Field | Values | Purpose |
241
+ |-------|--------|---------|
242
+ | `mtype` | `fact`, `episode` | Controls **decay rate** — facts decay slower (`lambda_fact=0.02`) than episodes (`lambda_episode=0.035`) |
243
+ | `category` | `fact`, `decision`, `preference`, `episode` | **Semantic label** for retrieval and filtering — returned in search results |
244
+
245
+ If `category` is omitted, it defaults to the `mtype` value. The recommended mapping from plugins:
246
+
247
+ | Category | `mtype` | Importance | Use case |
248
+ |----------|---------|------------|----------|
249
+ | `preference` | `fact` | 0.8–1.0 | User's role, style, habits, likes/dislikes |
250
+ | `decision` | `fact` | 0.8–0.9 | Why X was chosen, tradeoffs, rejected alternatives |
251
+ | `fact` | `fact` | 0.7–0.9 | Technical facts, API behaviors, architecture |
252
+ | `episode` | `episode` | 0.3–0.6 | What was worked on, session context |
253
+
254
+ 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.
255
+
256
+ ## Core Concepts
257
+
258
+ ### Decay Functions
259
+
260
+ The engine supports two built-in decay modes plus custom functions:
261
+
262
+ **Exponential decay** (default):
263
+ ```
264
+ A(t+1) = A(t) * exp(-λ_eff)
265
+ λ_eff = λ / ((1 + α * impact) * (1 + ρ * stability))
266
+ ```
267
+
268
+ **Power law decay** (longer tail):
269
+ ```
270
+ A(t+1) = A(t) / (1 + β_eff)
271
+ β_eff = β / ((1 + α * impact) * (1 + ρ * stability))
272
+ ```
273
+
274
+ **Soft-floor decay** (custom, used in best config):
275
+ ```
276
+ A(t+1) = floor(impact) + (A(t) - floor(impact)) * exp(-rate)
277
+ ```
278
+ High-impact memories decay toward a non-zero floor rather than vanishing, controlled by a sigmoid gate for smooth consolidation transitions.
279
+
280
+ ### Stability & Consolidation
281
+
282
+ Memories have a **stability score** that modulates decay rate. Higher stability = slower decay.
283
+
284
+ - Stability starts at 0 and increases when a memory is successfully recalled
285
+ - Each tick, stability itself decays slowly (`stability_decay=0.01`), so reinforcement effects are long-lived but finite
286
+ - Stability gain follows a saturation curve: `gain * (1 - current/cap)` — diminishing returns prevent runaway accumulation
287
+
288
+ ### Retrieval Consolidation (Testing Effect)
289
+
290
+ When a memory is successfully recalled during search, it gets strengthened — modeling the well-established [testing effect](https://en.wikipedia.org/wiki/Testing_effect) from cognitive psychology:
291
+
292
+ 1. Memory is found in top-K search results
293
+ 2. Retrieval score gets boosted (immediate recall advantage)
294
+ 3. Storage score gets a fractional boost (long-term strengthening)
295
+ 4. Stability increases (slower future decay)
296
+
297
+ Multiple consolidation modes are available:
298
+ - `activation_and_stability` — boost both scores + stability
299
+ - `retrieval_only` — only boost retrieval score
300
+ - `stability_only_direct` — only reinforce stability
301
+ - `retrieval_with_storage_fraction` — retrieval gets full boost, storage gets 25%
302
+ - `retrieval_rank_scaled_fraction` — boost scales inversely with rank position
303
+ - `retrieval_capped_fraction` — boost capped at a ceiling value
304
+ - `retrieval_margin_bm25_fraction` — requires both semantic margin and lexical agreement
305
+
306
+ ### Dual-Score Model
307
+
308
+ Each memory carries two activation scores:
309
+
310
+ | Score | Role | Analogy |
311
+ |-------|------|---------|
312
+ | **Storage score** | Can the memory be found at all? | "Is it still in the filing cabinet?" |
313
+ | **Retrieval score** | How easily can it be accessed? | "Can I find it quickly?" |
314
+
315
+ Search results are filtered by storage threshold, then ranked by retrieval score blended with similarity.
316
+
317
+ ### Hybrid Search
318
+
319
+ Retrieval combines three signals:
320
+
321
+ 1. **Vector similarity** — cosine similarity between query and memory embeddings
322
+ 2. **Activation weighting** — `similarity * retrieval_score^weight` (faded memories rank lower)
323
+ 3. **BM25 re-ranking** — lexical matching for exact term overlap (optional, configurable weight)
324
+
325
+ Spreading activation through graph edges also boosts memories whose neighbors are active.
326
+
327
+ ## Configuration
328
+
329
+ ### Decay Parameters
330
+
331
+ | Parameter | Default | Description |
332
+ |-----------|---------|-------------|
333
+ | `lambda_fact` | 0.02 | Exponential decay rate for facts |
334
+ | `lambda_episode` | 0.035 | Exponential decay rate for episodes |
335
+ | `alpha` | 0.5 | Impact scaling factor |
336
+ | `stability_weight` | 0.8 | How much stability slows decay |
337
+ | `stability_decay` | 0.01 | Per-tick stability erosion |
338
+ | `reinforcement_gain_direct` | 0.2 | Stability boost on direct recall |
339
+ | `reinforcement_gain_assoc` | 0.05 | Stability boost on associated recall |
340
+ | `stability_cap` | 1.0 | Maximum stability value |
341
+
342
+ ### Server Parameters
343
+
344
+ | Flag | Default | Description |
345
+ |------|---------|-------------|
346
+ | `--host` | `127.0.0.1` | Bind address |
347
+ | `--port` | `8100` | Port number |
348
+ | `--db-path` | `data/memories.db` | SQLite database path |
349
+ | `--tick-interval` | `3600` | Real seconds per tick |
350
+ | `--embedding-provider` | `local` | `local`, `gemini`, or `openai` |
351
+ | `--embedding-model` | auto | Model name (provider-specific) |
352
+ | `--embedding-dim` | auto | Embedding dimension (auto-detected) |
353
+ | `--experiment-dir` | `experiments/best` | Custom decay function directory |
354
+
355
+ ### Custom Decay Functions
356
+
357
+ Place a `decay_fn.py` in your experiment directory with a `compute_decay` function:
358
+
359
+ ```python
360
+ # experiments/my_experiment/decay_fn.py
361
+ def compute_decay(activation, impact, stability, mtype, params):
362
+ """Custom decay: must return float in [0, 1]."""
363
+ # Your decay math here
364
+ return new_activation
365
+ ```
366
+
367
+ The server auto-loads from `experiments/best/` on startup. Override with `--experiment-dir`.
368
+
369
+ ## Benchmarks
370
+
371
+ Evaluated on the full [LongMemBench](https://github.com/jasonphd/LongMemBench) benchmark (500 questions) using GPT-4o as judge, testing the complete pipeline: memory storage → decay → retrieval → answer generation.
372
+
373
+ | Metric | Score |
374
+ |--------|-------|
375
+ | **Accuracy** | **81%** |
376
+
377
+ ## OpenClaw Plugin Integration
378
+
379
+ 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.
380
+
381
+ ### Setup
382
+
383
+ 1. Start the memory-decay server:
384
+ ```bash
385
+ python -m memory_decay.server --port 8100 --db-path ./data/agent_memories.db
386
+ ```
387
+
388
+ 2. Configure the plugin to point at `http://localhost:8100`
389
+
390
+ 3. The plugin calls:
391
+ - `POST /store` when the agent forms new memories
392
+ - `POST /search` when the agent needs to recall (triggers retrieval consolidation automatically)
393
+ - `POST /auto-tick` periodically to advance decay based on real elapsed time
394
+ - `DELETE /forget/{id}` for explicit forgetting
395
+
396
+ ### Auto-Tick
397
+
398
+ The `/auto-tick` endpoint maps real time to simulation ticks:
399
+
400
+ ```
401
+ ticks_due = floor(elapsed_seconds / tick_interval)
402
+ ```
403
+
404
+ With the default `tick_interval=3600`, one tick equals one hour. This means:
405
+ - Recent memories (< 1 hour) are at full activation
406
+ - Day-old memories have decayed through ~24 ticks
407
+ - Week-old memories have been through ~168 ticks
408
+
409
+ Adjust `--tick-interval` to control how aggressively memories fade.
410
+
411
+ ## Running Tests
412
+
413
+ ```bash
414
+ pip install -e ".[dev]"
415
+ pytest tests/ -v
416
+ ```
417
+
418
+ ## Project Structure
419
+
420
+ ```
421
+ memory-decay-core/
422
+ ├── src/memory_decay/
423
+ │ ├── __init__.py # Public API: MemoryGraph, DecayEngine, MemoryStore
424
+ │ ├── graph.py # Graph memory store + hybrid search
425
+ │ ├── decay.py # Decay math (exponential, power law, soft-floor)
426
+ │ ├── bm25.py # Shared BM25 tokenizer + scorer
427
+ │ ├── memory_store.py # SQLite + sqlite-vec persistence
428
+ │ ├── server.py # FastAPI HTTP server
429
+ │ └── embedding_provider.py # Pluggable embedding backends
430
+ ├── tests/
431
+ ├── data/ # Default SQLite DB location
432
+ └── pyproject.toml
433
+ ```
434
+
435
+ ## References
436
+
437
+ - Ebbinghaus, H. (1885). *Memory: A Contribution to Experimental Psychology*
438
+ - Roediger, H. L., & Butler, A. C. (2011). The critical role of retrieval practice in long-term retention
439
+ - Wixted, J. T. (2004). On Common Ground: Jost's (1897) law of forgetting and Ribot's (1881) law of retrograde amnesia