elfmem 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 (105) hide show
  1. elfmem-0.1.0/.github/workflows/ci.yml +39 -0
  2. elfmem-0.1.0/.github/workflows/publish.yml +60 -0
  3. elfmem-0.1.0/.gitignore +10 -0
  4. elfmem-0.1.0/.python-version +1 -0
  5. elfmem-0.1.0/PKG-INFO +410 -0
  6. elfmem-0.1.0/QUICKSTART.md +229 -0
  7. elfmem-0.1.0/README.md +374 -0
  8. elfmem-0.1.0/SIMULATION_OVERVIEW.md +407 -0
  9. elfmem-0.1.0/START_HERE.md +236 -0
  10. elfmem-0.1.0/alembic/env.py +63 -0
  11. elfmem-0.1.0/alembic/script.py.mako +27 -0
  12. elfmem-0.1.0/alembic.ini +41 -0
  13. elfmem-0.1.0/docs/amgs_architecture.md +749 -0
  14. elfmem-0.1.0/docs/amgs_instructions.md +227 -0
  15. elfmem-0.1.0/docs/coding_principles.md +364 -0
  16. elfmem-0.1.0/docs/notes.md +275 -0
  17. elfmem-0.1.0/docs/plans/step_03_schema_storage.md +767 -0
  18. elfmem-0.1.0/docs/plans/step_04_mock_adapters.md +346 -0
  19. elfmem-0.1.0/docs/plans/step_05_learn_consolidate.md +682 -0
  20. elfmem-0.1.0/docs/plans/step_06_recall_frame.md +785 -0
  21. elfmem-0.1.0/docs/plans/step_07_curate.md +385 -0
  22. elfmem-0.1.0/docs/plans/step_08_real_adapters.md +416 -0
  23. elfmem-0.1.0/docs/plans/step_09_config_factory.md +446 -0
  24. elfmem-0.1.0/docs/prompt_ab_testing.md +638 -0
  25. elfmem-0.1.0/docs/prompt_team_01.md +18 -0
  26. elfmem-0.1.0/docs/testing_principles.md +133 -0
  27. elfmem-0.1.0/pyproject.toml +68 -0
  28. elfmem-0.1.0/sim/EXPLORATIONS.md +1146 -0
  29. elfmem-0.1.0/sim/README.md +278 -0
  30. elfmem-0.1.0/sim/explorations/001_basic_decay.md +177 -0
  31. elfmem-0.1.0/sim/explorations/002_confidence_trap.md +213 -0
  32. elfmem-0.1.0/sim/explorations/003_scoring_walkthrough.md +244 -0
  33. elfmem-0.1.0/sim/explorations/004_self_interest_model.md +395 -0
  34. elfmem-0.1.0/sim/explorations/005_decay_sophistication.md +326 -0
  35. elfmem-0.1.0/sim/explorations/006_self_as_system_prompt.md +751 -0
  36. elfmem-0.1.0/sim/explorations/007_constitutional_self.md +878 -0
  37. elfmem-0.1.0/sim/explorations/008_lifecycle_operations.md +895 -0
  38. elfmem-0.1.0/sim/explorations/009_near_duplicates_and_curate_scheduling.md +568 -0
  39. elfmem-0.1.0/sim/explorations/010_block_anatomy.md +488 -0
  40. elfmem-0.1.0/sim/explorations/011_identifying_self_blocks.md +551 -0
  41. elfmem-0.1.0/sim/explorations/012_self_tag_assignment.md +588 -0
  42. elfmem-0.1.0/sim/explorations/013_edges.md +643 -0
  43. elfmem-0.1.0/sim/explorations/014_edge_types.md +369 -0
  44. elfmem-0.1.0/sim/explorations/015_context_frames_api.md +596 -0
  45. elfmem-0.1.0/sim/explorations/016_custom_frames.md +428 -0
  46. elfmem-0.1.0/sim/explorations/017_storage_layer.md +595 -0
  47. elfmem-0.1.0/sim/explorations/018_duckdb_vs_sqlite.md +347 -0
  48. elfmem-0.1.0/sim/explorations/019_database_tooling.md +795 -0
  49. elfmem-0.1.0/sim/explorations/020_graph_layer.md +532 -0
  50. elfmem-0.1.0/sim/explorations/021_hybrid_retrieval.md +449 -0
  51. elfmem-0.1.0/sim/explorations/022_layer_model.md +557 -0
  52. elfmem-0.1.0/sim/explorations/023_agent_usage.md +625 -0
  53. elfmem-0.1.0/sim/explorations/024_system_refinement.md +954 -0
  54. elfmem-0.1.0/sim/explorations/025_llm_gateway.md +854 -0
  55. elfmem-0.1.0/sim/explorations/026_prompt_overrides.md +626 -0
  56. elfmem-0.1.0/sim/explorations/027_implementation_priority.md +595 -0
  57. elfmem-0.1.0/sim/explorations/_template.md +47 -0
  58. elfmem-0.1.0/sim/playgrounds/README.md +110 -0
  59. elfmem-0.1.0/sim/playgrounds/decay/decay.md +482 -0
  60. elfmem-0.1.0/sim/playgrounds/frames/frames.md +518 -0
  61. elfmem-0.1.0/sim/playgrounds/graph/graph.md +428 -0
  62. elfmem-0.1.0/sim/playgrounds/lifecycle/lifecycle.md +509 -0
  63. elfmem-0.1.0/sim/playgrounds/retrieval/retrieval.md +391 -0
  64. elfmem-0.1.0/sim/playgrounds/scoring/scoring.md +528 -0
  65. elfmem-0.1.0/src/elfmem/__init__.py +5 -0
  66. elfmem-0.1.0/src/elfmem/adapters/__init__.py +0 -0
  67. elfmem-0.1.0/src/elfmem/adapters/litellm.py +171 -0
  68. elfmem-0.1.0/src/elfmem/adapters/mock.py +172 -0
  69. elfmem-0.1.0/src/elfmem/adapters/models.py +38 -0
  70. elfmem-0.1.0/src/elfmem/api.py +367 -0
  71. elfmem-0.1.0/src/elfmem/config.py +187 -0
  72. elfmem-0.1.0/src/elfmem/context/__init__.py +0 -0
  73. elfmem-0.1.0/src/elfmem/context/contradiction.py +65 -0
  74. elfmem-0.1.0/src/elfmem/context/frames.py +123 -0
  75. elfmem-0.1.0/src/elfmem/context/rendering.py +94 -0
  76. elfmem-0.1.0/src/elfmem/db/__init__.py +6 -0
  77. elfmem-0.1.0/src/elfmem/db/engine.py +72 -0
  78. elfmem-0.1.0/src/elfmem/db/models.py +113 -0
  79. elfmem-0.1.0/src/elfmem/db/queries.py +630 -0
  80. elfmem-0.1.0/src/elfmem/memory/__init__.py +0 -0
  81. elfmem-0.1.0/src/elfmem/memory/blocks.py +42 -0
  82. elfmem-0.1.0/src/elfmem/memory/dedup.py +66 -0
  83. elfmem-0.1.0/src/elfmem/memory/graph.py +85 -0
  84. elfmem-0.1.0/src/elfmem/memory/retrieval.py +212 -0
  85. elfmem-0.1.0/src/elfmem/operations/__init__.py +0 -0
  86. elfmem-0.1.0/src/elfmem/operations/consolidate.py +190 -0
  87. elfmem-0.1.0/src/elfmem/operations/curate.py +144 -0
  88. elfmem-0.1.0/src/elfmem/operations/learn.py +53 -0
  89. elfmem-0.1.0/src/elfmem/operations/recall.py +139 -0
  90. elfmem-0.1.0/src/elfmem/ports/__init__.py +0 -0
  91. elfmem-0.1.0/src/elfmem/ports/services.py +39 -0
  92. elfmem-0.1.0/src/elfmem/prompts.py +79 -0
  93. elfmem-0.1.0/src/elfmem/py.typed +0 -0
  94. elfmem-0.1.0/src/elfmem/scoring.py +145 -0
  95. elfmem-0.1.0/src/elfmem/session.py +87 -0
  96. elfmem-0.1.0/src/elfmem/types.py +93 -0
  97. elfmem-0.1.0/tests/__init__.py +0 -0
  98. elfmem-0.1.0/tests/conftest.py +44 -0
  99. elfmem-0.1.0/tests/test_curate.py +470 -0
  100. elfmem-0.1.0/tests/test_lifecycle.py +505 -0
  101. elfmem-0.1.0/tests/test_mock_adapters.py +417 -0
  102. elfmem-0.1.0/tests/test_retrieval.py +471 -0
  103. elfmem-0.1.0/tests/test_scoring.py +256 -0
  104. elfmem-0.1.0/tests/test_storage.py +621 -0
  105. elfmem-0.1.0/uv.lock +2299 -0
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: true
25
+
26
+ - name: Set up Python ${{ matrix.python-version }}
27
+ run: uv python install ${{ matrix.python-version }}
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --extra dev
31
+
32
+ - name: Lint (ruff)
33
+ run: uv run ruff check src/ tests/
34
+
35
+ - name: Type-check (mypy)
36
+ run: uv run mypy --ignore-missing-imports src/elfmem/
37
+
38
+ - name: Test (pytest)
39
+ run: uv run pytest -q
@@ -0,0 +1,60 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v5
21
+ with:
22
+ enable-cache: true
23
+
24
+ - name: Set up Python
25
+ run: uv python install 3.11
26
+
27
+ - name: Install dependencies and run tests
28
+ run: |
29
+ uv sync --extra dev
30
+ uv run pytest -q
31
+
32
+ - name: Build package
33
+ run: uv build
34
+
35
+ - name: Upload distribution artifact
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ name: Publish to PyPI
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ environment: pypi
46
+ permissions:
47
+ id-token: write # required for OIDC trusted publishing
48
+
49
+ steps:
50
+ - name: Install uv
51
+ uses: astral-sh/setup-uv@v5
52
+
53
+ - name: Download distribution artifact
54
+ uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+
59
+ - name: Publish to PyPI
60
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.12
elfmem-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,410 @@
1
+ Metadata-Version: 2.4
2
+ Name: elfmem
3
+ Version: 0.1.0
4
+ Summary: Self-aware adaptive memory for LLM agents
5
+ Project-URL: Homepage, https://github.com/emson/elfmem
6
+ Project-URL: Repository, https://github.com/emson/elfmem
7
+ Project-URL: Issues, https://github.com/emson/elfmem/issues
8
+ Author: emson
9
+ License: MIT
10
+ Keywords: agents,llm,memory,rag,sqlite
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: aiosqlite>=0.19
21
+ Requires-Dist: alembic>=1.13
22
+ Requires-Dist: greenlet>=3.3.2
23
+ Requires-Dist: instructor>=1.2
24
+ Requires-Dist: litellm>=1.30
25
+ Requires-Dist: numpy>=1.26
26
+ Requires-Dist: pydantic>=2.0
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: sqlalchemy>=2.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.8; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.3; extra == 'dev'
34
+ Requires-Dist: types-pyyaml; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # elfmem
38
+
39
+ **Adaptive, self-aware memory for LLM agents.**
40
+
41
+ elfmem gives your LLM agent a memory that grows, evolves, and forgets — just like a human's. Knowledge that gets used survives; knowledge that doesn't fades away. Identity persists across sessions. Context is always relevant.
42
+
43
+ ```python
44
+ import asyncio
45
+ from elfmem import MemorySystem
46
+
47
+ async def main():
48
+ system = await MemorySystem.from_config("agent.db", {
49
+ "llm": {"model": "claude-sonnet-4-6"},
50
+ "embeddings": {"model": "text-embedding-3-small", "dimensions": 1536},
51
+ })
52
+
53
+ async with system.session():
54
+ # Teach the agent something
55
+ await system.learn("Use Celery with Redis for background tasks in Django.")
56
+ await system.learn("I always explain my reasoning before giving recommendations.")
57
+
58
+ # Retrieve relevant context for a prompt
59
+ identity = await system.frame("self") # Who am I?
60
+ context = await system.frame("attention", # What do I know about this?
61
+ query="background job processing")
62
+
63
+ print(identity.text) # Agent identity, values, style
64
+ print(context.text) # Relevant knowledge, ranked by importance
65
+
66
+ asyncio.run(main())
67
+ ```
68
+
69
+ ## Features
70
+
71
+ - **Adaptive decay** — Knowledge survives when reinforced through use, fades when ignored. Session-aware clock means your agent's memory doesn't decay over weekends.
72
+ - **SELF frame** — Persistent agent identity. Values, style, and constraints survive across sessions with near-permanent decay rates.
73
+ - **Hybrid retrieval** — 4-stage pipeline: pre-filter, vector search, graph expansion, composite scoring. Finds knowledge that's relevant *and* important.
74
+ - **Knowledge graph** — Semantic edges between memory blocks. Co-retrieved knowledge strengthens connections. Graph expansion recovers related-but-not-similar context.
75
+ - **Contradiction detection** — LLM-powered detection of conflicting knowledge. Newer, higher-confidence blocks win.
76
+ - **Near-duplicate resolution** — Detects when new knowledge updates existing knowledge. Old block archived, new block inherits history.
77
+ - **Zero infrastructure** — SQLite backend. No Redis, no Postgres, no vector database. One file, fully portable.
78
+ - **Any LLM provider** — LiteLLM backend supports 100+ providers. Switch from OpenAI to Anthropic to local Ollama with a config change.
79
+
80
+ ## Installation
81
+
82
+ ```bash
83
+ uv add elfmem
84
+ ```
85
+
86
+ Or with pip:
87
+
88
+ ```bash
89
+ pip install elfmem
90
+ ```
91
+
92
+ Requires Python 3.11+.
93
+
94
+ ## How It Works
95
+
96
+ ### The Lifecycle
97
+
98
+ Every piece of knowledge follows the same path:
99
+
100
+ ```
101
+ learn() → Instant ingestion. Content-hash dedup. No API calls.
102
+ consolidate() → Batch processing. Embeddings, self-alignment scoring,
103
+ tag inference, near-duplicate detection, graph edges.
104
+ recall() → 4-stage hybrid retrieval. Reinforces returned blocks.
105
+ curate() → Maintenance. Archives decayed blocks, prunes weak edges,
106
+ reinforces top-scoring knowledge.
107
+ ```
108
+
109
+ ### Three Frames
110
+
111
+ Frames are pre-configured retrieval pipelines optimized for different contexts:
112
+
113
+ | Frame | Purpose | Scoring Priority | Use Case |
114
+ |-------|---------|-----------------|----------|
115
+ | **SELF** | Agent identity | Confidence, reinforcement, centrality | System prompt injection |
116
+ | **ATTENTION** | Query-relevant knowledge | Similarity, recency | RAG-style retrieval |
117
+ | **TASK** | Goal-oriented context | Balanced across all signals | Task planning |
118
+
119
+ ```python
120
+ # Identity context — cached, no embedding needed
121
+ self_ctx = await system.frame("self")
122
+
123
+ # Knowledge retrieval — hybrid pipeline with graph expansion
124
+ attn_ctx = await system.frame("attention", query="async error handling")
125
+
126
+ # Task context — balanced scoring, goal blocks guaranteed
127
+ task_ctx = await system.frame("task", query="refactor the API layer")
128
+ ```
129
+
130
+ ### Decay Tiers
131
+
132
+ Knowledge decays at different rates based on its nature:
133
+
134
+ | Tier | Half-life | Use Case |
135
+ |------|-----------|----------|
136
+ | Permanent | ~80,000 hours | Constitutional beliefs, core identity |
137
+ | Durable | ~693 hours | Stable preferences, learned values |
138
+ | Standard | ~69 hours | General knowledge |
139
+ | Ephemeral | ~14 hours | Session observations, temporary facts |
140
+
141
+ Decay is **session-aware**: the clock only ticks during active use. Your agent's memory doesn't degrade over holidays or downtime.
142
+
143
+ ### Composite Scoring
144
+
145
+ Every block is scored across five dimensions:
146
+
147
+ ```
148
+ Score = w_similarity * cosine_similarity(query, block)
149
+ + w_confidence * block.confidence
150
+ + w_recency * exp(-lambda * hours_since_reinforced)
151
+ + w_centrality * normalized_weighted_degree(block)
152
+ + w_reinforcement * log(1 + count) / log(1 + max_count)
153
+ ```
154
+
155
+ Each frame uses different weights. SELF emphasizes confidence and reinforcement. ATTENTION emphasizes similarity and recency.
156
+
157
+ ## Configuration
158
+
159
+ ### Minimal (defaults)
160
+
161
+ ```python
162
+ system = await MemorySystem.from_config("agent.db")
163
+ # Uses claude-sonnet-4-6 for LLM, text-embedding-3-small for embeddings
164
+ # Requires ANTHROPIC_API_KEY environment variable
165
+ ```
166
+
167
+ ### YAML config file
168
+
169
+ ```yaml
170
+ # elfmem.yaml
171
+ llm:
172
+ model: "claude-sonnet-4-6"
173
+ contradiction_model: "claude-opus-4-6" # higher precision for contradictions
174
+
175
+ embeddings:
176
+ model: "text-embedding-3-small"
177
+ dimensions: 1536
178
+
179
+ memory:
180
+ inbox_threshold: 10
181
+ curate_interval_hours: 40
182
+ self_alignment_threshold: 0.70
183
+ prune_threshold: 0.05
184
+ ```
185
+
186
+ ```python
187
+ system = await MemorySystem.from_config("agent.db", "elfmem.yaml")
188
+ ```
189
+
190
+ ### Local models (no API key)
191
+
192
+ ```yaml
193
+ llm:
194
+ model: "ollama/llama3.2"
195
+ base_url: "http://localhost:11434"
196
+
197
+ embeddings:
198
+ model: "ollama/nomic-embed-text"
199
+ dimensions: 768
200
+ base_url: "http://localhost:11434"
201
+ ```
202
+
203
+ ### Environment variables
204
+
205
+ ```bash
206
+ export ANTHROPIC_API_KEY=sk-ant-...
207
+ # or
208
+ export OPENAI_API_KEY=sk-...
209
+ # or any provider LiteLLM supports
210
+ ```
211
+
212
+ API keys are read by LiteLLM from standard environment variables. They never appear in config files.
213
+
214
+ ## Agent Integration Pattern
215
+
216
+ ```python
217
+ async def run_turn(system, user_message):
218
+ # 1. Assemble context
219
+ self_ctx = await system.frame("self")
220
+ attn_ctx = await system.frame("attention", query=user_message)
221
+
222
+ # 2. Build prompt with memory context
223
+ prompt = f"""
224
+ {self_ctx.text}
225
+
226
+ {attn_ctx.text}
227
+
228
+ User: {user_message}
229
+ """
230
+
231
+ # 3. Generate response
232
+ response = await llm.complete(prompt)
233
+
234
+ # 4. Learn from the interaction
235
+ if worth_remembering(response):
236
+ await system.learn(extract_knowledge(response))
237
+
238
+ return response
239
+ ```
240
+
241
+ ## API Reference
242
+
243
+ ### MemorySystem
244
+
245
+ ```python
246
+ # Factory
247
+ system = await MemorySystem.from_config(db_path, config=None)
248
+
249
+ # Session management (required)
250
+ async with system.session():
251
+ ...
252
+
253
+ # Write
254
+ result = await system.learn(content, tags=None, category="knowledge")
255
+
256
+ # Read
257
+ frame_result = await system.frame(name, query=None, top_k=5)
258
+ blocks = await system.recall(name, query=None, top_k=5) # raw, no side effects
259
+
260
+ # Maintenance (usually automatic)
261
+ await system.consolidate() # process inbox → active
262
+ await system.curate() # archive decayed, prune edges, reinforce top-N
263
+ ```
264
+
265
+ ### Return Types
266
+
267
+ ```python
268
+ LearnResult(block_id, status) # "created" | "duplicate_rejected"
269
+ FrameResult(text, blocks, frame_name) # rendered text + scored blocks
270
+ ConsolidateResult(processed, promoted, deduplicated, edges_created)
271
+ CurateResult(archived, edges_pruned, reinforced)
272
+ ```
273
+
274
+ ### Custom Prompts
275
+
276
+ Override the LLM prompts for domain-specific agents:
277
+
278
+ ```yaml
279
+ prompts:
280
+ self_alignment: |
281
+ You are evaluating a memory block for a medical AI assistant...
282
+ {self_context}
283
+ {block}
284
+ Respond: {"score": <float>}
285
+
286
+ valid_self_tags:
287
+ - "self/constitutional"
288
+ - "self/domain/oncology"
289
+ - "self/regulatory/hipaa"
290
+ ```
291
+
292
+ ### Custom Adapters
293
+
294
+ For full control, implement the port protocols directly:
295
+
296
+ ```python
297
+ from elfmem.ports.services import LLMService, EmbeddingService
298
+
299
+ class MyLLMService:
300
+ async def score_self_alignment(self, block: str, self_context: str) -> float: ...
301
+ async def infer_self_tags(self, block: str, self_context: str) -> list[str]: ...
302
+ async def detect_contradiction(self, block_a: str, block_b: str) -> float: ...
303
+
304
+ system = MemorySystem(engine, llm_service=MyLLMService(), embedding_service=MyEmbedder())
305
+ ```
306
+
307
+ ## Architecture
308
+
309
+ ```
310
+ src/elfmem/
311
+ ├── api.py # MemorySystem — public API
312
+ ├── config.py # ElfmemConfig — Pydantic configuration
313
+ ├── scoring.py # Composite scoring formula (frozen)
314
+ ├── types.py # Domain types — shared vocabulary
315
+ ├── prompts.py # LLM prompt templates
316
+ ├── session.py # Session lifecycle, active hours tracking
317
+ ├── ports/
318
+ │ └── services.py # LLMService + EmbeddingService protocols
319
+ ├── adapters/
320
+ │ ├── mock.py # Deterministic mocks for testing
321
+ │ ├── litellm.py # Real adapters (LiteLLM + instructor)
322
+ │ └── models.py # Pydantic response models
323
+ ├── db/
324
+ │ ├── models.py # SQLAlchemy Core table definitions
325
+ │ ├── engine.py # Async engine factory
326
+ │ └── queries.py # All database operations
327
+ ├── memory/
328
+ │ ├── blocks.py # Block state, content hashing, decay tiers
329
+ │ ├── dedup.py # Near-duplicate detection and resolution
330
+ │ ├── graph.py # Centrality, expansion, edge reinforcement
331
+ │ └── retrieval.py # 4-stage hybrid retrieval pipeline
332
+ ├── context/
333
+ │ ├── frames.py # Frame definitions, registry, cache
334
+ │ ├── rendering.py # Blocks → rendered text
335
+ │ └── contradiction.py # Contradiction suppression
336
+ └── operations/
337
+ ├── learn.py # learn() — fast-path ingestion
338
+ ├── consolidate.py # consolidate() — batch promotion
339
+ ├── recall.py # recall() — retrieval + reinforcement
340
+ └── curate.py # curate() — maintenance
341
+ ```
342
+
343
+ **Four layers, clear boundaries:**
344
+
345
+ | Layer | Responsibility | Side Effects |
346
+ |-------|---------------|-------------|
347
+ | **Storage** (db/) | Tables, queries, engine | Database writes |
348
+ | **Memory** (memory/) | Blocks, dedup, graph, retrieval | None (pure) |
349
+ | **Context** (context/) | Frames, rendering, contradictions | None (pure) |
350
+ | **Operations** (operations/) | Orchestration, lifecycle | All side effects |
351
+
352
+ ## Development
353
+
354
+ ```bash
355
+ # Clone
356
+ git clone https://github.com/emson/elfmem.git
357
+ cd elfmem
358
+
359
+ # Install with dev dependencies
360
+ uv sync --extra dev
361
+
362
+ # Run tests (no API key needed — uses deterministic mocks)
363
+ uv run pytest
364
+
365
+ # Type checking
366
+ uv run mypy --ignore-missing-imports src/elfmem/
367
+
368
+ # Lint
369
+ uv run ruff check src/ tests/
370
+ ```
371
+
372
+ ### Testing Philosophy
373
+
374
+ All tests run against deterministic mock services. No API keys, no network calls, fully reproducible. The mock embedding service produces hash-seeded vectors — same input always gives the same embedding. The mock LLM service returns configurable scores and tags via substring matching.
375
+
376
+ ```python
377
+ from elfmem.adapters.mock import make_mock_llm, make_mock_embedding
378
+
379
+ # Control exactly what the LLM returns
380
+ llm = make_mock_llm(
381
+ alignment_overrides={"identity": 0.95},
382
+ tag_overrides={"identity": ["self/value"]},
383
+ )
384
+
385
+ # Control similarity between specific texts
386
+ embedding = make_mock_embedding(
387
+ similarity_overrides={
388
+ frozenset({"cats are great", "dogs are great"}): 0.85,
389
+ },
390
+ )
391
+ ```
392
+
393
+ ## Design Decisions
394
+
395
+ | Decision | Rationale |
396
+ |----------|-----------|
397
+ | SQLAlchemy Core, not ORM | Bulk updates, embedding BLOBs, N+1 centrality queries |
398
+ | Session-aware decay, not wall-clock | Knowledge survives holidays and downtime |
399
+ | Soft bias for identity, not hard gates | Everything is learned; self-aligned knowledge just survives longer |
400
+ | Retrieval is pure; reinforcement is separate | Clean separation of read path and side effects |
401
+ | LiteLLM as unified backend | One adapter for 100+ providers; switch with config |
402
+ | Mock-first testing | All logic verified without API keys; adapters are thin wrappers |
403
+
404
+ ## License
405
+
406
+ MIT
407
+
408
+ ## Acknowledgements
409
+
410
+ elfmem was designed through 26 structured explorations and 6 subsystem playgrounds, building mathematical confidence in every architectural decision before writing code. The complete design documentation is in `sim/explorations/`.