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,1078 @@
1
+ """
2
+ RunOrchestrator — Real-time parallel experiment execution engine.
3
+
4
+ Replaces the monolithic _do_execute_run with a clean orchestrator that:
5
+ - Runs agents in parallel by default (ThreadPoolExecutor)
6
+ - Emits structured SSE events for live UI updates
7
+ - Shares findings across agents via SharedFindingsBuffer
8
+ - Scores incrementally as agents complete
9
+ - Supports 3 modes: parallel, leader-follower, sequential
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import re
17
+ import time
18
+ import threading
19
+ from concurrent.futures import ThreadPoolExecutor, as_completed
20
+ from datetime import datetime, timezone
21
+ from typing import Any, Callable, Dict, List, Optional, Set
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ # ─── Structured SSE Event Types ────────────────────────────────────────────
27
+
28
+ class SSEEvent:
29
+ """Typed SSE event for real-time streaming."""
30
+
31
+ RUN_START = "run_start"
32
+ AGENT_START = "agent_start"
33
+ AGENT_PLAN = "agent_plan"
34
+ STEP_DONE = "step_done"
35
+ FINDING = "finding"
36
+ SCORE_UPDATE = "score_update"
37
+ AGENT_DONE = "agent_done"
38
+ RUN_COMPLETE = "run_complete"
39
+ ERROR = "error"
40
+
41
+ @staticmethod
42
+ def make(event_type: str, data: dict) -> dict:
43
+ data["ts"] = datetime.now(timezone.utc).isoformat()
44
+ return {"event": event_type, "data": data}
45
+
46
+
47
+ # ─── SharedFindingsBuffer ──────────────────────────────────────────────────
48
+
49
+ class SharedFindingsBuffer:
50
+ """Thread-safe buffer for cross-agent finding visibility during a run."""
51
+
52
+ def __init__(self):
53
+ self._lock = threading.Lock()
54
+ self._findings: List[Dict] = []
55
+
56
+ def add(self, agent_name: str, content: str, node_type: str = "Finding"):
57
+ with self._lock:
58
+ self._findings.append({
59
+ "agent": agent_name,
60
+ "content": content,
61
+ "node_type": node_type,
62
+ "ts": datetime.now(timezone.utc).isoformat(),
63
+ })
64
+
65
+ def get_others(self, exclude_agent: str) -> List[Dict]:
66
+ """Get findings from other agents (for cross-agent context)."""
67
+ with self._lock:
68
+ return [f for f in self._findings if f["agent"] != exclude_agent]
69
+
70
+ def all(self) -> List[Dict]:
71
+ with self._lock:
72
+ return list(self._findings)
73
+
74
+ def count(self) -> int:
75
+ with self._lock:
76
+ return len(self._findings)
77
+
78
+
79
+ # ─── Incremental Scorer ───────────────────────────────────────────────────
80
+
81
+ class IncrementalScorer:
82
+ """Computes partial scores as agents complete, without waiting for all."""
83
+
84
+ def __init__(self, total_agents: int, goal: str):
85
+ self.total_agents = total_agents
86
+ self.goal = goal
87
+ self._agent_results: List[Dict] = []
88
+ self._lock = threading.Lock()
89
+
90
+ def add_result(self, agent_result: Dict) -> Dict[str, int]:
91
+ """Add a completed agent result and return current partial scores."""
92
+ with self._lock:
93
+ self._agent_results.append(agent_result)
94
+ return self._compute()
95
+
96
+ def _compute(self) -> Dict[str, int]:
97
+ all_steps = []
98
+ for a in self._agent_results:
99
+ all_steps.extend(a.get("steps", []))
100
+
101
+ scores = {}
102
+
103
+ # Coverage
104
+ content_types = {"Person", "Organization", "Location", "Event", "Fact", "Document", "Passage"}
105
+ queried_types = set()
106
+ for s in all_steps:
107
+ p = s.get("params", {})
108
+ if p.get("label"):
109
+ queried_types.add(p["label"])
110
+ r_str = str(s.get("result", ""))
111
+ for ct in content_types:
112
+ if f"[{ct}]" in r_str:
113
+ queried_types.add(ct)
114
+ queried_content = queried_types & content_types
115
+ scores["coverage"] = min(100, int((len(queried_content) / len(content_types)) * 100))
116
+
117
+ # Discovery
118
+ findings = sum(1 for s in all_steps if s.get("tool") in ("search_nodes", "rag_query", "search", "query_graph") and s.get("success"))
119
+ scores["discovery"] = min(100, findings * 15)
120
+
121
+ # Enrichment
122
+ writes = sum(1 for s in all_steps if s.get("tool") in ("add_knowledge", "add_task", "add_decision", "log_action") and s.get("success"))
123
+ scores["enrichment"] = min(100, writes * 20)
124
+
125
+ # Collaboration — measures how the context layer enabled agents to build on each other's work
126
+ n_agents = len(self._agent_results)
127
+
128
+ # 1. Cross-agent findings consumed: agent B's search returned agent A's written data
129
+ cross_reads = 0
130
+ for a in self._agent_results:
131
+ other_names = {r.get("name", "") for r in self._agent_results if r.get("name") != a.get("name")}
132
+ for s in a.get("steps", []):
133
+ if s.get("tool") in ("search_nodes", "search") and s.get("success"):
134
+ result_text = str(s.get("result", ""))
135
+ if "Finding" in result_text or "Insight" in result_text:
136
+ cross_reads += 1
137
+ for other in other_names:
138
+ if other and other in result_text:
139
+ cross_reads += 1
140
+
141
+ # 2. Shared entity coverage: agents searched overlapping entities (complementary research)
142
+ agent_search_terms = {}
143
+ for a in self._agent_results:
144
+ name = a.get("name", "")
145
+ terms = set()
146
+ for s in a.get("steps", []):
147
+ if s.get("tool") in ("search_nodes", "search") and s.get("success"):
148
+ q = (s.get("params", {}).get("query", "") or "").lower()
149
+ lbl = (s.get("params", {}).get("label", "") or "").lower()
150
+ for t in q.split():
151
+ if len(t) > 2:
152
+ terms.add(t)
153
+ if lbl:
154
+ terms.add(lbl)
155
+ agent_search_terms[name] = terms
156
+ entity_overlap = 0
157
+ names_list = list(agent_search_terms.keys())
158
+ for i in range(len(names_list)):
159
+ for j in range(i + 1, len(names_list)):
160
+ overlap = agent_search_terms[names_list[i]] & agent_search_terms[names_list[j]]
161
+ entity_overlap += len(overlap)
162
+
163
+ # 3. Context amplification: agents got richer context via SharedFindingsBuffer
164
+ # Count how many agents had cross-agent findings injected into their prompt
165
+ agents_with_shared_context = 0
166
+ for a in self._agent_results:
167
+ ctx = a.get("context", "")
168
+ if "OTHER AGENTS" in ctx or "FINDINGS" in ctx:
169
+ agents_with_shared_context += 1
170
+
171
+ # 4. Graph density: agents collectively wrote nodes that reference overlapping entities
172
+ agents_writing = set()
173
+ agents_searching = set()
174
+ for a in self._agent_results:
175
+ name = a.get("name", "")
176
+ for s in a.get("steps", []):
177
+ if s.get("tool") == "add_knowledge" and s.get("success"):
178
+ agents_writing.add(name)
179
+ if s.get("tool") in ("search_nodes", "search") and s.get("success"):
180
+ agents_searching.add(name)
181
+ # Agents who both read and wrote = active graph participants
182
+ active_participants = len(agents_writing & agents_searching)
183
+
184
+ # 5. CU coverage: how many distinct CU topics were covered across all agents
185
+ cu_topics_covered = set()
186
+ for a in self._agent_results:
187
+ ctx = a.get("context", "")
188
+ for line in ctx.split("\n"):
189
+ if line.startswith("[ContextUnit:"):
190
+ cu_topics_covered.add(line)
191
+
192
+ # Score
193
+ collab_score = 0
194
+ collab_score += min(cross_reads * 12, 30) # Cross-agent reads (up to 30)
195
+ collab_score += min(entity_overlap * 10, 25) # Shared entity research (up to 25)
196
+ collab_score += min(agents_with_shared_context * 10, 15) # Context amplification (up to 15)
197
+ collab_score += min(active_participants * 10, 20) # Active graph participants (up to 20)
198
+ collab_score += min(len(cu_topics_covered) * 5, 10) # CU breadth (up to 10)
199
+ scores["collaboration"] = min(100, collab_score)
200
+
201
+ # Efficiency
202
+ total_steps = len(all_steps)
203
+ successful = sum(1 for s in all_steps if s.get("success"))
204
+ scores["efficiency"] = int((successful / max(total_steps, 1)) * 100)
205
+
206
+ # Completion (partial — based on completed vs total)
207
+ scores["completion"] = int((len(self._agent_results) / max(self.total_agents, 1)) * 100)
208
+
209
+ # Overall (without insight_quality — that comes at the end)
210
+ weights_partial = {
211
+ "coverage": 0.12, "discovery": 0.20, "enrichment": 0.20,
212
+ "collaboration": 0.18, "efficiency": 0.15, "completion": 0.15,
213
+ }
214
+ scores["overall"] = int(sum(scores.get(k, 0) * w for k, w in weights_partial.items()))
215
+
216
+ return scores
217
+
218
+
219
+ # ─── RunOrchestrator ──────────────────────────────────────────────────────
220
+
221
+ class RunOrchestrator:
222
+ """
223
+ Orchestrates parallel agent execution with real-time SSE streaming.
224
+
225
+ Usage:
226
+ orchestrator = RunOrchestrator(
227
+ run=run_dict, exp=exp_dict, agents=agents_list,
228
+ graph=graph_name, registry=reg, ...
229
+ )
230
+ orchestrator.execute()
231
+ """
232
+
233
+ def __init__(
234
+ self,
235
+ run: Dict,
236
+ exp: Dict,
237
+ agents: List[Dict],
238
+ graph: str,
239
+ registry,
240
+ run_id: str,
241
+ experiment_id: str,
242
+ track_id: str,
243
+ session_id: str,
244
+ llm,
245
+ llm_provider_name: str,
246
+ goal: str,
247
+ mode: str,
248
+ inputs: Optional[Dict],
249
+ # Injection points for existing infrastructure
250
+ PlaygroundConn,
251
+ create_tool_context,
252
+ ToolRegistry,
253
+ graph_node_fn: Callable,
254
+ save_run_fn: Callable,
255
+ check_signal_fn: Callable,
256
+ emit_sse_fn: Optional[Callable] = None,
257
+ get_llm_client_fn: Optional[Callable] = None,
258
+ experiment_tools: Optional[Set[str]] = None,
259
+ ):
260
+ self.run = run
261
+ self.exp = exp
262
+ self.agents = agents
263
+ self.graph = graph
264
+ self.registry = registry
265
+ self.run_id = run_id
266
+ self.experiment_id = experiment_id
267
+ self.track_id = track_id
268
+ self.session_id = session_id
269
+ self.llm = llm
270
+ self.llm_provider_name = llm_provider_name
271
+ self.goal = goal
272
+ self.mode = mode
273
+ self.inputs = inputs
274
+
275
+ self.PlaygroundConn = PlaygroundConn
276
+ self.create_tool_context = create_tool_context
277
+ self.ToolRegistry = ToolRegistry
278
+ self._graph_node = graph_node_fn
279
+ self._save_run = save_run_fn
280
+ self._check_signal = check_signal_fn
281
+ self._emit_sse = emit_sse_fn or (lambda event_type, data: None)
282
+ self._get_llm_client = get_llm_client_fn
283
+ self.EXPERIMENT_TOOLS = experiment_tools or set()
284
+
285
+ self.total_start = time.time()
286
+ self.findings = SharedFindingsBuffer()
287
+ self.scorer = IncrementalScorer(len(agents), goal)
288
+ self.completed_agents: Set[str] = set()
289
+
290
+ # Pre-init shared resources (avoids 40s+ lazy init per agent)
291
+ self._shared_conn = None
292
+ self._shared_project = None
293
+ self._manifest_cache = None
294
+ self._cu_context = "" # Full CU briefing (for leader/synthesizer)
295
+ self._cu_list = [] # Raw CU dicts (for partitioning)
296
+ self._agent_cus = {} # agent_name → [assigned CU dicts]
297
+ self._pre_init()
298
+
299
+ # ── Pre-initialization (expensive ops done ONCE) ─────────────────
300
+
301
+ def _pre_init(self):
302
+ """Pre-initialize expensive shared resources before agents run."""
303
+ try:
304
+ self._shared_conn = self.PlaygroundConn(self.graph, self.registry)
305
+ # Pre-init ProjectGraph once (avoids 40s+ per-agent lazy init)
306
+ from ..project.graph import ProjectGraph as _PG
307
+ self._shared_project = _PG(self.graph, connection=self._shared_conn)
308
+ logger.info("[ORCHESTRATOR] ProjectGraph pre-initialized for %s", self.graph)
309
+ except Exception as e:
310
+ logger.warning("[ORCHESTRATOR] ProjectGraph pre-init failed: %s", e)
311
+
312
+ # Pre-cache manifest (avoid Redis lookup per agent)
313
+ self._manifest_cache = self._get_manifest()
314
+
315
+ # Pre-load Context Units matched to the experiment goal
316
+ self._cu_context = self._load_context_units()
317
+
318
+ def _load_context_units(self) -> str:
319
+ """Load CUs, match to goal, partition across agents, return full briefing."""
320
+ try:
321
+ conn = self._shared_conn
322
+ if not conn or not conn.db:
323
+ return ""
324
+
325
+ db = conn.db
326
+ cu_nodes = db.get_all_nodes(label="ContextUnit")
327
+ if not cu_nodes:
328
+ return ""
329
+
330
+ from ..context.context_units import match_intent, format_cu_for_agent
331
+ cus = []
332
+ for node in cu_nodes:
333
+ props = node.properties if hasattr(node, 'properties') else {}
334
+ cus.append({
335
+ "id": node.id if hasattr(node, 'id') else "",
336
+ "label": "ContextUnit",
337
+ "properties": props,
338
+ })
339
+
340
+ if not cus:
341
+ return ""
342
+
343
+ # Match CUs to the experiment goal
344
+ matched = match_intent(self.goal, cus, limit=len(self.agents) * 3 or 5)
345
+ if not matched:
346
+ matched = sorted(cus, key=lambda c: len(c.get("evidence_ids", [])), reverse=True)[:len(self.agents) * 3 or 5]
347
+
348
+ self._cu_list = matched
349
+
350
+ # Partition CUs across agents — round-robin assignment
351
+ n_agents = len(self.agents)
352
+ if n_agents > 1 and len(matched) > 1:
353
+ for i, cu in enumerate(matched):
354
+ agent_name = self.agents[i % n_agents].get("name", f"Agent-{i % n_agents}")
355
+ if agent_name not in self._agent_cus:
356
+ self._agent_cus[agent_name] = []
357
+ self._agent_cus[agent_name].append(cu)
358
+
359
+ # Log partition
360
+ for name, assigned in self._agent_cus.items():
361
+ topics = [c.get("properties", {}).get("topic", "?") for c in assigned]
362
+ logger.info("[ORCHESTRATOR] %s assigned CUs: %s", name, topics)
363
+ else:
364
+ # Single agent gets everything
365
+ if self.agents:
366
+ self._agent_cus[self.agents[0].get("name", "Agent")] = matched
367
+
368
+ # Full briefing (for synthesizer or fallback)
369
+ lines = [f"INTELLIGENCE BRIEFING ({len(matched)} Context Units):"]
370
+ for cu in matched:
371
+ lines.append(format_cu_for_agent(cu))
372
+ lines.append("")
373
+
374
+ text = "\n".join(lines)
375
+ logger.info("[ORCHESTRATOR] Loaded %d CUs, partitioned across %d agents", len(matched), n_agents)
376
+ return text
377
+ except Exception as e:
378
+ logger.debug("[ORCHESTRATOR] CU loading failed: %s", e)
379
+ return ""
380
+
381
+ def _get_agent_cu_context(self, agent_name: str) -> str:
382
+ """Get the CU briefing for a specific agent (partitioned topics)."""
383
+ from ..context.context_units import format_cu_for_agent
384
+ assigned = self._agent_cus.get(agent_name, [])
385
+ if not assigned:
386
+ return self._cu_context or ""
387
+
388
+ # Show this agent's assigned topics + summary of what others are covering
389
+ lines = [f"YOUR ASSIGNED TOPICS ({len(assigned)} Context Units):"]
390
+ for cu in assigned:
391
+ lines.append(format_cu_for_agent(cu))
392
+ lines.append("")
393
+
394
+ # Tell agent what others are covering (so it doesn't duplicate)
395
+ other_topics = []
396
+ for other_name, other_cus in self._agent_cus.items():
397
+ if other_name != agent_name:
398
+ topics = [c.get("properties", {}).get("topic", "?") for c in other_cus]
399
+ other_topics.append(f"{other_name} is covering: {', '.join(topics)}")
400
+ if other_topics:
401
+ lines.append("OTHER AGENTS' ASSIGNED TOPICS (avoid duplicating their work):")
402
+ for ot in other_topics:
403
+ lines.append(f" - {ot}")
404
+
405
+ return "\n".join(lines)
406
+
407
+ # ── Progress ────────────────────────────────────────────────────────
408
+
409
+ def _progress(self, msg: str):
410
+ self.run["_progress"] = msg
411
+ self.run["_progress_at"] = datetime.now(timezone.utc).isoformat()
412
+ if "_progress_log" not in self.run:
413
+ self.run["_progress_log"] = []
414
+ self.run["_progress_log"].append({
415
+ "msg": msg,
416
+ "time": datetime.now(timezone.utc).isoformat(),
417
+ "elapsed_ms": int((time.time() - self.total_start) * 1000),
418
+ })
419
+ logger.info("[EXPERIMENT] %s — %s", self.track_id, msg)
420
+
421
+ def _emit(self, event_type: str, data: dict):
422
+ """Emit structured SSE event."""
423
+ self._emit_sse(event_type, data)
424
+
425
+ # ── Agent Execution (pre-planned, single LLM call) ─────────────────
426
+
427
+ def _plan_and_execute_agent(
428
+ self, agent_def: Dict, prompt: str, role: str, phase: str, max_tools: int = 6
429
+ ) -> Dict:
430
+ """Single LLM call → tool plan → execute all steps. Returns agent_result."""
431
+ agent_name = agent_def.get("name", "Agent")
432
+ agent_id = agent_def.get("agent_id", f"sim_{agent_name}")
433
+ agent_start = time.time()
434
+
435
+ agent_result = {
436
+ "name": agent_name, "agent_id": agent_id,
437
+ "role": role, "phase": phase, "status": "running",
438
+ "llm_model": agent_def.get("llm_model", ""),
439
+ "framework": agent_def.get("framework", ""),
440
+ "type": "experiment", "steps": [],
441
+ }
442
+
443
+ # Emit agent_start
444
+ self._emit(SSEEvent.AGENT_START, {
445
+ "agent_id": agent_id, "agent_name": agent_name,
446
+ "role": role, "phase": phase,
447
+ })
448
+
449
+ # Build tool context (reuse shared conn for reads, each agent gets own ctx)
450
+ conn = self._shared_conn or self.PlaygroundConn(self.graph, self.registry)
451
+ ctx = self.create_tool_context(conn, agent_id, agent_name)
452
+ ctx._has_oriented = True
453
+ ctx._hints_enabled = False
454
+ ctx.metadata = getattr(ctx, "metadata", {})
455
+ ctx.metadata.update({
456
+ "experiment_id": self.experiment_id,
457
+ "track_id": self.track_id, "run_id": self.run_id, "type": "experiment",
458
+ })
459
+
460
+ # Use pre-initialized project (instant, no lazy init)
461
+ ctx.project = self._shared_project
462
+
463
+ # Use cached manifest
464
+ manifest_text = self._manifest_cache or ""
465
+
466
+ # Inject cross-agent findings into context
467
+ other_findings = self.findings.get_others(agent_name)
468
+ findings_block = ""
469
+ if other_findings:
470
+ findings_block = "\n\nOTHER AGENTS' FINDINGS (use as additional context):\n"
471
+ for f in other_findings[-5:]: # last 5 findings
472
+ findings_block += f"- [{f['agent']}] {f['content'][:150]}\n"
473
+
474
+ # Build context: partitioned CUs + manifest + cross-agent findings
475
+ cu_block = self._get_agent_cu_context(agent_name)
476
+ context_block = cu_block + "\n\n" + (manifest_text or "Graph data available.") + findings_block
477
+ context_block = context_block.strip()
478
+
479
+ # Build the plan prompt
480
+ plan_prompt = f"""You are "{agent_name}" ({role}).
481
+
482
+ CONTEXT:
483
+ {context_block}
484
+
485
+ TASK: {prompt}
486
+
487
+ Return a JSON array of tool calls to execute. Each item: {{"tool": "tool_name", "params": {{...}}}}
488
+
489
+ Available tools (fastest first):
490
+ - search_nodes(label="Fact", query="topic") — FAST: find facts about a topic (<300ms)
491
+ - search_nodes(label="Person") — FAST: find people, orgs, locations, events (<300ms)
492
+ - search(query="keywords") — broader text search across all nodes
493
+ - add_knowledge(content="...", node_type="Finding") — write analysis
494
+ - add_task(title="...", description="...", assigned_to="name") — assign work
495
+
496
+ RULES:
497
+ - ALWAYS end with add_knowledge to write your analysis as a Finding or Insight
498
+ - Search first, then write. Every plan MUST include at least one add_knowledge call
499
+ - Use search_nodes with label + query for fast, relevant results
500
+
501
+ Return ONLY a JSON array. Max {max_tools} tool calls."""
502
+
503
+ self._progress(f"Agent '{agent_name}' ({role}) planning...")
504
+
505
+ # Get LLM
506
+ agent_llm = self.llm
507
+ agent_llm_model = agent_def.get("llm_model", "")
508
+ if agent_llm_model and agent_llm_model not in ("auto", "default", "") and self._get_llm_client:
509
+ try:
510
+ _prov, _, _model = agent_llm_model.partition(":")
511
+ agent_llm = self._get_llm_client(provider=_prov, model=_model) if _model else self._get_llm_client(provider=_prov)
512
+ except Exception:
513
+ pass
514
+
515
+ # Single LLM call
516
+ try:
517
+ import concurrent.futures as _cf
518
+ with _cf.ThreadPoolExecutor(max_workers=1) as _tp:
519
+ future = _tp.submit(agent_llm.generate, prompt=plan_prompt, max_tokens=800)
520
+ text = future.result(timeout=30)
521
+ llm_ms = int((time.time() - agent_start) * 1000)
522
+ logger.info("[EXPERIMENT] Agent '%s' LLM returned %d chars in %dms", agent_name, len(text or ""), llm_ms)
523
+ except Exception as e:
524
+ agent_result["status"] = "failed"
525
+ agent_result["error"] = f"LLM call failed: {e}"
526
+ agent_result["duration_ms"] = int((time.time() - agent_start) * 1000)
527
+ self._emit(SSEEvent.ERROR, {"agent_id": agent_id, "error": str(e)})
528
+ return agent_result
529
+
530
+ # Parse tool plan
531
+ tool_plan = []
532
+ try:
533
+ json_match = re.search(r'\[.*\]', text, re.DOTALL)
534
+ if json_match:
535
+ tool_plan = json.loads(json_match.group())
536
+ except Exception as parse_err:
537
+ logger.warning("[EXPERIMENT] Failed to parse tool plan: %s | LLM: %s", parse_err, (text or "")[:300])
538
+
539
+ # Emit plan event
540
+ self._emit(SSEEvent.AGENT_PLAN, {
541
+ "agent_id": agent_id, "agent_name": agent_name,
542
+ "tool_plan": [{"tool": c.get("tool", ""), "params": c.get("params", {})} for c in tool_plan[:max_tools]],
543
+ "llm_ms": llm_ms,
544
+ })
545
+
546
+ agent_result["context"] = context_block[:2000]
547
+ agent_result["llm_ms"] = llm_ms
548
+
549
+ # Execute tool plan
550
+ for i, call in enumerate(tool_plan[:max_tools]):
551
+ # Check for stop signal
552
+ if self._check_signal(self.run_id) == "stop":
553
+ agent_result["status"] = "aborted"
554
+ break
555
+
556
+ tool_name = call.get("tool", "")
557
+ params = call.get("params", {})
558
+ step_start = time.time()
559
+
560
+ if tool_name not in self.EXPERIMENT_TOOLS:
561
+ agent_result["steps"].append({
562
+ "step": i + 1, "tool": tool_name, "params": params,
563
+ "success": False, "error": f"Tool '{tool_name}' not available",
564
+ "duration_ms": 0,
565
+ })
566
+ continue
567
+
568
+ try:
569
+ result_str = self.ToolRegistry.dispatch(tool_name, ctx, params)
570
+ step_ms = int((time.time() - step_start) * 1000)
571
+
572
+ agent_result["steps"].append({
573
+ "step": i + 1, "tool": tool_name, "params": params,
574
+ "result": str(result_str)[:500], "success": True,
575
+ "duration_ms": step_ms,
576
+ "ts": datetime.now(timezone.utc).isoformat(),
577
+ })
578
+
579
+ self._progress(f"Agent '{agent_name}' step {i+1} — {tool_name} OK ({step_ms}ms)")
580
+
581
+ # Emit step_done
582
+ self._emit(SSEEvent.STEP_DONE, {
583
+ "agent_id": agent_id, "agent_name": agent_name,
584
+ "step": i + 1, "action": tool_name,
585
+ "params": {k: str(v)[:50] for k, v in params.items()},
586
+ "duration_ms": step_ms,
587
+ "result": str(result_str)[:200],
588
+ "success": True,
589
+ })
590
+
591
+ # Track findings for cross-agent sharing
592
+ if tool_name == "add_knowledge" and params.get("content"):
593
+ content = params["content"]
594
+ node_type = params.get("node_type", "Finding")
595
+ self.findings.add(agent_name, content, node_type)
596
+ self._emit(SSEEvent.FINDING, {
597
+ "agent_id": agent_id, "agent_name": agent_name,
598
+ "content_preview": content[:200],
599
+ "node_type": node_type,
600
+ "total_findings": self.findings.count(),
601
+ })
602
+
603
+ except Exception as e:
604
+ step_ms = int((time.time() - step_start) * 1000)
605
+ agent_result["steps"].append({
606
+ "step": i + 1, "tool": tool_name, "params": params,
607
+ "success": False, "error": str(e), "duration_ms": step_ms,
608
+ "ts": datetime.now(timezone.utc).isoformat(),
609
+ })
610
+
611
+ # Auto-write if agent searched but didn't write
612
+ self._auto_write_finding(agent_result, agent_name, ctx, phase)
613
+
614
+ # Finalize agent result
615
+ if agent_result["status"] == "running":
616
+ agent_result["status"] = "completed"
617
+ agent_result["duration_ms"] = int((time.time() - agent_start) * 1000)
618
+ agent_result["steps_count"] = len(agent_result["steps"])
619
+
620
+ # Emit agent_done
621
+ self._emit(SSEEvent.AGENT_DONE, {
622
+ "agent_id": agent_id, "agent_name": agent_name,
623
+ "steps": len(agent_result["steps"]),
624
+ "status": agent_result["status"],
625
+ "duration_ms": agent_result["duration_ms"],
626
+ "findings_count": len([s for s in agent_result["steps"] if s.get("tool") == "add_knowledge" and s.get("success")]),
627
+ })
628
+
629
+ return agent_result
630
+
631
+ # ── Auto-write finding helper ──────────────────────────────────────
632
+
633
+ def _auto_write_finding(self, agent_result: Dict, agent_name: str, ctx, phase: str):
634
+ """If agent searched but didn't write, auto-create a Finding from results."""
635
+ has_writes = any(s.get("tool") == "add_knowledge" and s.get("success") for s in agent_result["steps"])
636
+ has_searches = any(s.get("tool") in ("search_nodes", "search") and s.get("success") for s in agent_result["steps"])
637
+
638
+ if has_searches and not has_writes:
639
+ try:
640
+ facts = []
641
+ for s in agent_result["steps"]:
642
+ if s.get("tool") in ("search_nodes", "search") and s.get("success"):
643
+ for line in str(s.get("result", "")).split("\n"):
644
+ line = line.strip()
645
+ if any(line.startswith(f"[{t}]") for t in ("Fact", "Person", "Event", "Organization")):
646
+ fact_text = line.split("]", 1)[-1].strip()[:150]
647
+ if fact_text and len(fact_text) > 15:
648
+ facts.append(fact_text)
649
+ if facts:
650
+ node_type = "Insight" if phase == "synthesize" else "Finding"
651
+ label = "Synthesis" if phase == "synthesize" else "Analysis"
652
+ auto_content = f"{label} by {agent_name}: " + "; ".join(facts[:5])
653
+ auto_result = self.ToolRegistry.dispatch("add_knowledge", ctx, {
654
+ "content": auto_content[:800], "node_type": node_type
655
+ })
656
+ agent_result["steps"].append({
657
+ "step": len(agent_result["steps"]) + 1, "tool": "add_knowledge",
658
+ "params": {"content": auto_content[:800], "node_type": node_type},
659
+ "result": str(auto_result)[:200], "success": True,
660
+ "duration_ms": 0,
661
+ })
662
+ self.findings.add(agent_name, auto_content[:800], node_type)
663
+ logger.info("[EXPERIMENT] Auto-wrote %s for %s (%d facts)", node_type, agent_name, len(facts))
664
+ except Exception:
665
+ pass
666
+
667
+ # ── Manifest helper ────────────────────────────────────────────────
668
+
669
+ def _get_manifest(self) -> str:
670
+ """Load manifest text for agent context."""
671
+ try:
672
+ from ..context.quality import get_manifest, ManifestBuilder
673
+ import redis as _redis_mod
674
+ import os
675
+ _r = _redis_mod.from_url(os.environ.get("CONTEXTSYNAPSE_REDIS_URL") or os.environ.get("AICONTEXTDB_REDIS_URL", "redis://localhost:6379"))
676
+ source = self.run.get("source_graph", "")
677
+ m = None
678
+ if source:
679
+ m = get_manifest(source, _r)
680
+ if not m:
681
+ m = get_manifest(self.graph, _r)
682
+ if m:
683
+ return ManifestBuilder.to_text(m)
684
+ except Exception as e:
685
+ logger.debug("[EXPERIMENT] Manifest lookup: %s", e)
686
+ return ""
687
+
688
+ # ── Execution Modes ────────────────────────────────────────────────
689
+
690
+ def execute(self) -> Dict:
691
+ """Main entry point — dispatches to the appropriate execution mode."""
692
+ execution_mode = self.exp.get("execution_mode", "parallel")
693
+
694
+ # Emit run_start
695
+ self._emit(SSEEvent.RUN_START, {
696
+ "run_id": self.run_id,
697
+ "mode": execution_mode,
698
+ "agents": [{"name": a.get("name"), "role": a.get("role", "agent")} for a in self.agents],
699
+ "goal": self.goal[:300],
700
+ "total_agents": len(self.agents),
701
+ })
702
+
703
+ if execution_mode == "leader-follower" and len(self.agents) >= 2:
704
+ self._execute_leader_follower()
705
+ elif execution_mode == "sequential":
706
+ self._execute_sequential()
707
+ else:
708
+ # Default: parallel (replaces old "queue" mode)
709
+ self._execute_parallel()
710
+
711
+ # Final scoring (with insight quality from LLM)
712
+ self._final_score()
713
+
714
+ # Build output
715
+ self._build_output()
716
+
717
+ # Finalize run
718
+ any_aborted = any(a.get("status") == "aborted" for a in self.run["agents"])
719
+ if self._check_signal(self.run_id) == "stop" or any_aborted:
720
+ self.run["status"] = "aborted"
721
+ else:
722
+ self.run["status"] = "completed"
723
+ self.run["completed_at"] = datetime.now(timezone.utc).isoformat()
724
+ self.run["total_duration_ms"] = int((time.time() - self.total_start) * 1000)
725
+ self.run["graph"] = self.graph
726
+ self.run["session_id"] = self.session_id
727
+ self.run["llm_provider"] = self.llm_provider_name
728
+
729
+ # Persist score as graph node
730
+ if self.run.get("scores"):
731
+ try:
732
+ conn = self.PlaygroundConn(self.graph, self.registry)
733
+ self._graph_node(conn.executor, "ExperimentScore", {
734
+ "name": f"Score: {self.exp.get('name', '')}",
735
+ "overall": self.run["scores"].get("overall", 0),
736
+ "coverage": self.run["scores"].get("coverage", 0),
737
+ "discovery": self.run["scores"].get("discovery", 0),
738
+ "enrichment": self.run["scores"].get("enrichment", 0),
739
+ "collaboration": self.run["scores"].get("collaboration", 0),
740
+ "efficiency": self.run["scores"].get("efficiency", 0),
741
+ "completion": self.run["scores"].get("completion", 0),
742
+ "insight_quality": self.run["scores"].get("insight_quality", 0),
743
+ "track_id": self.track_id, "type": "experiment",
744
+ })
745
+ except Exception:
746
+ pass
747
+
748
+ self.exp["run_count"] = self.exp.get("run_count", 0) + 1
749
+ self._save_run(self.run)
750
+
751
+ # Emit run_complete
752
+ self._emit(SSEEvent.RUN_COMPLETE, {
753
+ "run_id": self.run_id,
754
+ "status": self.run["status"],
755
+ "total_ms": self.run["total_duration_ms"],
756
+ "score": self.run.get("scores", {}).get("overall", 0),
757
+ "scores": self.run.get("scores", {}),
758
+ "agents_completed": sum(1 for a in self.run["agents"] if a.get("status") == "completed"),
759
+ "findings_count": self.findings.count(),
760
+ "briefing_preview": (self.run.get("output") or "")[:300],
761
+ })
762
+
763
+ return self.run
764
+
765
+ # ── PARALLEL MODE (default) ────────────────────────────────────────
766
+
767
+ def _generate_focus_angles(self) -> Dict[str, str]:
768
+ """Generate unique focus angles for each agent based on CU topics or goal decomposition."""
769
+ n = len(self.agents)
770
+ if n <= 1:
771
+ return {}
772
+
773
+ angles = {}
774
+
775
+ # Strategy 1: Use partitioned CU topics as focus angles
776
+ if self._agent_cus:
777
+ for agent_name, cus in self._agent_cus.items():
778
+ topics = [c.get("properties", {}).get("topic", "") for c in cus if c.get("properties", {}).get("topic")]
779
+ if topics:
780
+ angles[agent_name] = "; ".join(topics)
781
+ if len(angles) == n:
782
+ logger.info("[ORCHESTRATOR] Focus angles from CU partition: %s",
783
+ {k: v[:50] for k, v in angles.items()})
784
+ return angles
785
+
786
+ # Strategy 2: Decompose goal into complementary perspectives
787
+ # Use predefined research lenses that work for any topic
788
+ perspectives = [
789
+ "Key actors, people, and organizations involved — WHO is doing what",
790
+ "Events, timeline, and recent developments — WHAT happened and WHEN",
791
+ "Causes, motivations, and strategic interests — WHY this is happening",
792
+ "Impacts, consequences, and future implications — WHAT NEXT",
793
+ "Regional and international reactions — HOW others are responding",
794
+ "Economic and financial dimensions — money, trade, sanctions",
795
+ "Military and security aspects — forces, threats, alliances",
796
+ "Diplomatic efforts and negotiations — talks, deals, agreements",
797
+ ]
798
+
799
+ for i, agent_def in enumerate(self.agents):
800
+ name = agent_def.get("name", f"Agent-{i}")
801
+ angles[name] = perspectives[i % len(perspectives)]
802
+
803
+ logger.info("[ORCHESTRATOR] Focus angles from perspectives: %s",
804
+ {k: v[:40] for k, v in angles.items()})
805
+ return angles
806
+
807
+ def _execute_parallel(self):
808
+ """All agents run concurrently with pre-planned execution."""
809
+ self._progress(f"Parallel mode: {len(self.agents)} agents starting concurrently...")
810
+
811
+ # Generate unique focus angles for each agent to avoid duplicate effort
812
+ focus_angles = self._generate_focus_angles()
813
+
814
+ def _run_agent(agent_def, focus=""):
815
+ a_name = agent_def.get("name", "Agent")
816
+ a_prompt = agent_def.get("prompt", "")
817
+ other_agents = [a.get("name") for a in self.agents if a.get("name") != a_name]
818
+ team_info = f"Other agents working in parallel: {', '.join(other_agents)}" if other_agents else ""
819
+
820
+ focus_directive = f"\nYOUR FOCUS: {focus}\nSearch for data specifically related to your assigned focus. Do NOT duplicate other agents' work." if focus else ""
821
+
822
+ prompt = f"""GOAL: {self.goal}
823
+ {f'INSTRUCTIONS: {a_prompt}' if a_prompt else ''}
824
+ {team_info}{focus_directive}
825
+
826
+ Search the graph for relevant data, then write a Finding with your analysis.
827
+ You MUST end with add_knowledge to write your analysis."""
828
+
829
+ return self._plan_and_execute_agent(agent_def, prompt, agent_def.get("role", "agent"), phase="execute")
830
+
831
+ max_workers = min(len(self.agents), 8) # Cap at 8 concurrent
832
+ with ThreadPoolExecutor(max_workers=max_workers) as pool:
833
+ futures = {pool.submit(_run_agent, a, focus_angles.get(a.get("name", ""), "")): a for a in self.agents}
834
+ for future in as_completed(futures):
835
+ try:
836
+ result = future.result()
837
+ self.run["agents"].append(result)
838
+ self._save_run(self.run)
839
+ self.completed_agents.add(result.get("name", ""))
840
+
841
+ # Incremental score
842
+ if result.get("status") == "completed":
843
+ partial_scores = self.scorer.add_result(result)
844
+ self._emit(SSEEvent.SCORE_UPDATE, {
845
+ "partial_score": partial_scores.get("overall", 0),
846
+ "scores": partial_scores,
847
+ "scored_agents": len(self.completed_agents),
848
+ "total_agents": len(self.agents),
849
+ })
850
+ except Exception as e:
851
+ agent_def = futures[future]
852
+ self.run["agents"].append({
853
+ "name": agent_def.get("name", "Agent"),
854
+ "status": "failed", "error": str(e),
855
+ "duration_ms": 0,
856
+ })
857
+ logger.error("[EXPERIMENT] Agent failed: %s", e)
858
+
859
+ # ── LEADER-FOLLOWER MODE ──────────────────────────────────────────
860
+
861
+ def _execute_leader_follower(self):
862
+ """Leader plans tasks, workers execute in parallel, leader synthesizes."""
863
+ leader = self.agents[0]
864
+ workers = self.agents[1:]
865
+ leader_name = leader.get("name", "Leader")
866
+ worker_names = ', '.join(w.get("name", f"Worker-{i}") for i, w in enumerate(workers))
867
+
868
+ # Phase 1: PLAN
869
+ self._progress(f"Phase 1: PLAN — {leader_name} analyzing graph...")
870
+ plan_prompt = f"""GOAL: {self.goal}
871
+
872
+ Your team: {worker_names} ({len(workers)} member(s))
873
+
874
+ You MUST create exactly {len(workers)} task(s) using add_task — one per team member.
875
+ Each task should specify what to research. Do NOT search — just create tasks.
876
+
877
+ Example plan for 1 worker named "Research Analyst":
878
+ [{{"tool": "add_task", "params": {{"title": "Research Iran-US tensions", "description": "Search for facts about Iran ceasefire, US military moves, key actors", "assigned_to": "Research Analyst"}}}}]"""
879
+
880
+ plan_result = self._plan_and_execute_agent(leader, plan_prompt, "leader", "plan", max_tools=len(workers) + 1)
881
+ self.run["agents"].append(plan_result)
882
+ self._save_run(self.run)
883
+ self.completed_agents.add(leader_name)
884
+
885
+ # Extract tasks from leader's plan
886
+ leader_tasks = {}
887
+ for step in plan_result.get("steps", []):
888
+ if step.get("tool") == "add_task" and step.get("success"):
889
+ params = step.get("params", {})
890
+ assigned = params.get("assigned_to", "")
891
+ if assigned:
892
+ leader_tasks[assigned] = {
893
+ "title": params.get("title", ""),
894
+ "description": params.get("description", ""),
895
+ }
896
+ logger.info("[EXPERIMENT] Leader created %d tasks: %s", len(leader_tasks), list(leader_tasks.keys()))
897
+
898
+ # Phase 2: EXECUTE — Workers in parallel
899
+ self._progress(f"Phase 2: EXECUTE — {len(workers)} workers in parallel...")
900
+
901
+ def _run_worker(worker_def):
902
+ w_name = worker_def.get("name", "Worker")
903
+ task_info = leader_tasks.get(w_name, {})
904
+ task_block = f"\nYOUR TASK: {task_info['title']}\n{task_info['description']}" if task_info else ""
905
+
906
+ worker_prompt = f"""GOAL: {self.goal}
907
+ {task_block}
908
+
909
+ Search for relevant data, then write a Finding with specific facts, names, and numbers."""
910
+
911
+ return self._plan_and_execute_agent(worker_def, worker_prompt, "worker", "execute", max_tools=6)
912
+
913
+ with ThreadPoolExecutor(max_workers=len(workers)) as pool:
914
+ futures = {pool.submit(_run_worker, w): w for w in workers}
915
+ for future in as_completed(futures):
916
+ try:
917
+ w_result = future.result()
918
+ self.run["agents"].append(w_result)
919
+ self._save_run(self.run)
920
+ self.completed_agents.add(w_result.get("name", ""))
921
+
922
+ # Incremental score
923
+ if w_result.get("status") == "completed":
924
+ partial_scores = self.scorer.add_result(w_result)
925
+ self._emit(SSEEvent.SCORE_UPDATE, {
926
+ "partial_score": partial_scores.get("overall", 0),
927
+ "scores": partial_scores,
928
+ "scored_agents": len(self.completed_agents),
929
+ "total_agents": len(self.agents),
930
+ })
931
+ except Exception as e:
932
+ worker_def = futures[future]
933
+ self.run["agents"].append({
934
+ "name": worker_def.get("name", "Worker"),
935
+ "status": "failed", "error": str(e),
936
+ })
937
+
938
+ # Phase 3: SYNTHESIZE — Leader reads findings and writes summary
939
+ if self._check_signal(self.run_id) != "stop":
940
+ self._progress(f"Phase 3: SYNTHESIZE — {leader_name} reviewing findings...")
941
+ synth_prompt = f"""Your team has completed their research.
942
+
943
+ GOAL: {self.goal}
944
+
945
+ Your plan MUST be: 1) search_nodes to read Findings, 2) add_knowledge to write a comprehensive Insight.
946
+ The Insight should be a strategic briefing paragraph with specific names, numbers, and facts.
947
+ node_type MUST be "Insight" (not "Finding")."""
948
+
949
+ synth_result = self._plan_and_execute_agent(leader, synth_prompt, "leader", "synthesize", max_tools=4)
950
+ self.run["agents"].append(synth_result)
951
+ self._save_run(self.run)
952
+
953
+ # ── SEQUENTIAL MODE (backward compat) ─────────────────────────────
954
+
955
+ def _execute_sequential(self):
956
+ """Agents run one at a time (backward compat). Still uses pre-planned execution."""
957
+ self._progress(f"Sequential mode: {len(self.agents)} agents...")
958
+
959
+ for agent_def in self.agents:
960
+ if self._check_signal(self.run_id) == "stop":
961
+ self.run["agents"].append({"name": agent_def.get("name"), "status": "aborted"})
962
+ break
963
+
964
+ a_name = agent_def.get("name", "Agent")
965
+ a_prompt = agent_def.get("prompt", "")
966
+
967
+ prompt = f"""GOAL: {self.goal}
968
+ {f'INSTRUCTIONS: {a_prompt}' if a_prompt else ''}
969
+
970
+ Search the graph for relevant data, then write a Finding with your analysis.
971
+ You MUST end with add_knowledge to write your analysis."""
972
+
973
+ result = self._plan_and_execute_agent(agent_def, prompt, agent_def.get("role", "agent"), phase="execute")
974
+ self.run["agents"].append(result)
975
+ self._save_run(self.run)
976
+ self.completed_agents.add(a_name)
977
+
978
+ # Incremental score
979
+ if result.get("status") == "completed":
980
+ partial_scores = self.scorer.add_result(result)
981
+ self._emit(SSEEvent.SCORE_UPDATE, {
982
+ "partial_score": partial_scores.get("overall", 0),
983
+ "scores": partial_scores,
984
+ "scored_agents": len(self.completed_agents),
985
+ "total_agents": len(self.agents),
986
+ })
987
+
988
+ # ── Final scoring with LLM insight quality ─────────────────────────
989
+
990
+ def _final_score(self):
991
+ """Compute final score including LLM-evaluated insight quality."""
992
+ try:
993
+ # Get partial scores from incremental scorer
994
+ scores = self.scorer._compute() if self.scorer._agent_results else {}
995
+
996
+ # LLM-evaluated insight quality
997
+ final_insights = []
998
+ for a in self.run.get("agents", []):
999
+ for s in a.get("steps", []):
1000
+ if s.get("tool") == "add_knowledge" and s.get("success"):
1001
+ content = s.get("params", {}).get("content", "")
1002
+ if content and len(content) > 30:
1003
+ final_insights.append(content)
1004
+
1005
+ if final_insights and self.llm:
1006
+ try:
1007
+ eval_prompt = f"""Rate the quality of this experiment's output on a scale of 0-100.
1008
+
1009
+ GOAL: {self.goal}
1010
+
1011
+ EXPERIMENT INSIGHTS:
1012
+ {chr(10).join(f'- {ins[:200]}' for ins in final_insights[:5])}
1013
+
1014
+ Rate based on:
1015
+ - Relevance: Do the insights address the goal?
1016
+ - Depth: Are they specific or generic?
1017
+ - Actionability: Can someone act on these findings?
1018
+
1019
+ Reply with ONLY a number between 0 and 100."""
1020
+ raw_score = self.llm.generate(prompt=eval_prompt, max_tokens=10)
1021
+ nums = re.findall(r'\d+', raw_score.strip())
1022
+ scores["insight_quality"] = min(100, max(0, int(nums[0]))) if nums else 50
1023
+ except Exception:
1024
+ scores["insight_quality"] = 50
1025
+ else:
1026
+ scores["insight_quality"] = 0 if not final_insights else 50
1027
+
1028
+ # Final overall with insight_quality weighted in
1029
+ weights = {
1030
+ "coverage": 0.10, "discovery": 0.15, "enrichment": 0.15,
1031
+ "collaboration": 0.15, "efficiency": 0.10, "completion": 0.10,
1032
+ "insight_quality": 0.25,
1033
+ }
1034
+ scores["overall"] = int(sum(scores.get(k, 0) * w for k, w in weights.items()))
1035
+
1036
+ self.run["scores"] = scores
1037
+ logger.info("[EXPERIMENT] Final scores: %s", scores)
1038
+ except Exception as e:
1039
+ logger.warning("[EXPERIMENT] Scoring failed: %s", e)
1040
+ self.run["scores"] = {}
1041
+
1042
+ # ── Build output briefing ──────────────────────────────────────────
1043
+
1044
+ def _build_output(self):
1045
+ """Build experiment summary from all Findings and Insights."""
1046
+ summary_parts = []
1047
+ for a in self.run.get("agents", []):
1048
+ for s in a.get("steps", []):
1049
+ if s.get("tool") == "add_knowledge" and s.get("success"):
1050
+ content = s.get("params", {}).get("content", "")
1051
+ node_type = s.get("params", {}).get("node_type", "Finding")
1052
+ if content and len(content) > 20:
1053
+ summary_parts.append({
1054
+ "type": node_type,
1055
+ "agent": a.get("name", ""),
1056
+ "phase": a.get("phase", ""),
1057
+ "content": content,
1058
+ })
1059
+ self.run["summary"] = summary_parts
1060
+
1061
+ if summary_parts:
1062
+ insights = [p for p in summary_parts if p["type"] == "Insight"]
1063
+ findings = [p for p in summary_parts if p["type"] == "Finding"]
1064
+ output_lines = []
1065
+ if insights:
1066
+ output_lines.append("=== INTELLIGENCE BRIEFING ===")
1067
+ for ins in insights:
1068
+ output_lines.append(f"\n{ins['content']}")
1069
+ if findings:
1070
+ if output_lines:
1071
+ output_lines.append("\n\n=== SUPPORTING FINDINGS ===")
1072
+ else:
1073
+ output_lines.append("=== FINDINGS ===")
1074
+ for f in findings:
1075
+ output_lines.append(f"\n[{f['agent']}] {f['content']}")
1076
+ self.run["output"] = "\n".join(output_lines)
1077
+ else:
1078
+ self.run["output"] = "No findings or insights were produced."