lmem 0.2.1__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.
lmem-0.2.1/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /target
2
+ **/*.rs.bk
3
+ *.db
4
+ *.db-wal
5
+ *.db-shm
6
+ .DS_Store
7
+ .fastembed_cache/
8
+ **/.fastembed_cache/
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ *.egg-info/
13
+ /python/dist/
14
+ /python/build/
15
+ .pytest_cache/
lmem-0.2.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pathi Krishna Kanth
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.
lmem-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.5
2
+ Name: lmem
3
+ Version: 0.2.1
4
+ Summary: Ultra-Fast Local AI Agent Persistent Memory Engine with Neural Extractive QA & Knowledge Graph
5
+ Project-URL: Homepage, https://github.com/krishnakanthpathi/lightmem
6
+ Project-URL: Repository, https://github.com/krishnakanthpathi/lightmem
7
+ Project-URL: Issues, https://github.com/krishnakanthpathi/lightmem/issues
8
+ Author-email: Pathi Krishna Kanth <krishnakanthpathi@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,ai,extractive-qa,fastembed,knowledge-graph,llm,memory,onnx,rag,sqlite
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+
28
+ # LightMem (`lmem`) Python SDK
29
+
30
+ Ultra-Fast Local AI Agent Persistent Memory Engine with Neural Extractive QA & Knowledge Graph.
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/lmem.svg)](https://pypi.org/project/lmem/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ ---
36
+
37
+ ## ⚡ Quickstart
38
+
39
+ ### Installation
40
+ ```bash
41
+ pip install lmem
42
+ ```
43
+
44
+ ### Python SDK Usage
45
+
46
+ ```python
47
+ from lmem import LightMem
48
+
49
+ # Initialize vault (defaults to ~/.lightmem/memories.db or custom path)
50
+ lm = LightMem()
51
+
52
+ # 1. Store a memory (auto-infers 14 categories)
53
+ memory = lm.remember(
54
+ "PostgreSQL primary runs on port 5432 with WAL archiving enabled",
55
+ category="fact",
56
+ title="PostgreSQL Primary Config",
57
+ tags=["postgres", "database", "production"],
58
+ confidence=0.95,
59
+ )
60
+ print("Stored memory:", memory.id)
61
+
62
+ # 2. Hybrid Search (FTS5 BM25 + Vector Cosine RRF)
63
+ results = lm.recall("postgres port", limit=5)
64
+ for r in results:
65
+ print(f"[{r.score:.2f}] {r.memory.title}: {r.memory.content}")
66
+
67
+ # 3. Neural Extractive QA (exact fact extraction in ~6ms via ONNX SQuAD2)
68
+ answer = lm.answer("what port does postgresql use?")
69
+ print("Extracted answer:", answer.answer) # "5432"
70
+ print("Confidence:", answer.confidence)
71
+ print("Reranker:", answer.reranker_used)
72
+
73
+ # 4. Knowledge Graph Autolink (title mentions, shared tags, vector similarity)
74
+ link_stats = lm.autolink(min_similarity=0.75)
75
+ print("Connections created:", link_stats)
76
+
77
+ # 5. Multi-Hop Graph Traversal
78
+ related = lm.related(memory.id, hops=2)
79
+ for rel in related:
80
+ print(f"Hop {rel.distance}: {rel.memory.title} via {' -> '.join(rel.relation_path)}")
81
+
82
+ # 6. Deploy Skill to AI Agents (Antigravity, Codex, Hermes, Cursor, Claude)
83
+ lm.connect("antigravity")
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 💻 CLI Commands (Included)
89
+
90
+ The `pip install lmem` package also installs the standalone CLI:
91
+
92
+ ```bash
93
+ lmem remember "Redis cache runs on port 6379" -t fact
94
+ lmem recall "redis port" -l 5
95
+ lmem answer "what is my pan card no"
96
+ lmem autolink
97
+ lmem graph
98
+ lmem connect all
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 📄 License
104
+ MIT License. Created by Pathi Krishna Kanth.
lmem-0.2.1/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # LightMem (`lmem`) Python SDK
2
+
3
+ Ultra-Fast Local AI Agent Persistent Memory Engine with Neural Extractive QA & Knowledge Graph.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/lmem.svg)](https://pypi.org/project/lmem/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## ⚡ Quickstart
11
+
12
+ ### Installation
13
+ ```bash
14
+ pip install lmem
15
+ ```
16
+
17
+ ### Python SDK Usage
18
+
19
+ ```python
20
+ from lmem import LightMem
21
+
22
+ # Initialize vault (defaults to ~/.lightmem/memories.db or custom path)
23
+ lm = LightMem()
24
+
25
+ # 1. Store a memory (auto-infers 14 categories)
26
+ memory = lm.remember(
27
+ "PostgreSQL primary runs on port 5432 with WAL archiving enabled",
28
+ category="fact",
29
+ title="PostgreSQL Primary Config",
30
+ tags=["postgres", "database", "production"],
31
+ confidence=0.95,
32
+ )
33
+ print("Stored memory:", memory.id)
34
+
35
+ # 2. Hybrid Search (FTS5 BM25 + Vector Cosine RRF)
36
+ results = lm.recall("postgres port", limit=5)
37
+ for r in results:
38
+ print(f"[{r.score:.2f}] {r.memory.title}: {r.memory.content}")
39
+
40
+ # 3. Neural Extractive QA (exact fact extraction in ~6ms via ONNX SQuAD2)
41
+ answer = lm.answer("what port does postgresql use?")
42
+ print("Extracted answer:", answer.answer) # "5432"
43
+ print("Confidence:", answer.confidence)
44
+ print("Reranker:", answer.reranker_used)
45
+
46
+ # 4. Knowledge Graph Autolink (title mentions, shared tags, vector similarity)
47
+ link_stats = lm.autolink(min_similarity=0.75)
48
+ print("Connections created:", link_stats)
49
+
50
+ # 5. Multi-Hop Graph Traversal
51
+ related = lm.related(memory.id, hops=2)
52
+ for rel in related:
53
+ print(f"Hop {rel.distance}: {rel.memory.title} via {' -> '.join(rel.relation_path)}")
54
+
55
+ # 6. Deploy Skill to AI Agents (Antigravity, Codex, Hermes, Cursor, Claude)
56
+ lm.connect("antigravity")
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 💻 CLI Commands (Included)
62
+
63
+ The `pip install lmem` package also installs the standalone CLI:
64
+
65
+ ```bash
66
+ lmem remember "Redis cache runs on port 6379" -t fact
67
+ lmem recall "redis port" -l 5
68
+ lmem answer "what is my pan card no"
69
+ lmem autolink
70
+ lmem graph
71
+ lmem connect all
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 📄 License
77
+ MIT License. Created by Pathi Krishna Kanth.
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lmem"
7
+ version = "0.2.1"
8
+ description = "Ultra-Fast Local AI Agent Persistent Memory Engine with Neural Extractive QA & Knowledge Graph"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "Pathi Krishna Kanth", email = "krishnakanthpathi@gmail.com" }
13
+ ]
14
+ requires-python = ">=3.8"
15
+ keywords = [
16
+ "agent",
17
+ "memory",
18
+ "ai",
19
+ "llm",
20
+ "rag",
21
+ "knowledge-graph",
22
+ "extractive-qa",
23
+ "sqlite",
24
+ "onnx",
25
+ "fastembed",
26
+ ]
27
+ classifiers = [
28
+ "Development Status :: 4 - Beta",
29
+ "Intended Audience :: Developers",
30
+ "License :: OSI Approved :: MIT License",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3.8",
33
+ "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
38
+ "Programming Language :: Python :: 3.14",
39
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
40
+ "Topic :: Software Development :: Libraries :: Python Modules",
41
+ ]
42
+ dependencies = []
43
+
44
+ [project.scripts]
45
+ lmem = "lmem.cli:main"
46
+ lightmem = "lmem.cli:main"
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/krishnakanthpathi/lightmem"
50
+ Repository = "https://github.com/krishnakanthpathi/lightmem"
51
+ Issues = "https://github.com/krishnakanthpathi/lightmem/issues"
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/lmem", "src/lightmem"]
55
+
56
+ [tool.hatch.build.targets.sdist]
57
+ include = [
58
+ "/src",
59
+ "/README.md",
60
+ "/LICENSE",
61
+ "/pyproject.toml",
62
+ ]
@@ -0,0 +1,29 @@
1
+ """
2
+ LightMem alias package re-exporting lmem.
3
+ """
4
+
5
+ from lmem import (
6
+ LightMem,
7
+ Memory,
8
+ ScoredMemory,
9
+ AnswerResult,
10
+ RelatedMemory,
11
+ StorageStats,
12
+ PaginatedList,
13
+ MemoryInspection,
14
+ get_binary_path,
15
+ __version__,
16
+ )
17
+
18
+ __all__ = [
19
+ "LightMem",
20
+ "Memory",
21
+ "ScoredMemory",
22
+ "AnswerResult",
23
+ "RelatedMemory",
24
+ "StorageStats",
25
+ "PaginatedList",
26
+ "MemoryInspection",
27
+ "get_binary_path",
28
+ "__version__",
29
+ ]
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561
@@ -0,0 +1,29 @@
1
+ """
2
+ LightMem (lmem): Ultra-Fast Local AI Agent Persistent Memory Engine
3
+ with Neural Extractive QA & Knowledge Graph.
4
+ """
5
+
6
+ from .client import LightMem
7
+ from .models import (
8
+ Memory,
9
+ ScoredMemory,
10
+ AnswerResult,
11
+ RelatedMemory,
12
+ StorageStats,
13
+ PaginatedList,
14
+ MemoryInspection,
15
+ )
16
+ from .binary import get_binary_path
17
+
18
+ __version__ = "0.2.1"
19
+ __all__ = [
20
+ "LightMem",
21
+ "Memory",
22
+ "ScoredMemory",
23
+ "AnswerResult",
24
+ "RelatedMemory",
25
+ "StorageStats",
26
+ "PaginatedList",
27
+ "MemoryInspection",
28
+ "get_binary_path",
29
+ ]
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import sys
4
+ import shutil
5
+ import platform
6
+ import subprocess
7
+ import urllib.request
8
+ import tarfile
9
+ import zipfile
10
+ from pathlib import Path
11
+ from typing import Optional, List, Tuple
12
+
13
+
14
+ def get_binary_path() -> str:
15
+ """
16
+ Locates the `lmem` executable.
17
+ Checks:
18
+ 1. Environment variable `LMEM_BINARY_PATH` or `LIGHTMEM_BINARY_PATH`
19
+ 2. PATH (`which lmem`)
20
+ 3. User local directory `~/.local/bin/lmem` or `~/.cargo/bin/lmem`
21
+ 4. Auto-downloads prebuilt binary to `~/.lightmem/bin/lmem` if missing.
22
+ """
23
+ env_path = os.environ.get("LMEM_BINARY_PATH") or os.environ.get("LIGHTMEM_BINARY_PATH")
24
+ if env_path and os.path.isfile(env_path) and os.access(env_path, os.X_OK):
25
+ return env_path
26
+
27
+ which_path = shutil.which("lmem")
28
+ if which_path:
29
+ return which_path
30
+
31
+ home = Path.home()
32
+ candidate_paths = [
33
+ home / ".local" / "bin" / "lmem",
34
+ home / ".cargo" / "bin" / "lmem",
35
+ home / ".lightmem" / "bin" / "lmem",
36
+ home / ".lightmem" / "bin" / "lmem.exe",
37
+ ]
38
+
39
+ for p in candidate_paths:
40
+ if p.is_file() and os.access(str(p), os.X_OK):
41
+ return str(p)
42
+
43
+ # If not found, attempt to auto-download to ~/.lightmem/bin/lmem
44
+ try:
45
+ downloaded = ensure_binary_downloaded()
46
+ if downloaded and os.path.isfile(downloaded) and os.access(downloaded, os.X_OK):
47
+ return downloaded
48
+ except Exception as e:
49
+ pass
50
+
51
+ raise FileNotFoundError(
52
+ "Could not find the 'lmem' native binary on this system. "
53
+ "Install it via:\n"
54
+ " curl -fsSL https://raw.githubusercontent.com/krishnakanthpathi/lightmem/neural-reranker/install.sh | sh\n"
55
+ "Or set the LMEM_BINARY_PATH environment variable."
56
+ )
57
+
58
+
59
+ def ensure_binary_downloaded(version: str = "v0.2.1") -> str:
60
+ """Downloads pre-built release binary for current system into ~/.lightmem/bin/lmem."""
61
+ system = platform.system().lower()
62
+ machine = platform.machine().lower()
63
+
64
+ if system == "darwin":
65
+ target = "aarch64-apple-darwin" if machine in ["arm64", "aarch64"] else "x86_64-apple-darwin"
66
+ archive_name = f"lmem-{target}.tar.gz"
67
+ exe_name = "lmem"
68
+ elif system == "linux":
69
+ target = "aarch64-unknown-linux-gnu" if machine in ["arm64", "aarch64"] else "x86_64-unknown-linux-gnu"
70
+ archive_name = f"lmem-{target}.tar.gz"
71
+ exe_name = "lmem"
72
+ elif system == "windows":
73
+ target = "x86_64-pc-windows-msvc"
74
+ archive_name = f"lmem-{target}.zip"
75
+ exe_name = "lmem.exe"
76
+ else:
77
+ raise RuntimeError(f"Unsupported OS: {system}")
78
+
79
+ dest_dir = Path.home() / ".lightmem" / "bin"
80
+ dest_dir.mkdir(parents=True, exist_ok=True)
81
+ dest_exe = dest_dir / exe_name
82
+
83
+ if dest_exe.is_file() and os.access(str(dest_exe), os.X_OK):
84
+ return str(dest_exe)
85
+
86
+ url = f"https://github.com/krishnakanthpathi/lightmem/releases/download/{version}/{archive_name}"
87
+ tmp_archive = dest_dir / archive_name
88
+
89
+ try:
90
+ urllib.request.urlretrieve(url, tmp_archive)
91
+ if archive_name.endswith(".zip"):
92
+ with zipfile.ZipFile(tmp_archive, "r") as z:
93
+ z.extractall(dest_dir)
94
+ else:
95
+ with tarfile.open(tmp_archive, "r:gz") as t:
96
+ t.extractall(dest_dir)
97
+
98
+ tmp_archive.unlink(missing_ok=True)
99
+ dest_exe.chmod(0o755)
100
+ return str(dest_exe)
101
+ except Exception as e:
102
+ tmp_archive.unlink(missing_ok=True)
103
+ raise RuntimeError(f"Failed to auto-download lmem binary from {url}: {e}")
104
+
105
+
106
+ def execute_lmem(args: List[str], input_text: Optional[str] = None) -> Tuple[int, str, str]:
107
+ binary = get_binary_path()
108
+ proc = subprocess.Popen(
109
+ [binary] + args,
110
+ stdin=subprocess.PIPE if input_text is not None else None,
111
+ stdout=subprocess.PIPE,
112
+ stderr=subprocess.PIPE,
113
+ text=True,
114
+ )
115
+ stdout, stderr = proc.communicate(input=input_text)
116
+ return proc.returncode, stdout, stderr
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+ import sys
3
+ import subprocess
4
+ from .binary import get_binary_path
5
+
6
+
7
+ def main() -> None:
8
+ """CLI entrypoint for lmem Python package."""
9
+ try:
10
+ binary = get_binary_path()
11
+ except FileNotFoundError as e:
12
+ sys.stderr.write(f"Error: {e}\n")
13
+ sys.exit(1)
14
+
15
+ result = subprocess.call([binary] + sys.argv[1:])
16
+ sys.exit(result)
17
+
18
+
19
+ if __name__ == "__main__":
20
+ main()
@@ -0,0 +1,269 @@
1
+ from __future__ import annotations
2
+ import json
3
+ from typing import List, Optional, Dict, Any, Tuple
4
+ from pathlib import Path
5
+
6
+ from .binary import execute_lmem, get_binary_path
7
+ from .models import (
8
+ Memory,
9
+ ScoredMemory,
10
+ AnswerResult,
11
+ RelatedMemory,
12
+ StorageStats,
13
+ PaginatedList,
14
+ MemoryInspection,
15
+ )
16
+
17
+
18
+ class LightMem:
19
+ """
20
+ LightMem: Ultra-Fast Local AI Agent Persistent Memory Engine.
21
+ Provides Pythonic methods for remember, recall, answer, graph, and maintenance.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ db_path: Optional[str | Path] = None,
27
+ global_db: bool = False,
28
+ ):
29
+ self.db_path = str(db_path) if db_path else None
30
+ self.global_db = global_db
31
+
32
+ @property
33
+ def binary_path(self) -> str:
34
+ return get_binary_path()
35
+
36
+ def _build_base_args(self) -> List[str]:
37
+ args: List[str] = []
38
+ if self.global_db:
39
+ args.append("--global")
40
+ elif self.db_path:
41
+ args.extend(["--db", self.db_path])
42
+ return args
43
+
44
+ def _call(self, subcommand: str, extra_args: List[str], input_text: Optional[str] = None) -> Any:
45
+ full_args = self._build_base_args() + [subcommand] + extra_args + ["--json"]
46
+ code, stdout, stderr = execute_lmem(full_args, input_text=input_text)
47
+ if code != 0:
48
+ err = stderr.strip() or stdout.strip()
49
+ raise RuntimeError(f"lmem {subcommand} failed (exit code {code}): {err}")
50
+ try:
51
+ return json.loads(stdout)
52
+ except json.JSONDecodeError as e:
53
+ raise RuntimeError(f"Failed to parse JSON output from lmem {subcommand}: {stdout}") from e
54
+
55
+ # 1. Store
56
+ def remember(
57
+ self,
58
+ content: str,
59
+ category: Optional[str] = None,
60
+ title: Optional[str] = None,
61
+ tags: Optional[List[str]] = None,
62
+ confidence: float = 0.9,
63
+ ttl: Optional[str] = None,
64
+ supersede: bool = False,
65
+ ) -> Memory:
66
+ args = [content]
67
+ if category:
68
+ args.extend(["-t", category])
69
+ if title:
70
+ args.extend(["--title", title])
71
+ if tags:
72
+ args.extend(["--tags", ",".join(tags)])
73
+ if confidence is not None:
74
+ args.extend(["--confidence", str(confidence)])
75
+ if ttl:
76
+ args.extend(["--ttl", ttl])
77
+ if supersede:
78
+ args.append("--supersede")
79
+
80
+ data = self._call("remember", args)
81
+ return Memory.from_dict(data)
82
+
83
+ # 2. Hybrid Recall
84
+ def recall(
85
+ self,
86
+ query: str,
87
+ limit: int = 5,
88
+ category: Optional[str] = None,
89
+ min_similarity: Optional[float] = None,
90
+ multi_hop: bool = False,
91
+ as_of: Optional[str] = None,
92
+ date: Optional[str] = None,
93
+ ) -> List[ScoredMemory]:
94
+ args = [query, "-l", str(limit)]
95
+ if category:
96
+ args.extend(["-t", category])
97
+ if min_similarity is not None:
98
+ args.extend(["--min-similarity", str(min_similarity)])
99
+ if multi_hop:
100
+ args.append("--multi-hop")
101
+ if as_of:
102
+ args.extend(["--as-of", as_of])
103
+ if date:
104
+ args.extend(["--date", date])
105
+
106
+ data = self._call("recall", args)
107
+ if isinstance(data, list):
108
+ return [ScoredMemory.from_dict(item) for item in data]
109
+ return []
110
+
111
+ # 3. Neural Extractive QA Answer
112
+ def answer(
113
+ self,
114
+ question: str,
115
+ category: Optional[str] = None,
116
+ reranker: Optional[str] = None,
117
+ limit: int = 10,
118
+ as_of: Optional[str] = None,
119
+ date: Optional[str] = None,
120
+ ) -> AnswerResult:
121
+ args = [question, "-l", str(limit)]
122
+ if category:
123
+ args.extend(["-t", category])
124
+ if reranker:
125
+ args.extend(["-r", reranker])
126
+ if as_of:
127
+ args.extend(["--as-of", as_of])
128
+ if date:
129
+ args.extend(["--date", date])
130
+
131
+ data = self._call("answer", args)
132
+ return AnswerResult.from_dict(data)
133
+
134
+ # 4. List
135
+ def list(
136
+ self,
137
+ page: Optional[int] = None,
138
+ limit: int = 20,
139
+ offset: int = 0,
140
+ category: Optional[str] = None,
141
+ status: str = "active",
142
+ as_of: Optional[str] = None,
143
+ date: Optional[str] = None,
144
+ ) -> PaginatedList:
145
+ args = ["-l", str(limit), "--status", status]
146
+ if page is not None:
147
+ args.extend(["--page", str(page)])
148
+ elif offset > 0:
149
+ args.extend(["--offset", str(offset)])
150
+ if category:
151
+ args.extend(["-t", category])
152
+ if as_of:
153
+ args.extend(["--as-of", as_of])
154
+ if date:
155
+ args.extend(["--date", date])
156
+
157
+ data = self._call("list", args)
158
+ items = [Memory.from_dict(item) for item in data.get("items", [])]
159
+ return PaginatedList(
160
+ items=items,
161
+ total=int(data.get("total", len(items))),
162
+ limit=int(data.get("limit", limit)),
163
+ offset=int(data.get("offset", offset)),
164
+ page=int(data.get("page", page or 1)),
165
+ total_pages=int(data.get("total_pages", 1)),
166
+ has_more=bool(data.get("has_more", False)),
167
+ )
168
+
169
+ # 5. Get Single Memory
170
+ def get(self, memory_id: str) -> Optional[Memory]:
171
+ try:
172
+ data = self._call("get", [memory_id])
173
+ return Memory.from_dict(data) if data else None
174
+ except RuntimeError:
175
+ return None
176
+
177
+ # 5b. Inspect Memory & Graph Links
178
+ def inspect(self, memory_id: str) -> Optional[MemoryInspection]:
179
+ try:
180
+ data = self._call("inspect", [memory_id])
181
+ return MemoryInspection.from_dict(data) if data else None
182
+ except RuntimeError:
183
+ return None
184
+
185
+ # 6. Forget (Soft or Hard)
186
+ def forget(self, memory_id: str, hard: bool = False) -> bool:
187
+ args = [memory_id]
188
+ if hard:
189
+ args.append("--hard")
190
+ data = self._call("forget", args)
191
+ return bool(data.get("success", False))
192
+
193
+ # 7. Deduplicate & Merge
194
+ def deduplicate(self) -> int:
195
+ data = self._call("dedup", [])
196
+ return int(data.get("merged", 0))
197
+
198
+ # 8. Nearest-Neighbor Conflicts
199
+ def conflicts(
200
+ self,
201
+ yes: bool = False,
202
+ min_similarity: float = 0.78,
203
+ reranker: Optional[str] = None,
204
+ ) -> List[Dict[str, Any]]:
205
+ args = ["--min-similarity", str(min_similarity)]
206
+ if yes:
207
+ args.append("--yes")
208
+ if reranker:
209
+ args.extend(["-r", reranker])
210
+ data = self._call("conflicts", args)
211
+ return data if isinstance(data, list) else []
212
+
213
+ # 9. Knowledge Graph Links
214
+ def link(
215
+ self,
216
+ source: str,
217
+ target: str,
218
+ relation: str = "relates_to",
219
+ weight: float = 1.0,
220
+ ) -> Tuple[str, str]:
221
+ args = [source, target, "-r", relation, "-w", str(weight)]
222
+ data = self._call("link", args)
223
+ return (data.get("source", source), data.get("target", target))
224
+
225
+ def unlink(self, source: str, target: str, relation: Optional[str] = None) -> bool:
226
+ args = [source, target]
227
+ if relation:
228
+ args.extend(["-r", relation])
229
+ data = self._call("unlink", args)
230
+ return bool(data.get("removed", False))
231
+
232
+ # 10. Multi-Hop Graph Traversal
233
+ def related(self, memory_id: str, hops: int = 2) -> List[RelatedMemory]:
234
+ data = self._call("related", [memory_id, "-n", str(hops)])
235
+ if isinstance(data, list):
236
+ return [RelatedMemory.from_dict(item) for item in data]
237
+ return []
238
+
239
+ # 11. Graph Snapshot
240
+ def graph(self, focus: Optional[str] = None, hops: int = 2) -> Dict[str, Any]:
241
+ args = ["-n", str(hops)]
242
+ if focus:
243
+ args.extend(["-f", focus])
244
+ return self._call("graph", args)
245
+
246
+ # 12. Autolink Vault
247
+ def autolink(self, min_similarity: float = 0.75) -> Dict[str, Any]:
248
+ return self._call("autolink", ["--min-similarity", str(min_similarity)])
249
+
250
+ # 13. Connect AI Agent Skills
251
+ def connect(
252
+ self,
253
+ platform: Optional[str] = None,
254
+ workspace: bool = False,
255
+ path: Optional[str] = None,
256
+ ) -> List[Dict[str, Any]]:
257
+ args = []
258
+ if platform:
259
+ args.append(platform)
260
+ if workspace:
261
+ args.append("--workspace")
262
+ if path:
263
+ args.extend(["--path", path])
264
+ return self._call("connect", args)
265
+
266
+ # 14. Vault Telemetry & Stats
267
+ def stats(self) -> StorageStats:
268
+ data = self._call("stats", [])
269
+ return StorageStats.from_dict(data)
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Optional, Any, Dict
4
+
5
+
6
+ @dataclass
7
+ class Memory:
8
+ id: str
9
+ category: str
10
+ title: str
11
+ content: str
12
+ tags: List[str] = field(default_factory=list)
13
+ confidence: float = 0.9
14
+ status: str = "active"
15
+ provenance: str = "explicit_statement"
16
+ created_at: str = ""
17
+ updated_at: str = ""
18
+ expired_at: Optional[str] = None
19
+
20
+ @classmethod
21
+ def from_dict(cls, data: Dict[str, Any]) -> Memory:
22
+ mem_data = data.get("memory", data)
23
+ return cls(
24
+ id=mem_data.get("id", ""),
25
+ category=mem_data.get("category", "fact"),
26
+ title=mem_data.get("title", ""),
27
+ content=mem_data.get("content", ""),
28
+ tags=mem_data.get("tags", []),
29
+ confidence=float(mem_data.get("confidence", 0.9)),
30
+ status=mem_data.get("status", "active"),
31
+ provenance=mem_data.get("provenance", "explicit_statement"),
32
+ created_at=mem_data.get("created_at", ""),
33
+ updated_at=mem_data.get("updated_at", ""),
34
+ expired_at=mem_data.get("expired_at"),
35
+ )
36
+
37
+
38
+ @dataclass
39
+ class MemoryInspection:
40
+ memory: Memory
41
+ links: List[Dict[str, Any]]
42
+
43
+ @classmethod
44
+ def from_dict(cls, data: Dict[str, Any]) -> MemoryInspection:
45
+ return cls(
46
+ memory=Memory.from_dict(data.get("memory", data)),
47
+ links=data.get("links", []),
48
+ )
49
+
50
+
51
+ @dataclass
52
+ class ScoredMemory:
53
+ memory: Memory
54
+ score: float
55
+ vector_rank: Optional[int] = None
56
+ bm25_rank: Optional[int] = None
57
+
58
+ @classmethod
59
+ def from_dict(cls, data: Dict[str, Any]) -> ScoredMemory:
60
+ mem_data = data.get("memory", data)
61
+ return cls(
62
+ memory=Memory.from_dict(mem_data),
63
+ score=float(data.get("score", 0.0)),
64
+ vector_rank=data.get("vector_rank"),
65
+ bm25_rank=data.get("bm25_rank"),
66
+ )
67
+
68
+
69
+ @dataclass
70
+ class AnswerResult:
71
+ answer: str
72
+ confidence: float
73
+ reranker_used: str
74
+ selected_memory: Optional[Memory] = None
75
+
76
+ @property
77
+ def evidence(self) -> Optional[str]:
78
+ return self.selected_memory.content if self.selected_memory else None
79
+
80
+ @classmethod
81
+ def from_dict(cls, data: Dict[str, Any]) -> AnswerResult:
82
+ selected = data.get("selected_memory")
83
+ return cls(
84
+ answer=data.get("answer", ""),
85
+ confidence=float(data.get("confidence", 0.0)),
86
+ reranker_used=data.get("reranker_used", ""),
87
+ selected_memory=Memory.from_dict(selected) if selected else None,
88
+ )
89
+
90
+
91
+ @dataclass
92
+ class RelatedMemory:
93
+ memory: Memory
94
+ distance: int
95
+ relation_path: List[str]
96
+ score: float
97
+
98
+ @classmethod
99
+ def from_dict(cls, data: Dict[str, Any]) -> RelatedMemory:
100
+ mem_data = data.get("memory", {})
101
+ return cls(
102
+ memory=Memory.from_dict(mem_data),
103
+ distance=int(data.get("distance", 1)),
104
+ relation_path=data.get("relation_path", []),
105
+ score=float(data.get("score", 1.0)),
106
+ )
107
+
108
+
109
+ @dataclass
110
+ class StorageStats:
111
+ total_memories: int
112
+ active_memories: int
113
+ expired_memories: int
114
+ vector_count: int
115
+ categories: Dict[str, int]
116
+ storage_size_bytes: int
117
+ db_path: str
118
+ backend: Optional[str] = None
119
+ embedding_model: Optional[str] = None
120
+ reranker: Optional[str] = None
121
+
122
+ @classmethod
123
+ def from_dict(cls, data: Dict[str, Any]) -> StorageStats:
124
+ return cls(
125
+ total_memories=int(data.get("total_memories", 0)),
126
+ active_memories=int(data.get("active_memories", 0)),
127
+ expired_memories=int(data.get("expired_memories", 0)),
128
+ vector_count=int(data.get("vector_count", 0)),
129
+ categories=data.get("categories", {}),
130
+ storage_size_bytes=int(data.get("storage_size_bytes", 0)),
131
+ db_path=data.get("db_path", ""),
132
+ backend=data.get("backend"),
133
+ embedding_model=data.get("embedding_model"),
134
+ reranker=data.get("reranker"),
135
+ )
136
+
137
+
138
+ class PaginatedList(list):
139
+ """A list of items that also carries pagination metadata (total, page, etc.)."""
140
+ def __init__(
141
+ self,
142
+ items: List[Memory],
143
+ total: int = 0,
144
+ limit: int = 20,
145
+ offset: int = 0,
146
+ page: int = 1,
147
+ total_pages: int = 1,
148
+ has_more: bool = False,
149
+ ):
150
+ super().__init__(items)
151
+ self.total = total
152
+ self.limit = limit
153
+ self.offset = offset
154
+ self.page = page
155
+ self.total_pages = total_pages
156
+ self.has_more = has_more
157
+
158
+ @property
159
+ def items(self) -> List[Memory]:
160
+ return list(self)
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561