clsplusplus 7.2.4__tar.gz → 7.3.0__tar.gz

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 (179) hide show
  1. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/PKG-INFO +10 -1
  2. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/pyproject.toml +35 -1
  3. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/api.py +244 -1
  4. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/config.py +13 -0
  5. clsplusplus-7.3.0/src/clsplusplus/embeddings.py +173 -0
  6. clsplusplus-7.3.0/src/clsplusplus/integrations/__init__.py +46 -0
  7. clsplusplus-7.3.0/src/clsplusplus/integrations/autogen.py +283 -0
  8. clsplusplus-7.3.0/src/clsplusplus/integrations/crewai.py +183 -0
  9. clsplusplus-7.3.0/src/clsplusplus/integrations/langgraph.py +330 -0
  10. clsplusplus-7.3.0/src/clsplusplus/integrations/llamaindex.py +253 -0
  11. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/local_routes.py +6 -9
  12. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/memory_phase.py +94 -15
  13. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/memory_service.py +92 -0
  14. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/middleware.py +2 -0
  15. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/models.py +3 -0
  16. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/oauth_server.py +62 -1
  17. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/oauth_store.py +9 -9
  18. clsplusplus-7.3.0/src/clsplusplus/write_metrics.py +74 -0
  19. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/PKG-INFO +10 -1
  20. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/SOURCES.txt +13 -1
  21. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/requires.txt +13 -0
  22. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_admin.py +54 -0
  23. clsplusplus-7.3.0/tests/test_autogen_memory.py +350 -0
  24. clsplusplus-7.3.0/tests/test_crewai_provider.py +161 -0
  25. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_embeddings.py +112 -8
  26. clsplusplus-7.3.0/tests/test_integration_sync_cli_contract.py +129 -0
  27. clsplusplus-7.3.0/tests/test_langgraph_store.py +341 -0
  28. clsplusplus-7.3.0/tests/test_llamaindex_block.py +288 -0
  29. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_mcp_oauth.py +53 -0
  30. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_memory_phase.py +75 -25
  31. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_reconsolidation.py +130 -1
  32. clsplusplus-7.3.0/tests/test_write_metrics.py +100 -0
  33. clsplusplus-7.2.4/src/clsplusplus/embeddings.py +0 -52
  34. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/LICENSE +0 -0
  35. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/README.md +0 -0
  36. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/setup.cfg +0 -0
  37. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/__init__.py +0 -0
  38. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/abuse_guard.py +0 -0
  39. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/api_usage_analytics.py +0 -0
  40. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/auth.py +0 -0
  41. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/cli.py +0 -0
  42. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/client.py +0 -0
  43. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/cost_forecast.py +0 -0
  44. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/cost_model.py +0 -0
  45. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/debug_console.py +0 -0
  46. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/demo_llm.py +0 -0
  47. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/demo_llm_calls.py +0 -0
  48. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/demo_local.py +0 -0
  49. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/email_service.py +0 -0
  50. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/funnel_routes.py +0 -0
  51. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/geo.py +0 -0
  52. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/health_metrics.py +0 -0
  53. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/homepage_autopromote.py +0 -0
  54. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/idempotency.py +0 -0
  55. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/integration_service.py +0 -0
  56. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/jwt_utils.py +0 -0
  57. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/main.py +0 -0
  58. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/mcp_http.py +0 -0
  59. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/mcp_server.py +0 -0
  60. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/memory_cycle.py +0 -0
  61. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/__init__.py +0 -0
  62. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/__main__.py +0 -0
  63. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/billing.py +0 -0
  64. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/healthcheck.py +0 -0
  65. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/notifier.py +0 -0
  66. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/pricing.py +0 -0
  67. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/reconciler.py +0 -0
  68. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/schema.py +0 -0
  69. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metering_v2/writer.py +0 -0
  70. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/metrics.py +0 -0
  71. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/namespace_resolver.py +0 -0
  72. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/permissions.py +0 -0
  73. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/plasticity.py +0 -0
  74. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/pricing.py +0 -0
  75. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/pricing_models.py +0 -0
  76. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/pricing_store.py +0 -0
  77. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/prompt_log.py +0 -0
  78. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/rate_limit.py +0 -0
  79. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/razorpay_service.py +0 -0
  80. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/rbac_service.py +0 -0
  81. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/reconsolidation.py +0 -0
  82. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/resilience.py +0 -0
  83. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/sleep_cycle.py +0 -0
  84. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/__init__.py +0 -0
  85. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/base.py +0 -0
  86. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/chat_session_store.py +0 -0
  87. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/integration_store.py +0 -0
  88. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/l0_working_buffer.py +0 -0
  89. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/l1_indexing_store.py +0 -0
  90. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/l2_schema_graph.py +0 -0
  91. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/l3_deep_recess.py +0 -0
  92. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/l3_postgres.py +0 -0
  93. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/rbac_store.py +0 -0
  94. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/user_store.py +0 -0
  95. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/waitlist_store.py +0 -0
  96. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stores/web_events_store.py +0 -0
  97. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/stripe_service.py +0 -0
  98. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/subscription_watchdog.py +0 -0
  99. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/temporal.py +0 -0
  100. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/test_suite.py +0 -0
  101. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/tier_resolver.py +0 -0
  102. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/tiers.py +0 -0
  103. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/topical_resonance.py +0 -0
  104. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/tracer.py +0 -0
  105. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/usage.py +0 -0
  106. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/user_embeddings.py +0 -0
  107. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/user_pulse.py +0 -0
  108. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/user_service.py +0 -0
  109. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/waitlist_service.py +0 -0
  110. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/webhook_dispatcher.py +0 -0
  111. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/weblab.py +0 -0
  112. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/weblab_watcher.py +0 -0
  113. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus/window_limits.py +0 -0
  114. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/dependency_links.txt +0 -0
  115. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/entry_points.txt +0 -0
  116. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/src/clsplusplus.egg-info/top_level.txt +0 -0
  117. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_abuse_guard.py +0 -0
  118. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_admin_seed.py +0 -0
  119. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_api.py +0 -0
  120. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_api_comprehensive.py +0 -0
  121. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_api_endpoints.py +0 -0
  122. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_api_usage_analytics.py +0 -0
  123. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_auth.py +0 -0
  124. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_auth_me_api_key.py +0 -0
  125. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_billing_e2e.py +0 -0
  126. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_burst_hardening.py +0 -0
  127. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_client_sdk.py +0 -0
  128. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_config.py +0 -0
  129. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_cost_forecast.py +0 -0
  130. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_cross_llm_memory.py +0 -0
  131. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_debug_console.py +0 -0
  132. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_demo_llm.py +0 -0
  133. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_extension_integration.py +0 -0
  134. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_extension_ui.py +0 -0
  135. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_feedback_github.py +0 -0
  136. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_free_tier_lifecycle.py +0 -0
  137. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_full_api_coverage.py +0 -0
  138. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_funnel_metrics.py +0 -0
  139. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_geo_gating.py +0 -0
  140. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_health_metrics.py +0 -0
  141. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_idempotency.py +0 -0
  142. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_integration_service.py +0 -0
  143. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_mcp_connect.py +0 -0
  144. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_memory_cycle.py +0 -0
  145. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_memory_service.py +0 -0
  146. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_memory_write_no_schema_garbage.py +0 -0
  147. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_healthcheck.py +0 -0
  148. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_notifier.py +0 -0
  149. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_pricing.py +0 -0
  150. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_reconciler.py +0 -0
  151. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_schema.py +0 -0
  152. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_metering_v2_writer.py +0 -0
  153. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_middleware.py +0 -0
  154. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_models.py +0 -0
  155. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_overage_billing.py +0 -0
  156. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_performance.py +0 -0
  157. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_plasticity.py +0 -0
  158. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_pricing.py +0 -0
  159. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_prototype_e2e.py +0 -0
  160. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_rate_limit.py +0 -0
  161. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_razorpay_billing.py +0 -0
  162. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_razorpay_subscription_webhooks.py +0 -0
  163. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_regression.py +0 -0
  164. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_resilience.py +0 -0
  165. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_security.py +0 -0
  166. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_sleep_cycle.py +0 -0
  167. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_stores.py +0 -0
  168. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_subscription_watchdog.py +0 -0
  169. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_tier_resolver.py +0 -0
  170. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_tiers.py +0 -0
  171. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_usage.py +0 -0
  172. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_user_auth.py +0 -0
  173. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_user_embeddings.py +0 -0
  174. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_user_pulse.py +0 -0
  175. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_user_stories.py +0 -0
  176. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_waitlist.py +0 -0
  177. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_webhook_dispatcher.py +0 -0
  178. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_weblab.py +0 -0
  179. {clsplusplus-7.2.4 → clsplusplus-7.3.0}/tests/test_window_limits.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clsplusplus
