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,379 @@
|
|
|
1
|
+
"""Connection Discoverer — find non-obvious connections across domains.
|
|
2
|
+
|
|
3
|
+
Scans memories, signals, and relationships to find patterns that connect
|
|
4
|
+
seemingly unrelated information (e.g., "Mom always calls after doctor
|
|
5
|
+
appointments", "stress spikes correlate with late-night work sessions").
|
|
6
|
+
|
|
7
|
+
Uses graph queries + signal analysis to discover temporal, causal,
|
|
8
|
+
entity-based, and behavioral patterns across domain boundaries.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import textwrap
|
|
15
|
+
import uuid
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConnectionType(str, Enum):
|
|
25
|
+
"""Categories of cross-domain connections.
|
|
26
|
+
|
|
27
|
+
TEMPORAL: A happens before/after B (time correlation)
|
|
28
|
+
CAUSAL: A causes B (inferred from repeated co-occurrence)
|
|
29
|
+
TOPIC: Same topic surfaces across different contexts
|
|
30
|
+
ENTITY: Same person/company appears in different domains
|
|
31
|
+
BEHAVIORAL: Repeating behavior pattern across contexts
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
TEMPORAL = "temporal"
|
|
35
|
+
CAUSAL = "causal"
|
|
36
|
+
TOPIC = "topic"
|
|
37
|
+
ENTITY = "entity"
|
|
38
|
+
BEHAVIORAL = "behavioral"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Connection:
|
|
43
|
+
"""A discovered connection between entities or concepts.
|
|
44
|
+
|
|
45
|
+
Represents a cross-domain relationship with evidence tracking
|
|
46
|
+
and confidence scoring.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
id: Unique connection identifier
|
|
50
|
+
connection_type: Category of connection
|
|
51
|
+
source_domain: Origin domain ("health", "work", "relationships")
|
|
52
|
+
target_domain: Destination domain
|
|
53
|
+
entities: Related person IDs, memory IDs, etc.
|
|
54
|
+
description: Human-readable description of the connection
|
|
55
|
+
confidence: Confidence score between 0 and 1
|
|
56
|
+
evidence: List of evidence references (memory IDs, signal IDs)
|
|
57
|
+
first_observed: When connection was first detected
|
|
58
|
+
last_observed: Most recent observation
|
|
59
|
+
observation_count: How many times this connection has been seen
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
id: str
|
|
63
|
+
connection_type: ConnectionType
|
|
64
|
+
source_domain: str
|
|
65
|
+
target_domain: str
|
|
66
|
+
entities: List[str]
|
|
67
|
+
description: str
|
|
68
|
+
confidence: float
|
|
69
|
+
evidence: List[str] = field(default_factory=list)
|
|
70
|
+
first_observed: Optional[datetime] = None
|
|
71
|
+
last_observed: Optional[datetime] = None
|
|
72
|
+
observation_count: int = 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@runtime_checkable
|
|
76
|
+
class GraphClient(Protocol):
|
|
77
|
+
"""Protocol for graph database access.
|
|
78
|
+
|
|
79
|
+
Any object providing these methods can serve as the graph backend
|
|
80
|
+
for connection discovery.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
async def recall(
|
|
84
|
+
self,
|
|
85
|
+
query: str,
|
|
86
|
+
limit: int = 10,
|
|
87
|
+
min_strength: float = 0.1,
|
|
88
|
+
) -> List[Dict[str, Any]]: ...
|
|
89
|
+
|
|
90
|
+
async def traverse_memory_connections(
|
|
91
|
+
self,
|
|
92
|
+
memory_id: str,
|
|
93
|
+
max_depth: int = 3,
|
|
94
|
+
min_strength: float = 0.3,
|
|
95
|
+
limit: int = 20,
|
|
96
|
+
) -> List[Dict[str, Any]]: ...
|
|
97
|
+
|
|
98
|
+
async def run_query(
|
|
99
|
+
self,
|
|
100
|
+
cypher: str,
|
|
101
|
+
params: Dict[str, Any],
|
|
102
|
+
) -> List[Dict[str, Any]]: ...
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ConnectionDiscoverer:
|
|
106
|
+
"""Discover cross-domain connections from graph data.
|
|
107
|
+
|
|
108
|
+
Analyzes memories, signals, and entity relationships to surface
|
|
109
|
+
patterns spanning multiple domains. Supports temporal correlation,
|
|
110
|
+
entity co-occurrence, and behavioral pattern detection.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
graph_client: Graph database client implementing GraphClient protocol
|
|
114
|
+
signal_threshold: Minimum signal strength to consider (0-1)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
graph_client: GraphClient,
|
|
120
|
+
signal_threshold: float = 0.6,
|
|
121
|
+
) -> None:
|
|
122
|
+
self.graph = graph_client
|
|
123
|
+
self.signal_threshold = signal_threshold
|
|
124
|
+
|
|
125
|
+
async def discover_connections(
|
|
126
|
+
self,
|
|
127
|
+
person_id: Optional[str] = None,
|
|
128
|
+
domain: Optional[str] = None,
|
|
129
|
+
min_confidence: float = 0.5,
|
|
130
|
+
) -> List[Connection]:
|
|
131
|
+
"""Find connections, optionally filtered by person or domain.
|
|
132
|
+
|
|
133
|
+
Runs temporal, entity, and behavioral pattern detectors in
|
|
134
|
+
sequence and returns results sorted by confidence descending.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
person_id: Restrict search to this person's connections
|
|
138
|
+
domain: Restrict search to connections involving this domain
|
|
139
|
+
min_confidence: Minimum confidence threshold for results
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Connections sorted by confidence, highest first.
|
|
143
|
+
"""
|
|
144
|
+
connections: List[Connection] = []
|
|
145
|
+
|
|
146
|
+
# Temporal patterns (e.g., "calls mom on Sundays")
|
|
147
|
+
temporal = await self._find_temporal_patterns(person_id)
|
|
148
|
+
connections.extend(temporal)
|
|
149
|
+
|
|
150
|
+
# Entity co-occurrence (same person in different contexts)
|
|
151
|
+
entity = await self._find_entity_patterns(person_id)
|
|
152
|
+
connections.extend(entity)
|
|
153
|
+
|
|
154
|
+
# Behavioral patterns (repeating actions)
|
|
155
|
+
behavioral = await self._find_behavioral_patterns(person_id)
|
|
156
|
+
connections.extend(behavioral)
|
|
157
|
+
|
|
158
|
+
# Filter by confidence and optional domain
|
|
159
|
+
filtered = [c for c in connections if c.confidence >= min_confidence]
|
|
160
|
+
if domain:
|
|
161
|
+
filtered = [
|
|
162
|
+
c
|
|
163
|
+
for c in filtered
|
|
164
|
+
if c.source_domain == domain or c.target_domain == domain
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
return sorted(filtered, key=lambda c: c.confidence, reverse=True)
|
|
168
|
+
|
|
169
|
+
async def _find_temporal_patterns(
|
|
170
|
+
self,
|
|
171
|
+
person_id: Optional[str],
|
|
172
|
+
) -> List[Connection]:
|
|
173
|
+
"""Find time-based connections between events.
|
|
174
|
+
|
|
175
|
+
Queries graph for Memory node pairs that co-occur within a 4-hour
|
|
176
|
+
window and have been seen together at least 2 times in the past
|
|
177
|
+
30 days. Confidence scales with daily co-occurrence rate.
|
|
178
|
+
"""
|
|
179
|
+
if person_id is not None:
|
|
180
|
+
cypher = textwrap.dedent("""
|
|
181
|
+
MATCH (m1:Memory)-[:ABOUT]->(p:Person {id: $person_id})
|
|
182
|
+
MATCH (m2:Memory)-[:ABOUT]->(p)
|
|
183
|
+
WHERE m1.id < m2.id
|
|
184
|
+
AND abs(duration.between(m1.created_at, m2.created_at).hours) <= $window_hours
|
|
185
|
+
AND m1.created_at >= datetime() - duration({days: $lookback_days})
|
|
186
|
+
WITH m1, m2, count(*) AS co_occurrences
|
|
187
|
+
WHERE co_occurrences >= $min_count
|
|
188
|
+
RETURN m1.id AS source_id, m2.id AS target_id,
|
|
189
|
+
m1.type AS source_type, m2.type AS target_type,
|
|
190
|
+
m1.metadata AS source_meta, m2.metadata AS target_meta,
|
|
191
|
+
co_occurrences,
|
|
192
|
+
toFloat(co_occurrences) / $lookback_days AS daily_rate
|
|
193
|
+
ORDER BY daily_rate DESC
|
|
194
|
+
LIMIT 20
|
|
195
|
+
""").strip()
|
|
196
|
+
params: Dict[str, Any] = {
|
|
197
|
+
"person_id": person_id,
|
|
198
|
+
"window_hours": 4,
|
|
199
|
+
"lookback_days": 30,
|
|
200
|
+
"min_count": 2,
|
|
201
|
+
}
|
|
202
|
+
else:
|
|
203
|
+
cypher = textwrap.dedent("""
|
|
204
|
+
MATCH (m1:Memory), (m2:Memory)
|
|
205
|
+
WHERE m1.id < m2.id
|
|
206
|
+
AND abs(duration.between(m1.created_at, m2.created_at).hours) <= $window_hours
|
|
207
|
+
AND m1.created_at >= datetime() - duration({days: $lookback_days})
|
|
208
|
+
WITH m1, m2, count(*) AS co_occurrences
|
|
209
|
+
WHERE co_occurrences >= $min_count
|
|
210
|
+
RETURN m1.id AS source_id, m2.id AS target_id,
|
|
211
|
+
m1.type AS source_type, m2.type AS target_type,
|
|
212
|
+
m1.metadata AS source_meta, m2.metadata AS target_meta,
|
|
213
|
+
co_occurrences,
|
|
214
|
+
toFloat(co_occurrences) / $lookback_days AS daily_rate
|
|
215
|
+
ORDER BY daily_rate DESC
|
|
216
|
+
LIMIT 20
|
|
217
|
+
""").strip()
|
|
218
|
+
params = {
|
|
219
|
+
"window_hours": 4,
|
|
220
|
+
"lookback_days": 30,
|
|
221
|
+
"min_count": 2,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
connections: List[Connection] = []
|
|
225
|
+
try:
|
|
226
|
+
rows = await self.graph.run_query(cypher, params)
|
|
227
|
+
except Exception:
|
|
228
|
+
logger.exception("_find_temporal_patterns query failed")
|
|
229
|
+
return connections
|
|
230
|
+
|
|
231
|
+
for row in rows:
|
|
232
|
+
source_type = row.get("source_type") or "unknown"
|
|
233
|
+
target_type = row.get("target_type") or "unknown"
|
|
234
|
+
daily_rate = float(row.get("daily_rate") or 0.0)
|
|
235
|
+
confidence = min(1.0, daily_rate * 7)
|
|
236
|
+
connections.append(
|
|
237
|
+
Connection(
|
|
238
|
+
id=self._make_id(),
|
|
239
|
+
connection_type=ConnectionType.TEMPORAL,
|
|
240
|
+
source_domain=source_type,
|
|
241
|
+
target_domain=target_type,
|
|
242
|
+
entities=[row["source_id"], row["target_id"]],
|
|
243
|
+
description=(
|
|
244
|
+
f"Memory type '{source_type}' co-occurs with"
|
|
245
|
+
f" '{target_type}' within 4 hours"
|
|
246
|
+
f" ({int(row['co_occurrences'])}x in 30 days)"
|
|
247
|
+
),
|
|
248
|
+
confidence=confidence,
|
|
249
|
+
evidence=[row["source_id"], row["target_id"]],
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
return connections
|
|
253
|
+
|
|
254
|
+
async def _find_entity_patterns(
|
|
255
|
+
self,
|
|
256
|
+
person_id: Optional[str],
|
|
257
|
+
) -> List[Connection]:
|
|
258
|
+
"""Find entity co-occurrence across domains.
|
|
259
|
+
|
|
260
|
+
Detects when the same person, company, or concept appears in
|
|
261
|
+
multiple domain contexts (e.g., "Alex" in both health and work).
|
|
262
|
+
Requires an entity to appear in at least 2 distinct domains.
|
|
263
|
+
"""
|
|
264
|
+
cypher = textwrap.dedent("""
|
|
265
|
+
MATCH (e:Entity)<-[:MENTIONS]-(m:Memory)
|
|
266
|
+
WHERE ($person_id IS NULL OR (m)-[:ABOUT]->(:Person {id: $person_id}))
|
|
267
|
+
WITH e, collect(DISTINCT m.id) AS mems
|
|
268
|
+
WITH e, mems, size(mems) AS mem_count
|
|
269
|
+
WHERE mem_count >= 2
|
|
270
|
+
RETURN e.name AS entity_name,
|
|
271
|
+
mem_count AS occurrence_count,
|
|
272
|
+
mems[0..5] AS evidence_sample
|
|
273
|
+
ORDER BY mem_count DESC
|
|
274
|
+
LIMIT 20
|
|
275
|
+
""").strip()
|
|
276
|
+
params = {
|
|
277
|
+
"person_id": person_id,
|
|
278
|
+
"min_domain_count": 2,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
connections: List[Connection] = []
|
|
282
|
+
try:
|
|
283
|
+
rows = await self.graph.run_query(cypher, params)
|
|
284
|
+
except Exception:
|
|
285
|
+
logger.exception("_find_entity_patterns query failed")
|
|
286
|
+
return connections
|
|
287
|
+
|
|
288
|
+
for row in rows:
|
|
289
|
+
entity_name = row.get("entity_name") or "unknown"
|
|
290
|
+
domains: List[str] = list(row.get("domains") or [])
|
|
291
|
+
evidence_sample = row.get("evidence_sample") or []
|
|
292
|
+
# Flatten nested lists from collect(collect(...))
|
|
293
|
+
evidence: List[str] = []
|
|
294
|
+
for item in evidence_sample:
|
|
295
|
+
if isinstance(item, list):
|
|
296
|
+
evidence.extend(str(x) for x in item)
|
|
297
|
+
else:
|
|
298
|
+
evidence.append(str(item))
|
|
299
|
+
source_domain = domains[0] if domains else "unknown"
|
|
300
|
+
target_domain = domains[1] if len(domains) > 1 else "multiple"
|
|
301
|
+
confidence = min(1.0, (len(domains) - 1) * 0.3 + 0.4)
|
|
302
|
+
connections.append(
|
|
303
|
+
Connection(
|
|
304
|
+
id=self._make_id(),
|
|
305
|
+
connection_type=ConnectionType.ENTITY,
|
|
306
|
+
source_domain=source_domain,
|
|
307
|
+
target_domain=target_domain,
|
|
308
|
+
entities=[entity_name],
|
|
309
|
+
description=f"{entity_name} appears across {', '.join(domains)}",
|
|
310
|
+
confidence=confidence,
|
|
311
|
+
evidence=evidence[:5],
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
return connections
|
|
315
|
+
|
|
316
|
+
async def _find_behavioral_patterns(
|
|
317
|
+
self,
|
|
318
|
+
person_id: Optional[str],
|
|
319
|
+
) -> List[Connection]:
|
|
320
|
+
"""Find repeating behavior patterns across contexts.
|
|
321
|
+
|
|
322
|
+
Analyzes Signal sequences where signal type A is consistently
|
|
323
|
+
followed by signal type B within 24 hours, for a given person.
|
|
324
|
+
Requires person_id; returns empty list when person_id is None.
|
|
325
|
+
"""
|
|
326
|
+
connections: List[Connection] = []
|
|
327
|
+
if person_id is None:
|
|
328
|
+
return connections
|
|
329
|
+
|
|
330
|
+
cypher = textwrap.dedent("""
|
|
331
|
+
MATCH (p:Person {id: $person_id})-[:EXHIBITED]->(s1:Signal)
|
|
332
|
+
MATCH (p)-[:EXHIBITED]->(s2:Signal)
|
|
333
|
+
WHERE s1.signal_type <> s2.signal_type
|
|
334
|
+
AND s2.timestamp > s1.timestamp
|
|
335
|
+
AND duration.between(s1.timestamp, s2.timestamp).hours <= $window_hours
|
|
336
|
+
WITH s1.signal_type AS type_a, s2.signal_type AS type_b,
|
|
337
|
+
count(*) AS occurrences,
|
|
338
|
+
avg(s2.normalized_value - s1.normalized_value) AS avg_delta,
|
|
339
|
+
collect(s1.id)[0..5] AS evidence
|
|
340
|
+
WHERE occurrences >= $min_occurrences
|
|
341
|
+
RETURN type_a, type_b, occurrences, avg_delta, evidence
|
|
342
|
+
ORDER BY occurrences DESC
|
|
343
|
+
LIMIT 15
|
|
344
|
+
""").strip()
|
|
345
|
+
params = {
|
|
346
|
+
"person_id": person_id,
|
|
347
|
+
"window_hours": 24,
|
|
348
|
+
"min_occurrences": 3,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
rows = await self.graph.run_query(cypher, params)
|
|
353
|
+
except Exception:
|
|
354
|
+
logger.exception("_find_behavioral_patterns query failed")
|
|
355
|
+
return connections
|
|
356
|
+
|
|
357
|
+
for row in rows:
|
|
358
|
+
type_a = row.get("type_a") or "unknown"
|
|
359
|
+
type_b = row.get("type_b") or "unknown"
|
|
360
|
+
occurrences = int(row.get("occurrences") or 0)
|
|
361
|
+
confidence = min(1.0, occurrences / 10.0)
|
|
362
|
+
connections.append(
|
|
363
|
+
Connection(
|
|
364
|
+
id=self._make_id(),
|
|
365
|
+
connection_type=ConnectionType.BEHAVIORAL,
|
|
366
|
+
source_domain="signals",
|
|
367
|
+
target_domain="signals",
|
|
368
|
+
entities=[],
|
|
369
|
+
description=f"Signal {type_a} consistently precedes {type_b}",
|
|
370
|
+
confidence=confidence,
|
|
371
|
+
evidence=list(row.get("evidence") or []),
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
return connections
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _make_id() -> str:
|
|
378
|
+
"""Generate a unique connection ID."""
|
|
379
|
+
return str(uuid.uuid4())
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Cross-Domain Analyzer — synthesize insights across domains.
|
|
2
|
+
|
|
3
|
+
Analyzes relationships between:
|
|
4
|
+
- Health data (sleep, stress → availability predictions)
|
|
5
|
+
- Work patterns (deadlines → communication shifts)
|
|
6
|
+
- Social patterns (relationship dynamics → scheduling impact)
|
|
7
|
+
- Financial patterns (spending → stress correlation)
|
|
8
|
+
|
|
9
|
+
Produces actionable DomainInsight objects that can be validated,
|
|
10
|
+
scored for novelty, and delivered through appropriate channels.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import math
|
|
17
|
+
import statistics
|
|
18
|
+
import uuid
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DomainInsight:
|
|
28
|
+
"""Insight derived from cross-domain analysis.
|
|
29
|
+
|
|
30
|
+
Captures a pattern or prediction that spans multiple domains,
|
|
31
|
+
along with evidence, confidence, and an optional recommended action.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
id: Unique insight identifier
|
|
35
|
+
domains: List of domains this insight spans (e.g. ["health", "work"])
|
|
36
|
+
insight_type: Category ("availability_prediction", "stress_pattern", etc.)
|
|
37
|
+
description: Human-readable description of the insight
|
|
38
|
+
confidence: Confidence score between 0 and 1
|
|
39
|
+
actionable: Whether this insight has a concrete recommended action
|
|
40
|
+
recommended_action: Suggested action if actionable
|
|
41
|
+
supporting_evidence: Memory IDs, signal IDs, or descriptions backing this insight
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
id: str
|
|
45
|
+
domains: List[str]
|
|
46
|
+
insight_type: str
|
|
47
|
+
description: str
|
|
48
|
+
confidence: float
|
|
49
|
+
actionable: bool
|
|
50
|
+
recommended_action: Optional[str] = None
|
|
51
|
+
supporting_evidence: List[str] = field(default_factory=list)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class AnalyzerGraphClient(Protocol):
|
|
56
|
+
"""Protocol for graph access needed by cross-domain analysis."""
|
|
57
|
+
|
|
58
|
+
async def recall(
|
|
59
|
+
self,
|
|
60
|
+
query: str,
|
|
61
|
+
limit: int = 10,
|
|
62
|
+
min_strength: float = 0.1,
|
|
63
|
+
) -> List[Dict[str, Any]]: ...
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@runtime_checkable
|
|
67
|
+
class AnalyzerEventBus(Protocol):
|
|
68
|
+
"""Protocol for event emission from the analyzer."""
|
|
69
|
+
|
|
70
|
+
async def emit_async(self, event: Any) -> None: ...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CrossDomainAnalyzer:
|
|
74
|
+
"""Analyze patterns spanning multiple domains.
|
|
75
|
+
|
|
76
|
+
Runs targeted analyzers for domain pairs (health+work,
|
|
77
|
+
relationships+health, etc.) and produces scored insights.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
graph_client: Graph database client for querying cross-domain data
|
|
81
|
+
event_bus: Event bus for emitting insight discovery events
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
graph_client: AnalyzerGraphClient,
|
|
87
|
+
event_bus: AnalyzerEventBus,
|
|
88
|
+
) -> None:
|
|
89
|
+
self.graph = graph_client
|
|
90
|
+
self.events = event_bus
|
|
91
|
+
|
|
92
|
+
async def analyze(
|
|
93
|
+
self,
|
|
94
|
+
domains: Optional[List[str]] = None,
|
|
95
|
+
) -> List[DomainInsight]:
|
|
96
|
+
"""Run cross-domain analysis across specified domains.
|
|
97
|
+
|
|
98
|
+
If no domains are specified, all available analyzers run.
|
|
99
|
+
Results are sorted by confidence descending.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
domains: Optional list of domains to include. None means all.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of DomainInsight objects, highest confidence first.
|
|
106
|
+
"""
|
|
107
|
+
insights: List[DomainInsight] = []
|
|
108
|
+
|
|
109
|
+
# Health + Work: stress → availability
|
|
110
|
+
if not domains or "health" in domains or "work" in domains:
|
|
111
|
+
health_work = await self._analyze_health_work()
|
|
112
|
+
insights.extend(health_work)
|
|
113
|
+
|
|
114
|
+
# Relationships + Health: social stress patterns
|
|
115
|
+
if not domains or "relationships" in domains or "health" in domains:
|
|
116
|
+
relationships_health = await self._analyze_relationships_health()
|
|
117
|
+
insights.extend(relationships_health)
|
|
118
|
+
|
|
119
|
+
# Work + Relationships: deadline → social impact
|
|
120
|
+
if not domains or "work" in domains or "relationships" in domains:
|
|
121
|
+
work_relationships = await self._analyze_work_relationships()
|
|
122
|
+
insights.extend(work_relationships)
|
|
123
|
+
|
|
124
|
+
return sorted(insights, key=lambda i: i.confidence, reverse=True)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _pearson_correlation(xs: list, ys: list) -> float:
|
|
128
|
+
"""Compute Pearson correlation coefficient between two lists."""
|
|
129
|
+
n = len(xs)
|
|
130
|
+
if n < 2:
|
|
131
|
+
return 0.0
|
|
132
|
+
try:
|
|
133
|
+
mean_x = sum(xs) / n
|
|
134
|
+
mean_y = sum(ys) / n
|
|
135
|
+
cov = sum((xs[i] - mean_x) * (ys[i] - mean_y) for i in range(n))
|
|
136
|
+
std_x = math.sqrt(sum((x - mean_x) ** 2 for x in xs))
|
|
137
|
+
std_y = math.sqrt(sum((y - mean_y) ** 2 for y in ys))
|
|
138
|
+
if std_x == 0.0 or std_y == 0.0:
|
|
139
|
+
return 0.0
|
|
140
|
+
return cov / (std_x * std_y)
|
|
141
|
+
except (ArithmeticError, ValueError, ZeroDivisionError) as exc:
|
|
142
|
+
logger.debug("Pearson correlation failed (treating as 0.0): %s", exc)
|
|
143
|
+
return 0.0
|
|
144
|
+
|
|
145
|
+
def _extract_date(self, mem: dict) -> Optional[datetime]:
|
|
146
|
+
"""Extract datetime from a memory dict."""
|
|
147
|
+
created_at = mem.get("created_at")
|
|
148
|
+
if created_at is None:
|
|
149
|
+
return None
|
|
150
|
+
def _aware(dt):
|
|
151
|
+
# Mixed naive/aware datetimes make the later subtraction raise
|
|
152
|
+
# TypeError (swallowed upstream -> insights silently vanish).
|
|
153
|
+
if isinstance(dt, datetime) and dt.tzinfo is None:
|
|
154
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
155
|
+
return dt
|
|
156
|
+
if isinstance(created_at, datetime):
|
|
157
|
+
return _aware(created_at)
|
|
158
|
+
# Neo4j DateTime object
|
|
159
|
+
if hasattr(created_at, 'to_native'):
|
|
160
|
+
return _aware(created_at.to_native())
|
|
161
|
+
# ISO string
|
|
162
|
+
try:
|
|
163
|
+
if isinstance(created_at, str):
|
|
164
|
+
return _aware(datetime.fromisoformat(created_at.replace("Z", "+00:00")))
|
|
165
|
+
except (ValueError, AttributeError) as exc:
|
|
166
|
+
logger.debug("_extract_date: unparseable created_at %r: %s", created_at, exc)
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def _correlate_domain_memories(
|
|
170
|
+
self,
|
|
171
|
+
mems_a: list,
|
|
172
|
+
mems_b: list,
|
|
173
|
+
window_days: float = 3.0,
|
|
174
|
+
) -> tuple:
|
|
175
|
+
"""Find temporally co-occurring pairs between two memory lists.
|
|
176
|
+
|
|
177
|
+
Returns (pairs, correlation) where pairs is list of (mem_a, mem_b) tuples
|
|
178
|
+
and correlation is the Pearson correlation of their strength values.
|
|
179
|
+
"""
|
|
180
|
+
pairs = []
|
|
181
|
+
for ma in mems_a:
|
|
182
|
+
date_a = self._extract_date(ma)
|
|
183
|
+
if date_a is None:
|
|
184
|
+
continue
|
|
185
|
+
for mb in mems_b:
|
|
186
|
+
date_b = self._extract_date(mb)
|
|
187
|
+
if date_b is None:
|
|
188
|
+
continue
|
|
189
|
+
diff_days = abs((date_a - date_b).total_seconds()) / 86400
|
|
190
|
+
if diff_days <= window_days:
|
|
191
|
+
pairs.append((ma, mb))
|
|
192
|
+
if len(pairs) < 3:
|
|
193
|
+
return pairs, 0.0
|
|
194
|
+
strengths_a = [p[0].get("strength", 0.5) or 0.5 for p in pairs]
|
|
195
|
+
strengths_b = [p[1].get("strength", 0.5) or 0.5 for p in pairs]
|
|
196
|
+
corr = self._pearson_correlation(strengths_a, strengths_b)
|
|
197
|
+
return pairs, corr
|
|
198
|
+
|
|
199
|
+
async def _analyze_health_work(self) -> List[DomainInsight]:
|
|
200
|
+
"""Analyze health -> work availability patterns."""
|
|
201
|
+
try:
|
|
202
|
+
health_mems = await self.graph.recall(
|
|
203
|
+
"sleep score stress energy fatigue exercise",
|
|
204
|
+
limit=50, min_strength=0.2,
|
|
205
|
+
)
|
|
206
|
+
work_mems = await self.graph.recall(
|
|
207
|
+
"deadline meeting cancelled productivity output",
|
|
208
|
+
limit=50, min_strength=0.2,
|
|
209
|
+
)
|
|
210
|
+
pairs, correlation = self._correlate_domain_memories(health_mems, work_mems, window_days=3.0)
|
|
211
|
+
if len(pairs) < 3 or abs(correlation) < 0.4:
|
|
212
|
+
return []
|
|
213
|
+
evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
|
|
214
|
+
evidence = [e for e in evidence if e][:5]
|
|
215
|
+
return [DomainInsight(
|
|
216
|
+
id=str(uuid.uuid4()),
|
|
217
|
+
domains=["health", "work"],
|
|
218
|
+
insight_type="availability_prediction",
|
|
219
|
+
description="Low sleep scores correlate with reduced work output in the following days",
|
|
220
|
+
confidence=min(1.0, abs(correlation)),
|
|
221
|
+
actionable=True,
|
|
222
|
+
recommended_action="Block focus time after poor sleep nights",
|
|
223
|
+
supporting_evidence=evidence,
|
|
224
|
+
)]
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.debug("_analyze_health_work failed: %s", e)
|
|
227
|
+
return []
|
|
228
|
+
|
|
229
|
+
async def _analyze_relationships_health(self) -> List[DomainInsight]:
|
|
230
|
+
"""Analyze relationship dynamics -> health impact."""
|
|
231
|
+
try:
|
|
232
|
+
relationship_mems = await self.graph.recall(
|
|
233
|
+
"conflict argument tension difficult person stressful interaction",
|
|
234
|
+
limit=50, min_strength=0.2,
|
|
235
|
+
)
|
|
236
|
+
health_mems = await self.graph.recall(
|
|
237
|
+
"stress anxiety sleep disruption fatigue mood",
|
|
238
|
+
limit=50, min_strength=0.2,
|
|
239
|
+
)
|
|
240
|
+
pairs, correlation = self._correlate_domain_memories(relationship_mems, health_mems, window_days=2.0)
|
|
241
|
+
if len(pairs) < 3 or abs(correlation) < 0.4:
|
|
242
|
+
return []
|
|
243
|
+
evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
|
|
244
|
+
evidence = [e for e in evidence if e][:5]
|
|
245
|
+
return [DomainInsight(
|
|
246
|
+
id=str(uuid.uuid4()),
|
|
247
|
+
domains=["relationships", "health"],
|
|
248
|
+
insight_type="stress_pattern",
|
|
249
|
+
description="Difficult relationship interactions correlate with health signal spikes within 48 hours",
|
|
250
|
+
confidence=min(1.0, abs(correlation)),
|
|
251
|
+
actionable=True,
|
|
252
|
+
recommended_action="Schedule recovery time after high-conflict interactions",
|
|
253
|
+
supporting_evidence=evidence,
|
|
254
|
+
)]
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.debug("_analyze_relationships_health failed: %s", e)
|
|
257
|
+
return []
|
|
258
|
+
|
|
259
|
+
async def _analyze_work_relationships(self) -> List[DomainInsight]:
|
|
260
|
+
"""Analyze work patterns -> relationship impact."""
|
|
261
|
+
try:
|
|
262
|
+
work_stress_mems = await self.graph.recall(
|
|
263
|
+
"deadline crunch sprint overloaded travel busy",
|
|
264
|
+
limit=50, min_strength=0.2,
|
|
265
|
+
)
|
|
266
|
+
social_mems = await self.graph.recall(
|
|
267
|
+
"cancelled plans late reply short message ignored social",
|
|
268
|
+
limit=50, min_strength=0.2,
|
|
269
|
+
)
|
|
270
|
+
pairs, correlation = self._correlate_domain_memories(work_stress_mems, social_mems, window_days=3.0)
|
|
271
|
+
if len(pairs) < 3 or abs(correlation) < 0.4:
|
|
272
|
+
return []
|
|
273
|
+
evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
|
|
274
|
+
evidence = [e for e in evidence if e][:5]
|
|
275
|
+
return [DomainInsight(
|
|
276
|
+
id=str(uuid.uuid4()),
|
|
277
|
+
domains=["work", "relationships"],
|
|
278
|
+
insight_type="communication_shift",
|
|
279
|
+
description="Heavy work stress periods correlate with reduced social responsiveness",
|
|
280
|
+
confidence=min(1.0, abs(correlation)),
|
|
281
|
+
actionable=True,
|
|
282
|
+
recommended_action="Proactively communicate reduced availability during high-load periods",
|
|
283
|
+
supporting_evidence=evidence,
|
|
284
|
+
)]
|
|
285
|
+
except Exception as e:
|
|
286
|
+
logger.debug("_analyze_work_relationships failed: %s", e)
|
|
287
|
+
return []
|