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,1195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contextual Graph Projection (CGP)
|
|
3
|
+
==================================
|
|
4
|
+
Queryless context delivery for multi-agent systems.
|
|
5
|
+
|
|
6
|
+
Instead of agents searching for knowledge, the system projects a relevant
|
|
7
|
+
subgraph from the atomic context into the agent's view — automatically,
|
|
8
|
+
based on their live work state in the runtime graph.
|
|
9
|
+
|
|
10
|
+
Three components:
|
|
11
|
+
- IntentExtractor: reads runtime graph, produces weighted intent vector
|
|
12
|
+
- SubgraphProjector: queries atomic graph, returns relevant subgraph
|
|
13
|
+
- ProjectionAssembler: formats projection for agent context window
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any, Dict, List, Optional, Set
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
_STOP_WORDS = frozenset({
|
|
26
|
+
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
|
|
27
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "shall",
|
|
28
|
+
"should", "may", "might", "can", "could", "of", "in", "to", "for",
|
|
29
|
+
"with", "on", "at", "by", "from", "as", "into", "through", "during",
|
|
30
|
+
"and", "or", "but", "not", "no", "if", "then", "than", "that", "this",
|
|
31
|
+
"it", "its", "he", "she", "his", "her", "we", "they", "them", "our",
|
|
32
|
+
"said", "also", "new", "one", "two", "who", "what", "when", "where",
|
|
33
|
+
"how", "all", "each", "every", "both", "few", "more", "most", "other",
|
|
34
|
+
"some", "such", "only", "own", "same", "so", "very", "just", "about",
|
|
35
|
+
"https", "http", "www", "com", "org", "html", "htm",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
import time as _time
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Tier Definitions ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
_TIER_DEFAULTS = {
|
|
44
|
+
"instant": {"max_tokens": 500, "max_time_ms": 50, "stages": {"cache"}},
|
|
45
|
+
"fast": {"max_tokens": 1500, "max_time_ms": 200, "stages": {"cache", "bm25"}},
|
|
46
|
+
"standard": {"max_tokens": 3000, "max_time_ms": 1000, "stages": {"cache", "bm25", "qdrant", "hops"}},
|
|
47
|
+
"deep": {"max_tokens": 8000, "max_time_ms": 5000, "stages": {"cache", "bm25", "qdrant", "hops", "signals", "extended"}},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProjectionBudget:
|
|
52
|
+
"""Tracks token and time budgets for progressive projection pipeline."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, max_tokens: int, max_time_ms: int, tier: str = "standard"):
|
|
55
|
+
self.max_tokens = max_tokens
|
|
56
|
+
self.max_time_ms = max_time_ms
|
|
57
|
+
self.tier = tier
|
|
58
|
+
self.tokens_used = 0
|
|
59
|
+
self._start = _time.monotonic()
|
|
60
|
+
self._stages = _TIER_DEFAULTS.get(tier, _TIER_DEFAULTS["standard"])["stages"]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_tier(cls, tier: str) -> "ProjectionBudget":
|
|
64
|
+
"""Create a budget from tier defaults."""
|
|
65
|
+
defaults = _TIER_DEFAULTS.get(tier, _TIER_DEFAULTS["standard"])
|
|
66
|
+
return cls(
|
|
67
|
+
max_tokens=defaults["max_tokens"],
|
|
68
|
+
max_time_ms=defaults["max_time_ms"],
|
|
69
|
+
tier=tier,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def has_remaining(self) -> bool:
|
|
73
|
+
elapsed_ms = (_time.monotonic() - self._start) * 1000
|
|
74
|
+
return self.tokens_used < self.max_tokens and elapsed_ms < self.max_time_ms
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def remaining_tokens(self) -> int:
|
|
78
|
+
return self.max_tokens - self.tokens_used
|
|
79
|
+
|
|
80
|
+
def consume_tokens(self, n: int) -> None:
|
|
81
|
+
self.tokens_used += n
|
|
82
|
+
|
|
83
|
+
def exhausted(self) -> bool:
|
|
84
|
+
return not self.has_remaining()
|
|
85
|
+
|
|
86
|
+
def should_run_stage(self, stage: str) -> bool:
|
|
87
|
+
"""Check if this tier includes the given pipeline stage."""
|
|
88
|
+
return stage in self._stages and self.has_remaining()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _extract_keywords(text: str) -> Dict[str, float]:
|
|
92
|
+
"""Extract weighted keywords from text. Returns {term: weight}."""
|
|
93
|
+
words = re.split(r"\W+", text.lower())
|
|
94
|
+
counts: Dict[str, int] = {}
|
|
95
|
+
for w in words:
|
|
96
|
+
if len(w) >= 3 and w not in _STOP_WORDS:
|
|
97
|
+
counts[w] = counts.get(w, 0) + 1
|
|
98
|
+
if not counts:
|
|
99
|
+
return {}
|
|
100
|
+
max_count = max(counts.values())
|
|
101
|
+
return {w: c / max_count for w, c in counts.items()}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _merge_keywords(base: Dict[str, float], new: Dict[str, float], weight: float) -> Dict[str, float]:
|
|
105
|
+
"""Merge new keywords into base with a source weight multiplier."""
|
|
106
|
+
merged = dict(base)
|
|
107
|
+
for k, v in new.items():
|
|
108
|
+
merged[k] = merged.get(k, 0.0) + v * weight
|
|
109
|
+
return merged
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class IntentVector:
|
|
114
|
+
"""Weighted intent extracted from an agent's runtime state."""
|
|
115
|
+
keywords: Dict[str, float] = field(default_factory=dict)
|
|
116
|
+
entity_ids: List[str] = field(default_factory=list)
|
|
117
|
+
edge_types: Set[str] = field(default_factory=set)
|
|
118
|
+
task_title: str = ""
|
|
119
|
+
agent_id: str = ""
|
|
120
|
+
confidence: float = 0.0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class IntentExtractor:
|
|
124
|
+
"""Reads the runtime graph and produces an IntentVector."""
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def extract(
|
|
128
|
+
tasks: List[Dict],
|
|
129
|
+
runtime_db: Any = None,
|
|
130
|
+
agent_name: str = "",
|
|
131
|
+
agent_id: str = "",
|
|
132
|
+
) -> IntentVector:
|
|
133
|
+
if not tasks:
|
|
134
|
+
return IntentVector(agent_id=agent_id)
|
|
135
|
+
|
|
136
|
+
task = tasks[0]
|
|
137
|
+
task_text = f"{task.get('title', '')} {task.get('description', '')}"
|
|
138
|
+
keywords = _extract_keywords(task_text)
|
|
139
|
+
confidence = 0.3
|
|
140
|
+
|
|
141
|
+
own_keywords: Dict[str, float] = {}
|
|
142
|
+
other_keywords: Dict[str, float] = {}
|
|
143
|
+
|
|
144
|
+
if runtime_db:
|
|
145
|
+
my_name = (agent_name or "").lower()
|
|
146
|
+
own_nodes = []
|
|
147
|
+
other_nodes = []
|
|
148
|
+
|
|
149
|
+
for label in ("Finding", "Insight", "AgentAction", "Decision", "Observation"):
|
|
150
|
+
try:
|
|
151
|
+
for n in runtime_db.get_all_nodes(label=label):
|
|
152
|
+
props = n.properties if hasattr(n, "properties") else {}
|
|
153
|
+
author = (
|
|
154
|
+
props.get("_agent_name", props.get("created_by", props.get("agent", ""))) or ""
|
|
155
|
+
).lower()
|
|
156
|
+
text = props.get("name", props.get("description", props.get("content", ""))) or ""
|
|
157
|
+
if not text:
|
|
158
|
+
continue
|
|
159
|
+
if author == my_name:
|
|
160
|
+
own_nodes.append(text)
|
|
161
|
+
elif author and author != "system" and not author.startswith("pipeline"):
|
|
162
|
+
other_nodes.append(text)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
for text in own_nodes[:5]:
|
|
167
|
+
own_keywords = _merge_keywords(own_keywords, _extract_keywords(text), 0.8)
|
|
168
|
+
if own_keywords:
|
|
169
|
+
keywords = _merge_keywords(keywords, own_keywords, 1.0)
|
|
170
|
+
confidence = 0.6
|
|
171
|
+
|
|
172
|
+
for text in other_nodes[:3]:
|
|
173
|
+
other_keywords = _merge_keywords(other_keywords, _extract_keywords(text), 0.4)
|
|
174
|
+
if other_keywords:
|
|
175
|
+
keywords = _merge_keywords(keywords, other_keywords, 1.0)
|
|
176
|
+
confidence = 0.8
|
|
177
|
+
|
|
178
|
+
return IntentVector(
|
|
179
|
+
keywords=keywords,
|
|
180
|
+
task_title=task.get("title", ""),
|
|
181
|
+
agent_id=agent_id,
|
|
182
|
+
confidence=confidence,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── ProjectedSubgraph ────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class ProjectedSubgraph:
|
|
190
|
+
"""Result of projecting the atomic graph through an intent vector."""
|
|
191
|
+
nodes: List[Dict] = field(default_factory=list)
|
|
192
|
+
edges: List[Dict] = field(default_factory=list)
|
|
193
|
+
seed_count: int = 0
|
|
194
|
+
hop_depth: int = 0
|
|
195
|
+
token_estimate: int = 0
|
|
196
|
+
intent_keywords: List[str] = field(default_factory=list)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
_INFRA_LABELS = frozenset({
|
|
200
|
+
"ContextMeta", "VectorIndex", "BM25Index", "Session", "ContextRef",
|
|
201
|
+
"Context", "Project", "ContextIntelligence", "CodeBase", "SystemStore",
|
|
202
|
+
"UserStore", "WebStore", "GeneratedStore", "MemoryStore", "ArtifactStore",
|
|
203
|
+
"ToolStore", "KnowledgeBase", "AgentThought", "AgentPresence",
|
|
204
|
+
"ExperimentRun", "PipelineRun",
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
_CHARS_PER_TOKEN = 4 # rough estimate for English text
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _estimate_tokens(text: str) -> int:
|
|
211
|
+
"""Rough token estimate: ~4 chars per token for English."""
|
|
212
|
+
return max(1, len(text) // _CHARS_PER_TOKEN)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _node_text(node) -> str:
|
|
216
|
+
"""Extract searchable text from a node."""
|
|
217
|
+
props = node.properties if hasattr(node, "properties") else {}
|
|
218
|
+
return " ".join(str(props.get(f, "")) for f in
|
|
219
|
+
("name", "statement", "content", "description", "topic", "claim",
|
|
220
|
+
"questions_answered", "next_clues")).lower()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _build_idf(nodes: list, keywords: Dict[str, float]) -> Dict[str, float]:
|
|
224
|
+
"""Compute IDF for keywords across the node corpus.
|
|
225
|
+
|
|
226
|
+
IDF = log(N / df) where df = number of nodes containing the term.
|
|
227
|
+
Words appearing in many nodes get downweighted.
|
|
228
|
+
"""
|
|
229
|
+
import math
|
|
230
|
+
n = max(len(nodes), 1)
|
|
231
|
+
doc_freq: Dict[str, int] = {}
|
|
232
|
+
for kw in keywords:
|
|
233
|
+
doc_freq[kw] = 0
|
|
234
|
+
for node in nodes:
|
|
235
|
+
text = _node_text(node)
|
|
236
|
+
for kw in keywords:
|
|
237
|
+
if kw in text:
|
|
238
|
+
doc_freq[kw] += 1
|
|
239
|
+
return {kw: math.log(n / max(df, 1)) + 1.0 for kw, df in doc_freq.items()}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Module-level IDF cache (recomputed when namespace changes)
|
|
243
|
+
_idf_cache: Dict[str, Dict[str, float]] = {} # namespace → {keyword → idf}
|
|
244
|
+
_idf_namespace: str = ""
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _node_keyword_score(node, keywords: Dict[str, float], idf: Optional[Dict[str, float]] = None) -> float:
|
|
248
|
+
"""Score a node against intent keywords with IDF weighting.
|
|
249
|
+
|
|
250
|
+
IDF downweights words that appear in many nodes (generic terms like
|
|
251
|
+
'module', 'service') and boosts words that appear in few (specific
|
|
252
|
+
terms like 'JWT', 'bcrypt').
|
|
253
|
+
"""
|
|
254
|
+
text = _node_text(node)
|
|
255
|
+
hits = 0
|
|
256
|
+
score = 0.0
|
|
257
|
+
max_idf_hit = 0.0
|
|
258
|
+
for kw, weight in keywords.items():
|
|
259
|
+
if kw in text:
|
|
260
|
+
idf_boost = idf.get(kw, 1.0) if idf else 1.0
|
|
261
|
+
score += weight * idf_boost
|
|
262
|
+
hits += 1
|
|
263
|
+
if idf_boost > max_idf_hit:
|
|
264
|
+
max_idf_hit = idf_boost
|
|
265
|
+
# 2+ hits: always include (multiple keyword overlap = strong match)
|
|
266
|
+
# 1 hit: only include if the matching keyword is specific (high IDF)
|
|
267
|
+
# IDF > 2.0 means the word appears in <37% of nodes — it's distinctive
|
|
268
|
+
if hits == 0:
|
|
269
|
+
return 0.0
|
|
270
|
+
if hits == 1 and max_idf_hit < 2.0:
|
|
271
|
+
return 0.0
|
|
272
|
+
return score
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _node_to_dict(node) -> Dict:
|
|
276
|
+
"""Convert a graph node to a projection dict. Keeps full content for budget-aware assembly."""
|
|
277
|
+
props = node.properties if hasattr(node, "properties") else {}
|
|
278
|
+
label = getattr(node, "node_type", None) or getattr(node, "label", None) or props.get("label", "?")
|
|
279
|
+
name = props.get("name", props.get("title", props.get("topic", "")))
|
|
280
|
+
# CUs have 'claim' as their main content; regular nodes use statement/content
|
|
281
|
+
content = props.get("claim", props.get("statement", props.get("content", props.get("description", "")))) or ""
|
|
282
|
+
# Don't repeat name in content if they're the same
|
|
283
|
+
if content == name:
|
|
284
|
+
content = ""
|
|
285
|
+
d = {
|
|
286
|
+
"id": node.id if hasattr(node, "id") else props.get("id", "?"),
|
|
287
|
+
"label": label,
|
|
288
|
+
"name": name,
|
|
289
|
+
"content": content,
|
|
290
|
+
"token_estimate": _estimate_tokens(f"{label} {name} {content}"),
|
|
291
|
+
}
|
|
292
|
+
# Preserve CU-specific fields for zoom-level rendering
|
|
293
|
+
if label == "ContextUnit":
|
|
294
|
+
d["confidence"] = props.get("confidence", 0.0)
|
|
295
|
+
d["questions_answered"] = props.get("questions_answered", [])
|
|
296
|
+
d["next_clues"] = props.get("next_clues", [])
|
|
297
|
+
d["evidence_ids"] = props.get("evidence_ids", [])
|
|
298
|
+
return d
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class SubgraphProjector:
|
|
302
|
+
"""Projects a relevant subgraph from the atomic graph based on intent."""
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def project(
|
|
306
|
+
intent: IntentVector,
|
|
307
|
+
atomic_db: Any = None,
|
|
308
|
+
namespace: str = "",
|
|
309
|
+
max_tokens: int = 2000,
|
|
310
|
+
budget: Optional["ProjectionBudget"] = None,
|
|
311
|
+
priority_labels: Optional[List[str]] = None,
|
|
312
|
+
) -> ProjectedSubgraph:
|
|
313
|
+
if not intent.keywords or intent.confidence == 0.0 or atomic_db is None:
|
|
314
|
+
return ProjectedSubgraph()
|
|
315
|
+
|
|
316
|
+
if budget is None:
|
|
317
|
+
budget = ProjectionBudget(max_tokens=max_tokens, max_time_ms=10000, tier="deep")
|
|
318
|
+
|
|
319
|
+
adapter = getattr(atomic_db, "csr_adapter", None) or atomic_db
|
|
320
|
+
top_keywords = sorted(intent.keywords.items(), key=lambda x: -x[1])[:10]
|
|
321
|
+
|
|
322
|
+
seeds: List = []
|
|
323
|
+
seed_scores: Dict[str, float] = {}
|
|
324
|
+
idf: Optional[Dict[str, float]] = None
|
|
325
|
+
used_index = False
|
|
326
|
+
|
|
327
|
+
# Step 1a: LMDB BM25 — O(K) index lookup, not O(N) scan
|
|
328
|
+
if budget.should_run_stage("bm25"):
|
|
329
|
+
try:
|
|
330
|
+
import os
|
|
331
|
+
idx_path = f"contextcore_data/lmdb_index/{namespace}"
|
|
332
|
+
if os.path.exists(idx_path):
|
|
333
|
+
from ..search.lmdb_index import get_lmdb_index
|
|
334
|
+
lmdb_idx = get_lmdb_index(namespace)
|
|
335
|
+
idx_stats = lmdb_idx.stats()
|
|
336
|
+
if idx_stats.get("nodes", 0) > 0:
|
|
337
|
+
used_index = True
|
|
338
|
+
# Get IDF directly from index — O(K) per keyword
|
|
339
|
+
idf = lmdb_idx.get_idf(list(intent.keywords.keys()))
|
|
340
|
+
# BM25 search — O(K) postings lookup
|
|
341
|
+
query = " ".join(kw for kw, _ in top_keywords[:5])
|
|
342
|
+
lmdb_results = lmdb_idx.search(query, limit=50)
|
|
343
|
+
for r in lmdb_results:
|
|
344
|
+
nid = r.get("node_id", "") # LMDB returns "node_id", not "id"
|
|
345
|
+
if nid and r.get("label", "") not in _INFRA_LABELS:
|
|
346
|
+
node = adapter.get_node(nid) if hasattr(adapter, "get_node") else None
|
|
347
|
+
if node:
|
|
348
|
+
score = _node_keyword_score(node, intent.keywords, idf=idf)
|
|
349
|
+
if score > 0:
|
|
350
|
+
seeds.append(node)
|
|
351
|
+
seed_scores[nid] = score
|
|
352
|
+
except Exception as _e:
|
|
353
|
+
logger.warning("[CGP] LMDB BM25 seed retrieval failed for ns=%s: %s", namespace, _e)
|
|
354
|
+
|
|
355
|
+
# Step 1b: Qdrant vectors — O(K) ANN search, not O(N)
|
|
356
|
+
if budget.should_run_stage("qdrant"):
|
|
357
|
+
try:
|
|
358
|
+
from ..context.vector_integration import get_session_vector_store
|
|
359
|
+
svs = get_session_vector_store()
|
|
360
|
+
if svs.available and svs.has_vectors(namespace):
|
|
361
|
+
used_index = True
|
|
362
|
+
query_text = intent.task_title or " ".join(kw for kw, _ in top_keywords[:5])
|
|
363
|
+
vec_results = svs.search(namespace, query_text, k=30)
|
|
364
|
+
for r in vec_results:
|
|
365
|
+
nid = r.get("node_id", "")
|
|
366
|
+
if nid and nid not in seed_scores:
|
|
367
|
+
meta = r.get("metadata", {})
|
|
368
|
+
if meta.get("label", "") not in _INFRA_LABELS:
|
|
369
|
+
node = adapter.get_node(nid) if hasattr(adapter, "get_node") else None
|
|
370
|
+
if node:
|
|
371
|
+
vec_score = r.get("score", 0.5) * 2.0
|
|
372
|
+
seeds.append(node)
|
|
373
|
+
seed_scores[nid] = vec_score
|
|
374
|
+
except Exception as _e:
|
|
375
|
+
logger.warning("[CGP] Qdrant vector seed retrieval failed for ns=%s: %s", namespace, _e)
|
|
376
|
+
|
|
377
|
+
# Step 1c: Full scan fallback — ONLY when no index available (small/test graphs)
|
|
378
|
+
content_nodes: list = [] # defined here so step 1d can reference it
|
|
379
|
+
if "bm25" in budget._stages and (not used_index or len(seeds) < 3):
|
|
380
|
+
try:
|
|
381
|
+
all_nodes = list(adapter.get_all_nodes()) if hasattr(adapter, "get_all_nodes") else []
|
|
382
|
+
content_nodes = [n for n in all_nodes
|
|
383
|
+
if (getattr(n, "node_type", None) or getattr(n, "label", "")) not in _INFRA_LABELS]
|
|
384
|
+
# Compute IDF from full scan only if index didn't provide it
|
|
385
|
+
if not idf and content_nodes:
|
|
386
|
+
idf = _build_idf(content_nodes, intent.keywords)
|
|
387
|
+
for n in content_nodes:
|
|
388
|
+
nid = n.id if hasattr(n, "id") else ""
|
|
389
|
+
if nid in seed_scores:
|
|
390
|
+
continue
|
|
391
|
+
score = _node_keyword_score(n, intent.keywords, idf=idf)
|
|
392
|
+
if score > 0:
|
|
393
|
+
seeds.append(n)
|
|
394
|
+
seed_scores[nid] = score
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
397
|
+
|
|
398
|
+
# Step 1d: ContextUnit retrieval — pre-assembled intelligence objects
|
|
399
|
+
# CUs are the richest nodes (bundled facts + claim + actors). Prioritize them.
|
|
400
|
+
cu_nodes = []
|
|
401
|
+
try:
|
|
402
|
+
for n in content_nodes:
|
|
403
|
+
nt = getattr(n, "node_type", None) or getattr(n, "label", "")
|
|
404
|
+
if nt != "ContextUnit":
|
|
405
|
+
continue
|
|
406
|
+
nid = n.id if hasattr(n, "id") else ""
|
|
407
|
+
if nid in seed_scores:
|
|
408
|
+
continue
|
|
409
|
+
score = _node_keyword_score(n, intent.keywords, idf=idf)
|
|
410
|
+
if score > 0:
|
|
411
|
+
cu_nodes.append(n)
|
|
412
|
+
seed_scores[nid] = score * 1.5 # boost CUs — they're pre-synthesized
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
# CUs go first, then regular seeds
|
|
417
|
+
seeds = cu_nodes + seeds
|
|
418
|
+
|
|
419
|
+
if not seeds:
|
|
420
|
+
return ProjectedSubgraph(intent_keywords=[kw for kw, _ in top_keywords])
|
|
421
|
+
|
|
422
|
+
# Apply schema priority label boosts — domain-critical node types always surface
|
|
423
|
+
if priority_labels:
|
|
424
|
+
_priority_set = set(priority_labels)
|
|
425
|
+
for seed in seeds:
|
|
426
|
+
nid = seed.id if hasattr(seed, "id") else ""
|
|
427
|
+
label = getattr(seed, "node_type", None) or getattr(seed, "label", "")
|
|
428
|
+
if label in _priority_set and nid in seed_scores:
|
|
429
|
+
seed_scores[nid] *= 1.8 # strong boost for domain-priority types
|
|
430
|
+
|
|
431
|
+
# Apply feedback boosts — nodes that were useful in past projections rank higher
|
|
432
|
+
for seed in seeds:
|
|
433
|
+
nid = seed.id if hasattr(seed, "id") else ""
|
|
434
|
+
if nid in seed_scores:
|
|
435
|
+
seed_scores[nid] *= get_feedback_boost(namespace, nid)
|
|
436
|
+
|
|
437
|
+
# Sort seeds by score, add within budget (using actual token estimates)
|
|
438
|
+
seeds.sort(key=lambda n: seed_scores.get(n.id if hasattr(n, "id") else "", 0), reverse=True)
|
|
439
|
+
|
|
440
|
+
projected_nodes = []
|
|
441
|
+
projected_ids = set()
|
|
442
|
+
tokens_used = 0
|
|
443
|
+
|
|
444
|
+
for seed in seeds:
|
|
445
|
+
nd = _node_to_dict(seed)
|
|
446
|
+
if tokens_used + nd["token_estimate"] > budget.max_tokens:
|
|
447
|
+
break
|
|
448
|
+
projected_nodes.append(nd)
|
|
449
|
+
projected_ids.add(nd["id"])
|
|
450
|
+
tokens_used += nd["token_estimate"]
|
|
451
|
+
budget.consume_tokens(nd["token_estimate"])
|
|
452
|
+
|
|
453
|
+
seed_count = len(projected_nodes)
|
|
454
|
+
hop_depth = 0
|
|
455
|
+
|
|
456
|
+
# Step 2: Adaptive hop expansion
|
|
457
|
+
if budget.should_run_stage("hops") and budget.has_remaining() and budget.remaining_tokens > 200 and hasattr(adapter, "get_neighbors"):
|
|
458
|
+
hop_depth = 1
|
|
459
|
+
neighbors = []
|
|
460
|
+
for seed in seeds[:5]:
|
|
461
|
+
if budget.exhausted():
|
|
462
|
+
break
|
|
463
|
+
sid = seed.id if hasattr(seed, "id") else ""
|
|
464
|
+
try:
|
|
465
|
+
for neighbor in adapter.get_neighbors(sid):
|
|
466
|
+
nid = neighbor.id if hasattr(neighbor, "id") else ""
|
|
467
|
+
label = getattr(neighbor, "node_type", None) or getattr(neighbor, "label", "")
|
|
468
|
+
if nid not in projected_ids and label not in _INFRA_LABELS:
|
|
469
|
+
neighbors.append(neighbor)
|
|
470
|
+
projected_ids.add(nid)
|
|
471
|
+
except Exception:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
for neighbor in neighbors:
|
|
475
|
+
nd = _node_to_dict(neighbor)
|
|
476
|
+
if tokens_used + nd["token_estimate"] > budget.max_tokens:
|
|
477
|
+
break
|
|
478
|
+
projected_nodes.append(nd)
|
|
479
|
+
tokens_used += nd["token_estimate"]
|
|
480
|
+
budget.consume_tokens(nd["token_estimate"])
|
|
481
|
+
|
|
482
|
+
remaining = budget.remaining_tokens
|
|
483
|
+
|
|
484
|
+
if remaining > 500 and len(seeds) >= 2 and budget.has_remaining():
|
|
485
|
+
hop_depth = 2
|
|
486
|
+
hop2_neighbors = []
|
|
487
|
+
for neighbor in neighbors[:3]:
|
|
488
|
+
if budget.exhausted():
|
|
489
|
+
break
|
|
490
|
+
nid = neighbor.id if hasattr(neighbor, "id") else ""
|
|
491
|
+
try:
|
|
492
|
+
for n2 in adapter.get_neighbors(nid):
|
|
493
|
+
n2id = n2.id if hasattr(n2, "id") else ""
|
|
494
|
+
label = getattr(n2, "node_type", None) or getattr(n2, "label", "")
|
|
495
|
+
if n2id not in projected_ids and label not in _INFRA_LABELS:
|
|
496
|
+
hop2_neighbors.append(n2)
|
|
497
|
+
projected_ids.add(n2id)
|
|
498
|
+
except Exception:
|
|
499
|
+
pass
|
|
500
|
+
for n2 in hop2_neighbors:
|
|
501
|
+
nd = _node_to_dict(n2)
|
|
502
|
+
if tokens_used + nd["token_estimate"] > budget.max_tokens:
|
|
503
|
+
break
|
|
504
|
+
projected_nodes.append(nd)
|
|
505
|
+
tokens_used += nd["token_estimate"]
|
|
506
|
+
budget.consume_tokens(nd["token_estimate"])
|
|
507
|
+
|
|
508
|
+
# Step 3: Collect edges between projected nodes
|
|
509
|
+
# O(projected × degree) via adjacency, not O(E) full scan
|
|
510
|
+
projected_edges = []
|
|
511
|
+
try:
|
|
512
|
+
if hasattr(adapter, "get_neighbors"):
|
|
513
|
+
# Fast path: use adjacency dict — O(projected_nodes × avg_degree)
|
|
514
|
+
seen_edges = set()
|
|
515
|
+
for nid in projected_ids:
|
|
516
|
+
try:
|
|
517
|
+
for neighbor_id, edge in adapter.get_neighbors(nid):
|
|
518
|
+
if neighbor_id in projected_ids:
|
|
519
|
+
eid = getattr(edge, "id", None) or f"{nid}-{neighbor_id}"
|
|
520
|
+
if eid not in seen_edges:
|
|
521
|
+
seen_edges.add(eid)
|
|
522
|
+
projected_edges.append({
|
|
523
|
+
"source": nid,
|
|
524
|
+
"target": neighbor_id,
|
|
525
|
+
"label": getattr(edge, "label", "RELATED"),
|
|
526
|
+
})
|
|
527
|
+
except Exception:
|
|
528
|
+
pass
|
|
529
|
+
else:
|
|
530
|
+
# Fallback: full scan (only for adapters without get_neighbors)
|
|
531
|
+
all_edges = adapter.get_all_edges() if hasattr(adapter, "get_all_edges") else []
|
|
532
|
+
for e in all_edges:
|
|
533
|
+
src = str(getattr(e, "source", getattr(e, "from_id", "")))
|
|
534
|
+
tgt = str(getattr(e, "target", getattr(e, "to_id", "")))
|
|
535
|
+
if src in projected_ids and tgt in projected_ids:
|
|
536
|
+
projected_edges.append({
|
|
537
|
+
"source": src,
|
|
538
|
+
"target": tgt,
|
|
539
|
+
"label": getattr(e, "label", "RELATED"),
|
|
540
|
+
})
|
|
541
|
+
except Exception:
|
|
542
|
+
pass
|
|
543
|
+
|
|
544
|
+
return ProjectedSubgraph(
|
|
545
|
+
nodes=projected_nodes,
|
|
546
|
+
edges=projected_edges,
|
|
547
|
+
seed_count=seed_count,
|
|
548
|
+
hop_depth=hop_depth,
|
|
549
|
+
token_estimate=tokens_used,
|
|
550
|
+
intent_keywords=[kw for kw, _ in top_keywords],
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class ProjectionAssembler:
|
|
555
|
+
"""Formats a ProjectedSubgraph into agent-readable context."""
|
|
556
|
+
|
|
557
|
+
# Tier → CU zoom level mapping
|
|
558
|
+
TIER_ZOOM = {
|
|
559
|
+
"instant": "headline",
|
|
560
|
+
"fast": "headline",
|
|
561
|
+
"standard": "summary",
|
|
562
|
+
"deep": "full",
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
@staticmethod
|
|
566
|
+
def assemble(
|
|
567
|
+
subgraph: ProjectedSubgraph,
|
|
568
|
+
task_title: str = "",
|
|
569
|
+
total_nodes: int = 0,
|
|
570
|
+
cross_agent_signals: Optional[List[Dict]] = None,
|
|
571
|
+
tier: str = "standard",
|
|
572
|
+
cu_config: Any = None,
|
|
573
|
+
) -> str:
|
|
574
|
+
if not subgraph.nodes:
|
|
575
|
+
kw_str = ", ".join(subgraph.intent_keywords[:5]) if subgraph.intent_keywords else "none"
|
|
576
|
+
return f"No relevant context found for task: \"{task_title}\" (keywords: {kw_str})"
|
|
577
|
+
|
|
578
|
+
lines = []
|
|
579
|
+
|
|
580
|
+
kw_display = ", ".join(subgraph.intent_keywords[:5])
|
|
581
|
+
lines.append(f"PROJECTED CONTEXT (for task: \"{task_title}\")")
|
|
582
|
+
lines.append(f"Based on: {kw_display} | {subgraph.seed_count} direct matches")
|
|
583
|
+
lines.append("")
|
|
584
|
+
|
|
585
|
+
direct = subgraph.nodes[:subgraph.seed_count]
|
|
586
|
+
connected = subgraph.nodes[subgraph.seed_count:]
|
|
587
|
+
|
|
588
|
+
# Resolve CU zoom level from tier
|
|
589
|
+
zoom = ProjectionAssembler.TIER_ZOOM.get(tier, "summary")
|
|
590
|
+
|
|
591
|
+
if direct:
|
|
592
|
+
lines.append("DIRECT MATCHES:")
|
|
593
|
+
for n in direct:
|
|
594
|
+
content = n.get("content", "")
|
|
595
|
+
if n.get("label") == "ContextUnit":
|
|
596
|
+
# ContextUnit: render at tier-appropriate zoom level
|
|
597
|
+
from ..context.context_units import format_cu_for_agent
|
|
598
|
+
cu_dict = {
|
|
599
|
+
"properties": {
|
|
600
|
+
"topic": n.get("name", ""),
|
|
601
|
+
"claim": content,
|
|
602
|
+
"confidence": n.get("confidence", 0.0),
|
|
603
|
+
"questions_answered": n.get("questions_answered", []),
|
|
604
|
+
"next_clues": n.get("next_clues", []),
|
|
605
|
+
},
|
|
606
|
+
"evidence_ids": n.get("evidence_ids", []),
|
|
607
|
+
}
|
|
608
|
+
formatted = format_cu_for_agent(cu_dict, zoom=zoom, cu_config=cu_config)
|
|
609
|
+
for fl in formatted.split("\n"):
|
|
610
|
+
lines.append(f" {fl}")
|
|
611
|
+
elif content and len(content) > 200:
|
|
612
|
+
# Passage-sized content: show full text
|
|
613
|
+
lines.append(f" [{n['label']}] {n['name']}")
|
|
614
|
+
lines.append(f" {content}")
|
|
615
|
+
elif content:
|
|
616
|
+
lines.append(f" [{n['label']}] {n['name']} — {content}")
|
|
617
|
+
else:
|
|
618
|
+
lines.append(f" [{n['label']}] {n['name']}")
|
|
619
|
+
|
|
620
|
+
if connected:
|
|
621
|
+
lines.append("")
|
|
622
|
+
lines.append("CONNECTED CONTEXT:")
|
|
623
|
+
for n in connected:
|
|
624
|
+
edge_label = ""
|
|
625
|
+
for e in subgraph.edges:
|
|
626
|
+
if e["target"] == n["id"] or e["source"] == n["id"]:
|
|
627
|
+
edge_label = f" -[{e['label']}]->"
|
|
628
|
+
break
|
|
629
|
+
content = n.get("content", "")
|
|
630
|
+
if content and len(content) > 200:
|
|
631
|
+
lines.append(f" [{n['label']}] {n['name']}{edge_label}")
|
|
632
|
+
lines.append(f" {content[:500]}")
|
|
633
|
+
elif content:
|
|
634
|
+
lines.append(f" [{n['label']}] {n['name']}{edge_label} — {content[:200]}")
|
|
635
|
+
else:
|
|
636
|
+
lines.append(f" [{n['label']}] {n['name']}{edge_label}")
|
|
637
|
+
|
|
638
|
+
if cross_agent_signals:
|
|
639
|
+
lines.append("")
|
|
640
|
+
lines.append("CROSS-AGENT SIGNALS:")
|
|
641
|
+
for sig in cross_agent_signals[:5]:
|
|
642
|
+
lines.append(f" [{sig.get('agent', '?')}] ({sig.get('label', '?')}) {sig.get('name', '')[:80]}")
|
|
643
|
+
|
|
644
|
+
pct = f"{len(subgraph.nodes) / total_nodes * 100:.1f}%" if total_nodes > 0 else "?"
|
|
645
|
+
lines.append("")
|
|
646
|
+
lines.append(
|
|
647
|
+
f"{len(subgraph.nodes)} nodes projected from {total_nodes} total ({pct}) "
|
|
648
|
+
f"| Budget: {subgraph.token_estimate:,} tokens"
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
return "\n".join(lines)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# ── Top-level orchestrator ───────────────────────────────────────────
|
|
655
|
+
|
|
656
|
+
def project_context(ctx: Any, max_tokens: int = 2000,
|
|
657
|
+
max_time_ms: int = 1000, tier: str = "standard",
|
|
658
|
+
schema: Any = None) -> Optional[str]:
|
|
659
|
+
"""Top-level orchestrator: extract intent -> project subgraph -> assemble.
|
|
660
|
+
|
|
661
|
+
Called by orient() when the agent has tasks. Returns formatted projection
|
|
662
|
+
string, or None if no tasks / projection not applicable.
|
|
663
|
+
Accepts an optional ExtractionSchema whose context_unit config drives
|
|
664
|
+
domain-aware priority labels in the projection.
|
|
665
|
+
"""
|
|
666
|
+
budget = ProjectionBudget(max_tokens=max_tokens, max_time_ms=max_time_ms, tier=tier)
|
|
667
|
+
|
|
668
|
+
tasks = []
|
|
669
|
+
try:
|
|
670
|
+
if ctx.project:
|
|
671
|
+
tasks = ctx.project.get_open_tasks(
|
|
672
|
+
agent_id=getattr(ctx, "agent_id", ""),
|
|
673
|
+
agent_name=getattr(ctx, "agent_name", ""),
|
|
674
|
+
)
|
|
675
|
+
except Exception:
|
|
676
|
+
pass
|
|
677
|
+
|
|
678
|
+
if not tasks:
|
|
679
|
+
return None
|
|
680
|
+
|
|
681
|
+
# Check cache first (60s TTL) — tier-aware
|
|
682
|
+
agent_id = getattr(ctx, "agent_id", "")
|
|
683
|
+
namespace = getattr(ctx.conn, "namespace", "") or getattr(ctx.conn, "_namespace", "")
|
|
684
|
+
if budget.should_run_stage("cache"):
|
|
685
|
+
cached = get_cached_projection(namespace, agent_id)
|
|
686
|
+
if cached:
|
|
687
|
+
return cached
|
|
688
|
+
if budget.tier == "instant":
|
|
689
|
+
return None
|
|
690
|
+
|
|
691
|
+
# 1. Extract intent from runtime graph
|
|
692
|
+
runtime_db = None
|
|
693
|
+
if hasattr(ctx, "runtime_conn") and ctx.runtime_conn:
|
|
694
|
+
runtime_db = getattr(ctx.runtime_conn, "db", None) or getattr(ctx.runtime_conn, "contextsynapse", None)
|
|
695
|
+
|
|
696
|
+
intent = IntentExtractor.extract(
|
|
697
|
+
tasks=tasks,
|
|
698
|
+
runtime_db=runtime_db,
|
|
699
|
+
agent_name=getattr(ctx, "agent_name", ""),
|
|
700
|
+
agent_id=getattr(ctx, "agent_id", ""),
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
if intent.confidence == 0.0:
|
|
704
|
+
return None
|
|
705
|
+
|
|
706
|
+
# 2. Project subgraph from atomic graph
|
|
707
|
+
atomic_db = getattr(ctx.conn, "db", None) or getattr(ctx.conn, "contextsynapse", None)
|
|
708
|
+
namespace = getattr(ctx.conn, "namespace", "") or getattr(ctx.conn, "_namespace", "")
|
|
709
|
+
|
|
710
|
+
# Extract priority labels from schema's context_unit config (if available)
|
|
711
|
+
_priority_labels = None
|
|
712
|
+
if schema is not None:
|
|
713
|
+
cu_cfg = getattr(schema, "context_unit", None)
|
|
714
|
+
if cu_cfg is not None:
|
|
715
|
+
_priority_labels = getattr(cu_cfg, "priority_labels", None)
|
|
716
|
+
|
|
717
|
+
subgraph = SubgraphProjector.project(
|
|
718
|
+
intent=intent,
|
|
719
|
+
atomic_db=atomic_db,
|
|
720
|
+
namespace=namespace,
|
|
721
|
+
budget=budget,
|
|
722
|
+
priority_labels=_priority_labels,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
# 3. Collect cross-agent signals — scored by relevance to THIS agent's task
|
|
726
|
+
cross_signals = []
|
|
727
|
+
if runtime_db and ("signals" in budget._stages or "hops" in budget._stages):
|
|
728
|
+
my_name = (getattr(ctx, "agent_name", "") or "").lower()
|
|
729
|
+
candidates = []
|
|
730
|
+
for label in ("Finding", "Insight", "Decision"):
|
|
731
|
+
try:
|
|
732
|
+
for n in runtime_db.get_all_nodes(label=label):
|
|
733
|
+
props = n.properties if hasattr(n, "properties") else {}
|
|
734
|
+
author = (props.get("_agent_name", props.get("created_by", "")) or "").lower()
|
|
735
|
+
if author and author != my_name and author != "system":
|
|
736
|
+
name = (props.get("name", props.get("content", "")) or "")[:80]
|
|
737
|
+
if name:
|
|
738
|
+
# Score by keyword overlap with this agent's intent
|
|
739
|
+
sig_kws = _extract_keywords(name)
|
|
740
|
+
overlap = sum(1 for k in sig_kws if k in intent.keywords)
|
|
741
|
+
candidates.append({
|
|
742
|
+
"agent": author, "label": label,
|
|
743
|
+
"name": name, "_relevance": overlap,
|
|
744
|
+
})
|
|
745
|
+
except Exception:
|
|
746
|
+
pass
|
|
747
|
+
# Take top 5 most relevant signals (not all 99 in a 100-agent session)
|
|
748
|
+
candidates.sort(key=lambda x: x["_relevance"], reverse=True)
|
|
749
|
+
cross_signals = candidates[:5]
|
|
750
|
+
|
|
751
|
+
# 4. Count total nodes in atomic graph for stats
|
|
752
|
+
total_nodes = 0
|
|
753
|
+
try:
|
|
754
|
+
adapter = getattr(atomic_db, "csr_adapter", None) or atomic_db
|
|
755
|
+
if hasattr(adapter, "get_all_nodes"):
|
|
756
|
+
total_nodes = len(list(adapter.get_all_nodes()))
|
|
757
|
+
except Exception:
|
|
758
|
+
pass
|
|
759
|
+
|
|
760
|
+
# 5. Assemble
|
|
761
|
+
# Extract cu_config for zoom-level rendering
|
|
762
|
+
_cu_config = None
|
|
763
|
+
if schema is not None:
|
|
764
|
+
_cu_config = getattr(schema, "context_unit", None)
|
|
765
|
+
|
|
766
|
+
projection = ProjectionAssembler.assemble(
|
|
767
|
+
subgraph=subgraph,
|
|
768
|
+
task_title=tasks[0].get("title", ""),
|
|
769
|
+
total_nodes=total_nodes,
|
|
770
|
+
cross_agent_signals=cross_signals[:5],
|
|
771
|
+
tier=tier,
|
|
772
|
+
cu_config=_cu_config,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
# 6. Prepend task list
|
|
776
|
+
task_lines = ["YOUR TASKS:"]
|
|
777
|
+
for t in tasks[:5]:
|
|
778
|
+
task_lines.append(f" [{t['priority'].upper()}] {t['title']} (id: {t['id']})")
|
|
779
|
+
|
|
780
|
+
# 7. Record what was projected (for feedback learning)
|
|
781
|
+
try:
|
|
782
|
+
_record_projection(
|
|
783
|
+
namespace=namespace,
|
|
784
|
+
agent_id=getattr(ctx, "agent_id", ""),
|
|
785
|
+
task_keywords=list(intent.keywords.keys())[:5],
|
|
786
|
+
projected_ids=[n["id"] for n in subgraph.nodes],
|
|
787
|
+
)
|
|
788
|
+
except Exception:
|
|
789
|
+
pass
|
|
790
|
+
|
|
791
|
+
# 8. Projection diff — show what changed since last orient
|
|
792
|
+
diff_section = ""
|
|
793
|
+
try:
|
|
794
|
+
prev_log = _projection_log.get(agent_id)
|
|
795
|
+
if prev_log and prev_log.get("projected_ids"):
|
|
796
|
+
prev_ids = prev_log["projected_ids"]
|
|
797
|
+
curr_ids = {n["id"] for n in subgraph.nodes}
|
|
798
|
+
new_ids = curr_ids - prev_ids
|
|
799
|
+
dropped_ids = prev_ids - curr_ids
|
|
800
|
+
if new_ids or dropped_ids:
|
|
801
|
+
diff_lines = []
|
|
802
|
+
if new_ids:
|
|
803
|
+
new_names = [n["name"] for n in subgraph.nodes if n["id"] in new_ids]
|
|
804
|
+
diff_lines.append(f" + {len(new_ids)} new: {', '.join(new_names[:3])}")
|
|
805
|
+
if dropped_ids:
|
|
806
|
+
diff_lines.append(f" - {len(dropped_ids)} no longer relevant")
|
|
807
|
+
diff_section = "\nCHANGED SINCE LAST ORIENT:\n" + "\n".join(diff_lines)
|
|
808
|
+
except Exception:
|
|
809
|
+
pass
|
|
810
|
+
|
|
811
|
+
# 9. Intent drift detection — are agent's actions diverging from task?
|
|
812
|
+
drift_section = ""
|
|
813
|
+
try:
|
|
814
|
+
if "hops" in budget._stages and intent.confidence >= 0.6: # only check when we have action signals
|
|
815
|
+
task_kws = set(_extract_keywords(tasks[0].get("title", "")).keys())
|
|
816
|
+
action_kws = set()
|
|
817
|
+
if runtime_db:
|
|
818
|
+
my_name = (getattr(ctx, "agent_name", "") or "").lower()
|
|
819
|
+
for label in ("Finding", "AgentAction"):
|
|
820
|
+
try:
|
|
821
|
+
for n in runtime_db.get_all_nodes(label=label):
|
|
822
|
+
props = n.properties if hasattr(n, "properties") else {}
|
|
823
|
+
author = (props.get("_agent_name", props.get("agent", "")) or "").lower()
|
|
824
|
+
if author == my_name:
|
|
825
|
+
text = props.get("name", props.get("description", "")) or ""
|
|
826
|
+
action_kws.update(_extract_keywords(text).keys())
|
|
827
|
+
except Exception:
|
|
828
|
+
pass
|
|
829
|
+
|
|
830
|
+
if action_kws and task_kws:
|
|
831
|
+
overlap = task_kws & action_kws
|
|
832
|
+
drift_kws = action_kws - task_kws - _STOP_WORDS
|
|
833
|
+
if len(drift_kws) > len(overlap) * 2 and len(drift_kws) > 3:
|
|
834
|
+
drift_sample = list(drift_kws)[:5]
|
|
835
|
+
drift_section = (
|
|
836
|
+
f"\nDRIFT DETECTED: Your recent work focuses on "
|
|
837
|
+
f"[{', '.join(drift_sample)}] which diverges from your task. "
|
|
838
|
+
f"Consider updating your task or refocusing."
|
|
839
|
+
)
|
|
840
|
+
except Exception:
|
|
841
|
+
pass
|
|
842
|
+
|
|
843
|
+
result = "\n".join(task_lines) + "\n\n" + projection
|
|
844
|
+
if diff_section:
|
|
845
|
+
result += diff_section
|
|
846
|
+
if drift_section:
|
|
847
|
+
result += drift_section
|
|
848
|
+
|
|
849
|
+
# Cache for 60s
|
|
850
|
+
try:
|
|
851
|
+
cache_projection(namespace, agent_id, result)
|
|
852
|
+
except Exception:
|
|
853
|
+
pass
|
|
854
|
+
|
|
855
|
+
return result
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
# ── Redis Helper ─────────────────────────────────────────────────────
|
|
859
|
+
|
|
860
|
+
def _get_redis():
|
|
861
|
+
"""Get Redis client, or None if unavailable."""
|
|
862
|
+
try:
|
|
863
|
+
import os, redis
|
|
864
|
+
url = os.environ.get("CONTEXTSYNAPSE_REDIS_URL") or os.environ.get("AICONTEXTDB_REDIS_URL")
|
|
865
|
+
if url:
|
|
866
|
+
client = redis.Redis.from_url(url, decode_responses=True)
|
|
867
|
+
client.ping()
|
|
868
|
+
return client
|
|
869
|
+
except Exception:
|
|
870
|
+
pass
|
|
871
|
+
return None
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
# ── Projection Cache ─────────────────────────────────────────────────
|
|
875
|
+
|
|
876
|
+
_CACHE_TTL = 60 # seconds
|
|
877
|
+
|
|
878
|
+
# In-memory fallback cache — used when Redis is unavailable (e.g. test environments)
|
|
879
|
+
_mem_projection_cache: Dict[str, tuple] = {} # key -> (expiry_ts, result)
|
|
880
|
+
|
|
881
|
+
def _cache_key(namespace: str, agent_id: str) -> str:
|
|
882
|
+
return f"cgp:cache:{namespace}:{agent_id}"
|
|
883
|
+
|
|
884
|
+
def cache_projection(namespace: str, agent_id: str, result: str) -> None:
|
|
885
|
+
"""Cache a projection result in Redis (60s TTL) and in-memory (always)."""
|
|
886
|
+
import time as _t
|
|
887
|
+
key = _cache_key(namespace, agent_id)
|
|
888
|
+
# Always populate in-memory cache — ensures warm calls are fast even without Redis
|
|
889
|
+
_mem_projection_cache[key] = (_t.monotonic() + _CACHE_TTL, result)
|
|
890
|
+
r = _get_redis()
|
|
891
|
+
if r:
|
|
892
|
+
try:
|
|
893
|
+
r.set(key, result, ex=_CACHE_TTL)
|
|
894
|
+
except Exception:
|
|
895
|
+
pass
|
|
896
|
+
|
|
897
|
+
def get_cached_projection(namespace: str, agent_id: str) -> Optional[str]:
|
|
898
|
+
"""Get cached projection, or None if expired/missing."""
|
|
899
|
+
import time as _t
|
|
900
|
+
key = _cache_key(namespace, agent_id)
|
|
901
|
+
# Check in-memory first (zero-latency, always consistent within this process)
|
|
902
|
+
entry = _mem_projection_cache.get(key)
|
|
903
|
+
if entry is not None:
|
|
904
|
+
expiry, result = entry
|
|
905
|
+
if _t.monotonic() < expiry:
|
|
906
|
+
return result
|
|
907
|
+
del _mem_projection_cache[key]
|
|
908
|
+
# Fall back to Redis (shared across workers)
|
|
909
|
+
r = _get_redis()
|
|
910
|
+
if r:
|
|
911
|
+
try:
|
|
912
|
+
val = r.get(key)
|
|
913
|
+
if val is not None:
|
|
914
|
+
return val
|
|
915
|
+
except Exception:
|
|
916
|
+
pass
|
|
917
|
+
return None
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def invalidate_projection_cache(namespace: str, agent_id: Optional[str] = None) -> int:
|
|
921
|
+
"""Invalidate projection cache for a namespace.
|
|
922
|
+
|
|
923
|
+
If agent_id is given, clears only that agent's cache entry.
|
|
924
|
+
If agent_id is None, clears all agents' entries for this namespace.
|
|
925
|
+
|
|
926
|
+
Returns the number of entries cleared.
|
|
927
|
+
"""
|
|
928
|
+
import logging as _log
|
|
929
|
+
_logger = _log.getLogger(__name__)
|
|
930
|
+
count = 0
|
|
931
|
+
prefix = _cache_key(namespace, "") # "cgp:cache:{namespace}:"
|
|
932
|
+
|
|
933
|
+
# Clear in-memory cache entries
|
|
934
|
+
to_remove = []
|
|
935
|
+
for key in list(_mem_projection_cache.keys()):
|
|
936
|
+
if agent_id is None:
|
|
937
|
+
if key.startswith(prefix):
|
|
938
|
+
to_remove.append(key)
|
|
939
|
+
else:
|
|
940
|
+
if key == _cache_key(namespace, agent_id):
|
|
941
|
+
to_remove.append(key)
|
|
942
|
+
for key in to_remove:
|
|
943
|
+
_mem_projection_cache.pop(key, None)
|
|
944
|
+
count += 1
|
|
945
|
+
|
|
946
|
+
# Clear Redis cache entries
|
|
947
|
+
r = _get_redis()
|
|
948
|
+
if r:
|
|
949
|
+
try:
|
|
950
|
+
if agent_id is None:
|
|
951
|
+
pattern = f"{prefix}*"
|
|
952
|
+
cursor = 0
|
|
953
|
+
while True:
|
|
954
|
+
cursor, keys = r.scan(cursor, match=pattern, count=100)
|
|
955
|
+
if keys:
|
|
956
|
+
r.delete(*keys)
|
|
957
|
+
count += len(keys)
|
|
958
|
+
if cursor == 0:
|
|
959
|
+
break
|
|
960
|
+
else:
|
|
961
|
+
key = _cache_key(namespace, agent_id)
|
|
962
|
+
deleted = r.delete(key)
|
|
963
|
+
count += deleted
|
|
964
|
+
except Exception as _e:
|
|
965
|
+
_logger.debug("Redis invalidation error: %s", _e)
|
|
966
|
+
|
|
967
|
+
return count
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
# ── Projection Feedback Loop (Redis-backed) ──────────────────────────
|
|
971
|
+
|
|
972
|
+
# In-memory fallback when Redis is unavailable
|
|
973
|
+
_projection_log: Dict[str, Dict] = {}
|
|
974
|
+
_feedback_scores: Dict[str, Dict[str, float]] = {}
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def _record_projection(namespace: str, agent_id: str, task_keywords: List[str],
|
|
978
|
+
projected_ids: List[str]) -> None:
|
|
979
|
+
"""Record what was projected to an agent (for learning from usage)."""
|
|
980
|
+
import time, json as _json
|
|
981
|
+
log_data = {
|
|
982
|
+
"projected_ids": list(projected_ids),
|
|
983
|
+
"task_keywords": task_keywords,
|
|
984
|
+
"timestamp": time.time(),
|
|
985
|
+
"namespace": namespace,
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
# Try Redis first
|
|
989
|
+
r = _get_redis()
|
|
990
|
+
if r:
|
|
991
|
+
try:
|
|
992
|
+
r.set(f"cgp:log:{agent_id}", _json.dumps(log_data), ex=3600) # 1h TTL
|
|
993
|
+
except Exception:
|
|
994
|
+
pass
|
|
995
|
+
|
|
996
|
+
# Always keep in-memory copy
|
|
997
|
+
log_data["projected_ids"] = set(projected_ids)
|
|
998
|
+
_projection_log[agent_id] = log_data
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def record_usage(agent_id: str, referenced_node_id: str) -> None:
|
|
1002
|
+
"""Called when an agent references a node in a Finding/Decision.
|
|
1003
|
+
|
|
1004
|
+
Positive signal: boost this node for future projections.
|
|
1005
|
+
"""
|
|
1006
|
+
import json as _json
|
|
1007
|
+
|
|
1008
|
+
# Get projection log (Redis first, then memory)
|
|
1009
|
+
log = None
|
|
1010
|
+
r = _get_redis()
|
|
1011
|
+
if r:
|
|
1012
|
+
try:
|
|
1013
|
+
raw = r.get(f"cgp:log:{agent_id}")
|
|
1014
|
+
if raw:
|
|
1015
|
+
log = _json.loads(raw)
|
|
1016
|
+
log["projected_ids"] = set(log["projected_ids"])
|
|
1017
|
+
except Exception:
|
|
1018
|
+
pass
|
|
1019
|
+
if not log:
|
|
1020
|
+
log = _projection_log.get(agent_id)
|
|
1021
|
+
if not log:
|
|
1022
|
+
return
|
|
1023
|
+
|
|
1024
|
+
ns = log["namespace"]
|
|
1025
|
+
projected = log["projected_ids"]
|
|
1026
|
+
|
|
1027
|
+
if referenced_node_id in projected:
|
|
1028
|
+
# Boost in Redis (7-day TTL)
|
|
1029
|
+
if r:
|
|
1030
|
+
try:
|
|
1031
|
+
key = f"cgp:feedback:{ns}"
|
|
1032
|
+
current = float(r.hget(key, referenced_node_id) or 1.0)
|
|
1033
|
+
r.hset(key, referenced_node_id, str(current * 1.1))
|
|
1034
|
+
r.expire(key, 604800) # 7 days
|
|
1035
|
+
except Exception:
|
|
1036
|
+
pass
|
|
1037
|
+
|
|
1038
|
+
# Also update in-memory
|
|
1039
|
+
if ns not in _feedback_scores:
|
|
1040
|
+
_feedback_scores[ns] = {}
|
|
1041
|
+
_feedback_scores[ns][referenced_node_id] = (
|
|
1042
|
+
_feedback_scores[ns].get(referenced_node_id, 1.0) * 1.1
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def get_feedback_boost(namespace: str, node_id: str) -> float:
|
|
1047
|
+
"""Get the learned relevance boost. 1.0 = neutral, >1.0 = useful."""
|
|
1048
|
+
# Try Redis first
|
|
1049
|
+
r = _get_redis()
|
|
1050
|
+
if r:
|
|
1051
|
+
try:
|
|
1052
|
+
val = r.hget(f"cgp:feedback:{namespace}", node_id)
|
|
1053
|
+
if val:
|
|
1054
|
+
return float(val)
|
|
1055
|
+
except Exception:
|
|
1056
|
+
pass
|
|
1057
|
+
return _feedback_scores.get(namespace, {}).get(node_id, 1.0)
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def clear_feedback(namespace: str) -> None:
|
|
1061
|
+
"""Clear feedback scores when a DIFFERENT atomic context is attached."""
|
|
1062
|
+
_feedback_scores.pop(namespace, None)
|
|
1063
|
+
r = _get_redis()
|
|
1064
|
+
if r:
|
|
1065
|
+
try:
|
|
1066
|
+
r.delete(f"cgp:feedback:{namespace}")
|
|
1067
|
+
except Exception:
|
|
1068
|
+
pass
|
|
1069
|
+
to_remove = [aid for aid, log in _projection_log.items() if log.get("namespace") == namespace]
|
|
1070
|
+
for aid in to_remove:
|
|
1071
|
+
_projection_log.pop(aid, None)
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
# ── Re-query Tracking (evidence of projection quality) ───────────────────────
|
|
1075
|
+
|
|
1076
|
+
_ORIENT_TIME_TTL = 300 # 5 minutes — window within which an ask() counts as a re-query
|
|
1077
|
+
_REQUERY_TTL = 86400 # 24 hours — counter persists across sessions for daily stats
|
|
1078
|
+
|
|
1079
|
+
# In-memory fallbacks (used when Redis is unavailable)
|
|
1080
|
+
_orient_times: Dict[str, float] = {} # "{namespace}:{agent_id}" → unix timestamp
|
|
1081
|
+
_requery_counts: Dict[str, int] = {} # "{namespace}:{agent_id}" → count
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def _rq_key(namespace: str, agent_id: str, suffix: str) -> str:
|
|
1085
|
+
return f"cgp:rq:{suffix}:{namespace}:{agent_id}"
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def record_orient_time(namespace: str, agent_id: str) -> None:
|
|
1089
|
+
"""Record that orient() was just called. Used to detect re-queries."""
|
|
1090
|
+
import time as _t
|
|
1091
|
+
ts = _t.time()
|
|
1092
|
+
mem_key = f"{namespace}:{agent_id}"
|
|
1093
|
+
_orient_times[mem_key] = ts
|
|
1094
|
+
r = _get_redis()
|
|
1095
|
+
if r:
|
|
1096
|
+
try:
|
|
1097
|
+
r.set(_rq_key(namespace, agent_id, "orient_ts"), str(ts), ex=_ORIENT_TIME_TTL)
|
|
1098
|
+
except Exception as _e:
|
|
1099
|
+
logger.debug("record_orient_time Redis error: %s", _e)
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def get_orient_time(namespace: str, agent_id: str) -> Optional[float]:
|
|
1103
|
+
"""Return unix timestamp of last orient() for this agent, or None."""
|
|
1104
|
+
import time as _t
|
|
1105
|
+
mem_key = f"{namespace}:{agent_id}"
|
|
1106
|
+
ts = _orient_times.get(mem_key)
|
|
1107
|
+
if ts is not None and (_t.time() - ts) < _ORIENT_TIME_TTL:
|
|
1108
|
+
return ts
|
|
1109
|
+
r = _get_redis()
|
|
1110
|
+
if r:
|
|
1111
|
+
try:
|
|
1112
|
+
val = r.get(_rq_key(namespace, agent_id, "orient_ts"))
|
|
1113
|
+
if val is not None:
|
|
1114
|
+
return float(val)
|
|
1115
|
+
except Exception as _e:
|
|
1116
|
+
logger.debug("get_orient_time Redis error: %s", _e)
|
|
1117
|
+
return None
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
def record_requery(namespace: str, agent_id: str) -> int:
|
|
1121
|
+
"""Increment re-query counter if agent is asking within 5 min of orient().
|
|
1122
|
+
|
|
1123
|
+
Returns the new counter value (or current value if orient was not recent).
|
|
1124
|
+
"""
|
|
1125
|
+
import time as _t
|
|
1126
|
+
orient_ts = get_orient_time(namespace, agent_id)
|
|
1127
|
+
if orient_ts is None or (_t.time() - orient_ts) > _ORIENT_TIME_TTL:
|
|
1128
|
+
return get_requery_count(namespace, agent_id)
|
|
1129
|
+
|
|
1130
|
+
mem_key = f"{namespace}:{agent_id}"
|
|
1131
|
+
_requery_counts[mem_key] = _requery_counts.get(mem_key, 0) + 1
|
|
1132
|
+
count = _requery_counts[mem_key]
|
|
1133
|
+
r = _get_redis()
|
|
1134
|
+
if r:
|
|
1135
|
+
try:
|
|
1136
|
+
rk = _rq_key(namespace, agent_id, "requery_count")
|
|
1137
|
+
count = r.incr(rk)
|
|
1138
|
+
r.expire(rk, _REQUERY_TTL)
|
|
1139
|
+
except Exception as _e:
|
|
1140
|
+
logger.debug("record_requery Redis error: %s", _e)
|
|
1141
|
+
return count
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def get_requery_count(namespace: str, agent_id: str) -> int:
|
|
1145
|
+
"""Return the current re-query count for this agent."""
|
|
1146
|
+
mem_key = f"{namespace}:{agent_id}"
|
|
1147
|
+
r = _get_redis()
|
|
1148
|
+
if r:
|
|
1149
|
+
try:
|
|
1150
|
+
val = r.get(_rq_key(namespace, agent_id, "requery_count"))
|
|
1151
|
+
if val is not None:
|
|
1152
|
+
return int(val)
|
|
1153
|
+
except Exception as _e:
|
|
1154
|
+
logger.debug("get_requery_count Redis error: %s", _e)
|
|
1155
|
+
return _requery_counts.get(mem_key, 0)
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
# ── Promotion Event Consumer ──────────────────────────────────────────────────
|
|
1159
|
+
|
|
1160
|
+
def get_recent_promotions(namespace: str, since_ts: float) -> list:
|
|
1161
|
+
"""Return promotion events for a namespace newer than since_ts.
|
|
1162
|
+
|
|
1163
|
+
Each event is: {"node_id": str, "label": str, "title": str, "promoted_at": float}
|
|
1164
|
+
Returns empty list if no events or on any error.
|
|
1165
|
+
"""
|
|
1166
|
+
import json as _json
|
|
1167
|
+
results = []
|
|
1168
|
+
|
|
1169
|
+
# Try Redis first (shared across workers)
|
|
1170
|
+
r = _get_redis()
|
|
1171
|
+
if r:
|
|
1172
|
+
try:
|
|
1173
|
+
rk = f"cgp:promotions:{namespace}"
|
|
1174
|
+
raw_events = r.lrange(rk, 0, 49)
|
|
1175
|
+
for raw in raw_events:
|
|
1176
|
+
try:
|
|
1177
|
+
evt = _json.loads(raw)
|
|
1178
|
+
if evt.get("promoted_at", 0) > since_ts:
|
|
1179
|
+
results.append(evt)
|
|
1180
|
+
except Exception as _e:
|
|
1181
|
+
logger.debug("get_recent_promotions JSON parse error: %s", _e)
|
|
1182
|
+
return results
|
|
1183
|
+
except Exception as _e:
|
|
1184
|
+
logger.debug("get_recent_promotions Redis error: %s", _e)
|
|
1185
|
+
|
|
1186
|
+
# In-memory fallback
|
|
1187
|
+
try:
|
|
1188
|
+
from ..context.promotion import _promotion_events
|
|
1189
|
+
for evt in _promotion_events.get(namespace, []):
|
|
1190
|
+
if evt.get("promoted_at", 0) > since_ts:
|
|
1191
|
+
results.append(evt)
|
|
1192
|
+
except Exception as _e:
|
|
1193
|
+
logger.debug("get_recent_promotions in-memory fallback error: %s", _e)
|
|
1194
|
+
|
|
1195
|
+
return results
|