3
- Version: 7.2.4
3
+ Version: 7.3.0
4
4
  Summary: Brain-inspired, model-agnostic persistent memory for LLMs. Learn, recall, forget — like a brain. Works with OpenAI, Claude, Gemini, Llama.
5
5
  Author-email: AlphaForge AI Labs <contact@alphaforge.ai>
6
6
  Maintainer-email: Rajamohan Jabbala <contact@alphaforge.ai>
@@ -52,6 +52,15 @@ Requires-Dist: razorpay>=1.4.0; extra == "server"
52
52
  Requires-Dist: minio>=7.2.0; extra == "server"
53
53
  Requires-Dist: pyarrow>=14.0.0; extra == "server"
54
54
  Requires-Dist: posthog>=3.5.0; extra == "server"
55
+ Provides-Extra: crewai
56
+ Requires-Dist: crewai>=1.0.0; extra == "crewai"
57
+ Provides-Extra: langgraph
58
+ Requires-Dist: langgraph>=1.0.0; extra == "langgraph"
59
+ Provides-Extra: llamaindex
60
+ Requires-Dist: llama-index-core>=0.12.0; extra == "llamaindex"
61
+ Provides-Extra: autogen
62
+ Requires-Dist: autogen-core>=0.4.0; extra == "autogen"
63
+ Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen"
55
64
  Provides-Extra: dev
