meminfra 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.
Files changed (54) hide show
  1. meminfra-0.1.0/LICENSE +21 -0
  2. meminfra-0.1.0/PKG-INFO +248 -0
  3. meminfra-0.1.0/README.md +196 -0
  4. meminfra-0.1.0/pyproject.toml +58 -0
  5. meminfra-0.1.0/setup.cfg +4 -0
  6. meminfra-0.1.0/src/meminfra/__init__.py +11 -0
  7. meminfra-0.1.0/src/meminfra/cli.py +47 -0
  8. meminfra-0.1.0/src/meminfra/config.py +142 -0
  9. meminfra-0.1.0/src/meminfra/database/__init__.py +19 -0
  10. meminfra-0.1.0/src/meminfra/database/connection.py +76 -0
  11. meminfra-0.1.0/src/meminfra/database/models.py +180 -0
  12. meminfra-0.1.0/src/meminfra/memory/__init__.py +37 -0
  13. meminfra-0.1.0/src/meminfra/memory/context.py +486 -0
  14. meminfra-0.1.0/src/meminfra/memory/extractor.py +298 -0
  15. meminfra-0.1.0/src/meminfra/memory/fact_keys.py +74 -0
  16. meminfra-0.1.0/src/meminfra/memory/predicates.py +57 -0
  17. meminfra-0.1.0/src/meminfra/memory/prompts.py +66 -0
  18. meminfra-0.1.0/src/meminfra/memory/schemas.py +45 -0
  19. meminfra-0.1.0/src/meminfra/memory/summaries.py +157 -0
  20. meminfra-0.1.0/src/meminfra/memory/writer.py +418 -0
  21. meminfra-0.1.0/src/meminfra/memory_layer.py +339 -0
  22. meminfra-0.1.0/src/meminfra/migrations/__init__.py +1 -0
  23. meminfra-0.1.0/src/meminfra/migrations/env.py +64 -0
  24. meminfra-0.1.0/src/meminfra/migrations/script.py.mako +27 -0
  25. meminfra-0.1.0/src/meminfra/migrations/versions/0001_initial_schema.py +109 -0
  26. meminfra-0.1.0/src/meminfra/migrations/versions/0002_conversation_summaries.py +68 -0
  27. meminfra-0.1.0/src/meminfra/migrations/versions/0003_hybrid_memory_retrieval.py +40 -0
  28. meminfra-0.1.0/src/meminfra/migrations/versions/0004_message_embedding_persistence.py +33 -0
  29. meminfra-0.1.0/src/meminfra/migrations/versions/0005_structured_fact_uniqueness.py +43 -0
  30. meminfra-0.1.0/src/meminfra/migrations/versions/__init__.py +1 -0
  31. meminfra-0.1.0/src/meminfra/providers/__init__.py +11 -0
  32. meminfra-0.1.0/src/meminfra/providers/embeddings.py +46 -0
  33. meminfra-0.1.0/src/meminfra/providers/llm.py +46 -0
  34. meminfra-0.1.0/src/meminfra/retrieval/__init__.py +97 -0
  35. meminfra-0.1.0/src/meminfra/retrieval/bm25.py +130 -0
  36. meminfra-0.1.0/src/meminfra/retrieval/errors.py +37 -0
  37. meminfra-0.1.0/src/meminfra/retrieval/fusion.py +310 -0
  38. meminfra-0.1.0/src/meminfra/retrieval/lexical.py +92 -0
  39. meminfra-0.1.0/src/meminfra/retrieval/message_vector.py +689 -0
  40. meminfra-0.1.0/src/meminfra/retrieval/schemas.py +58 -0
  41. meminfra-0.1.0/src/meminfra/retrieval/search.py +150 -0
  42. meminfra-0.1.0/src/meminfra/retrieval/structured.py +75 -0
  43. meminfra-0.1.0/src/meminfra/retrieval/vector.py +516 -0
  44. meminfra-0.1.0/src/meminfra/retrieval/vector_support.py +140 -0
  45. meminfra-0.1.0/src/meminfra.egg-info/PKG-INFO +248 -0
  46. meminfra-0.1.0/src/meminfra.egg-info/SOURCES.txt +52 -0
  47. meminfra-0.1.0/src/meminfra.egg-info/dependency_links.txt +1 -0
  48. meminfra-0.1.0/src/meminfra.egg-info/entry_points.txt +2 -0
  49. meminfra-0.1.0/src/meminfra.egg-info/requires.txt +11 -0
  50. meminfra-0.1.0/src/meminfra.egg-info/top_level.txt +1 -0
  51. meminfra-0.1.0/tests/test_cli.py +84 -0
  52. meminfra-0.1.0/tests/test_config.py +211 -0
  53. meminfra-0.1.0/tests/test_database.py +231 -0
  54. meminfra-0.1.0/tests/test_memory_layer.py +594 -0
