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,413 @@
|
|
|
1
|
+
"""Anomaly Detector — detect unusual patterns in behavior and data.
|
|
2
|
+
|
|
3
|
+
Detects:
|
|
4
|
+
- Unusual communication patterns
|
|
5
|
+
- Anomalous health metrics
|
|
6
|
+
- Abnormal relationship signals
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Minimum fractional deviation from baseline to generate an anomaly
|
|
19
|
+
_ANOMALY_DEVIATION_THRESHOLD = 0.3
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AnomalyType(str, Enum):
|
|
23
|
+
"""Categories of detectable anomalies."""
|
|
24
|
+
|
|
25
|
+
COMMUNICATION = "communication"
|
|
26
|
+
HEALTH = "health"
|
|
27
|
+
BEHAVIOR = "behavior"
|
|
28
|
+
RELATIONSHIP = "relationship"
|
|
29
|
+
# Phase E: conversation-sourced anomaly types
|
|
30
|
+
CONVERSATION_TIMING = "conversation_timing" # unusual message time vs baseline
|
|
31
|
+
TONE_SHIFT = "tone_shift" # sentiment significantly changed
|
|
32
|
+
SUDDEN_SILENCE = "sudden_silence" # person who messages daily gone quiet
|
|
33
|
+
TOPIC_ANOMALY = "topic_anomaly" # far outside normal topic pattern
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Anomaly:
|
|
38
|
+
"""A detected anomaly.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
id: Unique anomaly identifier
|
|
42
|
+
type: Category of anomaly
|
|
43
|
+
description: Human-readable description
|
|
44
|
+
severity: How severe the anomaly is (0-1, higher = more severe)
|
|
45
|
+
entity_id: Optional related entity (person, device, etc.)
|
|
46
|
+
baseline_value: Expected/normal value
|
|
47
|
+
observed_value: Actual observed value
|
|
48
|
+
detected_at: When the anomaly was detected
|
|
49
|
+
context: Additional anomaly-specific metadata
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
type: AnomalyType
|
|
54
|
+
description: str
|
|
55
|
+
severity: float
|
|
56
|
+
entity_id: Optional[str] = None
|
|
57
|
+
baseline_value: Any = None
|
|
58
|
+
observed_value: Any = None
|
|
59
|
+
detected_at: datetime = field(default_factory=datetime.now)
|
|
60
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AnomalyDetector:
|
|
64
|
+
"""Detect anomalies across domains.
|
|
65
|
+
|
|
66
|
+
Compares observed values against learned baselines to surface
|
|
67
|
+
unusual patterns in communication, health, and behavior.
|
|
68
|
+
|
|
69
|
+
Observations are added via ``add_observation()``. Each observation
|
|
70
|
+
is tagged with an ``AnomalyType`` so detection methods can filter
|
|
71
|
+
domain-specific data. Severity is computed as the fractional
|
|
72
|
+
deviation from the baseline, capped at 1.0.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
graph_client: Colony graph client for baseline data
|
|
76
|
+
event_bus: Colony event bus for anomaly alert events
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, graph_client: Any, event_bus: Any) -> None:
|
|
80
|
+
self.graph = graph_client
|
|
81
|
+
self.events = event_bus
|
|
82
|
+
self._baselines: Dict[str, Any] = {}
|
|
83
|
+
# key → list of (observed_value, anomaly_type) tuples
|
|
84
|
+
self._observations: Dict[str, List[Tuple[float, AnomalyType]]] = {}
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
# Observation ingestion
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
async def add_observation(
|
|
91
|
+
self,
|
|
92
|
+
entity_id: str,
|
|
93
|
+
metric: str,
|
|
94
|
+
value: float,
|
|
95
|
+
anomaly_type: AnomalyType = AnomalyType.BEHAVIOR,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Record an observed metric value for a given entity.
|
|
98
|
+
|
|
99
|
+
Does not immediately raise an alert — call ``detect()`` to check
|
|
100
|
+
all recorded observations against their baselines.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
entity_id: Entity the metric belongs to
|
|
104
|
+
metric: Metric name (e.g., "message_frequency")
|
|
105
|
+
value: Observed value
|
|
106
|
+
anomaly_type: Domain this observation belongs to
|
|
107
|
+
"""
|
|
108
|
+
key = f"{entity_id}.{metric}"
|
|
109
|
+
if key not in self._observations:
|
|
110
|
+
self._observations[key] = []
|
|
111
|
+
self._observations[key].append((value, anomaly_type))
|
|
112
|
+
logger.debug("Recorded observation: %s = %s (%s)", key, value, anomaly_type.value)
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
# Baseline management
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
async def update_baseline(
|
|
119
|
+
self,
|
|
120
|
+
entity_id: str,
|
|
121
|
+
metric: str,
|
|
122
|
+
value: Any,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Update the baseline for a metric.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
entity_id: Entity the metric belongs to
|
|
128
|
+
metric: Metric name
|
|
129
|
+
value: New baseline value
|
|
130
|
+
"""
|
|
131
|
+
key = f"{entity_id}.{metric}"
|
|
132
|
+
self._baselines[key] = value
|
|
133
|
+
logger.debug("Updated baseline: %s = %s", key, value)
|
|
134
|
+
|
|
135
|
+
async def get_baseline(self, entity_id: str, metric: str) -> Optional[Any]:
|
|
136
|
+
"""Get the current baseline for a metric.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
entity_id: Entity the metric belongs to
|
|
140
|
+
metric: Metric name
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Baseline value or None if not set
|
|
144
|
+
"""
|
|
145
|
+
key = f"{entity_id}.{metric}"
|
|
146
|
+
return self._baselines.get(key)
|
|
147
|
+
|
|
148
|
+
# ------------------------------------------------------------------
|
|
149
|
+
# Detection
|
|
150
|
+
# ------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------
|
|
153
|
+
# Phase E: Conversation-pattern observation helpers
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
async def observe_message_timing(
|
|
157
|
+
self,
|
|
158
|
+
entity_id: str,
|
|
159
|
+
hour_of_day: int,
|
|
160
|
+
baseline_hours: Optional[list] = None,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Record a message hour observation and update baseline if provided.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
entity_id: Person who sent the message
|
|
166
|
+
hour_of_day: UTC hour (0-23) the message was sent
|
|
167
|
+
baseline_hours: If provided, the list of typical hours for this person
|
|
168
|
+
"""
|
|
169
|
+
key = f"{entity_id}.message_hour"
|
|
170
|
+
if baseline_hours and len(baseline_hours) > 0:
|
|
171
|
+
# Use midpoint of typical hours as baseline
|
|
172
|
+
avg_hour = sum(baseline_hours) / len(baseline_hours)
|
|
173
|
+
await self.update_baseline(entity_id, "message_hour", avg_hour)
|
|
174
|
+
await self.add_observation(entity_id, "message_hour", float(hour_of_day),
|
|
175
|
+
anomaly_type=AnomalyType.CONVERSATION_TIMING)
|
|
176
|
+
logger.debug("Timing observation for %s: hour=%d", entity_id, hour_of_day)
|
|
177
|
+
|
|
178
|
+
async def observe_sentiment(
|
|
179
|
+
self,
|
|
180
|
+
entity_id: str,
|
|
181
|
+
sentiment_score: float,
|
|
182
|
+
baseline_sentiment: Optional[float] = None,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Record a message sentiment observation.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
entity_id: Person whose sentiment was measured
|
|
188
|
+
sentiment_score: Current sentiment (-1.0 to 1.0)
|
|
189
|
+
baseline_sentiment: Expected sentiment baseline for this person
|
|
190
|
+
"""
|
|
191
|
+
if baseline_sentiment is not None:
|
|
192
|
+
await self.update_baseline(entity_id, "sentiment", baseline_sentiment)
|
|
193
|
+
await self.add_observation(entity_id, "sentiment", sentiment_score,
|
|
194
|
+
anomaly_type=AnomalyType.TONE_SHIFT)
|
|
195
|
+
|
|
196
|
+
async def observe_silence_days(
|
|
197
|
+
self,
|
|
198
|
+
entity_id: str,
|
|
199
|
+
days_since_last_message: float,
|
|
200
|
+
baseline_days: float = 1.0,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Record a silence duration observation.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
entity_id: Person who has been silent
|
|
206
|
+
days_since_last_message: How many days since their last message
|
|
207
|
+
baseline_days: Expected messaging cadence in days
|
|
208
|
+
"""
|
|
209
|
+
await self.update_baseline(entity_id, "message_cadence_days", baseline_days)
|
|
210
|
+
await self.add_observation(entity_id, "message_cadence_days", days_since_last_message,
|
|
211
|
+
anomaly_type=AnomalyType.SUDDEN_SILENCE)
|
|
212
|
+
|
|
213
|
+
async def detect(
|
|
214
|
+
self,
|
|
215
|
+
domain: Optional[AnomalyType] = None,
|
|
216
|
+
threshold: float = 0.7,
|
|
217
|
+
) -> List[Anomaly]:
|
|
218
|
+
"""Detect anomalies, optionally filtered by domain.
|
|
219
|
+
|
|
220
|
+
Runs detection across all domains (or a specific one) and
|
|
221
|
+
returns anomalies above the severity threshold.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
domain: If provided, only check this domain
|
|
225
|
+
threshold: Minimum severity to include (0-1)
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
List of anomalies at or above the threshold
|
|
229
|
+
"""
|
|
230
|
+
anomalies: List[Anomaly] = []
|
|
231
|
+
|
|
232
|
+
if domain in (None, AnomalyType.COMMUNICATION):
|
|
233
|
+
anomalies.extend(await self._detect_communication_anomalies())
|
|
234
|
+
|
|
235
|
+
if domain in (None, AnomalyType.HEALTH):
|
|
236
|
+
anomalies.extend(await self._detect_health_anomalies())
|
|
237
|
+
|
|
238
|
+
if domain in (None, AnomalyType.BEHAVIOR):
|
|
239
|
+
anomalies.extend(await self._detect_behavior_anomalies())
|
|
240
|
+
|
|
241
|
+
if domain in (None, AnomalyType.RELATIONSHIP):
|
|
242
|
+
anomalies.extend(await self._detect_relationship_anomalies())
|
|
243
|
+
|
|
244
|
+
# Phase E: conversation-sourced anomaly types
|
|
245
|
+
if domain in (None, AnomalyType.CONVERSATION_TIMING):
|
|
246
|
+
anomalies.extend(await self._detect_conversation_timing_anomalies())
|
|
247
|
+
|
|
248
|
+
if domain in (None, AnomalyType.TONE_SHIFT):
|
|
249
|
+
anomalies.extend(await self._detect_tone_shift_anomalies())
|
|
250
|
+
|
|
251
|
+
if domain in (None, AnomalyType.SUDDEN_SILENCE):
|
|
252
|
+
anomalies.extend(await self._detect_sudden_silence_anomalies())
|
|
253
|
+
|
|
254
|
+
filtered = [a for a in anomalies if a.severity >= threshold]
|
|
255
|
+
|
|
256
|
+
logger.debug(
|
|
257
|
+
"Anomaly detection complete: %d found, %d above threshold %.2f",
|
|
258
|
+
len(anomalies),
|
|
259
|
+
len(filtered),
|
|
260
|
+
threshold,
|
|
261
|
+
)
|
|
262
|
+
return filtered
|
|
263
|
+
|
|
264
|
+
# ------------------------------------------------------------------
|
|
265
|
+
# Internal helpers
|
|
266
|
+
# ------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
def _compute_anomaly(
|
|
269
|
+
self,
|
|
270
|
+
entity_id: str,
|
|
271
|
+
metric: str,
|
|
272
|
+
observed: float,
|
|
273
|
+
baseline: float,
|
|
274
|
+
anomaly_type: AnomalyType,
|
|
275
|
+
) -> Optional[Anomaly]:
|
|
276
|
+
"""Compute an Anomaly if the observed value deviates significantly.
|
|
277
|
+
|
|
278
|
+
Severity = |observed - baseline| / max(|baseline|, 1.0), capped at 1.0.
|
|
279
|
+
Returns None if severity < _ANOMALY_DEVIATION_THRESHOLD.
|
|
280
|
+
"""
|
|
281
|
+
if baseline == 0:
|
|
282
|
+
severity = min(1.0, abs(observed) / max(abs(observed), 1.0)) if observed != 0 else 0.0
|
|
283
|
+
else:
|
|
284
|
+
severity = min(1.0, abs(observed - baseline) / abs(baseline))
|
|
285
|
+
|
|
286
|
+
if severity < _ANOMALY_DEVIATION_THRESHOLD:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
direction = "above" if observed > baseline else "below"
|
|
290
|
+
return Anomaly(
|
|
291
|
+
id=f"anomaly-{entity_id}-{metric}-{datetime.now().isoformat()}",
|
|
292
|
+
type=anomaly_type,
|
|
293
|
+
description=(
|
|
294
|
+
f"{metric} for {entity_id} is {direction} baseline: "
|
|
295
|
+
f"observed={observed}, baseline={baseline}"
|
|
296
|
+
),
|
|
297
|
+
severity=severity,
|
|
298
|
+
entity_id=entity_id,
|
|
299
|
+
baseline_value=baseline,
|
|
300
|
+
observed_value=observed,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def _detect_for_type(self, target_type: AnomalyType) -> List[Anomaly]:
|
|
304
|
+
"""Detect anomalies for observations of a given type."""
|
|
305
|
+
anomalies: List[Anomaly] = []
|
|
306
|
+
for key, observations in self._observations.items():
|
|
307
|
+
for obs_value, obs_type in observations:
|
|
308
|
+
if obs_type != target_type:
|
|
309
|
+
continue
|
|
310
|
+
baseline = self._baselines.get(key)
|
|
311
|
+
if baseline is None:
|
|
312
|
+
continue
|
|
313
|
+
# key is "entity_id.metric" — split on last dot
|
|
314
|
+
dot_idx = key.rfind(".")
|
|
315
|
+
if dot_idx < 0:
|
|
316
|
+
continue
|
|
317
|
+
entity_id = key[:dot_idx]
|
|
318
|
+
metric = key[dot_idx + 1:]
|
|
319
|
+
anomaly = self._compute_anomaly(entity_id, metric, obs_value, baseline, target_type)
|
|
320
|
+
if anomaly:
|
|
321
|
+
anomalies.append(anomaly)
|
|
322
|
+
return anomalies
|
|
323
|
+
|
|
324
|
+
async def _detect_communication_anomalies(self) -> List[Anomaly]:
|
|
325
|
+
"""Detect unusual communication patterns.
|
|
326
|
+
|
|
327
|
+
Compares message frequency, response times, and contact-pattern
|
|
328
|
+
observations against their stored baselines.
|
|
329
|
+
"""
|
|
330
|
+
return self._detect_for_type(AnomalyType.COMMUNICATION)
|
|
331
|
+
|
|
332
|
+
async def _detect_health_anomalies(self) -> List[Anomaly]:
|
|
333
|
+
"""Detect unusual health metrics (e.g., Oura/Whoop data)."""
|
|
334
|
+
return self._detect_for_type(AnomalyType.HEALTH)
|
|
335
|
+
|
|
336
|
+
async def _detect_behavior_anomalies(self) -> List[Anomaly]:
|
|
337
|
+
"""Detect unusual behavior patterns (activity, schedule adherence)."""
|
|
338
|
+
return self._detect_for_type(AnomalyType.BEHAVIOR)
|
|
339
|
+
|
|
340
|
+
async def _detect_relationship_anomalies(self) -> List[Anomaly]:
|
|
341
|
+
"""Detect unusual relationship signals (score trajectories)."""
|
|
342
|
+
return self._detect_for_type(AnomalyType.RELATIONSHIP)
|
|
343
|
+
|
|
344
|
+
async def _detect_conversation_timing_anomalies(self) -> List[Anomaly]:
|
|
345
|
+
"""Detect messages arriving at unusual times relative to baseline hours."""
|
|
346
|
+
anomalies = self._detect_for_type(AnomalyType.CONVERSATION_TIMING)
|
|
347
|
+
# Enrich descriptions for timing anomalies
|
|
348
|
+
for a in anomalies:
|
|
349
|
+
if "message_hour" in a.description:
|
|
350
|
+
obs_hour = int(a.observed_value) if a.observed_value is not None else "?"
|
|
351
|
+
baseline_hour = int(a.baseline_value) if a.baseline_value is not None else "?"
|
|
352
|
+
a.description = (
|
|
353
|
+
f"Unusual message timing for {a.entity_id}: received at hour {obs_hour}, "
|
|
354
|
+
f"baseline is hour {baseline_hour}"
|
|
355
|
+
)
|
|
356
|
+
return anomalies
|
|
357
|
+
|
|
358
|
+
async def _detect_tone_shift_anomalies(self) -> List[Anomaly]:
|
|
359
|
+
"""Detect significant sentiment shifts from baseline."""
|
|
360
|
+
anomalies = self._detect_for_type(AnomalyType.TONE_SHIFT)
|
|
361
|
+
for a in anomalies:
|
|
362
|
+
if "sentiment" in a.description:
|
|
363
|
+
direction = "more negative" if (a.observed_value or 0) < (a.baseline_value or 0) else "more positive"
|
|
364
|
+
a.description = (
|
|
365
|
+
f"Tone shift for {a.entity_id}: sentiment is {direction} than baseline "
|
|
366
|
+
f"(observed={a.observed_value:.2f}, baseline={a.baseline_value:.2f})"
|
|
367
|
+
)
|
|
368
|
+
return anomalies
|
|
369
|
+
|
|
370
|
+
async def _detect_sudden_silence_anomalies(self) -> List[Anomaly]:
|
|
371
|
+
"""Detect unusual silence from a person who normally messages regularly."""
|
|
372
|
+
anomalies = self._detect_for_type(AnomalyType.SUDDEN_SILENCE)
|
|
373
|
+
for a in anomalies:
|
|
374
|
+
if "message_cadence" in a.description:
|
|
375
|
+
obs_days = f"{a.observed_value:.1f}" if a.observed_value is not None else "?"
|
|
376
|
+
baseline_days = f"{a.baseline_value:.1f}" if a.baseline_value is not None else "?"
|
|
377
|
+
a.description = (
|
|
378
|
+
f"Sudden silence: {a.entity_id} hasn't messaged in {obs_days} days "
|
|
379
|
+
f"(baseline: every {baseline_days} day(s))"
|
|
380
|
+
)
|
|
381
|
+
return anomalies
|
|
382
|
+
|
|
383
|
+
# ------------------------------------------------------------------
|
|
384
|
+
# Convenience API for the autonomy loop
|
|
385
|
+
# ------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
def get_recent(
|
|
388
|
+
self,
|
|
389
|
+
min_severity: float = 0.6,
|
|
390
|
+
limit: int = 20,
|
|
391
|
+
) -> List[Anomaly]:
|
|
392
|
+
"""Return recently detected anomalies above min_severity.
|
|
393
|
+
|
|
394
|
+
Synchronous convenience wrapper over the in-memory observation
|
|
395
|
+
store. Does not re-run full async detection — uses cached results
|
|
396
|
+
from the last ``detect()`` call if available, otherwise runs
|
|
397
|
+
synchronous detection on the current observation set.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
min_severity: Minimum severity threshold (0–1)
|
|
401
|
+
limit: Maximum number of anomalies to return
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
List of Anomaly objects at or above min_severity, newest first.
|
|
405
|
+
"""
|
|
406
|
+
anomalies: List[Anomaly] = []
|
|
407
|
+
for anom_type in AnomalyType:
|
|
408
|
+
anomalies.extend(self._detect_for_type(anom_type))
|
|
409
|
+
|
|
410
|
+
filtered = [a for a in anomalies if a.severity >= min_severity]
|
|
411
|
+
# Sort by severity descending, then by detection time descending
|
|
412
|
+
filtered.sort(key=lambda a: (a.severity, a.detected_at.timestamp()), reverse=True)
|
|
413
|
+
return filtered[:limit]
|