56
65
  Requires-Dist: pytest>=7.4.0; extra == "dev"
57
66
  Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clsplusplus"
7
- version = "7.2.4"
7
+ version = "7.3.0"
8
8
  description = "Brain-inspired, model-agnostic persistent memory for LLMs. Learn, recall, forget — like a brain. Works with OpenAI, Claude, Gemini, Llama."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -63,6 +63,40 @@ server = [
63
63
  "pyarrow>=14.0.0",
64
64
  "posthog>=3.5.0",
65
65
  ]
66
+ # CrewAI integration — pip install clsplusplus[crewai]
67
+ # Provides clsplusplus.integrations.crewai.CLSMemoryStorage, a native CrewAI
68
+ # external-memory backend. Floor is the CrewAI 1.x line that ships the stable
69
+ # crewai.memory.storage.interface.Storage + ExternalMemory API this adapts to.
70
+ crewai = [
71
+ "crewai>=1.0.0",
72
+ ]
73
+ # LangGraph integration — pip install clsplusplus[langgraph]
74
+ # Provides clsplusplus.integrations.langgraph.CLSMemoryStore, a native
75
+ # subclass of langgraph.store.base.BaseStore (the long-term-memory store you
76
+ # pass as store= to graph.compile). Floor is the LangGraph 1.x line; BaseStore
77
+ # and its Op/Item/SearchItem types are re-exported from langgraph-checkpoint
78
+ # and stable across the 1.x series.
79
+ langgraph = [
80
+ "langgraph>=1.0.0",
81
+ ]
82
+ # LlamaIndex integration — pip install clsplusplus[llamaindex]
83
+ # Provides clsplusplus.integrations.llamaindex.CLSMemoryBlock, a native subclass
84
+ # of llama_index.core.memory.BaseMemoryBlock (a pluggable memory_block you pass
85
+ # to Memory.from_defaults). Floor is the 0.12 line that introduced the modern
86
+ # Memory + BaseMemoryBlock API; verified against llama-index-core 0.14.x.
87
+ llamaindex = [
88
+ "llama-index-core>=0.12.0",
89
+ ]
90
+ # AutoGen integration — pip install clsplusplus[autogen]
91
+ # Provides clsplusplus.integrations.autogen.CLSMemory, a native implementation
92
+ # of Microsoft AutoGen's autogen_core.memory.Memory protocol (the object you
93
+ # pass to AssistantAgent(memory=[...])). Floors target the new AutoGen line
94
+ # (autogen-core / autogen-agentchat 0.4+); the Memory protocol + MemoryContent /
95
+ # MemoryQueryResult / UpdateContextResult shapes are stable across 0.4 → 0.7.x.
96
+ autogen = [
97
+ "autogen-core>=0.4.0",
98
+ "autogen-agentchat>=0.4.0",
99
+ ]
66
100
  dev = [
67
101
  "pytest>=7.4.0",
68
102
  "pytest-asyncio>=0.23.0",
@@ -25,6 +25,7 @@ from clsplusplus.user_service import UserService
25
25
  from clsplusplus.middleware import AuthMiddleware, HealthMetricsMiddleware, QuotaMiddleware, RateLimitMiddleware, RequestIdMiddleware, TracingMiddleware
26
26
  from clsplusplus.abuse_guard import AbuseGuardMiddleware
27
27
  from clsplusplus.tracer import tracer
28
+ from clsplusplus import write_metrics
28
29
  from clsplusplus.models import (
29
30
  AdjudicateRequest,
30
31
  ApiKeyCreate,
@@ -1232,7 +1233,14 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
1232
1233
  "store": "Setup",
1233
1234
  "error": "Add CLS_REDIS_URL and CLS_DATABASE_URL in Render Dashboard.",
1234
1235
  }
1235
- return HealthResponse(status=h["status"], stores=stores)
1236
+ wm = write_metrics.snapshot()
1237
+ return HealthResponse(
1238
+ status=h["status"],
1239
+ stores=stores,
1240
+ writes=wm["total_writes"],
1241
+ degraded_writes=wm["degraded_writes"],
1242
+ degraded_write_rate=wm["degraded_write_rate"],
1243
+ )
1236
1244
 
1237
1245
  @app.get("/v1/health/score", response_model=HealthResponse)
1238
1246
  async def health_score():
@@ -2430,6 +2438,241 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
2430
2438
  logger.error("Admin user list error: %s: %s", type(e).__name__, e)
2431
2439
  raise HTTPException(status_code=500, detail="User data unavailable")
2432
2440
 
2441
+ @app.get("/admin/integrations")
2442
+ async def admin_integrations(request: Request):
2443
+ """Per-integration release sync: LIVE published version of each
2444
+ publishable artifact vs the current main release tag.
2445
+
2446
+ The "main release tag" is the single source of truth that every
2447
+ integration is supposed to track (the rule: the release tag IS the
2448
+ version for every artifact). We read it from the newest git tag; if
2449
+ the deployed image has stripped `.git`, we fall back to the committed
2450
+ package version. Each integration's `live_version` is read from its
2451
+ own registry/manifest using the same checks the sync tooling uses.
2452
+
2453
+ Fully resilient: every external/registry probe degrades to a
2454
+ `live_version` of None and `in_sync: false` rather than failing the
2455
+ request. This endpoint NEVER returns 500.
2456
+ """
2457
+ _require_admin(request)
2458
+
2459
+ # --- main release tag: the source of truth every artifact tracks ---
2460
+ def _read_main_tag() -> str:
2461
+ # 1) newest git tag (release-of-record).
2462
+ try:
2463
+ import subprocess
2464
+ repo_root = FilePath(__file__).resolve().parent.parent.parent
2465
+ out = subprocess.run(
2466
+ ["git", "describe", "--tags", "--abbrev=0"],
2467
+ cwd=str(repo_root),
2468
+ capture_output=True,
2469
+ timeout=5,
2470
+ )
2471
+ if out.returncode == 0:
2472
+ tag = out.stdout.decode().strip()
2473
+ if tag:
2474
+ return tag
2475
+ except Exception:
2476
+ pass
2477
+ # 2) committed package version (prod images often strip .git).
2478
+ try:
2479
+ pyproject = FilePath(__file__).resolve().parent.parent.parent / "pyproject.toml"
2480
+ if pyproject.exists():
2481
+ for line in pyproject.read_text().splitlines():
2482
+ s = line.strip()
2483
+ if s.startswith("version") and "=" in s:
2484
+ v = s.split("=", 1)[1].strip().strip('"').strip("'")
2485
+ if v:
2486
+ return f"v{v}"
2487
+ except Exception:
2488
+ pass
2489
+ # 3) last resort: in-package __version__.
2490
+ try:
2491
+ from clsplusplus import __version__ as _v
2492
+ if _v:
2493
+ return f"v{_v}"
2494
+ except Exception:
2495
+ pass
2496
+ return "unknown"
2497
+
2498
+ def _norm(v):
2499
+ """Normalise a version for comparison: drop leading 'v', None-safe."""
2500
+ if not v:
2501
+ return None
2502
+ return str(v).strip().lstrip("v") or None
2503
+
2504
+ def _http_json(url: str, timeout: float = 4.0):
2505
+ """Best-effort HTTP GET -> parsed JSON; None on any failure."""
2506
+ try:
2507
+ import json as _json
2508
+ import urllib.request
2509
+ req = urllib.request.Request(url, headers={"User-Agent": "cls-admin-integrations"})
2510
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
2511
+ return _json.loads(resp.read().decode())
2512
+ except Exception:
2513
+ return None
2514
+
2515
+ # --- per-integration LIVE version probes (each isolated/best-effort) ---
2516
+ def _npm_live() -> Optional[str]:
2517
+ # registry.npmjs.org dist-tag latest (matches `npm view ... version`)
2518
+ data = _http_json("https://registry.npmjs.org/clsplusplus")
2519
+ if isinstance(data, dict):
2520
+ latest = (data.get("dist-tags") or {}).get("latest")
2521
+ if latest:
2522
+ return latest
2523
+ return None
2524
+
2525
+ def _pypi_live() -> Optional[str]:
2526
+ data = _http_json("https://pypi.org/pypi/clsplusplus/json")
2527
+ if isinstance(data, dict):
2528
+ return (data.get("info") or {}).get("version")
2529
+ return None
2530
+
2531
+ def _mcp_registry_live() -> Optional[str]:
2532
+ data = _http_json(
2533
+ "https://registry.modelcontextprotocol.io/v0/servers"
2534
+ "?search=io.github.rajamohan1950/cls-memory"
2535
+ )
2536
+ if isinstance(data, dict):
2537
+ servers = data.get("servers") or data.get("data") or []
2538
+ for s in servers if isinstance(servers, list) else []:
2539
+ name = s.get("name") if isinstance(s, dict) else None
2540
+ if name and "cls-memory" in name:
2541
+ # version may live at top level or nested under meta.
2542
+ return (
2543
+ s.get("version")
2544
+ or (s.get("version_detail") or {}).get("version")
2545
+ or (s.get("_meta") or {}).get("version")
2546
+ )
2547
+ return None
2548
+
2549
+ def _extension_live() -> Optional[str]:
2550
+ # The Chrome Web Store artifact == the committed manifest version.
2551
+ try:
2552
+ import json as _json
2553
+ manifest = FilePath(__file__).resolve().parent.parent.parent / "extension" / "manifest.json"
2554
+ if manifest.exists():
2555
+ return _json.loads(manifest.read_text()).get("version")
2556
+ except Exception:
2557
+ pass
2558
+ return None
2559
+
2560
+ def _remote_mcp_live() -> Optional[str]:
2561
+ # Live hosted MCP endpoint: serverInfo.version from initialize, if
2562
+ # exposed; the metadata doc otherwise. Best-effort only.
2563
+ doc = _http_json(
2564
+ "https://www.clsplusplus.com/.well-known/oauth-protected-resource",
2565
+ timeout=3.0,
2566
+ )
2567
+ if isinstance(doc, dict):
2568
+ v = doc.get("version") or (doc.get("serverInfo") or {}).get("version")
2569
+ if v:
2570
+ return v
2571
+ return None
2572
+
2573
+ try:
2574
+ # The version probes below each block on network I/O (urllib) or a
2575
+ # `git` subprocess. Offload every blocking probe to a worker thread
2576
+ # and run them concurrently so this async handler never stalls the
2577
+ # event loop (previously up to ~15s of serial blocking). Each probe
2578
+ # is already isolated/best-effort, so any failure degrades to None.
2579
+ (
2580
+ main_tag,
2581
+ npm_live,
2582
+ pypi_live,
2583
+ mcp_registry_live,
2584
+ extension_live,
2585
+ remote_mcp_live,
2586
+ ) = await asyncio.gather(
2587
+ asyncio.to_thread(_read_main_tag),
2588
+ asyncio.to_thread(_npm_live),
2589
+ asyncio.to_thread(_pypi_live),
2590
+ asyncio.to_thread(_mcp_registry_live),
2591
+ asyncio.to_thread(_extension_live),
2592
+ asyncio.to_thread(_remote_mcp_live),
2593
+ )
2594
+ rel = _norm(main_tag)
2595
+
2596
+ # Aggregate usage (best-effort; shared across artifacts).
2597
+ usage = None
2598
+ try:
2599
+ usage = await _metrics.get_aggregate_metrics()
2600
+ except Exception:
2601
+ usage = None
2602
+
2603
+ # Static catalogue of publishable integrations + how to read each
2604
+ # one's LIVE version and where it is published.
2605
+ catalogue = [
2606
+ {
2607
+ "name": "npm (clsplusplus JS/TS SDK)",
2608
+ "live_version": npm_live,
2609
+ "publish_url": "https://www.npmjs.com/package/clsplusplus",
2610
+ "tracks_main_tag": True,
2611
+ },
2612
+ {
2613
+ "name": "PyPI (clsplusplus Python SDK + server + MCP)",
2614
+ "live_version": pypi_live,
2615
+ "publish_url": "https://pypi.org/project/clsplusplus/",
2616
+ "tracks_main_tag": True,
2617
+ },
2618
+ {
2619
+ "name": "MCP Registry (io.github.rajamohan1950/cls-memory)",
2620
+ "live_version": mcp_registry_live,
2621
+ "publish_url": (
2622
+ "https://registry.modelcontextprotocol.io/v0/servers"
2623
+ "?search=io.github.rajamohan1950/cls-memory"
2624
+ ),
2625
+ "tracks_main_tag": True,
2626
+ },
2627
+ {
2628
+ "name": "Chrome Web Store (CLS++ Memory extension)",
2629
+ "live_version": extension_live,
2630
+ "publish_url": "https://chromewebstore.google.com/",
2631
+ # Extension is intentionally on its own 6.x track.
2632
+ "tracks_main_tag": False,
2633
+ },
2634
+ {
2635
+ "name": "Remote MCP server (clsplusplus.com/mcp)",
2636
+ "live_version": remote_mcp_live,
2637
+ "publish_url": "https://www.clsplusplus.com/mcp",
2638
+ # A running service tracks the deployed release.
2639
+ "tracks_main_tag": True,
2640
+ },
2641
+ ]
2642
+
2643
+ integrations = []
2644
+ for entry in catalogue:
2645
+ live = entry["live_version"]
2646
+ if entry["tracks_main_tag"]:
2647
+ in_sync = bool(live) and rel is not None and _norm(live) == rel
2648
+ else:
2649
+ # Decoupled track: "in sync" only means we could read it.
2650
+ in_sync = bool(live)
2651
+ integrations.append({
2652
+ "name": entry["name"],
2653
+ "live_version": live,
2654
+ "main_tag": main_tag,
2655
+ "in_sync": in_sync,
2656
+ "publish_url": entry["publish_url"],
2657
+ "usage": usage,
2658
+ })
2659
+
2660
+ return {"main_tag": main_tag, "integrations": integrations}
2661
+ except Exception as e:
2662
+ # Defensive: this endpoint must never 500. Degrade to a fully
2663
+ # "unknown" report so the dashboard always renders.
2664
+ logger.error("Admin integrations error: %s: %s", type(e).__name__, e)
2665
+ tag = "unknown"
2666
+ try:
2667
+ tag = _read_main_tag()
2668
+ except Exception:
2669
+ pass
2670
+ return {
2671
+ "main_tag": tag,
2672
+ "integrations": [],
2673
+ "error": "integration data degraded",
2674
+ }
2675
+
2433
2676
  def _require_admin(request: Request):
2434
2677
  """Helper to enforce admin access on endpoints."""
2435
2678
  if not getattr(request.state, "is_admin", False):
@@ -114,6 +114,14 @@ class Settings(BaseSettings):
114
114
  # Embeddings (used by L1/L2/L3 tiers, not by phase engine)
115
115
  embedding_model: str = "all-MiniLM-L6-v2"
116
116
  embedding_dim: int = 384
117
+ # Embedding backend. "sentence-transformers" (default) keeps the local
118
+ # all-MiniLM-L6-v2 behavior unchanged. Set CLS_EMBEDDING_PROVIDER=openai to
119
+ # use the OpenAI embeddings API instead — openai is lazy-imported and is NOT
120
+ # a hard dependency; if it's missing the provider falls back to
121
+ # sentence-transformers. openai_api_key is read from the existing setting.
122
+ embedding_provider: str = "sentence-transformers" # CLS_EMBEDDING_PROVIDER
123
+ openai_embedding_model: str = "text-embedding-3-small" # CLS_OPENAI_EMBEDDING_MODEL
124
+ openai_embedding_dim: int = 1536 # CLS_OPENAI_EMBEDDING_DIM
117
125
 
118
126
  # Plasticity coefficients (α, β, γ, λ, δ)
119
127
  alpha_salience: float = 1.0
@@ -136,6 +144,11 @@ class Settings(BaseSettings):
136
144
  similarity_threshold: float = 0.7
137
145
  conflict_threshold: float = 0.3
138
146
  quorum_threshold: float = 0.8
147
+ # Belief-revision-on-write: when ON, the normal write() path routes new
148
+ # facts through the ReconsolidationGate to detect contradictions against
149
+ # existing same-namespace items before storing. OFF by default so the
150
+ # current write behavior is unchanged.
151
+ belief_revision_on_write: bool = False # CLS_BELIEF_REVISION_ON_WRITE
139
152
 
140
153
  # Working buffer
141
154
  l0_capacity_tokens: int = 4096
@@ -0,0 +1,173 @@
1
+ """Embedding service for CLS++."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+ from clsplusplus.config import Settings
9
+ from clsplusplus.models import MemoryItem
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BaseEmbeddingProvider:
15
+ """Interface for embedding backends.
16
+
17
+ A provider turns text into dense vectors. Implementations are lazy: the
18
+ underlying model/client is loaded on first use, never at construction.
19
+ """
20
+
21
+ name: str = "base"
22
+
23
+ @property
24
+ def dim(self) -> int:
25
+ """Dimensionality of the vectors this provider returns."""
26
+ raise NotImplementedError
27
+
28
+ def embed(self, texts: list[str]) -> list[list[float]]:
29
+ """Embed a batch of texts. Returns one vector per input text.
30
+
31
+ On failure (model/client unavailable) returns empty vectors so callers
32
+ never break — same contract the service has always honored.
33
+ """
34
+ raise NotImplementedError
35
+
36
+
37
+ class SentenceTransformerProvider(BaseEmbeddingProvider):
38
+ """Local sentence-transformers backend (default, unchanged behavior).
39
+
40
+ Wraps the original all-MiniLM-L6-v2 logic: lazy model load, returns empty
41
+ vectors when sentence-transformers is not installed.
42
+ """
43
+
44
+ name = "sentence-transformers"
45
+
46
+ def __init__(self, settings: Settings):
47
+ self.settings = settings
48
+ self._model = None
49
+
50
+ @property
51
+ def model(self):
52
+ """Lazy load the embedding model."""
53
+ if self._model is None:
54
+ try:
55
+ from sentence_transformers import SentenceTransformer
56
+ self._model = SentenceTransformer(self.settings.embedding_model)
57
+ except ImportError:
58
+ self._model = False # sentinel: unavailable
59
+ return self._model
60
+
61
+ @property
62
+ def dim(self) -> int:
63
+ return self.settings.embedding_dim
64
+
65
+ def embed(self, texts: list[str]) -> list[list[float]]:
66
+ if self.model is False:
67
+ return [[] for _ in texts]
68
+ return self.model.encode(texts, convert_to_numpy=True).tolist()
69
+
70
+
71
+ class OpenAIProvider(BaseEmbeddingProvider):
72
+ """OpenAI embeddings backend.
73
+
74
+ Selected via CLS_EMBEDDING_PROVIDER=openai. openai is lazy-imported and is
75
+ NOT a hard dependency. If the client/key is unavailable the embed call
76
+ returns empty vectors; selection-time fallback to sentence-transformers is
77
+ handled by the service factory.
78
+ """
79
+
80
+ name = "openai"
81
+
82
+ def __init__(self, settings: Settings):
83
+ self.settings = settings
84
+ self._client = None
85
+
86
+ @property
87
+ def client(self):
88
+ """Lazy build the OpenAI client."""
89
+ if self._client is None:
90
+ try:
91
+ from openai import OpenAI
92
+ self._client = OpenAI(api_key=self.settings.openai_api_key)
93
+ except ImportError:
94
+ self._client = False # sentinel: unavailable
95
+ return self._client
96
+
97
+ @property
98
+ def dim(self) -> int:
99
+ return self.settings.openai_embedding_dim
100
+
101
+ def embed(self, texts: list[str]) -> list[list[float]]:
102
+ if self.client is False:
103
+ return [[] for _ in texts]
104
+ try:
105
+ resp = self.client.embeddings.create(
106
+ model=self.settings.openai_embedding_model,
107
+ input=texts,
108
+ )
109
+ return [d.embedding for d in resp.data]
110
+ except Exception as e: # network / auth / quota — never break the caller
111
+ logger.warning("OpenAI embedding failed (returning empty vectors): %s", e)
112
+ return [[] for _ in texts]
113
+
114
+
115
+ def _build_provider(settings: Settings) -> BaseEmbeddingProvider:
116
+ """Select the embedding provider from settings.
117
+
118
+ Default is sentence-transformers. CLS_EMBEDDING_PROVIDER=openai selects
119
+ OpenAI, but if the openai package is not installed we warn and fall back to
120
+ sentence-transformers so behavior degrades gracefully.
121
+ """
122
+ provider = (settings.embedding_provider or "").strip().lower()
123
+ if provider == "openai":
124
+ try:
125
+ import openai # noqa: F401
126
+ except ImportError:
127
+ logger.warning(
128
+ "CLS_EMBEDDING_PROVIDER=openai but the openai package is not "
129
+ "installed; falling back to sentence-transformers."
130
+ )
131
+ return SentenceTransformerProvider(settings)
132
+ return OpenAIProvider(settings)
133
+ return SentenceTransformerProvider(settings)
134
+
135
+
136
+ class EmbeddingService:
137
+ """Produces embeddings for memory items."""
138
+
139
+ def __init__(self, settings: Optional[Settings] = None):
140
+ self.settings = settings or Settings()
141
+ self.provider = _build_provider(self.settings)
142
+
143
+ @property
144
+ def model(self):
145
+ """Backward-compat accessor for the underlying sentence-transformers model.
146
+
147
+ Returns None for non-sentence-transformers providers.
148
+ """
149
+ return getattr(self.provider, "model", None)
150
+
151
+ def embed(self, text: str) -> list[float]:
152
+ """Embed a single text. Returns empty list if provider unavailable."""
153
+ out = self.provider.embed([text])
154
+ return out[0] if out else []
155
+
156
+ def embed_batch(self, texts: list[str]) -> list[list[float]]:
157
+ """Embed multiple texts. Returns empty lists if provider unavailable."""
158
+ if not texts:
159
+ return []
160
+ return self.provider.embed(texts)
161
+
162
+ def embed_item(self, item: MemoryItem) -> MemoryItem:
163
+ """Add embedding to a memory item."""
164
+ if not item.embedding:
165
+ item.embedding = self.embed(item.text)
166
+ return item
167
+
168
+ @staticmethod
169
+ def cosine_similarity(a: list[float], b: list[float]) -> float:
170
+ """Compute cosine similarity between two vectors."""
171
+ va = np.array(a)
172
+ vb = np.array(b)
173
+ return float(np.dot(va, vb) / (np.linalg.norm(va) * np.linalg.norm(vb) + 1e-9))
@@ -0,0 +1,46 @@
1
+ """Third-party framework integrations for CLS++.
2
+
3
+ Each submodule adapts the CLS++ memory layer to a specific agent framework so
4
+ that framework users can plug CLS++ in as their persistent memory backend
5
+ without touching the HTTP client directly.
6
+
7
+ Currently provided:
8
+
9
+ clsplusplus.integrations.crewai.CLSMemoryStorage
10
+ Implements CrewAI's ``crewai.memory.storage.interface.Storage``
11
+ contract (``save`` / ``search`` / ``reset``), delegating to the CLS++
12
+ ``Brain`` client. Wire it into a Crew via ``external_memory=``.
13
+
14
+ clsplusplus.integrations.langgraph.CLSMemoryStore
15
+ Subclasses LangGraph's ``langgraph.store.base.BaseStore`` (the
16
+ long-term-memory ``store=`` you pass to ``graph.compile``), delegating
17
+ to the CLS++ ``Brain`` client. Requires ``pip install
18
+ clsplusplus[langgraph]``.
19
+
20
+ Integrations keep their framework imports lazy: importing ``clsplusplus`` (or
21
+ ``clsplusplus.integrations``) never requires the framework to be installed.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ __all__ = ["CLSMemoryStorage", "CLSMemoryStore", "CLSMemoryBlock", "CLSMemory"]
27
+
28
+
29
+ def __getattr__(name: str):
30
+ if name == "CLSMemoryStorage":
31
+ from clsplusplus.integrations.crewai import CLSMemoryStorage
32
+
33
+ return CLSMemoryStorage
34
+ if name == "CLSMemoryStore":
35
+ from clsplusplus.integrations.langgraph import CLSMemoryStore
36
+
37
+ return CLSMemoryStore
38
+ if name == "CLSMemoryBlock":
39
+ from clsplusplus.integrations.llamaindex import CLSMemoryBlock
40
+
41
+ return CLSMemoryBlock
42
+ if name == "CLSMemory":
43
+ from clsplusplus.integrations.autogen import CLSMemory
44
+
45
+ return CLSMemory
46
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")