meminfra-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 banalasaisathwik
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,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: meminfra
3
+ Version: 0.1.0
4
+ Summary: Foundation for reusable, user-scoped long-term memory infrastructure.
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 banalasaisathwik
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Project-URL: Repository, https://github.com/banalasaisathwik/memory-layer
28
+ Project-URL: Issues, https://github.com/banalasaisathwik/memory-layer/issues
29
+ Keywords: memory,llm,infrastructure,postgresql
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
38
+ Requires-Python: >=3.11
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: alembic>=1.13
42
+ Requires-Dist: openai>=1.0
43
+ Requires-Dist: psycopg[binary]>=3.1
44
+ Requires-Dist: pydantic>=2.0
45
+ Requires-Dist: python-dotenv>=1.0
46
+ Requires-Dist: SQLAlchemy>=2.0
47
+ Requires-Dist: faiss-cpu
48
+ Requires-Dist: numpy
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=8.0; extra == "dev"
51
+ Dynamic: license-file
52
+
53
+ # Memory Layer
54
+
55
+ A Python library for durable, user-scoped long-term memory in LLM applications.
56
+
57
+ Applications send conversations to Memory Layer. It extracts durable memories, updates existing facts deterministically, stores them in PostgreSQL, and retrieves relevant memory with hybrid search. It is memory infrastructure—not a chatbot, agent framework, hosted API, or full context builder.
58
+
59
+ ## Install
60
+
61
+ The intended PyPI installation for the 0.1.0 release is:
62
+
63
+ ```bash
64
+ pip install meminfra
65
+ ```
66
+
67
+ Until the package is published, install from source for development:
68
+
69
+ ```bash
70
+ git clone https://github.com/banalasaisathwik/memory-layer.git
71
+ cd memory-layer
72
+ pip install -e .
73
+ ```
74
+
75
+ ## Quickstart
76
+
77
+ Set the required database and provider configuration, then apply the packaged migrations:
78
+
79
+ ```bash
80
+ meminfra migrate
81
+ ```
82
+
83
+ ```python
84
+ from meminfra import MemoryLayer
85
+ from meminfra.database import SessionLocal
86
+
87
+ db = SessionLocal()
88
+ memory = MemoryLayer(db)
89
+
90
+ memory.add(
91
+ user_id="user-123",
92
+ conversation_id="conversation-1",
93
+ messages=[
94
+ {
95
+ "role": "user",
96
+ "content": "I prefer PostgreSQL for backend projects.",
97
+ }
98
+ ],
99
+ )
100
+
101
+ hits = memory.search(
102
+ user_id="user-123",
103
+ query="What database do I prefer?",
104
+ )
105
+
106
+ for hit in hits:
107
+ print(hit.memory_text)
108
+ ```
109
+
110
+ `add()` persists the messages, extracts candidates, writes durable memory, and may update the conversation summary. It creates the user and conversation scope when needed. `search()` returns ranked `SearchHit` records for one user.
111
+
112
+ To generate an answer grounded in retrieved memory:
113
+
114
+ ```python
115
+ result = memory.answer(
116
+ user_id="user-123",
117
+ query="What database do I prefer?",
118
+ )
119
+
120
+ print(result.answer)
121
+ ```
122
+
123
+ `answer()` retrieves memory before asking the configured LLM to answer from it. If search finds no memory, it abstains without making an answer-generation call.
124
+
125
+ ## What happens when you add memory?
126
+
127
+ Memory Layer stores semantic and episodic memories. Structured, single-value semantic facts are updated deterministically; open semantic and episodic memories use conservative duplicate handling. Persisted memories retain source-message provenance.
128
+
129
+ | Existing state | New statement | Action |
130
+ | --- | --- | --- |
131
+ | No active database preference | “I use PostgreSQL” | ADD |
132
+ | Same active preference | “I use PostgreSQL” | NOOP |
133
+ | Active database preference | “I switched to SQLite” | SUPERSEDE |
134
+
135
+ `SUPERSEDE` applies when a supported single-value structured fact changes. The prior memory stays stored for history and provenance, but default retrieval returns active memories only.
136
+
137
+ Durable memory is user-scoped. Raw-message context is conversation-scoped, while durable memory can be retrieved across that user’s conversations.
138
+
139
+ ## Retrieval
140
+
141
+ Natural-language retrieval combines BM25 lexical search with FAISS vector search, then ranks candidates with discounted agreement fusion:
142
+
143
+ ```text
144
+ BM25 + FAISS vector search → discounted agreement fusion → ranked memories
145
+ ```
146
+
147
+ The default fusion strategy rewards agreement while allowing a strong result from one branch to remain competitive. Equal reciprocal-rank fusion (RRF) remains available for compatibility and ablation, but is not the default. When you supply structured filters, exact structured lookup is used as an additional retrieval branch; it is not inferred from every query.
148
+
149
+ PostgreSQL is the durable source of truth for memories and embeddings. Local FAISS indexes are derived retrieval state and rebuild from PostgreSQL when missing, stale, or corrupt.
150
+
151
+ ## Benchmark
152
+
153
+ Frozen internal LoCoMo results for `conv-30`:
154
+
155
+ | Metric | Baseline | Current |
156
+ | --- | ---: | ---: |
157
+ | Hit@5 | 0.457 | 0.686 |
158
+ | Recall@5 | 0.426 | 0.657 |
159
+ | MRR | 0.376 | 0.595 |
160
+
161
+ LoCoMo · `conv-30` · 105 QA · same evaluation protocol.
162
+
163
+ This is an internal baseline comparison, not a cross-system comparison against another memory product. Hit@5 measures whether relevant memory appears in the top five; Recall@5 measures how much gold evidence appears there; MRR measures how early the first relevant result appears. See [`evals/`](evals/) for the harnesses and adapters.
164
+
165
+ ## Configuration
166
+
167
+ Configure connections and providers with environment variables. Do not commit credentials.
168
+
169
+ | Variable | Purpose |
170
+ | --- | --- |
171
+ | `DATABASE_URL` | PostgreSQL runtime connection |
172
+ | `DIRECT_URL` | Direct PostgreSQL URL for migrations; takes precedence for `meminfra migrate` |
173
+ | `LLM_PROVIDER` | LLM provider (`openai`, `openrouter`, or `openai_compatible`) |
174
+ | `LLM_API_KEY` | LLM provider credential |
175
+ | `LLM_BASE_URL` | Optional custom/OpenAI-compatible LLM endpoint |
176
+ | `LLM_MODEL` | LLM model used for extraction, summaries, and answers |
177
+ | `EMBEDDING_PROVIDER` | Embedding provider |
178
+ | `EMBEDDING_API_KEY` | Embedding provider credential |
179
+ | `EMBEDDING_BASE_URL` | Optional custom/OpenAI-compatible embedding endpoint |
180
+ | `EMBEDDING_MODEL` | Embedding model |
181
+ | `FAISS_INDEX_DIR` | Local directory for derived FAISS indexes |
182
+
183
+ Minimal example:
184
+
185
+ ```env
186
+ DATABASE_URL=postgresql+psycopg://user:password@host/database
187
+ LLM_PROVIDER=openai
188
+ LLM_API_KEY=your-llm-key
189
+ LLM_MODEL=your-llm-model
190
+ EMBEDDING_PROVIDER=openai
191
+ EMBEDDING_API_KEY=your-embedding-key
192
+ EMBEDDING_MODEL=text-embedding-3-small
193
+ ```
194
+
195
+ `LLM_BASE_URL` and `EMBEDDING_BASE_URL` are optional and are useful for custom or OpenAI-compatible endpoints.
196
+
197
+ ## Database setup
198
+
199
+ Run migrations after configuring `DATABASE_URL` (or `DIRECT_URL` when the migration connection should differ):
200
+
201
+ ```bash
202
+ meminfra migrate
203
+ ```
204
+
205
+ This applies the packaged Alembic migrations to the configured PostgreSQL database. For repository development, `python -m alembic upgrade head` also works.
206
+
207
+ ## Public API
208
+
209
+ `MemoryLayer` is the high-level facade:
210
+
211
+ ```python
212
+ MemoryLayer.add(user_id=..., conversation_id=..., messages=...)
213
+ MemoryLayer.search(user_id=..., query=..., limit=10, filters=None)
214
+ MemoryLayer.answer(user_id=..., query=..., limit=5, filters=None)
215
+ ```
216
+
217
+ - `add` persists messages and extracts or updates durable memory.
218
+ - `search` retrieves ranked memory for one user.
219
+ - `answer` retrieves memory and produces a grounded answer.
220
+
221
+ The application remains responsible for deciding how retrieved memory is inserted into its final prompt or context.
222
+
223
+ ## Architecture
224
+
225
+ ```mermaid
226
+ flowchart TD
227
+ A[Conversation] --> B[Bounded extraction context]
228
+ B --> C[LLM extraction]
229
+ C --> D[Deterministic writer<br/>ADD / NOOP / SUPERSEDE]
230
+ D --> E[PostgreSQL]
231
+ E --> F[BM25 + FAISS]
232
+ F --> G[Discounted agreement fusion]
233
+ G --> H[Search or grounded answer]
234
+ ```
235
+
236
+ ## Development
237
+
238
+ ```bash
239
+ pip install -e ".[dev]"
240
+ python -m pytest
241
+ python -m compileall -q src tests
242
+ ```
243
+
244
+ Database integration tests run only when `TEST_DATABASE_URL` is configured; they never fall back to `DATABASE_URL`. The core memory, retrieval, CLI, and package paths are covered by automated tests.
245
+
246
+ ## License
247
+
248
+ [MIT](LICENSE)
@@ -0,0 +1,196 @@
1
+ # Memory Layer
2
+
3
+ A Python library for durable, user-scoped long-term memory in LLM applications.
4
+
5
+ Applications send conversations to Memory Layer. It extracts durable memories, updates existing facts deterministically, stores them in PostgreSQL, and retrieves relevant memory with hybrid search. It is memory infrastructure—not a chatbot, agent framework, hosted API, or full context builder.
6
+
7
+ ## Install
8
+
9
+ The intended PyPI installation for the 0.1.0 release is:
10
+
11
+ ```bash
12
+ pip install meminfra
13
+ ```
14
+
15
+ Until the package is published, install from source for development:
16
+
17
+ ```bash
18
+ git clone https://github.com/banalasaisathwik/memory-layer.git
19
+ cd memory-layer
20
+ pip install -e .
21
+ ```
22
+
23
+ ## Quickstart
24
+
25
+ Set the required database and provider configuration, then apply the packaged migrations:
26
+
27
+ ```bash
28
+ meminfra migrate
29
+ ```
30
+
31
+ ```python
32
+ from meminfra import MemoryLayer
33
+ from meminfra.database import SessionLocal
34
+
35
+ db = SessionLocal()
36
+ memory = MemoryLayer(db)
37
+
38
+ memory.add(
39
+ user_id="user-123",
40
+ conversation_id="conversation-1",
41
+ messages=[
42
+ {
43
+ "role": "user",
44
+ "content": "I prefer PostgreSQL for backend projects.",
45
+ }
46
+ ],
47
+ )
48
+
49
+ hits = memory.search(
50
+ user_id="user-123",
51
+ query="What database do I prefer?",
52
+ )
53
+
54
+ for hit in hits:
55
+ print(hit.memory_text)
56
+ ```
57
+
58
+ `add()` persists the messages, extracts candidates, writes durable memory, and may update the conversation summary. It creates the user and conversation scope when needed. `search()` returns ranked `SearchHit` records for one user.
59
+
60
+ To generate an answer grounded in retrieved memory:
61
+
62
+ ```python
63
+ result = memory.answer(
64
+ user_id="user-123",
65
+ query="What database do I prefer?",
66
+ )
67
+
68
+ print(result.answer)
69
+ ```
70
+
71
+ `answer()` retrieves memory before asking the configured LLM to answer from it. If search finds no memory, it abstains without making an answer-generation call.
72
+
73
+ ## What happens when you add memory?
74
+
75
+ Memory Layer stores semantic and episodic memories. Structured, single-value semantic facts are updated deterministically; open semantic and episodic memories use conservative duplicate handling. Persisted memories retain source-message provenance.
76
+
77
+ | Existing state | New statement | Action |
78
+ | --- | --- | --- |
79
+ | No active database preference | “I use PostgreSQL” | ADD |
80
+ | Same active preference | “I use PostgreSQL” | NOOP |
81
+ | Active database preference | “I switched to SQLite” | SUPERSEDE |
82
+
83
+ `SUPERSEDE` applies when a supported single-value structured fact changes. The prior memory stays stored for history and provenance, but default retrieval returns active memories only.
84
+
85
+ Durable memory is user-scoped. Raw-message context is conversation-scoped, while durable memory can be retrieved across that user’s conversations.
86
+
87
+ ## Retrieval
88
+
89
+ Natural-language retrieval combines BM25 lexical search with FAISS vector search, then ranks candidates with discounted agreement fusion:
90
+
91
+ ```text
92
+ BM25 + FAISS vector search → discounted agreement fusion → ranked memories
93
+ ```
94
+
95
+ The default fusion strategy rewards agreement while allowing a strong result from one branch to remain competitive. Equal reciprocal-rank fusion (RRF) remains available for compatibility and ablation, but is not the default. When you supply structured filters, exact structured lookup is used as an additional retrieval branch; it is not inferred from every query.
96
+
97
+ PostgreSQL is the durable source of truth for memories and embeddings. Local FAISS indexes are derived retrieval state and rebuild from PostgreSQL when missing, stale, or corrupt.
98
+
99
+ ## Benchmark
100
+
101
+ Frozen internal LoCoMo results for `conv-30`:
102
+
103
+ | Metric | Baseline | Current |
104
+ | --- | ---: | ---: |
105
+ | Hit@5 | 0.457 | 0.686 |
106
+ | Recall@5 | 0.426 | 0.657 |
107
+ | MRR | 0.376 | 0.595 |
108
+
109
+ LoCoMo · `conv-30` · 105 QA · same evaluation protocol.
110
+
111
+ This is an internal baseline comparison, not a cross-system comparison against another memory product. Hit@5 measures whether relevant memory appears in the top five; Recall@5 measures how much gold evidence appears there; MRR measures how early the first relevant result appears. See [`evals/`](evals/) for the harnesses and adapters.
112
+
113
+ ## Configuration
114
+
115
+ Configure connections and providers with environment variables. Do not commit credentials.
116
+
117
+ | Variable | Purpose |
118
+ | --- | --- |
119
+ | `DATABASE_URL` | PostgreSQL runtime connection |
120
+ | `DIRECT_URL` | Direct PostgreSQL URL for migrations; takes precedence for `meminfra migrate` |
121
+ | `LLM_PROVIDER` | LLM provider (`openai`, `openrouter`, or `openai_compatible`) |
122
+ | `LLM_API_KEY` | LLM provider credential |
123
+ | `LLM_BASE_URL` | Optional custom/OpenAI-compatible LLM endpoint |
124
+ | `LLM_MODEL` | LLM model used for extraction, summaries, and answers |
125
+ | `EMBEDDING_PROVIDER` | Embedding provider |
126
+ | `EMBEDDING_API_KEY` | Embedding provider credential |
127
+ | `EMBEDDING_BASE_URL` | Optional custom/OpenAI-compatible embedding endpoint |
128
+ | `EMBEDDING_MODEL` | Embedding model |
129
+ | `FAISS_INDEX_DIR` | Local directory for derived FAISS indexes |
130
+
131
+ Minimal example:
132
+
133
+ ```env
134
+ DATABASE_URL=postgresql+psycopg://user:password@host/database
135
+ LLM_PROVIDER=openai
136
+ LLM_API_KEY=your-llm-key
137
+ LLM_MODEL=your-llm-model
138
+ EMBEDDING_PROVIDER=openai
139
+ EMBEDDING_API_KEY=your-embedding-key
140
+ EMBEDDING_MODEL=text-embedding-3-small
141
+ ```
142
+
143
+ `LLM_BASE_URL` and `EMBEDDING_BASE_URL` are optional and are useful for custom or OpenAI-compatible endpoints.
144
+
145
+ ## Database setup
146
+
147
+ Run migrations after configuring `DATABASE_URL` (or `DIRECT_URL` when the migration connection should differ):
148
+
149
+ ```bash
150
+ meminfra migrate
151
+ ```
152
+
153
+ This applies the packaged Alembic migrations to the configured PostgreSQL database. For repository development, `python -m alembic upgrade head` also works.
154
+
155
+ ## Public API
156
+
157
+ `MemoryLayer` is the high-level facade:
158
+
159
+ ```python
160
+ MemoryLayer.add(user_id=..., conversation_id=..., messages=...)
161
+ MemoryLayer.search(user_id=..., query=..., limit=10, filters=None)
162
+ MemoryLayer.answer(user_id=..., query=..., limit=5, filters=None)
163
+ ```
164
+
165
+ - `add` persists messages and extracts or updates durable memory.
166
+ - `search` retrieves ranked memory for one user.
167
+ - `answer` retrieves memory and produces a grounded answer.
168
+
169
+ The application remains responsible for deciding how retrieved memory is inserted into its final prompt or context.
170
+
171
+ ## Architecture
172
+
173
+ ```mermaid
174
+ flowchart TD
175
+ A[Conversation] --> B[Bounded extraction context]
176
+ B --> C[LLM extraction]
177
+ C --> D[Deterministic writer<br/>ADD / NOOP / SUPERSEDE]
178
+ D --> E[PostgreSQL]
179
+ E --> F[BM25 + FAISS]
180
+ F --> G[Discounted agreement fusion]
181
+ G --> H[Search or grounded answer]
182
+ ```
183
+
184
+ ## Development
185
+
186
+ ```bash
187
+ pip install -e ".[dev]"
188
+ python -m pytest
189
+ python -m compileall -q src tests
190
+ ```
191
+
192
+ Database integration tests run only when `TEST_DATABASE_URL` is configured; they never fall back to `DATABASE_URL`. The core memory, retrieval, CLI, and package paths are covered by automated tests.
193
+
194
+ ## License
195
+
196
+ [MIT](LICENSE)
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "meminfra"
7
+ version = "0.1.0"
8
+ description = "Foundation for reusable, user-scoped long-term memory infrastructure."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = {file = "LICENSE"}
12
+ keywords = ["memory", "llm", "infrastructure", "postgresql"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ ]
23
+ dependencies = [
24
+ "alembic>=1.13",
25
+ "openai>=1.0",
26
+ "psycopg[binary]>=3.1",
27
+ "pydantic>=2.0",
28
+ "python-dotenv>=1.0",
29
+ "SQLAlchemy>=2.0",
30
+ "faiss-cpu",
31
+ "numpy",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=8.0"]
36
+
37
+ [project.urls]
38
+ Repository = "https://github.com/banalasaisathwik/memory-layer"
39
+ Issues = "https://github.com/banalasaisathwik/memory-layer/issues"
40
+
41
+ [project.scripts]
42
+ meminfra = "meminfra.cli:main"
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-ra"
47
+ markers = [
48
+ "database: requires TEST_DATABASE_URL and never uses DATABASE_URL",
49
+ ]
50
+
51
+ [tool.setuptools]
52
+ package-dir = {"" = "src"}
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.setuptools.package-data]
58
+ meminfra = ["migrations/*.py", "migrations/*.mako", "migrations/versions/*.py"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """Reusable long-term memory infrastructure foundation."""
2
+
3
+ from .memory_layer import AddResult, AnswerError, AnswerResult, MemoryLayer, MemoryLayerError
4
+
5
+ __all__ = [
6
+ "AddResult",
7
+ "AnswerError",
8
+ "AnswerResult",
9
+ "MemoryLayer",
10
+ "MemoryLayerError",
11
+ ]
@@ -0,0 +1,47 @@
1
+ """Small installed-package command line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from importlib import resources
7
+
8
+ from alembic import command
9
+ from alembic.config import Config
10
+
11
+ from meminfra.config import get_migration_database_url
12
+
13
+
14
+ def _parser() -> argparse.ArgumentParser:
15
+ parser = argparse.ArgumentParser(prog="meminfra", description="meminfra package commands")
16
+ parser.add_argument("command", choices=("migrate",), help="command to run")
17
+ return parser
18
+
19
+
20
+ def _migration_config() -> Config:
21
+ """Configure Alembic from the installed migration package, not cwd."""
22
+
23
+ migration_path = resources.files("meminfra").joinpath("migrations")
24
+ config = Config()
25
+ config.set_main_option("script_location", str(migration_path))
26
+ return config
27
+
28
+
29
+ def _migrate() -> None:
30
+ # Validate before Alembic starts so a missing URL is concise and secret-free.
31
+ get_migration_database_url()
32
+ command.upgrade(_migration_config(), "head")
33
+
34
+
35
+ def main() -> None:
36
+ """Run the supported meminfra command."""
37
+
38
+ parser = _parser()
39
+ parser.parse_args()
40
+ try:
41
+ _migrate()
42
+ except RuntimeError as error:
43
+ parser.error(str(error))
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()