typedmem 0.4.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 (65) hide show
  1. typedmem-0.4.0/LICENSE +21 -0
  2. typedmem-0.4.0/PKG-INFO +279 -0
  3. typedmem-0.4.0/README.md +237 -0
  4. typedmem-0.4.0/pyproject.toml +51 -0
  5. typedmem-0.4.0/setup.cfg +4 -0
  6. typedmem-0.4.0/tests/test_cli.py +47 -0
  7. typedmem-0.4.0/tests/test_conflicts.py +134 -0
  8. typedmem-0.4.0/tests/test_embeddings.py +34 -0
  9. typedmem-0.4.0/tests/test_evolvers_contradictions.py +74 -0
  10. typedmem-0.4.0/tests/test_evolvers_drift.py +83 -0
  11. typedmem-0.4.0/tests/test_evolvers_goals.py +88 -0
  12. typedmem-0.4.0/tests/test_evolvers_summary.py +105 -0
  13. typedmem-0.4.0/tests/test_extractor.py +30 -0
  14. typedmem-0.4.0/tests/test_extractor_with_profile.py +104 -0
  15. typedmem-0.4.0/tests/test_jsonl_store.py +58 -0
  16. typedmem-0.4.0/tests/test_llm_clients.py +53 -0
  17. typedmem-0.4.0/tests/test_llm_extractor.py +186 -0
  18. typedmem-0.4.0/tests/test_migration.py +108 -0
  19. typedmem-0.4.0/tests/test_policy.py +32 -0
  20. typedmem-0.4.0/tests/test_profile_loading.py +40 -0
  21. typedmem-0.4.0/tests/test_profiles.py +174 -0
  22. typedmem-0.4.0/tests/test_replace_bookkeeping.py +42 -0
  23. typedmem-0.4.0/tests/test_retriever.py +45 -0
  24. typedmem-0.4.0/tests/test_retriever_semantic.py +53 -0
  25. typedmem-0.4.0/tests/test_schema.py +32 -0
  26. typedmem-0.4.0/tests/test_source.py +58 -0
  27. typedmem-0.4.0/tests/test_sqlite_store.py +54 -0
  28. typedmem-0.4.0/tests/test_store.py +32 -0
  29. typedmem-0.4.0/tests/test_store_with_profile.py +64 -0
  30. typedmem-0.4.0/tests/test_workspaces.py +76 -0
  31. typedmem-0.4.0/typedmem/__init__.py +77 -0
  32. typedmem-0.4.0/typedmem/cli.py +316 -0
  33. typedmem-0.4.0/typedmem/embeddings.py +86 -0
  34. typedmem-0.4.0/typedmem/evolvers/__init__.py +23 -0
  35. typedmem-0.4.0/typedmem/evolvers/base.py +84 -0
  36. typedmem-0.4.0/typedmem/evolvers/contradictions.py +82 -0
  37. typedmem-0.4.0/typedmem/evolvers/drift.py +97 -0
  38. typedmem-0.4.0/typedmem/evolvers/goals.py +127 -0
  39. typedmem-0.4.0/typedmem/evolvers/summary.py +179 -0
  40. typedmem-0.4.0/typedmem/extractor.py +370 -0
  41. typedmem-0.4.0/typedmem/llm/__init__.py +9 -0
  42. typedmem-0.4.0/typedmem/llm/anthropic.py +46 -0
  43. typedmem-0.4.0/typedmem/llm/base.py +11 -0
  44. typedmem-0.4.0/typedmem/llm/fake.py +40 -0
  45. typedmem-0.4.0/typedmem/llm/openai.py +41 -0
  46. typedmem-0.4.0/typedmem/policy.py +160 -0
  47. typedmem-0.4.0/typedmem/profiles/__init__.py +11 -0
  48. typedmem-0.4.0/typedmem/profiles/base.py +208 -0
  49. typedmem-0.4.0/typedmem/profiles/builtins.py +475 -0
  50. typedmem-0.4.0/typedmem/profiles/loaders.py +23 -0
  51. typedmem-0.4.0/typedmem/prompts.py +84 -0
  52. typedmem-0.4.0/typedmem/retriever.py +187 -0
  53. typedmem-0.4.0/typedmem/schema.py +127 -0
  54. typedmem-0.4.0/typedmem/source.py +89 -0
  55. typedmem-0.4.0/typedmem/stores/__init__.py +6 -0
  56. typedmem-0.4.0/typedmem/stores/base.py +198 -0
  57. typedmem-0.4.0/typedmem/stores/jsonl.py +92 -0
  58. typedmem-0.4.0/typedmem/stores/memory.py +37 -0
  59. typedmem-0.4.0/typedmem/stores/sqlite.py +239 -0
  60. typedmem-0.4.0/typedmem.egg-info/PKG-INFO +279 -0
  61. typedmem-0.4.0/typedmem.egg-info/SOURCES.txt +63 -0
  62. typedmem-0.4.0/typedmem.egg-info/dependency_links.txt +1 -0
  63. typedmem-0.4.0/typedmem.egg-info/entry_points.txt +2 -0
  64. typedmem-0.4.0/typedmem.egg-info/requires.txt +21 -0
  65. typedmem-0.4.0/typedmem.egg-info/top_level.txt +1 -0
