openmem-engine 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 (50) hide show
  1. openmem_engine-0.1.0/.github/workflows/docs.yml +46 -0
  2. openmem_engine-0.1.0/.github/workflows/publish.yml +28 -0
  3. openmem_engine-0.1.0/.gitignore +10 -0
  4. openmem_engine-0.1.0/PKG-INFO +166 -0
  5. openmem_engine-0.1.0/README.md +139 -0
  6. openmem_engine-0.1.0/plugin/.claude-plugin/plugin.json +9 -0
  7. openmem_engine-0.1.0/plugin/.mcp.json +9 -0
  8. openmem_engine-0.1.0/plugin/servers/formatting.py +69 -0
  9. openmem_engine-0.1.0/plugin/servers/openmem_server.py +209 -0
  10. openmem_engine-0.1.0/plugin/skills/recall/SKILL.md +31 -0
  11. openmem_engine-0.1.0/plugin/skills/status/SKILL.md +25 -0
  12. openmem_engine-0.1.0/plugin/skills/store/SKILL.md +41 -0
  13. openmem_engine-0.1.0/pyproject.toml +41 -0
  14. openmem_engine-0.1.0/src/openmem/__init__.py +4 -0
  15. openmem_engine-0.1.0/src/openmem/activation.py +35 -0
  16. openmem_engine-0.1.0/src/openmem/conflict.py +59 -0
  17. openmem_engine-0.1.0/src/openmem/engine.py +182 -0
  18. openmem_engine-0.1.0/src/openmem/models.py +40 -0
  19. openmem_engine-0.1.0/src/openmem/scoring.py +118 -0
  20. openmem_engine-0.1.0/src/openmem/store.py +206 -0
  21. openmem_engine-0.1.0/tests/test_activation.py +63 -0
  22. openmem_engine-0.1.0/tests/test_engine.py +160 -0
  23. openmem_engine-0.1.0/tests/test_scoring.py +101 -0
  24. openmem_engine-0.1.0/tests/test_store.py +141 -0
  25. openmem_engine-0.1.0/uv.lock +861 -0
  26. openmem_engine-0.1.0/web/.gitignore +20 -0
  27. openmem_engine-0.1.0/web/README.md +41 -0
  28. openmem_engine-0.1.0/web/docs/api.md +267 -0
  29. openmem_engine-0.1.0/web/docs/claude-code.md +171 -0
  30. openmem_engine-0.1.0/web/docs/configuration.md +129 -0
  31. openmem_engine-0.1.0/web/docs/integration.md +187 -0
  32. openmem_engine-0.1.0/web/docs/intro.md +60 -0
  33. openmem_engine-0.1.0/web/docs/quickstart.md +117 -0
  34. openmem_engine-0.1.0/web/docs/retrieval.md +173 -0
  35. openmem_engine-0.1.0/web/docusaurus.config.ts +99 -0
  36. openmem_engine-0.1.0/web/package-lock.json +19706 -0
  37. openmem_engine-0.1.0/web/package.json +48 -0
  38. openmem_engine-0.1.0/web/sidebars.ts +15 -0
  39. openmem_engine-0.1.0/web/src/css/custom.css +22 -0
  40. openmem_engine-0.1.0/web/src/pages/index.module.css +25 -0
  41. openmem_engine-0.1.0/web/src/pages/index.tsx +72 -0
  42. openmem_engine-0.1.0/web/static/.nojekyll +0 -0
  43. openmem_engine-0.1.0/web/static/img/docusaurus-social-card.jpg +0 -0
  44. openmem_engine-0.1.0/web/static/img/docusaurus.png +0 -0
  45. openmem_engine-0.1.0/web/static/img/favicon.ico +0 -0
  46. openmem_engine-0.1.0/web/static/img/logo.svg +1 -0
  47. openmem_engine-0.1.0/web/static/img/undraw_docusaurus_mountain.svg +171 -0
  48. openmem_engine-0.1.0/web/static/img/undraw_docusaurus_react.svg +170 -0
  49. openmem_engine-0.1.0/web/static/img/undraw_docusaurus_tree.svg +40 -0
  50. openmem_engine-0.1.0/web/tsconfig.json +8 -0
