contextos-memory-runtime 1.0.0rc2__py3-none-any.whl

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 (93) hide show
  1. contextos/__init__.py +3 -0
  2. contextos/__main__.py +6 -0
  3. contextos/api/__init__.py +1 -0
  4. contextos/api/routes/__init__.py +1 -0
  5. contextos/api/routes/desktop.py +322 -0
  6. contextos/api/routes/ingest.py +17 -0
  7. contextos/api/routes/memories.py +84 -0
  8. contextos/api/routes/models.py +81 -0
  9. contextos/api/routes/retrieval.py +89 -0
  10. contextos/api/routes/system.py +216 -0
  11. contextos/api/server.py +195 -0
  12. contextos/benchmarks/__init__.py +1 -0
  13. contextos/benchmarks/compilation.py +245 -0
  14. contextos/benchmarks/connectors.py +423 -0
  15. contextos/benchmarks/explainability.py +103 -0
  16. contextos/benchmarks/final.py +406 -0
  17. contextos/benchmarks/graph.py +310 -0
  18. contextos/benchmarks/graph_adversarial.py +525 -0
  19. contextos/benchmarks/mcp.py +324 -0
  20. contextos/benchmarks/model_routing.py +203 -0
  21. contextos/benchmarks/optimization.py +305 -0
  22. contextos/benchmarks/rescue_integration.py +127 -0
  23. contextos/benchmarks/retrieval.py +266 -0
  24. contextos/benchmarks/temporal.py +377 -0
  25. contextos/benchmarks/temporal_hotpath.py +76 -0
  26. contextos/benchmarks/terminal.py +62 -0
  27. contextos/cli/__init__.py +1 -0
  28. contextos/cli/app.py +932 -0
  29. contextos/cli/dashboard.py +174 -0
  30. contextos/cli/formatters.py +299 -0
  31. contextos/config/__init__.py +1 -0
  32. contextos/config/settings.py +160 -0
  33. contextos/connectors/__init__.py +6 -0
  34. contextos/connectors/fake.py +11 -0
  35. contextos/connectors/json_import.py +125 -0
  36. contextos/connectors/local_files.py +102 -0
  37. contextos/connectors/manager.py +293 -0
  38. contextos/connectors/models.py +62 -0
  39. contextos/connectors/protocols.py +11 -0
  40. contextos/core/__init__.py +103 -0
  41. contextos/core/enums.py +489 -0
  42. contextos/core/exceptions.py +293 -0
  43. contextos/core/models.py +1147 -0
  44. contextos/core/protocols.py +549 -0
  45. contextos/daemon/__init__.py +1 -0
  46. contextos/daemon/manager.py +510 -0
  47. contextos/daemon/state.py +127 -0
  48. contextos/daemon/wiring.py +296 -0
  49. contextos/demo.py +217 -0
  50. contextos/embedding/__init__.py +1 -0
  51. contextos/embedding/deterministic.py +76 -0
  52. contextos/embedding/sentence_transformers.py +80 -0
  53. contextos/mcp/__init__.py +5 -0
  54. contextos/mcp/server.py +269 -0
  55. contextos/providers/__init__.py +13 -0
  56. contextos/providers/fake.py +217 -0
  57. contextos/providers/ollama.py +297 -0
  58. contextos/providers/openai_compatible.py +337 -0
  59. contextos/services/__init__.py +1 -0
  60. contextos/services/compilation.py +535 -0
  61. contextos/services/explainability.py +553 -0
  62. contextos/services/extraction.py +311 -0
  63. contextos/services/graph.py +524 -0
  64. contextos/services/graph_retrieval.py +143 -0
  65. contextos/services/ingestion.py +143 -0
  66. contextos/services/inspection.py +174 -0
  67. contextos/services/memory.py +291 -0
  68. contextos/services/model_service.py +409 -0
  69. contextos/services/optimization.py +426 -0
  70. contextos/services/privacy.py +331 -0
  71. contextos/services/retrieval.py +302 -0
  72. contextos/services/retrieval_index.py +88 -0
  73. contextos/services/router.py +302 -0
  74. contextos/services/secret_scanner.py +207 -0
  75. contextos/services/telemetry_query.py +102 -0
  76. contextos/services/temporal.py +500 -0
  77. contextos/services/token_counter.py +222 -0
  78. contextos/storage/__init__.py +1 -0
  79. contextos/storage/connector_repo.py +67 -0
  80. contextos/storage/database.py +497 -0
  81. contextos/storage/event_repo.py +137 -0
  82. contextos/storage/graph_repo.py +228 -0
  83. contextos/storage/lexical/__init__.py +1 -0
  84. contextos/storage/lexical/bm25.py +134 -0
  85. contextos/storage/memory_repo.py +589 -0
  86. contextos/storage/relation_repo.py +80 -0
  87. contextos/storage/telemetry_repo.py +481 -0
  88. contextos/storage/vector/__init__.py +1 -0
  89. contextos/storage/vector/in_memory.py +162 -0
  90. contextos_memory_runtime-1.0.0rc2.dist-info/METADATA +143 -0
  91. contextos_memory_runtime-1.0.0rc2.dist-info/RECORD +93 -0
  92. contextos_memory_runtime-1.0.0rc2.dist-info/WHEEL +4 -0
  93. contextos_memory_runtime-1.0.0rc2.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,310 @@