typedmem-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ruxiz
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,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: typedmem
3
+ Version: 0.4.0
4
+ Summary: Typed, policy-aware, evolving memory layer for AI agents.
5
+ Author: ruxiz
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/canis-minor/typedmem
8
+ Project-URL: Documentation, https://github.com/canis-minor/typedmem#readme
9
+ Project-URL: Repository, https://github.com/canis-minor/typedmem
10
+ Project-URL: Issues, https://github.com/canis-minor/typedmem/issues
11
+ Project-URL: Changelog, https://github.com/canis-minor/typedmem/blob/main/CHANGELOG.md
12
+ Keywords: ai,memory,agents,llm,knowledge,rag,anthropic,openai
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=7; extra == "test"
28
+ Provides-Extra: openai
29
+ Requires-Dist: openai>=1.0; extra == "openai"
30
+ Provides-Extra: anthropic
31
+ Requires-Dist: anthropic>=0.34; extra == "anthropic"
32
+ Provides-Extra: yaml
33
+ Requires-Dist: PyYAML>=6; extra == "yaml"
34
+ Provides-Extra: docs
35
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
36
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
37
+ Provides-Extra: all
38
+ Requires-Dist: openai>=1.0; extra == "all"
39
+ Requires-Dist: anthropic>=0.34; extra == "all"
40
+ Requires-Dist: PyYAML>=6; extra == "all"
41
+ Dynamic: license-file
42
+
43
+ # TypedMemory
44
+
45
+ **Typed, policy-aware, evolving memory layer for AI agents.**
46
+
47
+ [![CI](https://github.com/canis-minor/typedmem/actions/workflows/ci.yml/badge.svg)](https://github.com/canis-minor/typedmem/actions/workflows/ci.yml)
48
+ [![PyPI](https://img.shields.io/pypi/v/typedmem.svg)](https://pypi.org/project/typedmem/)
49
+ [![Python](https://img.shields.io/pypi/pyversions/typedmem.svg)](https://pypi.org/project/typedmem/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
51
+
52
+ TypedMemory is the layer that sits between data and reasoning. Every memory has a **type** (claim, decision, observation, …), a **confidence**, a **structured source**, a **lifecycle policy**, and a **workspace** — not just a string in a vector database. Memories know how to update themselves on conflict, how to decay, and how to be summarized.
53
+
54
+ ```
55
+ ┌──────────────────┐
56
+ │ DomainProfile │ ← schema: which types,
57
+ │ TypeSpec × N │ which policies,
58
+ │ prompt + rules │ which validations
59
+ └────────┬─────────┘
60
+ │
61
+ text ──► Extractor ──► Memory ──┴──► MemoryStore ──► Retriever
62
+ │
63
+ ▼
64
+ Evolver
65
+ (contradictions, drift, goals,
66
+ non-destructive summarization)
67
+ ```
68
+
69
+ **Zero runtime dependencies.** Stdlib only. LLM clients, YAML profile loading, and sentence-transformer-style retrieval are optional extras.
70
+
71
+ ## Why this exists
72
+
73
+ Most "AI memory" libraries are wrappers around a vector database. That's fine for "remember what the user said," but it falls apart the moment you want an agent to:
74
+
75
+ - track **who said what, in which document, at which span** (provenance)
76
+ - handle **the same fact from three sources** without storing it three times (reinforcement)
77
+ - recognize that **a new decision supersedes the old one** without losing the audit trail
78
+ - **summarize stale events** without throwing away the originals
79
+ - **isolate** legal memory from medical memory on the same machine
80
+ - **flag contradictions** instead of silently overwriting them
81
+
82
+ TypedMemory handles these as first-class concepts, not bolt-ons.
83
+
84
+ ## Install
85
+
86
+ ```bash
87
+ pip install typedmem
88
+ ```
89
+
90
+ Optional extras:
91
+
92
+ ```bash
93
+ pip install 'typedmem[anthropic]' # AnthropicClient
94
+ pip install 'typedmem[openai]' # OpenAIClient
95
+ pip install 'typedmem[yaml]' # DomainProfile.from_yaml()
96
+ pip install 'typedmem[all]'
97
+ ```
98
+
99
+ Python 3.10+.
100
+
101
+ ## 60-second demo: an engineering design agent
102
+
103
+ ```python
104
+ import json
105
+ from typedmem import (
106
+ DomainProfile, FakeClient, LLMExtractor, SQLiteMemoryStore,
107
+ )
108
+
109
+ profile = DomainProfile.builtin("engineering_design")
110
+ store = SQLiteMemoryStore.for_profile(profile, "design.db")
111
+
112
+ # Pretend the LLM extracted these from your design docs.
113
+ extractor = LLMExtractor(client=FakeClient([
114
+ json.dumps([
115
+ {"type": "decision", "content": "Use SQLite for storage",
116
+ "subject": "storage_backend", "confidence": 0.9,
117
+ "source": {"document_id": "design_v1.md"}},
118
+ {"type": "risk", "content": "SQLite is single-writer",
119
+ "subject": "storage_backend", "confidence": 0.8,
120
+ "source": {"document_id": "design_v1.md"}},
121
+ ]),
122
+ json.dumps([
123
+ {"type": "decision", "content": "Switch to PostgreSQL for concurrent writes",
124
+ "subject": "storage_backend", "confidence": 0.9,
125
+ "source": {"document_id": "design_v2.md"}},
126
+ {"type": "risk", "content": "Postgres adds an external service",
127
+ "subject": "storage_backend", "confidence": 0.85,
128
+ "source": {"document_id": "design_v2.md"}},
129
+ ]),
130
+ ]), profile=profile)
131
+
132
+ for snippet in ("v1 text", "v2 text"):
133
+ for m in extractor.extract(snippet):
134
+ store.add(m)
135
+
136
+ # decision → SUPERSEDE: old preserved, new active.
137
+ print(store.by_type("decision")) # → just PostgreSQL
138
+ print(store.by_type("decision", include_superseded=True)) # → both
139
+
140
+ # risk → FLAG: two risks on the same subject get cross-linked.
141
+ for cluster in store.contradictions():
142
+ for m in cluster:
143
+ print(m.content) # → both risks
144
+ ```
145
+
146
+ See [`examples/engineering_design_demo.py`](examples/engineering_design_demo.py) for the full version with audit trail and source provenance, or run:
147
+
148
+ ```bash
149
+ typedmem profiles
150
+ typedmem --profile engineering_design add "..." --document-id design_v3.md
151
+ typedmem --profile engineering_design list --type decision
152
+ typedmem evolve --evolver contradictions
153
+ ```
154
+
155
+ ## The mental model
156
+
157
+ | Layer | What it gives you | Examples |
158
+ |---|---|---|
159
+ | **`Memory`** | Typed object with content + confidence + workspace + sources + status | `Memory(type="claim", content=..., sources=[Source(...)])` |
160
+ | **`Source`** | Structured provenance with hashable identity | `(document_id, chunk_id, span)` — dedup key for REINFORCE |
161
+ | **`workspace`** | Namespace on every memory | One agent, multiple corpora, zero cross-contamination |
162
+ | **`ConflictPolicy`** | What to do when a new memory hits the same `(workspace, type, subject)` slot | `REPLACE` · `KEEP_BOTH` · `SUPERSEDE` · `REINFORCE` · `FLAG` · `IGNORE` |
163
+ | **`DomainProfile`** | Schema for a domain: which types, what policy each obeys, what's required | `engineering_design` · `research_paper` · `legal` · `medical_literature` · `personal` · … |
164
+ | **`Evolver`** | Reads memories (not text); produces audit-trailed actions | `ContradictionSurfacer` · `PreferenceDriftDetector` · `GoalResolver` · `SummaryEvolver` |
165
+
166
+ ## Built-in profiles
167
+
168
+ | Profile | Types | Notable policies |
169
+ |---|---|---|
170
+ | `core` | fact, note, goal, task, event | Shared primitives all other profiles can opt into |
171
+ | `personal` | + preference, observation | `preference → REPLACE (60d decay)` |
172
+ | `child_development` | + observation (tagged), milestone, concern | observation tags: language/motor/emotional/cognitive/social |
173
+ | `research_paper` | + claim, method, evidence, limitation, open_question | **evidence → REINFORCE** (multiple papers corroborate) |
174
+ | `engineering_design` | + decision, constraint, risk, assumption, todo | **decision → SUPERSEDE**, **risk → FLAG** |
175
+ | `legal` | + obligation, exception, deadline, definition, citation | **definition → SUPERSEDE** |
176
+ | `medical_literature` | + finding, population, intervention, outcome, limitation | **outcome → REINFORCE** across studies |
177
+
178
+ Custom profiles via Python dataclass, JSON, or YAML.
179
+
180
+ ## Storage
181
+
182
+ Three backends, one ABC:
183
+
184
+ | Store | Persistence | Notes |
185
+ |---|---|---|
186
+ | `InMemoryStore` | None | Default; fastest |
187
+ | `JSONLMemoryStore` | Append-only file | Last-write-wins; tombstones; `compact()` rewrites |
188
+ | `SQLiteMemoryStore` | SQLite file | Indexed on `(workspace, type, subject)`; persists embeddings; auto-migrates v0.2 → v0.4 schemas |
189
+
190
+ ```python
191
+ from typedmem import SQLiteMemoryStore, DomainProfile
192
+
193
+ store = SQLiteMemoryStore.for_profile(
194
+ DomainProfile.builtin("research_paper"),
195
+ path="papers.db",
196
+ )
197
+ ```
198
+
199
+ ## Retrieval
200
+
201
+ ```python
202
+ from typedmem import HashingEmbeddingProvider, Retriever
203
+
204
+ retriever = Retriever(store, embedder=HashingEmbeddingProvider())
205
+ hits = retriever.relevant(
206
+ "blood pressure reduction",
207
+ types=["evidence"],
208
+ workspace="cardiology",
209
+ )
210
+ ```
211
+
212
+ `relevant()` blends three signals: `semantic` (cosine), `recency` (exponential decay), `confidence` (with type-specific half-life). Without an embedder, falls back to token overlap.
213
+
214
+ ## Evolution
215
+
216
+ Evolvers read stored memories and produce auditable actions.
217
+
218
+ ```python
219
+ from typedmem import (
220
+ ContradictionSurfacer, PreferenceDriftDetector,
221
+ GoalResolver, SummaryEvolver,
222
+ HashingEmbeddingProvider, AnthropicClient,
223
+ )
224
+
225
+ # 1. Pure read: walk the FLAG graph.
226
+ for cluster in store.contradictions():
227
+ print(f"{len(cluster)} memories cross-link as contradictions")
228
+
229
+ # 2. Annotation: catch unstable preferences.
230
+ PreferenceDriftDetector(min_replaces=3, window_days=30).evolve(store)
231
+
232
+ # 3. Safe match: dry-run first, then commit.
233
+ embedder = HashingEmbeddingProvider()
234
+ plan = GoalResolver(embedder, threshold=0.85).evolve(store, dry_run=True)
235
+ print(plan.summary())
236
+ GoalResolver(embedder, threshold=0.85).evolve(store) # commit
237
+
238
+ # 4. Non-destructive summary of stale events.
239
+ SummaryEvolver(AnthropicClient(), min_cluster_size=3).evolve(store)
240
+ # Originals untouched; new memory links via metadata["summarizes"].
241
+ ```
242
+
243
+ Every action emits an `EvolutionRecord` (`evolver`, `action`, `input_ids`, `output_ids`, `reason`, `timestamp`) and gets appended to each affected memory's `metadata["evolution_history"]`. No black-box mutations.
244
+
245
+ ## CLI
246
+
247
+ ```bash
248
+ typedmem profiles # list built-in domain profiles
249
+ typedmem --profile research_paper add "..." --document-id paper.pdf
250
+ typedmem --profile engineering_design list --type decision
251
+ typedmem search "blood pressure" --type evidence
252
+ typedmem evolve --evolver contradictions
253
+ typedmem evolve --evolver goals --apply --threshold 0.9 # dry-run by default
254
+ typedmem history MEMORY_ID # audit trail for one memory
255
+ typedmem workspaces
256
+ ```
257
+
258
+ Default store: `~/.typedmem/memories.db` (override with `--store path.db` or `--store path.jsonl`).
259
+
260
+ ## Status & roadmap
261
+
262
+ v0.4 is the first public release.
263
+
264
+ - **v0.5** sentence-transformer embedder, profile composition (`extends`), destructive compaction (`MemoryStore.compact_summaries()`)
265
+ - **v0.6** hybrid BM25+semantic retrieval, query DSL, observability hooks
266
+
267
+ What TypedMemory **doesn't** do and doesn't plan to:
268
+
269
+ - ship document chunkers / loaders — define the `ingest()` seam, bring your own (`unstructured`, `langchain`, plain regex)
270
+ - ship its own vector DB — the abstraction is ready for one, but brute-force cosine wins under ~50k memories
271
+ - pull network dependencies into the default install — every provider is an opt-in extra
272
+
273
+ ## License
274
+
275
+ MIT — see [LICENSE](LICENSE).
276
+
277
+ ## Contributing
278
+
279
+ Issues and PRs welcome. Please run `pytest` and the demos in `examples/` before opening a PR; CI runs them on Python 3.10/3.11/3.12.
@@ -0,0 +1,237 @@
1
+ # TypedMemory
2
+
3
+ **Typed, policy-aware, evolving memory layer for AI agents.**
4
+
5
+ [![CI](https://github.com/canis-minor/typedmem/actions/workflows/ci.yml/badge.svg)](https://github.com/canis-minor/typedmem/actions/workflows/ci.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/typedmem.svg)](https://pypi.org/project/typedmem/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/typedmem.svg)](https://pypi.org/project/typedmem/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+
10
+ TypedMemory is the layer that sits between data and reasoning. Every memory has a **type** (claim, decision, observation, …), a **confidence**, a **structured source**, a **lifecycle policy**, and a **workspace** — not just a string in a vector database. Memories know how to update themselves on conflict, how to decay, and how to be summarized.
11
+
12
+ ```
13
+ ┌──────────────────┐
14
+ │ DomainProfile │ ← schema: which types,
15
+ │ TypeSpec × N │ which policies,
16
+ │ prompt + rules │ which validations
17
+ └────────┬─────────┘
18
+ │
19
+ text ──► Extractor ──► Memory ──┴──► MemoryStore ──► Retriever
20
+ │
21
+ ▼
22
+ Evolver
23
+ (contradictions, drift, goals,
24
+ non-destructive summarization)
25
+ ```
26
+
27
+ **Zero runtime dependencies.** Stdlib only. LLM clients, YAML profile loading, and sentence-transformer-style retrieval are optional extras.
28
+
29
+ ## Why this exists
30
+
31
+ Most "AI memory" libraries are wrappers around a vector database. That's fine for "remember what the user said," but it falls apart the moment you want an agent to:
32
+
33
+ - track **who said what, in which document, at which span** (provenance)
34
+ - handle **the same fact from three sources** without storing it three times (reinforcement)
35
+ - recognize that **a new decision supersedes the old one** without losing the audit trail
36
+ - **summarize stale events** without throwing away the originals
37
+ - **isolate** legal memory from medical memory on the same machine
38
+ - **flag contradictions** instead of silently overwriting them
39
+
40
+ TypedMemory handles these as first-class concepts, not bolt-ons.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install typedmem
46
+ ```
47
+
48
+ Optional extras:
49
+
50
+ ```bash
51
+ pip install 'typedmem[anthropic]' # AnthropicClient
52
+ pip install 'typedmem[openai]' # OpenAIClient
53
+ pip install 'typedmem[yaml]' # DomainProfile.from_yaml()
54
+ pip install 'typedmem[all]'
55
+ ```
56
+
57
+ Python 3.10+.
58
+
59
+ ## 60-second demo: an engineering design agent
60
+
61
+ ```python
62
+ import json
63
+ from typedmem import (
64
+ DomainProfile, FakeClient, LLMExtractor, SQLiteMemoryStore,
65
+ )
66
+
67
+ profile = DomainProfile.builtin("engineering_design")
68
+ store = SQLiteMemoryStore.for_profile(profile, "design.db")
69
+
70
+ # Pretend the LLM extracted these from your design docs.
71
+ extractor = LLMExtractor(client=FakeClient([
72
+ json.dumps([
73
+ {"type": "decision", "content": "Use SQLite for storage",
74
+ "subject": "storage_backend", "confidence": 0.9,
75
+ "source": {"document_id": "design_v1.md"}},
76
+ {"type": "risk", "content": "SQLite is single-writer",
77
+ "subject": "storage_backend", "confidence": 0.8,
78
+ "source": {"document_id": "design_v1.md"}},
79
+ ]),
80
+ json.dumps([
81
+ {"type": "decision", "content": "Switch to PostgreSQL for concurrent writes",
82
+ "subject": "storage_backend", "confidence": 0.9,
83
+ "source": {"document_id": "design_v2.md"}},
84
+ {"type": "risk", "content": "Postgres adds an external service",
85
+ "subject": "storage_backend", "confidence": 0.85,
86
+ "source": {"document_id": "design_v2.md"}},
87
+ ]),
88
+ ]), profile=profile)
89
+
90
+ for snippet in ("v1 text", "v2 text"):
91
+ for m in extractor.extract(snippet):
92
+ store.add(m)
93
+
94
+ # decision → SUPERSEDE: old preserved, new active.
95
+ print(store.by_type("decision")) # → just PostgreSQL
96
+ print(store.by_type("decision", include_superseded=True)) # → both
97
+
98
+ # risk → FLAG: two risks on the same subject get cross-linked.
99
+ for cluster in store.contradictions():
100
+ for m in cluster:
101
+ print(m.content) # → both risks
102
+ ```
103
+
104
+ See [`examples/engineering_design_demo.py`](examples/engineering_design_demo.py) for the full version with audit trail and source provenance, or run:
105
+
106
+ ```bash
107
+ typedmem profiles
108
+ typedmem --profile engineering_design add "..." --document-id design_v3.md
109
+ typedmem --profile engineering_design list --type decision
110
+ typedmem evolve --evolver contradictions
111
+ ```
112
+
113
+ ## The mental model
114
+
115
+ | Layer | What it gives you | Examples |
116
+ |---|---|---|
117
+ | **`Memory`** | Typed object with content + confidence + workspace + sources + status | `Memory(type="claim", content=..., sources=[Source(...)])` |
118
+ | **`Source`** | Structured provenance with hashable identity | `(document_id, chunk_id, span)` — dedup key for REINFORCE |
119
+ | **`workspace`** | Namespace on every memory | One agent, multiple corpora, zero cross-contamination |
120
+ | **`ConflictPolicy`** | What to do when a new memory hits the same `(workspace, type, subject)` slot | `REPLACE` · `KEEP_BOTH` · `SUPERSEDE` · `REINFORCE` · `FLAG` · `IGNORE` |
121
+ | **`DomainProfile`** | Schema for a domain: which types, what policy each obeys, what's required | `engineering_design` · `research_paper` · `legal` · `medical_literature` · `personal` · … |
122
+ | **`Evolver`** | Reads memories (not text); produces audit-trailed actions | `ContradictionSurfacer` · `PreferenceDriftDetector` · `GoalResolver` · `SummaryEvolver` |
123
+
124
+ ## Built-in profiles
125
+
126
+ | Profile | Types | Notable policies |
127
+ |---|---|---|
128
+ | `core` | fact, note, goal, task, event | Shared primitives all other profiles can opt into |
129
+ | `personal` | + preference, observation | `preference → REPLACE (60d decay)` |
130
+ | `child_development` | + observation (tagged), milestone, concern | observation tags: language/motor/emotional/cognitive/social |
131
+ | `research_paper` | + claim, method, evidence, limitation, open_question | **evidence → REINFORCE** (multiple papers corroborate) |
132
+ | `engineering_design` | + decision, constraint, risk, assumption, todo | **decision → SUPERSEDE**, **risk → FLAG** |
133
+ | `legal` | + obligation, exception, deadline, definition, citation | **definition → SUPERSEDE** |
134
+ | `medical_literature` | + finding, population, intervention, outcome, limitation | **outcome → REINFORCE** across studies |
135
+
136
+ Custom profiles via Python dataclass, JSON, or YAML.
137
+
138
+ ## Storage
139
+
140
+ Three backends, one ABC:
141
+
142
+ | Store | Persistence | Notes |
143
+ |---|---|---|
144
+ | `InMemoryStore` | None | Default; fastest |
145
+ | `JSONLMemoryStore` | Append-only file | Last-write-wins; tombstones; `compact()` rewrites |
146
+ | `SQLiteMemoryStore` | SQLite file | Indexed on `(workspace, type, subject)`; persists embeddings; auto-migrates v0.2 → v0.4 schemas |
147
+
148
+ ```python
149
+ from typedmem import SQLiteMemoryStore, DomainProfile
150
+
151
+ store = SQLiteMemoryStore.for_profile(
152
+ DomainProfile.builtin("research_paper"),
153
+ path="papers.db",
154
+ )
155
+ ```
156
+
157
+ ## Retrieval
158
+
159
+ ```python
160
+ from typedmem import HashingEmbeddingProvider, Retriever
161
+
162
+ retriever = Retriever(store, embedder=HashingEmbeddingProvider())
163
+ hits = retriever.relevant(
164
+ "blood pressure reduction",
165
+ types=["evidence"],
166
+ workspace="cardiology",
167
+ )
168
+ ```
169
+
170
+ `relevant()` blends three signals: `semantic` (cosine), `recency` (exponential decay), `confidence` (with type-specific half-life). Without an embedder, falls back to token overlap.
171
+
172
+ ## Evolution
173
+
174
+ Evolvers read stored memories and produce auditable actions.
175
+
176
+ ```python
177
+ from typedmem import (
178
+ ContradictionSurfacer, PreferenceDriftDetector,
179
+ GoalResolver, SummaryEvolver,
180
+ HashingEmbeddingProvider, AnthropicClient,
181
+ )
182
+
183
+ # 1. Pure read: walk the FLAG graph.
184
+ for cluster in store.contradictions():
185
+ print(f"{len(cluster)} memories cross-link as contradictions")
186
+
187
+ # 2. Annotation: catch unstable preferences.
188
+ PreferenceDriftDetector(min_replaces=3, window_days=30).evolve(store)
189
+
190
+ # 3. Safe match: dry-run first, then commit.
191
+ embedder = HashingEmbeddingProvider()
192
+ plan = GoalResolver(embedder, threshold=0.85).evolve(store, dry_run=True)
193
+ print(plan.summary())
194
+ GoalResolver(embedder, threshold=0.85).evolve(store) # commit
195
+
196
+ # 4. Non-destructive summary of stale events.
197
+ SummaryEvolver(AnthropicClient(), min_cluster_size=3).evolve(store)
198
+ # Originals untouched; new memory links via metadata["summarizes"].
199
+ ```
200
+
201
+ Every action emits an `EvolutionRecord` (`evolver`, `action`, `input_ids`, `output_ids`, `reason`, `timestamp`) and gets appended to each affected memory's `metadata["evolution_history"]`. No black-box mutations.
202
+
203
+ ## CLI
204
+
205
+ ```bash
206
+ typedmem profiles # list built-in domain profiles
207
+ typedmem --profile research_paper add "..." --document-id paper.pdf
208
+ typedmem --profile engineering_design list --type decision
209
+ typedmem search "blood pressure" --type evidence
210
+ typedmem evolve --evolver contradictions
211
+ typedmem evolve --evolver goals --apply --threshold 0.9 # dry-run by default
212
+ typedmem history MEMORY_ID # audit trail for one memory
213
+ typedmem workspaces
214
+ ```
215
+
216
+ Default store: `~/.typedmem/memories.db` (override with `--store path.db` or `--store path.jsonl`).
217
+
218
+ ## Status & roadmap
219
+
220
+ v0.4 is the first public release.
221
+
222
+ - **v0.5** sentence-transformer embedder, profile composition (`extends`), destructive compaction (`MemoryStore.compact_summaries()`)
223
+ - **v0.6** hybrid BM25+semantic retrieval, query DSL, observability hooks
224
+
225
+ What TypedMemory **doesn't** do and doesn't plan to:
226
+
227
+ - ship document chunkers / loaders — define the `ingest()` seam, bring your own (`unstructured`, `langchain`, plain regex)
228
+ - ship its own vector DB — the abstraction is ready for one, but brute-force cosine wins under ~50k memories
229
+ - pull network dependencies into the default install — every provider is an opt-in extra
230
+
231
+ ## License
232
+
233
+ MIT — see [LICENSE](LICENSE).
234
+
235
+ ## Contributing
236
+
237
+ Issues and PRs welcome. Please run `pytest` and the demos in `examples/` before opening a PR; CI runs them on Python 3.10/3.11/3.12.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "typedmem"
7
+ version = "0.4.0"
8
+ description = "Typed, policy-aware, evolving memory layer for AI agents."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "ruxiz" }]
13
+ keywords = ["ai", "memory", "agents", "llm", "knowledge", "rag", "anthropic", "openai"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/canis-minor/typedmem"
30
+ Documentation = "https://github.com/canis-minor/typedmem#readme"
31
+ Repository = "https://github.com/canis-minor/typedmem"
32
+ Issues = "https://github.com/canis-minor/typedmem/issues"
33
+ Changelog = "https://github.com/canis-minor/typedmem/blob/main/CHANGELOG.md"
34
+
35
+ [project.optional-dependencies]
36
+ test = ["pytest>=7"]
37
+ openai = ["openai>=1.0"]
38
+ anthropic = ["anthropic>=0.34"]
39
+ yaml = ["PyYAML>=6"]
40
+ docs = ["mkdocs>=1.5", "mkdocs-material>=9.5"]
41
+ all = ["openai>=1.0", "anthropic>=0.34", "PyYAML>=6"]
42
+
43
+ [project.scripts]
44
+ typedmem = "typedmem.cli:main"
45
+
46
+ [tool.setuptools.packages.find]
47
+ include = ["typedmem*"]
48
+ exclude = ["tests*", "examples*", "docs*"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ from pathlib import Path
2
+
3
+ from typedmem.cli import main
4
+
5
+
6
+ def test_add_and_list_and_search(tmp_path: Path, capsys):
7
+ db = tmp_path / "m.db"
8
+
9
+ assert main(["--store", str(db), "add", "Today child said more milk", "--subject", "child"]) == 0
10
+ captured = capsys.readouterr()
11
+ assert "added" in captured.out
12
+
13
+ assert main(["--store", str(db), "list", "--type", "observation"]) == 0
14
+ listed = capsys.readouterr().out
15
+ assert "milk" in listed
16
+
17
+ assert main(["--store", str(db), "search", "milk", "--limit", "3"]) == 0
18
+ found = capsys.readouterr().out
19
+ assert "milk" in found
20
+
21
+
22
+ def test_force_type(tmp_path: Path, capsys):
23
+ db = tmp_path / "m.db"
24
+ assert main([
25
+ "--store", str(db), "add", "ship v0.2", "--type", "goal", "--confidence", "0.9",
26
+ ]) == 0
27
+ out = capsys.readouterr().out
28
+ assert "added 1 memory (goal)" in out
29
+
30
+
31
+ def test_jsonl_store_via_extension(tmp_path: Path, capsys):
32
+ path = tmp_path / "m.jsonl"
33
+ assert main(["--store", str(path), "add", "the sky is blue", "--type", "fact"]) == 0
34
+ assert path.exists()
35
+ assert main(["--store", str(path), "list"]) == 0
36
+ out = capsys.readouterr().out
37
+ assert "sky" in out
38
+
39
+
40
+ def test_compact_jsonl(tmp_path: Path, capsys):
41
+ path = tmp_path / "m.jsonl"
42
+ for v in ("tea", "coffee", "matcha"):
43
+ main(["--store", str(path), "add", f"likes {v}", "--type", "preference", "--subject", "user"])
44
+ capsys.readouterr()
45
+ assert main(["--store", str(path), "compact"]) == 0
46
+ assert "compacted" in capsys.readouterr().out
47
+ assert path.read_text().count("\n") == 1