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.
- memory_decay-0.1.0/LICENSE +21 -0
- memory_decay-0.1.0/PKG-INFO +439 -0
- memory_decay-0.1.0/README.md +405 -0
- memory_decay-0.1.0/pyproject.toml +51 -0
- memory_decay-0.1.0/setup.cfg +4 -0
- memory_decay-0.1.0/src/memory_decay/__init__.py +16 -0
- memory_decay-0.1.0/src/memory_decay/bm25.py +78 -0
- memory_decay-0.1.0/src/memory_decay/decay.py +333 -0
- memory_decay-0.1.0/src/memory_decay/embedding_provider.py +197 -0
- memory_decay-0.1.0/src/memory_decay/graph.py +603 -0
- memory_decay-0.1.0/src/memory_decay/memory_store.py +502 -0
- memory_decay-0.1.0/src/memory_decay/server.py +549 -0
- memory_decay-0.1.0/src/memory_decay.egg-info/PKG-INFO +439 -0
- memory_decay-0.1.0/src/memory_decay.egg-info/SOURCES.txt +22 -0
- memory_decay-0.1.0/src/memory_decay.egg-info/dependency_links.txt +1 -0
- memory_decay-0.1.0/src/memory_decay.egg-info/requires.txt +14 -0
- memory_decay-0.1.0/src/memory_decay.egg-info/top_level.txt +1 -0
- memory_decay-0.1.0/tests/test_bm25.py +82 -0
- memory_decay-0.1.0/tests/test_core.py +469 -0
- memory_decay-0.1.0/tests/test_decay_sqlite.py +53 -0
- memory_decay-0.1.0/tests/test_embedding_provider.py +96 -0
- memory_decay-0.1.0/tests/test_graph_temporal.py +46 -0
- memory_decay-0.1.0/tests/test_memory_store.py +312 -0
- memory_decay-0.1.0/tests/test_server.py +196 -0
|
@@ -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
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
[](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
|