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,423 @@
1
+ """Deterministic Phase 11 connector performance and unchanged skip benchmark.
2
+
3
+ Label: LOCAL DEVELOPMENT SYNTHETIC CONNECTOR BENCHMARK
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import hashlib
10
+ import json
11
+ import tempfile
12
+ import time
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ from contextos.connectors.fake import FakeConnector
17
+ from contextos.connectors.json_import import JsonImportConnector
18
+ from contextos.connectors.local_files import LocalFileConnector
19
+ from contextos.connectors.manager import ConnectorManager
20
+ from contextos.connectors.models import ConnectorItem, RetentionPolicy
21
+ from contextos.core.enums import SecretDetectionMode
22
+ from contextos.embedding.deterministic import DeterministicEmbedding
23
+ from contextos.services.extraction import RuleBasedMemoryExtractor
24
+ from contextos.services.ingestion import IngestionPipeline
25
+ from contextos.services.secret_scanner import PatternSecretScanner
26
+ from contextos.services.temporal import TemporalMemoryService
27
+ from contextos.services.token_counter import DeterministicWordTokenCounter
28
+ from contextos.storage.connector_repo import SqliteConnectorRepository
29
+ from contextos.storage.database import Database
30
+ from contextos.storage.event_repo import SqliteEventRepository
31
+ from contextos.storage.lexical.bm25 import BM25Index
32
+ from contextos.storage.memory_repo import SqliteMemoryRepository
33
+ from contextos.storage.vector.in_memory import InMemoryVectorStore
34
+
35
+
36
+ class CountingSecretScanner(PatternSecretScanner):
37
+ def __init__(self) -> None:
38
+ super().__init__()
39
+ self.calls = 0
40
+
41
+ def scan(self, text: str, *args, **kwargs):
42
+ self.calls += 1
43
+ return super().scan(text, *args, **kwargs)
44
+
45
+
46
+ class CountingExtractor(RuleBasedMemoryExtractor):
47
+ def __init__(self) -> None:
48
+ super().__init__()
49
+ self.calls = 0
50
+
51
+ def extract(self, text: str, *args, **kwargs):
52
+ self.calls += 1
53
+ return super().extract(text, *args, **kwargs)
54
+
55
+
56
+ class CountingTemporalService(TemporalMemoryService):
57
+ def __init__(self, memory_repo) -> None:
58
+ super().__init__(memory_repo)
59
+ self.accept_calls = 0
60
+
61
+ async def accept(self, candidate, *, provenance_event_id=None):
62
+ self.accept_calls += 1
63
+ return await super().accept(candidate, provenance_event_id=provenance_event_id)
64
+
65
+
66
+ @dataclass
67
+ class BenchmarkScenarioResult:
68
+ name: str
69
+ items_count: int
70
+ duration_ms: float
71
+ items_per_sec: float
72
+ scanned: int
73
+ accepted: int
74
+ unchanged: int
75
+ updated: int
76
+ rejected: int
77
+ failed: int
78
+ privacy_calls: int
79
+ extractor_calls: int
80
+ temporal_calls: int
81
+
82
+
83
+ async def run_connector_benchmark() -> list[BenchmarkScenarioResult]:
84
+ tmp_dir = Path(tempfile.mkdtemp())
85
+ db_path = tmp_dir / "bench_connectors.db"
86
+ db = Database(db_path)
87
+ await db.initialize()
88
+ conn = db.connection()
89
+
90
+ memory_repo = SqliteMemoryRepository(conn)
91
+ event_repo = SqliteEventRepository(conn)
92
+ connector_repo = SqliteConnectorRepository(conn)
93
+
94
+ scanner = CountingSecretScanner()
95
+ extractor = CountingExtractor()
96
+ token_counter = DeterministicWordTokenCounter()
97
+ embedding = DeterministicEmbedding(16)
98
+ lexical = BM25Index()
99
+ vector = InMemoryVectorStore(16)
100
+
101
+ ingestion = IngestionPipeline(
102
+ secret_scanner=scanner,
103
+ memory_extractor=extractor,
104
+ memory_repo=memory_repo,
105
+ event_repo=event_repo,
106
+ embedding_service=embedding,
107
+ vector_store=vector,
108
+ lexical_index=lexical,
109
+ token_counter=token_counter,
110
+ secret_detection_mode=SecretDetectionMode.STRICT,
111
+ )
112
+ temporal = CountingTemporalService(memory_repo)
113
+ manager = ConnectorManager(
114
+ state_repo=connector_repo,
115
+ ingestion=ingestion,
116
+ temporal=temporal,
117
+ retention_policy=RetentionPolicy.KEEP_DERIVED_MEMORY,
118
+ memory_repo=memory_repo,
119
+ )
120
+
121
+ results: list[BenchmarkScenarioResult] = []
122
+
123
+ def reset_counters():
124
+ scanner.calls = 0
125
+ extractor.calls = 0
126
+ temporal.accept_calls = 0
127
+
128
+ # --- Scenario 1: Initial Sync (100 items) ---
129
+ items_100 = [
130
+ ConnectorItem(
131
+ external_id=f"doc_{i}",
132
+ source_type="fake",
133
+ source_uri=f"fake://doc_{i}",
134
+ content=f"I am working on Project Atlas module {i}. Project Atlas uses Python.",
135
+ revision=f"rev_1_{i}",
136
+ )
137
+ for i in range(100)
138
+ ]
139
+ connector_100 = FakeConnector("fake-100", items_100)
140
+ manager.register(connector_100)
141
+
142
+ reset_counters()
143
+ started = time.perf_counter()
144
+ sync_res = await manager.sync("fake-100")
145
+ dur_ms = (time.perf_counter() - started) * 1000
146
+ results.append(
147
+ BenchmarkScenarioResult(
148
+ name="Initial Sync (100 items)",
149
+ items_count=100,
150
+ duration_ms=dur_ms,
151
+ items_per_sec=100 / (dur_ms / 1000) if dur_ms > 0 else 0,
152
+ scanned=sync_res.scanned,
153
+ accepted=sync_res.accepted,
154
+ unchanged=sync_res.unchanged,
155
+ updated=sync_res.updated,
156
+ rejected=sync_res.rejected,
157
+ failed=sync_res.failed,
158
+ privacy_calls=scanner.calls,
159
+ extractor_calls=extractor.calls,
160
+ temporal_calls=temporal.accept_calls,
161
+ )
162
+ )
163
+
164
+ # --- Scenario 2: Unchanged Second Sync (100 items) ---
165
+ reset_counters()
166
+ started = time.perf_counter()
167
+ sync_res = await manager.sync("fake-100")
168
+ dur_ms = (time.perf_counter() - started) * 1000
169
+ results.append(
170
+ BenchmarkScenarioResult(
171
+ name="Unchanged Second Sync (100 items)",
172
+ items_count=100,
173
+ duration_ms=dur_ms,
174
+ items_per_sec=100 / (dur_ms / 1000) if dur_ms > 0 else 0,
175
+ scanned=sync_res.scanned,
176
+ accepted=sync_res.accepted,
177
+ unchanged=sync_res.unchanged,
178
+ updated=sync_res.updated,
179
+ rejected=sync_res.rejected,
180
+ failed=sync_res.failed,
181
+ privacy_calls=scanner.calls,
182
+ extractor_calls=extractor.calls,
183
+ temporal_calls=temporal.accept_calls,
184
+ )
185
+ )
186
+
187
+ # --- Scenario 3: 10% Changed Sync (100 items, 10 modified) ---
188
+ changed_items_100 = list(items_100)
189
+ for i in range(10):
190
+ changed_items_100[i] = ConnectorItem(
191
+ external_id=f"doc_{i}",
192
+ source_type="fake",
193
+ source_uri=f"fake://doc_{i}",
194
+ content=f"I am working on Project Atlas module {i}. Now using Rust.",
195
+ revision=f"rev_2_{i}",
196
+ )
197
+ connector_100.items = changed_items_100
198
+
199
+ reset_counters()
200
+ started = time.perf_counter()
201
+ sync_res = await manager.sync("fake-100")
202
+ dur_ms = (time.perf_counter() - started) * 1000
203
+ results.append(
204
+ BenchmarkScenarioResult(
205
+ name="10% Changed Sync (100 items)",
206
+ items_count=100,
207
+ duration_ms=dur_ms,
208
+ items_per_sec=100 / (dur_ms / 1000) if dur_ms > 0 else 0,
209
+ scanned=sync_res.scanned,
210
+ accepted=sync_res.accepted,
211
+ unchanged=sync_res.unchanged,
212
+ updated=sync_res.updated,
213
+ rejected=sync_res.rejected,
214
+ failed=sync_res.failed,
215
+ privacy_calls=scanner.calls,
216
+ extractor_calls=extractor.calls,
217
+ temporal_calls=temporal.accept_calls,
218
+ )
219
+ )
220
+
221
+ # --- Scenario 4: Privacy Rejection Batch (20 items with secrets) ---
222
+ secret_items = [
223
+ ConnectorItem(
224
+ external_id=f"sec_{i}",
225
+ source_type="fake",
226
+ source_uri=f"fake://sec_{i}",
227
+ content=f"I prefer secret key: sk-proj-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
228
+ revision="rev_sec",
229
+ )
230
+ for i in range(20)
231
+ ]
232
+ connector_sec = FakeConnector("fake-sec", secret_items)
233
+ manager.register(connector_sec)
234
+
235
+ reset_counters()
236
+ started = time.perf_counter()
237
+ sync_res = await manager.sync("fake-sec")
238
+ dur_ms = (time.perf_counter() - started) * 1000
239
+ results.append(
240
+ BenchmarkScenarioResult(
241
+ name="Privacy Rejection Batch (20 items)",
242
+ items_count=20,
243
+ duration_ms=dur_ms,
244
+ items_per_sec=20 / (dur_ms / 1000) if dur_ms > 0 else 0,
245
+ scanned=sync_res.scanned,
246
+ accepted=sync_res.accepted,
247
+ unchanged=sync_res.unchanged,
248
+ updated=sync_res.updated,
249
+ rejected=sync_res.rejected,
250
+ failed=sync_res.failed,
251
+ privacy_calls=scanner.calls,
252
+ extractor_calls=extractor.calls,
253
+ temporal_calls=temporal.accept_calls,
254
+ )
255
+ )
256
+
257
+ # --- Scenario 5: JSONL Import Scan (100 items) ---
258
+ jsonl_file = tmp_dir / "import.jsonl"
259
+ jsonl_lines = [
260
+ json.dumps({"id": f"j_{i}", "content": f"I am working on Project Gamma {i}. Project Gamma uses PostgreSQL."})
261
+ for i in range(100)
262
+ ]
263
+ jsonl_file.write_text("\n".join(jsonl_lines), encoding="utf-8")
264
+ json_conn = JsonImportConnector("json-100", jsonl_file)
265
+ manager.register(json_conn)
266
+
267
+ reset_counters()
268
+ started = time.perf_counter()
269
+ sync_res = await manager.sync("json-100")
270
+ dur_ms = (time.perf_counter() - started) * 1000
271
+ results.append(
272
+ BenchmarkScenarioResult(
273
+ name="JSONL Import Scan (100 items)",
274
+ items_count=100,
275
+ duration_ms=dur_ms,
276
+ items_per_sec=100 / (dur_ms / 1000) if dur_ms > 0 else 0,
277
+ scanned=sync_res.scanned,
278
+ accepted=sync_res.accepted,
279
+ unchanged=sync_res.unchanged,
280
+ updated=sync_res.updated,
281
+ rejected=sync_res.rejected,
282
+ failed=sync_res.failed,
283
+ privacy_calls=scanner.calls,
284
+ extractor_calls=extractor.calls,
285
+ temporal_calls=temporal.accept_calls,
286
+ )
287
+ )
288
+
289
+ # --- Scenario 6: Local File Scan (50 files) ---
290
+ files_dir = tmp_dir / "files"
291
+ files_dir.mkdir()
292
+ for i in range(50):
293
+ (files_dir / f"note_{i}.txt").write_text(
294
+ f"I am working on Project Beta module {i}. Project Beta uses Rust.", encoding="utf-8"
295
+ )
296
+ file_conn = LocalFileConnector("files-50", [files_dir])
297
+ manager.register(file_conn)
298
+
299
+ reset_counters()
300
+ started = time.perf_counter()
301
+ sync_res = await manager.sync("files-50")
302
+ dur_ms = (time.perf_counter() - started) * 1000
303
+ results.append(
304
+ BenchmarkScenarioResult(
305
+ name="Local File Scan (50 files)",
306
+ items_count=50,
307
+ duration_ms=dur_ms,
308
+ items_per_sec=50 / (dur_ms / 1000) if dur_ms > 0 else 0,
309
+ scanned=sync_res.scanned,
310
+ accepted=sync_res.accepted,
311
+ unchanged=sync_res.unchanged,
312
+ updated=sync_res.updated,
313
+ rejected=sync_res.rejected,
314
+ failed=sync_res.failed,
315
+ privacy_calls=scanner.calls,
316
+ extractor_calls=extractor.calls,
317
+ temporal_calls=temporal.accept_calls,
318
+ )
319
+ )
320
+
321
+ # --- Scenario 7: Scale Initial Sync (1000 items) ---
322
+ items_1000 = [
323
+ ConnectorItem(
324
+ external_id=f"big_{i}",
325
+ source_type="fake",
326
+ source_uri=f"fake://big_{i}",
327
+ content=f"I am working on Project Scale component {i}. Uses TypeScript.",
328
+ revision=f"rev_scale_{i}",
329
+ )
330
+ for i in range(1000)
331
+ ]
332
+ connector_1000 = FakeConnector("fake-1000", items_1000)
333
+ manager.register(connector_1000)
334
+
335
+ reset_counters()
336
+ started = time.perf_counter()
337
+ sync_res = await manager.sync("fake-1000")
338
+ dur_ms = (time.perf_counter() - started) * 1000
339
+ results.append(
340
+ BenchmarkScenarioResult(
341
+ name="Initial Sync (1000 items)",
342
+ items_count=1000,
343
+ duration_ms=dur_ms,
344
+ items_per_sec=1000 / (dur_ms / 1000) if dur_ms > 0 else 0,
345
+ scanned=sync_res.scanned,
346
+ accepted=sync_res.accepted,
347
+ unchanged=sync_res.unchanged,
348
+ updated=sync_res.updated,
349
+ rejected=sync_res.rejected,
350
+ failed=sync_res.failed,
351
+ privacy_calls=scanner.calls,
352
+ extractor_calls=extractor.calls,
353
+ temporal_calls=temporal.accept_calls,
354
+ )
355
+ )
356
+
357
+ # --- Scenario 8: Scale Unchanged Second Sync (1000 items) ---
358
+ reset_counters()
359
+ started = time.perf_counter()
360
+ sync_res = await manager.sync("fake-1000")
361
+ dur_ms = (time.perf_counter() - started) * 1000
362
+ results.append(
363
+ BenchmarkScenarioResult(
364
+ name="Unchanged Second Sync (1000 items)",
365
+ items_count=1000,
366
+ duration_ms=dur_ms,
367
+ items_per_sec=1000 / (dur_ms / 1000) if dur_ms > 0 else 0,
368
+ scanned=sync_res.scanned,
369
+ accepted=sync_res.accepted,
370
+ unchanged=sync_res.unchanged,
371
+ updated=sync_res.updated,
372
+ rejected=sync_res.rejected,
373
+ failed=sync_res.failed,
374
+ privacy_calls=scanner.calls,
375
+ extractor_calls=extractor.calls,
376
+ temporal_calls=temporal.accept_calls,
377
+ )
378
+ )
379
+
380
+ await db.close()
381
+ return results
382
+
383
+
384
+ def main() -> None:
385
+ results = asyncio.run(run_connector_benchmark())
386
+
387
+ print("=" * 80)
388
+ print("LOCAL DEVELOPMENT SYNTHETIC CONNECTOR BENCHMARK")
389
+ print("=" * 80)
390
+ print()
391
+ print(
392
+ f"{'Scenario':<38} | {'Items':<6} | {'Time (ms)':<9} | {'Items/sec':<10} | "
393
+ f"{'Accepted':<8} | {'Unchanged':<9} | {'Pipeline Calls (Scanner/Extract/Temporal)'}"
394
+ )
395
+ print("-" * 115)
396
+
397
+ for r in results:
398
+ pipe_str = f"{r.privacy_calls}/{r.extractor_calls}/{r.temporal_calls}"
399
+ print(
400
+ f"{r.name:<38} | {r.items_count:<6} | {r.duration_ms:>9.2f} | "
401
+ f"{r.items_per_sec:>10.1f} | {r.accepted:<8} | {r.unchanged:<9} | {pipe_str}"
402
+ )
403
+
404
+ print()
405
+ print("UNCHANGED SKIP INVARIANT SUMMARY:")
406
+ print("-" * 50)
407
+ unchanged_100 = next(r for r in results if r.name == "Unchanged Second Sync (100 items)")
408
+ unchanged_1000 = next(r for r in results if r.name == "Unchanged Second Sync (1000 items)")
409
+ print(
410
+ f"100 Unchanged Sync: Unchanged={unchanged_100.unchanged}/100, "
411
+ f"Pipeline Calls (Scanner/Extract/Temporal) = {unchanged_100.privacy_calls}/{unchanged_100.extractor_calls}/{unchanged_100.temporal_calls}"
412
+ )
413
+ print(
414
+ f"1000 Unchanged Sync: Unchanged={unchanged_1000.unchanged}/1000, "
415
+ f"Pipeline Calls (Scanner/Extract/Temporal) = {unchanged_1000.privacy_calls}/{unchanged_1000.extractor_calls}/{unchanged_1000.temporal_calls}"
416
+ )
417
+ print("* Instrumentation Note: Scanner calls count internal PatternSecretScanner checks across content and candidates (14 per item).")
418
+ print()
419
+ print("Benchmark complete.")
420
+
421
+
422
+ if __name__ == "__main__":
423
+ main()
@@ -0,0 +1,103 @@
1
+ """LOCAL DEVELOPMENT SYNTHETIC EXPLAINABILITY BENCHMARK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import statistics
8
+ import tempfile
9
+ import time
10
+ from pathlib import Path
11
+
12
+ from contextos.config.settings import Settings
13
+ from contextos.core.models import CompilationConfig, ContextBudget, RetrievalQuery
14
+ from contextos.daemon.wiring import wire_services
15
+ from contextos.services.explainability import ExplainabilityService, ExplanationRequest
16
+ from contextos.core.models import Memory
17
+ from contextos.core.enums import MemoryStatus
18
+
19
+
20
+ def _p95(values: list[float]) -> float:
21
+ ordered = sorted(values)
22
+ return ordered[min(len(ordered) - 1, int(0.95 * len(ordered)))]
23
+
24
+
25
+ async def measure() -> dict:
26
+ report: dict[str, object] = {
27
+ "label": "LOCAL DEVELOPMENT SYNTHETIC EXPLAINABILITY BENCHMARK",
28
+ "iterations": 5,
29
+ "datasets": {},
30
+ }
31
+ for size in (100, 1000):
32
+ with tempfile.TemporaryDirectory(prefix=f"contextos-explain-{size}-") as folder:
33
+ services = await wire_services(Settings(
34
+ daemon={"data_dir": Path(folder)}, embedding={"model": "deterministic"}
35
+ ))
36
+ try:
37
+ for index in range(size):
38
+ await services["memory_repo"].create(Memory(
39
+ content=f"I use Python automation tool number {index} for local project task {index % 31}.",
40
+ status=MemoryStatus.ACTIVE,
41
+ source_type="benchmark",
42
+ ))
43
+ await services["retrieval_index"].ensure_current()
44
+ explanation_service = ExplainabilityService(services)
45
+ retrieval_ms: list[float] = []
46
+ full_explain_ms: list[float] = []
47
+ compile_ms: list[float] = []
48
+ trace_bytes: list[int] = []
49
+ candidate_counts: list[int] = []
50
+ trace_overhead_ms: list[float] = []
51
+ measured_pipeline_ms: list[float] = []
52
+ for _ in range(5):
53
+ query = "Python automation local project"
54
+ started = time.perf_counter()
55
+ retrieved = await services["retrieval"].retrieve(
56
+ RetrievalQuery(text=query, k=25)
57
+ )
58
+ retrieval_ms.append((time.perf_counter() - started) * 1000)
59
+ started = time.perf_counter()
60
+ explained = await explanation_service.explain(ExplanationRequest(
61
+ query=query, budget=1000, limit=25, graph=False,
62
+ ))
63
+ full_explain_ms.append((time.perf_counter() - started) * 1000)
64
+ candidate_counts.append(len(explained.candidates))
65
+ trace_bytes.append(len(explained.model_dump_json().encode("utf-8")))
66
+ trace_overhead_ms.append(explained.explanation_overhead_ms)
67
+ measured_pipeline_ms.append(explained.measured_pipeline_ms)
68
+
69
+ started = time.perf_counter()
70
+ compile_retrieved = await services["retrieval"].retrieve(RetrievalQuery(text=query, k=25))
71
+ selection = services["optimizer"].optimize(query, compile_retrieved.memories,
72
+ ContextBudget(max_tokens=1000))
73
+ await services["compilation"].compile(
74
+ query, selection, CompilationConfig(budget=1000)
75
+ )
76
+ compile_ms.append((time.perf_counter() - started) * 1000)
77
+ baseline = statistics.mean(compile_ms)
78
+ explained_mean = statistics.mean(full_explain_ms)
79
+ report["datasets"][str(size)] = {
80
+ "mean_ms": round(explained_mean, 3),
81
+ "median_ms": round(statistics.median(full_explain_ms), 3),
82
+ "p95_ms": round(_p95(full_explain_ms), 3),
83
+ "retrieval_without_explanation_mean_ms": round(statistics.mean(retrieval_ms), 3),
84
+ "compile_without_explanation_mean_ms": round(baseline, 3),
85
+ "compile_with_explanation_mean_ms": round(explained_mean, 3),
86
+ "measured_pipeline_mean_ms": round(statistics.mean(measured_pipeline_ms), 3),
87
+ "explanation_overhead_ms": round(statistics.mean(trace_overhead_ms), 3),
88
+ "explanation_overhead_percent": round(statistics.mean(trace_overhead_ms) / statistics.mean(measured_pipeline_ms) * 100, 2) if statistics.mean(measured_pipeline_ms) else 0.0,
89
+ "trace_size_bytes_mean": round(statistics.mean(trace_bytes)),
90
+ "candidate_count_mean": round(statistics.mean(candidate_counts), 2),
91
+ "samples": 5,
92
+ }
93
+ finally:
94
+ await services["database"].close()
95
+ return report
96
+
97
+
98
+ def main() -> None:
99
+ print(json.dumps(asyncio.run(measure()), indent=2))
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()