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,560 @@
|
|
|
1
|
+
"""Colony relationship scoring — evidence-based assessment of Colony's own social world.
|
|
2
|
+
|
|
3
|
+
Colony maintains its own relationship scores for every person it interacts with across
|
|
4
|
+
connected gateways (iMessage, Telegram, etc.). These scores represent Colony's independent
|
|
5
|
+
assessment of each relationship, built from interaction patterns Colony itself observes.
|
|
6
|
+
|
|
7
|
+
Two-layer scoring model
|
|
8
|
+
-----------------------
|
|
9
|
+
Owner layer (``owner_guidance`` weight = 0.18): the owner's explicit input informs
|
|
10
|
+
Colony's view but does not dominate it. Owner input is authoritative at onboarding
|
|
11
|
+
when Colony has no history; its relative weight naturally declines as Colony accumulates
|
|
12
|
+
its own evidence.
|
|
13
|
+
|
|
14
|
+
Colony observation layer (remaining 0.82): all signals Colony observes directly —
|
|
15
|
+
message frequency, sentiment, latency, initiative, recency — plus three Colony-specific
|
|
16
|
+
signals that capture the quality of Colony's own engagement: ``colony_initiative_response``
|
|
17
|
+
(do people respond to Colony's outreach?), ``colony_effectiveness`` (do Colony's actions
|
|
18
|
+
help?), and ``colony_style_fit`` (is Colony's adapted style working?).
|
|
19
|
+
|
|
20
|
+
A score of 80+ means Colony has observed strong, consistent engagement with this person
|
|
21
|
+
over time — not merely that the owner considers them important.
|
|
22
|
+
"""
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import Optional, Dict, Any, List
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
import logging
|
|
27
|
+
import math
|
|
28
|
+
import time
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ScoreWeights:
|
|
35
|
+
"""Weights for different scoring dimensions.
|
|
36
|
+
|
|
37
|
+
Total must sum to 1.0.
|
|
38
|
+
|
|
39
|
+
Interaction signals are Colony's direct observations of the exchange.
|
|
40
|
+
Owner guidance informs but does not dominate.
|
|
41
|
+
Colony-specific signals capture the quality of Colony's own behavior.
|
|
42
|
+
"""
|
|
43
|
+
# Interaction signals (Colony's observations of the exchange)
|
|
44
|
+
message_frequency: float = 0.12
|
|
45
|
+
response_latency: float = 0.08
|
|
46
|
+
sentiment: float = 0.12
|
|
47
|
+
initiative_ratio: float = 0.08
|
|
48
|
+
meeting_coattendance: float = 0.08
|
|
49
|
+
task_involvement: float = 0.08
|
|
50
|
+
recency: float = 0.08
|
|
51
|
+
|
|
52
|
+
# Owner guidance (informs but does not dominate)
|
|
53
|
+
owner_guidance: float = 0.18
|
|
54
|
+
|
|
55
|
+
# Colony-specific signals (new)
|
|
56
|
+
colony_initiative_response: float = 0.08 # response rate to Colony's own outreach
|
|
57
|
+
colony_effectiveness: float = 0.06 # are Colony's suggestions/actions valued?
|
|
58
|
+
colony_style_fit: float = 0.04 # how well Colony's adapted style is received
|
|
59
|
+
|
|
60
|
+
def total(self) -> float:
|
|
61
|
+
return sum([
|
|
62
|
+
self.message_frequency,
|
|
63
|
+
self.response_latency,
|
|
64
|
+
self.sentiment,
|
|
65
|
+
self.initiative_ratio,
|
|
66
|
+
self.meeting_coattendance,
|
|
67
|
+
self.task_involvement,
|
|
68
|
+
self.recency,
|
|
69
|
+
self.owner_guidance,
|
|
70
|
+
self.colony_initiative_response,
|
|
71
|
+
self.colony_effectiveness,
|
|
72
|
+
self.colony_style_fit,
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ScoreBreakdown:
|
|
78
|
+
"""Breakdown of Colony's score into source layers.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
person_id: The person this breakdown is for
|
|
82
|
+
total_score: Final 0-100 score
|
|
83
|
+
owner_layer_contribution: Points contributed by owner_guidance signal
|
|
84
|
+
colony_observation_contribution: Points from contact-behavior signals
|
|
85
|
+
colony_action_contribution: Points from Colony's own behavior signals
|
|
86
|
+
dominant_signal: Which single signal most influenced the score
|
|
87
|
+
"""
|
|
88
|
+
person_id: str
|
|
89
|
+
total_score: float
|
|
90
|
+
owner_layer_contribution: float
|
|
91
|
+
colony_observation_contribution: float
|
|
92
|
+
colony_action_contribution: float
|
|
93
|
+
dominant_signal: str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class ScoreAuditEntry:
|
|
98
|
+
"""Immutable audit record for score changes."""
|
|
99
|
+
person_id: str
|
|
100
|
+
old_score: float
|
|
101
|
+
new_score: float
|
|
102
|
+
old_tier: str
|
|
103
|
+
new_tier: str
|
|
104
|
+
delta: float
|
|
105
|
+
reason: str
|
|
106
|
+
timestamp: datetime
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class TierChangeProposal:
|
|
111
|
+
"""A Colony-generated proposal to change a person's tier.
|
|
112
|
+
|
|
113
|
+
Colony surfaces these via the briefing system. Owner approval is required
|
|
114
|
+
before any tier change takes effect.
|
|
115
|
+
"""
|
|
116
|
+
person_id: str
|
|
117
|
+
current_tier: str
|
|
118
|
+
proposed_tier: str
|
|
119
|
+
direction: str # "upward" | "downward"
|
|
120
|
+
evidence_score: float
|
|
121
|
+
reasoning: str
|
|
122
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class _SignalRecord:
|
|
127
|
+
"""Internal container for signal data from Neo4j."""
|
|
128
|
+
signal_type: str
|
|
129
|
+
normalized_value: float
|
|
130
|
+
days_old: float
|
|
131
|
+
direction: str = "contact"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class RelationshipScorer:
|
|
135
|
+
"""Colony's evidence-based scoring of its own relationships with each person.
|
|
136
|
+
|
|
137
|
+
Scores are Colony's independent assessment derived from interaction signals
|
|
138
|
+
(message patterns, sentiment, engagement, response cadence) observed across
|
|
139
|
+
all connected gateways. The owner's guidance is factored in via the
|
|
140
|
+
owner_guidance weight (0.18) but does not override Colony's evidence-based view.
|
|
141
|
+
|
|
142
|
+
Colony-specific signals (colony_initiative_response, colony_effectiveness,
|
|
143
|
+
colony_style_fit) capture the quality of Colony's own engagement behavior and
|
|
144
|
+
carry a combined 0.18 weight.
|
|
145
|
+
|
|
146
|
+
A score of 80+ means Colony has observed strong, consistent engagement with
|
|
147
|
+
this person over time — not merely that the owner considers them important.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
TIER_THRESHOLDS = {
|
|
151
|
+
"inner_circle": 80,
|
|
152
|
+
"trusted": 60,
|
|
153
|
+
"regular": 30,
|
|
154
|
+
"peripheral": 10,
|
|
155
|
+
}
|
|
156
|
+
WINDOW_DAYS = 90
|
|
157
|
+
|
|
158
|
+
# Normalization ranges for each signal type
|
|
159
|
+
SIGNAL_RANGES = {
|
|
160
|
+
"message_frequency": (0, 20, False), # (min, max, inverted)
|
|
161
|
+
"response_latency": (0, 480, True), # Lower is better
|
|
162
|
+
"sentiment": (-1, 1, False),
|
|
163
|
+
"initiative_ratio": (0, 1, False),
|
|
164
|
+
"message_length": (-3, 3, False), # Z-score
|
|
165
|
+
# Colony-specific signals are already normalized 0-1
|
|
166
|
+
"colony_initiative_response": (0, 1, False),
|
|
167
|
+
"colony_effectiveness": (0, 1, False),
|
|
168
|
+
"colony_style_fit": (0, 1, False),
|
|
169
|
+
"contact_response_to_colony": (0, 1, False),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Colony-specific signal types (direction="colony" or "bilateral")
|
|
173
|
+
COLONY_SIGNAL_TYPES = frozenset({
|
|
174
|
+
"colony_initiative",
|
|
175
|
+
"colony_initiative_response",
|
|
176
|
+
"colony_effectiveness",
|
|
177
|
+
"contact_response_to_colony",
|
|
178
|
+
"colony_style_fit",
|
|
179
|
+
"colony_style_signal",
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
def __init__(self, graph: "ColonyGraph", weights: Optional[ScoreWeights] = None, metrics=None):
|
|
183
|
+
self._metrics = metrics # Optional ColonyMetricsCollector
|
|
184
|
+
self.graph = graph
|
|
185
|
+
self.weights = weights or ScoreWeights()
|
|
186
|
+
|
|
187
|
+
async def compute_score(
|
|
188
|
+
self,
|
|
189
|
+
person_id: str,
|
|
190
|
+
) -> tuple[float, str]:
|
|
191
|
+
"""Compute relationship score from signals in window."""
|
|
192
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=self.WINDOW_DAYS)
|
|
193
|
+
|
|
194
|
+
# Fetch signals from graph
|
|
195
|
+
signals = await self._fetch_signals(person_id)
|
|
196
|
+
|
|
197
|
+
if not signals:
|
|
198
|
+
return 50.0, "regular" # Default
|
|
199
|
+
|
|
200
|
+
# Group by type, apply recency weighting
|
|
201
|
+
type_scores: Dict[str, float] = {}
|
|
202
|
+
type_counts: Dict[str, int] = {}
|
|
203
|
+
|
|
204
|
+
for sig in signals:
|
|
205
|
+
sig_type = sig.signal_type
|
|
206
|
+
value = sig.normalized_value
|
|
207
|
+
|
|
208
|
+
# Exponential recency decay
|
|
209
|
+
decay = math.exp(-sig.days_old / self.WINDOW_DAYS)
|
|
210
|
+
|
|
211
|
+
if sig_type not in type_scores:
|
|
212
|
+
type_scores[sig_type] = 0.0
|
|
213
|
+
type_counts[sig_type] = 0
|
|
214
|
+
type_scores[sig_type] += value * decay
|
|
215
|
+
type_counts[sig_type] += 1
|
|
216
|
+
|
|
217
|
+
# Normalize and weight
|
|
218
|
+
final_score = 0.0
|
|
219
|
+
weight_map = {
|
|
220
|
+
"message_frequency": self.weights.message_frequency,
|
|
221
|
+
"response_latency": self.weights.response_latency,
|
|
222
|
+
"sentiment": self.weights.sentiment,
|
|
223
|
+
"initiative_ratio": self.weights.initiative_ratio,
|
|
224
|
+
"message_length": self.weights.message_frequency * 0.5, # Split weight
|
|
225
|
+
# Colony-specific signals
|
|
226
|
+
"colony_initiative_response": self.weights.colony_initiative_response,
|
|
227
|
+
"contact_response_to_colony": self.weights.colony_initiative_response,
|
|
228
|
+
"colony_effectiveness": self.weights.colony_effectiveness,
|
|
229
|
+
"colony_style_fit": self.weights.colony_style_fit,
|
|
230
|
+
"colony_style_signal": self.weights.colony_style_fit,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for sig_type, total in type_scores.items():
|
|
234
|
+
if sig_type in weight_map and type_counts[sig_type] > 0:
|
|
235
|
+
avg = total / type_counts[sig_type]
|
|
236
|
+
normalized = self._normalize_to_100(sig_type, avg)
|
|
237
|
+
final_score += normalized * weight_map[sig_type]
|
|
238
|
+
|
|
239
|
+
# Owner's guidance contributes as one weighted signal.
|
|
240
|
+
# Informs Colony's assessment without overriding it.
|
|
241
|
+
final_score += 50.0 * self.weights.owner_guidance
|
|
242
|
+
|
|
243
|
+
# Clamp to 0-100
|
|
244
|
+
final_score = max(0.0, min(100.0, final_score))
|
|
245
|
+
|
|
246
|
+
# Determine tier
|
|
247
|
+
tier = self.tier_for(final_score)
|
|
248
|
+
|
|
249
|
+
if self._metrics is not None:
|
|
250
|
+
try:
|
|
251
|
+
self._metrics.record_score_computed(final_score)
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
return final_score, tier
|
|
256
|
+
|
|
257
|
+
async def compute_score_with_breakdown(
|
|
258
|
+
self,
|
|
259
|
+
person_id: str,
|
|
260
|
+
) -> tuple[float, str, ScoreBreakdown]:
|
|
261
|
+
"""Compute score and return a ScoreBreakdown explaining source contributions."""
|
|
262
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=self.WINDOW_DAYS)
|
|
263
|
+
signals = await self._fetch_signals(person_id)
|
|
264
|
+
|
|
265
|
+
if not signals:
|
|
266
|
+
bd = ScoreBreakdown(
|
|
267
|
+
person_id=person_id,
|
|
268
|
+
total_score=50.0,
|
|
269
|
+
owner_layer_contribution=50.0 * self.weights.owner_guidance,
|
|
270
|
+
colony_observation_contribution=0.0,
|
|
271
|
+
colony_action_contribution=0.0,
|
|
272
|
+
dominant_signal="owner_guidance",
|
|
273
|
+
)
|
|
274
|
+
return 50.0, "regular", bd
|
|
275
|
+
|
|
276
|
+
type_scores: Dict[str, float] = {}
|
|
277
|
+
type_counts: Dict[str, int] = {}
|
|
278
|
+
|
|
279
|
+
for sig in signals:
|
|
280
|
+
decay = math.exp(-sig.days_old / self.WINDOW_DAYS)
|
|
281
|
+
if sig.signal_type not in type_scores:
|
|
282
|
+
type_scores[sig.signal_type] = 0.0
|
|
283
|
+
type_counts[sig.signal_type] = 0
|
|
284
|
+
type_scores[sig.signal_type] += sig.normalized_value * decay
|
|
285
|
+
type_counts[sig.signal_type] += 1
|
|
286
|
+
|
|
287
|
+
weight_map = {
|
|
288
|
+
"message_frequency": self.weights.message_frequency,
|
|
289
|
+
"response_latency": self.weights.response_latency,
|
|
290
|
+
"sentiment": self.weights.sentiment,
|
|
291
|
+
"initiative_ratio": self.weights.initiative_ratio,
|
|
292
|
+
"message_length": self.weights.message_frequency * 0.5,
|
|
293
|
+
"colony_initiative_response": self.weights.colony_initiative_response,
|
|
294
|
+
"contact_response_to_colony": self.weights.colony_initiative_response,
|
|
295
|
+
"colony_effectiveness": self.weights.colony_effectiveness,
|
|
296
|
+
"colony_style_fit": self.weights.colony_style_fit,
|
|
297
|
+
"colony_style_signal": self.weights.colony_style_fit,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
colony_action_signals = {
|
|
301
|
+
"colony_initiative_response", "contact_response_to_colony",
|
|
302
|
+
"colony_effectiveness", "colony_style_fit", "colony_style_signal",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
contributions: Dict[str, float] = {}
|
|
306
|
+
observation_total = 0.0
|
|
307
|
+
action_total = 0.0
|
|
308
|
+
|
|
309
|
+
for sig_type, total in type_scores.items():
|
|
310
|
+
if sig_type in weight_map and type_counts[sig_type] > 0:
|
|
311
|
+
avg = total / type_counts[sig_type]
|
|
312
|
+
normalized = self._normalize_to_100(sig_type, avg)
|
|
313
|
+
contribution = normalized * weight_map[sig_type]
|
|
314
|
+
contributions[sig_type] = contribution
|
|
315
|
+
if sig_type in colony_action_signals:
|
|
316
|
+
action_total += contribution
|
|
317
|
+
else:
|
|
318
|
+
observation_total += contribution
|
|
319
|
+
|
|
320
|
+
owner_contribution = 50.0 * self.weights.owner_guidance
|
|
321
|
+
total_score = max(0.0, min(100.0, observation_total + action_total + owner_contribution))
|
|
322
|
+
tier = self.tier_for(total_score)
|
|
323
|
+
|
|
324
|
+
dominant = max(contributions, key=contributions.get) if contributions else "owner_guidance"
|
|
325
|
+
|
|
326
|
+
bd = ScoreBreakdown(
|
|
327
|
+
person_id=person_id,
|
|
328
|
+
total_score=total_score,
|
|
329
|
+
owner_layer_contribution=owner_contribution,
|
|
330
|
+
colony_observation_contribution=observation_total,
|
|
331
|
+
colony_action_contribution=action_total,
|
|
332
|
+
dominant_signal=dominant,
|
|
333
|
+
)
|
|
334
|
+
return total_score, tier, bd
|
|
335
|
+
|
|
336
|
+
def tier_for(self, score: float) -> str:
|
|
337
|
+
"""Map score to tier name."""
|
|
338
|
+
for tier_name, threshold in sorted(
|
|
339
|
+
self.TIER_THRESHOLDS.items(),
|
|
340
|
+
key=lambda x: -x[1]
|
|
341
|
+
):
|
|
342
|
+
if score >= threshold:
|
|
343
|
+
return tier_name
|
|
344
|
+
return "peripheral"
|
|
345
|
+
|
|
346
|
+
async def record_score_change(
|
|
347
|
+
self,
|
|
348
|
+
person_id: str,
|
|
349
|
+
new_score: float,
|
|
350
|
+
new_tier: str,
|
|
351
|
+
old_score: float,
|
|
352
|
+
reason: str,
|
|
353
|
+
store=None,
|
|
354
|
+
) -> ScoreAuditEntry:
|
|
355
|
+
"""Atomic score update with audit trail.
|
|
356
|
+
|
|
357
|
+
``store`` (optional SQLiteContactStore) is forwarded to the graph so the
|
|
358
|
+
behavioral score reverse-syncs onto the linked contact row. Without it the
|
|
359
|
+
computed score dead-ends in the graph and the contact's relationship_score
|
|
360
|
+
never reflects behavioral signals (the "scoring starved" symptom)."""
|
|
361
|
+
old_tier = self.tier_for(old_score)
|
|
362
|
+
entry = ScoreAuditEntry(
|
|
363
|
+
person_id=person_id,
|
|
364
|
+
old_score=old_score,
|
|
365
|
+
new_score=new_score,
|
|
366
|
+
old_tier=old_tier,
|
|
367
|
+
new_tier=new_tier,
|
|
368
|
+
delta=new_score - old_score,
|
|
369
|
+
reason=reason,
|
|
370
|
+
timestamp=datetime.now(timezone.utc),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Persist to graph (and reverse-sync to SQLite when a store is supplied)
|
|
374
|
+
await self.graph.record_score_change(
|
|
375
|
+
person_id=person_id,
|
|
376
|
+
new_score=new_score,
|
|
377
|
+
new_tier=new_tier,
|
|
378
|
+
old_score=old_score,
|
|
379
|
+
reason=reason,
|
|
380
|
+
store=store,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return entry
|
|
384
|
+
|
|
385
|
+
async def refresh_all_scores(self, store=None) -> Dict[str, tuple]:
|
|
386
|
+
"""Recompute scores for all active persons.
|
|
387
|
+
|
|
388
|
+
``store`` is forwarded to each ``record_score_change`` so behavioral scores
|
|
389
|
+
reverse-sync onto the linked SQLite contact rows."""
|
|
390
|
+
t0 = time.monotonic()
|
|
391
|
+
results = {}
|
|
392
|
+
no_signal_count = 0
|
|
393
|
+
tier_changes = 0
|
|
394
|
+
persons = await self.graph.get_all_people()
|
|
395
|
+
|
|
396
|
+
for person in persons:
|
|
397
|
+
person_id = person.get("id")
|
|
398
|
+
if not person_id:
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
old_score = person.get("score", 50.0)
|
|
402
|
+
old_tier = self.tier_for(old_score)
|
|
403
|
+
new_score, new_tier = await self.compute_score(person_id)
|
|
404
|
+
|
|
405
|
+
if new_score == 50.0 and new_tier == "regular":
|
|
406
|
+
no_signal_count += 1
|
|
407
|
+
|
|
408
|
+
if new_tier != old_tier:
|
|
409
|
+
tier_changes += 1
|
|
410
|
+
if self._metrics is not None:
|
|
411
|
+
try:
|
|
412
|
+
direction = "upward" if new_score > old_score else "downward"
|
|
413
|
+
self._metrics.record_tier_change(direction)
|
|
414
|
+
except Exception:
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
# Skip the no-signal default (50/"regular") — persisting it would
|
|
418
|
+
# clobber the contact's interaction-derived score. Only record a
|
|
419
|
+
# genuine, signal-backed change.
|
|
420
|
+
is_no_signal_default = (new_score == 50.0 and new_tier == "regular")
|
|
421
|
+
if not is_no_signal_default and abs(new_score - old_score) > 0.5:
|
|
422
|
+
await self.record_score_change(
|
|
423
|
+
person_id, new_score, new_tier, old_score, "periodic_refresh", store=store
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
results[person_id] = (new_score, new_tier)
|
|
427
|
+
|
|
428
|
+
elapsed_ms = (time.monotonic() - t0) * 1000
|
|
429
|
+
logger.info(
|
|
430
|
+
"Refresh scores: %d persons, %d tier changes, %d no-signal in %.0fms",
|
|
431
|
+
len(results), tier_changes, no_signal_count, elapsed_ms,
|
|
432
|
+
)
|
|
433
|
+
if self._metrics is not None:
|
|
434
|
+
try:
|
|
435
|
+
self._metrics.record_scoring_run(
|
|
436
|
+
latency_ms=elapsed_ms,
|
|
437
|
+
persons_scored=len(results),
|
|
438
|
+
tier_changes=tier_changes,
|
|
439
|
+
)
|
|
440
|
+
self._metrics.set_persons_no_signals(no_signal_count)
|
|
441
|
+
except Exception:
|
|
442
|
+
pass
|
|
443
|
+
|
|
444
|
+
return results
|
|
445
|
+
|
|
446
|
+
def propose_tier_change(
|
|
447
|
+
self,
|
|
448
|
+
person_id: str,
|
|
449
|
+
current_tier: str,
|
|
450
|
+
evidence_score: float,
|
|
451
|
+
days_at_score: int = 0,
|
|
452
|
+
interaction_count: int = 0,
|
|
453
|
+
) -> Optional[TierChangeProposal]:
|
|
454
|
+
"""Generate a tier-change proposal based on Colony's evidence.
|
|
455
|
+
|
|
456
|
+
Colony MAY propose tier changes (upward or downward) based on its
|
|
457
|
+
own observations. Proposals are surfaced via the briefing system and
|
|
458
|
+
require owner approval before taking effect.
|
|
459
|
+
|
|
460
|
+
Upward proposals require the score to have been above the threshold
|
|
461
|
+
for 30+ days with ≥20 interactions.
|
|
462
|
+
|
|
463
|
+
Downward proposals are triggered when the score has been below the
|
|
464
|
+
threshold for 21+ days with no manual override in place.
|
|
465
|
+
|
|
466
|
+
Returns a TierChangeProposal or None if no proposal is warranted.
|
|
467
|
+
"""
|
|
468
|
+
evidence_tier = self.tier_for(evidence_score)
|
|
469
|
+
|
|
470
|
+
if evidence_tier == current_tier:
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
tier_order = ["peripheral", "regular", "trusted", "inner_circle"]
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
current_idx = tier_order.index(current_tier)
|
|
477
|
+
evidence_idx = tier_order.index(evidence_tier)
|
|
478
|
+
except ValueError:
|
|
479
|
+
return None
|
|
480
|
+
|
|
481
|
+
if evidence_idx > current_idx:
|
|
482
|
+
# Upward proposal
|
|
483
|
+
if days_at_score < 30 or interaction_count < 20:
|
|
484
|
+
return None
|
|
485
|
+
direction = "upward"
|
|
486
|
+
reasoning = (
|
|
487
|
+
f"Colony has observed consistent engagement from this person "
|
|
488
|
+
f"(score {evidence_score:.0f}, {interaction_count} interactions "
|
|
489
|
+
f"over {days_at_score} days). Evidence suggests moving from "
|
|
490
|
+
f"{current_tier} to {evidence_tier}. Approve?"
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
# Downward proposal
|
|
494
|
+
if days_at_score < 21:
|
|
495
|
+
return None
|
|
496
|
+
direction = "downward"
|
|
497
|
+
reasoning = (
|
|
498
|
+
f"Colony's engagement with this person has declined "
|
|
499
|
+
f"(score {evidence_score:.0f} for {days_at_score} days). "
|
|
500
|
+
f"Suggest moving from {current_tier} to {evidence_tier} "
|
|
501
|
+
f"unless this person should remain {current_tier}."
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return TierChangeProposal(
|
|
505
|
+
person_id=person_id,
|
|
506
|
+
current_tier=current_tier,
|
|
507
|
+
proposed_tier=evidence_tier,
|
|
508
|
+
direction=direction,
|
|
509
|
+
evidence_score=evidence_score,
|
|
510
|
+
reasoning=reasoning,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
async def _fetch_signals(
|
|
514
|
+
self,
|
|
515
|
+
person_id: str,
|
|
516
|
+
) -> list:
|
|
517
|
+
"""Fetch signals from Neo4j within window."""
|
|
518
|
+
try:
|
|
519
|
+
raw_signals = await self.graph.get_recent_signals(
|
|
520
|
+
person_id, hours=self.WINDOW_DAYS * 24
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
signals = []
|
|
524
|
+
now = datetime.now(timezone.utc)
|
|
525
|
+
for sig in raw_signals:
|
|
526
|
+
days_old = (now - sig.timestamp).total_seconds() / 86400
|
|
527
|
+
direction = getattr(sig, "direction", "contact")
|
|
528
|
+
signals.append(_SignalRecord(
|
|
529
|
+
signal_type=sig.signal_type,
|
|
530
|
+
normalized_value=sig.normalized_value,
|
|
531
|
+
days_old=days_old,
|
|
532
|
+
direction=direction,
|
|
533
|
+
))
|
|
534
|
+
return signals
|
|
535
|
+
except Exception:
|
|
536
|
+
return []
|
|
537
|
+
|
|
538
|
+
def _normalize_to_100(self, signal_type: str, value: float) -> float:
|
|
539
|
+
"""Normalize signal value to 0-100 range."""
|
|
540
|
+
if signal_type not in self.SIGNAL_RANGES:
|
|
541
|
+
return 50.0 # Neutral
|
|
542
|
+
|
|
543
|
+
min_val, max_val, inverted = self.SIGNAL_RANGES[signal_type]
|
|
544
|
+
|
|
545
|
+
# Clamp value to range
|
|
546
|
+
clamped = max(min_val, min(max_val, value))
|
|
547
|
+
|
|
548
|
+
# Normalize to 0-1
|
|
549
|
+
normalized = (clamped - min_val) / (max_val - min_val)
|
|
550
|
+
|
|
551
|
+
# Invert if needed
|
|
552
|
+
if inverted:
|
|
553
|
+
normalized = 1.0 - normalized
|
|
554
|
+
|
|
555
|
+
# Scale to 0-100
|
|
556
|
+
return normalized * 100.0
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
# Import timedelta for cutoff calculation
|
|
560
|
+
from datetime import timedelta
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Retain direct-exchange provenance without treating scalar closeness as evidence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def min_direct_exchanges() -> int:
|
|
10
|
+
try:
|
|
11
|
+
return max(1, int(os.environ.get("COLONY_RELATIONSHIP_MIN_EXCHANGES", "3")))
|
|
12
|
+
except (TypeError, ValueError):
|
|
13
|
+
return 3
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_direct_interlocutor(candidate: Dict[str, Any]) -> bool:
|
|
17
|
+
"""True only with recorded direct-exchange evidence at/above the floor.
|
|
18
|
+
|
|
19
|
+
Missing evidence means passively observed -- fail closed.
|
|
20
|
+
"""
|
|
21
|
+
count = candidate.get("interaction_count")
|
|
22
|
+
try:
|
|
23
|
+
return count is not None and int(count) >= min_direct_exchanges()
|
|
24
|
+
except (TypeError, ValueError):
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def filter_relationship_candidates(candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
29
|
+
"""Direct-exchange provenance only; scalar closeness never proves usefulness.
|
|
30
|
+
|
|
31
|
+
The caller still needs an actual purpose and permission for any outreach.
|
|
32
|
+
Legacy scores are removed before candidates reach planning prompts.
|
|
33
|
+
"""
|
|
34
|
+
return [{k: v for k, v in c.items() if k not in {'relationship_score', 'score', 'score_events'}}
|
|
35
|
+
for c in candidates if is_direct_interlocutor(c)]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def enrich_interaction_counts(
|
|
39
|
+
candidates: List[Dict[str, Any]],
|
|
40
|
+
contacts_store: Any,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Fill ``interaction_count`` from the contact
|
|
43
|
+
record's direct-exchange data. Candidates that resolve to no contact keep
|
|
44
|
+
no count and will fail the provenance gate (correct: no direct-exchange
|
|
45
|
+
evidence means passively observed)."""
|
|
46
|
+
if contacts_store is None:
|
|
47
|
+
return
|
|
48
|
+
for c in candidates:
|
|
49
|
+
if c.get("interaction_count") is not None:
|
|
50
|
+
continue
|
|
51
|
+
entity_id = c.get("entity_id") or ""
|
|
52
|
+
contact = None
|
|
53
|
+
try:
|
|
54
|
+
if hasattr(contacts_store, "get"):
|
|
55
|
+
contact = await contacts_store.get(entity_id)
|
|
56
|
+
if contact is None and hasattr(contacts_store, "find_by_person_node_id"):
|
|
57
|
+
contact = await contacts_store.find_by_person_node_id(entity_id)
|
|
58
|
+
except Exception:
|
|
59
|
+
contact = None
|
|
60
|
+
if contact is None:
|
|
61
|
+
continue
|
|
62
|
+
count = getattr(contact, "interaction_count", None)
|
|
63
|
+
if count is None and isinstance(contact, dict):
|
|
64
|
+
count = contact.get("interaction_count")
|
|
65
|
+
if count is not None:
|
|
66
|
+
c["interaction_count"] = int(count)
|