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.
- contextsynapse/__init__.py +107 -0
- contextsynapse/__main__.py +9 -0
- contextsynapse/a2a/__init__.py +28 -0
- contextsynapse/a2a/client.py +120 -0
- contextsynapse/a2a/discovery.py +128 -0
- contextsynapse/a2a/handlers.py +164 -0
- contextsynapse/a2a/models.py +246 -0
- contextsynapse/a2a/streaming.py +92 -0
- contextsynapse/a2a/task_manager.py +275 -0
- contextsynapse/adapters/__init__.py +98 -0
- contextsynapse/adapters/_base.py +298 -0
- contextsynapse/adapters/autogen/__init__.py +4 -0
- contextsynapse/adapters/autogen/tools.py +34 -0
- contextsynapse/adapters/crewai/__init__.py +4 -0
- contextsynapse/adapters/crewai/tools.py +63 -0
- contextsynapse/adapters/langchain/__init__.py +19 -0
- contextsynapse/adapters/langchain/message_history.py +17 -0
- contextsynapse/adapters/langchain/retriever.py +158 -0
- contextsynapse/adapters/langchain/tools.py +43 -0
- contextsynapse/adapters/langgraph/__init__.py +21 -0
- contextsynapse/adapters/langgraph/checkpoint.py +223 -0
- contextsynapse/adapters/langgraph/context_tools.py +341 -0
- contextsynapse/adapters/langgraph/message_history.py +138 -0
- contextsynapse/adapters/langgraph/tools.py +43 -0
- contextsynapse/adapters/llamaindex/__init__.py +4 -0
- contextsynapse/adapters/llamaindex/tools.py +64 -0
- contextsynapse/adapters/openai/__init__.py +24 -0
- contextsynapse/adapters/openai/tools.py +206 -0
- contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
- contextsynapse/adapters/pydantic_ai/tools.py +36 -0
- contextsynapse/adapters/swarm/__init__.py +4 -0
- contextsynapse/adapters/swarm/tools.py +44 -0
- contextsynapse/agents/__init__.py +6 -0
- contextsynapse/agents/auto_capture.py +80 -0
- contextsynapse/agents/dispatcher.py +299 -0
- contextsynapse/agents/notifications.py +112 -0
- contextsynapse/aiql/__init__.py +71 -0
- contextsynapse/aiql/cache.py +341 -0
- contextsynapse/aiql/compiler/__init__.py +62 -0
- contextsynapse/aiql/compiler/optimizer.py +126 -0
- contextsynapse/aiql/compiler/planner.py +366 -0
- contextsynapse/aiql/compiler/statistics.py +235 -0
- contextsynapse/aiql/compiler/validator.py +425 -0
- contextsynapse/aiql/engine/__init__.py +11 -0
- contextsynapse/aiql/engine/chunk_storage.py +2584 -0
- contextsynapse/aiql/engine/executor.py +7737 -0
- contextsynapse/aiql/engine/extraction_integration.py +442 -0
- contextsynapse/aiql/engine/graph_builder.py +405 -0
- contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
- contextsynapse/aiql/engine/performance_monitor.py +289 -0
- contextsynapse/aiql/engine/postprocessing.py +365 -0
- contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
- contextsynapse/aiql/engine/redis_cache.py +106 -0
- contextsynapse/aiql/engine/streaming_json.py +366 -0
- contextsynapse/aiql/engine/streaming_parquet.py +568 -0
- contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
- contextsynapse/aiql/grammar/__init__.py +14 -0
- contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
- contextsynapse/aiql/grammar/base.py +71 -0
- contextsynapse/aiql/parser/__init__.py +46 -0
- contextsynapse/aiql/parser/aiql_parser.py +11330 -0
- contextsynapse/aiql/parser/base_parser.py +47 -0
- contextsynapse/api/__init__.py +5 -0
- contextsynapse/api/a2a_router.py +233 -0
- contextsynapse/api/admin_router.py +140 -0
- contextsynapse/api/agent_worker_router.py +978 -0
- contextsynapse/api/algorithms_router.py +317 -0
- contextsynapse/api/api.py +4144 -0
- contextsynapse/api/audit.py +204 -0
- contextsynapse/api/auth.py +441 -0
- contextsynapse/api/auth_router.py +537 -0
- contextsynapse/api/billing.py +261 -0
- contextsynapse/api/billing_router.py +275 -0
- contextsynapse/api/boundary_router.py +452 -0
- contextsynapse/api/cognition_router.py +116 -0
- contextsynapse/api/connector_router.py +237 -0
- contextsynapse/api/context_router.py +1619 -0
- contextsynapse/api/dashboard_router.py +11367 -0
- contextsynapse/api/errors.py +183 -0
- contextsynapse/api/events.py +88 -0
- contextsynapse/api/experiment_bootstrap.py +321 -0
- contextsynapse/api/experiment_orchestrator.py +1078 -0
- contextsynapse/api/experiment_router.py +2089 -0
- contextsynapse/api/feature_gate.py +89 -0
- contextsynapse/api/federation_router.py +108 -0
- contextsynapse/api/integrations.py +567 -0
- contextsynapse/api/integrations_router.py +461 -0
- contextsynapse/api/intelligence_router.py +1028 -0
- contextsynapse/api/metering.py +155 -0
- contextsynapse/api/metrics.py +112 -0
- contextsynapse/api/models.py +38 -0
- contextsynapse/api/monitoring_router.py +174 -0
- contextsynapse/api/pipeline_router.py +1437 -0
- contextsynapse/api/playground_conn.py +116 -0
- contextsynapse/api/projects_router.py +131 -0
- contextsynapse/api/queries_router.py +282 -0
- contextsynapse/api/quota.py +95 -0
- contextsynapse/api/rate_limit.py +64 -0
- contextsynapse/api/search_router.py +261 -0
- contextsynapse/api/shield_router.py +447 -0
- contextsynapse/api/tenant_guard.py +103 -0
- contextsynapse/api/tenant_quotas.py +231 -0
- contextsynapse/api/tenants.py +305 -0
- contextsynapse/api/users.py +646 -0
- contextsynapse/api/v1.py +167 -0
- contextsynapse/api/vertical_builder_router.py +387 -0
- contextsynapse/benchmark/__init__.py +15 -0
- contextsynapse/benchmark/__main__.py +64 -0
- contextsynapse/benchmark/agents.py +338 -0
- contextsynapse/benchmark/live_ab.py +338 -0
- contextsynapse/benchmark/reporter.py +149 -0
- contextsynapse/benchmark/runner.py +264 -0
- contextsynapse/benchmark/savings.py +266 -0
- contextsynapse/benchmark/token_counter.py +212 -0
- contextsynapse/benchmark/workloads.py +289 -0
- contextsynapse/buffer/__init__.py +154 -0
- contextsynapse/buffer/base.py +257 -0
- contextsynapse/buffer/duckdb_buffer.py +246 -0
- contextsynapse/buffer/file_buffer.py +229 -0
- contextsynapse/buffer/lmdb_buffer.py +286 -0
- contextsynapse/buffer/local_buffer.py +154 -0
- contextsynapse/buffer/redis_buffer.py +211 -0
- contextsynapse/buffer/sqlite_buffer.py +266 -0
- contextsynapse/cache.py +2 -0
- contextsynapse/cli/__init__.py +40 -0
- contextsynapse/cli/cli.py +475 -0
- contextsynapse/cli/config_cli.py +177 -0
- contextsynapse/cli/scaffold.py +465 -0
- contextsynapse/cognition/__init__.py +31 -0
- contextsynapse/cognition/derivation.py +55 -0
- contextsynapse/cognition/events.py +135 -0
- contextsynapse/cognition/feedback.py +52 -0
- contextsynapse/cognition/hallucination.py +90 -0
- contextsynapse/cognition/invalidation.py +70 -0
- contextsynapse/cognition/read_tracker.py +50 -0
- contextsynapse/collaboration.py +2 -0
- contextsynapse/comparison.py +2 -0
- contextsynapse/config/__init__.py +10 -0
- contextsynapse/config/connectors/amfi_nav.yaml +18 -0
- contextsynapse/config/connectors/auto_sector.yaml +20 -0
- contextsynapse/config/connectors/banking_sector.yaml +20 -0
- contextsynapse/config/connectors/energy_sector.yaml +20 -0
- contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
- contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
- contextsynapse/config/connectors/nse_price.yaml +61 -0
- contextsynapse/config/connectors/pharma_sector.yaml +20 -0
- contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
- contextsynapse/config/context_presets/ai_research.json +25 -0
- contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
- contextsynapse/config/context_presets/claude_conversations.json +19 -0
- contextsynapse/config/context_presets/legal_compliance.json +21 -0
- contextsynapse/config/context_presets/meeting_notes.json +21 -0
- contextsynapse/config/context_presets/slack_engineering.json +21 -0
- contextsynapse/config/context_presets/tech_climate.json +24 -0
- contextsynapse/config/context_presets/toi_politics.json +25 -0
- contextsynapse/config/database_config.py +453 -0
- contextsynapse/config/deployment.py +221 -0
- contextsynapse/config/gpt_action_schema.json +164 -0
- contextsynapse/config/indicators/india_macro.yaml +213 -0
- contextsynapse/config/logging_config.py +88 -0
- contextsynapse/config/mode_config.py +223 -0
- contextsynapse/config/schemas/api_spec.yaml +48 -0
- contextsynapse/config/schemas/architecture_doc.yaml +51 -0
- contextsynapse/config/schemas/chat_history.yaml +90 -0
- contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
- contextsynapse/config/schemas/claude_conversation.yaml +148 -0
- contextsynapse/config/schemas/contract.yaml +48 -0
- contextsynapse/config/schemas/conversation.yaml +167 -0
- contextsynapse/config/schemas/database.yaml +74 -0
- contextsynapse/config/schemas/decision.yaml +72 -0
- contextsynapse/config/schemas/ecommerce.yaml +56 -0
- contextsynapse/config/schemas/education.yaml +42 -0
- contextsynapse/config/schemas/healthcare.yaml +133 -0
- contextsynapse/config/schemas/hr_document.yaml +48 -0
- contextsynapse/config/schemas/invoice.yaml +40 -0
- contextsynapse/config/schemas/knowledge_base.yaml +75 -0
- contextsynapse/config/schemas/legal.yaml +42 -0
- contextsynapse/config/schemas/marketing_content.yaml +48 -0
- contextsynapse/config/schemas/meeting_notes.yaml +51 -0
- contextsynapse/config/schemas/news_article.yaml +61 -0
- contextsynapse/config/schemas/real_estate.yaml +49 -0
- contextsynapse/config/schemas/requirements_doc.yaml +48 -0
- contextsynapse/config/schemas/research_paper.yaml +56 -0
- contextsynapse/config/schemas/resume.yaml +49 -0
- contextsynapse/config/schemas/retail.yaml +42 -0
- contextsynapse/config/schemas/rule_meta.yaml +173 -0
- contextsynapse/config/schemas/rules.yaml +72 -0
- contextsynapse/config/schemas/screener_meta.yaml +236 -0
- contextsynapse/config/schemas/sdlc.yaml +103 -0
- contextsynapse/config/schemas/session_graph.yaml +172 -0
- contextsynapse/config/schemas/support_ticket.yaml +48 -0
- contextsynapse/config/schemas/web.yaml +64 -0
- contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
- contextsynapse/config/sensors/global_risk.yaml +53 -0
- contextsynapse/config/sensors/government_policy.yaml +54 -0
- contextsynapse/config/sensors/supply_chain.yaml +50 -0
- contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
- contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
- contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
- contextsynapse/config/sensors/templates/market_data.yaml +118 -0
- contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
- contextsynapse/config/sensors/weather.yaml +52 -0
- contextsynapse/config/storage_strategy.py +202 -0
- contextsynapse/config/templates/competitive_intel.yaml +39 -0
- contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
- contextsynapse/config/templates/market_analysis.yaml +58 -0
- contextsynapse/connectors/__init__.py +15 -0
- contextsynapse/connectors/api_router.py +121 -0
- contextsynapse/connectors/base.py +357 -0
- contextsynapse/connectors/financial/audio.py +170 -0
- contextsynapse/connectors/financial/youtube.py +272 -0
- contextsynapse/connectors/github.py +164 -0
- contextsynapse/connectors/jira.py +120 -0
- contextsynapse/connectors/kafka_connector.py +124 -0
- contextsynapse/connectors/pipeline_connector.py +747 -0
- contextsynapse/connectors/registry.py +116 -0
- contextsynapse/connectors/scheduler.py +87 -0
- contextsynapse/connectors/universal.py +360 -0
- contextsynapse/context/__init__.py +96 -0
- contextsynapse/context/__main__.py +4 -0
- contextsynapse/context/acl.py +125 -0
- contextsynapse/context/agent_card.py +201 -0
- contextsynapse/context/agent_memory.py +814 -0
- contextsynapse/context/agents.py +472 -0
- contextsynapse/context/agents_redis.py +391 -0
- contextsynapse/context/assembled.py +204 -0
- contextsynapse/context/attribution.py +113 -0
- contextsynapse/context/blob.py +184 -0
- contextsynapse/context/boundaries.py +187 -0
- contextsynapse/context/boundary.py +507 -0
- contextsynapse/context/bundle.py +155 -0
- contextsynapse/context/cli.py +519 -0
- contextsynapse/context/collaboration.py +165 -0
- contextsynapse/context/compiler.py +604 -0
- contextsynapse/context/composite.py +536 -0
- contextsynapse/context/compression.py +147 -0
- contextsynapse/context/context_manager.py +888 -0
- contextsynapse/context/context_schema.py +90 -0
- contextsynapse/context/context_schema.yaml +105 -0
- contextsynapse/context/context_units.py +891 -0
- contextsynapse/context/conversation.py +488 -0
- contextsynapse/context/dedup.py +207 -0
- contextsynapse/context/document_processor.py +698 -0
- contextsynapse/context/embedding_hooks.py +338 -0
- contextsynapse/context/execution_schema.py +274 -0
- contextsynapse/context/fan_out.py +138 -0
- contextsynapse/context/frozen.py +80 -0
- contextsynapse/context/gravity.py +227 -0
- contextsynapse/context/hub.py +1401 -0
- contextsynapse/context/ingest.py +391 -0
- contextsynapse/context/injection.py +226 -0
- contextsynapse/context/interaction_graphifier.py +562 -0
- contextsynapse/context/layers.py +574 -0
- contextsynapse/context/projection.py +1195 -0
- contextsynapse/context/promotion.py +634 -0
- contextsynapse/context/proof.py +131 -0
- contextsynapse/context/propagation.py +577 -0
- contextsynapse/context/pubsub.py +135 -0
- contextsynapse/context/quality.py +1054 -0
- contextsynapse/context/quality_tracker.py +270 -0
- contextsynapse/context/scoping.py +370 -0
- contextsynapse/context/seed.py +992 -0
- contextsynapse/context/session.py +1142 -0
- contextsynapse/context/session_graph.py +593 -0
- contextsynapse/context/session_memory.py +309 -0
- contextsynapse/context/session_resolver.py +111 -0
- contextsynapse/context/skill_learning.py +237 -0
- contextsynapse/context/smart_budget.py +129 -0
- contextsynapse/context/source_policy.py +242 -0
- contextsynapse/context/store_factory.py +53 -0
- contextsynapse/context/sync.py +648 -0
- contextsynapse/context/token.py +236 -0
- contextsynapse/context/vector_integration.py +297 -0
- contextsynapse/context/webhooks.py +194 -0
- contextsynapse/context/working_memory.py +224 -0
- contextsynapse/core/__init__.py +18 -0
- contextsynapse/core/archiver.py +237 -0
- contextsynapse/core/change_stream.py +282 -0
- contextsynapse/core/checkpoint.py +310 -0
- contextsynapse/core/cloud_storage.py +387 -0
- contextsynapse/core/context_state.py +651 -0
- contextsynapse/core/cron.py +324 -0
- contextsynapse/core/datasource.py +643 -0
- contextsynapse/core/db.py +289 -0
- contextsynapse/core/federation.py +283 -0
- contextsynapse/core/graph_coordinator.py +201 -0
- contextsynapse/core/graph_intelligence.py +388 -0
- contextsynapse/core/graph_structures.py +106 -0
- contextsynapse/core/graph_sync.py +157 -0
- contextsynapse/core/hybrid_graph_storage.py +2998 -0
- contextsynapse/core/lifecycle.py +691 -0
- contextsynapse/core/multiworker.py +378 -0
- contextsynapse/core/pruning.py +312 -0
- contextsynapse/core/redis_registry.py +524 -0
- contextsynapse/core/registry.py +1043 -0
- contextsynapse/core/registry_factory.py +51 -0
- contextsynapse/core/registry_metadata.py +294 -0
- contextsynapse/core/replay.py +354 -0
- contextsynapse/core/replication.py +339 -0
- contextsynapse/core/time_travel.py +260 -0
- contextsynapse/core/write_behind.py +152 -0
- contextsynapse/core/write_context.py +39 -0
- contextsynapse/db/__init__.py +1 -0
- contextsynapse/db/postgres.py +267 -0
- contextsynapse/db/profile.py +236 -0
- contextsynapse/db/rules.py +328 -0
- contextsynapse/demo/__init__.py +0 -0
- contextsynapse/demo/report.py +147 -0
- contextsynapse/demo/runner.py +520 -0
- contextsynapse/demo/scenario.py +165 -0
- contextsynapse/demo/session_demo.py +280 -0
- contextsynapse/demo/session_report.py +143 -0
- contextsynapse/demo/universal_runner.py +257 -0
- contextsynapse/engine/__init__.py +15 -0
- contextsynapse/engine/core.py +555 -0
- contextsynapse/extraction/__init__.py +69 -0
- contextsynapse/extraction/cli.py +274 -0
- contextsynapse/extraction/config.py +107 -0
- contextsynapse/extraction/examples/example_usage.py +118 -0
- contextsynapse/extraction/examples/sample_manifest.json +53 -0
- contextsynapse/extraction/examples/sample_normalized.json +185 -0
- contextsynapse/extraction/extract_engine.py +440 -0
- contextsynapse/extraction/extractors/__init__.py +37 -0
- contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
- contextsynapse/extraction/extractors/csv_extractor.py +83 -0
- contextsynapse/extraction/extractors/docx_extractor.py +129 -0
- contextsynapse/extraction/extractors/excel_extractor.py +152 -0
- contextsynapse/extraction/extractors/html_extractor.py +146 -0
- contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
- contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
- contextsynapse/extraction/extractors/register_extractors.py +63 -0
- contextsynapse/extraction/extractors/text_extractor.py +65 -0
- contextsynapse/extraction/extractors/txt_extractor.py +120 -0
- contextsynapse/extraction/extractors/website_extractor.py +246 -0
- contextsynapse/extraction/fact_extractor.py +236 -0
- contextsynapse/extraction/file_manager.py +353 -0
- contextsynapse/extraction/hierarchy.py +239 -0
- contextsynapse/extraction/id_generator.py +450 -0
- contextsynapse/extraction/layout_detector.py +409 -0
- contextsynapse/extraction/llm_entity_extractor.py +301 -0
- contextsynapse/extraction/normalized_store.py +480 -0
- contextsynapse/extraction/normalizer.py +594 -0
- contextsynapse/extraction/parquet_writer.py +598 -0
- contextsynapse/extraction/post_processors/__init__.py +40 -0
- contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
- contextsynapse/extraction/ray_normalization.py +233 -0
- contextsynapse/extraction/ray_runner.py +207 -0
- contextsynapse/extraction/redis_schema_registry.py +222 -0
- contextsynapse/extraction/registry.py +204 -0
- contextsynapse/extraction/schema_loader.py +663 -0
- contextsynapse/extraction/schema_registry.py +230 -0
- contextsynapse/extraction/semantic_normalizer.py +245 -0
- contextsynapse/extraction/version_manager.py +314 -0
- contextsynapse/gateway/__init__.py +24 -0
- contextsynapse/gateway/action_emitter.py +212 -0
- contextsynapse/gateway/cost_tracker.py +508 -0
- contextsynapse/gateway/gateway.py +377 -0
- contextsynapse/gateway/policy.py +303 -0
- contextsynapse/governance/__init__.py +13 -0
- contextsynapse/governance/layer.py +513 -0
- contextsynapse/ingestion/__init__.py +15 -0
- contextsynapse/ingestion/amplifier.py +288 -0
- contextsynapse/ingestion/async_ingest.py +463 -0
- contextsynapse/ingestion/chunker.py +303 -0
- contextsynapse/ingestion/chunking.py +58 -0
- contextsynapse/ingestion/cleaner.py +190 -0
- contextsynapse/ingestion/cleanse.py +252 -0
- contextsynapse/ingestion/connectors/__init__.py +38 -0
- contextsynapse/ingestion/connectors/base.py +300 -0
- contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
- contextsynapse/ingestion/connectors/claude_memory.py +274 -0
- contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
- contextsynapse/ingestion/content_detector.py +80 -0
- contextsynapse/ingestion/content_monitor.py +502 -0
- contextsynapse/ingestion/dedup.py +106 -0
- contextsynapse/ingestion/feed.py +640 -0
- contextsynapse/ingestion/filters.py +461 -0
- contextsynapse/ingestion/graph_builder.py +705 -0
- contextsynapse/ingestion/input_classifier.py +307 -0
- contextsynapse/ingestion/job_manager.py +571 -0
- contextsynapse/ingestion/llm_extractor.py +610 -0
- contextsynapse/ingestion/orchestrator.py +116 -0
- contextsynapse/ingestion/parsers/__init__.py +19 -0
- contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
- contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
- contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
- contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
- contextsynapse/ingestion/pipeline_context.py +228 -0
- contextsynapse/ingestion/pipeline_store.py +1195 -0
- contextsynapse/ingestion/queue.py +133 -0
- contextsynapse/ingestion/reprocess.py +179 -0
- contextsynapse/ingestion/scenario_router.py +224 -0
- contextsynapse/ingestion/schema_extractor.py +759 -0
- contextsynapse/ingestion/schema_validator.py +274 -0
- contextsynapse/ingestion/sdlc_ingest.py +87 -0
- contextsynapse/ingestion/smart_ingest.py +2451 -0
- contextsynapse/ingestion/source_connector.py +171 -0
- contextsynapse/ingestion/stage_executor.py +2095 -0
- contextsynapse/ingestion/stages/__init__.py +9 -0
- contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
- contextsynapse/ingestion/strategies.py +386 -0
- contextsynapse/ingestion/universal/__init__.py +4 -0
- contextsynapse/ingestion/universal/_operator_registry.py +59 -0
- contextsynapse/ingestion/universal/ingest_content.py +32 -0
- contextsynapse/ingestion/universal/operators/__init__.py +1 -0
- contextsynapse/ingestion/universal/operators/base.py +19 -0
- contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
- contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
- contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
- contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
- contextsynapse/ingestion/universal/operators/embed.py +80 -0
- contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
- contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
- contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
- contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
- contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
- contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
- contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
- contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
- contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
- contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
- contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
- contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
- contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
- contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
- contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
- contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
- contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
- contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
- contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
- contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
- contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
- contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
- contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
- contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
- contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
- contextsynapse/ingestion/universal/shared_signals.py +161 -0
- contextsynapse/ingestion/universal/stage_executor.py +219 -0
- contextsynapse/ingestion/web_crawler.py +265 -0
- contextsynapse/intelligence/__init__.py +53 -0
- contextsynapse/intelligence/alerts.py +198 -0
- contextsynapse/intelligence/collector.py +655 -0
- contextsynapse/intelligence/comparison.py +198 -0
- contextsynapse/intelligence/config.py +56 -0
- contextsynapse/intelligence/conflict_detector.py +182 -0
- contextsynapse/intelligence/context_gaps.py +192 -0
- contextsynapse/intelligence/context_radar.py +135 -0
- contextsynapse/intelligence/correlation_engine.py +416 -0
- contextsynapse/intelligence/correlation_template.py +120 -0
- contextsynapse/intelligence/dedup.py +296 -0
- contextsynapse/intelligence/event_bus.py +91 -0
- contextsynapse/intelligence/feedback_loop.py +101 -0
- contextsynapse/intelligence/freshness.py +307 -0
- contextsynapse/intelligence/fundamental_feed.py +551 -0
- contextsynapse/intelligence/geo_data.py +170 -0
- contextsynapse/intelligence/geo_reference.py +112 -0
- contextsynapse/intelligence/graph_fusion.py +465 -0
- contextsynapse/intelligence/heuristics.py +176 -0
- contextsynapse/intelligence/impact_tracker.py +336 -0
- contextsynapse/intelligence/macro_indicators.py +319 -0
- contextsynapse/intelligence/ocr.py +104 -0
- contextsynapse/intelligence/persistence.py +267 -0
- contextsynapse/intelligence/pipeline_health.py +130 -0
- contextsynapse/intelligence/price_feed.py +221 -0
- contextsynapse/intelligence/rate_limiter.py +130 -0
- contextsynapse/intelligence/reactive.py +411 -0
- contextsynapse/intelligence/runtime_context.py +390 -0
- contextsynapse/intelligence/sentiment_decay.py +426 -0
- contextsynapse/intelligence/session.py +277 -0
- contextsynapse/intelligence/signal_hierarchy.py +438 -0
- contextsynapse/intelligence/signals.py +185 -0
- contextsynapse/intelligence/source_watcher.py +132 -0
- contextsynapse/intelligence/table_enricher.py +256 -0
- contextsynapse/intelligence/tagger.py +319 -0
- contextsynapse/intelligence/timeseries.py +209 -0
- contextsynapse/intelligence/tracker.py +318 -0
- contextsynapse/intelligence/watchdog.py +392 -0
- contextsynapse/intelligence/watchdog_manager.py +263 -0
- contextsynapse/intelligence/webhook_receiver.py +165 -0
- contextsynapse/intelligence/weight_learner.py +234 -0
- contextsynapse/llm/__init__.py +8 -0
- contextsynapse/llm/client.py +348 -0
- contextsynapse/llm/rate_limiter.py +166 -0
- contextsynapse/logging_config.py +2 -0
- contextsynapse/marketplace/__init__.py +7 -0
- contextsynapse/marketplace/registry.py +173 -0
- contextsynapse/mcp/__init__.py +11 -0
- contextsynapse/mcp/__main__.py +4 -0
- contextsynapse/mcp/auth_middleware.py +212 -0
- contextsynapse/mcp/connection_pool.py +128 -0
- contextsynapse/mcp/server.py +986 -0
- contextsynapse/mcp/session_context.py +67 -0
- contextsynapse/memory/__init__.py +34 -0
- contextsynapse/memory/engine.py +475 -0
- contextsynapse/memory/temporal.py +327 -0
- contextsynapse/metadata/__init__.py +12 -0
- contextsynapse/metadata/metadata_db.py +272 -0
- contextsynapse/metadata/metadata_query_tool.py +300 -0
- contextsynapse/metadata/metadata_tracker.py +425 -0
- contextsynapse/models/__init__.py +27 -0
- contextsynapse/models/embedding_service.py +360 -0
- contextsynapse/models/model_registry.py +228 -0
- contextsynapse/multiworker.py +2 -0
- contextsynapse/pipelines/__init__.py +1 -0
- contextsynapse/pipelines/executor.py +851 -0
- contextsynapse/pipelines/filters.py +189 -0
- contextsynapse/pipelines/models.py +176 -0
- contextsynapse/pipelines/scheduler.py +268 -0
- contextsynapse/playground/__init__.py +1 -0
- contextsynapse/playground/templates.py +85 -0
- contextsynapse/plugins/__init__.py +43 -0
- contextsynapse/plugins/api.py +66 -0
- contextsynapse/plugins/base.py +350 -0
- contextsynapse/plugins/domain_interface.py +508 -0
- contextsynapse/plugins/domain_loader.py +241 -0
- contextsynapse/plugins/loader.py +148 -0
- contextsynapse/plugins/registry.py +265 -0
- contextsynapse/project/__init__.py +11 -0
- contextsynapse/project/code_context.py +807 -0
- contextsynapse/project/connectors/__init__.py +4 -0
- contextsynapse/project/connectors/github_connector.py +113 -0
- contextsynapse/project/connectors/jira_connector.py +160 -0
- contextsynapse/project/graph.py +1023 -0
- contextsynapse/project/project_context.py +545 -0
- contextsynapse/project/scanner_registry.py +61 -0
- contextsynapse/project/schema_manager.py +322 -0
- contextsynapse/project/schemas/knowledge.yaml +59 -0
- contextsynapse/project/schemas/sdlc.yaml +369 -0
- contextsynapse/project/sdlc_schema.py +172 -0
- contextsynapse/project/spec_parser.py +85 -0
- contextsynapse/project/task_lock.py +181 -0
- contextsynapse/project/task_stream.py +203 -0
- contextsynapse/project/team_tools.py +537 -0
- contextsynapse/py.typed +0 -0
- contextsynapse/realtime/__init__.py +6 -0
- contextsynapse/realtime/collaboration.py +147 -0
- contextsynapse/rules/__init__.py +10 -0
- contextsynapse/rules/engine.py +116 -0
- contextsynapse/rules/evaluators/__init__.py +6 -0
- contextsynapse/rules/evaluators/aggregate.py +205 -0
- contextsynapse/rules/evaluators/custom.py +170 -0
- contextsynapse/rules/evaluators/pre_action.py +125 -0
- contextsynapse/rules/evaluators/temporal.py +129 -0
- contextsynapse/rules/loader.py +52 -0
- contextsynapse/rules/models.py +124 -0
- contextsynapse/rules/universal.py +393 -0
- contextsynapse/scheduler.py +229 -0
- contextsynapse/schema/__init__.py +42 -0
- contextsynapse/schema/compiler.py +252 -0
- contextsynapse/schema/composer.py +158 -0
- contextsynapse/schema/dedup.py +73 -0
- contextsynapse/schema/derivation.py +172 -0
- contextsynapse/schema/sdl.py +326 -0
- contextsynapse/schema/validation_gate.py +124 -0
- contextsynapse/sdk/__init__.py +63 -0
- contextsynapse/sdk/agent.py +214 -0
- contextsynapse/sdk/client.py +269 -0
- contextsynapse/sdk/exceptions.py +23 -0
- contextsynapse/sdk/models.py +78 -0
- contextsynapse/sdk/session.py +408 -0
- contextsynapse/sdk/tracked.py +218 -0
- contextsynapse/sdk/worker.py +266 -0
- contextsynapse/sdk/workspace_client.py +98 -0
- contextsynapse/sdk/ws.py +41 -0
- contextsynapse/search/__init__.py +20 -0
- contextsynapse/search/embedding_cache.py +140 -0
- contextsynapse/search/enhanced_search.py +152 -0
- contextsynapse/search/fulltext.py +130 -0
- contextsynapse/search/graph_search.py +634 -0
- contextsynapse/search/lmdb_index.py +1218 -0
- contextsynapse/search/nl_to_aiql.py +199 -0
- contextsynapse/search/rag.py +1346 -0
- contextsynapse/search/rag_cache.py +167 -0
- contextsynapse/search/reasoning_chain.py +231 -0
- contextsynapse/search/redis_search.py +207 -0
- contextsynapse/search/retrieval_quality.py +314 -0
- contextsynapse/search/semantic_query.py +455 -0
- contextsynapse/search/text_resolver.py +159 -0
- contextsynapse/search/whoosh_search.py +373 -0
- contextsynapse/security/__init__.py +17 -0
- contextsynapse/security/audit.py +297 -0
- contextsynapse/security/audit_logger.py +97 -0
- contextsynapse/security/audit_trail.py +270 -0
- contextsynapse/security/auth_unified.py +284 -0
- contextsynapse/security/auto_tagger.py +230 -0
- contextsynapse/security/data_security.py +218 -0
- contextsynapse/security/encryption.py +207 -0
- contextsynapse/security/identity.py +447 -0
- contextsynapse/security/jwt_identity.py +59 -0
- contextsynapse/security/middleware.py +336 -0
- contextsynapse/security/pii.py +233 -0
- contextsynapse/security/rbac.py +96 -0
- contextsynapse/security/rls.py +205 -0
- contextsynapse/security/sanitize.py +233 -0
- contextsynapse/security/scoped_encryption.py +131 -0
- contextsynapse/security/tenant.py +401 -0
- contextsynapse/shield/__init__.py +21 -0
- contextsynapse/shield/anomaly.py +56 -0
- contextsynapse/shield/permissions.py +99 -0
- contextsynapse/shield/profile.py +103 -0
- contextsynapse/shield/shield.py +108 -0
- contextsynapse/shield/trust_engine.py +69 -0
- contextsynapse/skills/__init__.py +14 -0
- contextsynapse/skills/engine.py +233 -0
- contextsynapse/skills/loader.py +59 -0
- contextsynapse/skills/models.py +139 -0
- contextsynapse/storage/__init__.py +7 -0
- contextsynapse/storage/cache.py +208 -0
- contextsynapse/storage/columnar_store_v2.py +178 -0
- contextsynapse/storage/csr_graph_storage.py +587 -0
- contextsynapse/storage/csr_store.py +388 -0
- contextsynapse/storage/document_store.py +491 -0
- contextsynapse/storage/hnsw_index.py +294 -0
- contextsynapse/storage/hybrid_store_v2.py +278 -0
- contextsynapse/storage/lmdb_graph_storage.py +366 -0
- contextsynapse/storage/migration.py +279 -0
- contextsynapse/storage/namespace_store.py +346 -0
- contextsynapse/storage/pure_graph_storage.py +185 -0
- contextsynapse/storage/redis_graph_adapter.py +186 -0
- contextsynapse/storage/redis_graph_storage.py +375 -0
- contextsynapse/storage/router/__init__.py +29 -0
- contextsynapse/storage/router/duckdb_store.py +398 -0
- contextsynapse/storage/router/factory.py +83 -0
- contextsynapse/storage/router/interface.py +125 -0
- contextsynapse/storage/router/postgres_store.py +339 -0
- contextsynapse/storage/router/resolver.py +192 -0
- contextsynapse/storage/router/router.py +300 -0
- contextsynapse/storage/single_file_storage.py +391 -0
- contextsynapse/storage/storage_manager.py +214 -0
- contextsynapse/storage/storage_query_optimizer.py +314 -0
- contextsynapse/storage/wal.py +359 -0
- contextsynapse/temporal/__init__.py +0 -0
- contextsynapse/temporal/storage.py +106 -0
- contextsynapse/tests/__init__.py +11 -0
- contextsynapse/tests/regression/__init__.py +1 -0
- contextsynapse/tests/regression/add_metadata.py +395 -0
- contextsynapse/tests/regression/add_skip_flags.py +85 -0
- contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
- contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
- contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
- contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
- contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
- contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
- contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
- contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
- contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
- contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
- contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
- contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
- contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
- contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
- contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
- contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
- contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
- contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
- contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
- contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
- contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
- contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
- contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/evaluation_metrics.py +227 -0
- contextsynapse/tests/regression/regression_runner.py +1081 -0
- contextsynapse/tests/regression/setup_regression_data.py +212 -0
- contextsynapse/tests/regression/test_basic_only.py +84 -0
- contextsynapse/tests/regression/test_namespace_creation.py +29 -0
- contextsynapse/tests/regression/test_single_fix.py +61 -0
- contextsynapse/tests/regression/test_single_query.py +20 -0
- contextsynapse/tests/regression/validate_test_syntax.py +179 -0
- contextsynapse/tests/test_advanced_features.py +347 -0
- contextsynapse/tests/test_aiql_summary.py +283 -0
- contextsynapse/tests/test_all_aiql_queries.py +948 -0
- contextsynapse/tests/test_all_create_queries.py +213 -0
- contextsynapse/tests/test_api_vs_native.py +280 -0
- contextsynapse/tests/test_chunking_dedup.py +199 -0
- contextsynapse/tests/test_connectors.py +171 -0
- contextsynapse/tests/test_edges_and_match.py +202 -0
- contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
- contextsynapse/tests/test_name_property.py +224 -0
- contextsynapse/tests/test_node_edge_metadata.py +250 -0
- contextsynapse/tests/test_playground_conn.py +114 -0
- contextsynapse/tests/test_query_cache.py +213 -0
- contextsynapse/tests/test_source_policy_auth.py +348 -0
- contextsynapse/tests/test_temporal_and_domain.py +224 -0
- contextsynapse/tests/test_time_travel_e2e.py +571 -0
- contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
- contextsynapse/tools/__init__.py +36 -0
- contextsynapse/tools/a2a_tools.py +106 -0
- contextsynapse/tools/cognition_tools.py +118 -0
- contextsynapse/tools/context.py +905 -0
- contextsynapse/tools/formatting.py +92 -0
- contextsynapse/tools/gateway_tools.py +186 -0
- contextsynapse/tools/graph.py +1700 -0
- contextsynapse/tools/intelligence.py +235 -0
- contextsynapse/tools/memory.py +118 -0
- contextsynapse/tools/registry.py +373 -0
- contextsynapse/tools/shield_tools.py +159 -0
- contextsynapse/tools/tasks.py +341 -0
- contextsynapse/tools/workspace_tools.py +124 -0
- contextsynapse/utils/__init__.py +15 -0
- contextsynapse/utils/config_loader.py +198 -0
- contextsynapse/utils/env_loader.py +156 -0
- contextsynapse/utils/file_utils.py +288 -0
- contextsynapse/utils/logging_optimizer.py +119 -0
- contextsynapse/vector/__init__.py +30 -0
- contextsynapse/vector/chroma_vector_store.py +152 -0
- contextsynapse/vector/custom_vector_store.py +537 -0
- contextsynapse/vector/faiss_vector_store.py +296 -0
- contextsynapse/vector/qdrant_vector_store.py +163 -0
- contextsynapse/vector/vector_db.py +953 -0
- contextsynapse/vector/vector_db_manager.py +197 -0
- contextsynapse/workspace/__init__.py +27 -0
- contextsynapse/workspace/base.py +94 -0
- contextsynapse/workspace/git.py +228 -0
- contextsynapse/workspace/github.py +78 -0
- contextsynapse/workspace/local.py +73 -0
- contextsynapse/workspace/tools.py +222 -0
- contextsynapse-1.0.0.dist-info/METADATA +847 -0
- contextsynapse-1.0.0.dist-info/RECORD +776 -0
- contextsynapse-1.0.0.dist-info/WHEEL +5 -0
- contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
- contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
- 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."
|