contextsynapse 1.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 (776) hide show
  1. contextsynapse/__init__.py +107 -0
  2. contextsynapse/__main__.py +9 -0
  3. contextsynapse/a2a/__init__.py +28 -0
  4. contextsynapse/a2a/client.py +120 -0
  5. contextsynapse/a2a/discovery.py +128 -0
  6. contextsynapse/a2a/handlers.py +164 -0
  7. contextsynapse/a2a/models.py +246 -0
  8. contextsynapse/a2a/streaming.py +92 -0
  9. contextsynapse/a2a/task_manager.py +275 -0
  10. contextsynapse/adapters/__init__.py +98 -0
  11. contextsynapse/adapters/_base.py +298 -0
  12. contextsynapse/adapters/autogen/__init__.py +4 -0
  13. contextsynapse/adapters/autogen/tools.py +34 -0
  14. contextsynapse/adapters/crewai/__init__.py +4 -0
  15. contextsynapse/adapters/crewai/tools.py +63 -0
  16. contextsynapse/adapters/langchain/__init__.py +19 -0
  17. contextsynapse/adapters/langchain/message_history.py +17 -0
  18. contextsynapse/adapters/langchain/retriever.py +158 -0
  19. contextsynapse/adapters/langchain/tools.py +43 -0
  20. contextsynapse/adapters/langgraph/__init__.py +21 -0
  21. contextsynapse/adapters/langgraph/checkpoint.py +223 -0
  22. contextsynapse/adapters/langgraph/context_tools.py +341 -0
  23. contextsynapse/adapters/langgraph/message_history.py +138 -0
  24. contextsynapse/adapters/langgraph/tools.py +43 -0
  25. contextsynapse/adapters/llamaindex/__init__.py +4 -0
  26. contextsynapse/adapters/llamaindex/tools.py +64 -0
  27. contextsynapse/adapters/openai/__init__.py +24 -0
  28. contextsynapse/adapters/openai/tools.py +206 -0
  29. contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
  30. contextsynapse/adapters/pydantic_ai/tools.py +36 -0
  31. contextsynapse/adapters/swarm/__init__.py +4 -0
  32. contextsynapse/adapters/swarm/tools.py +44 -0
  33. contextsynapse/agents/__init__.py +6 -0
  34. contextsynapse/agents/auto_capture.py +80 -0
  35. contextsynapse/agents/dispatcher.py +299 -0
  36. contextsynapse/agents/notifications.py +112 -0
  37. contextsynapse/aiql/__init__.py +71 -0
  38. contextsynapse/aiql/cache.py +341 -0
  39. contextsynapse/aiql/compiler/__init__.py +62 -0
  40. contextsynapse/aiql/compiler/optimizer.py +126 -0
  41. contextsynapse/aiql/compiler/planner.py +366 -0
  42. contextsynapse/aiql/compiler/statistics.py +235 -0
  43. contextsynapse/aiql/compiler/validator.py +425 -0
  44. contextsynapse/aiql/engine/__init__.py +11 -0
  45. contextsynapse/aiql/engine/chunk_storage.py +2584 -0
  46. contextsynapse/aiql/engine/executor.py +7737 -0
  47. contextsynapse/aiql/engine/extraction_integration.py +442 -0
  48. contextsynapse/aiql/engine/graph_builder.py +405 -0
  49. contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
  50. contextsynapse/aiql/engine/performance_monitor.py +289 -0
  51. contextsynapse/aiql/engine/postprocessing.py +365 -0
  52. contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
  53. contextsynapse/aiql/engine/redis_cache.py +106 -0
  54. contextsynapse/aiql/engine/streaming_json.py +366 -0
  55. contextsynapse/aiql/engine/streaming_parquet.py +568 -0
  56. contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
  57. contextsynapse/aiql/grammar/__init__.py +14 -0
  58. contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
  59. contextsynapse/aiql/grammar/base.py +71 -0
  60. contextsynapse/aiql/parser/__init__.py +46 -0
  61. contextsynapse/aiql/parser/aiql_parser.py +11330 -0
  62. contextsynapse/aiql/parser/base_parser.py +47 -0
  63. contextsynapse/api/__init__.py +5 -0
  64. contextsynapse/api/a2a_router.py +233 -0
  65. contextsynapse/api/admin_router.py +140 -0
  66. contextsynapse/api/agent_worker_router.py +978 -0
  67. contextsynapse/api/algorithms_router.py +317 -0
  68. contextsynapse/api/api.py +4144 -0
  69. contextsynapse/api/audit.py +204 -0
  70. contextsynapse/api/auth.py +441 -0
  71. contextsynapse/api/auth_router.py +537 -0
  72. contextsynapse/api/billing.py +261 -0
  73. contextsynapse/api/billing_router.py +275 -0
  74. contextsynapse/api/boundary_router.py +452 -0
  75. contextsynapse/api/cognition_router.py +116 -0
  76. contextsynapse/api/connector_router.py +237 -0
  77. contextsynapse/api/context_router.py +1619 -0
  78. contextsynapse/api/dashboard_router.py +11367 -0
  79. contextsynapse/api/errors.py +183 -0
  80. contextsynapse/api/events.py +88 -0
  81. contextsynapse/api/experiment_bootstrap.py +321 -0
  82. contextsynapse/api/experiment_orchestrator.py +1078 -0
  83. contextsynapse/api/experiment_router.py +2089 -0
  84. contextsynapse/api/feature_gate.py +89 -0
  85. contextsynapse/api/federation_router.py +108 -0
  86. contextsynapse/api/integrations.py +567 -0
  87. contextsynapse/api/integrations_router.py +461 -0
  88. contextsynapse/api/intelligence_router.py +1028 -0
  89. contextsynapse/api/metering.py +155 -0
  90. contextsynapse/api/metrics.py +112 -0
  91. contextsynapse/api/models.py +38 -0
  92. contextsynapse/api/monitoring_router.py +174 -0
  93. contextsynapse/api/pipeline_router.py +1437 -0
  94. contextsynapse/api/playground_conn.py +116 -0
  95. contextsynapse/api/projects_router.py +131 -0
  96. contextsynapse/api/queries_router.py +282 -0
  97. contextsynapse/api/quota.py +95 -0
  98. contextsynapse/api/rate_limit.py +64 -0
  99. contextsynapse/api/search_router.py +261 -0
  100. contextsynapse/api/shield_router.py +447 -0
  101. contextsynapse/api/tenant_guard.py +103 -0
  102. contextsynapse/api/tenant_quotas.py +231 -0
  103. contextsynapse/api/tenants.py +305 -0
  104. contextsynapse/api/users.py +646 -0
  105. contextsynapse/api/v1.py +167 -0
  106. contextsynapse/api/vertical_builder_router.py +387 -0
  107. contextsynapse/benchmark/__init__.py +15 -0
  108. contextsynapse/benchmark/__main__.py +64 -0
  109. contextsynapse/benchmark/agents.py +338 -0
  110. contextsynapse/benchmark/live_ab.py +338 -0
  111. contextsynapse/benchmark/reporter.py +149 -0
  112. contextsynapse/benchmark/runner.py +264 -0
  113. contextsynapse/benchmark/savings.py +266 -0
  114. contextsynapse/benchmark/token_counter.py +212 -0
  115. contextsynapse/benchmark/workloads.py +289 -0
  116. contextsynapse/buffer/__init__.py +154 -0
  117. contextsynapse/buffer/base.py +257 -0
  118. contextsynapse/buffer/duckdb_buffer.py +246 -0
  119. contextsynapse/buffer/file_buffer.py +229 -0
  120. contextsynapse/buffer/lmdb_buffer.py +286 -0
  121. contextsynapse/buffer/local_buffer.py +154 -0
  122. contextsynapse/buffer/redis_buffer.py +211 -0
  123. contextsynapse/buffer/sqlite_buffer.py +266 -0
  124. contextsynapse/cache.py +2 -0
  125. contextsynapse/cli/__init__.py +40 -0
  126. contextsynapse/cli/cli.py +475 -0
  127. contextsynapse/cli/config_cli.py +177 -0
  128. contextsynapse/cli/scaffold.py +465 -0
  129. contextsynapse/cognition/__init__.py +31 -0
  130. contextsynapse/cognition/derivation.py +55 -0
  131. contextsynapse/cognition/events.py +135 -0
  132. contextsynapse/cognition/feedback.py +52 -0
  133. contextsynapse/cognition/hallucination.py +90 -0
  134. contextsynapse/cognition/invalidation.py +70 -0
  135. contextsynapse/cognition/read_tracker.py +50 -0
  136. contextsynapse/collaboration.py +2 -0
  137. contextsynapse/comparison.py +2 -0
  138. contextsynapse/config/__init__.py +10 -0
  139. contextsynapse/config/connectors/amfi_nav.yaml +18 -0
  140. contextsynapse/config/connectors/auto_sector.yaml +20 -0
  141. contextsynapse/config/connectors/banking_sector.yaml +20 -0
  142. contextsynapse/config/connectors/energy_sector.yaml +20 -0
  143. contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
  144. contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
  145. contextsynapse/config/connectors/nse_price.yaml +61 -0
  146. contextsynapse/config/connectors/pharma_sector.yaml +20 -0
  147. contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
  148. contextsynapse/config/context_presets/ai_research.json +25 -0
  149. contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
  150. contextsynapse/config/context_presets/claude_conversations.json +19 -0
  151. contextsynapse/config/context_presets/legal_compliance.json +21 -0
  152. contextsynapse/config/context_presets/meeting_notes.json +21 -0
  153. contextsynapse/config/context_presets/slack_engineering.json +21 -0
  154. contextsynapse/config/context_presets/tech_climate.json +24 -0
  155. contextsynapse/config/context_presets/toi_politics.json +25 -0
  156. contextsynapse/config/database_config.py +453 -0
  157. contextsynapse/config/deployment.py +221 -0
  158. contextsynapse/config/gpt_action_schema.json +164 -0
  159. contextsynapse/config/indicators/india_macro.yaml +213 -0
  160. contextsynapse/config/logging_config.py +88 -0
  161. contextsynapse/config/mode_config.py +223 -0
  162. contextsynapse/config/schemas/api_spec.yaml +48 -0
  163. contextsynapse/config/schemas/architecture_doc.yaml +51 -0
  164. contextsynapse/config/schemas/chat_history.yaml +90 -0
  165. contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
  166. contextsynapse/config/schemas/claude_conversation.yaml +148 -0
  167. contextsynapse/config/schemas/contract.yaml +48 -0
  168. contextsynapse/config/schemas/conversation.yaml +167 -0
  169. contextsynapse/config/schemas/database.yaml +74 -0
  170. contextsynapse/config/schemas/decision.yaml +72 -0
  171. contextsynapse/config/schemas/ecommerce.yaml +56 -0
  172. contextsynapse/config/schemas/education.yaml +42 -0
  173. contextsynapse/config/schemas/healthcare.yaml +133 -0
  174. contextsynapse/config/schemas/hr_document.yaml +48 -0
  175. contextsynapse/config/schemas/invoice.yaml +40 -0
  176. contextsynapse/config/schemas/knowledge_base.yaml +75 -0
  177. contextsynapse/config/schemas/legal.yaml +42 -0
  178. contextsynapse/config/schemas/marketing_content.yaml +48 -0
  179. contextsynapse/config/schemas/meeting_notes.yaml +51 -0
  180. contextsynapse/config/schemas/news_article.yaml +61 -0
  181. contextsynapse/config/schemas/real_estate.yaml +49 -0
  182. contextsynapse/config/schemas/requirements_doc.yaml +48 -0
  183. contextsynapse/config/schemas/research_paper.yaml +56 -0
  184. contextsynapse/config/schemas/resume.yaml +49 -0
  185. contextsynapse/config/schemas/retail.yaml +42 -0
  186. contextsynapse/config/schemas/rule_meta.yaml +173 -0
  187. contextsynapse/config/schemas/rules.yaml +72 -0
  188. contextsynapse/config/schemas/screener_meta.yaml +236 -0
  189. contextsynapse/config/schemas/sdlc.yaml +103 -0
  190. contextsynapse/config/schemas/session_graph.yaml +172 -0
  191. contextsynapse/config/schemas/support_ticket.yaml +48 -0
  192. contextsynapse/config/schemas/web.yaml +64 -0
  193. contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
  194. contextsynapse/config/sensors/global_risk.yaml +53 -0
  195. contextsynapse/config/sensors/government_policy.yaml +54 -0
  196. contextsynapse/config/sensors/supply_chain.yaml +50 -0
  197. contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
  198. contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
  199. contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
  200. contextsynapse/config/sensors/templates/market_data.yaml +118 -0
  201. contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
  202. contextsynapse/config/sensors/weather.yaml +52 -0
  203. contextsynapse/config/storage_strategy.py +202 -0
  204. contextsynapse/config/templates/competitive_intel.yaml +39 -0
  205. contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
  206. contextsynapse/config/templates/market_analysis.yaml +58 -0
  207. contextsynapse/connectors/__init__.py +15 -0
  208. contextsynapse/connectors/api_router.py +121 -0
  209. contextsynapse/connectors/base.py +357 -0
  210. contextsynapse/connectors/financial/audio.py +170 -0
  211. contextsynapse/connectors/financial/youtube.py +272 -0
  212. contextsynapse/connectors/github.py +164 -0
  213. contextsynapse/connectors/jira.py +120 -0
  214. contextsynapse/connectors/kafka_connector.py +124 -0
  215. contextsynapse/connectors/pipeline_connector.py +747 -0
  216. contextsynapse/connectors/registry.py +116 -0
  217. contextsynapse/connectors/scheduler.py +87 -0
  218. contextsynapse/connectors/universal.py +360 -0
  219. contextsynapse/context/__init__.py +96 -0
  220. contextsynapse/context/__main__.py +4 -0
  221. contextsynapse/context/acl.py +125 -0
  222. contextsynapse/context/agent_card.py +201 -0
  223. contextsynapse/context/agent_memory.py +814 -0
  224. contextsynapse/context/agents.py +472 -0
  225. contextsynapse/context/agents_redis.py +391 -0
  226. contextsynapse/context/assembled.py +204 -0
  227. contextsynapse/context/attribution.py +113 -0
  228. contextsynapse/context/blob.py +184 -0
  229. contextsynapse/context/boundaries.py +187 -0
  230. contextsynapse/context/boundary.py +507 -0
  231. contextsynapse/context/bundle.py +155 -0
  232. contextsynapse/context/cli.py +519 -0
  233. contextsynapse/context/collaboration.py +165 -0
  234. contextsynapse/context/compiler.py +604 -0
  235. contextsynapse/context/composite.py +536 -0
  236. contextsynapse/context/compression.py +147 -0
  237. contextsynapse/context/context_manager.py +888 -0
  238. contextsynapse/context/context_schema.py +90 -0
  239. contextsynapse/context/context_schema.yaml +105 -0
  240. contextsynapse/context/context_units.py +891 -0
  241. contextsynapse/context/conversation.py +488 -0
  242. contextsynapse/context/dedup.py +207 -0
  243. contextsynapse/context/document_processor.py +698 -0
  244. contextsynapse/context/embedding_hooks.py +338 -0
  245. contextsynapse/context/execution_schema.py +274 -0
  246. contextsynapse/context/fan_out.py +138 -0
  247. contextsynapse/context/frozen.py +80 -0
  248. contextsynapse/context/gravity.py +227 -0
  249. contextsynapse/context/hub.py +1401 -0
  250. contextsynapse/context/ingest.py +391 -0
  251. contextsynapse/context/injection.py +226 -0
  252. contextsynapse/context/interaction_graphifier.py +562 -0
  253. contextsynapse/context/layers.py +574 -0
  254. contextsynapse/context/projection.py +1195 -0
  255. contextsynapse/context/promotion.py +634 -0
  256. contextsynapse/context/proof.py +131 -0
  257. contextsynapse/context/propagation.py +577 -0
  258. contextsynapse/context/pubsub.py +135 -0
  259. contextsynapse/context/quality.py +1054 -0
  260. contextsynapse/context/quality_tracker.py +270 -0
  261. contextsynapse/context/scoping.py +370 -0
  262. contextsynapse/context/seed.py +992 -0
  263. contextsynapse/context/session.py +1142 -0
  264. contextsynapse/context/session_graph.py +593 -0
  265. contextsynapse/context/session_memory.py +309 -0
  266. contextsynapse/context/session_resolver.py +111 -0
  267. contextsynapse/context/skill_learning.py +237 -0
  268. contextsynapse/context/smart_budget.py +129 -0
  269. contextsynapse/context/source_policy.py +242 -0
  270. contextsynapse/context/store_factory.py +53 -0
  271. contextsynapse/context/sync.py +648 -0
  272. contextsynapse/context/token.py +236 -0
  273. contextsynapse/context/vector_integration.py +297 -0
  274. contextsynapse/context/webhooks.py +194 -0
  275. contextsynapse/context/working_memory.py +224 -0
  276. contextsynapse/core/__init__.py +18 -0
  277. contextsynapse/core/archiver.py +237 -0
  278. contextsynapse/core/change_stream.py +282 -0
  279. contextsynapse/core/checkpoint.py +310 -0
  280. contextsynapse/core/cloud_storage.py +387 -0
  281. contextsynapse/core/context_state.py +651 -0
  282. contextsynapse/core/cron.py +324 -0
  283. contextsynapse/core/datasource.py +643 -0
  284. contextsynapse/core/db.py +289 -0
  285. contextsynapse/core/federation.py +283 -0
  286. contextsynapse/core/graph_coordinator.py +201 -0
  287. contextsynapse/core/graph_intelligence.py +388 -0
  288. contextsynapse/core/graph_structures.py +106 -0
  289. contextsynapse/core/graph_sync.py +157 -0
  290. contextsynapse/core/hybrid_graph_storage.py +2998 -0
  291. contextsynapse/core/lifecycle.py +691 -0
  292. contextsynapse/core/multiworker.py +378 -0
  293. contextsynapse/core/pruning.py +312 -0
  294. contextsynapse/core/redis_registry.py +524 -0
  295. contextsynapse/core/registry.py +1043 -0
  296. contextsynapse/core/registry_factory.py +51 -0
  297. contextsynapse/core/registry_metadata.py +294 -0
  298. contextsynapse/core/replay.py +354 -0
  299. contextsynapse/core/replication.py +339 -0
  300. contextsynapse/core/time_travel.py +260 -0
  301. contextsynapse/core/write_behind.py +152 -0
  302. contextsynapse/core/write_context.py +39 -0
  303. contextsynapse/db/__init__.py +1 -0
  304. contextsynapse/db/postgres.py +267 -0
  305. contextsynapse/db/profile.py +236 -0
  306. contextsynapse/db/rules.py +328 -0
  307. contextsynapse/demo/__init__.py +0 -0
  308. contextsynapse/demo/report.py +147 -0
  309. contextsynapse/demo/runner.py +520 -0
  310. contextsynapse/demo/scenario.py +165 -0
  311. contextsynapse/demo/session_demo.py +280 -0
  312. contextsynapse/demo/session_report.py +143 -0
  313. contextsynapse/demo/universal_runner.py +257 -0
  314. contextsynapse/engine/__init__.py +15 -0
  315. contextsynapse/engine/core.py +555 -0
  316. contextsynapse/extraction/__init__.py +69 -0
  317. contextsynapse/extraction/cli.py +274 -0
  318. contextsynapse/extraction/config.py +107 -0
  319. contextsynapse/extraction/examples/example_usage.py +118 -0
  320. contextsynapse/extraction/examples/sample_manifest.json +53 -0
  321. contextsynapse/extraction/examples/sample_normalized.json +185 -0
  322. contextsynapse/extraction/extract_engine.py +440 -0
  323. contextsynapse/extraction/extractors/__init__.py +37 -0
  324. contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
  325. contextsynapse/extraction/extractors/csv_extractor.py +83 -0
  326. contextsynapse/extraction/extractors/docx_extractor.py +129 -0
  327. contextsynapse/extraction/extractors/excel_extractor.py +152 -0
  328. contextsynapse/extraction/extractors/html_extractor.py +146 -0
  329. contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
  330. contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
  331. contextsynapse/extraction/extractors/register_extractors.py +63 -0
  332. contextsynapse/extraction/extractors/text_extractor.py +65 -0
  333. contextsynapse/extraction/extractors/txt_extractor.py +120 -0
  334. contextsynapse/extraction/extractors/website_extractor.py +246 -0
  335. contextsynapse/extraction/fact_extractor.py +236 -0
  336. contextsynapse/extraction/file_manager.py +353 -0
  337. contextsynapse/extraction/hierarchy.py +239 -0
  338. contextsynapse/extraction/id_generator.py +450 -0
  339. contextsynapse/extraction/layout_detector.py +409 -0
  340. contextsynapse/extraction/llm_entity_extractor.py +301 -0
  341. contextsynapse/extraction/normalized_store.py +480 -0
  342. contextsynapse/extraction/normalizer.py +594 -0
  343. contextsynapse/extraction/parquet_writer.py +598 -0
  344. contextsynapse/extraction/post_processors/__init__.py +40 -0
  345. contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
  346. contextsynapse/extraction/ray_normalization.py +233 -0
  347. contextsynapse/extraction/ray_runner.py +207 -0
  348. contextsynapse/extraction/redis_schema_registry.py +222 -0
  349. contextsynapse/extraction/registry.py +204 -0
  350. contextsynapse/extraction/schema_loader.py +663 -0
  351. contextsynapse/extraction/schema_registry.py +230 -0
  352. contextsynapse/extraction/semantic_normalizer.py +245 -0
  353. contextsynapse/extraction/version_manager.py +314 -0
  354. contextsynapse/gateway/__init__.py +24 -0
  355. contextsynapse/gateway/action_emitter.py +212 -0
  356. contextsynapse/gateway/cost_tracker.py +508 -0
  357. contextsynapse/gateway/gateway.py +377 -0
  358. contextsynapse/gateway/policy.py +303 -0
  359. contextsynapse/governance/__init__.py +13 -0
  360. contextsynapse/governance/layer.py +513 -0
  361. contextsynapse/ingestion/__init__.py +15 -0
  362. contextsynapse/ingestion/amplifier.py +288 -0
  363. contextsynapse/ingestion/async_ingest.py +463 -0
  364. contextsynapse/ingestion/chunker.py +303 -0
  365. contextsynapse/ingestion/chunking.py +58 -0
  366. contextsynapse/ingestion/cleaner.py +190 -0
  367. contextsynapse/ingestion/cleanse.py +252 -0
  368. contextsynapse/ingestion/connectors/__init__.py +38 -0
  369. contextsynapse/ingestion/connectors/base.py +300 -0
  370. contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
  371. contextsynapse/ingestion/connectors/claude_memory.py +274 -0
  372. contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
  373. contextsynapse/ingestion/content_detector.py +80 -0
  374. contextsynapse/ingestion/content_monitor.py +502 -0
  375. contextsynapse/ingestion/dedup.py +106 -0
  376. contextsynapse/ingestion/feed.py +640 -0
  377. contextsynapse/ingestion/filters.py +461 -0
  378. contextsynapse/ingestion/graph_builder.py +705 -0
  379. contextsynapse/ingestion/input_classifier.py +307 -0
  380. contextsynapse/ingestion/job_manager.py +571 -0
  381. contextsynapse/ingestion/llm_extractor.py +610 -0
  382. contextsynapse/ingestion/orchestrator.py +116 -0
  383. contextsynapse/ingestion/parsers/__init__.py +19 -0
  384. contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
  385. contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
  386. contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
  387. contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
  388. contextsynapse/ingestion/pipeline_context.py +228 -0
  389. contextsynapse/ingestion/pipeline_store.py +1195 -0
  390. contextsynapse/ingestion/queue.py +133 -0
  391. contextsynapse/ingestion/reprocess.py +179 -0
  392. contextsynapse/ingestion/scenario_router.py +224 -0
  393. contextsynapse/ingestion/schema_extractor.py +759 -0
  394. contextsynapse/ingestion/schema_validator.py +274 -0
  395. contextsynapse/ingestion/sdlc_ingest.py +87 -0
  396. contextsynapse/ingestion/smart_ingest.py +2451 -0
  397. contextsynapse/ingestion/source_connector.py +171 -0
  398. contextsynapse/ingestion/stage_executor.py +2095 -0
  399. contextsynapse/ingestion/stages/__init__.py +9 -0
  400. contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
  401. contextsynapse/ingestion/strategies.py +386 -0
  402. contextsynapse/ingestion/universal/__init__.py +4 -0
  403. contextsynapse/ingestion/universal/_operator_registry.py +59 -0
  404. contextsynapse/ingestion/universal/ingest_content.py +32 -0
  405. contextsynapse/ingestion/universal/operators/__init__.py +1 -0
  406. contextsynapse/ingestion/universal/operators/base.py +19 -0
  407. contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
  408. contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
  409. contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
  410. contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
  411. contextsynapse/ingestion/universal/operators/embed.py +80 -0
  412. contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
  413. contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
  414. contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
  415. contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
  416. contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
  417. contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
  418. contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
  419. contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
  420. contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
  421. contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
  422. contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
  423. contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
  424. contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
  425. contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
  426. contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
  427. contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
  428. contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
  429. contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
  430. contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
  431. contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
  432. contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
  433. contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
  434. contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
  435. contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
  436. contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
  437. contextsynapse/ingestion/universal/shared_signals.py +161 -0
  438. contextsynapse/ingestion/universal/stage_executor.py +219 -0
  439. contextsynapse/ingestion/web_crawler.py +265 -0
  440. contextsynapse/intelligence/__init__.py +53 -0
  441. contextsynapse/intelligence/alerts.py +198 -0
  442. contextsynapse/intelligence/collector.py +655 -0
  443. contextsynapse/intelligence/comparison.py +198 -0
  444. contextsynapse/intelligence/config.py +56 -0
  445. contextsynapse/intelligence/conflict_detector.py +182 -0
  446. contextsynapse/intelligence/context_gaps.py +192 -0
  447. contextsynapse/intelligence/context_radar.py +135 -0
  448. contextsynapse/intelligence/correlation_engine.py +416 -0
  449. contextsynapse/intelligence/correlation_template.py +120 -0
  450. contextsynapse/intelligence/dedup.py +296 -0
  451. contextsynapse/intelligence/event_bus.py +91 -0
  452. contextsynapse/intelligence/feedback_loop.py +101 -0
  453. contextsynapse/intelligence/freshness.py +307 -0
  454. contextsynapse/intelligence/fundamental_feed.py +551 -0
  455. contextsynapse/intelligence/geo_data.py +170 -0
  456. contextsynapse/intelligence/geo_reference.py +112 -0
  457. contextsynapse/intelligence/graph_fusion.py +465 -0
  458. contextsynapse/intelligence/heuristics.py +176 -0
  459. contextsynapse/intelligence/impact_tracker.py +336 -0
  460. contextsynapse/intelligence/macro_indicators.py +319 -0
  461. contextsynapse/intelligence/ocr.py +104 -0
  462. contextsynapse/intelligence/persistence.py +267 -0
  463. contextsynapse/intelligence/pipeline_health.py +130 -0
  464. contextsynapse/intelligence/price_feed.py +221 -0
  465. contextsynapse/intelligence/rate_limiter.py +130 -0
  466. contextsynapse/intelligence/reactive.py +411 -0
  467. contextsynapse/intelligence/runtime_context.py +390 -0
  468. contextsynapse/intelligence/sentiment_decay.py +426 -0
  469. contextsynapse/intelligence/session.py +277 -0
  470. contextsynapse/intelligence/signal_hierarchy.py +438 -0
  471. contextsynapse/intelligence/signals.py +185 -0
  472. contextsynapse/intelligence/source_watcher.py +132 -0
  473. contextsynapse/intelligence/table_enricher.py +256 -0
  474. contextsynapse/intelligence/tagger.py +319 -0
  475. contextsynapse/intelligence/timeseries.py +209 -0
  476. contextsynapse/intelligence/tracker.py +318 -0
  477. contextsynapse/intelligence/watchdog.py +392 -0
  478. contextsynapse/intelligence/watchdog_manager.py +263 -0
  479. contextsynapse/intelligence/webhook_receiver.py +165 -0
  480. contextsynapse/intelligence/weight_learner.py +234 -0
  481. contextsynapse/llm/__init__.py +8 -0
  482. contextsynapse/llm/client.py +348 -0
  483. contextsynapse/llm/rate_limiter.py +166 -0
  484. contextsynapse/logging_config.py +2 -0
  485. contextsynapse/marketplace/__init__.py +7 -0
  486. contextsynapse/marketplace/registry.py +173 -0
  487. contextsynapse/mcp/__init__.py +11 -0
  488. contextsynapse/mcp/__main__.py +4 -0
  489. contextsynapse/mcp/auth_middleware.py +212 -0
  490. contextsynapse/mcp/connection_pool.py +128 -0
  491. contextsynapse/mcp/server.py +986 -0
  492. contextsynapse/mcp/session_context.py +67 -0
  493. contextsynapse/memory/__init__.py +34 -0
  494. contextsynapse/memory/engine.py +475 -0
  495. contextsynapse/memory/temporal.py +327 -0
  496. contextsynapse/metadata/__init__.py +12 -0
  497. contextsynapse/metadata/metadata_db.py +272 -0
  498. contextsynapse/metadata/metadata_query_tool.py +300 -0
  499. contextsynapse/metadata/metadata_tracker.py +425 -0
  500. contextsynapse/models/__init__.py +27 -0
  501. contextsynapse/models/embedding_service.py +360 -0
  502. contextsynapse/models/model_registry.py +228 -0
  503. contextsynapse/multiworker.py +2 -0
  504. contextsynapse/pipelines/__init__.py +1 -0
  505. contextsynapse/pipelines/executor.py +851 -0
  506. contextsynapse/pipelines/filters.py +189 -0
  507. contextsynapse/pipelines/models.py +176 -0
  508. contextsynapse/pipelines/scheduler.py +268 -0
  509. contextsynapse/playground/__init__.py +1 -0
  510. contextsynapse/playground/templates.py +85 -0
  511. contextsynapse/plugins/__init__.py +43 -0
  512. contextsynapse/plugins/api.py +66 -0
  513. contextsynapse/plugins/base.py +350 -0
  514. contextsynapse/plugins/domain_interface.py +508 -0
  515. contextsynapse/plugins/domain_loader.py +241 -0
  516. contextsynapse/plugins/loader.py +148 -0
  517. contextsynapse/plugins/registry.py +265 -0
  518. contextsynapse/project/__init__.py +11 -0
  519. contextsynapse/project/code_context.py +807 -0
  520. contextsynapse/project/connectors/__init__.py +4 -0
  521. contextsynapse/project/connectors/github_connector.py +113 -0
  522. contextsynapse/project/connectors/jira_connector.py +160 -0
  523. contextsynapse/project/graph.py +1023 -0
  524. contextsynapse/project/project_context.py +545 -0
  525. contextsynapse/project/scanner_registry.py +61 -0
  526. contextsynapse/project/schema_manager.py +322 -0
  527. contextsynapse/project/schemas/knowledge.yaml +59 -0
  528. contextsynapse/project/schemas/sdlc.yaml +369 -0
  529. contextsynapse/project/sdlc_schema.py +172 -0
  530. contextsynapse/project/spec_parser.py +85 -0
  531. contextsynapse/project/task_lock.py +181 -0
  532. contextsynapse/project/task_stream.py +203 -0
  533. contextsynapse/project/team_tools.py +537 -0
  534. contextsynapse/py.typed +0 -0
  535. contextsynapse/realtime/__init__.py +6 -0
  536. contextsynapse/realtime/collaboration.py +147 -0
  537. contextsynapse/rules/__init__.py +10 -0
  538. contextsynapse/rules/engine.py +116 -0
  539. contextsynapse/rules/evaluators/__init__.py +6 -0
  540. contextsynapse/rules/evaluators/aggregate.py +205 -0
  541. contextsynapse/rules/evaluators/custom.py +170 -0
  542. contextsynapse/rules/evaluators/pre_action.py +125 -0
  543. contextsynapse/rules/evaluators/temporal.py +129 -0
  544. contextsynapse/rules/loader.py +52 -0
  545. contextsynapse/rules/models.py +124 -0
  546. contextsynapse/rules/universal.py +393 -0
  547. contextsynapse/scheduler.py +229 -0
  548. contextsynapse/schema/__init__.py +42 -0
  549. contextsynapse/schema/compiler.py +252 -0
  550. contextsynapse/schema/composer.py +158 -0
  551. contextsynapse/schema/dedup.py +73 -0
  552. contextsynapse/schema/derivation.py +172 -0
  553. contextsynapse/schema/sdl.py +326 -0
  554. contextsynapse/schema/validation_gate.py +124 -0
  555. contextsynapse/sdk/__init__.py +63 -0
  556. contextsynapse/sdk/agent.py +214 -0
  557. contextsynapse/sdk/client.py +269 -0
  558. contextsynapse/sdk/exceptions.py +23 -0
  559. contextsynapse/sdk/models.py +78 -0
  560. contextsynapse/sdk/session.py +408 -0
  561. contextsynapse/sdk/tracked.py +218 -0
  562. contextsynapse/sdk/worker.py +266 -0
  563. contextsynapse/sdk/workspace_client.py +98 -0
  564. contextsynapse/sdk/ws.py +41 -0
  565. contextsynapse/search/__init__.py +20 -0
  566. contextsynapse/search/embedding_cache.py +140 -0
  567. contextsynapse/search/enhanced_search.py +152 -0
  568. contextsynapse/search/fulltext.py +130 -0
  569. contextsynapse/search/graph_search.py +634 -0
  570. contextsynapse/search/lmdb_index.py +1218 -0
  571. contextsynapse/search/nl_to_aiql.py +199 -0
  572. contextsynapse/search/rag.py +1346 -0
  573. contextsynapse/search/rag_cache.py +167 -0
  574. contextsynapse/search/reasoning_chain.py +231 -0
  575. contextsynapse/search/redis_search.py +207 -0
  576. contextsynapse/search/retrieval_quality.py +314 -0
  577. contextsynapse/search/semantic_query.py +455 -0
  578. contextsynapse/search/text_resolver.py +159 -0
  579. contextsynapse/search/whoosh_search.py +373 -0
  580. contextsynapse/security/__init__.py +17 -0
  581. contextsynapse/security/audit.py +297 -0
  582. contextsynapse/security/audit_logger.py +97 -0
  583. contextsynapse/security/audit_trail.py +270 -0
  584. contextsynapse/security/auth_unified.py +284 -0
  585. contextsynapse/security/auto_tagger.py +230 -0
  586. contextsynapse/security/data_security.py +218 -0
  587. contextsynapse/security/encryption.py +207 -0
  588. contextsynapse/security/identity.py +447 -0
  589. contextsynapse/security/jwt_identity.py +59 -0
  590. contextsynapse/security/middleware.py +336 -0
  591. contextsynapse/security/pii.py +233 -0
  592. contextsynapse/security/rbac.py +96 -0
  593. contextsynapse/security/rls.py +205 -0
  594. contextsynapse/security/sanitize.py +233 -0
  595. contextsynapse/security/scoped_encryption.py +131 -0
  596. contextsynapse/security/tenant.py +401 -0
  597. contextsynapse/shield/__init__.py +21 -0
  598. contextsynapse/shield/anomaly.py +56 -0
  599. contextsynapse/shield/permissions.py +99 -0
  600. contextsynapse/shield/profile.py +103 -0
  601. contextsynapse/shield/shield.py +108 -0
  602. contextsynapse/shield/trust_engine.py +69 -0
  603. contextsynapse/skills/__init__.py +14 -0
  604. contextsynapse/skills/engine.py +233 -0
  605. contextsynapse/skills/loader.py +59 -0
  606. contextsynapse/skills/models.py +139 -0
  607. contextsynapse/storage/__init__.py +7 -0
  608. contextsynapse/storage/cache.py +208 -0
  609. contextsynapse/storage/columnar_store_v2.py +178 -0
  610. contextsynapse/storage/csr_graph_storage.py +587 -0
  611. contextsynapse/storage/csr_store.py +388 -0
  612. contextsynapse/storage/document_store.py +491 -0
  613. contextsynapse/storage/hnsw_index.py +294 -0
  614. contextsynapse/storage/hybrid_store_v2.py +278 -0
  615. contextsynapse/storage/lmdb_graph_storage.py +366 -0
  616. contextsynapse/storage/migration.py +279 -0
  617. contextsynapse/storage/namespace_store.py +346 -0
  618. contextsynapse/storage/pure_graph_storage.py +185 -0
  619. contextsynapse/storage/redis_graph_adapter.py +186 -0
  620. contextsynapse/storage/redis_graph_storage.py +375 -0
  621. contextsynapse/storage/router/__init__.py +29 -0
  622. contextsynapse/storage/router/duckdb_store.py +398 -0
  623. contextsynapse/storage/router/factory.py +83 -0
  624. contextsynapse/storage/router/interface.py +125 -0
  625. contextsynapse/storage/router/postgres_store.py +339 -0
  626. contextsynapse/storage/router/resolver.py +192 -0
  627. contextsynapse/storage/router/router.py +300 -0
  628. contextsynapse/storage/single_file_storage.py +391 -0
  629. contextsynapse/storage/storage_manager.py +214 -0
  630. contextsynapse/storage/storage_query_optimizer.py +314 -0
  631. contextsynapse/storage/wal.py +359 -0
  632. contextsynapse/temporal/__init__.py +0 -0
  633. contextsynapse/temporal/storage.py +106 -0
  634. contextsynapse/tests/__init__.py +11 -0
  635. contextsynapse/tests/regression/__init__.py +1 -0
  636. contextsynapse/tests/regression/add_metadata.py +395 -0
  637. contextsynapse/tests/regression/add_skip_flags.py +85 -0
  638. contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
  639. contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
  640. contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
  641. contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
  642. contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
  643. contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
  644. contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
  645. contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
  646. contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
  647. contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
  648. contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
  649. contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
  650. contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
  651. contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
  652. contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
  653. contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
  654. contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
  655. contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
  656. contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
  657. contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
  658. contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
  659. contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
  660. contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
  661. contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
  662. contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
  663. contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
  664. contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
  665. contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
  666. contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
  667. contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
  668. contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
  669. contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
  670. contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
  671. contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
  672. contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
  673. contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
  674. contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
  675. contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
  676. contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
  677. contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
  678. contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
  679. contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
  680. contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
  681. contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
  682. contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
  683. contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
  684. contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
  685. contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
  686. contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
  687. contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
  688. contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
  689. contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
  690. contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
  691. contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
  692. contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
  693. contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
  694. contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
  695. contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
  696. contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
  697. contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
  698. contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
  699. contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
  700. contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
  701. contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
  702. contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
  703. contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
  704. contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
  705. contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
  706. contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
  707. contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
  708. contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
  709. contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
  710. contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
  711. contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
  712. contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
  713. contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
  714. contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
  715. contextsynapse/tests/regression/evaluation_metrics.py +227 -0
  716. contextsynapse/tests/regression/regression_runner.py +1081 -0
  717. contextsynapse/tests/regression/setup_regression_data.py +212 -0
  718. contextsynapse/tests/regression/test_basic_only.py +84 -0
  719. contextsynapse/tests/regression/test_namespace_creation.py +29 -0
  720. contextsynapse/tests/regression/test_single_fix.py +61 -0
  721. contextsynapse/tests/regression/test_single_query.py +20 -0
  722. contextsynapse/tests/regression/validate_test_syntax.py +179 -0
  723. contextsynapse/tests/test_advanced_features.py +347 -0
  724. contextsynapse/tests/test_aiql_summary.py +283 -0
  725. contextsynapse/tests/test_all_aiql_queries.py +948 -0
  726. contextsynapse/tests/test_all_create_queries.py +213 -0
  727. contextsynapse/tests/test_api_vs_native.py +280 -0
  728. contextsynapse/tests/test_chunking_dedup.py +199 -0
  729. contextsynapse/tests/test_connectors.py +171 -0
  730. contextsynapse/tests/test_edges_and_match.py +202 -0
  731. contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
  732. contextsynapse/tests/test_name_property.py +224 -0
  733. contextsynapse/tests/test_node_edge_metadata.py +250 -0
  734. contextsynapse/tests/test_playground_conn.py +114 -0
  735. contextsynapse/tests/test_query_cache.py +213 -0
  736. contextsynapse/tests/test_source_policy_auth.py +348 -0
  737. contextsynapse/tests/test_temporal_and_domain.py +224 -0
  738. contextsynapse/tests/test_time_travel_e2e.py +571 -0
  739. contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
  740. contextsynapse/tools/__init__.py +36 -0
  741. contextsynapse/tools/a2a_tools.py +106 -0
  742. contextsynapse/tools/cognition_tools.py +118 -0
  743. contextsynapse/tools/context.py +905 -0
  744. contextsynapse/tools/formatting.py +92 -0
  745. contextsynapse/tools/gateway_tools.py +186 -0
  746. contextsynapse/tools/graph.py +1700 -0
  747. contextsynapse/tools/intelligence.py +235 -0
  748. contextsynapse/tools/memory.py +118 -0
  749. contextsynapse/tools/registry.py +373 -0
  750. contextsynapse/tools/shield_tools.py +159 -0
  751. contextsynapse/tools/tasks.py +341 -0
  752. contextsynapse/tools/workspace_tools.py +124 -0
  753. contextsynapse/utils/__init__.py +15 -0
  754. contextsynapse/utils/config_loader.py +198 -0
  755. contextsynapse/utils/env_loader.py +156 -0
  756. contextsynapse/utils/file_utils.py +288 -0
  757. contextsynapse/utils/logging_optimizer.py +119 -0
  758. contextsynapse/vector/__init__.py +30 -0
  759. contextsynapse/vector/chroma_vector_store.py +152 -0
  760. contextsynapse/vector/custom_vector_store.py +537 -0
  761. contextsynapse/vector/faiss_vector_store.py +296 -0
  762. contextsynapse/vector/qdrant_vector_store.py +163 -0
  763. contextsynapse/vector/vector_db.py +953 -0
  764. contextsynapse/vector/vector_db_manager.py +197 -0
  765. contextsynapse/workspace/__init__.py +27 -0
  766. contextsynapse/workspace/base.py +94 -0
  767. contextsynapse/workspace/git.py +228 -0
  768. contextsynapse/workspace/github.py +78 -0
  769. contextsynapse/workspace/local.py +73 -0
  770. contextsynapse/workspace/tools.py +222 -0
  771. contextsynapse-1.0.0.dist-info/METADATA +847 -0
  772. contextsynapse-1.0.0.dist-info/RECORD +776 -0
  773. contextsynapse-1.0.0.dist-info/WHEEL +5 -0
  774. contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
  775. contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
  776. contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1142 @@