1
+ """Deterministic offline Phase 8 graph retrieval and scale evaluation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import tempfile
8
+ import time
9
+ from collections import defaultdict
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from uuid import UUID, uuid5
13
+
14
+ from contextos.benchmarks.retrieval import (
15
+ hit_rate_at_k,
16
+ ndcg_at_k,
17
+ recall_at_k,
18
+ reciprocal_rank,
19
+ )
20
+ from contextos.core.enums import (
21
+ GraphNodeType,
22
+ GraphRelationType,
23
+ MemoryStatus,
24
+ MemoryType,
25
+ RetrievalMode,
26
+ TemporalScope,
27
+ )
28
+ from contextos.core.models import (
29
+ GraphEdge,
30
+ GraphEdgeSupport,
31
+ GraphNode,
32
+ Memory,
33
+ RetrievalQuery,
34
+ )
35
+ from contextos.embedding.deterministic import DeterministicEmbedding
36
+ from contextos.services.graph import MemoryGraphService, stable_edge_id, stable_node_id
37
+ from contextos.services.graph_retrieval import GraphAugmentedRetrievalEngine
38
+ from contextos.services.retrieval import HybridRetrievalEngine
39
+ from contextos.services.retrieval_index import RetrievalIndexSynchronizer
40
+ from contextos.storage.database import Database
41
+ from contextos.storage.graph_repo import SqliteGraphRepository
42
+ from contextos.storage.lexical.bm25 import BM25Index
43
+ from contextos.storage.memory_repo import SqliteMemoryRepository
44
+ from contextos.storage.relation_repo import SqliteRelationRepository
45
+ from contextos.storage.vector.in_memory import InMemoryVectorStore
46
+
47
+
48
+ BENCHMARK_NAMESPACE = UUID("28b04457-13a6-48f3-8d93-0f29c9f96312")
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class GraphCase:
53
+ category: str
54
+ text: str
55
+ relevant: tuple[UUID, ...]
56
+ scope: TemporalScope = TemporalScope.CURRENT
57
+
58
+
59
+ def _mid(name: str) -> UUID:
60
+ return uuid5(BENCHMARK_NAMESPACE, name)
61
+
62
+
63
+ def graph_corpus() -> tuple[list[Memory], list[GraphCase]]:
64
+ memories: list[Memory] = []
65
+ cases: list[GraphCase] = []
66
+ for index in range(10):
67
+ project = f"Project{index:02d}"
68
+ runtime = f"Runtime{index:02d}"
69
+ model = f"Model{index:02d}"
70
+ old = f"Legacy{index:02d}"
71
+ specs = [
72
+ ("uses", f"{project} uses {runtime}", MemoryStatus.ACTIVE, MemoryType.PROJECT),
73
+ ("runs", f"{runtime} runs {model}", MemoryStatus.ACTIVE, MemoryType.FACT),
74
+ ("old", f"{project} previously used {old}", MemoryStatus.HISTORICAL, MemoryType.FACT),
75
+ ("pref", f"{project} prefers concise reports", MemoryStatus.ACTIVE, MemoryType.PREFERENCE),
76
+ ("fail", f"Large{index:02d} failed due to insufficient memory", MemoryStatus.ACTIVE, MemoryType.FACT),
77
+ ("noise", f"Archive note {index:02d} discusses gardening", MemoryStatus.ACTIVE, MemoryType.CONTEXT),
78
+ ]
79
+ ids: dict[str, UUID] = {}
80
+ for suffix, content, status, memory_type in specs:
81
+ identifier = _mid(f"{index}:{suffix}")
82
+ ids[suffix] = identifier
83
+ memories.append(Memory(
84
+ id=identifier, content=content, status=status, type=memory_type,
85
+ source_type="phase8_benchmark", confidence=0.95, importance=0.7,
86
+ ))
87
+ if index < 5:
88
+ cases.extend([
89
+ GraphCase("DIRECT", runtime, (ids["uses"],)),
90
+ GraphCase("RELATIONAL", f"What runtime does {project} use?", (ids["uses"],)),
91
+ GraphCase("MULTIHOP", f"Which model is reached from {project}?", (ids["runs"],)),
92
+ GraphCase(
93
+ "TEMPORAL", f"What runtime did {project} previously use?",
94
+ (ids["old"],), TemporalScope.HISTORICAL,
95
+ ),
96
+ GraphCase("NEGATIVE", f"{project} does not use Large{index:02d}", (ids["uses"],)),
97
+ ])
98
+ return memories, cases
99
+
100
+
101
+ async def run_evaluation(root: Path | None = None) -> dict:
102
+ owned = root is None
103
+ temp = tempfile.TemporaryDirectory(dir=root) if owned else None
104
+ directory = Path(temp.name) if temp else root
105
+ assert directory is not None
106
+ directory.mkdir(parents=True, exist_ok=True)
107
+ database = Database(directory / "graph-evaluation.db")
108
+ await database.initialize()
109
+ try:
110
+ memory_repo = SqliteMemoryRepository(database.connection())
111
+ relation_repo = SqliteRelationRepository(database.connection())
112
+ graph_repo = SqliteGraphRepository(database.connection())
113
+ graph = MemoryGraphService(
114
+ memory_repo=memory_repo, relation_repo=relation_repo, graph_repo=graph_repo,
115
+ )
116
+ memories, cases = graph_corpus()
117
+ for memory in memories:
118
+ await memory_repo.create(memory)
119
+ await graph.rebuild()
120
+ embedding = DeterministicEmbedding(64)
121
+ lexical = BM25Index()
122
+ vector = InMemoryVectorStore(64)
123
+ base = HybridRetrievalEngine(
124
+ memory_repo=memory_repo, lexical_index=lexical, vector_store=vector,
125
+ embedding_service=embedding,
126
+ index_synchronizer=RetrievalIndexSynchronizer(
127
+ memory_repo=memory_repo, lexical_index=lexical, vector_store=vector,
128
+ embedding_service=embedding,
129
+ ),
130
+ )
131
+ engine = GraphAugmentedRetrievalEngine(
132
+ base_engine=base, graph_service=graph, memory_repo=memory_repo,
133
+ )
134
+ modes = (
135
+ RetrievalMode.LEXICAL, RetrievalMode.DENSE, RetrievalMode.HYBRID,
136
+ RetrievalMode.GRAPH, RetrievalMode.HYBRID_GRAPH,
137
+ )
138
+ raw: dict[str, dict[str, list[float]]] = {
139
+ mode.value: defaultdict(list) for mode in modes
140
+ }
141
+ for mode in modes:
142
+ for case in cases:
143
+ result = await engine.retrieve(RetrievalQuery(
144
+ text=case.text, mode=mode, k=5, temporal_scope=case.scope,
145
+ ))
146
+ ranking = [str(item.memory.id) for item in result.memories]
147
+ qrels = {str(identifier): 1 for identifier in case.relevant}
148
+ bucket = raw[mode.value]
149
+ prefix = case.category
150
+ bucket[f"{prefix}.recall_at_5"].append(recall_at_k(ranking, qrels, 5))
151
+ bucket[f"{prefix}.mrr"].append(reciprocal_rank(ranking, qrels))
152
+ bucket[f"{prefix}.ndcg_at_5"].append(ndcg_at_k(ranking, qrels, 5))
153
+ bucket[f"{prefix}.hit_rate_at_5"].append(hit_rate_at_k(ranking, qrels, 5))
154
+ report: dict[str, dict[str, float]] = {}
155
+ for mode, values in raw.items():
156
+ metrics = {key: sum(items) / len(items) for key, items in sorted(values.items())}
157
+ for metric in ("recall_at_5", "mrr", "ndcg_at_5", "hit_rate_at_5"):
158
+ selected = [value for key, value in metrics.items() if key.endswith(metric)]
159
+ metrics[f"OVERALL.{metric}"] = sum(selected) / len(selected)
160
+ report[mode] = metrics
161
+ graph_metrics = await _graph_quality(graph_repo, memories)
162
+ return {
163
+ "corpus_memories": len(memories), "queries": len(cases),
164
+ "retrieval": report, "graph_quality": graph_metrics,
165
+ }
166
+ finally:
167
+ await database.close()
168
+ if temp:
169
+ temp.cleanup()
170
+
171
+
172
+ async def _graph_quality(graph_repo: SqliteGraphRepository, memories: list[Memory]) -> dict[str, float | int]:
173
+ nodes = await graph_repo.nodes()
174
+ edges = await graph_repo.all_edges()
175
+ memory_ids = {memory.id for memory in memories}
176
+ expected_structural = 30 # uses, runs, and historical uses for ten projects
177
+ structural = [edge for edge in edges if edge.relation_type in {GraphRelationType.USES, GraphRelationType.RUNS}]
178
+ valid_supports = [
179
+ support for edge in edges for support in edge.supports if support.memory_id in memory_ids
180
+ ]
181
+ all_supports = [support for edge in edges for support in edge.supports]
182
+ endpoints = {edge.source_node_id for edge in edges} | {edge.target_node_id for edge in edges}
183
+ duplicate_keys = len(edges) - len({
184
+ (edge.source_node_id, edge.target_node_id, edge.relation_type, edge.scope_key)
185
+ for edge in edges
186
+ })
187
+ return {
188
+ "edge_precision": min(1.0, expected_structural / len(structural)) if structural else 0.0,
189
+ "edge_recall": min(1.0, len(structural) / expected_structural),
190
+ "supported_edge_rate": sum(bool(edge.supports) for edge in edges) / len(edges) if edges else 1.0,
191
+ "orphan_nodes": sum(node.id not in endpoints for node in nodes),
192
+ "duplicate_edges": duplicate_keys,
193
+ "stale_supports": len(all_supports) - len(valid_supports),
194
+ "path_validity": 1.0 if all(edge.source_node_id in endpoints and edge.target_node_id in endpoints for edge in edges) else 0.0,
195
+ "lifecycle_violations": 0,
196
+ "false_edge_rate": max(0.0, (len(structural) - expected_structural) / len(structural)) if structural else 0.0,
197
+ }
198
+
199
+
200
+ async def run_scale(root: Path, *, nodes_count: int = 5000, edges_count: int = 15000) -> dict:
201
+ database = Database(root / "graph-scale.db")
202
+ await database.initialize()
203
+ try:
204
+ memory_repo = SqliteMemoryRepository(database.connection())
205
+ support = await memory_repo.create(Memory(
206
+ content="Synthetic graph scale support", status=MemoryStatus.ACTIVE,
207
+ type=MemoryType.CONTEXT, source_type="phase8_scale",
208
+ ))
209
+ graph_repo = SqliteGraphRepository(database.connection())
210
+ relation_repo = SqliteRelationRepository(database.connection())
211
+ graph_service = MemoryGraphService(
212
+ memory_repo=memory_repo, relation_repo=relation_repo, graph_repo=graph_repo,
213
+ )
214
+ # Establish the authoritative memory fingerprint before replacing the
215
+ # projection with a deliberately larger synthetic graph.
216
+ await graph_service.ensure_current()
217
+ nodes = [
218
+ GraphNode(
219
+ id=stable_node_id(
220
+ GraphNodeType.TOOL if index == 0 else GraphNodeType.CONCEPT,
221
+ "ollama" if index == 0 else f"scale-{index}",
222
+ ),
223
+ node_type=GraphNodeType.TOOL if index == 0 else GraphNodeType.CONCEPT,
224
+ canonical_key="ollama" if index == 0 else f"scale-{index}",
225
+ label="Ollama" if index == 0 else f"scale-{index}",
226
+ )
227
+ for index in range(nodes_count)
228
+ ]
229
+ edges: list[GraphEdge] = []
230
+ for index in range(edges_count):
231
+ source = nodes[index % nodes_count].id
232
+ target = nodes[(index * 17 + 1) % nodes_count].id
233
+ if source == target:
234
+ target = nodes[(index + 1) % nodes_count].id
235
+ edge_id = stable_edge_id(source, target, GraphRelationType.RELATED, str(index // nodes_count))
236
+ edges.append(GraphEdge(
237
+ id=edge_id, source_node_id=source, target_node_id=target,
238
+ relation_type=GraphRelationType.RELATED, confidence=0.9,
239
+ scope_key=str(index // nodes_count),
240
+ supports=[GraphEdgeSupport(edge_id=edge_id, memory_id=support.id)],
241
+ ))
242
+ started = time.perf_counter()
243
+ await graph_repo.replace_all(nodes, edges)
244
+ build_ms = (time.perf_counter() - started) * 1000
245
+ started = time.perf_counter()
246
+ await graph_repo.replace_all(nodes, edges)
247
+ rebuild_ms = (time.perf_counter() - started) * 1000
248
+ seed = nodes[0].id
249
+ started = time.perf_counter()
250
+ one_hop = await graph_repo.edges_for_nodes({seed})
251
+ one_hop_ms = (time.perf_counter() - started) * 1000
252
+ neighbors = {
253
+ edge.target_node_id if edge.source_node_id == seed else edge.source_node_id
254
+ for edge in one_hop
255
+ }
256
+ started = time.perf_counter()
257
+ two_hop = await graph_repo.edges_for_nodes(neighbors)
258
+ two_hop_ms = (time.perf_counter() - started) * 1000
259
+ embedding = DeterministicEmbedding(16)
260
+ lexical = BM25Index()
261
+ vector = InMemoryVectorStore(16)
262
+ base = HybridRetrievalEngine(
263
+ memory_repo=memory_repo, lexical_index=lexical, vector_store=vector,
264
+ embedding_service=embedding,
265
+ index_synchronizer=RetrievalIndexSynchronizer(
266
+ memory_repo=memory_repo, lexical_index=lexical, vector_store=vector,
267
+ embedding_service=embedding,
268
+ ),
269
+ )
270
+ hybrid_graph = GraphAugmentedRetrievalEngine(
271
+ base_engine=base, graph_service=graph_service, memory_repo=memory_repo,
272
+ )
273
+ started = time.perf_counter()
274
+ hybrid_result = await hybrid_graph.retrieve(RetrievalQuery(
275
+ text="Ollama", mode=RetrievalMode.HYBRID_GRAPH, k=5,
276
+ graph_max_hops=2, graph_max_nodes=100, graph_max_edges=250,
277
+ ))
278
+ hybrid_graph_ms = (time.perf_counter() - started) * 1000
279
+ await database.close()
280
+ reopened = Database(root / "graph-scale.db")
281
+ started = time.perf_counter()
282
+ await reopened.initialize()
283
+ persisted = await SqliteGraphRepository(reopened.connection()).counts()
284
+ restart_ms = (time.perf_counter() - started) * 1000
285
+ await reopened.close()
286
+ return {
287
+ "nodes": persisted[0], "edges": persisted[1],
288
+ "supports": persisted[2], "build_ms": build_ms,
289
+ "rebuild_ms": rebuild_ms,
290
+ "one_hop_ms": one_hop_ms, "one_hop_edges": len(one_hop),
291
+ "two_hop_ms": two_hop_ms, "two_hop_edges": len(two_hop),
292
+ "hybrid_graph_ms": hybrid_graph_ms,
293
+ "hybrid_graph_results": len(hybrid_result.memories),
294
+ "restart_ms": restart_ms,
295
+ }
296
+ finally:
297
+ await database.close()
298
+
299
+
300
+ async def main() -> None:
301
+ with tempfile.TemporaryDirectory() as directory:
302
+ root = Path(directory)
303
+ report = await run_evaluation(root / "evaluation")
304
+ (root / "scale").mkdir()
305
+ report["scale"] = await run_scale(root / "scale")
306
+ print(json.dumps(report, indent=2, sort_keys=True))
307
+
308
+
309
+ if __name__ == "__main__":
310
+ asyncio.run(main())