memorybrain 1.0.0__py3-none-any.whl

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,20 @@
1
+ """
2
+ memorybrain — long-term memory for AI assistants and agents.
3
+
4
+ Example::
5
+
6
+ from memorybrain import MemoryBrain
7
+
8
+ brain = MemoryBrain()
9
+ brain.remember(
10
+ "User likes Flutter development",
11
+ tags=["flutter", "programming"],
12
+ importance=10,
13
+ )
14
+ results = brain.recall("What framework does the user like?")
15
+ """
16
+
17
+ from memorybrain.brain import MemoryBrain
18
+
19
+ __version__ = "1.0.0"
20
+ __all__ = ["MemoryBrain", "__version__"]
memorybrain/brain.py ADDED
@@ -0,0 +1,166 @@
1
+ """MemoryBrain — main public API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import threading
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from memorybrain.ranking import RankWeights, rank_memories
11
+ from memorybrain.search import compute_relevance
12
+ from memorybrain.storage import Memory, SQLiteStorage, utc_now
13
+
14
+
15
+ class MemoryBrain:
16
+ """
17
+ Long-term memory for AI assistants and agents.
18
+
19
+ Stores memories in a local SQLite database with ranked recall and search.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ db_path: str | Path | None = None,
25
+ rank_weights: RankWeights | None = None,
26
+ ) -> None:
27
+ self._storage = SQLiteStorage(db_path)
28
+ self._rank_weights = rank_weights
29
+ self._lock = threading.RLock()
30
+
31
+ def remember(
32
+ self,
33
+ text: str,
34
+ *,
35
+ tags: list[str] | None = None,
36
+ importance: float = 5.0,
37
+ metadata: dict[str, Any] | None = None,
38
+ ) -> Memory:
39
+ """
40
+ Store a new memory.
41
+
42
+ Args:
43
+ text: Memory content.
44
+ tags: Optional list of tags.
45
+ importance: Priority from 0 to 10 (default 5).
46
+ metadata: Optional extra key-value data.
47
+
48
+ Returns:
49
+ The stored Memory instance.
50
+ """
51
+ with self._lock:
52
+ memory = Memory(
53
+ text=text,
54
+ tags=tags or [],
55
+ importance=importance,
56
+ metadata=metadata or {},
57
+ )
58
+ return self._storage.add(memory)
59
+
60
+ def recall(self, query: str, limit: int = 10) -> list[Memory]:
61
+ """
62
+ Retrieve memories ranked for a natural-language query.
63
+
64
+ Uses relevance, importance, and recency scoring.
65
+ """
66
+ return self._retrieve(query, limit=limit, min_relevance=0.0)
67
+
68
+ def search(self, query: str, limit: int = 10) -> list[Memory]:
69
+ """
70
+ Search memories by keyword relevance.
71
+
72
+ Slightly stricter pre-filter than recall (any token match required when
73
+ query has tokens).
74
+ """
75
+ tokens = query.strip()
76
+ min_rel = 0.01 if tokens else 0.0
77
+ return self._retrieve(query, limit=limit, min_relevance=min_rel)
78
+
79
+ def _retrieve(self, query: str, limit: int, min_relevance: float) -> list[Memory]:
80
+ with self._lock:
81
+ candidates = self._storage.list_all()
82
+ if not candidates:
83
+ return []
84
+ ranked = rank_memories(
85
+ query,
86
+ candidates,
87
+ weights=self._rank_weights,
88
+ min_relevance=min_relevance,
89
+ )
90
+ if not ranked and query.strip():
91
+ ranked = rank_memories(
92
+ query,
93
+ candidates,
94
+ weights=self._rank_weights,
95
+ min_relevance=0.0,
96
+ )
97
+ top = ranked[:limit]
98
+ now = utc_now()
99
+ for memory, _ in top:
100
+ memory.access_count += 1
101
+ memory.metadata["last_accessed"] = now.isoformat()
102
+ self._storage.update(memory)
103
+ return [m for m, _ in top]
104
+
105
+ def forget(self, memory_id: str) -> bool:
106
+ """Delete a memory by ID."""
107
+ with self._lock:
108
+ return self._storage.delete(memory_id)
109
+
110
+ def get(self, memory_id: str) -> Memory | None:
111
+ """Fetch a single memory by ID."""
112
+ with self._lock:
113
+ return self._storage.get(memory_id)
114
+
115
+ def export(self, path: str | Path) -> int:
116
+ """
117
+ Export all memories to a JSON file.
118
+
119
+ Returns:
120
+ Number of memories exported.
121
+ """
122
+ with self._lock:
123
+ records = self._storage.export_records()
124
+ path = Path(path)
125
+ path.parent.mkdir(parents=True, exist_ok=True)
126
+ path.write_text(json.dumps(records, indent=2), encoding="utf-8")
127
+ return len(records)
128
+
129
+ def import_file(self, path: str | Path) -> int:
130
+ """
131
+ Import memories from a JSON file (upsert by ID).
132
+
133
+ Returns:
134
+ Number of memories imported.
135
+ """
136
+ with self._lock:
137
+ path = Path(path)
138
+ records = json.loads(path.read_text(encoding="utf-8"))
139
+ if not isinstance(records, list):
140
+ raise ValueError("Import file must contain a JSON array of memories")
141
+ return self._storage.import_records(records)
142
+
143
+ def stats(self) -> dict[str, Any]:
144
+ """
145
+ Return memory statistics.
146
+
147
+ Returns:
148
+ dict with total_memories, most_common_tags, database_size_bytes
149
+ """
150
+ with self._lock:
151
+ total = self._storage.count()
152
+ tag_counts = self._storage.tag_counts()
153
+ sorted_tags = sorted(tag_counts.items(), key=lambda x: (-x[1], x[0]))
154
+ most_common = [
155
+ {"tag": tag, "count": count} for tag, count in sorted_tags[:10]
156
+ ]
157
+ return {
158
+ "total_memories": total,
159
+ "most_common_tags": most_common,
160
+ "database_size_bytes": self._storage.db_file_size_bytes(),
161
+ }
162
+
163
+ @property
164
+ def db_path(self) -> Path:
165
+ """Path to the SQLite database file."""
166
+ return self._storage.db_path
memorybrain/ranking.py ADDED
@@ -0,0 +1,88 @@
1
+ """Memory ranking by relevance, importance, and recency."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone
8
+
9
+ from memorybrain.search import compute_relevance
10
+ from memorybrain.storage import Memory, utc_now
11
+
12
+
13
+ @dataclass
14
+ class RankWeights:
15
+ """Weights for composite memory ranking."""
16
+
17
+ relevance: float = 0.5
18
+ importance: float = 0.3
19
+ recency: float = 0.2
20
+
21
+ def normalized(self) -> RankWeights:
22
+ total = self.relevance + self.importance + self.recency
23
+ if total <= 0:
24
+ return RankWeights(1 / 3, 1 / 3, 1 / 3)
25
+ return RankWeights(
26
+ relevance=self.relevance / total,
27
+ importance=self.importance / total,
28
+ recency=self.recency / total,
29
+ )
30
+
31
+
32
+ def recency_score(memory: Memory, now: datetime, half_life_days: float = 30.0) -> float:
33
+ """Exponential decay score based on memory age."""
34
+ ts = memory.timestamp
35
+ if ts.tzinfo is None:
36
+ ts = ts.replace(tzinfo=timezone.utc)
37
+ if now.tzinfo is None:
38
+ now = now.replace(tzinfo=timezone.utc)
39
+ age_days = max(0.0, (now - ts).total_seconds() / 86400.0)
40
+ return math.exp(-age_days / half_life_days)
41
+
42
+
43
+ def importance_score(memory: Memory) -> float:
44
+ """Normalize importance to [0, 1]."""
45
+ return memory.importance / 10.0
46
+
47
+
48
+ def score_memory(
49
+ memory: Memory,
50
+ relevance: float,
51
+ now: datetime | None = None,
52
+ weights: RankWeights | None = None,
53
+ ) -> float:
54
+ """Compute weighted composite score for a memory."""
55
+ w = (weights or RankWeights()).normalized()
56
+ now = now or utc_now()
57
+ rec = recency_score(memory, now)
58
+ imp = importance_score(memory)
59
+ return w.relevance * relevance + w.importance * imp + w.recency * rec
60
+
61
+
62
+ def rank_memories(
63
+ query: str,
64
+ memories: list[Memory],
65
+ weights: RankWeights | None = None,
66
+ min_relevance: float = 0.0,
67
+ ) -> list[tuple[Memory, float]]:
68
+ """
69
+ Rank memories by composite score.
70
+
71
+ Returns list of (memory, score) tuples sorted descending by score.
72
+ """
73
+ now = utc_now()
74
+ scored: list[tuple[Memory, float]] = []
75
+ for memory in memories:
76
+ rel = compute_relevance(query, memory)
77
+ if rel < min_relevance:
78
+ continue
79
+ total = score_memory(memory, rel, now=now, weights=weights)
80
+ scored.append((memory, total))
81
+ scored.sort(
82
+ key=lambda item: (
83
+ -item[1],
84
+ -item[0].importance,
85
+ -item[0].timestamp.timestamp(),
86
+ )
87
+ )
88
+ return scored
memorybrain/search.py ADDED
@@ -0,0 +1,103 @@
1
+ """Keyword search and relevance scoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Iterable
7
+
8
+ from memorybrain.storage import Memory
9
+
10
+ _TOKEN_RE = re.compile(r"[a-z0-9]+", re.IGNORECASE)
11
+ _STOPWORDS = frozenset(
12
+ {
13
+ "a",
14
+ "an",
15
+ "the",
16
+ "is",
17
+ "are",
18
+ "was",
19
+ "were",
20
+ "be",
21
+ "been",
22
+ "being",
23
+ "have",
24
+ "has",
25
+ "had",
26
+ "do",
27
+ "does",
28
+ "did",
29
+ "will",
30
+ "would",
31
+ "could",
32
+ "should",
33
+ "may",
34
+ "might",
35
+ "must",
36
+ "can",
37
+ "to",
38
+ "of",
39
+ "in",
40
+ "for",
41
+ "on",
42
+ "with",
43
+ "at",
44
+ "by",
45
+ "from",
46
+ "as",
47
+ "what",
48
+ "which",
49
+ "who",
50
+ "whom",
51
+ "this",
52
+ "that",
53
+ "these",
54
+ "those",
55
+ "and",
56
+ "but",
57
+ "or",
58
+ "if",
59
+ "user",
60
+ "like",
61
+ "likes",
62
+ "does",
63
+ }
64
+ )
65
+
66
+
67
+ def tokenize(text: str) -> list[str]:
68
+ """Extract lowercase alphanumeric tokens, skipping stopwords."""
69
+ tokens = [m.group(0).lower() for m in _TOKEN_RE.finditer(text)]
70
+ return [t for t in tokens if t not in _STOPWORDS and len(t) > 1]
71
+
72
+
73
+ def relevance_score(query: str, text: str, tags: Iterable[str] | None = None) -> float:
74
+ """
75
+ Score relevance in [0, 1] based on query token overlap with text and tags.
76
+ """
77
+ query_tokens = set(tokenize(query))
78
+ if not query_tokens:
79
+ return 0.0
80
+ text_tokens = set(tokenize(text))
81
+ tag_tokens: set[str] = set()
82
+ if tags:
83
+ for tag in tags:
84
+ tag_tokens.add(tag.lower().strip())
85
+ tag_tokens.update(tokenize(tag))
86
+ matched = len(query_tokens & (text_tokens | tag_tokens))
87
+ return min(1.0, matched / len(query_tokens))
88
+
89
+
90
+ def compute_relevance(query: str, memory: Memory) -> float:
91
+ """Compute relevance score for a memory."""
92
+ return relevance_score(query, memory.text, memory.tags)
93
+
94
+
95
+ def filter_candidates(
96
+ memories: list[Memory],
97
+ query: str,
98
+ min_relevance: float = 0.0,
99
+ ) -> list[Memory]:
100
+ """Return memories meeting minimum relevance threshold."""
101
+ if min_relevance <= 0:
102
+ return list(memories)
103
+ return [m for m in memories if compute_relevance(query, m) >= min_relevance]
memorybrain/storage.py ADDED
@@ -0,0 +1,255 @@
1
+ """SQLite storage backend for MemoryBrain."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sqlite3
7
+ import threading
8
+ import uuid
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+
15
+ def utc_now() -> datetime:
16
+ """Return current UTC datetime."""
17
+ return datetime.now(timezone.utc)
18
+
19
+
20
+ def normalize_tags(tags: list[str] | None) -> list[str]:
21
+ """Normalize tags to lowercase stripped unique sorted list."""
22
+ if not tags:
23
+ return []
24
+ return sorted({t.strip().lower() for t in tags if t.strip()})
25
+
26
+
27
+ @dataclass
28
+ class Memory:
29
+ """A single stored memory entry."""
30
+
31
+ text: str
32
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
33
+ timestamp: datetime = field(default_factory=utc_now)
34
+ tags: list[str] = field(default_factory=list)
35
+ importance: float = 5.0
36
+ access_count: int = 0
37
+ metadata: dict[str, Any] = field(default_factory=dict)
38
+
39
+ def __post_init__(self) -> None:
40
+ self.tags = normalize_tags(self.tags)
41
+ self.importance = max(0.0, min(10.0, float(self.importance)))
42
+ if self.timestamp.tzinfo is None:
43
+ self.timestamp = self.timestamp.replace(tzinfo=timezone.utc)
44
+
45
+ def to_dict(self) -> dict[str, Any]:
46
+ """Serialize memory to a JSON-compatible dict."""
47
+ return {
48
+ "id": self.id,
49
+ "text": self.text,
50
+ "timestamp": self.timestamp.isoformat(),
51
+ "tags": self.tags,
52
+ "importance": self.importance,
53
+ "access_count": self.access_count,
54
+ "metadata": self.metadata,
55
+ }
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: dict[str, Any]) -> Memory:
59
+ """Deserialize memory from a dict."""
60
+ ts = data.get("timestamp")
61
+ if isinstance(ts, str):
62
+ timestamp = datetime.fromisoformat(ts.replace("Z", "+00:00"))
63
+ elif isinstance(ts, datetime):
64
+ timestamp = ts
65
+ else:
66
+ timestamp = utc_now()
67
+ return cls(
68
+ id=str(data.get("id", str(uuid.uuid4()))),
69
+ text=str(data["text"]),
70
+ timestamp=timestamp,
71
+ tags=data.get("tags", []),
72
+ importance=float(data.get("importance", 5.0)),
73
+ access_count=int(data.get("access_count", 0)),
74
+ metadata=dict(data.get("metadata", {})),
75
+ )
76
+
77
+
78
+ _SCHEMA = """
79
+ CREATE TABLE IF NOT EXISTS memories (
80
+ id TEXT PRIMARY KEY,
81
+ text TEXT NOT NULL,
82
+ timestamp TEXT NOT NULL,
83
+ tags TEXT NOT NULL,
84
+ importance REAL NOT NULL,
85
+ access_count INTEGER DEFAULT 0,
86
+ metadata TEXT
87
+ );
88
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
89
+ """
90
+
91
+
92
+ def default_db_path() -> Path:
93
+ """Default SQLite database path."""
94
+ return Path.home() / ".memorybrain" / "memories.db"
95
+
96
+
97
+ class SQLiteStorage:
98
+ """Thread-safe SQLite storage for memories."""
99
+
100
+ def __init__(self, db_path: str | Path | None = None) -> None:
101
+ path = Path(db_path).expanduser() if db_path else default_db_path()
102
+ path.parent.mkdir(parents=True, exist_ok=True)
103
+ self._db_path = path
104
+ self._lock = threading.RLock()
105
+ self._init_db()
106
+
107
+ @property
108
+ def db_path(self) -> Path:
109
+ return self._db_path
110
+
111
+ def _connect(self) -> sqlite3.Connection:
112
+ conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
113
+ conn.row_factory = sqlite3.Row
114
+ return conn
115
+
116
+ def _init_db(self) -> None:
117
+ with self._lock:
118
+ conn = self._connect()
119
+ try:
120
+ conn.executescript(_SCHEMA)
121
+ conn.commit()
122
+ finally:
123
+ conn.close()
124
+
125
+ def _row_to_memory(self, row: sqlite3.Row) -> Memory:
126
+ return Memory(
127
+ id=row["id"],
128
+ text=row["text"],
129
+ timestamp=datetime.fromisoformat(row["timestamp"].replace("Z", "+00:00")),
130
+ tags=json.loads(row["tags"]),
131
+ importance=float(row["importance"]),
132
+ access_count=int(row["access_count"] or 0),
133
+ metadata=json.loads(row["metadata"] or "{}"),
134
+ )
135
+
136
+ def _memory_to_row(self, memory: Memory) -> tuple[Any, ...]:
137
+ return (
138
+ memory.id,
139
+ memory.text,
140
+ memory.timestamp.isoformat(),
141
+ json.dumps(memory.tags),
142
+ memory.importance,
143
+ memory.access_count,
144
+ json.dumps(memory.metadata),
145
+ )
146
+
147
+ def add(self, memory: Memory) -> Memory:
148
+ with self._lock:
149
+ conn = self._connect()
150
+ try:
151
+ conn.execute(
152
+ """
153
+ INSERT INTO memories (
154
+ id, text, timestamp, tags, importance, access_count, metadata
155
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
156
+ """,
157
+ self._memory_to_row(memory),
158
+ )
159
+ conn.commit()
160
+ finally:
161
+ conn.close()
162
+ return memory
163
+
164
+ def get(self, memory_id: str) -> Memory | None:
165
+ with self._lock:
166
+ conn = self._connect()
167
+ try:
168
+ row = conn.execute(
169
+ "SELECT * FROM memories WHERE id = ?", (memory_id,)
170
+ ).fetchone()
171
+ return self._row_to_memory(row) if row else None
172
+ finally:
173
+ conn.close()
174
+
175
+ def update(self, memory: Memory) -> Memory:
176
+ with self._lock:
177
+ conn = self._connect()
178
+ try:
179
+ conn.execute(
180
+ """
181
+ UPDATE memories SET
182
+ text=?, timestamp=?, tags=?, importance=?,
183
+ access_count=?, metadata=?
184
+ WHERE id=?
185
+ """,
186
+ (
187
+ memory.text,
188
+ memory.timestamp.isoformat(),
189
+ json.dumps(memory.tags),
190
+ memory.importance,
191
+ memory.access_count,
192
+ json.dumps(memory.metadata),
193
+ memory.id,
194
+ ),
195
+ )
196
+ conn.commit()
197
+ finally:
198
+ conn.close()
199
+ return memory
200
+
201
+ def delete(self, memory_id: str) -> bool:
202
+ with self._lock:
203
+ conn = self._connect()
204
+ try:
205
+ cur = conn.execute("DELETE FROM memories WHERE id = ?", (memory_id,))
206
+ conn.commit()
207
+ return cur.rowcount > 0
208
+ finally:
209
+ conn.close()
210
+
211
+ def list_all(self) -> list[Memory]:
212
+ with self._lock:
213
+ conn = self._connect()
214
+ try:
215
+ rows = conn.execute(
216
+ "SELECT * FROM memories ORDER BY timestamp DESC"
217
+ ).fetchall()
218
+ return [self._row_to_memory(r) for r in rows]
219
+ finally:
220
+ conn.close()
221
+
222
+ def count(self) -> int:
223
+ with self._lock:
224
+ conn = self._connect()
225
+ try:
226
+ row = conn.execute("SELECT COUNT(*) AS c FROM memories").fetchone()
227
+ return int(row["c"]) if row else 0
228
+ finally:
229
+ conn.close()
230
+
231
+ def export_records(self) -> list[dict[str, Any]]:
232
+ return [m.to_dict() for m in self.list_all()]
233
+
234
+ def import_records(self, records: list[dict[str, Any]]) -> int:
235
+ count = 0
236
+ for record in records:
237
+ memory = Memory.from_dict(record)
238
+ if self.get(memory.id):
239
+ self.update(memory)
240
+ else:
241
+ self.add(memory)
242
+ count += 1
243
+ return count
244
+
245
+ def db_file_size_bytes(self) -> int:
246
+ if self._db_path.exists():
247
+ return self._db_path.stat().st_size
248
+ return 0
249
+
250
+ def tag_counts(self) -> dict[str, int]:
251
+ counts: dict[str, int] = {}
252
+ for memory in self.list_all():
253
+ for tag in memory.tags:
254
+ counts[tag] = counts.get(tag, 0) + 1
255
+ return counts
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: memorybrain
3
+ Version: 1.0.0
4
+ Summary: Long-term memory library for AI assistants and agents
5
+ Home-page: https://github.com/memorybrain/memorybrain
6
+ Author: MemoryBrain Contributors
7
+ Author-email: memorybrain@example.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/memorybrain/memorybrain
10
+ Project-URL: Tracker, https://github.com/memorybrain/memorybrain/issues
11
+ Keywords: ai memory agents llm chatbot sqlite
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/plain
26
+ License-File: LICENSE.txt
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: home-page
33
+ Dynamic: keywords
34
+ Dynamic: license
35
+ Dynamic: license-file
36
+ Dynamic: project-url
37
+ Dynamic: requires-python
38
+ Dynamic: summary
39
+
40
+ MemoryBrain
41
+ ===========
42
+
43
+ Long-term memory library for AI assistants, chatbots, and autonomous agents.
44
+ Stores memories in a local SQLite database with intelligent ranked retrieval.
45
+
46
+ Requirements
47
+ ------------
48
+ - Python 3.10 or newer
49
+ - No third-party runtime dependencies
50
+
51
+ Installation
52
+ ------------
53
+ pip install memorybrain
54
+
55
+ Or install from source:
56
+
57
+ pip install .
58
+
59
+ Quick Start
60
+ -----------
61
+ from memorybrain import MemoryBrain
62
+
63
+ brain = MemoryBrain()
64
+
65
+ brain.remember(
66
+ "User likes Flutter development",
67
+ tags=["flutter", "programming"],
68
+ importance=10,
69
+ )
70
+
71
+ results = brain.recall("What framework does the user like?")
72
+ for memory in results:
73
+ print(memory.text)
74
+
75
+ matches = brain.search("Flutter")
76
+ print(matches)
77
+
78
+ print(brain.stats())
79
+
80
+ API Overview
81
+ ------------
82
+ remember(text, tags=None, importance=5)
83
+ Store a new memory. Returns a Memory object.
84
+
85
+ recall(query, limit=10)
86
+ Retrieve memories ranked for a natural-language query.
87
+
88
+ search(query, limit=10)
89
+ Search memories by keyword relevance.
90
+
91
+ forget(memory_id)
92
+ Delete a memory by ID. Returns True if deleted.
93
+
94
+ export("memories.json")
95
+ Export all memories to a JSON file.
96
+
97
+ import_file("memories.json")
98
+ Import memories from JSON (upsert by ID).
99
+
100
+ stats()
101
+ Return statistics:
102
+ - total_memories
103
+ - most_common_tags (list of {tag, count})
104
+ - database_size_bytes
105
+
106
+ Custom Database Path
107
+ --------------------
108
+ brain = MemoryBrain(db_path="./data/my_memories.db")
109
+
110
+ Default location: ~/.memorybrain/memories.db
111
+
112
+ Build and Publish to PyPI
113
+ -------------------------
114
+ Install build tools:
115
+
116
+ pip install wheel twine build
117
+
118
+ Build source distribution and wheel:
119
+
120
+ python setup.py sdist bdist_wheel
121
+
122
+ Or using the modern build frontend:
123
+
124
+ python -m build
125
+
126
+ Validate packages:
127
+
128
+ twine check dist/*
129
+
130
+ Upload to PyPI (requires PyPI account and API token):
131
+
132
+ twine upload dist/*
133
+
134
+ Test upload to TestPyPI first:
135
+
136
+ twine upload --repository testpypi dist/*
137
+
138
+ License
139
+ -------
140
+ MIT License — see LICENSE.txt
141
+
142
+
143
+ MIT License
144
+
145
+ Copyright (c) 2026 MemoryBrain Contributors
146
+
147
+ Permission is hereby granted, free of charge, to any person obtaining a copy
148
+ of this software and associated documentation files (the "Software"), to deal
149
+ in the Software without restriction, including without limitation the rights
150
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
151
+ copies of the Software, and to permit persons to whom the Software is
152
+ furnished to do so, subject to the following conditions:
153
+
154
+ The above copyright notice and this permission notice shall be included in all
155
+ copies or substantial portions of the Software.
156
+
157
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
158
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
159
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
160
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
161
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
162
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
163
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ memorybrain/__init__.py,sha256=Fxw8g-4p9ynpXOUb5IWkU-AujUsc5e9yNW0IvlnsWs0,474
2
+ memorybrain/brain.py,sha256=-fM5lU3jn9TPSIHDrmMwmMUZw4afOruc76TQ5MFpl-I,5441
3
+ memorybrain/ranking.py,sha256=gtl1QAvVRj0Y3uGzBIlsmakWD3KO2j6dYcX0Fbp31Is,2636
4
+ memorybrain/search.py,sha256=R51OffgVOr48kxKnMLprST0a3wtF33ECOiVjay9KM_s,2440
5
+ memorybrain/storage.py,sha256=a6YJAdWtXFOL_Act_KRjtqLMnEJBhoCA1FymWK6eUJI,8305
6
+ memorybrain-1.0.0.dist-info/licenses/LICENSE.txt,sha256=sO4omrWczV4cARDCHnAUSXWMRMQhZgNSikNC6EPHXsw,1102
7
+ memorybrain-1.0.0.dist-info/METADATA,sha256=oun-9paS6gk0OxyPw-1DiCsVNzIA0I5_Q1H62o45yc8,4688
8
+ memorybrain-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ memorybrain-1.0.0.dist-info/top_level.txt,sha256=qrmLKumFk_2elpc-AuOgIyESXjhd8-68A2hewxXFsIs,12
10
+ memorybrain-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MemoryBrain 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 @@
1
+ memorybrain