tribalmemory 0.1.1__tar.gz → 0.3.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.
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/PKG-INFO +1 -1
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/pyproject.toml +1 -1
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/cli.py +147 -4
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/interfaces.py +66 -3
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/mcp/server.py +272 -14
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/app.py +53 -2
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/config.py +41 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/models.py +65 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/routes.py +68 -0
- tribalmemory-0.3.0/src/tribalmemory/services/fts_store.py +255 -0
- tribalmemory-0.3.0/src/tribalmemory/services/graph_store.py +627 -0
- tribalmemory-0.3.0/src/tribalmemory/services/memory.py +709 -0
- tribalmemory-0.3.0/src/tribalmemory/services/reranker.py +267 -0
- tribalmemory-0.3.0/src/tribalmemory/services/session_store.py +412 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/services/vector_store.py +86 -1
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/PKG-INFO +1 -1
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/SOURCES.txt +10 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_a21_config.py +2 -2
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_a21_container.py +8 -8
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_a21_providers.py +10 -11
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_a21_system.py +4 -4
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_benchmarks.py +1 -1
- tribalmemory-0.3.0/tests/test_cli.py +428 -0
- tribalmemory-0.3.0/tests/test_graph_aware_recall.py +375 -0
- tribalmemory-0.3.0/tests/test_graph_memory_integration.py +216 -0
- tribalmemory-0.3.0/tests/test_graph_store.py +263 -0
- tribalmemory-0.3.0/tests/test_hybrid_search.py +323 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_mcp_server.py +9 -5
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_negative_security.py +3 -3
- tribalmemory-0.3.0/tests/test_reranking.py +392 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_services.py +7 -7
- tribalmemory-0.3.0/tests/test_session_store.py +429 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_tier1_functional.py +3 -3
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_tier2_capability.py +1 -1
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_tier3_emergence.py +1 -1
- tribalmemory-0.1.1/src/tribalmemory/services/memory.py +0 -275
- tribalmemory-0.1.1/tests/test_cli.py +0 -207
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/LICENSE +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/README.md +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/setup.cfg +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/config/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/config/providers.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/config/system.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/container/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/container/container.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/base.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/deduplication.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/lancedb.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/memory.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/mock.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/openai.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/providers/timestamp.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/a21/system.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/mcp/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/mcp/__main__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/performance/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/performance/benchmarks.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/performance/corpus_generator.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/portability/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/portability/embedding_metadata.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/server/__main__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/services/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/services/deduplication.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/services/embeddings.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/services/import_export.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/__init__.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/embedding_utils.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/fixtures.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/metrics.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/mocks.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/testing/semantic_expansions.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory/utils.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/dependency_links.txt +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/entry_points.txt +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/requires.txt +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/src/tribalmemory.egg-info/top_level.txt +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_embedding_portability.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_import_export.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_local_embeddings.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_mcp_integration.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_memory_harness.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_performance.py +0 -0
- {tribalmemory-0.1.1 → tribalmemory-0.3.0}/tests/test_server.py +0 -0
|
@@ -9,6 +9,7 @@ Usage:
|
|
|
9
9
|
import argparse
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import shutil
|
|
12
13
|
import sys
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
|
|
@@ -26,6 +27,36 @@ TRIBAL_DIR = Path.home() / ".tribal-memory"
|
|
|
26
27
|
CONFIG_FILE = TRIBAL_DIR / "config.yaml"
|
|
27
28
|
DEFAULT_INSTANCE_ID = "default"
|
|
28
29
|
|
|
30
|
+
# Paths to global instructions files (relative to home)
|
|
31
|
+
CLAUDE_INSTRUCTIONS_FILE = Path(".claude") / "CLAUDE.md"
|
|
32
|
+
CODEX_INSTRUCTIONS_FILE = Path(".codex") / "AGENTS.md"
|
|
33
|
+
|
|
34
|
+
# Section marker used to detect if auto-capture instructions already exist
|
|
35
|
+
_AUTO_CAPTURE_MARKER = "## Tribal Memory — Auto-Capture"
|
|
36
|
+
|
|
37
|
+
AUTO_CAPTURE_INSTRUCTIONS = f"""\
|
|
38
|
+
|
|
39
|
+
{_AUTO_CAPTURE_MARKER}
|
|
40
|
+
|
|
41
|
+
You have access to Tribal Memory — a shared memory system for AI agents.
|
|
42
|
+
Use it proactively:
|
|
43
|
+
|
|
44
|
+
**After completing tasks, making decisions, or learning something:**
|
|
45
|
+
- Call `tribal_remember` to store the insight with relevant tags
|
|
46
|
+
- Good memories: architectural decisions, bug fixes, "gotchas", user preferences,
|
|
47
|
+
project conventions, API quirks, things that took a while to figure out
|
|
48
|
+
- Bad memories: trivial actions ("I ran the tests"), obvious facts, temporary state
|
|
49
|
+
|
|
50
|
+
**Before answering questions about prior work:**
|
|
51
|
+
- Call `tribal_recall` to search for relevant context first
|
|
52
|
+
- Search by topic, not exact phrases — it's semantic search
|
|
53
|
+
|
|
54
|
+
**Tips:**
|
|
55
|
+
- Use tags to organize: `["python", "debugging"]`, `["api", "auth"]`
|
|
56
|
+
- One clear insight per memory is better than a wall of text
|
|
57
|
+
- If you're unsure whether to remember something, remember it — recall is cheap
|
|
58
|
+
"""
|
|
59
|
+
|
|
29
60
|
# MCP config for Claude Code CLI and Claude Desktop
|
|
30
61
|
CLAUDE_CODE_MCP_CONFIG = {
|
|
31
62
|
"mcpServers": {
|
|
@@ -55,7 +86,7 @@ db:
|
|
|
55
86
|
server:
|
|
56
87
|
host: 127.0.0.1
|
|
57
88
|
port: 18790
|
|
58
|
-
"""
|
|
89
|
+
{auto_capture_line}"""
|
|
59
90
|
|
|
60
91
|
LOCAL_CONFIG_TEMPLATE = """\
|
|
61
92
|
# Tribal Memory Configuration — Local Mode (Zero Cloud)
|
|
@@ -78,7 +109,7 @@ db:
|
|
|
78
109
|
server:
|
|
79
110
|
host: 127.0.0.1
|
|
80
111
|
port: 18790
|
|
81
|
-
"""
|
|
112
|
+
{auto_capture_line}"""
|
|
82
113
|
|
|
83
114
|
|
|
84
115
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
@@ -89,16 +120,23 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
89
120
|
# Create config directory
|
|
90
121
|
TRIBAL_DIR.mkdir(parents=True, exist_ok=True)
|
|
91
122
|
|
|
123
|
+
# Auto-capture config line (only included when flag is set)
|
|
124
|
+
auto_capture_line = ""
|
|
125
|
+
if args.auto_capture:
|
|
126
|
+
auto_capture_line = "\nauto_capture: true\n"
|
|
127
|
+
|
|
92
128
|
# Choose template
|
|
93
129
|
if args.local:
|
|
94
130
|
config_content = LOCAL_CONFIG_TEMPLATE.format(
|
|
95
131
|
instance_id=instance_id,
|
|
96
132
|
db_path=db_path,
|
|
133
|
+
auto_capture_line=auto_capture_line,
|
|
97
134
|
)
|
|
98
135
|
else:
|
|
99
136
|
config_content = OPENAI_CONFIG_TEMPLATE.format(
|
|
100
137
|
instance_id=instance_id,
|
|
101
138
|
db_path=db_path,
|
|
139
|
+
auto_capture_line=auto_capture_line,
|
|
102
140
|
)
|
|
103
141
|
|
|
104
142
|
# Write config
|
|
@@ -124,16 +162,74 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
124
162
|
if args.codex:
|
|
125
163
|
_setup_codex_mcp(args.local)
|
|
126
164
|
|
|
165
|
+
# Set up auto-capture instructions
|
|
166
|
+
if args.auto_capture:
|
|
167
|
+
_setup_auto_capture(
|
|
168
|
+
claude_code=args.claude_code,
|
|
169
|
+
codex=args.codex,
|
|
170
|
+
)
|
|
171
|
+
|
|
127
172
|
print()
|
|
128
173
|
print("🚀 Start the server:")
|
|
129
174
|
print(" tribalmemory serve")
|
|
130
175
|
print()
|
|
131
176
|
print("🧠 Or use with Claude Code (MCP):")
|
|
132
177
|
print(" tribalmemory-mcp")
|
|
178
|
+
|
|
179
|
+
if not args.auto_capture:
|
|
180
|
+
print()
|
|
181
|
+
print("💡 Want your agents to remember things automatically?")
|
|
182
|
+
print(" tribalmemory init --auto-capture --force")
|
|
133
183
|
|
|
134
184
|
return 0
|
|
135
185
|
|
|
136
186
|
|
|
187
|
+
def _setup_auto_capture(claude_code: bool = False, codex: bool = False) -> None:
|
|
188
|
+
"""Write auto-capture instructions to agent instruction files.
|
|
189
|
+
|
|
190
|
+
Appends memory usage instructions so agents proactively use
|
|
191
|
+
tribal_remember and tribal_recall without being explicitly asked.
|
|
192
|
+
|
|
193
|
+
Writes to:
|
|
194
|
+
- ~/.claude/CLAUDE.md (Claude Code) — when --claude-code is set
|
|
195
|
+
- ~/.codex/AGENTS.md (Codex CLI) — when --codex is set
|
|
196
|
+
- Both files if neither flag is set (covers the common case)
|
|
197
|
+
|
|
198
|
+
Skips if instructions are already present (idempotent).
|
|
199
|
+
"""
|
|
200
|
+
# If no specific flag, write to both (default behavior)
|
|
201
|
+
if not claude_code and not codex:
|
|
202
|
+
claude_code = codex = True
|
|
203
|
+
|
|
204
|
+
targets = []
|
|
205
|
+
if claude_code:
|
|
206
|
+
targets.append(("Claude Code", Path.home() / CLAUDE_INSTRUCTIONS_FILE))
|
|
207
|
+
if codex:
|
|
208
|
+
targets.append(("Codex CLI", Path.home() / CODEX_INSTRUCTIONS_FILE))
|
|
209
|
+
|
|
210
|
+
for label, instructions_path in targets:
|
|
211
|
+
_write_instructions_file(instructions_path, label)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _write_instructions_file(instructions_path: Path, label: str) -> None:
|
|
215
|
+
"""Write auto-capture instructions to a single instructions file."""
|
|
216
|
+
instructions_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
|
|
218
|
+
if instructions_path.exists():
|
|
219
|
+
existing = instructions_path.read_text()
|
|
220
|
+
if _AUTO_CAPTURE_MARKER in existing:
|
|
221
|
+
print(f"✅ Auto-capture already present in {label}: {instructions_path}")
|
|
222
|
+
return
|
|
223
|
+
# Append to existing file
|
|
224
|
+
if not existing.endswith("\n"):
|
|
225
|
+
existing += "\n"
|
|
226
|
+
instructions_path.write_text(existing + AUTO_CAPTURE_INSTRUCTIONS)
|
|
227
|
+
else:
|
|
228
|
+
instructions_path.write_text(AUTO_CAPTURE_INSTRUCTIONS.lstrip("\n"))
|
|
229
|
+
|
|
230
|
+
print(f"✅ Auto-capture instructions written for {label}: {instructions_path}")
|
|
231
|
+
|
|
232
|
+
|
|
137
233
|
def _setup_claude_code_mcp(is_local: bool) -> None:
|
|
138
234
|
"""Add Tribal Memory to Claude Code's MCP configuration.
|
|
139
235
|
|
|
@@ -151,8 +247,13 @@ def _setup_claude_code_mcp(is_local: bool) -> None:
|
|
|
151
247
|
Path.home() / ".claude" / "claude_desktop_config.json", # Legacy / Linux
|
|
152
248
|
]
|
|
153
249
|
|
|
250
|
+
# Resolve full path to tribalmemory-mcp binary.
|
|
251
|
+
# Claude Desktop doesn't inherit the user's shell PATH (e.g. ~/.local/bin),
|
|
252
|
+
# so we need the absolute path for it to find the command.
|
|
253
|
+
mcp_command = _resolve_mcp_command()
|
|
254
|
+
|
|
154
255
|
mcp_entry = {
|
|
155
|
-
"command":
|
|
256
|
+
"command": mcp_command,
|
|
156
257
|
"env": {},
|
|
157
258
|
}
|
|
158
259
|
|
|
@@ -169,6 +270,42 @@ def _setup_claude_code_mcp(is_local: bool) -> None:
|
|
|
169
270
|
print(f"✅ Claude Desktop config updated: {desktop_path}")
|
|
170
271
|
|
|
171
272
|
|
|
273
|
+
def _resolve_mcp_command() -> str:
|
|
274
|
+
"""Resolve the full path to the tribalmemory-mcp binary.
|
|
275
|
+
|
|
276
|
+
Claude Desktop doesn't inherit the user's shell PATH (e.g. ~/.local/bin
|
|
277
|
+
from uv/pipx installs), so bare command names like "tribalmemory-mcp"
|
|
278
|
+
fail with "No such file or directory". We resolve the absolute path at
|
|
279
|
+
init time so the config works regardless of the app's PATH.
|
|
280
|
+
|
|
281
|
+
Falls back to the bare command name if not found on PATH (e.g. user
|
|
282
|
+
hasn't installed yet and will do so later).
|
|
283
|
+
"""
|
|
284
|
+
resolved = shutil.which("tribalmemory-mcp")
|
|
285
|
+
if resolved:
|
|
286
|
+
return resolved
|
|
287
|
+
|
|
288
|
+
# Check common tool install locations that might not be on PATH
|
|
289
|
+
base_name = "tribalmemory-mcp"
|
|
290
|
+
search_dirs = [
|
|
291
|
+
Path.home() / ".local" / "bin", # uv/pipx (Linux/macOS)
|
|
292
|
+
Path.home() / ".cargo" / "bin", # unlikely but possible
|
|
293
|
+
]
|
|
294
|
+
# On Windows, executables may have .exe/.cmd extensions
|
|
295
|
+
suffixes = [""]
|
|
296
|
+
if sys.platform == "win32":
|
|
297
|
+
suffixes = [".exe", ".cmd", ""]
|
|
298
|
+
|
|
299
|
+
for search_dir in search_dirs:
|
|
300
|
+
for suffix in suffixes:
|
|
301
|
+
candidate = search_dir / (base_name + suffix)
|
|
302
|
+
if candidate.exists() and os.access(candidate, os.X_OK):
|
|
303
|
+
return str(candidate)
|
|
304
|
+
|
|
305
|
+
# Fall back to bare command — will work if PATH is set correctly
|
|
306
|
+
return "tribalmemory-mcp"
|
|
307
|
+
|
|
308
|
+
|
|
172
309
|
def _get_claude_desktop_config_path() -> Path:
|
|
173
310
|
"""Get the platform-appropriate Claude Desktop config path."""
|
|
174
311
|
if sys.platform == "darwin":
|
|
@@ -211,6 +348,10 @@ def _setup_codex_mcp(is_local: bool) -> None:
|
|
|
211
348
|
codex_config_path = Path.home() / ".codex" / "config.toml"
|
|
212
349
|
codex_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
213
350
|
|
|
351
|
+
# Resolve full path (same reason as Claude Desktop — Codex may not
|
|
352
|
+
# inherit the user's full shell PATH)
|
|
353
|
+
mcp_command = _resolve_mcp_command()
|
|
354
|
+
|
|
214
355
|
# Build the TOML section manually (avoid tomli_w dependency)
|
|
215
356
|
# Codex uses [mcp_servers.name] sections in config.toml
|
|
216
357
|
section_marker = "[mcp_servers.tribal-memory]"
|
|
@@ -219,7 +360,7 @@ def _setup_codex_mcp(is_local: bool) -> None:
|
|
|
219
360
|
"",
|
|
220
361
|
"# Tribal Memory — shared memory for AI agents",
|
|
221
362
|
section_marker,
|
|
222
|
-
'command = "
|
|
363
|
+
f'command = "{mcp_command}"',
|
|
223
364
|
]
|
|
224
365
|
|
|
225
366
|
if is_local:
|
|
@@ -286,6 +427,8 @@ def main() -> None:
|
|
|
286
427
|
help="Configure Claude Code MCP integration")
|
|
287
428
|
init_parser.add_argument("--codex", action="store_true",
|
|
288
429
|
help="Configure Codex CLI MCP integration")
|
|
430
|
+
init_parser.add_argument("--auto-capture", action="store_true",
|
|
431
|
+
help="Enable auto-capture (writes instructions to agent config files)")
|
|
289
432
|
init_parser.add_argument("--instance-id", type=str, default=None,
|
|
290
433
|
help="Instance identifier (default: 'default')")
|
|
291
434
|
init_parser.add_argument("--force", action="store_true",
|
|
@@ -7,9 +7,12 @@ from abc import ABC, abstractmethod
|
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from enum import Enum
|
|
10
|
-
from typing import Optional
|
|
10
|
+
from typing import Literal, Optional
|
|
11
11
|
import uuid
|
|
12
12
|
|
|
13
|
+
# Valid retrieval methods for RecallResult
|
|
14
|
+
RetrievalMethod = Literal["vector", "graph", "hybrid", "entity"]
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class MemorySource(Enum):
|
|
15
18
|
"""Source of a memory entry."""
|
|
@@ -69,13 +72,21 @@ class MemoryEntry:
|
|
|
69
72
|
|
|
70
73
|
@dataclass
|
|
71
74
|
class RecallResult:
|
|
72
|
-
"""Result of a memory recall query.
|
|
75
|
+
"""Result of a memory recall query.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
memory: The recalled memory entry.
|
|
79
|
+
similarity_score: Relevance score (0.0-1.0 for vector, 1.0 for exact entity match).
|
|
80
|
+
retrieval_time_ms: Time taken for retrieval.
|
|
81
|
+
retrieval_method: How this result was found (see RetrievalMethod type).
|
|
82
|
+
"""
|
|
73
83
|
memory: MemoryEntry
|
|
74
84
|
similarity_score: float
|
|
75
85
|
retrieval_time_ms: float
|
|
86
|
+
retrieval_method: RetrievalMethod = "vector"
|
|
76
87
|
|
|
77
88
|
def __repr__(self) -> str:
|
|
78
|
-
return f"RecallResult(score={self.similarity_score:.3f}, memory_id={self.memory.id[:8]}...)"
|
|
89
|
+
return f"RecallResult(score={self.similarity_score:.3f}, method={self.retrieval_method}, memory_id={self.memory.id[:8]}...)"
|
|
79
90
|
|
|
80
91
|
|
|
81
92
|
@dataclass
|
|
@@ -174,6 +185,50 @@ class IVectorStore(ABC):
|
|
|
174
185
|
"""Count memories matching filters."""
|
|
175
186
|
pass
|
|
176
187
|
|
|
188
|
+
async def get_stats(self) -> dict:
|
|
189
|
+
"""Compute aggregate statistics over all memories.
|
|
190
|
+
|
|
191
|
+
Returns dict with keys:
|
|
192
|
+
total_memories, by_source_type, by_tag, by_instance, corrections
|
|
193
|
+
|
|
194
|
+
Default implementation iterates in pages of 500. Subclasses
|
|
195
|
+
should override with native queries (SQL GROUP BY, etc.) for
|
|
196
|
+
stores with >10k entries.
|
|
197
|
+
"""
|
|
198
|
+
page_size = 500
|
|
199
|
+
total = 0
|
|
200
|
+
corrections = 0
|
|
201
|
+
by_source: dict[str, int] = {}
|
|
202
|
+
by_instance: dict[str, int] = {}
|
|
203
|
+
by_tag: dict[str, int] = {}
|
|
204
|
+
|
|
205
|
+
offset = 0
|
|
206
|
+
while True:
|
|
207
|
+
page = await self.list(limit=page_size, offset=offset)
|
|
208
|
+
if not page:
|
|
209
|
+
break
|
|
210
|
+
total += len(page)
|
|
211
|
+
for m in page:
|
|
212
|
+
src = m.source_type.value
|
|
213
|
+
by_source[src] = by_source.get(src, 0) + 1
|
|
214
|
+
inst = m.source_instance
|
|
215
|
+
by_instance[inst] = by_instance.get(inst, 0) + 1
|
|
216
|
+
for tag in m.tags:
|
|
217
|
+
by_tag[tag] = by_tag.get(tag, 0) + 1
|
|
218
|
+
if m.supersedes:
|
|
219
|
+
corrections += 1
|
|
220
|
+
if len(page) < page_size:
|
|
221
|
+
break
|
|
222
|
+
offset += page_size
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
"total_memories": total,
|
|
226
|
+
"by_source_type": by_source,
|
|
227
|
+
"by_tag": by_tag,
|
|
228
|
+
"by_instance": by_instance,
|
|
229
|
+
"corrections": corrections,
|
|
230
|
+
}
|
|
231
|
+
|
|
177
232
|
|
|
178
233
|
class IDeduplicationService(ABC):
|
|
179
234
|
"""Interface for detecting duplicate memories."""
|
|
@@ -271,6 +326,7 @@ class IMemoryService(ABC):
|
|
|
271
326
|
limit: int = 5,
|
|
272
327
|
min_relevance: float = 0.7,
|
|
273
328
|
tags: Optional[list[str]] = None,
|
|
329
|
+
graph_expansion: bool = True,
|
|
274
330
|
) -> list[RecallResult]:
|
|
275
331
|
"""Recall relevant memories for a query.
|
|
276
332
|
|
|
@@ -279,6 +335,13 @@ class IMemoryService(ABC):
|
|
|
279
335
|
limit: Maximum results
|
|
280
336
|
min_relevance: Minimum similarity score
|
|
281
337
|
tags: Filter by tags (e.g., ["work", "preferences"])
|
|
338
|
+
graph_expansion: Expand candidates via entity graph (default True)
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
List of RecallResult objects with retrieval_method indicating source:
|
|
342
|
+
- "vector": Vector similarity search
|
|
343
|
+
- "hybrid": Vector + BM25 merge
|
|
344
|
+
- "graph": Entity graph traversal
|
|
282
345
|
"""
|
|
283
346
|
pass
|
|
284
347
|
|