apsimo 1.3.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.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""DuckDuckGo HTML search provider — zero-config fallback.
|
|
2
|
+
|
|
3
|
+
Uses the lite HTML endpoint (https://html.duckduckgo.com/html/) because
|
|
4
|
+
it requires no API key and no JS rendering. This is best-effort — DDG
|
|
5
|
+
can change their markup at any time — so any failure degrades to an
|
|
6
|
+
empty result set rather than raising.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import html as _html
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
from typing import List
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
|
|
19
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_RESULT_RE = re.compile(
|
|
25
|
+
r'<a rel="nofollow" class="result__a" href="(?P<url>[^"]+)"[^>]*>'
|
|
26
|
+
r"(?P<title>.*?)</a>"
|
|
27
|
+
r'.*?<a class="result__snippet"[^>]*>(?P<snippet>.*?)</a>',
|
|
28
|
+
re.DOTALL,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_TAG_RE = re.compile(r"<[^>]+>")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _strip_tags(s: str) -> str:
|
|
35
|
+
return _html.unescape(_TAG_RE.sub("", s)).strip()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DuckDuckGoProvider(SearchProvider):
|
|
39
|
+
"""HTML-endpoint DuckDuckGo provider.
|
|
40
|
+
|
|
41
|
+
No API key required. Fragile against markup changes — callers
|
|
42
|
+
MUST tolerate empty results silently.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
_ENDPOINT = "https://html.duckduckgo.com/html/"
|
|
46
|
+
|
|
47
|
+
def __init__(self, timeout_secs: float = 8.0) -> None:
|
|
48
|
+
self._timeout = timeout_secs
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def name(self) -> str:
|
|
52
|
+
return "duckduckgo"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def rate_limit_per_minute(self) -> int:
|
|
56
|
+
# DuckDuckGo has no documented limit; stay polite.
|
|
57
|
+
return 20
|
|
58
|
+
|
|
59
|
+
async def search(self, query: str, max_results: int = 5) -> List[SearchResult]:
|
|
60
|
+
params = {"q": query}
|
|
61
|
+
try:
|
|
62
|
+
# UA is deployment-overridable (COLONY_SEARCH_USER_AGENT); the
|
|
63
|
+
# default is a neutral project identifier with no deployment URL.
|
|
64
|
+
_ua = os.environ.get(
|
|
65
|
+
"COLONY_SEARCH_USER_AGENT",
|
|
66
|
+
"Mozilla/5.0 (compatible; ColonyAI research crawler)")
|
|
67
|
+
async with httpx.AsyncClient(
|
|
68
|
+
timeout=self._timeout,
|
|
69
|
+
headers={
|
|
70
|
+
"User-Agent": _ua,
|
|
71
|
+
"Accept": "text/html,application/xhtml+xml",
|
|
72
|
+
},
|
|
73
|
+
follow_redirects=True,
|
|
74
|
+
) as client:
|
|
75
|
+
resp = await client.post(self._ENDPOINT, data=params)
|
|
76
|
+
resp.raise_for_status()
|
|
77
|
+
body = resp.text
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
logger.warning("DuckDuckGo request failed: %s", exc)
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
results: List[SearchResult] = []
|
|
83
|
+
for i, m in enumerate(_RESULT_RE.finditer(body)):
|
|
84
|
+
if i >= max_results:
|
|
85
|
+
break
|
|
86
|
+
url = m.group("url")
|
|
87
|
+
# DDG wraps external URLs in a redirect; strip it when present.
|
|
88
|
+
if url.startswith("//duckduckgo.com/l/?uddg="):
|
|
89
|
+
from urllib.parse import parse_qs, unquote, urlparse
|
|
90
|
+
qs = parse_qs(urlparse("https:" + url).query)
|
|
91
|
+
uddg = qs.get("uddg", [""])[0]
|
|
92
|
+
if uddg:
|
|
93
|
+
url = unquote(uddg)
|
|
94
|
+
results.append(
|
|
95
|
+
SearchResult(
|
|
96
|
+
title=_strip_tags(m.group("title")),
|
|
97
|
+
url=url,
|
|
98
|
+
snippet=_strip_tags(m.group("snippet")),
|
|
99
|
+
source=self.name,
|
|
100
|
+
rank=i + 1,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
return results
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Search orchestrator — routes queries, handles rate limits, caches results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
10
|
+
from apsimo.research.search.cache import SearchCache
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SearchOrchestrator:
|
|
16
|
+
"""Routes search queries to providers, handles rate limits and caching.
|
|
17
|
+
|
|
18
|
+
If no providers are configured, returns empty results (graceful degradation).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
providers: Optional[List[SearchProvider]] = None,
|
|
24
|
+
cache_ttl: int = 3600,
|
|
25
|
+
max_cache_entries: int = 500,
|
|
26
|
+
):
|
|
27
|
+
self._providers: Dict[str, SearchProvider] = {}
|
|
28
|
+
if providers:
|
|
29
|
+
for p in providers:
|
|
30
|
+
self._providers[p.name] = p
|
|
31
|
+
self._cache = SearchCache(ttl_seconds=cache_ttl, max_entries=max_cache_entries)
|
|
32
|
+
self._rate_tracker: Dict[str, List[float]] = {name: [] for name in self._providers}
|
|
33
|
+
self._default_provider = next(iter(self._providers), None)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def has_providers(self) -> bool:
|
|
37
|
+
return len(self._providers) > 0
|
|
38
|
+
|
|
39
|
+
def add_provider(self, provider: SearchProvider) -> None:
|
|
40
|
+
"""Add a search provider."""
|
|
41
|
+
self._providers[provider.name] = provider
|
|
42
|
+
self._rate_tracker[provider.name] = []
|
|
43
|
+
if not self._default_provider:
|
|
44
|
+
self._default_provider = provider
|
|
45
|
+
|
|
46
|
+
async def search(
|
|
47
|
+
self,
|
|
48
|
+
query: str,
|
|
49
|
+
max_results: int = 5,
|
|
50
|
+
provider: str = "",
|
|
51
|
+
) -> List[SearchResult]:
|
|
52
|
+
"""Execute a search query.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
query: Search query string
|
|
56
|
+
max_results: Maximum number of results
|
|
57
|
+
provider: Preferred provider name (empty = auto-select)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of search results, empty if no providers available.
|
|
61
|
+
"""
|
|
62
|
+
if not self._providers:
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
# Check cache
|
|
66
|
+
cached = self._cache.get(query)
|
|
67
|
+
if cached is not None:
|
|
68
|
+
logger.debug("Search cache hit for '%s'", query[:50])
|
|
69
|
+
return cached[:max_results]
|
|
70
|
+
|
|
71
|
+
# Select provider
|
|
72
|
+
prov = self._select_provider(provider)
|
|
73
|
+
if not prov:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
# Rate limit check
|
|
77
|
+
if not self._check_rate_limit(prov.name):
|
|
78
|
+
prov = self._fallback_provider(prov.name)
|
|
79
|
+
if not prov:
|
|
80
|
+
logger.warning("All search providers rate-limited")
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
# Execute search
|
|
84
|
+
try:
|
|
85
|
+
results = await prov.search(query, max_results)
|
|
86
|
+
self._cache.put(query, results)
|
|
87
|
+
logger.info("Search via %s: '%s' → %d results", prov.name, query[:50], len(results))
|
|
88
|
+
return results
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning("Search via %s failed: %s", prov.name, e)
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
def _select_provider(self, preferred: str) -> Optional[SearchProvider]:
|
|
94
|
+
if preferred and preferred in self._providers:
|
|
95
|
+
return self._providers[preferred]
|
|
96
|
+
return self._default_provider
|
|
97
|
+
|
|
98
|
+
def _fallback_provider(self, exclude: str) -> Optional[SearchProvider]:
|
|
99
|
+
for name, prov in self._providers.items():
|
|
100
|
+
if name != exclude and self._check_rate_limit(name):
|
|
101
|
+
return prov
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def _check_rate_limit(self, name: str) -> bool:
|
|
105
|
+
if name not in self._rate_tracker:
|
|
106
|
+
return False
|
|
107
|
+
now = time.time()
|
|
108
|
+
# Clean up entries older than 60 seconds
|
|
109
|
+
self._rate_tracker[name] = [t for t in self._rate_tracker[name] if now - t < 60]
|
|
110
|
+
prov = self._providers.get(name)
|
|
111
|
+
if not prov:
|
|
112
|
+
return False
|
|
113
|
+
return len(self._rate_tracker[name]) < prov.rate_limit_per_minute
|
|
114
|
+
|
|
115
|
+
def clear_cache(self) -> None:
|
|
116
|
+
self._cache.clear()
|
|
117
|
+
|
|
118
|
+
def list_providers(self) -> List[str]:
|
|
119
|
+
return list(self._providers.keys())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""SerpAPI search provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import httpx
|
|
14
|
+
HAS_HTTPX = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_HTTPX = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SerpAPIProvider(SearchProvider):
|
|
20
|
+
"""Google search via SerpAPI."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, api_key: str):
|
|
23
|
+
self._api_key = api_key
|
|
24
|
+
self._base_url = "https://serpapi.com/search"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "serpapi"
|
|
29
|
+
|
|
30
|
+
async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
31
|
+
if not HAS_HTTPX:
|
|
32
|
+
logger.warning("httpx not installed — cannot use SerpAPI")
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
async with httpx.AsyncClient(timeout=15) as client:
|
|
37
|
+
resp = await client.get(self._base_url, params={
|
|
38
|
+
"q": query,
|
|
39
|
+
"api_key": self._api_key,
|
|
40
|
+
"engine": "google",
|
|
41
|
+
"num": max_results,
|
|
42
|
+
"output": "json",
|
|
43
|
+
})
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
data = resp.json()
|
|
46
|
+
results = []
|
|
47
|
+
for i, r in enumerate(data.get("organic_results", [])[:max_results]):
|
|
48
|
+
results.append(SearchResult(
|
|
49
|
+
title=r.get("title", ""),
|
|
50
|
+
url=r.get("link", ""),
|
|
51
|
+
snippet=r.get("snippet", ""),
|
|
52
|
+
content=r.get("snippet"), # SerpAPI doesn't return full content
|
|
53
|
+
source="serpapi",
|
|
54
|
+
rank=i,
|
|
55
|
+
))
|
|
56
|
+
return results
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.warning("SerpAPI search failed: %s", e)
|
|
59
|
+
return []
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Tavily search provider — designed for AI agents, returns clean content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import httpx
|
|
14
|
+
HAS_HTTPX = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_HTTPX = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TavilyProvider(SearchProvider):
|
|
20
|
+
"""Tavily search API — optimized for AI agent use cases."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, api_key: str):
|
|
23
|
+
self._api_key = api_key
|
|
24
|
+
self._base_url = "https://api.tavily.com/search"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "tavily"
|
|
29
|
+
|
|
30
|
+
async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
31
|
+
if not HAS_HTTPX:
|
|
32
|
+
logger.warning("httpx not installed — cannot use Tavily")
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
async with httpx.AsyncClient(timeout=15) as client:
|
|
37
|
+
resp = await client.post(self._base_url, json={
|
|
38
|
+
"api_key": self._api_key,
|
|
39
|
+
"query": query,
|
|
40
|
+
"max_results": max_results,
|
|
41
|
+
"include_raw_content": True,
|
|
42
|
+
"search_depth": "advanced",
|
|
43
|
+
})
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
data = resp.json()
|
|
46
|
+
return [
|
|
47
|
+
SearchResult(
|
|
48
|
+
title=r.get("title", ""),
|
|
49
|
+
url=r.get("url", ""),
|
|
50
|
+
snippet=r.get("content", "")[:500],
|
|
51
|
+
content=r.get("raw_content"),
|
|
52
|
+
source="tavily",
|
|
53
|
+
rank=i,
|
|
54
|
+
)
|
|
55
|
+
for i, r in enumerate(data.get("results", []))
|
|
56
|
+
]
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.warning("Tavily search failed: %s", e)
|
|
59
|
+
return []
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Research synthesizer — Stage 3 of the Research-to-Artifact Pipeline.
|
|
2
|
+
|
|
3
|
+
Runs gathered evidence through Colony's intelligence synthesis layer to
|
|
4
|
+
produce a structured ``SynthesisReport`` containing validated, novelty-ranked
|
|
5
|
+
insights and annotated contradictions.
|
|
6
|
+
|
|
7
|
+
Steps:
|
|
8
|
+
1. Connection discovery — temporal, causal, entity, behavioural patterns
|
|
9
|
+
2. Cross-domain analysis — groups evidence by domain, extracts insights
|
|
10
|
+
3. Novelty scoring — rank by formula: 0.4*(1/freq) + 0.3*domain_dist + 0.3*conf_delta
|
|
11
|
+
4. Quality validation — min evidence count, confidence thresholds, recency
|
|
12
|
+
5. Contradiction — conflicting evidence annotated with provenance
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from apsimo.research.gatherer import EvidenceItem, EvidencePackage, SourceType
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Synthesis models
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DomainInsight:
|
|
34
|
+
"""A validated, novelty-ranked insight derived from evidence."""
|
|
35
|
+
|
|
36
|
+
id: str
|
|
37
|
+
title: str
|
|
38
|
+
summary: str
|
|
39
|
+
domains: List[str]
|
|
40
|
+
supporting_citations: List[str] # Evidence item citations
|
|
41
|
+
confidence: float = 0.0 # 0.0–1.0
|
|
42
|
+
novelty_score: float = 0.0 # 0.0–1.0
|
|
43
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
44
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Contradiction:
|
|
49
|
+
"""Two evidence items with conflicting claims."""
|
|
50
|
+
|
|
51
|
+
id: str
|
|
52
|
+
claim_a: str
|
|
53
|
+
claim_b: str
|
|
54
|
+
citation_a: str
|
|
55
|
+
citation_b: str
|
|
56
|
+
topic: str
|
|
57
|
+
confidence_a: float = 0.5
|
|
58
|
+
confidence_b: float = 0.5
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class SynthesisReport:
|
|
63
|
+
"""Output of the synthesis stage."""
|
|
64
|
+
|
|
65
|
+
goal_id: str
|
|
66
|
+
insights: List[DomainInsight]
|
|
67
|
+
contradictions: List[Contradiction]
|
|
68
|
+
evidence_count: int
|
|
69
|
+
source_breakdown: Dict[str, int] # SourceType.value -> count
|
|
70
|
+
synthesis_confidence: float # 0.0–1.0, aggregate
|
|
71
|
+
graph_entities_referenced: List[str] # Neo4j node IDs
|
|
72
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Synthesis configuration
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class SynthesisConfig:
|
|
82
|
+
min_evidence_for_insight: int = 2
|
|
83
|
+
min_confidence: float = 0.55
|
|
84
|
+
min_novelty: float = 0.30
|
|
85
|
+
max_insights: int = 50
|
|
86
|
+
timeout_seconds: float = 90.0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Synthesizer
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ResearchSynthesizer:
|
|
95
|
+
"""Synthesize an EvidencePackage into a SynthesisReport.
|
|
96
|
+
|
|
97
|
+
Tries to use Colony's intelligence synthesis layer when available;
|
|
98
|
+
falls back to a lightweight built-in implementation for offline/test use.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, config: Optional[SynthesisConfig] = None) -> None:
|
|
102
|
+
self.config = config or SynthesisConfig()
|
|
103
|
+
|
|
104
|
+
async def synthesize(
|
|
105
|
+
self,
|
|
106
|
+
evidence: EvidencePackage,
|
|
107
|
+
goal_id: str,
|
|
108
|
+
goal_text: str = "",
|
|
109
|
+
) -> SynthesisReport:
|
|
110
|
+
"""Run evidence through the synthesis pipeline.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
evidence: List of EvidenceItem objects (already deduplicated).
|
|
114
|
+
goal_id: Unique ID of the parent research run.
|
|
115
|
+
goal_text: Original research goal text (used for context).
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A ``SynthesisReport`` with ranked insights and contradictions.
|
|
119
|
+
"""
|
|
120
|
+
# Filter out flagged items before any LLM exposure
|
|
121
|
+
safe_evidence = [e for e in evidence if e.is_safe()]
|
|
122
|
+
flagged_count = len(evidence) - len(safe_evidence)
|
|
123
|
+
if flagged_count:
|
|
124
|
+
logger.warning(
|
|
125
|
+
"Synthesizer: excluded %d flagged evidence items (pii/injection)",
|
|
126
|
+
flagged_count,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
source_breakdown: Dict[str, int] = {}
|
|
130
|
+
for item in safe_evidence:
|
|
131
|
+
source_breakdown[item.source_type.value] = (
|
|
132
|
+
source_breakdown.get(item.source_type.value, 0) + 1
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
insights = await self._extract_insights(safe_evidence, goal_text)
|
|
136
|
+
contradictions = self._detect_contradictions(safe_evidence)
|
|
137
|
+
|
|
138
|
+
# Enforce limits
|
|
139
|
+
insights = insights[: self.config.max_insights]
|
|
140
|
+
|
|
141
|
+
aggregate_confidence = (
|
|
142
|
+
sum(i.confidence for i in insights) / len(insights) if insights else 0.0
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
graph_entities: List[str] = []
|
|
146
|
+
for item in safe_evidence:
|
|
147
|
+
if item.source_type == SourceType.GRAPH:
|
|
148
|
+
node_id = item.citation.replace("graph://", "")
|
|
149
|
+
if node_id and node_id not in graph_entities:
|
|
150
|
+
graph_entities.append(node_id)
|
|
151
|
+
|
|
152
|
+
return SynthesisReport(
|
|
153
|
+
goal_id=goal_id,
|
|
154
|
+
insights=insights,
|
|
155
|
+
contradictions=contradictions,
|
|
156
|
+
evidence_count=len(safe_evidence),
|
|
157
|
+
source_breakdown=source_breakdown,
|
|
158
|
+
synthesis_confidence=aggregate_confidence,
|
|
159
|
+
graph_entities_referenced=graph_entities,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def _extract_insights(
|
|
163
|
+
self,
|
|
164
|
+
evidence: EvidencePackage,
|
|
165
|
+
goal_text: str,
|
|
166
|
+
) -> List[DomainInsight]:
|
|
167
|
+
"""Extract and rank insights from evidence."""
|
|
168
|
+
# Attempt to use Colony's CrossDomainAnalyzer
|
|
169
|
+
try:
|
|
170
|
+
return await self._run_colony_synthesis(evidence, goal_text)
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
logger.debug("CrossDomainAnalyzer unavailable (%s), using built-in", exc)
|
|
173
|
+
return self._builtin_extract(evidence, goal_text)
|
|
174
|
+
|
|
175
|
+
async def _run_colony_synthesis(
|
|
176
|
+
self,
|
|
177
|
+
evidence: EvidencePackage,
|
|
178
|
+
goal_text: str,
|
|
179
|
+
) -> List[DomainInsight]:
|
|
180
|
+
"""Run Colony intelligence synthesis layer."""
|
|
181
|
+
from apsimo.intelligence.synthesis.cross_domain_analyzer import CrossDomainAnalyzer
|
|
182
|
+
from apsimo.intelligence.synthesis.novelty_scorer import NoveltyScorer
|
|
183
|
+
from apsimo.intelligence.synthesis.insight_validator import InsightValidator
|
|
184
|
+
|
|
185
|
+
# Build a minimal data structure the analyzer expects
|
|
186
|
+
signals = [
|
|
187
|
+
{
|
|
188
|
+
"domain": item.source_type.value,
|
|
189
|
+
"content": item.content,
|
|
190
|
+
"timestamp": item.retrieved_at.isoformat(),
|
|
191
|
+
"confidence": item.relevance_score,
|
|
192
|
+
"source": item.citation,
|
|
193
|
+
}
|
|
194
|
+
for item in evidence
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
analyzer = CrossDomainAnalyzer()
|
|
198
|
+
raw_insights = await analyzer.analyze(signals)
|
|
199
|
+
|
|
200
|
+
validator = InsightValidator(
|
|
201
|
+
min_confidence=self.config.min_confidence,
|
|
202
|
+
min_evidence_count=self.config.min_evidence_for_insight,
|
|
203
|
+
)
|
|
204
|
+
scorer = NoveltyScorer()
|
|
205
|
+
|
|
206
|
+
results: List[DomainInsight] = []
|
|
207
|
+
import secrets as _secrets
|
|
208
|
+
|
|
209
|
+
for raw in raw_insights:
|
|
210
|
+
validation = validator.validate(raw)
|
|
211
|
+
if not validation.valid:
|
|
212
|
+
continue
|
|
213
|
+
novelty = scorer.score(raw)
|
|
214
|
+
if novelty.score < self.config.min_novelty:
|
|
215
|
+
continue
|
|
216
|
+
results.append(
|
|
217
|
+
DomainInsight(
|
|
218
|
+
id=_secrets.token_hex(6),
|
|
219
|
+
title=getattr(raw, "title", str(raw)[:80]),
|
|
220
|
+
summary=getattr(raw, "summary", str(raw)),
|
|
221
|
+
domains=getattr(raw, "domains", []),
|
|
222
|
+
supporting_citations=[],
|
|
223
|
+
confidence=getattr(raw, "confidence", 0.6),
|
|
224
|
+
novelty_score=novelty.score,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return sorted(results, key=lambda x: x.novelty_score, reverse=True)
|
|
229
|
+
|
|
230
|
+
def _builtin_extract(
|
|
231
|
+
self,
|
|
232
|
+
evidence: EvidencePackage,
|
|
233
|
+
goal_text: str,
|
|
234
|
+
) -> List[DomainInsight]:
|
|
235
|
+
"""Lightweight built-in insight extractor (no external deps)."""
|
|
236
|
+
import secrets as _secrets
|
|
237
|
+
|
|
238
|
+
cfg = self.config
|
|
239
|
+
insights: List[DomainInsight] = []
|
|
240
|
+
|
|
241
|
+
# Group evidence by source type
|
|
242
|
+
by_source: Dict[str, List[EvidenceItem]] = {}
|
|
243
|
+
for item in evidence:
|
|
244
|
+
by_source.setdefault(item.source_type.value, []).append(item)
|
|
245
|
+
|
|
246
|
+
for source_type, items in by_source.items():
|
|
247
|
+
if len(items) < cfg.min_evidence_for_insight:
|
|
248
|
+
continue
|
|
249
|
+
# One insight per source type, summarizing top items
|
|
250
|
+
top_items = sorted(items, key=lambda x: x.relevance_score, reverse=True)[:5]
|
|
251
|
+
combined = " ".join(i.content[:200] for i in top_items)
|
|
252
|
+
avg_confidence = sum(i.relevance_score for i in top_items) / len(top_items)
|
|
253
|
+
|
|
254
|
+
if avg_confidence < cfg.min_confidence:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
# Compute a simple novelty score from source diversity
|
|
258
|
+
novelty = min(1.0, 0.3 + 0.1 * len(items))
|
|
259
|
+
if novelty < cfg.min_novelty:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
insights.append(
|
|
263
|
+
DomainInsight(
|
|
264
|
+
id=_secrets.token_hex(6),
|
|
265
|
+
title=f"Findings from {source_type} sources",
|
|
266
|
+
summary=combined[:500],
|
|
267
|
+
domains=[source_type],
|
|
268
|
+
supporting_citations=[i.citation for i in top_items],
|
|
269
|
+
confidence=avg_confidence,
|
|
270
|
+
novelty_score=novelty,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return sorted(insights, key=lambda x: x.novelty_score, reverse=True)
|
|
275
|
+
|
|
276
|
+
def _detect_contradictions(
|
|
277
|
+
self,
|
|
278
|
+
evidence: EvidencePackage,
|
|
279
|
+
) -> List[Contradiction]:
|
|
280
|
+
"""Detect pairs of evidence items with potentially conflicting claims.
|
|
281
|
+
|
|
282
|
+
Uses a heuristic: items from different sources with similar topics
|
|
283
|
+
but significantly different relevance scores are flagged.
|
|
284
|
+
"""
|
|
285
|
+
import secrets as _secrets
|
|
286
|
+
|
|
287
|
+
contradictions: List[Contradiction] = []
|
|
288
|
+
# Simple heuristic — compare web vs graph items on same topic keywords
|
|
289
|
+
web_items = [e for e in evidence if e.source_type == SourceType.WEB]
|
|
290
|
+
graph_items = [e for e in evidence if e.source_type == SourceType.GRAPH]
|
|
291
|
+
|
|
292
|
+
for w in web_items[:5]:
|
|
293
|
+
for g in graph_items[:5]:
|
|
294
|
+
# If relevance scores differ significantly, flag as potential contradiction
|
|
295
|
+
if abs(w.relevance_score - g.relevance_score) > 0.4:
|
|
296
|
+
contradictions.append(
|
|
297
|
+
Contradiction(
|
|
298
|
+
id=_secrets.token_hex(6),
|
|
299
|
+
claim_a=w.content[:200],
|
|
300
|
+
claim_b=g.content[:200],
|
|
301
|
+
citation_a=w.citation,
|
|
302
|
+
citation_b=g.citation,
|
|
303
|
+
topic="relevance discrepancy",
|
|
304
|
+
confidence_a=w.relevance_score,
|
|
305
|
+
confidence_b=g.relevance_score,
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return contradictions[:10] # Cap at 10 contradictions
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Colony LLM Cost Router — intelligent model tier selection.
|
|
2
|
+
|
|
3
|
+
Route LLM requests to the cheapest capable model based on prompt complexity.
|
|
4
|
+
Expected cost savings: 30–40% vs always using the largest model.
|
|
5
|
+
|
|
6
|
+
Usage::
|
|
7
|
+
|
|
8
|
+
from apsimo.router import LLMRouter, ModelTier
|
|
9
|
+
|
|
10
|
+
router = LLMRouter()
|
|
11
|
+
response = await router.complete(messages, context={"task": "summarise"})
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from apsimo.router.tiers import ModelTier, TierConfig, DEFAULT_TIERS
|
|
15
|
+
from apsimo.router.complexity_scorer import ComplexityScorer, ComplexitySignals
|
|
16
|
+
from apsimo.router.router import LLMRouter, LLMResponse
|
|
17
|
+
from apsimo.router.self_learning import RouterSelfLearner
|
|
18
|
+
from apsimo.router.fallback import FallbackHandler
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"LLMRouter",
|
|
22
|
+
"LLMResponse",
|
|
23
|
+
"ModelTier",
|
|
24
|
+
"TierConfig",
|
|
25
|
+
"DEFAULT_TIERS",
|
|
26
|
+
"ComplexityScorer",
|
|
27
|
+
"ComplexitySignals",
|
|
28
|
+
"RouterSelfLearner",
|
|
29
|
+
"FallbackHandler",
|
|
30
|
+
]
|