remembrane 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
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e .[dev]
20
+ - run: ruff check src tests
21
+ - run: pytest --cov=remembrane --cov-report=term-missing
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .coverage
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ *.db
10
+ .venv/
11
+ pytest-cache-files-*/
@@ -0,0 +1,22 @@
1
+ # Contributing to remembrane
2
+
3
+ Thanks for your interest!
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/satyasairay/remembrane
9
+ cd remembrane
10
+ pip install -e .[dev]
11
+ pytest
12
+ ```
13
+
14
+ ## Guidelines
15
+
16
+ - Keep the core dependency-free. New integrations go in `src/remembrane/adapters/` as duck-typed, optional modules.
17
+ - Every PR needs tests. Run `pytest` and `ruff check src tests` before submitting.
18
+ - One feature per PR. Open an issue first for anything large.
19
+
20
+ ## Ideas welcome
21
+
22
+ Good first contributions: new framework adapters, embedder backends, recall strategies (MMR, hybrid keyword+vector), benchmarks.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Satyasai Ray
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,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: remembrane
3
+ Version: 0.1.0
4
+ Summary: Local-first persistent memory for AI agents. SQLite-backed, zero required dependencies, pluggable embeddings, framework adapters and an MCP server.
5
+ Project-URL: Homepage, https://github.com/satyasairay/remembrane
6
+ Project-URL: Issues, https://github.com/satyasairay/remembrane/issues
7
+ Author-email: Satyasai Ray <satyasairay@yahoo.com>, Satyasai Ray <satyasairay2@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,ai,crewai,langchain,llm,local-first,mcp,memory,sqlite
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-cov; extra == 'dev'
24
+ Requires-Dist: pytest>=7.0; extra == 'dev'
25
+ Requires-Dist: ruff; extra == 'dev'
26
+ Provides-Extra: mcp
27
+ Requires-Dist: mcp>=1.0; extra == 'mcp'
28
+ Provides-Extra: openai
29
+ Requires-Dist: openai>=1.0; extra == 'openai'
30
+ Provides-Extra: sentence-transformers
31
+ Requires-Dist: sentence-transformers>=2.2; extra == 'sentence-transformers'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # remembrane
35
+
36
+ **Local-first persistent memory for AI agents.** SQLite-backed, zero required dependencies, pluggable embeddings, with adapters for LangChain and CrewAI and a built-in MCP server.
37
+
38
+ ```bash
39
+ pip install remembrane
40
+ ```
41
+
42
+ ## Why
43
+
44
+ Agents forget everything between sessions. Existing memory solutions are cloud APIs, require a vector database, or drag in a heavyweight framework. `remembrane` is the opposite:
45
+
46
+ - **One file.** Your agent's entire memory is a SQLite database you can copy, back up, diff, or delete.
47
+ - **Zero required dependencies.** The default embedder is pure stdlib. `pip install remembrane` pulls in nothing else.
48
+ - **Human-like recall.** Results are ranked by a composite of semantic similarity, recency decay (memories halve in weight every week by default), and importance. Recalled memories are *reinforced* — spaced repetition for agents.
49
+ - **Framework-agnostic.** Use it bare, through the LangChain or CrewAI adapters, or expose it to any MCP-capable agent (like Claude) as an MCP server.
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ from remembrane import MemoryStore
55
+
56
+ mem = MemoryStore("agent.db") # or ":memory:" for ephemeral
57
+
58
+ mem.store("User prefers dark mode", importance=0.8)
59
+ mem.store("Deploy target is AWS us-east-1", namespace="ops")
60
+
61
+ results = mem.recall("what theme does the user like?")
62
+ print(results[0].memory.content) # → "User prefers dark mode"
63
+ print(results[0].score) # similarity × recency × importance
64
+ ```
65
+
66
+ ### Memory lifecycle
67
+
68
+ ```python
69
+ mem.reinforce(memory_id) # strengthen: slower decay, higher rank
70
+ mem.forget(memory_id) # delete one
71
+ mem.forget(namespace="ops") # delete a namespace
72
+ mem.forget(older_than_seconds=30*86400) # prune stale memories
73
+ mem.consolidate() # merge near-duplicates
74
+ mem.export() # plain dicts, ready for json.dump
75
+ ```
76
+
77
+ ### Tuning recall
78
+
79
+ ```python
80
+ from remembrane import MemoryStore, ScoringConfig
81
+
82
+ mem = MemoryStore(
83
+ "agent.db",
84
+ scoring=ScoringConfig(
85
+ weight_similarity=0.7,
86
+ weight_recency=0.15,
87
+ weight_importance=0.15,
88
+ half_life_seconds=7 * 24 * 3600, # recency halves every week
89
+ ),
90
+ )
91
+ ```
92
+
93
+ ## Embedders
94
+
95
+ The default `HashEmbedder` is deterministic, offline, and dependency-free — it hashes word and character n-grams. That makes similarity *lexical*, not semantic. It works well for typical agent memories (facts, preferences, short statements). For true semantic recall, plug in a real model:
96
+
97
+ ```python
98
+ from remembrane import MemoryStore, SentenceTransformerEmbedder, OpenAIEmbedder
99
+
100
+ mem = MemoryStore("agent.db", embedder=SentenceTransformerEmbedder()) # local, pip install remembrane[sentence-transformers]
101
+ mem = MemoryStore("agent.db", embedder=OpenAIEmbedder()) # API, pip install remembrane[openai]
102
+ ```
103
+
104
+ Any object with `embed(texts) -> List[List[float]]` and a `dimension` attribute works.
105
+
106
+ Note: don't mix embedders in one database. Vectors from different embedders aren't comparable.
107
+
108
+ ## LangChain
109
+
110
+ ```python
111
+ from remembrane import MemoryStore
112
+ from remembrane.adapters import RemembraneChatMemory
113
+
114
+ memory = RemembraneChatMemory(MemoryStore("agent.db"), session_id="user-42")
115
+
116
+ memory.save_context({"input": "my favorite color is teal"}, {"output": "Noted!"})
117
+ memory.load_memory_variables({"input": "what color do I like?"})
118
+ # {'history': 'human: my favorite color is teal\nai: Noted!'}
119
+ ```
120
+
121
+ Unlike buffer memory, this retrieves the exchanges *relevant to the current input* — the context window stays small no matter how long the history grows.
122
+
123
+ ## CrewAI
124
+
125
+ ```python
126
+ from remembrane import MemoryStore
127
+ from remembrane.adapters import RemembraneStorage
128
+
129
+ storage = RemembraneStorage(MemoryStore("crew.db"))
130
+ storage.save("the deadline is next friday", metadata={"task": "planning"})
131
+ storage.search("when is the deadline?")
132
+ ```
133
+
134
+ ## MCP server
135
+
136
+ Give any MCP-capable agent (e.g. Claude Desktop, Claude Code) persistent memory:
137
+
138
+ ```bash
139
+ pip install remembrane[mcp]
140
+ remembrane-mcp --db ~/agent-memory.db
141
+ ```
142
+
143
+ ```json
144
+ {
145
+ "mcpServers": {
146
+ "remembrane": {
147
+ "command": "remembrane-mcp",
148
+ "args": ["--db", "/path/to/agent-memory.db"]
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ Tools exposed: `memory_store`, `memory_recall`, `memory_forget`, `memory_reinforce`, `memory_stats`.
155
+
156
+ ## CLI
157
+
158
+ ```bash
159
+ remembrane --db agent.db store "the user prefers dark mode" --importance 0.8
160
+ remembrane --db agent.db recall "what theme?"
161
+ remembrane --db agent.db list
162
+ remembrane --db agent.db stats
163
+ remembrane --db agent.db export > backup.json
164
+ ```
165
+
166
+ ## How ranking works
167
+
168
+ ```
169
+ score = 0.7·similarity + 0.15·recency + 0.15·importance
170
+ recency = exp(−ln2 · age / half_life)
171
+ ```
172
+
173
+ `age` is measured from the memory's **last access**, not creation — every recall resets the decay clock. Frequently-used memories stay vivid; untouched ones fade. All weights and the half-life are configurable.
174
+
175
+ ## Design choices
176
+
177
+ - **SQLite over a vector DB** — agent memory stores are small (thousands, not billions, of rows). Brute-force cosine over a few thousand vectors is sub-millisecond, and you gain transactions, a single portable file, and zero infra.
178
+ - **No background daemon** — decay is computed at read time, so nothing runs when your agent doesn't.
179
+ - **Duck-typed adapters** — `remembrane` never imports langchain or crewai; the adapters match their interfaces structurally, so there are no version-pinning fights.
180
+
181
+ ## Development
182
+
183
+ ```bash
184
+ git clone https://github.com/satyasairay/remembrane
185
+ cd remembrane
186
+ pip install -e .[dev]
187
+ pytest
188
+ ```
189
+
190
+ ## License
191
+
192
+ MIT
@@ -0,0 +1,159 @@
1
+ # remembrane
2
+
3
+ **Local-first persistent memory for AI agents.** SQLite-backed, zero required dependencies, pluggable embeddings, with adapters for LangChain and CrewAI and a built-in MCP server.
4
+
5
+ ```bash
6
+ pip install remembrane
7
+ ```
8
+
9
+ ## Why
10
+
11
+ Agents forget everything between sessions. Existing memory solutions are cloud APIs, require a vector database, or drag in a heavyweight framework. `remembrane` is the opposite:
12
+
13
+ - **One file.** Your agent's entire memory is a SQLite database you can copy, back up, diff, or delete.
14
+ - **Zero required dependencies.** The default embedder is pure stdlib. `pip install remembrane` pulls in nothing else.
15
+ - **Human-like recall.** Results are ranked by a composite of semantic similarity, recency decay (memories halve in weight every week by default), and importance. Recalled memories are *reinforced* — spaced repetition for agents.
16
+ - **Framework-agnostic.** Use it bare, through the LangChain or CrewAI adapters, or expose it to any MCP-capable agent (like Claude) as an MCP server.
17
+
18
+ ## Quick start
19
+
20
+ ```python
21
+ from remembrane import MemoryStore
22
+
23
+ mem = MemoryStore("agent.db") # or ":memory:" for ephemeral
24
+
25
+ mem.store("User prefers dark mode", importance=0.8)
26
+ mem.store("Deploy target is AWS us-east-1", namespace="ops")
27
+
28
+ results = mem.recall("what theme does the user like?")
29
+ print(results[0].memory.content) # → "User prefers dark mode"
30
+ print(results[0].score) # similarity × recency × importance
31
+ ```
32
+
33
+ ### Memory lifecycle
34
+
35
+ ```python
36
+ mem.reinforce(memory_id) # strengthen: slower decay, higher rank
37
+ mem.forget(memory_id) # delete one
38
+ mem.forget(namespace="ops") # delete a namespace
39
+ mem.forget(older_than_seconds=30*86400) # prune stale memories
40
+ mem.consolidate() # merge near-duplicates
41
+ mem.export() # plain dicts, ready for json.dump
42
+ ```
43
+
44
+ ### Tuning recall
45
+
46
+ ```python
47
+ from remembrane import MemoryStore, ScoringConfig
48
+
49
+ mem = MemoryStore(
50
+ "agent.db",
51
+ scoring=ScoringConfig(
52
+ weight_similarity=0.7,
53
+ weight_recency=0.15,
54
+ weight_importance=0.15,
55
+ half_life_seconds=7 * 24 * 3600, # recency halves every week
56
+ ),
57
+ )
58
+ ```
59
+
60
+ ## Embedders
61
+
62
+ The default `HashEmbedder` is deterministic, offline, and dependency-free — it hashes word and character n-grams. That makes similarity *lexical*, not semantic. It works well for typical agent memories (facts, preferences, short statements). For true semantic recall, plug in a real model:
63
+
64
+ ```python
65
+ from remembrane import MemoryStore, SentenceTransformerEmbedder, OpenAIEmbedder
66
+
67
+ mem = MemoryStore("agent.db", embedder=SentenceTransformerEmbedder()) # local, pip install remembrane[sentence-transformers]
68
+ mem = MemoryStore("agent.db", embedder=OpenAIEmbedder()) # API, pip install remembrane[openai]
69
+ ```
70
+
71
+ Any object with `embed(texts) -> List[List[float]]` and a `dimension` attribute works.
72
+
73
+ Note: don't mix embedders in one database. Vectors from different embedders aren't comparable.
74
+
75
+ ## LangChain
76
+
77
+ ```python
78
+ from remembrane import MemoryStore
79
+ from remembrane.adapters import RemembraneChatMemory
80
+
81
+ memory = RemembraneChatMemory(MemoryStore("agent.db"), session_id="user-42")
82
+
83
+ memory.save_context({"input": "my favorite color is teal"}, {"output": "Noted!"})
84
+ memory.load_memory_variables({"input": "what color do I like?"})
85
+ # {'history': 'human: my favorite color is teal\nai: Noted!'}
86
+ ```
87
+
88
+ Unlike buffer memory, this retrieves the exchanges *relevant to the current input* — the context window stays small no matter how long the history grows.
89
+
90
+ ## CrewAI
91
+
92
+ ```python
93
+ from remembrane import MemoryStore
94
+ from remembrane.adapters import RemembraneStorage
95
+
96
+ storage = RemembraneStorage(MemoryStore("crew.db"))
97
+ storage.save("the deadline is next friday", metadata={"task": "planning"})
98
+ storage.search("when is the deadline?")
99
+ ```
100
+
101
+ ## MCP server
102
+
103
+ Give any MCP-capable agent (e.g. Claude Desktop, Claude Code) persistent memory:
104
+
105
+ ```bash
106
+ pip install remembrane[mcp]
107
+ remembrane-mcp --db ~/agent-memory.db
108
+ ```
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "remembrane": {
114
+ "command": "remembrane-mcp",
115
+ "args": ["--db", "/path/to/agent-memory.db"]
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ Tools exposed: `memory_store`, `memory_recall`, `memory_forget`, `memory_reinforce`, `memory_stats`.
122
+
123
+ ## CLI
124
+
125
+ ```bash
126
+ remembrane --db agent.db store "the user prefers dark mode" --importance 0.8
127
+ remembrane --db agent.db recall "what theme?"
128
+ remembrane --db agent.db list
129
+ remembrane --db agent.db stats
130
+ remembrane --db agent.db export > backup.json
131
+ ```
132
+
133
+ ## How ranking works
134
+
135
+ ```
136
+ score = 0.7·similarity + 0.15·recency + 0.15·importance
137
+ recency = exp(−ln2 · age / half_life)
138
+ ```
139
+
140
+ `age` is measured from the memory's **last access**, not creation — every recall resets the decay clock. Frequently-used memories stay vivid; untouched ones fade. All weights and the half-life are configurable.
141
+
142
+ ## Design choices
143
+
144
+ - **SQLite over a vector DB** — agent memory stores are small (thousands, not billions, of rows). Brute-force cosine over a few thousand vectors is sub-millisecond, and you gain transactions, a single portable file, and zero infra.
145
+ - **No background daemon** — decay is computed at read time, so nothing runs when your agent doesn't.
146
+ - **Duck-typed adapters** — `remembrane` never imports langchain or crewai; the adapters match their interfaces structurally, so there are no version-pinning fights.
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ git clone https://github.com/satyasairay/remembrane
152
+ cd remembrane
153
+ pip install -e .[dev]
154
+ pytest
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "remembrane"
7
+ version = "0.1.0"
8
+ description = "Local-first persistent memory for AI agents. SQLite-backed, zero required dependencies, pluggable embeddings, framework adapters and an MCP server."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Satyasai Ray", email = "satyasairay@yahoo.com" },
14
+ { name = "Satyasai Ray", email = "satyasairay2@gmail.com" },
15
+ ]
16
+ keywords = ["ai", "agents", "memory", "llm", "mcp", "langchain", "crewai", "sqlite", "local-first"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/satyasairay/remembrane"
32
+ Issues = "https://github.com/satyasairay/remembrane/issues"
33
+
34
+ [project.optional-dependencies]
35
+ openai = ["openai>=1.0"]
36
+ sentence-transformers = ["sentence-transformers>=2.2"]
37
+ mcp = ["mcp>=1.0"]
38
+ dev = ["pytest>=7.0", "pytest-cov", "ruff"]
39
+
40
+ [project.scripts]
41
+ remembrane = "remembrane.cli:main"
42
+ remembrane-mcp = "remembrane.mcp_server:main"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/remembrane"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+
50
+ [tool.ruff]
51
+ line-length = 100
@@ -0,0 +1,17 @@
1
+ """remembrane — local-first persistent memory for AI agents."""
2
+ from .embedders import HashEmbedder, OpenAIEmbedder, SentenceTransformerEmbedder, cosine_similarity
3
+ from .models import Memory, RecallResult
4
+ from .scoring import ScoringConfig
5
+ from .store import MemoryStore
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = [
9
+ "MemoryStore",
10
+ "Memory",
11
+ "RecallResult",
12
+ "ScoringConfig",
13
+ "HashEmbedder",
14
+ "OpenAIEmbedder",
15
+ "SentenceTransformerEmbedder",
16
+ "cosine_similarity",
17
+ ]
@@ -0,0 +1,10 @@
1
+ """Framework adapters for remembrane.
2
+
3
+ Adapters are duck-typed against their target frameworks so remembrane never
4
+ hard-depends on them. Import errors only occur if you actually use an adapter
5
+ without its framework installed.
6
+ """
7
+ from .crewai import RemembraneStorage
8
+ from .langchain import RemembraneChatMemory
9
+
10
+ __all__ = ["RemembraneChatMemory", "RemembraneStorage"]
@@ -0,0 +1,47 @@
1
+ """CrewAI-compatible storage adapter.
2
+
3
+ Duck-typed against crewai's external-memory Storage interface
4
+ (save / search / reset), so it works without importing crewai itself.
5
+
6
+ Example::
7
+
8
+ from remembrane import MemoryStore
9
+ from remembrane.adapters import RemembraneStorage
10
+
11
+ storage = RemembraneStorage(MemoryStore("crew.db"))
12
+ # pass wherever CrewAI accepts a custom storage backend,
13
+ # or call .save() / .search() directly.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from ..store import MemoryStore
20
+
21
+
22
+ class RemembraneStorage:
23
+ """Storage backend backed by a remembrane MemoryStore."""
24
+
25
+ def __init__(self, store: MemoryStore, namespace: str = "crew"):
26
+ self.store = store
27
+ self.namespace = namespace
28
+
29
+ def save(self, value: Any, metadata: Optional[Dict[str, Any]] = None) -> None:
30
+ self.store.store(str(value), namespace=self.namespace, metadata=metadata or {})
31
+
32
+ def search(self, query: str, limit: int = 5, score_threshold: float = 0.0) -> List[Dict[str, Any]]:
33
+ results = self.store.recall(
34
+ query, k=limit, namespace=self.namespace, min_score=score_threshold
35
+ )
36
+ return [
37
+ {
38
+ "id": r.memory.id,
39
+ "context": r.memory.content,
40
+ "metadata": r.memory.metadata,
41
+ "score": r.score,
42
+ }
43
+ for r in results
44
+ ]
45
+
46
+ def reset(self) -> None:
47
+ self.store.forget(namespace=self.namespace)
@@ -0,0 +1,73 @@
1
+ """LangChain-compatible memory adapter.
2
+
3
+ Duck-typed against LangChain's memory interface (load_memory_variables /
4
+ save_context / clear), so it works without importing langchain itself.
5
+
6
+ Example::
7
+
8
+ from remembrane import MemoryStore
9
+ from remembrane.adapters import RemembraneChatMemory
10
+
11
+ memory = RemembraneChatMemory(MemoryStore("agent.db"), session_id="user-42")
12
+ # use anywhere LangChain expects a memory object,
13
+ # or call .save_context() / .load_memory_variables() directly.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Any, Dict, List
18
+
19
+ from ..store import MemoryStore
20
+
21
+
22
+ class RemembraneChatMemory:
23
+ """Persistent, semantically-recalled conversation memory.
24
+
25
+ Instead of replaying the full transcript, ``load_memory_variables`` returns
26
+ the memories most relevant to the *current input*, ranked by
27
+ similarity x recency x importance.
28
+ """
29
+
30
+ memory_key: str = "history"
31
+
32
+ def __init__(
33
+ self,
34
+ store: MemoryStore,
35
+ session_id: str = "default",
36
+ k: int = 6,
37
+ input_key: str = "input",
38
+ output_key: str = "output",
39
+ ):
40
+ self.store = store
41
+ self.session_id = session_id
42
+ self.k = k
43
+ self.input_key = input_key
44
+ self.output_key = output_key
45
+
46
+ @property
47
+ def memory_variables(self) -> List[str]:
48
+ return [self.memory_key]
49
+
50
+ def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
51
+ """Persist one exchange as two memories (human + ai)."""
52
+ human = str(inputs.get(self.input_key, "")).strip()
53
+ ai = str(outputs.get(self.output_key, "")).strip()
54
+ if human:
55
+ self.store.store(human, namespace=self.session_id, metadata={"role": "human"})
56
+ if ai:
57
+ self.store.store(ai, namespace=self.session_id, metadata={"role": "ai"})
58
+
59
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]:
60
+ """Return the k memories most relevant to the current input."""
61
+ query = str(inputs.get(self.input_key, "")).strip()
62
+ if not query:
63
+ memories = self.store.all(self.session_id)[-self.k :]
64
+ lines = [f"{m.metadata.get('role', '?')}: {m.content}" for m in memories]
65
+ else:
66
+ results = self.store.recall(query, k=self.k, namespace=self.session_id)
67
+ lines = [
68
+ f"{r.memory.metadata.get('role', '?')}: {r.memory.content}" for r in results
69
+ ]
70
+ return {self.memory_key: "\n".join(lines)}
71
+
72
+ def clear(self) -> None:
73
+ self.store.forget(namespace=self.session_id)