clm-kernel 0.0.0__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 (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. clm_kernel-0.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,333 @@
1
+ """
2
+ Persistent Vector Indexer
3
+
4
+ Manages automatic indexing of MCards into the vector store,
5
+ with persistent storage alongside the main MCard database.
6
+ """
7
+
8
+ import logging
9
+ import threading
10
+ from collections.abc import Callable
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from clm.layer0.model.card import MCard
15
+ from clm.layer2.card_collection import CardCollection
16
+
17
+ from .config import DEFAULT_RAG_CONFIG, RAGConfig
18
+ from .embeddings import OllamaEmbeddingProvider
19
+ from .vector import MCardVectorStore
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class PersistentIndexer:
25
+ """
26
+ Manages persistent vector indexing for MCard collections.
27
+
28
+ Features:
29
+ - Automatic indexing when MCards are added
30
+ - Persistent vector database alongside MCard database
31
+ - Background indexing for large collections
32
+ - Index status tracking
33
+
34
+ Usage:
35
+ from clm.layer3.rag import PersistentIndexer
36
+
37
+ indexer = PersistentIndexer() # Uses default collection
38
+
39
+ # Index all existing content
40
+ stats = indexer.index_all()
41
+
42
+ # Search
43
+ results = indexer.search("query")
44
+ """
45
+
46
+ _instance = None
47
+ _lock = threading.Lock()
48
+
49
+ def __new__(cls, *args, **kwargs):
50
+ """Singleton pattern for global indexer."""
51
+ if cls._instance is None:
52
+ with cls._lock:
53
+ if cls._instance is None:
54
+ cls._instance = super().__new__(cls)
55
+ cls._instance._initialized = False
56
+ return cls._instance
57
+
58
+ def __init__(
59
+ self,
60
+ collection: CardCollection = None,
61
+ config: RAGConfig = None,
62
+ vector_db_path: str = None,
63
+ auto_index: bool = False,
64
+ ):
65
+ """
66
+ Initialize persistent indexer.
67
+
68
+ Args:
69
+ collection: MCard collection (uses default if None)
70
+ config: RAG configuration
71
+ vector_db_path: Path to vector database (auto-derived if None)
72
+ auto_index: Automatically index new MCards
73
+ """
74
+ if self._initialized:
75
+ return
76
+
77
+ from clm.layer2.card_collection import get_default_collection
78
+ default_collection = get_default_collection()
79
+
80
+ self.collection = collection or default_collection
81
+ self.config = config or DEFAULT_RAG_CONFIG
82
+ self.auto_index = auto_index
83
+
84
+ # Derive vector DB path from MCard DB path
85
+ if vector_db_path is None:
86
+ mcard_db_path = self._get_mcard_db_path()
87
+ if mcard_db_path and mcard_db_path != ":memory:":
88
+ base_path = Path(mcard_db_path)
89
+ vector_db_path = str(base_path.parent / f"{base_path.stem}_vectors.db")
90
+ else:
91
+ vector_db_path = ":memory:"
92
+
93
+ self.vector_db_path = vector_db_path
94
+
95
+ # Initialize embedding provider
96
+ self.embedder = OllamaEmbeddingProvider(
97
+ model=self.config.embedding_model, base_url=self.config.ollama_base_url
98
+ )
99
+
100
+ # Initialize vector store
101
+ self.vector_store = MCardVectorStore(
102
+ db_path=self.vector_db_path,
103
+ config=self.config,
104
+ embedding_provider=self.embedder,
105
+ )
106
+
107
+ # Track indexing state
108
+ self._indexing_in_progress = False
109
+ self._indexed_hashes: set[str] = set()
110
+ self._load_indexed_hashes()
111
+
112
+ self._initialized = True
113
+ logger.info(f"PersistentIndexer initialized: {self.vector_db_path}")
114
+
115
+ def _get_mcard_db_path(self) -> str | None:
116
+ """Get the path of the MCard database."""
117
+ try:
118
+ if hasattr(self.collection, "engine"):
119
+ engine = self.collection.engine
120
+ if hasattr(engine, "connection") and hasattr(
121
+ engine.connection, "db_path"
122
+ ):
123
+ return engine.connection.db_path
124
+ except Exception:
125
+ pass
126
+ return None
127
+
128
+ def _load_indexed_hashes(self):
129
+ """Load the set of already-indexed hashes."""
130
+ try:
131
+ cursor = self.vector_store.conn.cursor()
132
+ cursor.execute("SELECT DISTINCT hash FROM mcard_vector_metadata")
133
+ self._indexed_hashes = {row[0] for row in cursor.fetchall()}
134
+ logger.debug(f"Loaded {len(self._indexed_hashes)} indexed hashes")
135
+ except Exception as e:
136
+ logger.warning(f"Failed to load indexed hashes: {e}")
137
+ self._indexed_hashes = set()
138
+
139
+ def is_indexed(self, hash: str) -> bool:
140
+ """Check if an MCard is already indexed."""
141
+ return hash in self._indexed_hashes or self.vector_store.is_indexed(hash)
142
+
143
+ def index_mcard(self, mcard: MCard, force: bool = False) -> bool:
144
+ """
145
+ Index a single MCard.
146
+
147
+ Args:
148
+ mcard: MCard to index
149
+ force: Re-index even if already indexed
150
+
151
+ Returns:
152
+ True if indexed successfully
153
+ """
154
+ if not force and self.is_indexed(mcard.hash):
155
+ logger.debug(f"MCard {mcard.hash[:8]} already indexed, skipping")
156
+ return True
157
+
158
+ try:
159
+ count = self.vector_store.index(mcard)
160
+ if count > 0:
161
+ self._indexed_hashes.add(mcard.hash)
162
+ logger.debug(f"Indexed MCard {mcard.hash[:8]} ({count} vectors)")
163
+ return True
164
+ return False
165
+ except Exception as e:
166
+ logger.error(f"Failed to index MCard {mcard.hash[:8]}: {e}", exc_info=True)
167
+ return False
168
+
169
+ def index_all(
170
+ self,
171
+ force: bool = False,
172
+ progress_callback: Callable[[int, int], None] = None,
173
+ _batch_size: int = 50,
174
+ ) -> dict[str, int]:
175
+ """
176
+ Index all MCards in the collection.
177
+
178
+ Args:
179
+ force: Re-index even if already indexed
180
+ progress_callback: Optional callback(current, total)
181
+ batch_size: Number of cards to process at once
182
+
183
+ Returns:
184
+ Dictionary with 'indexed', 'skipped', 'failed', 'total' counts
185
+ """
186
+ if self._indexing_in_progress:
187
+ logger.warning("Indexing already in progress")
188
+ return {
189
+ "indexed": 0,
190
+ "skipped": 0,
191
+ "failed": 0,
192
+ "total": 0,
193
+ "status": "busy",
194
+ }
195
+
196
+ self._indexing_in_progress = True
197
+ stats = {"indexed": 0, "skipped": 0, "failed": 0, "total": 0}
198
+
199
+ try:
200
+ # Get all cards
201
+ all_cards = self.collection.get_all_mcards_raw()
202
+ stats["total"] = len(all_cards)
203
+
204
+ for i, mcard in enumerate(all_cards):
205
+ if not force and self.is_indexed(mcard.hash):
206
+ stats["skipped"] += 1
207
+ elif self.index_mcard(mcard, force=force):
208
+ stats["indexed"] += 1
209
+ else:
210
+ stats["failed"] += 1
211
+
212
+ # Progress callback
213
+ if progress_callback and (i + 1) % 10 == 0:
214
+ progress_callback(i + 1, stats["total"])
215
+
216
+ logger.info(f"Indexing complete: {stats}")
217
+
218
+ finally:
219
+ self._indexing_in_progress = False
220
+
221
+ return stats
222
+
223
+ def search(self, query: str, k: int = None, hybrid: bool = True) -> list[Any]:
224
+ """
225
+ Search for similar MCards.
226
+
227
+ Args:
228
+ query: Search query
229
+ k: Number of results
230
+ hybrid: Use hybrid (vector + FTS) search
231
+
232
+ Returns:
233
+ List of VectorSearchResult
234
+ """
235
+ if hybrid and self.config.enable_hybrid_search:
236
+ return self.vector_store.hybrid_search(query, k)
237
+ else:
238
+ return self.vector_store.search(query, k)
239
+
240
+ def delete(self, hash: str) -> bool:
241
+ """Delete an MCard from the index."""
242
+ count = self.vector_store.delete(hash)
243
+ if count > 0:
244
+ self._indexed_hashes.discard(hash)
245
+ return True
246
+ return False
247
+
248
+ def clear(self):
249
+ """Clear the entire vector index."""
250
+ cursor = self.vector_store.conn.cursor()
251
+ cursor.execute("DELETE FROM mcard_vector_metadata")
252
+ cursor.execute("DELETE FROM mcard_embeddings")
253
+ if self.config.enable_hybrid_search:
254
+ cursor.execute("DELETE FROM mcard_fts")
255
+ self.vector_store.conn.commit()
256
+ self._indexed_hashes.clear()
257
+ logger.info("Vector index cleared")
258
+
259
+ def get_stats(self) -> dict[str, Any]:
260
+ """Get indexer statistics."""
261
+ return {
262
+ "vector_db_path": self.vector_db_path,
263
+ "embedding_model": self.embedder.model_name,
264
+ "dimensions": self.embedder.dimensions,
265
+ "indexed_count": len(self._indexed_hashes),
266
+ "vector_count": self.vector_store.count(),
267
+ "unique_mcards": self.vector_store.count_unique(),
268
+ "has_vec_extension": self.vector_store.has_vec_extension,
269
+ "hybrid_search_enabled": self.config.enable_hybrid_search,
270
+ "indexing_in_progress": self._indexing_in_progress,
271
+ }
272
+
273
+ @classmethod
274
+ def reset_instance(cls):
275
+ """Reset the singleton instance (for testing)."""
276
+ with cls._lock:
277
+ cls._instance = None
278
+
279
+
280
+ # ─────────────────────────────────────────────────────────────────────────────
281
+ # Global Convenience Functions
282
+ # ─────────────────────────────────────────────────────────────────────────────
283
+
284
+ _default_indexer: PersistentIndexer | None = None
285
+
286
+
287
+ def get_indexer(
288
+ collection: CardCollection = None, config: RAGConfig = None
289
+ ) -> PersistentIndexer:
290
+ """
291
+ Get or create the default persistent indexer.
292
+
293
+ Args:
294
+ collection: Optional collection override
295
+ config: Optional configuration override
296
+
297
+ Returns:
298
+ PersistentIndexer instance
299
+ """
300
+ global _default_indexer
301
+
302
+ if _default_indexer is None:
303
+ _default_indexer = PersistentIndexer(collection=collection, config=config)
304
+
305
+ return _default_indexer
306
+
307
+
308
+ def semantic_search(query: str, k: int = 5) -> list[Any]:
309
+ """
310
+ Convenience function for semantic search.
311
+
312
+ Args:
313
+ query: Search query
314
+ k: Number of results
315
+
316
+ Returns:
317
+ List of VectorSearchResult
318
+ """
319
+ return get_indexer().search(query, k)
320
+
321
+
322
+ def index_mcard(mcard: MCard, force: bool = False) -> bool:
323
+ """
324
+ Convenience function to index an MCard.
325
+
326
+ Args:
327
+ mcard: MCard to index
328
+ force: Re-index if exists
329
+
330
+ Returns:
331
+ True if indexed
332
+ """
333
+ return get_indexer().index_mcard(mcard, force)
@@ -0,0 +1,52 @@
1
+ {
2
+ "embedding_models": {
3
+ "nomic-embed-text": {
4
+ "dimensions": 768,
5
+ "provider": "ollama",
6
+ "max_tokens": 8192,
7
+ "description": "High quality, general purpose embeddings"
8
+ },
9
+ "bge-m3": {
10
+ "dimensions": 1024,
11
+ "provider": "ollama",
12
+ "max_tokens": 8192,
13
+ "description": "Multi-lingual, multi-granularity embeddings"
14
+ },
15
+ "mxbai-embed-large": {
16
+ "dimensions": 1024,
17
+ "provider": "ollama",
18
+ "max_tokens": 512,
19
+ "description": "High quality embeddings for retrieval"
20
+ },
21
+ "all-minilm": {
22
+ "dimensions": 384,
23
+ "provider": "ollama",
24
+ "max_tokens": 256,
25
+ "description": "Fast, lightweight embeddings"
26
+ }
27
+ },
28
+ "vision_models": {
29
+ "moondream": {
30
+ "size": "1.7GB",
31
+ "description": "Moondream - Tiny, high-performance vision language model",
32
+ "capabilities": ["image_description", "visual_qa"]
33
+ },
34
+ "llama3.2-vision": {
35
+ "size": "7.9GB",
36
+ "description": "Llama 3.2 Vision - 11B multimodal model with strong OCR",
37
+ "capabilities": ["image_description", "ocr", "visual_qa"]
38
+ },
39
+ "llava": {
40
+ "size": "4.7GB",
41
+ "description": "LLaVA - Large Language and Vision Assistant",
42
+ "capabilities": ["image_description", "visual_qa"]
43
+ },
44
+ "minicpm-v": {
45
+ "size": "5.6GB",
46
+ "description": "MiniCPM-V - Efficient vision-language model",
47
+ "capabilities": ["image_description", "ocr", "multi_image"]
48
+ }
49
+ },
50
+ "default_embedding_model": "nomic-embed-text",
51
+ "default_vision_model": "moondream"
52
+ }
@@ -0,0 +1,313 @@
1
+ """
2
+ Semantic Versioning API for MCards
3
+
4
+ High-level API functions for managing MCard versions linked to handles
5
+ with semantic similarity detection.
6
+
7
+ This module provides a simplified interface for:
8
+ - Linking MCards to handles
9
+ - Retrieving version history with semantic info
10
+ - Comparing versions by semantic similarity
11
+ - Searching within a handle's version history
12
+
13
+ See: docs/architecture/Handle_Vector_Similarity_Design.md
14
+ """
15
+
16
+ import logging
17
+
18
+ from clm.layer0.model.card import MCard
19
+ from clm.layer3.rag.config import DEFAULT_RAG_CONFIG, RAGConfig
20
+ from clm.layer3.rag.vector import (
21
+ HandleVectorStore,
22
+ VersionSimilarityResult,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Default store instance (lazy-initialized)
28
+ _default_store: HandleVectorStore | None = None
29
+
30
+
31
+ def get_store(
32
+ db_path: str = None, config: RAGConfig = None, reinitialize: bool = False
33
+ ) -> HandleVectorStore:
34
+ """
35
+ Get the default HandleVectorStore instance.
36
+
37
+ Args:
38
+ db_path: Path to SQLite database (None = in-memory)
39
+ config: RAG configuration
40
+ reinitialize: Force re-initialization of the store
41
+
42
+ Returns:
43
+ HandleVectorStore instance
44
+ """
45
+ global _default_store
46
+
47
+ if _default_store is None or reinitialize:
48
+ _default_store = HandleVectorStore(
49
+ db_path=db_path, config=config or DEFAULT_RAG_CONFIG
50
+ )
51
+
52
+ return _default_store
53
+
54
+
55
+ # ─────────────────────────────────────────────────────────────────────────────
56
+ # Core API Functions
57
+ # ─────────────────────────────────────────────────────────────────────────────
58
+
59
+
60
+ def link_mcard_to_handle(
61
+ mcard: MCard, handle: str, is_current: bool = True, store: HandleVectorStore = None
62
+ ) -> bool:
63
+ """
64
+ Link an MCard to a handle with semantic embedding.
65
+
66
+ This function:
67
+ 1. Indexes the MCard content with vector embeddings
68
+ 2. Associates the MCard with the specified handle
69
+ 3. Computes semantic delta from previous version (if exists)
70
+ 4. Classifies the upgrade type based on similarity
71
+
72
+ Args:
73
+ mcard: MCard to link
74
+ handle: Handle name (stable identifier)
75
+ is_current: Whether this becomes the current version
76
+ store: Optional custom HandleVectorStore instance
77
+
78
+ Returns:
79
+ True if successful, False if indexing failed
80
+
81
+ Example:
82
+ >>> from clm.layer0.model.card import MCard
83
+ >>> doc = MCard(content="Introduction to Machine Learning...")
84
+ >>> link_mcard_to_handle(doc, "ml_intro")
85
+ True
86
+ """
87
+ store = store or get_store()
88
+ indexed = store.index_with_handle(mcard, handle, is_current=is_current)
89
+ return indexed > 0
90
+
91
+
92
+ def get_handle_version_history(
93
+ handle: str, store: HandleVectorStore = None
94
+ ) -> list[dict]:
95
+ """
96
+ Get version history for a handle with semantic info.
97
+
98
+ Args:
99
+ handle: Handle name to query
100
+ store: Optional custom HandleVectorStore instance
101
+
102
+ Returns:
103
+ List of version dicts with hash, order, timestamps,
104
+ embedding status, and upgrade type
105
+
106
+ Example:
107
+ >>> history = get_handle_version_history("ml_intro")
108
+ >>> for v in history:
109
+ ... print(f"v{v['version_order']}: {v['hash'][:8]} ({v['upgrade_type']})")
110
+ """
111
+ store = store or get_store()
112
+ versions = store.get_handle_versions(handle)
113
+
114
+ return [
115
+ {
116
+ "hash": v.hash,
117
+ "version_order": v.version_order,
118
+ "is_current": v.is_current,
119
+ "created_at": v.created_at,
120
+ "has_embedding": v.has_embedding,
121
+ "parent_hash": v.parent_hash,
122
+ "semantic_delta": v.semantic_delta,
123
+ "upgrade_type": v.upgrade_type,
124
+ }
125
+ for v in versions
126
+ ]
127
+
128
+
129
+ def compare_versions_by_similarity(
130
+ handle: str,
131
+ reference_hash: str = None,
132
+ metric: str = "cosine",
133
+ store: HandleVectorStore = None,
134
+ ) -> list[VersionSimilarityResult]:
135
+ """
136
+ Compare all versions of a handle by semantic similarity.
137
+
138
+ This is useful for:
139
+ - Understanding how content has evolved
140
+ - Finding which version is most similar to current
141
+ - Identifying major semantic drift points
142
+
143
+ Args:
144
+ handle: Handle name
145
+ reference_hash: Compare to this hash (default: current version)
146
+ metric: 'cosine' (higher = more similar) or 'euclidean' (lower = closer)
147
+ store: Optional custom HandleVectorStore instance
148
+
149
+ Returns:
150
+ Versions sorted by similarity to reference
151
+
152
+ Example:
153
+ >>> results = compare_versions_by_similarity("ml_intro")
154
+ >>> for r in results:
155
+ ... print(f"v{r.version_order}: sim={r.similarity_to_current:.4f}")
156
+ """
157
+ store = store or get_store()
158
+ return store.get_versions_by_similarity(
159
+ handle=handle, reference_hash=reference_hash, metric=metric
160
+ )
161
+
162
+
163
+ def search_within_handle(
164
+ handle: str, query: str, k: int = 10, store: HandleVectorStore = None
165
+ ) -> list[VersionSimilarityResult]:
166
+ """
167
+ Search semantically within a handle's version history.
168
+
169
+ Useful for finding which version of a document best matches
170
+ a specific concept, topic, or query.
171
+
172
+ Args:
173
+ handle: Handle name to filter by
174
+ query: Search query text
175
+ k: Number of results to return
176
+ store: Optional custom HandleVectorStore instance
177
+
178
+ Returns:
179
+ Matching versions sorted by query similarity
180
+
181
+ Example:
182
+ >>> results = search_within_handle("ml_intro", "neural networks")
183
+ >>> for r in results:
184
+ ... print(f"v{r.version_order}: query_sim={r.similarity_to_query:.4f}")
185
+ """
186
+ store = store or get_store()
187
+ return store.search_handle_versions(handle=handle, query=query, k=k)
188
+
189
+
190
+ def get_version_distances(
191
+ handle: str, cache: bool = True, store: HandleVectorStore = None
192
+ ) -> dict[tuple, float]:
193
+ """
194
+ Compute pairwise semantic distances between all versions.
195
+
196
+ Returns a dictionary mapping (hash_a, hash_b) tuples to
197
+ their cosine similarity scores.
198
+
199
+ Args:
200
+ handle: Handle name
201
+ cache: Whether to cache computed similarities
202
+ store: Optional custom HandleVectorStore instance
203
+
204
+ Returns:
205
+ Dict mapping (hash_a, hash_b) to cosine similarity [-1, 1]
206
+
207
+ Example:
208
+ >>> distances = get_version_distances("ml_intro")
209
+ >>> for (h1, h2), sim in distances.items():
210
+ ... print(f"{h1[:8]} <-> {h2[:8]}: {sim:.4f}")
211
+ """
212
+ store = store or get_store()
213
+ return store.compute_version_distances(handle=handle, cache=cache)
214
+
215
+
216
+ def find_most_similar_version(
217
+ handle: str, query: str, store: HandleVectorStore = None
218
+ ) -> VersionSimilarityResult | None:
219
+ """
220
+ Find the version most similar to a query.
221
+
222
+ Args:
223
+ handle: Handle name
224
+ query: Search query text
225
+ store: Optional custom HandleVectorStore instance
226
+
227
+ Returns:
228
+ Most similar version, or None if no versions exist
229
+ """
230
+ results = search_within_handle(handle, query, k=1, store=store)
231
+ return results[0] if results else None
232
+
233
+
234
+ def get_semantic_evolution(handle: str, store: HandleVectorStore = None) -> list[dict]:
235
+ """
236
+ Get the semantic evolution of a handle's versions.
237
+
238
+ Returns version history annotated with semantic deltas,
239
+ showing how content has evolved over time.
240
+
241
+ Args:
242
+ handle: Handle name
243
+ store: Optional custom HandleVectorStore instance
244
+
245
+ Returns:
246
+ List of version dicts with evolution info
247
+ """
248
+ store = store or get_store()
249
+ versions = store.get_handle_versions(handle)
250
+
251
+ evolution = []
252
+ for v in versions:
253
+ entry = {
254
+ "hash": v.hash,
255
+ "version": v.version_order,
256
+ "is_current": v.is_current,
257
+ "created_at": v.created_at,
258
+ }
259
+
260
+ if v.parent_hash:
261
+ entry["parent_hash"] = v.parent_hash
262
+ entry["semantic_delta"] = v.semantic_delta
263
+ entry["upgrade_type"] = v.upgrade_type
264
+
265
+ # Interpret the delta
266
+ if v.semantic_delta:
267
+ if v.semantic_delta >= 0.95:
268
+ entry["interpretation"] = "Nearly identical to parent"
269
+ elif v.semantic_delta >= 0.85:
270
+ entry["interpretation"] = "Minor changes from parent"
271
+ elif v.semantic_delta >= 0.70:
272
+ entry["interpretation"] = "Significant changes from parent"
273
+ else:
274
+ entry["interpretation"] = "Major semantic shift from parent"
275
+ else:
276
+ entry["interpretation"] = "Initial version (no parent)"
277
+
278
+ evolution.append(entry)
279
+
280
+ return evolution
281
+
282
+
283
+ # ─────────────────────────────────────────────────────────────────────────────
284
+ # Utility Functions
285
+ # ─────────────────────────────────────────────────────────────────────────────
286
+
287
+
288
+ def list_handles(store: HandleVectorStore = None) -> list[str]:
289
+ """
290
+ List all handles with indexed versions.
291
+
292
+ Args:
293
+ store: Optional custom HandleVectorStore instance
294
+
295
+ Returns:
296
+ List of handle names
297
+ """
298
+ store = store or get_store()
299
+ return store.list_handles()
300
+
301
+
302
+ def get_store_info(store: HandleVectorStore = None) -> dict:
303
+ """
304
+ Get information about the vector store.
305
+
306
+ Args:
307
+ store: Optional custom HandleVectorStore instance
308
+
309
+ Returns:
310
+ Dict with store statistics
311
+ """
312
+ store = store or get_store()
313
+ return store.get_info()