@@ -0,0 +1,46 @@
1
+ name: Deploy docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "web/**"
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ deploy:
21
+ runs-on: ubuntu-latest
22
+ environment:
23
+ name: github-pages
24
+ url: ${{ steps.deployment.outputs.page_url }}
25
+ defaults:
26
+ run:
27
+ working-directory: web
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - uses: actions/setup-node@v4
32
+ with:
33
+ node-version: 20
34
+ cache: npm
35
+ cache-dependency-path: web/package-lock.json
36
+
37
+ - run: npm ci
38
+
39
+ - run: npm run build
40
+
41
+ - uses: actions/upload-pages-artifact@v4
42
+ with:
43
+ path: web/build
44
+
45
+ - id: deployment
46
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install build tools
22
+ run: pip install build
23
+
24
+ - name: Build package
25
+ run: python -m build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
8
+ web/node_modules/
9
+ web/build/
10
+ web/.docusaurus/
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: openmem-engine
3
+ Version: 0.1.0
4
+ Summary: Cognitive memory engine for AI agents — human-inspired retrieval via activation, competition, and reconstruction
5
+ Project-URL: Homepage, https://github.com/dunkinfrunkin/OpenMem
6
+ Project-URL: Documentation, https://dunkinfrunkin.github.io/OpenMem/
7
+ Project-URL: Repository, https://github.com/dunkinfrunkin/OpenMem
8
+ Project-URL: Issues, https://github.com/dunkinfrunkin/OpenMem/issues
9
+ Author: OpenMem
10
+ License-Expression: MIT
11
+ Keywords: agents,ai,cognitive,llm,memory,sqlite
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp>=1.0; extra == 'mcp'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # OpenMem
29
+
30
+ Deterministic memory engine for AI agents. Retrieves context via BM25 lexical search, graph-based spreading activation, and human-inspired competition scoring. SQLite-backed, zero dependencies.
31
+
32
+ ## How it works
33
+
34
+ ```
35
+ Query → FTS5/BM25 (lexical trigger)
36
+ → Seed Activation
37
+ → Spreading Activation (graph edges, max 2 hops)
38
+ → Recency + Strength + Confidence weighting
39
+ → Competition (score-based ranking)
40
+ → Context Pack (token-budgeted output)
41
+ ```
42
+
43
+ No vectors, no embeddings, no LLM in the retrieval loop. The LLM is the consumer, not the retriever.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install openmem-engine
49
+ ```
50
+
51
+ Or from source:
52
+
53
+ ```bash
54
+ git clone https://github.com/yourorg/openmem.git
55
+ cd openmem
56
+ pip install -e ".[dev]"
57
+ ```
58
+
59
+ ## Quick start
60
+
61
+ ```python
62
+ from openmem import MemoryEngine
63
+
64
+ engine = MemoryEngine() # in-memory, or MemoryEngine("memories.db") for persistence
65
+
66
+ # Store memories
67
+ m1 = engine.add("We chose SQLite over Postgres for simplicity", type="decision", entities=["SQLite", "Postgres"])
68
+ m2 = engine.add("Postgres has better concurrent write support", type="fact", entities=["Postgres"])
69
+
70
+ # Link related memories
71
+ engine.link(m1.id, m2.id, "supports")
72
+
73
+ # Recall
74
+ results = engine.recall("Why did we pick SQLite?")
75
+ for r in results:
76
+ print(f"{r.score:.3f} | {r.memory.text}")
77
+ # 0.800 | We chose SQLite over Postgres for simplicity
78
+ # 0.500 | Postgres has better concurrent write support
79
+ ```
80
+
81
+ ## Claude Code plugin
82
+
83
+ Install OpenMem as a Claude Code plugin to get persistent memory across sessions:
84
+
85
+ ```bash
86
+ pip install openmem-engine "mcp>=1.0"
87
+ claude plugin install --path ./plugin
88
+ ```
89
+
90
+ Once installed, you get three slash commands:
91
+
92
+ | Command | Description |
93
+ |---------|-------------|
94
+ | `/openmem:recall` | Recall memories relevant to the current conversation |
95
+ | `/openmem:store` | Store key facts, decisions, and preferences from the conversation |
96
+ | `/openmem:status` | Show memory store statistics |
97
+
98
+ The plugin also registers an MCP server with 7 tools (`memory_store`, `memory_recall`, `memory_link`, `memory_reinforce`, `memory_supersede`, `memory_contradict`, `memory_stats`) that Claude can call automatically.
99
+
100
+ Memories persist in `~/.openmem/memories.db` by default (override with the `OPENMEM_DB` env var).
101
+
102
+ ## Usage with an LLM agent
103
+
104
+ ```python
105
+ engine = MemoryEngine("project.db")
106
+
107
+ # Agent stores what it learns
108
+ engine.add("User prefers TypeScript over JavaScript", type="preference", entities=["TypeScript", "JavaScript"])
109
+ engine.add("Auth system uses JWT with 24h expiry", type="decision", entities=["JWT", "auth"])
110
+ engine.add("The /api/users endpoint returns 500 on empty payload", type="incident", entities=["/api/users"])
111
+
112
+ # Before each LLM call, recall relevant context
113
+ results = engine.recall("set up authentication", top_k=5, token_budget=2000)
114
+ context = "\n".join(r.memory.text for r in results)
115
+
116
+ prompt = f"""Relevant context from previous work:
117
+ {context}
118
+
119
+ User request: {user_message}"""
120
+ ```
121
+
122
+ ## API
123
+
124
+ ### `MemoryEngine(db_path=":memory:", **config)`
125
+
126
+ | Method | Description |
127
+ |--------|-------------|
128
+ | `add(text, type="fact", entities=None, confidence=1.0, gist=None)` | Store a memory |
129
+ | `link(source_id, target_id, rel_type, weight=0.5)` | Create an edge between memories |
130
+ | `recall(query, top_k=5, token_budget=2000)` | Retrieve relevant memories |
131
+ | `reinforce(memory_id)` | Boost a memory's strength |
132
+ | `supersede(old_id, new_id)` | Mark a memory as outdated |
133
+ | `contradict(id_a, id_b)` | Flag two memories as contradicting |
134
+ | `decay_all()` | Run decay pass over all memories |
135
+ | `stats()` | Get summary statistics |
136
+
137
+ ### Memory types
138
+
139
+ `fact` · `decision` · `preference` · `incident` · `plan` · `constraint`
140
+
141
+ ### Edge types
142
+
143
+ `mentions` · `supports` · `contradicts` · `depends_on` · `same_as`
144
+
145
+ ## Retrieval model
146
+
147
+ **Recency** — Exponential decay with ~14-day half-life. Recently accessed memories surface first.
148
+
149
+ **Strength** — Reinforced on access, decays naturally over time. Frequently recalled memories persist.
150
+
151
+ **Spreading activation** — Memories linked by edges activate their neighbors. A query hitting one memory pulls in related context up to 2 hops away.
152
+
153
+ **Competition** — Final score combines activation (50%), recency (20%), strength (20%), and confidence (10%). Superseded memories are penalized 50%, contradicted ones 70%.
154
+
155
+ **Conflict resolution** — When two contradicting memories both activate, the weaker one (by strength × confidence × recency) gets demoted.
156
+
157
+ ## Tests
158
+
159
+ ```bash
160
+ pip install -e ".[dev]"
161
+ pytest tests/ -v
162
+ ```
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,139 @@
1
+ # OpenMem
2
+
3
+ Deterministic memory engine for AI agents. Retrieves context via BM25 lexical search, graph-based spreading activation, and human-inspired competition scoring. SQLite-backed, zero dependencies.
4
+
5
+ ## How it works
6
+
7
+ ```
8
+ Query → FTS5/BM25 (lexical trigger)
9
+ → Seed Activation
10
+ → Spreading Activation (graph edges, max 2 hops)
11
+ → Recency + Strength + Confidence weighting
12
+ → Competition (score-based ranking)
13
+ → Context Pack (token-budgeted output)
14
+ ```
15
+
16
+ No vectors, no embeddings, no LLM in the retrieval loop. The LLM is the consumer, not the retriever.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install openmem-engine
22
+ ```
23
+
24
+ Or from source:
25
+
26
+ ```bash
27
+ git clone https://github.com/yourorg/openmem.git
28
+ cd openmem
29
+ pip install -e ".[dev]"
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ ```python
35
+ from openmem import MemoryEngine
36
+
37
+ engine = MemoryEngine() # in-memory, or MemoryEngine("memories.db") for persistence
38
+
39
+ # Store memories
40
+ m1 = engine.add("We chose SQLite over Postgres for simplicity", type="decision", entities=["SQLite", "Postgres"])
41
+ m2 = engine.add("Postgres has better concurrent write support", type="fact", entities=["Postgres"])
42
+
43
+ # Link related memories
44
+ engine.link(m1.id, m2.id, "supports")
45
+
46
+ # Recall
47
+ results = engine.recall("Why did we pick SQLite?")
48
+ for r in results:
49
+ print(f"{r.score:.3f} | {r.memory.text}")
50
+ # 0.800 | We chose SQLite over Postgres for simplicity
51
+ # 0.500 | Postgres has better concurrent write support
52
+ ```
53
+
54
+ ## Claude Code plugin
55
+
56
+ Install OpenMem as a Claude Code plugin to get persistent memory across sessions:
57
+
58
+ ```bash
59
+ pip install openmem-engine "mcp>=1.0"
60
+ claude plugin install --path ./plugin
61
+ ```
62
+
63
+ Once installed, you get three slash commands:
64
+
65
+ | Command | Description |
66
+ |---------|-------------|
67
+ | `/openmem:recall` | Recall memories relevant to the current conversation |
68
+ | `/openmem:store` | Store key facts, decisions, and preferences from the conversation |
69
+ | `/openmem:status` | Show memory store statistics |
70
+
71
+ The plugin also registers an MCP server with 7 tools (`memory_store`, `memory_recall`, `memory_link`, `memory_reinforce`, `memory_supersede`, `memory_contradict`, `memory_stats`) that Claude can call automatically.
72
+
73
+ Memories persist in `~/.openmem/memories.db` by default (override with the `OPENMEM_DB` env var).
74
+
75
+ ## Usage with an LLM agent
76
+
77
+ ```python
78
+ engine = MemoryEngine("project.db")
79
+
80
+ # Agent stores what it learns
81
+ engine.add("User prefers TypeScript over JavaScript", type="preference", entities=["TypeScript", "JavaScript"])
82
+ engine.add("Auth system uses JWT with 24h expiry", type="decision", entities=["JWT", "auth"])
83
+ engine.add("The /api/users endpoint returns 500 on empty payload", type="incident", entities=["/api/users"])
84
+
85
+ # Before each LLM call, recall relevant context
86
+ results = engine.recall("set up authentication", top_k=5, token_budget=2000)
87
+ context = "\n".join(r.memory.text for r in results)
88
+
89
+ prompt = f"""Relevant context from previous work:
90
+ {context}
91
+
92
+ User request: {user_message}"""
93
+ ```
94
+
95
+ ## API
96
+
97
+ ### `MemoryEngine(db_path=":memory:", **config)`
98
+
99
+ | Method | Description |
100
+ |--------|-------------|
101
+ | `add(text, type="fact", entities=None, confidence=1.0, gist=None)` | Store a memory |
102
+ | `link(source_id, target_id, rel_type, weight=0.5)` | Create an edge between memories |
103
+ | `recall(query, top_k=5, token_budget=2000)` | Retrieve relevant memories |
104
+ | `reinforce(memory_id)` | Boost a memory's strength |
105
+ | `supersede(old_id, new_id)` | Mark a memory as outdated |
106
+ | `contradict(id_a, id_b)` | Flag two memories as contradicting |
107
+ | `decay_all()` | Run decay pass over all memories |
108
+ | `stats()` | Get summary statistics |
109
+
110
+ ### Memory types
111
+
112
+ `fact` · `decision` · `preference` · `incident` · `plan` · `constraint`
113
+
114
+ ### Edge types
115
+
116
+ `mentions` · `supports` · `contradicts` · `depends_on` · `same_as`
117
+
118
+ ## Retrieval model
119
+
120
+ **Recency** — Exponential decay with ~14-day half-life. Recently accessed memories surface first.
121
+
122
+ **Strength** — Reinforced on access, decays naturally over time. Frequently recalled memories persist.
123
+
124
+ **Spreading activation** — Memories linked by edges activate their neighbors. A query hitting one memory pulls in related context up to 2 hops away.
125
+
126
+ **Competition** — Final score combines activation (50%), recency (20%), strength (20%), and confidence (10%). Superseded memories are penalized 50%, contradicted ones 70%.
127
+
128
+ **Conflict resolution** — When two contradicting memories both activate, the weaker one (by strength × confidence × recency) gets demoted.
129
+
130
+ ## Tests
131
+
132
+ ```bash
133
+ pip install -e ".[dev]"
134
+ pytest tests/ -v
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "openmem",
3
+ "version": "0.1.0",
4
+ "description": "Persistent memory for Claude Code across sessions",
5
+ "author": { "name": "OpenMem" },
6
+ "repository": "https://github.com/yourorg/openmem",
7
+ "skills": "./skills/",
8
+ "mcpServers": "./.mcp.json"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "openmem": {
4
+ "command": "python3",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/servers/openmem_server.py"],
6
+ "env": {}
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,69 @@
1
+ """Output formatting helpers for the OpenMem MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from openmem.models import Memory, ScoredMemory
6
+
7
+
8
+ def format_memory(mem: Memory) -> str:
9
+ """Format a single Memory as structured plain text."""
10
+ lines = [
11
+ f"[{mem.id}]",
12
+ f" type: {mem.type}",
13
+ f" status: {mem.status}",
14
+ f" text: {mem.text}",
15
+ ]
16
+ if mem.gist:
17
+ lines.append(f" gist: {mem.gist}")
18
+ if mem.entities:
19
+ lines.append(f" entities: {', '.join(mem.entities)}")
20
+ lines.append(f" strength: {mem.strength:.2f}")
21
+ lines.append(f" confidence: {mem.confidence:.2f}")
22
+ lines.append(f" access_count: {mem.access_count}")
23
+ return "\n".join(lines)
24
+
25
+
26
+ def format_scored_memory(sm: ScoredMemory) -> str:
27
+ """Format a ScoredMemory with its score and components."""
28
+ lines = [
29
+ f"[{sm.memory.id}]",
30
+ f" score: {sm.score:.4f}",
31
+ f" type: {sm.memory.type}",
32
+ f" status: {sm.memory.status}",
33
+ f" text: {sm.memory.text}",
34
+ ]
35
+ if sm.memory.gist:
36
+ lines.append(f" gist: {sm.memory.gist}")
37
+ if sm.memory.entities:
38
+ lines.append(f" entities: {', '.join(sm.memory.entities)}")
39
+ lines.append(f" strength: {sm.memory.strength:.2f}")
40
+ lines.append(f" confidence: {sm.memory.confidence:.2f}")
41
+ if sm.components:
42
+ parts = ", ".join(f"{k}={v:.3f}" for k, v in sm.components.items())
43
+ lines.append(f" components: {parts}")
44
+ return "\n".join(lines)
45
+
46
+
47
+ def format_recall_results(results: list[ScoredMemory]) -> str:
48
+ """Format a list of recall results."""
49
+ if not results:
50
+ return "No memories found."
51
+ sections = [f"Found {len(results)} memories:\n"]
52
+ for i, sm in enumerate(results, 1):
53
+ sections.append(f"--- Result {i} ---")
54
+ sections.append(format_scored_memory(sm))
55
+ sections.append("")
56
+ return "\n".join(sections)
57
+
58
+
59
+ def format_stats(stats: dict) -> str:
60
+ """Format memory store statistics."""
61
+ return "\n".join([
62
+ "Memory Store Statistics:",
63
+ f" Total memories: {stats['memory_count']}",
64
+ f" Active: {stats['active_count']}",
65
+ f" Superseded: {stats['superseded_count']}",
66
+ f" Contradicted: {stats['contradicted_count']}",
67
+ f" Total edges: {stats['edge_count']}",
68
+ f" Average strength: {stats['avg_strength']:.2f}",
69
+ ])
@@ -0,0 +1,209 @@
1
+ """OpenMem MCP server — persistent memory tools for Claude Code.
2
+
3
+ Exposes 7 tools via FastMCP (stdio transport):
4
+ memory_store, memory_recall, memory_link,
5
+ memory_reinforce, memory_supersede, memory_contradict,
6
+ memory_stats
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from mcp.server.fastmcp import FastMCP
16
+
17
+ # Ensure openmem is importable — handle both installed and dev setups
18
+ _repo_root = Path(__file__).resolve().parent.parent.parent
19
+ _src_dir = _repo_root / "src"
20
+ if _src_dir.is_dir() and str(_src_dir) not in sys.path:
21
+ sys.path.insert(0, str(_src_dir))
22
+
23
+ from openmem import MemoryEngine # noqa: E402
24
+
25
+ from formatting import format_memory, format_recall_results, format_stats # noqa: E402
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Initialisation
29
+ # ---------------------------------------------------------------------------
30
+
31
+ DEFAULT_DB = os.path.join(Path.home(), ".openmem", "memories.db")
32
+ db_path = os.environ.get("OPENMEM_DB", DEFAULT_DB)
33
+
34
+ # Ensure the DB directory exists
35
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
36
+
37
+ engine = MemoryEngine(db_path=db_path)
38
+
39
+ # Run decay pass on startup so stale memories lose strength naturally
40
+ engine.decay_all()
41
+
42
+ mcp = FastMCP(
43
+ "openmem",
44
+ description="Persistent cognitive memory for Claude Code",
45
+ )
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Tools
49
+ # ---------------------------------------------------------------------------
50
+
51
+
52
+ @mcp.tool()
53
+ def memory_store(
54
+ text: str,
55
+ type: str = "fact",
56
+ entities: list[str] | None = None,
57
+ confidence: float = 1.0,
58
+ gist: str | None = None,
59
+ ) -> str:
60
+ """Store a new memory.
61
+
62
+ Args:
63
+ text: The content of the memory to store.
64
+ type: Memory type — one of: fact, decision, preference, incident, plan, constraint.
65
+ entities: Key entities mentioned (project names, people, technologies).
66
+ confidence: How certain this memory is, from 0.0 to 1.0.
67
+ gist: Optional one-line summary for quick retrieval.
68
+
69
+ Returns:
70
+ Confirmation with the stored memory's ID and details.
71
+ """
72
+ mem = engine.add(
73
+ text=text,
74
+ type=type,
75
+ entities=entities,
76
+ confidence=confidence,
77
+ gist=gist,
78
+ )
79
+ return f"Stored memory:\n{format_memory(mem)}"
80
+
81
+
82
+ @mcp.tool()
83
+ def memory_recall(
84
+ query: str,
85
+ top_k: int = 5,
86
+ token_budget: int = 2000,
87
+ ) -> str:
88
+ """Recall memories relevant to a query.
89
+
90
+ Uses BM25 lexical search, spreading activation through the memory graph,
91
+ competition scoring, and conflict resolution to find the most relevant memories.
92
+
93
+ Args:
94
+ query: The search query — what you want to remember.
95
+ top_k: Maximum number of memories to return.
96
+ token_budget: Approximate token budget for returned memories.
97
+
98
+ Returns:
99
+ Formatted list of matching memories ranked by relevance.
100
+ """
101
+ results = engine.recall(query=query, top_k=top_k, token_budget=token_budget)
102
+ return format_recall_results(results)
103
+
104
+
105
+ @mcp.tool()
106
+ def memory_link(
107
+ source_id: str,
108
+ target_id: str,
109
+ rel_type: str = "mentions",
110
+ weight: float = 0.5,
111
+ ) -> str:
112
+ """Create a relationship between two memories.
113
+
114
+ Links enable spreading activation — related memories boost each other during recall.
115
+
116
+ Args:
117
+ source_id: ID of the source memory.
118
+ target_id: ID of the target memory.
119
+ rel_type: Relationship type — one of: mentions, supports, contradicts, depends_on, same_as.
120
+ weight: Edge weight from 0.0 to 1.0, controls activation spread strength.
121
+
122
+ Returns:
123
+ Confirmation with the edge details.
124
+ """
125
+ edge = engine.link(
126
+ source_id=source_id,
127
+ target_id=target_id,
128
+ rel_type=rel_type,
129
+ weight=weight,
130
+ )
131
+ return f"Linked memories:\n edge_id: {edge.id}\n {source_id} --[{rel_type}, w={weight}]--> {target_id}"
132
+
133
+
134
+ @mcp.tool()
135
+ def memory_reinforce(memory_id: str) -> str:
136
+ """Reinforce a memory, boosting its strength.
137
+
138
+ Call this when a memory proves useful — it increases strength by 0.1 (up to 1.0)
139
+ and updates access stats. Reinforced memories rank higher in future recalls.
140
+
141
+ Args:
142
+ memory_id: ID of the memory to reinforce.
143
+
144
+ Returns:
145
+ Confirmation of the reinforcement.
146
+ """
147
+ engine.reinforce(memory_id)
148
+ mem = engine.store.get_memory(memory_id)
149
+ if mem:
150
+ return f"Reinforced memory:\n{format_memory(mem)}"
151
+ return f"Memory {memory_id} not found."
152
+
153
+
154
+ @mcp.tool()
155
+ def memory_supersede(old_id: str, new_id: str) -> str:
156
+ """Mark an old memory as superseded by a newer one.
157
+
158
+ The old memory receives a 50% score penalty in future recalls.
159
+ A 'same_as' link is created from the new memory to the old one.
160
+
161
+ Args:
162
+ old_id: ID of the outdated memory.
163
+ new_id: ID of the replacement memory.
164
+
165
+ Returns:
166
+ Confirmation of the supersession.
167
+ """
168
+ engine.supersede(old_id=old_id, new_id=new_id)
169
+ return f"Memory {old_id} superseded by {new_id}."
170
+
171
+
172
+ @mcp.tool()
173
+ def memory_contradict(id_a: str, id_b: str) -> str:
174
+ """Mark two memories as contradicting each other.
175
+
176
+ Creates a 'contradicts' edge. During recall, the weaker memory
177
+ (by strength x confidence x recency) is demoted to 30% of its score.
178
+
179
+ Args:
180
+ id_a: ID of the first memory.
181
+ id_b: ID of the second memory.
182
+
183
+ Returns:
184
+ Confirmation of the contradiction.
185
+ """
186
+ engine.contradict(id_a=id_a, id_b=id_b)
187
+ return f"Memories {id_a} and {id_b} marked as contradicting."
188
+
189
+
190
+ @mcp.tool()
191
+ def memory_stats() -> str:
192
+ """Get summary statistics about the memory store.
193
+
194
+ Returns counts of total, active, superseded, and contradicted memories,
195
+ the number of edges, and average memory strength.
196
+
197
+ Returns:
198
+ Formatted statistics summary.
199
+ """
200
+ stats = engine.stats()
201
+ return format_stats(stats)
202
+
203
+
204
+ # ---------------------------------------------------------------------------
205
+ # Entry point
206
+ # ---------------------------------------------------------------------------
207
+
208
+ if __name__ == "__main__":
209
+ mcp.run(transport="stdio")