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,1619 @@
1
+ """
2
+ Context-as-a-Service API Router
3
+ ================================
4
+ All ``/context/`` endpoints for multi-agent shared context.
5
+
6
+ Mount on the main FastAPI app with::
7
+
8
+ from contextsynapse.api.context_router import create_context_router
9
+ app.include_router(create_context_router(graph_registry))
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import logging
16
+ import secrets
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from fastapi import (
20
+ APIRouter,
21
+ BackgroundTasks,
22
+ Depends,
23
+ HTTPException,
24
+ Query,
25
+ UploadFile,
26
+ File,
27
+ WebSocket,
28
+ WebSocketDisconnect,
29
+ )
30
+ from pydantic import BaseModel, Field
31
+
32
+ from .auth import AgentAuth
33
+ from ..context.agents import AgentIdentity, ProvenanceRecord
34
+ from ..context.store_factory import create_agent_registry
35
+ from ..context.session import ContextSessionManager
36
+ from ..context.hub import ContextHub, ContextItem, ContextRole
37
+ from ..context.blob import BlobStore
38
+ from ..context.ingest import UnifiedIngestor
39
+ from ..context.document_processor import DocumentProcessor
40
+ from ..context.pubsub import ContextEvent, PubSubHub, pubsub_hub
41
+ from ..context.vector_integration import SessionVectorStore
42
+ from ..context.dedup import ContextDedup
43
+ from ..context.scoping import ContextScoper, ScopingConfig
44
+ from ..context.conversation import ConversationStore
45
+ from ..context.embedding_hooks import EmbeddingHooks
46
+ from ..context.session_graph import SessionGraphBuilder
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ # ======================================================================
52
+ # Pydantic request/response models
53
+ # ======================================================================
54
+
55
+ class AgentRegisterRequest(BaseModel):
56
+ name: str
57
+ role: str = "agent"
58
+ platform: str = "app" # desktop | mobile | app | browser | embedded
59
+ capabilities: List[str] = Field(default_factory=lambda: ["read", "write"])
60
+ metadata: Dict[str, Any] = Field(default_factory=dict)
61
+
62
+ class AgentResponse(BaseModel):
63
+ agent_id: str
64
+ name: str
65
+ role: str
66
+ platform: str = "app"
67
+ capabilities: List[str]
68
+ status: str = "active"
69
+ created_at: str
70
+ last_seen: Optional[str] = None
71
+ metadata: Dict[str, Any]
72
+
73
+ class AgentRegisterResponse(AgentResponse):
74
+ api_key: str # returned only on registration
75
+
76
+ class SessionCreateRequest(BaseModel):
77
+ name: str
78
+ agent_id: Optional[str] = None
79
+ context_id: Optional[str] = None # auto-attach context (e.g. SDLC context)
80
+ goal: Optional[str] = None # session goal / objective
81
+ config: Dict[str, Any] = Field(default_factory=dict)
82
+
83
+ class SessionResponse(BaseModel):
84
+ session_id: str
85
+ name: str
86
+ created_at: str
87
+ updated_at: str
88
+ owner_agent_id: Optional[str]
89
+ graph_namespace: str
90
+ vector_collection: str
91
+ document_collection: str
92
+ config: Dict[str, Any]
93
+ status: str
94
+
95
+ class AccessRequest(BaseModel):
96
+ agent_id: str
97
+ level: str = "read" # read, write, admin
98
+ allowed_tags: List[str] = Field(default_factory=list) # extra tags this agent may see
99
+
100
+ class IngestRequest(BaseModel):
101
+ data: Any
102
+ agent_id: str
103
+ data_type: str = "auto"
104
+ metadata: Dict[str, Any] = Field(default_factory=dict)
105
+ source: Optional[str] = None
106
+ mime_type: Optional[str] = None
107
+ filename: Optional[str] = None
108
+ tags: List[str] = Field(default_factory=list)
109
+ sensitivity: Optional[str] = None # public|internal|confidential|restricted
110
+ auto_pii_scan: bool = True
111
+
112
+ class ContextItemRequest(BaseModel):
113
+ content: str
114
+ agent_id: str
115
+ role: str = "user"
116
+ label: Optional[str] = None
117
+ source: Optional[str] = None
118
+ metadata: Dict[str, Any] = Field(default_factory=dict)
119
+ tags: List[str] = Field(default_factory=list)
120
+ sensitivity: str = "public"
121
+
122
+ class DocumentProcessRequest(BaseModel):
123
+ file_path: str
124
+ agent_id: str
125
+ extract_tables: bool = True
126
+ extract_images: bool = True
127
+ chunk_size: Optional[int] = None
128
+ chunk_overlap: Optional[int] = None
129
+ metadata: Dict[str, Any] = Field(default_factory=dict)
130
+
131
+ class ContributeRequest(BaseModel):
132
+ content: str
133
+ agent_id: Optional[str] = None # auto-filled from auth if omitted
134
+ role: str = "generated" # generated, synthesis, decision
135
+ label: Optional[str] = None
136
+ source_ids: List[str] = Field(default_factory=list)
137
+ metadata: Dict[str, Any] = Field(default_factory=dict)
138
+ tags: List[str] = Field(default_factory=list)
139
+ sensitivity: str = "internal"
140
+
141
+ class QueryRequest(BaseModel):
142
+ query: str
143
+ agent_id: Optional[str] = None
144
+ limit: int = 50
145
+
146
+ class SemanticSearchRequest(BaseModel):
147
+ query: str
148
+ k: int = 10
149
+ agent_id: Optional[str] = None
150
+
151
+
152
+ # --- Phase 2 models ---
153
+
154
+ class ScopingConfigRequest(BaseModel):
155
+ max_tokens: int = 8000
156
+ strategy: str = "combined"
157
+ role_weights: Dict[str, float] = Field(default_factory=dict)
158
+ recency_decay: float = 0.95
159
+ reserve_system: bool = True
160
+
161
+ class ConversationCreateRequest(BaseModel):
162
+ session_id: str
163
+ title: Optional[str] = None
164
+ metadata: Dict[str, Any] = Field(default_factory=dict)
165
+ ttl_seconds: Optional[int] = None
166
+
167
+ class ConversationMessageRequest(BaseModel):
168
+ role: str # user | assistant | system | tool
169
+ content: str
170
+ agent_id: Optional[str] = None
171
+ metadata: Dict[str, Any] = Field(default_factory=dict)
172
+
173
+ class ConversationKVRequest(BaseModel):
174
+ key: str
175
+ value: Any
176
+
177
+
178
+ # ======================================================================
179
+ # Router factory
180
+ # ======================================================================
181
+
182
+ def create_context_router(graph_registry=None) -> APIRouter:
183
+ """
184
+ Build and return the ``/context`` router.
185
+
186
+ Instantiates the shared singletons (AgentRegistry, SessionManager, etc.)
187
+ so they live for the lifetime of the FastAPI app.
188
+ """
189
+ router = APIRouter(prefix="/context", tags=["Context Service"])
190
+
191
+ # Shared state
192
+ agent_registry = create_agent_registry()
193
+ session_manager = ContextSessionManager(graph_registry=graph_registry)
194
+ blob_store = BlobStore()
195
+
196
+ # Vector store — auto-embed on ingest, semantic search
197
+ vector_store = SessionVectorStore()
198
+ dedup = ContextDedup(vector_store=vector_store)
199
+
200
+ # Phase 2: embedding hooks, conversation store
201
+ embedding_hooks = EmbeddingHooks(vector_store=vector_store)
202
+ conversation_store = ConversationStore()
203
+
204
+ ingestor = UnifiedIngestor(
205
+ session_manager=session_manager,
206
+ agent_registry=agent_registry,
207
+ blob_store=blob_store,
208
+ embedding_hooks=embedding_hooks,
209
+ )
210
+ doc_processor = DocumentProcessor(
211
+ session_manager=session_manager,
212
+ agent_registry=agent_registry,
213
+ graph_registry=graph_registry,
214
+ blob_store=blob_store,
215
+ )
216
+
217
+ # Per-session scoping configs
218
+ _scoping_configs: Dict[str, ScopingConfig] = {}
219
+
220
+ # Session graph builders (session_id -> SessionGraphBuilder)
221
+ _session_builders: Dict[str, SessionGraphBuilder] = {}
222
+
223
+ async def _get_or_create_builder(session_id: str) -> SessionGraphBuilder:
224
+ """Lazily get or create a SessionGraphBuilder for a session."""
225
+ if session_id not in _session_builders:
226
+ session = session_manager.get_session(session_id)
227
+ if session:
228
+ builder = SessionGraphBuilder(session, graph_registry)
229
+ await builder.initialize()
230
+ _session_builders[session_id] = builder
231
+ return _session_builders.get(session_id)
232
+
233
+ # Auth dependency — inject into protected endpoints
234
+ from .auth import require_agent_capability
235
+ require_agent = AgentAuth(agent_registry)
236
+
237
+ # RBAC: capability-gated dependencies
238
+ require_read = require_agent_capability(require_agent, "read")
239
+ require_write = require_agent_capability(require_agent, "read", "write")
240
+ require_agent_admin = require_agent_capability(require_agent, "read", "write", "admin")
241
+
242
+ # Per-session ContextHub cache (session_id -> ContextHub)
243
+ _hubs: Dict[str, ContextHub] = {}
244
+
245
+ def _get_hub(session_id: str) -> ContextHub:
246
+ if session_id not in _hubs:
247
+ _hubs[session_id] = ContextHub()
248
+ return _hubs[session_id]
249
+
250
+ # ==================================================================
251
+ # Agent endpoints (registration is PUBLIC, others require auth)
252
+ # ==================================================================
253
+
254
+ @router.post("/agents", response_model=AgentRegisterResponse)
255
+ async def register_agent(req: AgentRegisterRequest):
256
+ agent, api_key = agent_registry.register(
257
+ name=req.name,
258
+ role=req.role,
259
+ platform=req.platform,
260
+ capabilities=req.capabilities,
261
+ metadata=req.metadata,
262
+ )
263
+ # Return composite key: "<agent_id>:<secret>"
264
+ # Clients use this as: Authorization: Bearer <agent_id>:<secret>
265
+ composite_key = f"{agent.agent_id}:{api_key}"
266
+ return AgentRegisterResponse(
267
+ **agent.to_dict(),
268
+ api_key=composite_key,
269
+ )
270
+
271
+ @router.get("/agents", response_model=List[AgentResponse])
272
+ async def list_agents(caller: AgentIdentity = Depends(require_read)):
273
+ agents = agent_registry.list_agents()
274
+ return [AgentResponse(**a.to_dict()) for a in agents]
275
+
276
+ @router.get("/agents/{agent_id}", response_model=AgentResponse)
277
+ async def get_agent(agent_id: str, caller: AgentIdentity = Depends(require_read)):
278
+ agent = agent_registry.get(agent_id)
279
+ if not agent:
280
+ raise HTTPException(404, "Agent not found")
281
+ return AgentResponse(**agent.to_dict())
282
+
283
+ @router.delete("/agents/{agent_id}")
284
+ async def deregister_agent(agent_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
285
+ # Only allow self-deregistration or admin
286
+ if caller.agent_id != agent_id and caller.role != "admin":
287
+ raise HTTPException(403, "Can only deregister yourself unless admin")
288
+ ok = agent_registry.deregister(agent_id)
289
+ if not ok:
290
+ raise HTTPException(404, "Agent not found")
291
+ return {"success": True, "message": f"Agent {agent_id} deregistered"}
292
+
293
+ # ==================================================================
294
+ # Session endpoints
295
+ # ==================================================================
296
+
297
+ @router.post("/sessions", response_model=SessionResponse)
298
+ async def create_session(req: SessionCreateRequest, caller: AgentIdentity = Depends(require_agent_admin)):
299
+ # Merge goal into config if provided
300
+ config = dict(req.config)
301
+ if req.goal:
302
+ config["goal"] = req.goal
303
+
304
+ try:
305
+ session = session_manager.create_session(
306
+ name=req.name,
307
+ owner_agent_id=req.agent_id or caller.agent_id,
308
+ config=config,
309
+ )
310
+ except Exception as e:
311
+ raise HTTPException(400, str(e))
312
+
313
+ # Initialize session graph builder (unified knowledge + conversation layer)
314
+ try:
315
+ builder = SessionGraphBuilder(session, graph_registry)
316
+ await builder.initialize()
317
+ _session_builders[session.session_id] = builder
318
+ except Exception as e:
319
+ logger.warning("Failed to initialize session graph builder: %s", e)
320
+
321
+ # Auto-attach context and create thread if context_id provided
322
+ if req.context_id:
323
+ try:
324
+ session_manager.attach_context(session.session_id, req.context_id)
325
+
326
+ # Create thread conversation
327
+ thread = conversation_store.create(
328
+ session_id=session.session_id,
329
+ title="Thread",
330
+ metadata={"type": "sdlc_thread", "context_id": req.context_id, "goal": req.goal or ""},
331
+ )
332
+
333
+ # Store thread_id in session config
334
+ config["thread_id"] = thread.conversation_id
335
+ config["context_id"] = req.context_id
336
+ session_manager.update_session(session.session_id, config=config)
337
+
338
+ # Post initial system message with context stats
339
+ doc_count = 0
340
+ chunk_count = 0
341
+ ctx_name = req.context_id
342
+ try:
343
+ ns = session.graph_namespace
344
+ db = graph_registry.get_graph(ns, load_if_missing=True)
345
+ if db:
346
+ for n in db.get_all_nodes():
347
+ if n.label in ("Document", "Source"):
348
+ doc_count += 1
349
+ elif n.label in ("TextChunk", "Chunk", "Passage"):
350
+ chunk_count += 1
351
+ except Exception:
352
+ pass # optional
353
+
354
+ goal_text = f" Goal: {req.goal}" if req.goal else ""
355
+ conversation_store.append_message(
356
+ conversation_id=thread.conversation_id,
357
+ role="system",
358
+ content=(
359
+ f"Session started.{goal_text} "
360
+ f"Context '{ctx_name}' attached with {doc_count} documents, "
361
+ f"{chunk_count} chunks available via RAG."
362
+ ),
363
+ agent_id=None,
364
+ metadata={"type": "system_event", "event": "session_started"},
365
+ )
366
+ logger.info("Auto-created thread %s for session %s", thread.conversation_id, session.session_id)
367
+ except Exception as e:
368
+ logger.warning("Failed to auto-create thread for session: %s", e)
369
+
370
+ return SessionResponse(**session.to_dict())
371
+
372
+ @router.get("/sessions", response_model=List[SessionResponse])
373
+ async def list_sessions(agent_id: Optional[str] = Query(None), caller: AgentIdentity = Depends(require_read)):
374
+ sessions = session_manager.list_sessions(agent_id=agent_id)
375
+ return [SessionResponse(**s.to_dict()) for s in sessions]
376
+
377
+ @router.get("/sessions/{session_id}", response_model=SessionResponse)
378
+ async def get_session(session_id: str, caller: AgentIdentity = Depends(require_read)):
379
+ session = session_manager.get_session(session_id)
380
+ if not session:
381
+ raise HTTPException(404, "Session not found")
382
+ return SessionResponse(**session.to_dict())
383
+
384
+ @router.delete("/sessions/{session_id}")
385
+ async def delete_session(session_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
386
+ ok = session_manager.delete_session(session_id)
387
+ if not ok:
388
+ raise HTTPException(404, "Session not found")
389
+ _hubs.pop(session_id, None)
390
+ vector_store.delete_session(session_id)
391
+ return {"success": True, "message": "Session deleted"}
392
+
393
+ # --- access control ---
394
+
395
+ @router.post("/sessions/{session_id}/access")
396
+ async def grant_access(session_id: str, req: AccessRequest, caller: AgentIdentity = Depends(require_agent_admin)):
397
+ session = session_manager.get_session(session_id)
398
+ if not session:
399
+ raise HTTPException(404, "Session not found")
400
+ session_manager.grant_access(
401
+ session_id, req.agent_id, req.level, allowed_tags=req.allowed_tags,
402
+ agent_registry=agent_registry,
403
+ )
404
+ return {
405
+ "success": True,
406
+ "agent_id": req.agent_id,
407
+ "level": req.level,
408
+ "allowed_tags": req.allowed_tags,
409
+ }
410
+
411
+ @router.delete("/sessions/{session_id}/access/{agent_id}")
412
+ async def revoke_access(session_id: str, agent_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
413
+ ok = session_manager.revoke_access(session_id, agent_id)
414
+ if not ok:
415
+ raise HTTPException(404, "Access entry not found")
416
+ return {"success": True}
417
+
418
+ @router.get("/sessions/{session_id}/access")
419
+ async def get_access_list(session_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
420
+ return session_manager.get_access_list(session_id)
421
+
422
+ # ==================================================================
423
+ # Join requests (approval gate)
424
+ # ==================================================================
425
+
426
+ @router.post("/sessions/{session_id}/join")
427
+ async def request_join(session_id: str, level: str = "read", reason: str = None, caller: AgentIdentity = Depends(require_read)):
428
+ """Agent requests to join a session. Goes to pending queue until approved."""
429
+ session = session_manager.get_session(session_id)
430
+ if not session:
431
+ raise HTTPException(404, "Session not found")
432
+ result = session_manager.request_join(session_id, caller.agent_id, level, reason)
433
+ return result
434
+
435
+ @router.get("/sessions/{session_id}/join-requests")
436
+ async def list_join_requests(session_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
437
+ """List pending join requests (admin only)."""
438
+ requests = session_manager.get_pending_requests(session_id)
439
+ enriched = []
440
+ for req in requests:
441
+ entry = dict(req)
442
+ agent = agent_registry.get(req["agent_id"])
443
+ if agent:
444
+ entry["agent"] = agent.to_dict()
445
+ enriched.append(entry)
446
+ return {"requests": enriched, "total": len(enriched)}
447
+
448
+ @router.post("/sessions/{session_id}/join-requests/{request_id}/approve")
449
+ async def approve_join(session_id: str, request_id: str, level: str = None, caller: AgentIdentity = Depends(require_agent_admin)):
450
+ """Approve a pending join request."""
451
+ ok = session_manager.approve_join(request_id, caller.agent_id, level)
452
+ if not ok:
453
+ raise HTTPException(404, "Request not found or already reviewed")
454
+ return {"status": "approved", "request_id": request_id}
455
+
456
+ @router.post("/sessions/{session_id}/join-requests/{request_id}/deny")
457
+ async def deny_join(session_id: str, request_id: str, caller: AgentIdentity = Depends(require_agent_admin)):
458
+ """Deny a pending join request."""
459
+ ok = session_manager.deny_join(request_id, caller.agent_id)
460
+ if not ok:
461
+ raise HTTPException(404, "Request not found or already reviewed")
462
+ return {"status": "denied", "request_id": request_id}
463
+
464
+ # ==================================================================
465
+ # Ingest endpoints
466
+ # ==================================================================
467
+
468
+ @router.post("/sessions/{session_id}/ingest")
469
+ async def ingest_data(session_id: str, req: IngestRequest, caller: AgentIdentity = Depends(require_write)):
470
+ from ..ingestion.amplifier import build_context_purpose
471
+
472
+ session = session_manager.get_session(session_id)
473
+ if not session:
474
+ raise HTTPException(404, "Session not found")
475
+
476
+ # Auto-detect context purpose from session metadata for amplifier scoring
477
+ ctx_meta = session.config.get("context_meta", {}) if hasattr(session, "config") and session.config else {}
478
+ context_purpose = build_context_purpose(
479
+ context_name=ctx_meta.get("name", session.name if hasattr(session, "name") else ""),
480
+ context_description=ctx_meta.get("description", ""),
481
+ tags=ctx_meta.get("tags", req.tags or []),
482
+ )
483
+
484
+ # --- Dedup check ---
485
+ text_repr = str(req.data) if not isinstance(req.data, str) else req.data
486
+ dup = dedup.check(session_id, text_repr)
487
+ if dup.is_duplicate:
488
+ return {
489
+ "success": False,
490
+ "duplicate": True,
491
+ "match_type": dup.match_type,
492
+ "matched_id": dup.matched_id,
493
+ "score": dup.score,
494
+ "message": f"Duplicate detected ({dup.match_type}, score={dup.score:.3f})",
495
+ }
496
+
497
+ # Inject context_purpose into metadata so it propagates downstream
498
+ enriched_metadata = {**(req.metadata or {})}
499
+ if context_purpose:
500
+ enriched_metadata["_context_purpose"] = context_purpose
501
+
502
+ try:
503
+ result = ingestor.ingest(
504
+ session_id=session_id,
505
+ agent_id=req.agent_id,
506
+ data=req.data,
507
+ data_type=req.data_type,
508
+ metadata=enriched_metadata,
509
+ source=req.source,
510
+ mime_type=req.mime_type,
511
+ filename=req.filename,
512
+ tags=req.tags,
513
+ sensitivity=req.sensitivity,
514
+ auto_pii_scan=req.auto_pii_scan,
515
+ )
516
+ except ValueError as e:
517
+ raise HTTPException(400, str(e))
518
+ except Exception as e:
519
+ logger.error(f"Ingest error: {e}")
520
+ raise HTTPException(500, f"Ingestion failed: {e}")
521
+
522
+ # Auto-embed ingested text into vector store
523
+ embedded = False
524
+ if vector_store.available and result.get("node_ids"):
525
+ if text_repr and len(text_repr.strip()) > 0:
526
+ for nid in result["node_ids"]:
527
+ vector_store.add_text(
528
+ session_id=session_id,
529
+ text=text_repr,
530
+ node_id=nid,
531
+ metadata={"agent_id": req.agent_id, "source": req.source or "ingest"},
532
+ )
533
+ embedded = True
534
+
535
+ # Register content hash so future duplicates are caught
536
+ for nid in result.get("node_ids", []):
537
+ dedup.register(session_id, nid, text_repr)
538
+
539
+ # Publish event
540
+ await pubsub_hub.publish(ContextEvent(
541
+ event_type="context_added",
542
+ session_id=session_id,
543
+ agent_id=req.agent_id,
544
+ payload={"data_type": result["data_type"], "node_ids": result["node_ids"]},
545
+ ))
546
+
547
+ return {"success": True, "embedded": embedded, **result}
548
+
549
+ # ==================================================================
550
+ # Document processing endpoints
551
+ # ==================================================================
552
+
553
+ @router.post("/sessions/{session_id}/documents")
554
+ async def process_document(session_id: str, req: DocumentProcessRequest, caller: AgentIdentity = Depends(require_write)):
555
+ """Process a document (PDF, CSV, TXT, etc.) into the session's graph.
556
+
557
+ Extracts text, tables, images → creates Source, Document, Chunk, Table,
558
+ Image nodes with edges → permanent context.
559
+ """
560
+ session = session_manager.get_session(session_id)
561
+ if not session:
562
+ raise HTTPException(404, "Session not found")
563
+
564
+ try:
565
+ result = doc_processor.process(
566
+ session_id=session_id,
567
+ agent_id=req.agent_id,
568
+ file_path=req.file_path,
569
+ extract_tables=req.extract_tables,
570
+ extract_images=req.extract_images,
571
+ chunk_size=req.chunk_size,
572
+ chunk_overlap=req.chunk_overlap,
573
+ metadata=req.metadata,
574
+ )
575
+ except FileNotFoundError as e:
576
+ raise HTTPException(404, str(e))
577
+ except Exception as e:
578
+ logger.error(f"Document processing error: {e}")
579
+ raise HTTPException(500, f"Document processing failed: {e}")
580
+
581
+ await pubsub_hub.publish(ContextEvent(
582
+ event_type="context_added",
583
+ session_id=session_id,
584
+ agent_id=req.agent_id,
585
+ payload={
586
+ "type": "document",
587
+ "document_id": result["document_id"],
588
+ "chunk_count": result["chunk_count"],
589
+ "table_count": result["table_count"],
590
+ "image_count": result["image_count"],
591
+ },
592
+ ))
593
+
594
+ return {"success": True, **result}
595
+
596
+ @router.post("/sessions/{session_id}/documents/upload")
597
+ async def upload_document(
598
+ session_id: str,
599
+ file: UploadFile = File(...),
600
+ agent_id: str = Query(...),
601
+ extract_tables: bool = Query(True),
602
+ extract_images: bool = Query(True),
603
+ caller: AgentIdentity = Depends(require_write),
604
+ ):
605
+ """Upload a document file and process it into the session's graph."""
606
+ session = session_manager.get_session(session_id)
607
+ if not session:
608
+ raise HTTPException(404, "Session not found")
609
+
610
+ raw_bytes = await file.read()
611
+ try:
612
+ result = doc_processor.process(
613
+ session_id=session_id,
614
+ agent_id=agent_id,
615
+ raw_bytes=raw_bytes,
616
+ filename=file.filename or "upload",
617
+ extract_tables=extract_tables,
618
+ extract_images=extract_images,
619
+ )
620
+ except Exception as e:
621
+ logger.error(f"Document upload processing error: {e}")
622
+ raise HTTPException(500, f"Document processing failed: {e}")
623
+
624
+ await pubsub_hub.publish(ContextEvent(
625
+ event_type="context_added",
626
+ session_id=session_id,
627
+ agent_id=agent_id,
628
+ payload={
629
+ "type": "document",
630
+ "document_id": result["document_id"],
631
+ "chunk_count": result["chunk_count"],
632
+ },
633
+ ))
634
+
635
+ return {"success": True, **result}
636
+
637
+ # ==================================================================
638
+ # Context item endpoints
639
+ # ==================================================================
640
+
641
+ @router.post("/sessions/{session_id}/items")
642
+ async def add_context_item(session_id: str, req: ContextItemRequest, caller: AgentIdentity = Depends(require_write)):
643
+ session = session_manager.get_session(session_id)
644
+ if not session:
645
+ raise HTTPException(404, "Session not found")
646
+
647
+ hub = _get_hub(session_id)
648
+ hub.add_text(
649
+ text=req.content,
650
+ role=req.role,
651
+ label=req.label,
652
+ source=req.source,
653
+ metadata={**req.metadata, "agent_id": req.agent_id},
654
+ tags=req.tags,
655
+ sensitivity=req.sensitivity,
656
+ )
657
+ # Tag the last item with agent_id
658
+ hub._items[-1].agent_id = req.agent_id
659
+
660
+ await pubsub_hub.publish(ContextEvent(
661
+ event_type="context_added",
662
+ session_id=session_id,
663
+ agent_id=req.agent_id,
664
+ payload={"label": req.label, "role": req.role},
665
+ ))
666
+
667
+ return {"success": True, "item_count": len(hub)}
668
+
669
+ @router.get("/sessions/{session_id}/items")
670
+ async def list_context_items(
671
+ session_id: str,
672
+ role: Optional[str] = Query(None),
673
+ agent_id: Optional[str] = Query(None),
674
+ source: Optional[str] = Query(None),
675
+ label: Optional[str] = Query(None),
676
+ tag: Optional[str] = Query(None, description="Filter by tag"),
677
+ sensitivity: Optional[str] = Query(None),
678
+ caller: AgentIdentity = Depends(require_read),
679
+ ):
680
+ hub = _get_hub(session_id)
681
+
682
+ # First apply authorization filter based on caller's access
683
+ access_info = session_manager.get_agent_access(session_id, caller.agent_id)
684
+ if access_info:
685
+ access_level = access_info["access_level"]
686
+ allowed_tags = access_info["allowed_tags"]
687
+ else:
688
+ access_level = "read"
689
+ allowed_tags = []
690
+
691
+ visible_items = hub.filter_for_agent(access_level, allowed_tags)
692
+
693
+ # Then apply user-specified filters on visible items
694
+ filtered_hub = ContextHub()
695
+ filtered_hub._items = visible_items
696
+ items = filtered_hub.filter(
697
+ role=role, agent_id=agent_id, source=source, label=label,
698
+ tags=[tag] if tag else None, sensitivity=sensitivity,
699
+ )
700
+ return {"items": [i.to_dict() for i in items], "count": len(items)}
701
+
702
+ # ==================================================================
703
+ # Agent contributions (write outputs back into shared context)
704
+ # ==================================================================
705
+
706
+ @router.post("/sessions/{session_id}/contribute")
707
+ async def contribute(session_id: str, req: ContributeRequest, caller: AgentIdentity = Depends(require_write)):
708
+ """
709
+ Agent writes its output (summary, analysis, decision) back into the
710
+ session. The output is:
711
+
712
+ 1. Added to the in-memory ContextHub (available via /export)
713
+ 2. Persisted as an ``Insight`` graph node (survives restarts)
714
+ 3. Linked to source nodes via ``DERIVED_FROM`` edges (lineage)
715
+ 4. Broadcast to subscribers via WebSocket
716
+ """
717
+ session = session_manager.get_session(session_id)
718
+ if not session:
719
+ raise HTTPException(404, "Session not found")
720
+
721
+ # Verify agent has write access to this session
722
+ if session.owner_agent_id != caller.agent_id:
723
+ if not session_manager.check_access(session_id, caller.agent_id, "write"):
724
+ raise HTTPException(403, "No write access to this session")
725
+
726
+ # Auto-fill agent_id from authenticated caller if not provided
727
+ if not req.agent_id:
728
+ req.agent_id = caller.agent_id
729
+
730
+ # Dedup check on contributions too
731
+ dup = dedup.check(session_id, req.content)
732
+ if dup.is_duplicate:
733
+ return {
734
+ "success": False,
735
+ "duplicate": True,
736
+ "match_type": dup.match_type,
737
+ "matched_id": dup.matched_id,
738
+ "score": dup.score,
739
+ "message": f"Duplicate contribution ({dup.match_type}, score={dup.score:.3f})",
740
+ }
741
+
742
+ # 1. Add to hub
743
+ hub = _get_hub(session_id)
744
+ hub.contribute(
745
+ content=req.content,
746
+ agent_id=req.agent_id,
747
+ role=req.role,
748
+ label=req.label,
749
+ source_ids=req.source_ids,
750
+ metadata=req.metadata,
751
+ tags=req.tags,
752
+ sensitivity=req.sensitivity,
753
+ )
754
+
755
+ # 2. Persist as Insight node in graph
756
+ insight_node_id = None
757
+ if graph_registry:
758
+ graph = graph_registry.get_graph(session.graph_namespace, load_if_missing=True)
759
+ if graph:
760
+ import uuid as _uuid
761
+ from ..core.graph_structures import GraphNode, GraphEdge
762
+
763
+ insight_node_id = str(_uuid.uuid4())
764
+ node = GraphNode(
765
+ id=insight_node_id,
766
+ label="Insight",
767
+ properties={
768
+ "content": req.content,
769
+ "insight_type": req.role,
770
+ "derived_from": req.source_ids,
771
+ "_ctx_session_id": session.session_id,
772
+ "_ctx_agent_id": req.agent_id,
773
+ "_ctx_source": "contribute",
774
+ **req.metadata,
775
+ },
776
+ )
777
+ graph.add_node(node)
778
+
779
+ # 3. Create DERIVED_FROM edges to source nodes
780
+ for src_id in req.source_ids:
781
+ try:
782
+ edge = GraphEdge(
783
+ id=str(_uuid.uuid4()),
784
+ source=insight_node_id,
785
+ target=src_id,
786
+ label="DERIVED_FROM",
787
+ properties={"agent_id": req.agent_id},
788
+ )
789
+ graph.add_edge(edge)
790
+ except Exception:
791
+ pass # source node may not exist
792
+
793
+ # Save
794
+ graph_registry.save_graph(session.graph_namespace, create_checkpoint=False)
795
+
796
+ # Provenance
797
+ if insight_node_id:
798
+ agent_registry.record_provenance(ProvenanceRecord(
799
+ session_id=session_id,
800
+ agent_id=req.agent_id,
801
+ operation="contribute",
802
+ target_type="insight",
803
+ target_id=insight_node_id,
804
+ source_ids=req.source_ids,
805
+ metadata={"role": req.role},
806
+ ))
807
+
808
+ # 4. Auto-embed the contribution into vector store
809
+ if vector_store.available and insight_node_id:
810
+ vector_store.add_text(
811
+ session_id=session_id,
812
+ text=req.content,
813
+ node_id=insight_node_id,
814
+ metadata={"agent_id": req.agent_id, "role": req.role, "source": "contribute"},
815
+ )
816
+
817
+ # Register hash for dedup
818
+ if insight_node_id:
819
+ dedup.register(session_id, insight_node_id, req.content)
820
+
821
+ # 5. Broadcast
822
+ await pubsub_hub.publish(ContextEvent(
823
+ event_type="context_added",
824
+ session_id=session_id,
825
+ agent_id=req.agent_id,
826
+ payload={
827
+ "type": "insight",
828
+ "role": req.role,
829
+ "label": req.label,
830
+ "insight_node_id": insight_node_id,
831
+ "source_ids": req.source_ids,
832
+ },
833
+ ))
834
+
835
+ session_manager.touch_session(session_id)
836
+
837
+ return {
838
+ "success": True,
839
+ "insight_node_id": insight_node_id,
840
+ "item_count": len(hub),
841
+ }
842
+
843
+ # ==================================================================
844
+ # Export / query endpoints
845
+ # ==================================================================
846
+
847
+ @router.get("/sessions/{session_id}/export")
848
+ async def export_context(
849
+ session_id: str,
850
+ format: str = Query("messages", description="messages|prompt|markdown|dict"),
851
+ pii_mode: str = Query("block", description="block|mask|redact|allow"),
852
+ max_tokens: Optional[int] = Query(None, description="Token budget (applies scoping)"),
853
+ strategy: Optional[str] = Query(None, description="Scoping strategy: recency|relevance|role_priority|combined"),
854
+ caller: AgentIdentity = Depends(require_read),
855
+ ):
856
+ """Export context filtered by the calling agent's access level + tag grants."""
857
+ session = session_manager.get_session(session_id)
858
+ if not session:
859
+ raise HTTPException(404, "Session not found")
860
+
861
+ # Verify agent has access to this session
862
+ if session.owner_agent_id != caller.agent_id:
863
+ if not session_manager.check_access(session_id, caller.agent_id, "read"):
864
+ raise HTTPException(403, "No access to this session")
865
+
866
+ if pii_mode not in ("block", "mask", "redact", "allow"):
867
+ raise HTTPException(400, "pii_mode must be one of: block, mask, redact, allow")
868
+
869
+ hub = _get_hub(session_id)
870
+
871
+ # Also pull graph nodes into the hub if it's empty
872
+ if len(hub) == 0 and graph_registry:
873
+ graph = graph_registry.get_graph(session.graph_namespace, load_if_missing=True)
874
+ if graph:
875
+ try:
876
+ all_nodes = graph.get_all_nodes()
877
+ if all_nodes:
878
+ hub.add_nodes(all_nodes, label="Session Graph Nodes")
879
+ except Exception:
880
+ pass # optional
881
+
882
+ # --- Authorization-filtered export ---
883
+ access_info = session_manager.get_agent_access(session_id, caller.agent_id)
884
+ if access_info:
885
+ access_level = access_info["access_level"]
886
+ allowed_tags = access_info["allowed_tags"]
887
+ else:
888
+ access_level = "read"
889
+ allowed_tags = []
890
+
891
+ # Build a filtered hub containing only items this agent may see
892
+ visible_items = hub.filter_for_agent(access_level, allowed_tags, pii_mode=pii_mode)
893
+ filtered_hub = ContextHub(max_tokens=hub.max_tokens)
894
+ filtered_hub._items = visible_items
895
+
896
+ # Apply scoping if requested
897
+ budget = max_tokens
898
+ if budget or strategy or session_id in _scoping_configs:
899
+ cfg = _scoping_configs.get(session_id, ScopingConfig())
900
+ if strategy:
901
+ cfg.strategy = strategy
902
+ if budget:
903
+ cfg.max_tokens = budget
904
+ filtered_hub.set_scoping(cfg)
905
+
906
+ if format in ("messages", "prompt"):
907
+ return filtered_hub.export(format) if not budget else (
908
+ filtered_hub.to_messages(budget=budget) if format == "messages"
909
+ else filtered_hub.to_prompt(budget=budget)
910
+ )
911
+ return filtered_hub.export(format)
912
+
913
+ @router.post("/sessions/{session_id}/query")
914
+ async def query_session(session_id: str, req: QueryRequest, caller: AgentIdentity = Depends(require_read)):
915
+ """Execute an AIQL query scoped to the session's graph namespace."""
916
+ session = session_manager.get_session(session_id)
917
+ if not session:
918
+ raise HTTPException(404, "Session not found")
919
+
920
+ # Verify agent has access to this session
921
+ if session.owner_agent_id != caller.agent_id:
922
+ if not session_manager.check_access(session_id, caller.agent_id, "read"):
923
+ raise HTTPException(403, "No access to this session")
924
+
925
+ if not graph_registry:
926
+ raise HTTPException(500, "Graph registry not available")
927
+
928
+ graph = graph_registry.get_graph(session.graph_namespace, load_if_missing=True)
929
+ if not graph:
930
+ # Create graph on first query if it doesn't exist yet
931
+ graph = graph_registry.create_graph(session.graph_namespace)
932
+ if not graph:
933
+ return {"nodes": [], "edges": [], "message": "Could not create graph"}
934
+
935
+ try:
936
+ from ..aiql.engine import AIQLExecutor
937
+
938
+ ex = AIQLExecutor(contextcore=graph, graph_registry=graph_registry)
939
+ ex.active_namespace = session.graph_namespace
940
+ result = ex.execute(req.query)
941
+
942
+ # Persist after write operations
943
+ query_upper = req.query.strip().upper()
944
+ if query_upper.startswith("CREATE") or query_upper.startswith("DELETE") or query_upper.startswith("UPDATE"):
945
+ try:
946
+ graph_registry.save_graph(session.graph_namespace, create_checkpoint=False)
947
+ except Exception:
948
+ pass # optional
949
+
950
+ return {"success": True, "data": result}
951
+ except Exception as e:
952
+ raise HTTPException(400, f"Query failed: {e}")
953
+
954
+ # ==================================================================
955
+ # Semantic search (vector DB)
956
+ # ==================================================================
957
+
958
+ @router.post("/sessions/{session_id}/search")
959
+ async def semantic_search(
960
+ session_id: str,
961
+ req: SemanticSearchRequest,
962
+ caller: AgentIdentity = Depends(require_read),
963
+ ):
964
+ """Semantic similarity search across a session's embedded context."""
965
+ session = session_manager.get_session(session_id)
966
+ if not session:
967
+ raise HTTPException(404, "Session not found")
968
+
969
+ if not vector_store.available:
970
+ raise HTTPException(503, "Vector search unavailable (no vector backend)")
971
+
972
+ results = vector_store.search(
973
+ session_id=session_id,
974
+ query=req.query,
975
+ k=req.k,
976
+ )
977
+
978
+ return {"success": True, "results": results, "count": len(results)}
979
+
980
+ @router.get("/sessions/{session_id}/vectors/stats")
981
+ async def vector_stats(session_id: str, caller: AgentIdentity = Depends(require_read)):
982
+ """Get vector store statistics for a session."""
983
+ return vector_store.get_stats(session_id)
984
+
985
+ # ==================================================================
986
+ # Provenance
987
+ # ==================================================================
988
+
989
+ @router.get("/sessions/{session_id}/provenance")
990
+ async def get_provenance(
991
+ session_id: str,
992
+ agent_id: Optional[str] = Query(None),
993
+ limit: int = Query(100),
994
+ caller: AgentIdentity = Depends(require_read),
995
+ ):
996
+ records = agent_registry.get_provenance(
997
+ session_id=session_id,
998
+ agent_id=agent_id,
999
+ limit=limit,
1000
+ )
1001
+ return {"provenance": records, "count": len(records)}
1002
+
1003
+ # ==================================================================
1004
+ # Blob endpoints
1005
+ # ==================================================================
1006
+
1007
+ @router.post("/sessions/{session_id}/blobs")
1008
+ async def upload_blob(
1009
+ session_id: str,
1010
+ file: UploadFile = File(...),
1011
+ agent_id: str = Query(...),
1012
+ caller: AgentIdentity = Depends(require_read),
1013
+ ):
1014
+ session = session_manager.get_session(session_id)
1015
+ if not session:
1016
+ raise HTTPException(404, "Session not found")
1017
+
1018
+ data = await file.read()
1019
+ mime = file.content_type or "application/octet-stream"
1020
+
1021
+ blob_info = blob_store.store(
1022
+ session_id=session_id,
1023
+ data=data,
1024
+ filename=file.filename or "upload",
1025
+ mime_type=mime,
1026
+ agent_id=agent_id,
1027
+ )
1028
+
1029
+ agent_registry.record_provenance(ProvenanceRecord(
1030
+ session_id=session_id,
1031
+ agent_id=agent_id,
1032
+ operation="ingest",
1033
+ target_type="blob",
1034
+ target_id=blob_info["blob_id"],
1035
+ ))
1036
+
1037
+ await pubsub_hub.publish(ContextEvent(
1038
+ event_type="context_added",
1039
+ session_id=session_id,
1040
+ agent_id=agent_id,
1041
+ payload={"blob_id": blob_info["blob_id"], "filename": file.filename},
1042
+ ))
1043
+
1044
+ return {"success": True, **blob_info}
1045
+
1046
+ @router.get("/sessions/{session_id}/blobs")
1047
+ async def list_blobs(session_id: str, caller: AgentIdentity = Depends(require_read)):
1048
+ return blob_store.list_blobs(session_id)
1049
+
1050
+ @router.get("/sessions/{session_id}/blobs/{blob_id}")
1051
+ async def get_blob(session_id: str, blob_id: str, caller: AgentIdentity = Depends(require_read)):
1052
+ result = blob_store.get(blob_id)
1053
+ if not result:
1054
+ raise HTTPException(404, "Blob not found")
1055
+ raw_bytes, meta = result
1056
+ from fastapi.responses import Response
1057
+ return Response(
1058
+ content=raw_bytes,
1059
+ media_type=meta.get("mime_type", "application/octet-stream"),
1060
+ headers={"Content-Disposition": f'attachment; filename="{meta.get("filename", "download")}"'},
1061
+ )
1062
+
1063
+ @router.delete("/sessions/{session_id}/blobs/{blob_id}")
1064
+ async def delete_blob(session_id: str, blob_id: str, caller: AgentIdentity = Depends(require_write)):
1065
+ ok = blob_store.delete(blob_id)
1066
+ if not ok:
1067
+ raise HTTPException(404, "Blob not found")
1068
+ return {"success": True}
1069
+
1070
+ # ==================================================================
1071
+ # WebSocket — real-time context subscription
1072
+ # ==================================================================
1073
+
1074
+ @router.websocket("/sessions/{session_id}/ws")
1075
+ async def websocket_subscribe(websocket: WebSocket, session_id: str):
1076
+ await websocket.accept()
1077
+
1078
+ # Expect first message to be {"agent_id": "..."}
1079
+ try:
1080
+ init = await websocket.receive_json()
1081
+ agent_id = init.get("agent_id", "anonymous")
1082
+ except Exception:
1083
+ agent_id = "anonymous"
1084
+
1085
+ conn_id = secrets.token_hex(8)
1086
+ await pubsub_hub.subscribe(
1087
+ session_id=session_id,
1088
+ connection_id=conn_id,
1089
+ agent_id=agent_id,
1090
+ send_fn=websocket.send_text,
1091
+ )
1092
+
1093
+ try:
1094
+ while True:
1095
+ # Keep connection alive; clients can also send messages
1096
+ data = await websocket.receive_text()
1097
+ # Echo back or handle client-side events if needed
1098
+ except WebSocketDisconnect:
1099
+ await pubsub_hub.unsubscribe(conn_id)
1100
+
1101
+ # ==================================================================
1102
+ # Scoping config endpoints
1103
+ # ==================================================================
1104
+
1105
+ @router.put("/sessions/{session_id}/scoping")
1106
+ async def set_scoping_config(
1107
+ session_id: str, req: ScopingConfigRequest,
1108
+ caller: AgentIdentity = Depends(require_agent_admin),
1109
+ ):
1110
+ """Set token-budget scoping config for a session (requires admin)."""
1111
+ session = session_manager.get_session(session_id)
1112
+ if not session:
1113
+ raise HTTPException(404, "Session not found")
1114
+ cfg = ScopingConfig(
1115
+ max_tokens=req.max_tokens,
1116
+ strategy=req.strategy,
1117
+ role_weights=req.role_weights or dict(ScopingConfig().role_weights),
1118
+ recency_decay=req.recency_decay,
1119
+ reserve_system=req.reserve_system,
1120
+ )
1121
+ _scoping_configs[session_id] = cfg
1122
+ return {"success": True, "config": {
1123
+ "max_tokens": cfg.max_tokens,
1124
+ "strategy": cfg.strategy,
1125
+ "recency_decay": cfg.recency_decay,
1126
+ "reserve_system": cfg.reserve_system,
1127
+ }}
1128
+
1129
+ @router.get("/sessions/{session_id}/scoping")
1130
+ async def get_scoping_config(session_id: str, caller: AgentIdentity = Depends(require_read)):
1131
+ """Get current scoping config for a session."""
1132
+ cfg = _scoping_configs.get(session_id)
1133
+ if not cfg:
1134
+ return {"configured": False, "config": None}
1135
+ return {"configured": True, "config": {
1136
+ "max_tokens": cfg.max_tokens,
1137
+ "strategy": cfg.strategy,
1138
+ "recency_decay": cfg.recency_decay,
1139
+ "reserve_system": cfg.reserve_system,
1140
+ }}
1141
+
1142
+ # ==================================================================
1143
+ # Conversation endpoints
1144
+ # ==================================================================
1145
+
1146
+ @router.post("/conversations")
1147
+ async def create_conversation(
1148
+ req: ConversationCreateRequest,
1149
+ caller: AgentIdentity = Depends(require_write),
1150
+ ):
1151
+ """Create a new conversation tied to a session."""
1152
+ session = session_manager.get_session(req.session_id)
1153
+ if not session:
1154
+ raise HTTPException(404, "Session not found")
1155
+ conv = conversation_store.create(
1156
+ session_id=req.session_id,
1157
+ title=req.title,
1158
+ metadata=req.metadata,
1159
+ ttl_seconds=req.ttl_seconds,
1160
+ )
1161
+ return {"success": True, **conv.to_dict()}
1162
+
1163
+ @router.get("/conversations")
1164
+ async def list_conversations(
1165
+ session_id: str = Query(...),
1166
+ status: str = Query("active"),
1167
+ caller: AgentIdentity = Depends(require_read),
1168
+ ):
1169
+ convs = conversation_store.list_conversations(session_id, status=status)
1170
+ return {"conversations": [c.to_dict() for c in convs], "count": len(convs)}
1171
+
1172
+ @router.get("/conversations/{conversation_id}")
1173
+ async def get_conversation(
1174
+ conversation_id: str,
1175
+ caller: AgentIdentity = Depends(require_read),
1176
+ ):
1177
+ conv = conversation_store.get(conversation_id)
1178
+ if not conv:
1179
+ raise HTTPException(404, "Conversation not found")
1180
+ return {
1181
+ **conv.to_dict(),
1182
+ "messages": [m.to_dict() for m in conv.messages],
1183
+ }
1184
+
1185
+ @router.delete("/conversations/{conversation_id}")
1186
+ async def delete_conversation(
1187
+ conversation_id: str,
1188
+ caller: AgentIdentity = Depends(require_agent_admin),
1189
+ ):
1190
+ ok = conversation_store.delete(conversation_id)
1191
+ if not ok:
1192
+ raise HTTPException(404, "Conversation not found")
1193
+ return {"success": True}
1194
+
1195
+ @router.post("/conversations/{conversation_id}/messages")
1196
+ async def append_conversation_message(
1197
+ conversation_id: str,
1198
+ req: ConversationMessageRequest,
1199
+ background_tasks: BackgroundTasks,
1200
+ caller: AgentIdentity = Depends(require_write),
1201
+ ):
1202
+ """Append a message to a conversation.
1203
+
1204
+ Also graphifies the interaction into the session's unified graph
1205
+ (fire-and-forget background task) so agents can later traverse
1206
+ from conversation turns to knowledge entities.
1207
+ """
1208
+ conv = conversation_store.get(conversation_id, include_messages=False)
1209
+ if not conv:
1210
+ raise HTTPException(404, "Conversation not found")
1211
+ msg = conversation_store.append_message(
1212
+ conversation_id=conversation_id,
1213
+ role=req.role,
1214
+ content=req.content,
1215
+ agent_id=req.agent_id,
1216
+ metadata=req.metadata,
1217
+ )
1218
+
1219
+ # Fire-and-forget: graphify interaction into session graph
1220
+ if hasattr(conv, "session_id") and conv.session_id:
1221
+ builder = await _get_or_create_builder(conv.session_id)
1222
+ if builder:
1223
+ background_tasks.add_task(
1224
+ builder.graphify_interaction,
1225
+ role=req.role,
1226
+ content=req.content,
1227
+ agent_id=req.agent_id or caller.agent_id,
1228
+ conversation_id=conversation_id,
1229
+ )
1230
+
1231
+ return {"success": True, **msg.to_dict()}
1232
+
1233
+ @router.get("/conversations/{conversation_id}/messages")
1234
+ async def get_conversation_messages(
1235
+ conversation_id: str,
1236
+ limit: Optional[int] = Query(None),
1237
+ offset: int = Query(0),
1238
+ caller: AgentIdentity = Depends(require_read),
1239
+ ):
1240
+ messages = conversation_store.get_history(conversation_id, limit=limit, offset=offset)
1241
+ return {"messages": [m.to_dict() for m in messages], "count": len(messages)}
1242
+
1243
+ @router.put("/conversations/{conversation_id}/kv")
1244
+ async def set_conversation_kv(
1245
+ conversation_id: str,
1246
+ req: ConversationKVRequest,
1247
+ caller: AgentIdentity = Depends(require_write),
1248
+ ):
1249
+ """Set a key-value pair on a conversation."""
1250
+ conv = conversation_store.get(conversation_id, include_messages=False)
1251
+ if not conv:
1252
+ raise HTTPException(404, "Conversation not found")
1253
+ conversation_store.set_kv(conversation_id, req.key, req.value)
1254
+ return {"success": True, "key": req.key}
1255
+
1256
+ @router.get("/conversations/{conversation_id}/kv")
1257
+ async def get_conversation_kv_all(
1258
+ conversation_id: str,
1259
+ caller: AgentIdentity = Depends(require_read),
1260
+ ):
1261
+ """Get all KV pairs for a conversation."""
1262
+ return conversation_store.get_all_kv(conversation_id)
1263
+
1264
+ @router.get("/conversations/{conversation_id}/kv/{key}")
1265
+ async def get_conversation_kv(
1266
+ conversation_id: str,
1267
+ key: str,
1268
+ caller: AgentIdentity = Depends(require_read),
1269
+ ):
1270
+ """Get a single KV value."""
1271
+ value = conversation_store.get_kv(conversation_id, key)
1272
+ if value is None:
1273
+ raise HTTPException(404, f"Key '{key}' not found")
1274
+ return {"key": key, "value": value}
1275
+
1276
+ @router.delete("/conversations/{conversation_id}/kv/{key}")
1277
+ async def delete_conversation_kv(
1278
+ conversation_id: str,
1279
+ key: str,
1280
+ caller: AgentIdentity = Depends(require_write),
1281
+ ):
1282
+ ok = conversation_store.delete_kv(conversation_id, key)
1283
+ if not ok:
1284
+ raise HTTPException(404, f"Key '{key}' not found")
1285
+ return {"success": True}
1286
+
1287
+ @router.get("/conversations/{conversation_id}/export")
1288
+ async def export_conversation(
1289
+ conversation_id: str,
1290
+ format: str = Query("messages", description="messages|prompt|markdown|dict"),
1291
+ last_n: Optional[int] = Query(None, description="Only export last N messages"),
1292
+ system_prompt: Optional[str] = Query(None),
1293
+ caller: AgentIdentity = Depends(require_read),
1294
+ ):
1295
+ """Export a conversation as a ContextHub format."""
1296
+ conv = conversation_store.get(conversation_id, include_messages=False)
1297
+ if not conv:
1298
+ raise HTTPException(404, "Conversation not found")
1299
+ hub = conversation_store.to_hub(conversation_id, system_prompt=system_prompt, last_n=last_n)
1300
+ return hub.export(format)
1301
+
1302
+ # ==================================================================
1303
+ # Embedding capabilities
1304
+ # ==================================================================
1305
+
1306
+ @router.get("/capabilities")
1307
+ async def get_capabilities():
1308
+ """Report available embedding/extraction capabilities."""
1309
+ return {
1310
+ **embedding_hooks.capabilities,
1311
+ "embedding_service": vector_store._embedding_service is not None,
1312
+ }
1313
+
1314
+ # ==================================================================
1315
+ # Health / info
1316
+ # ==================================================================
1317
+
1318
+ @router.get("/health")
1319
+ async def context_health():
1320
+ # Lazy TTL cleanup
1321
+ try:
1322
+ conversation_store.cleanup_expired()
1323
+ except Exception:
1324
+ pass # optional
1325
+
1326
+ return {
1327
+ "status": "ok",
1328
+ "agents": len(agent_registry.list_agents()),
1329
+ "sessions": len(session_manager.list_sessions()),
1330
+ "vector_backend": vector_store._manager.backend_name if vector_store._manager else None,
1331
+ "vector_available": vector_store.available,
1332
+ "embedding_available": vector_store._embedding_service is not None,
1333
+ "embedding_capabilities": embedding_hooks.capabilities,
1334
+ }
1335
+
1336
+ # ------------------------------------------------------------------
1337
+ # Agent Memory endpoints
1338
+ # ------------------------------------------------------------------
1339
+
1340
+ _agent_memory = None
1341
+
1342
+ def _get_memory():
1343
+ nonlocal _agent_memory
1344
+ if _agent_memory is None:
1345
+ if graph_registry:
1346
+ from ..context.agent_memory import AgentMemory
1347
+ _agent_memory = AgentMemory(graph_registry)
1348
+ else:
1349
+ raise HTTPException(status_code=503, detail="Graph registry not available")
1350
+ return _agent_memory
1351
+
1352
+ class RememberRequest(BaseModel):
1353
+ content: str = Field(..., min_length=1)
1354
+ tags: Optional[List[str]] = None
1355
+ confidence: float = 1.0
1356
+ source: Optional[str] = None
1357
+ metadata: Optional[Dict[str, Any]] = None
1358
+
1359
+ class InteractionRequest(BaseModel):
1360
+ role: str = Field(..., min_length=1)
1361
+ content: str = Field(..., min_length=1)
1362
+ session_id: Optional[str] = None
1363
+ metadata: Optional[Dict[str, Any]] = None
1364
+
1365
+ class BuildContextRequest(BaseModel):
1366
+ system_prompt: Optional[str] = None
1367
+ include_memories: bool = True
1368
+ include_recent_interactions: int = 10
1369
+ memory_tags: Optional[List[str]] = None
1370
+ memory_limit: int = 20
1371
+ max_tokens: int = 8000
1372
+
1373
+ @router.post("/context/agents/{agent_id}/memory")
1374
+ async def remember(agent_id: str, req: RememberRequest, agent=Depends(require_write)):
1375
+ """Store a memory for an agent."""
1376
+ memory = _get_memory()
1377
+ mem_id = memory.remember(
1378
+ agent_id=agent_id,
1379
+ content=req.content,
1380
+ tags=req.tags,
1381
+ confidence=req.confidence,
1382
+ source=req.source,
1383
+ metadata=req.metadata,
1384
+ )
1385
+ return {"memory_id": mem_id, "agent_id": agent_id}
1386
+
1387
+ @router.get("/context/agents/{agent_id}/memory")
1388
+ async def recall(
1389
+ agent_id: str,
1390
+ query: Optional[str] = None,
1391
+ tags: Optional[str] = None,
1392
+ limit: int = 10,
1393
+ agent=Depends(require_read),
1394
+ ):
1395
+ """Recall memories for an agent."""
1396
+ memory = _get_memory()
1397
+ tag_list = tags.split(",") if tags else None
1398
+ memories = memory.recall(agent_id=agent_id, query=query, tags=tag_list, limit=limit)
1399
+ return {"agent_id": agent_id, "memories": memories, "count": len(memories)}
1400
+
1401
+ @router.delete("/context/agents/{agent_id}/memory/{memory_id}")
1402
+ async def forget(agent_id: str, memory_id: str, agent=Depends(require_write)):
1403
+ """Remove a specific memory."""
1404
+ memory = _get_memory()
1405
+ ok = memory.forget(agent_id, memory_id)
1406
+ if not ok:
1407
+ raise HTTPException(status_code=404, detail="Memory not found")
1408
+ return {"message": "Memory removed", "memory_id": memory_id}
1409
+
1410
+ @router.post("/context/agents/{agent_id}/sessions")
1411
+ async def start_session(agent_id: str, agent=Depends(require_write)):
1412
+ """Start a new interaction session."""
1413
+ memory = _get_memory()
1414
+ session_id = memory.start_session(agent_id)
1415
+ return {"session_id": session_id, "agent_id": agent_id}
1416
+
1417
+ @router.post("/context/agents/{agent_id}/interactions")
1418
+ async def log_interaction(agent_id: str, req: InteractionRequest, agent=Depends(require_write)):
1419
+ """Log an interaction turn."""
1420
+ memory = _get_memory()
1421
+ interaction_id = memory.log_interaction(
1422
+ agent_id=agent_id,
1423
+ role=req.role,
1424
+ content=req.content,
1425
+ session_id=req.session_id,
1426
+ metadata=req.metadata,
1427
+ )
1428
+ return {"interaction_id": interaction_id, "session_id": req.session_id}
1429
+
1430
+ @router.post("/context/agents/{agent_id}/build-context")
1431
+ async def build_agent_context(agent_id: str, req: BuildContextRequest, agent=Depends(require_write)):
1432
+ """Build a ContextHub pre-loaded with agent memories and interactions."""
1433
+ memory = _get_memory()
1434
+ hub = memory.build_context(
1435
+ agent_id=agent_id,
1436
+ system_prompt=req.system_prompt,
1437
+ include_memories=req.include_memories,
1438
+ include_recent_interactions=req.include_recent_interactions,
1439
+ memory_tags=req.memory_tags,
1440
+ memory_limit=req.memory_limit,
1441
+ max_tokens=req.max_tokens,
1442
+ )
1443
+ return {
1444
+ "agent_id": agent_id,
1445
+ "messages": hub.to_messages(),
1446
+ "item_count": len(hub._items),
1447
+ }
1448
+
1449
+ @router.get("/context/agents/{agent_id}/summary")
1450
+ async def get_agent_memory_summary(agent_id: str, agent=Depends(require_read)):
1451
+ """Get summary of agent's memory state."""
1452
+ memory = _get_memory()
1453
+ return memory.get_agent_summary(agent_id)
1454
+
1455
+ @router.post("/context/agents/{agent_id}/share/{to_agent_id}")
1456
+ async def share_memory(
1457
+ agent_id: str, to_agent_id: str, memory_id: str = Query(...), agent=Depends(require_write)
1458
+ ):
1459
+ """Share a memory from one agent to another."""
1460
+ memory = _get_memory()
1461
+ ok = memory.share_memory(agent_id, to_agent_id, memory_id)
1462
+ if not ok:
1463
+ raise HTTPException(status_code=404, detail="Memory not found")
1464
+ return {"message": "Memory shared", "from": agent_id, "to": to_agent_id}
1465
+
1466
+ # ==================================================================
1467
+ # Session Graph endpoints (unified knowledge + conversation layer)
1468
+ # ==================================================================
1469
+
1470
+ @router.get("/sessions/{session_id}/graph/schema")
1471
+ async def get_session_graph_schema(
1472
+ session_id: str,
1473
+ caller: AgentIdentity = Depends(require_read),
1474
+ ):
1475
+ """Get the session graph schema definition."""
1476
+ builder = await _get_or_create_builder(session_id)
1477
+ if not builder:
1478
+ raise HTTPException(404, "Session not found")
1479
+ return builder.get_schema_dict()
1480
+
1481
+ @router.put("/sessions/{session_id}/graph/schema")
1482
+ async def update_session_graph_schema(
1483
+ session_id: str,
1484
+ req: Dict[str, Any],
1485
+ caller: AgentIdentity = Depends(require_agent_admin),
1486
+ ):
1487
+ """Update the session graph schema with new YAML content."""
1488
+ builder = await _get_or_create_builder(session_id)
1489
+ if not builder:
1490
+ raise HTTPException(404, "Session not found")
1491
+ yaml_content = req.get("yaml", "")
1492
+ if not yaml_content:
1493
+ raise HTTPException(400, "Missing 'yaml' field in request body")
1494
+ try:
1495
+ new_schema = await builder.update_schema(yaml_content)
1496
+ return {
1497
+ "success": True,
1498
+ "node_types": list(new_schema.node_types.keys()),
1499
+ "edge_types": list(new_schema.edge_types.keys()),
1500
+ }
1501
+ except Exception as e:
1502
+ raise HTTPException(400, f"Invalid schema: {e}")
1503
+
1504
+ @router.get("/sessions/{session_id}/graph/context")
1505
+ async def get_session_graph_context(
1506
+ session_id: str,
1507
+ system_prompt: str = Query("You are an AI assistant."),
1508
+ max_tokens: int = Query(8000),
1509
+ recent_turns: int = Query(20),
1510
+ format: str = Query("messages", description="messages|prompt|markdown|dict"),
1511
+ caller: AgentIdentity = Depends(require_read),
1512
+ ):
1513
+ """Build graph-aware context from the unified session graph.
1514
+
1515
+ Follows REFERENCES edges from recent conversation turns to pull
1516
+ in contextually relevant knowledge nodes.
1517
+ """
1518
+ builder = await _get_or_create_builder(session_id)
1519
+ if not builder:
1520
+ raise HTTPException(404, "Session not found")
1521
+ hub = builder.build_context(
1522
+ agent_id=caller.agent_id,
1523
+ system_prompt=system_prompt,
1524
+ max_tokens=max_tokens,
1525
+ recent_turns=recent_turns,
1526
+ )
1527
+ if format == "messages":
1528
+ return {"messages": hub.to_messages(), "item_count": len(hub.items())}
1529
+ elif format == "prompt":
1530
+ return {"prompt": hub.to_string(), "item_count": len(hub.items())}
1531
+ elif format == "markdown":
1532
+ return {"markdown": hub.to_markdown(), "item_count": len(hub.items())}
1533
+ else:
1534
+ return hub.to_dict()
1535
+
1536
+ @router.get("/sessions/{session_id}/graph/turns")
1537
+ async def get_session_graph_turns(
1538
+ session_id: str,
1539
+ limit: int = Query(20),
1540
+ caller: AgentIdentity = Depends(require_read),
1541
+ ):
1542
+ """Get recent Turn nodes from the session graph."""
1543
+ builder = await _get_or_create_builder(session_id)
1544
+ if not builder:
1545
+ raise HTTPException(404, "Session not found")
1546
+ turns = builder.get_recent_turns(limit=limit)
1547
+ return {"turns": turns, "count": len(turns)}
1548
+
1549
+ @router.get("/sessions/{session_id}/graph/decisions")
1550
+ async def get_session_graph_decisions(
1551
+ session_id: str,
1552
+ include_done: bool = Query(False),
1553
+ caller: AgentIdentity = Depends(require_read),
1554
+ ):
1555
+ """Get Decision nodes from the session graph."""
1556
+ builder = await _get_or_create_builder(session_id)
1557
+ if not builder:
1558
+ raise HTTPException(404, "Session not found")
1559
+ decisions = builder.get_decisions(include_done=include_done)
1560
+ return {"decisions": decisions, "count": len(decisions)}
1561
+
1562
+ @router.get("/sessions/{session_id}/graph/topics")
1563
+ async def get_session_graph_topics(
1564
+ session_id: str,
1565
+ caller: AgentIdentity = Depends(require_read),
1566
+ ):
1567
+ """Get Topic nodes from the session graph, sorted by mention count."""
1568
+ builder = await _get_or_create_builder(session_id)
1569
+ if not builder:
1570
+ raise HTTPException(404, "Session not found")
1571
+ topics = builder.get_topics()
1572
+ return {"topics": topics, "count": len(topics)}
1573
+
1574
+ @router.get("/sessions/{session_id}/graph/stats")
1575
+ async def get_session_graph_stats(
1576
+ session_id: str,
1577
+ caller: AgentIdentity = Depends(require_read),
1578
+ ):
1579
+ """Get summary statistics of the session graph."""
1580
+ builder = await _get_or_create_builder(session_id)
1581
+ if not builder:
1582
+ raise HTTPException(404, "Session not found")
1583
+ return builder.get_graph_stats()
1584
+
1585
+ # ==================================================================
1586
+ # Context manifest endpoint
1587
+ # ==================================================================
1588
+
1589
+ @router.get("/contexts/{context_id}/manifest")
1590
+ async def get_context_manifest(
1591
+ context_id: str,
1592
+ caller: AgentIdentity = Depends(require_read),
1593
+ ):
1594
+ """Get the pre-computed context quality manifest."""
1595
+ from contextsynapse.context.quality import get_manifest, ManifestBuilder, EntityIndex
1596
+
1597
+ redis_client = None
1598
+ try:
1599
+ import redis as _redis_mod
1600
+ import os
1601
+ url = os.environ.get("CONTEXTSYNAPSE_REDIS_URL") or os.environ.get("AICONTEXTDB_REDIS_URL", "redis://localhost:6379")
1602
+ redis_client = _redis_mod.from_url(url)
1603
+ except Exception:
1604
+ pass
1605
+
1606
+ manifest = get_manifest(context_id, redis_client)
1607
+ if manifest:
1608
+ return manifest
1609
+
1610
+ # Build on demand if not cached
1611
+ try:
1612
+ eidx = EntityIndex(redis_client=redis_client)
1613
+ builder = ManifestBuilder(entity_index=eidx, redis_client=redis_client)
1614
+ manifest = builder.build(context_id, context_id)
1615
+ return manifest
1616
+ except Exception as e:
1617
+ raise HTTPException(status_code=500, detail=f"Failed to build manifest: {e}")
1618
+
1619
+ return router