1
+ """
2
+ Context Sessions
3
+ ================
4
+ Named, persistent workspaces where multiple agents can read/write shared
5
+ context backed by graph + vector + document stores.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import secrets
13
+ import sqlite3
14
+ from dataclasses import dataclass, field, asdict
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Prefix to avoid collisions with user-created namespaces
22
+ SESSION_NS_PREFIX = "ctx_"
23
+
24
+
25
+ @dataclass
26
+ class ContextSession:
27
+ """A shared context workspace."""
28
+ session_id: str
29
+ name: str
30
+ created_at: str
31
+ updated_at: str
32
+ owner_agent_id: Optional[str] = None
33
+ graph_namespace: str = ""
34
+ runtime_namespace: str = "" # separate graph for agent work (tasks, findings, actions)
35
+ vector_collection: str = ""
36
+ document_collection: str = ""
37
+ config: Dict[str, Any] = field(default_factory=dict)
38
+ status: str = "active" # active, archived, deleted
39
+ region: str = "local" # region where session was created
40
+
41
+ @property
42
+ def security_config(self) -> Dict[str, Any]:
43
+ """Get the runtime security configuration for this session.
44
+
45
+ Stored in config['security']. Controls how agents access data.
46
+ """
47
+ defaults = {
48
+ "audit_trail": True,
49
+ "redaction_mode": "mask",
50
+ "agent_clearances": {}, # agent_id → {level, pii_access}
51
+ }
52
+ return {**defaults, **self.config.get("security", {})}
53
+
54
+ @security_config.setter
55
+ def security_config(self, value: Dict[str, Any]):
56
+ self.config["security"] = value
57
+
58
+ def get_agent_clearance(self, agent_id: str) -> Dict[str, Any]:
59
+ """Get clearance for a specific agent in this session."""
60
+ clearances = self.security_config.get("agent_clearances", {})
61
+ return clearances.get(agent_id, {"level": "public", "pii_access": []})
62
+
63
+ def set_agent_clearance(self, agent_id: str, level: str = "public",
64
+ pii_access: Optional[List[str]] = None):
65
+ """Set clearance for an agent in this session."""
66
+ sec = self.config.get("security", {})
67
+ clearances = sec.get("agent_clearances", {})
68
+ clearances[agent_id] = {"level": level, "pii_access": pii_access or []}
69
+ sec["agent_clearances"] = clearances
70
+ self.config["security"] = sec
71
+
72
+ def to_dict(self) -> Dict[str, Any]:
73
+ return asdict(self)
74
+
75
+
76
+ class ContextSessionManager:
77
+ """
78
+ Manages context sessions and their backing stores.
79
+
80
+ Each session maps 1-to-1 to:
81
+ - A graph namespace (via GraphRegistry)
82
+ - A vector collection (name only — actual vector ops are done by caller)
83
+ - A document collection (name only)
84
+
85
+ Access control is stored in the same SQLite DB as the agent registry
86
+ (``contextcore_data/context.db``).
87
+ """
88
+
89
+ _SCHEMA = """
90
+ CREATE TABLE IF NOT EXISTS context_sessions (
91
+ session_id TEXT PRIMARY KEY,
92
+ name TEXT NOT NULL UNIQUE,
93
+ created_at TEXT NOT NULL,
94
+ updated_at TEXT NOT NULL,
95
+ owner_agent_id TEXT,
96
+ graph_namespace TEXT NOT NULL,
97
+ runtime_namespace TEXT DEFAULT '',
98
+ vector_collection TEXT NOT NULL,
99
+ document_collection TEXT NOT NULL,
100
+ config TEXT DEFAULT '{}',
101
+ status TEXT DEFAULT 'active'
102
+ );
103
+
104
+ CREATE TABLE IF NOT EXISTS session_access (
105
+ session_id TEXT NOT NULL,
106
+ agent_id TEXT NOT NULL,
107
+ access_level TEXT NOT NULL DEFAULT 'read',
108
+ allowed_tags TEXT DEFAULT '[]',
109
+ granted_at TEXT NOT NULL,
110
+ PRIMARY KEY (session_id, agent_id)
111
+ );
112
+
113
+ CREATE TABLE IF NOT EXISTS session_contexts (
114
+ session_id TEXT NOT NULL,
115
+ context_id TEXT NOT NULL,
116
+ attached_at TEXT NOT NULL,
117
+ role TEXT DEFAULT 'input',
118
+ last_synced_at TEXT,
119
+ PRIMARY KEY (session_id, context_id)
120
+ );
121
+
122
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON context_sessions(status);
123
+ CREATE INDEX IF NOT EXISTS idx_sessions_owner ON context_sessions(owner_agent_id);
124
+ CREATE INDEX IF NOT EXISTS idx_access_agent ON session_access(agent_id);
125
+ CREATE INDEX IF NOT EXISTS idx_session_contexts_ctx ON session_contexts(context_id);
126
+ """
127
+
128
+ _MIGRATIONS = [
129
+ # v1 → v2: add allowed_tags column to session_access
130
+ "ALTER TABLE session_access ADD COLUMN allowed_tags TEXT DEFAULT '[]'",
131
+ # v2 → v3: add region column for multi-region support
132
+ "ALTER TABLE context_sessions ADD COLUMN region TEXT DEFAULT 'local'",
133
+ # v3 → v4: runtime graph for agent work (tasks, findings, actions)
134
+ "ALTER TABLE context_sessions ADD COLUMN runtime_namespace TEXT DEFAULT ''",
135
+ ]
136
+
137
+ _POST_SCHEMA = """
138
+ CREATE TABLE IF NOT EXISTS session_join_requests (
139
+ request_id TEXT PRIMARY KEY,
140
+ session_id TEXT NOT NULL,
141
+ agent_id TEXT NOT NULL,
142
+ requested_level TEXT NOT NULL DEFAULT 'read',
143
+ status TEXT NOT NULL DEFAULT 'pending',
144
+ reason TEXT,
145
+ reviewed_by TEXT,
146
+ reviewed_at TEXT,
147
+ created_at TEXT NOT NULL,
148
+ expires_at TEXT,
149
+ UNIQUE(session_id, agent_id, status)
150
+ );
151
+
152
+ CREATE INDEX IF NOT EXISTS idx_join_req_session ON session_join_requests(session_id);
153
+ CREATE INDEX IF NOT EXISTS idx_join_req_agent ON session_join_requests(agent_id);
154
+ CREATE INDEX IF NOT EXISTS idx_join_req_status ON session_join_requests(status);
155
+ """
156
+
157
+ def __init__(self, db_path: str = "contextcore_data/context.db", graph_registry=None):
158
+ self._db_path = Path(db_path)
159
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
160
+ self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
161
+ self._conn.row_factory = sqlite3.Row
162
+ self._conn.executescript(self._SCHEMA)
163
+ self._conn.executescript(self._POST_SCHEMA)
164
+ self._run_migrations()
165
+ self._graph_registry = graph_registry
166
+
167
+ def _run_migrations(self):
168
+ """Apply schema migrations idempotently."""
169
+ for sql in self._MIGRATIONS:
170
+ try:
171
+ self._conn.execute(sql)
172
+ self._conn.commit()
173
+ except sqlite3.OperationalError:
174
+ pass # column/table already exists
175
+
176
+ # ------------------------------------------------------------------
177
+ # Session CRUD
178
+ # ------------------------------------------------------------------
179
+
180
+ def create_session(
181
+ self,
182
+ name: str,
183
+ owner_agent_id: Optional[str] = None,
184
+ config: Optional[Dict[str, Any]] = None,
185
+ ) -> ContextSession:
186
+ """Create a new context session with backing stores."""
187
+ # Check for duplicate name (among active sessions)
188
+ existing = self._conn.execute(
189
+ "SELECT session_id FROM context_sessions WHERE name = ? AND status = 'active'",
190
+ (name,),
191
+ ).fetchone()
192
+ if existing:
193
+ raise ValueError(f"A boundary with name '{name}' already exists")
194
+
195
+ session_id = secrets.token_hex(12)
196
+ now = datetime.now(timezone.utc).isoformat()
197
+ # Sanitize name for use as filesystem path (Windows forbids : ? * etc.)
198
+ import re
199
+ safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", name)
200
+ # Include session_id prefix for unique namespace
201
+ ns = f"{SESSION_NS_PREFIX}{safe_name}_{session_id[:8]}"
202
+ rt_ns = f"{ns}_rt" # runtime graph for agent work (tasks, findings, actions)
203
+
204
+ from contextsynapse.core.write_context import REGION
205
+ session = ContextSession(
206
+ session_id=session_id,
207
+ name=name,
208
+ created_at=now,
209
+ updated_at=now,
210
+ owner_agent_id=owner_agent_id,
211
+ graph_namespace=ns,
212
+ runtime_namespace=rt_ns,
213
+ vector_collection=ns,
214
+ document_collection=ns,
215
+ config=config or {},
216
+ status="active",
217
+ region=REGION,
218
+ )
219
+
220
+ # Graph namespace is reserved but NOT created until a context is attached
221
+ # or data is ingested. This keeps session creation lightweight.
222
+
223
+ # Persist session
224
+ self._conn.execute(
225
+ "INSERT INTO context_sessions "
226
+ "(session_id, name, created_at, updated_at, owner_agent_id, "
227
+ "graph_namespace, runtime_namespace, vector_collection, document_collection, config, status) "
228
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
229
+ (
230
+ session.session_id,
231
+ session.name,
232
+ session.created_at,
233
+ session.updated_at,
234
+ session.owner_agent_id,
235
+ session.graph_namespace,
236
+ session.runtime_namespace,
237
+ session.vector_collection,
238
+ session.document_collection,
239
+ json.dumps(session.config),
240
+ session.status,
241
+ ),
242
+ )
243
+
244
+ # Owner gets admin access automatically
245
+ if owner_agent_id:
246
+ self._conn.execute(
247
+ "INSERT OR REPLACE INTO session_access (session_id, agent_id, access_level, granted_at) "
248
+ "VALUES (?, ?, 'admin', ?)",
249
+ (session_id, owner_agent_id, now),
250
+ )
251
+
252
+ self._conn.commit()
253
+
254
+ # Graph-first: add Session root node in BOTH the session's own graph AND default graph
255
+ if self._graph_registry:
256
+ from ..core.graph_structures import GraphNode
257
+ session_props = {
258
+ "name": session.name,
259
+ "graph_namespace": session.graph_namespace,
260
+ "owner_agent_id": session.owner_agent_id or "",
261
+ "status": session.status,
262
+ "created_at": session.created_at,
263
+ }
264
+
265
+ # Root node in session's own graph
266
+ try:
267
+ session_graph = self._graph_registry.get_graph(session.graph_namespace)
268
+ if session_graph:
269
+ session_graph.add_node(GraphNode(
270
+ id=session.session_id, label="Session", properties=session_props,
271
+ ), write_through=True)
272
+ except Exception:
273
+ pass
274
+
275
+ # Reference node in default graph
276
+ try:
277
+ default_graph = self._graph_registry.get_graph("default") or self._graph_registry.create_graph("default")
278
+ default_graph.add_node(GraphNode(
279
+ id=session.session_id, label="Session", properties=session_props,
280
+ ), write_through=True)
281
+ except Exception:
282
+ pass
283
+
284
+ return session
285
+
286
+ def rotate_session_id(self, old_session_id: str) -> Optional[str]:
287
+ """Rotate session ID — generates new ID, old one stops working.
288
+
289
+ Updates all references: session_access, session_contexts, join_requests.
290
+ Returns the new session_id, or None if session not found.
291
+ """
292
+ session = self.get_session(old_session_id)
293
+ if not session:
294
+ return None
295
+
296
+ new_id = secrets.token_hex(12)
297
+ now = datetime.now(timezone.utc).isoformat()
298
+
299
+ # Update main session record
300
+ self._conn.execute(
301
+ "UPDATE context_sessions SET session_id = ?, updated_at = ? WHERE session_id = ?",
302
+ (new_id, now, old_session_id),
303
+ )
304
+ # Update access table
305
+ self._conn.execute(
306
+ "UPDATE session_access SET session_id = ? WHERE session_id = ?",
307
+ (new_id, old_session_id),
308
+ )
309
+ # Update session_contexts
310
+ self._conn.execute(
311
+ "UPDATE session_contexts SET session_id = ? WHERE session_id = ?",
312
+ (new_id, old_session_id),
313
+ )
314
+ # Update join requests
315
+ try:
316
+ self._conn.execute(
317
+ "UPDATE session_join_requests SET session_id = ? WHERE session_id = ?",
318
+ (new_id, old_session_id),
319
+ )
320
+ except Exception:
321
+ pass
322
+ self._conn.commit()
323
+
324
+ # Update graph node if exists
325
+ if self._graph_registry:
326
+ try:
327
+ graph = self._graph_registry.get_graph(session.graph_namespace)
328
+ if graph:
329
+ old_node = graph.get_node(old_session_id)
330
+ if old_node:
331
+ from ..core.graph_structures import GraphNode
332
+ graph.add_node(GraphNode(
333
+ id=new_id, label="Session",
334
+ properties={**old_node.properties, "rotated_from": old_session_id, "rotated_at": now},
335
+ ), write_through=True)
336
+ try:
337
+ graph.remove_node(old_session_id)
338
+ except Exception:
339
+ pass
340
+ except Exception:
341
+ pass
342
+
343
+ logger.info("Rotated session ID: %s → %s", old_session_id[:8], new_id[:8])
344
+ return new_id
345
+
346
+ def get_session(self, session_id: str) -> Optional[ContextSession]:
347
+ row = self._conn.execute(
348
+ "SELECT * FROM context_sessions WHERE session_id = ? AND status != 'deleted'",
349
+ (session_id,),
350
+ ).fetchone()
351
+ return self._row_to_session(row) if row else None
352
+
353
+ def get_session_by_name(self, name: str) -> Optional[ContextSession]:
354
+ row = self._conn.execute(
355
+ "SELECT * FROM context_sessions WHERE name = ? AND status != 'deleted'",
356
+ (name,),
357
+ ).fetchone()
358
+ return self._row_to_session(row) if row else None
359
+
360
+ def list_sessions(self, agent_id: Optional[str] = None, user_id: Optional[str] = None) -> List[ContextSession]:
361
+ """List sessions, optionally filtered to those a user/agent has access to.
362
+
363
+ When *user_id* is provided the result includes sessions where the user
364
+ is the owner **or** has been granted access via ``session_access``.
365
+ """
366
+ if user_id:
367
+ rows = self._conn.execute(
368
+ "SELECT DISTINCT cs.* FROM context_sessions cs "
369
+ "LEFT JOIN session_access sa ON cs.session_id = sa.session_id "
370
+ "WHERE cs.status != 'deleted' "
371
+ "AND (cs.owner_agent_id = ? OR sa.agent_id = ?) "
372
+ "ORDER BY cs.updated_at DESC",
373
+ (user_id, user_id),
374
+ ).fetchall()
375
+ elif agent_id:
376
+ rows = self._conn.execute(
377
+ "SELECT cs.* FROM context_sessions cs "
378
+ "JOIN session_access sa ON cs.session_id = sa.session_id "
379
+ "WHERE sa.agent_id = ? AND cs.status != 'deleted' "
380
+ "ORDER BY cs.updated_at DESC",
381
+ (agent_id,),
382
+ ).fetchall()
383
+ else:
384
+ rows = self._conn.execute(
385
+ "SELECT * FROM context_sessions WHERE status != 'deleted' ORDER BY updated_at DESC"
386
+ ).fetchall()
387
+ return [self._row_to_session(r) for r in rows]
388
+
389
+ def update_session(self, session_id: str, config: Optional[Dict[str, Any]] = None) -> bool:
390
+ """Update session fields (currently config)."""
391
+ now = datetime.now(timezone.utc).isoformat()
392
+ if config is not None:
393
+ self._conn.execute(
394
+ "UPDATE context_sessions SET config = ?, updated_at = ? WHERE session_id = ?",
395
+ (json.dumps(config), now, session_id),
396
+ )
397
+ self._conn.commit()
398
+ return True
399
+ return False
400
+
401
+ def delete_session(self, session_id: str) -> bool:
402
+ """Soft-delete a session."""
403
+ # Final promotion sweep before deleting session
404
+ try:
405
+ session = self.get_session(session_id)
406
+ if session:
407
+ from .promotion import PromotionScorer, PromotionQueue, promote_node, PROMOTION_DEFAULTS
408
+ rt_ns = getattr(session, 'runtime_namespace', '') or f"{session.graph_namespace}_rt"
409
+ at_ns = session.graph_namespace
410
+ # Get graph registry from global
411
+ from ..core.registry import GraphRegistry
412
+ import os
413
+ reg = GraphRegistry(storage_dir=os.environ.get("CONTEXTSYNAPSE_STORAGE_DIR") or os.environ.get("AICONTEXTDB_STORAGE_DIR", "contextcore_data"))
414
+ rt_graph = reg.get_graph(rt_ns, load_if_missing=False)
415
+ if rt_graph:
416
+ scorer = PromotionScorer(PROMOTION_DEFAULTS)
417
+ for label in ("Finding", "Insight", "Observation", "Note"):
418
+ for n in rt_graph.get_all_nodes(label=label):
419
+ props = n.properties or {}
420
+ if props.get("_promotion_scored_at"):
421
+ continue
422
+ score = scorer.score(n, rt_graph)
423
+ if score.score >= 0.3:
424
+ try:
425
+ from ..adapters._base import AIContextDBConnection
426
+ at_conn = AIContextDBConnection(namespace=at_ns)
427
+ rt_conn = AIContextDBConnection(namespace=rt_ns)
428
+ promote_node(n.id, rt_conn, at_conn, promoted_by=props.get("_agent_name", "system"))
429
+ except Exception:
430
+ pass
431
+ except Exception:
432
+ pass
433
+ now = datetime.now(timezone.utc).isoformat()
434
+ cur = self._conn.execute(
435
+ "UPDATE context_sessions SET status = 'deleted', updated_at = ? WHERE session_id = ?",
436
+ (now, session_id),
437
+ )
438
+ self._conn.commit()
439
+ return cur.rowcount > 0
440
+
441
+ def touch_session(self, session_id: str):
442
+ """Update the updated_at timestamp."""
443
+ now = datetime.now(timezone.utc).isoformat()
444
+ self._conn.execute(
445
+ "UPDATE context_sessions SET updated_at = ? WHERE session_id = ?",
446
+ (now, session_id),
447
+ )
448
+ self._conn.commit()
449
+
450
+ # ------------------------------------------------------------------
451
+ # Access control
452
+ # ------------------------------------------------------------------
453
+
454
+ def grant_access(
455
+ self,
456
+ session_id: str,
457
+ agent_id: str,
458
+ level: str = "",
459
+ allowed_tags: Optional[List[str]] = None,
460
+ agent_registry=None,
461
+ ) -> bool:
462
+ """Grant access with optional tag-based scope.
463
+
464
+ If level is empty, it's auto-derived from the agent's role/capabilities:
465
+ - admin role or 'admin' capability → 'admin'
466
+ - 'write' capability → 'write'
467
+ - fallback → 'read'
468
+ """
469
+ # Auto-derive level from agent registration
470
+ if not level and agent_registry:
471
+ try:
472
+ agent = agent_registry.get(agent_id)
473
+ if agent:
474
+ caps = agent.capabilities or []
475
+ if agent.role == "admin" or "admin" in caps:
476
+ level = "admin"
477
+ elif "write" in caps:
478
+ level = "write"
479
+ else:
480
+ level = "read"
481
+ except Exception:
482
+ pass
483
+ if not level:
484
+ level = "read"
485
+ now = datetime.now(timezone.utc).isoformat()
486
+ self._conn.execute(
487
+ "INSERT OR REPLACE INTO session_access "
488
+ "(session_id, agent_id, access_level, allowed_tags, granted_at) "
489
+ "VALUES (?, ?, ?, ?, ?)",
490
+ (session_id, agent_id, level, json.dumps(allowed_tags or []), now),
491
+ )
492
+ self._conn.commit()
493
+ return True
494
+
495
+ def revoke_access(self, session_id: str, agent_id: str) -> bool:
496
+ cur = self._conn.execute(
497
+ "DELETE FROM session_access WHERE session_id = ? AND agent_id = ?",
498
+ (session_id, agent_id),
499
+ )
500
+ self._conn.commit()
501
+ return cur.rowcount > 0
502
+
503
+ def check_access(self, session_id: str, agent_id: str, required: str = "read") -> bool:
504
+ """Check if agent has the required access level.
505
+
506
+ Levels: read < write < admin.
507
+ """
508
+ row = self._conn.execute(
509
+ "SELECT access_level FROM session_access WHERE session_id = ? AND agent_id = ?",
510
+ (session_id, agent_id),
511
+ ).fetchone()
512
+ if not row:
513
+ return False
514
+
515
+ levels = {"read": 0, "write": 1, "admin": 2}
516
+ return levels.get(row["access_level"], -1) >= levels.get(required, 99)
517
+
518
+ def get_agent_access(self, session_id: str, agent_id: str) -> Optional[Dict[str, Any]]:
519
+ """Return the full access record for an agent on a session.
520
+
521
+ Returns ``{"access_level": str, "allowed_tags": List[str]}`` or None.
522
+ """
523
+ row = self._conn.execute(
524
+ "SELECT access_level, allowed_tags FROM session_access "
525
+ "WHERE session_id = ? AND agent_id = ?",
526
+ (session_id, agent_id),
527
+ ).fetchone()
528
+ if not row:
529
+ return None
530
+ return {
531
+ "access_level": row["access_level"],
532
+ "allowed_tags": json.loads(row["allowed_tags"]) if row["allowed_tags"] else [],
533
+ }
534
+
535
+ def get_access_list(self, session_id: str) -> List[Dict[str, Any]]:
536
+ rows = self._conn.execute(
537
+ "SELECT agent_id, access_level, allowed_tags, granted_at FROM session_access WHERE session_id = ?",
538
+ (session_id,),
539
+ ).fetchall()
540
+ result = []
541
+ for r in rows:
542
+ entry = dict(r)
543
+ entry["allowed_tags"] = json.loads(entry.get("allowed_tags") or "[]")
544
+ result.append(entry)
545
+ return result
546
+
547
+ # ------------------------------------------------------------------
548
+ # Join requests (approval gate)
549
+ # ------------------------------------------------------------------
550
+
551
+ def request_join(
552
+ self,
553
+ session_id: str,
554
+ agent_id: str,
555
+ level: str = "read",
556
+ reason: Optional[str] = None,
557
+ expires_in_hours: int = 72,
558
+ ) -> Dict[str, Any]:
559
+ """Agent requests to join a session. Returns the request record.
560
+
561
+ The request stays pending until approved by the session owner or
562
+ an agent with admin access. If the requesting agent already has
563
+ access, this is a no-op that returns the existing access info.
564
+ """
565
+ # Already has access?
566
+ if self.check_access(session_id, agent_id, "read"):
567
+ return {"status": "already_granted", "agent_id": agent_id, "session_id": session_id}
568
+
569
+ # Already has a pending request?
570
+ existing = self._conn.execute(
571
+ "SELECT * FROM session_join_requests "
572
+ "WHERE session_id = ? AND agent_id = ? AND status = 'pending'",
573
+ (session_id, agent_id),
574
+ ).fetchone()
575
+ if existing:
576
+ return dict(existing)
577
+
578
+ request_id = secrets.token_hex(12)
579
+ now = datetime.now(timezone.utc)
580
+ expires_at = (
581
+ datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, tzinfo=timezone.utc)
582
+ )
583
+ from datetime import timedelta
584
+ expires_at = (now + timedelta(hours=expires_in_hours)).isoformat()
585
+
586
+ self._conn.execute(
587
+ "INSERT INTO session_join_requests "
588
+ "(request_id, session_id, agent_id, requested_level, status, reason, created_at, expires_at) "
589
+ "VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)",
590
+ (request_id, session_id, agent_id, level, reason, now.isoformat(), expires_at),
591
+ )
592
+ self._conn.commit()
593
+ return {
594
+ "request_id": request_id,
595
+ "session_id": session_id,
596
+ "agent_id": agent_id,
597
+ "requested_level": level,
598
+ "status": "pending",
599
+ "reason": reason,
600
+ "created_at": now.isoformat(),
601
+ "expires_at": expires_at,
602
+ }
603
+
604
+ def approve_join(
605
+ self,
606
+ request_id: str,
607
+ reviewed_by: str,
608
+ level: Optional[str] = None,
609
+ allowed_tags: Optional[List[str]] = None,
610
+ ) -> bool:
611
+ """Approve a pending join request. Grants access automatically."""
612
+ row = self._conn.execute(
613
+ "SELECT * FROM session_join_requests WHERE request_id = ? AND status = 'pending'",
614
+ (request_id,),
615
+ ).fetchone()
616
+ if not row:
617
+ return False
618
+
619
+ now = datetime.now(timezone.utc).isoformat()
620
+ self._conn.execute(
621
+ "UPDATE session_join_requests SET status = 'approved', reviewed_by = ?, reviewed_at = ? "
622
+ "WHERE request_id = ?",
623
+ (reviewed_by, now, request_id),
624
+ )
625
+
626
+ # Grant actual access
627
+ access_level = level or row["requested_level"]
628
+ self.grant_access(row["session_id"], row["agent_id"], access_level, allowed_tags)
629
+ return True
630
+
631
+ def deny_join(self, request_id: str, reviewed_by: str) -> bool:
632
+ """Deny a pending join request."""
633
+ now = datetime.now(timezone.utc).isoformat()
634
+ cur = self._conn.execute(
635
+ "UPDATE session_join_requests SET status = 'denied', reviewed_by = ?, reviewed_at = ? "
636
+ "WHERE request_id = ? AND status = 'pending'",
637
+ (reviewed_by, now, request_id),
638
+ )
639
+ self._conn.commit()
640
+ return cur.rowcount > 0
641
+
642
+ def get_pending_requests(self, session_id: str) -> List[Dict[str, Any]]:
643
+ """Get all pending join requests for a session."""
644
+ rows = self._conn.execute(
645
+ "SELECT * FROM session_join_requests WHERE session_id = ? AND status = 'pending' "
646
+ "ORDER BY created_at ASC",
647
+ (session_id,),
648
+ ).fetchall()
649
+ return [dict(r) for r in rows]
650
+
651
+ def get_agent_requests(self, agent_id: str) -> List[Dict[str, Any]]:
652
+ """Get all join requests by an agent."""
653
+ rows = self._conn.execute(
654
+ "SELECT * FROM session_join_requests WHERE agent_id = ? ORDER BY created_at DESC",
655
+ (agent_id,),
656
+ ).fetchall()
657
+ return [dict(r) for r in rows]
658
+
659
+ def cleanup_expired_requests(self) -> int:
660
+ """Remove expired pending requests. Returns count removed."""
661
+ now = datetime.now(timezone.utc).isoformat()
662
+ cur = self._conn.execute(
663
+ "UPDATE session_join_requests SET status = 'expired' "
664
+ "WHERE status = 'pending' AND expires_at < ?",
665
+ (now,),
666
+ )
667
+ self._conn.commit()
668
+ return cur.rowcount
669
+
670
+ # ------------------------------------------------------------------
671
+ # Helpers
672
+ # ------------------------------------------------------------------
673
+
674
+ def _row_to_session(self, row: sqlite3.Row) -> ContextSession:
675
+ # region column may not exist in old DBs before migration
676
+ try:
677
+ region = row["region"]
678
+ except (IndexError, KeyError):
679
+ region = "local"
680
+ # runtime_namespace added in v4 migration
681
+ try:
682
+ runtime_ns = row["runtime_namespace"] or ""
683
+ except (IndexError, KeyError):
684
+ runtime_ns = ""
685
+ # Backfill: derive runtime_namespace from graph_namespace for old sessions
686
+ if not runtime_ns:
687
+ runtime_ns = f"{row['graph_namespace']}_rt"
688
+ return ContextSession(
689
+ session_id=row["session_id"],
690
+ name=row["name"],
691
+ created_at=row["created_at"],
692
+ updated_at=row["updated_at"],
693
+ owner_agent_id=row["owner_agent_id"],
694
+ graph_namespace=row["graph_namespace"],
695
+ runtime_namespace=runtime_ns,
696
+ vector_collection=row["vector_collection"],
697
+ document_collection=row["document_collection"],
698
+ config=json.loads(row["config"]) if row["config"] else {},
699
+ status=row["status"],
700
+ region=region or "local",
701
+ )
702
+
703
+ # ------------------------------------------------------------------
704
+ # Session ↔ Context linkage
705
+ # ------------------------------------------------------------------
706
+
707
+ def attach_context(self, session_id: str, context_id: str, role: str = "input") -> bool:
708
+ """Attach a context to a session — merges context graph into runtime graph."""
709
+ now = datetime.now(timezone.utc).isoformat()
710
+ try:
711
+ self._conn.execute(
712
+ "INSERT OR REPLACE INTO session_contexts (session_id, context_id, attached_at, role) "
713
+ "VALUES (?, ?, ?, ?)",
714
+ (session_id, context_id, now, role),
715
+ )
716
+ self._conn.commit()
717
+
718
+ if self._graph_registry:
719
+ try:
720
+ session = self.get_session(session_id)
721
+ if not session:
722
+ return True
723
+
724
+ from ..core.graph_structures import GraphNode, GraphEdge
725
+ import uuid as _uuid
726
+
727
+ runtime_graph = self._graph_registry.get_graph(session.graph_namespace)
728
+ if not runtime_graph:
729
+ runtime_graph = self._graph_registry.create_graph(session.graph_namespace)
730
+ if not runtime_graph:
731
+ return True
732
+
733
+ # Ensure Session root node
734
+ session_root_id = session_id
735
+ existing_sessions = [n for n in runtime_graph.get_all_nodes()
736
+ if getattr(n, "label", "") == "Session"]
737
+ if existing_sessions:
738
+ session_root_id = existing_sessions[0].id
739
+ elif not runtime_graph.get_node(session_id):
740
+ runtime_graph.add_node(GraphNode(
741
+ id=session_id, label="Session",
742
+ properties={"name": session.name, "status": "active",
743
+ "created_at": session.created_at},
744
+ ), write_through=True)
745
+
746
+ # Get context details
747
+ ctx_name = context_id[:12]
748
+ ctx_ns = ""
749
+ ctx_type = ""
750
+ ctx_desc = ""
751
+ ctx_items = 0
752
+ ctx_tokens = 0
753
+ ctx_tags: list = []
754
+ ctx_sensitivity = ""
755
+ try:
756
+ from .context_manager import ContextManager
757
+ cm = ContextManager(graph_registry=self._graph_registry)
758
+ ctx_obj = cm.get_context(context_id)
759
+ if ctx_obj:
760
+ ctx_name = ctx_obj.name
761
+ ctx_ns = ctx_obj.graph_namespace
762
+ ctx_type = ctx_obj.context_type or ""
763
+ ctx_desc = ctx_obj.description or ""
764
+ ctx_items = ctx_obj.item_count or 0
765
+ ctx_tokens = ctx_obj.estimated_tokens or 0
766
+ ctx_tags = ctx_obj.tags or []
767
+ ctx_sensitivity = ctx_obj.sensitivity or ""
768
+ except Exception:
769
+ pass
770
+
771
+ # Add ContextRef node with full context details
772
+ runtime_graph.add_node(GraphNode(
773
+ id=context_id, label="ContextRef",
774
+ properties={
775
+ "name": ctx_name,
776
+ "context_id": context_id,
777
+ "graph_namespace": ctx_ns,
778
+ "context_type": ctx_type,
779
+ "description": ctx_desc,
780
+ "item_count": ctx_items,
781
+ "estimated_tokens": ctx_tokens,
782
+ "tags": ctx_tags,
783
+ "sensitivity": ctx_sensitivity,
784
+ "role": role,
785
+ "attached_at": now,
786
+ },
787
+ ), write_through=True)
788
+ # Only add edge if it doesn't already exist (avoid duplicates on re-attach)
789
+ existing_edges = runtime_graph.get_all_edges()
790
+ has_edge = any(
791
+ e.source == session_root_id and e.target == context_id and e.label == "HAS_CONTEXT"
792
+ for e in existing_edges
793
+ )
794
+ if not has_edge:
795
+ runtime_graph.add_edge(GraphEdge(
796
+ id=str(_uuid.uuid4()), source=session_root_id, target=context_id,
797
+ label="HAS_CONTEXT", properties={"role": role},
798
+ ))
799
+
800
+ # ── REFERENCE-ONLY: store graph namespace, don't copy data ──
801
+ # Agents search across all attached graphs via fan-out.
802
+ # This is instant — no node/edge copying needed.
803
+ if ctx_ns:
804
+ log = logging.getLogger(__name__)
805
+ log.info("[SESSION] Attached context '%s' (%s) to runtime '%s' (reference-only, no merge)",
806
+ ctx_name, ctx_ns, session.graph_namespace)
807
+
808
+ # Save — create a checkpoint BEFORE the merge so detach can
809
+ # restore to this exact state (like a git commit before a change)
810
+ try:
811
+ self._graph_registry.save_graph(
812
+ session.graph_namespace, create_checkpoint=True,
813
+ )
814
+ # Tag the checkpoint with the context being attached
815
+ try:
816
+ from ..core.checkpoint import checkpoint_manager
817
+ cps = checkpoint_manager.get_checkpoints(session.graph_namespace)
818
+ if cps:
819
+ cps[0].message = f"pre-attach:{context_id}"
820
+ checkpoint_manager._save_checkpoint_index(session.graph_namespace)
821
+ except Exception:
822
+ pass
823
+ except Exception:
824
+ pass
825
+
826
+ except Exception as e:
827
+ logging.getLogger(__name__).debug("Graph merge for attach_context failed: %s", e)
828
+
829
+ return True
830
+ except Exception:
831
+ return False
832
+
833
+ def get_attached_contexts(self, session_id: str) -> List[Dict[str, Any]]:
834
+ """Get all contexts attached to a session with their graph namespaces."""
835
+ rows = self._conn.execute(
836
+ "SELECT context_id, role, attached_at FROM session_contexts WHERE session_id = ?",
837
+ (session_id,),
838
+ ).fetchall()
839
+ result = []
840
+ for row in rows:
841
+ ctx_id = row["context_id"]
842
+ entry = {"context_id": ctx_id, "role": row["role"], "attached_at": row["attached_at"]}
843
+ # Resolve full context details
844
+ try:
845
+ from .context_manager import ContextManager
846
+ cm = ContextManager(graph_registry=self._graph_registry)
847
+ ctx_obj = cm.get_context(ctx_id)
848
+ if ctx_obj:
849
+ entry["name"] = ctx_obj.name
850
+ entry["graph_namespace"] = ctx_obj.graph_namespace
851
+ entry["context_type"] = ctx_obj.context_type or ""
852
+ entry["description"] = ctx_obj.description or ""
853
+ entry["item_count"] = ctx_obj.item_count or 0
854
+ entry["estimated_tokens"] = ctx_obj.estimated_tokens or 0
855
+ entry["tags"] = ctx_obj.tags or []
856
+ entry["sensitivity"] = ctx_obj.sensitivity or ""
857
+ else:
858
+ entry["graph_namespace"] = ""
859
+ except Exception:
860
+ entry["graph_namespace"] = ""
861
+ result.append(entry)
862
+ return result
863
+
864
+ def detach_context(self, session_id: str, context_id: str) -> bool:
865
+ """Detach a context from a session — removes merged nodes from runtime graph.
866
+
867
+ Handles versioned context IDs (e.g. ``abc123:1``) by trying both
868
+ the exact ID and the base ID (without the version suffix). After
869
+ removing the context's nodes the session graph falls back to its
870
+ base state — only native session nodes remain.
871
+ """
872
+ log = logging.getLogger(__name__)
873
+
874
+ # Try exact match first, then base ID (strip version suffix like ":1")
875
+ cur = self._conn.execute(
876
+ "DELETE FROM session_contexts WHERE session_id = ? AND context_id = ?",
877
+ (session_id, context_id),
878
+ )
879
+ self._conn.commit()
880
+ removed = cur.rowcount > 0
881
+
882
+ # If exact match failed and ID has a version suffix, try the base ID
883
+ base_context_id = context_id
884
+ if not removed and ":" in context_id:
885
+ base_context_id = context_id.rsplit(":", 1)[0]
886
+ cur = self._conn.execute(
887
+ "DELETE FROM session_contexts WHERE session_id = ? AND context_id = ?",
888
+ (session_id, base_context_id),
889
+ )
890
+ self._conn.commit()
891
+ removed = cur.rowcount > 0
892
+ if removed:
893
+ log.info("[SESSION] Detached via base context_id '%s' (requested '%s')",
894
+ base_context_id, context_id)
895
+
896
+ # Also try matching by prefix if the stored ID has a version but the
897
+ # request doesn't (opposite direction)
898
+ if not removed:
899
+ cur = self._conn.execute(
900
+ "DELETE FROM session_contexts WHERE session_id = ? AND context_id LIKE ?",
901
+ (session_id, f"{context_id}%"),
902
+ )
903
+ self._conn.commit()
904
+ removed = cur.rowcount > 0
905
+
906
+ # Revert the graph to its pre-attach state — like `git checkout` to the
907
+ # checkpoint created before this context was merged.
908
+ if removed and self._graph_registry:
909
+ try:
910
+ session = self.get_session(session_id)
911
+ if session:
912
+ ns = session.graph_namespace
913
+ restored_from_checkpoint = False
914
+
915
+ # Strategy 1: Restore from the pre-attach checkpoint (clean revert)
916
+ try:
917
+ from ..core.checkpoint import checkpoint_manager
918
+ cps = checkpoint_manager.get_checkpoints(ns)
919
+ # Find the checkpoint tagged with this context attach
920
+ target_cp = None
921
+ for cp in cps:
922
+ msg = cp.message or ""
923
+ if msg == f"pre-attach:{context_id}" or msg == f"pre-attach:{base_context_id}":
924
+ target_cp = cp
925
+ break
926
+
927
+ if target_cp:
928
+ # Resolve the graph file path
929
+ ns_path = self._graph_registry._get_namespace_path(ns)
930
+ if ns_path:
931
+ result = checkpoint_manager.restore_checkpoint(
932
+ ns, target_cp.checkpoint_id, str(ns_path),
933
+ )
934
+ if result.get("success"):
935
+ # Reload the graph from the restored file
936
+ self._graph_registry.load_graph(ns)
937
+ restored_from_checkpoint = True
938
+ log.info(
939
+ "[SESSION] Restored graph '%s' from checkpoint '%s' "
940
+ "(pre-attach:%s) — clean revert like git checkout",
941
+ ns, target_cp.checkpoint_id, context_id,
942
+ )
943
+ except Exception as e:
944
+ log.debug("Checkpoint restore failed, falling back to node removal: %s", e)
945
+
946
+ # Strategy 2: Fallback — manually remove tagged nodes
947
+ if not restored_from_checkpoint:
948
+ runtime_graph = self._graph_registry.get_graph(ns)
949
+ if runtime_graph:
950
+ removed_nodes = 0
951
+ match_ids = {context_id, base_context_id}
952
+
953
+ to_remove = []
954
+ for node in runtime_graph.get_all_nodes():
955
+ props = node.properties if hasattr(node, "properties") else {}
956
+ source_ctx = props.get("_source_context", "")
957
+ if source_ctx in match_ids:
958
+ to_remove.append(node.id)
959
+ elif node.id in match_ids: # ContextRef node
960
+ to_remove.append(node.id)
961
+ elif getattr(node, "label", "") == "ContextRef":
962
+ if props.get("context_id", "") in match_ids:
963
+ to_remove.append(node.id)
964
+
965
+ for nid in to_remove:
966
+ try:
967
+ runtime_graph.delete_node(nid)
968
+ removed_nodes += 1
969
+ except Exception:
970
+ try:
971
+ from ..aiql import AIQLExecutor
972
+ ex = AIQLExecutor(contextcore=runtime_graph, graph_registry=self._graph_registry)
973
+ ex.active_namespace = ns
974
+ ex.execute(f'DELETE NODE "{nid}"')
975
+ removed_nodes += 1
976
+ except Exception:
977
+ pass
978
+ # Clean LMDB regardless of which delete path succeeded
979
+ try:
980
+ from ..search.lmdb_index import lmdb_delete_node
981
+ lmdb_delete_node(ns, nid)
982
+ except Exception:
983
+ pass
984
+
985
+ log.info("[SESSION] Detached context '%s' from '%s': removed %d nodes "
986
+ "(fallback — no checkpoint found)",
987
+ context_id, ns, removed_nodes)
988
+
989
+ # Save the reverted graph (with a new checkpoint)
990
+ try:
991
+ self._graph_registry.save_graph(ns, create_checkpoint=True)
992
+ except Exception:
993
+ pass
994
+ except Exception as e:
995
+ log.debug("Graph cleanup for detach_context failed: %s", e)
996
+
997
+ return removed
998
+
999
+ def get_session_context_ids(self, session_id: str) -> List[Dict[str, Any]]:
1000
+ """Get all context attachments for a session."""
1001
+ rows = self._conn.execute(
1002
+ "SELECT context_id, role, attached_at FROM session_contexts WHERE session_id = ? ORDER BY attached_at",
1003
+ (session_id,),
1004
+ ).fetchall()
1005
+ return [dict(r) for r in rows]
1006
+
1007
+ def get_context_session_ids(self, context_id: str) -> List[Dict[str, Any]]:
1008
+ """Get all sessions that use a given context."""
1009
+ rows = self._conn.execute(
1010
+ "SELECT session_id, role, attached_at FROM session_contexts WHERE context_id = ? ORDER BY attached_at",
1011
+ (context_id,),
1012
+ ).fetchall()
1013
+ return [dict(r) for r in rows]
1014
+
1015
+ def sync_context_to_boundaries(self, context_id: str, context_manager=None) -> int:
1016
+ """Sync new nodes from a context to all boundaries that have it attached.
1017
+
1018
+ Called after ingestion or data addition. Only syncs nodes added since
1019
+ the last sync (delta sync, not full re-copy).
1020
+
1021
+ Returns number of nodes synced across all boundaries.
1022
+ """
1023
+ if not self._graph_registry:
1024
+ return 0
1025
+
1026
+ # Find all sessions attached to this context
1027
+ sessions = self.get_context_session_ids(context_id)
1028
+ if not sessions:
1029
+ return 0
1030
+
1031
+ # Get context graph
1032
+ ctx_obj = None
1033
+ ctx_graph = None
1034
+ if context_manager:
1035
+ ctx_obj = context_manager.get_context(context_id)
1036
+ if ctx_obj:
1037
+ ctx_graph = self._graph_registry.get_graph(ctx_obj.graph_namespace, load_if_missing=True)
1038
+
1039
+ if not ctx_graph:
1040
+ return 0
1041
+
1042
+ from ..core.graph_structures import GraphNode, GraphEdge
1043
+ from ..context.boundaries import BOUNDARY_NODE_LABELS
1044
+ import uuid as _sync_uuid
1045
+
1046
+ # Structural labels to skip
1047
+ skip_labels = BOUNDARY_NODE_LABELS | {"Context", "KnowledgeBase", "CodeBase",
1048
+ "SystemStore", "UserStore", "WebStore",
1049
+ "GeneratedStore", "MemoryStore", "ArtifactStore", "ToolStore"}
1050
+
1051
+ total_synced = 0
1052
+ now = datetime.now(timezone.utc).isoformat()
1053
+
1054
+ for att in sessions:
1055
+ session_id = att.get("session_id", "")
1056
+ session = self.get_session(session_id)
1057
+ if not session:
1058
+ continue
1059
+
1060
+ boundary_graph = self._graph_registry.get_graph(session.graph_namespace)
1061
+ if not boundary_graph:
1062
+ continue
1063
+
1064
+ # Get existing node IDs in boundary to avoid duplicates
1065
+ existing_ids = set()
1066
+ for n in boundary_graph.get_all_nodes():
1067
+ existing_ids.add(n.id if hasattr(n, "id") else "")
1068
+
1069
+ # Sync new nodes from context graph
1070
+ synced = 0
1071
+ for node in ctx_graph.get_all_nodes():
1072
+ label = node.label if hasattr(node, "label") else ""
1073
+ nid = node.id if hasattr(node, "id") else ""
1074
+ if label in skip_labels or nid in existing_ids:
1075
+ continue
1076
+
1077
+ props = node.properties if hasattr(node, "properties") else {}
1078
+ synced_props = dict(props)
1079
+ synced_props["_source_context"] = context_id
1080
+ synced_props["_source_context_name"] = ctx_obj.name if ctx_obj else ""
1081
+ synced_props["_synced_at"] = now
1082
+
1083
+ boundary_graph.add_node(GraphNode(
1084
+ id=nid, label=label, properties=synced_props,
1085
+ ), write_through=True)
1086
+
1087
+ # Link to ContextRef if it exists
1088
+ if context_id in existing_ids:
1089
+ boundary_graph.add_edge(GraphEdge(
1090
+ id=str(_sync_uuid.uuid4()),
1091
+ source=context_id, target=nid,
1092
+ label="CONTAINS", properties={},
1093
+ ))
1094
+ synced += 1
1095
+
1096
+ # Sync edges between synced nodes
1097
+ all_boundary_ids = existing_ids | {n.id for n in boundary_graph.get_all_nodes() if hasattr(n, "id")}
1098
+ for edge in ctx_graph.get_all_edges():
1099
+ src = edge.source if hasattr(edge, "source") else edge.get("source", "")
1100
+ tgt = edge.target if hasattr(edge, "target") else edge.get("target", "")
1101
+ if src in all_boundary_ids and tgt in all_boundary_ids:
1102
+ lbl = edge.label if hasattr(edge, "label") else edge.get("label", "")
1103
+ try:
1104
+ boundary_graph.add_edge(GraphEdge(
1105
+ id=str(_sync_uuid.uuid4()),
1106
+ source=src, target=tgt, label=lbl, properties={},
1107
+ ))
1108
+ except Exception:
1109
+ pass
1110
+
1111
+ if synced > 0:
1112
+ try:
1113
+ self._graph_registry.save_graph(session.graph_namespace, create_checkpoint=False)
1114
+ except Exception:
1115
+ pass
1116
+ total_synced += synced
1117
+
1118
+ # Update last_synced_at
1119
+ try:
1120
+ self._conn.execute(
1121
+ "UPDATE session_contexts SET last_synced_at = ? WHERE session_id = ? AND context_id = ?",
1122
+ (now, session_id, context_id),
1123
+ )
1124
+ self._conn.commit()
1125
+ except Exception:
1126
+ pass
1127
+
1128
+ if total_synced > 0:
1129
+ logging.getLogger(__name__).info(
1130
+ "Reactive sync: %d nodes from context %s to %d boundaries",
1131
+ total_synced, context_id[:12], len(sessions),
1132
+ )
1133
+
1134
+ return total_synced
1135
+
1136
+ def close(self):
1137
+ if self._conn:
1138
+ self._conn.close()
1139
+
1140
+
1141
+ # Alias for simpler usage in demos and scripts
1142
+ SessionStore = ContextSessionManager