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,329 @@
|
|
|
1
|
+
"""IdentityResolver — normalize person identifiers across Colony subsystems.
|
|
2
|
+
|
|
3
|
+
A person is referenced by different identifier formats depending on the
|
|
4
|
+
subsystem:
|
|
5
|
+
|
|
6
|
+
- Contact store: CID (``cid-<ts>-<rand>``), display name, platform handles
|
|
7
|
+
- Neo4j graph: Person node ID (UUID), ``Person.name``
|
|
8
|
+
- Affect store: ``contact_id`` (whatever the caller passed — historically
|
|
9
|
+
display names or CIDs)
|
|
10
|
+
|
|
11
|
+
The contact store is the source of truth (Step 0 finding, v0.16.0 spec):
|
|
12
|
+
a Contact record carries the CID, names, the linked Neo4j node via
|
|
13
|
+
``person_node_id``, and all platform handles via the ``contact_handles``
|
|
14
|
+
table. The resolver is an index over it.
|
|
15
|
+
|
|
16
|
+
Owner identity rules:
|
|
17
|
+
- The owner is resolved once from ``COLONY_OWNER_CONTACT_ID`` and cached.
|
|
18
|
+
- ``COLONY_HOST_CONTACT_ID`` is accepted as a deprecated alias.
|
|
19
|
+
- A configured-but-unresolvable owner raises :class:`OwnerIdentityError`.
|
|
20
|
+
There is NO fallback to a default string — the silent ``"owner"``
|
|
21
|
+
default was the bug this module replaces. Callers that filter the owner
|
|
22
|
+
out of generated work must fail closed (generate nothing) rather than
|
|
23
|
+
fall through.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
from typing import Any, Optional, Set
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
_PHONE_RE = re.compile(r"^\+?[\d\s().-]{7,}$")
|
|
36
|
+
|
|
37
|
+
from apsimo.channels.phone_gateways import get_phone_gateways as _get_phone_gateways
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OwnerIdentityError(RuntimeError):
|
|
41
|
+
"""The owner's identity is missing or cannot be resolved."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_owner_contact_id() -> Optional[str]:
|
|
45
|
+
"""Return the configured owner contact ID, honouring the legacy alias.
|
|
46
|
+
|
|
47
|
+
``COLONY_OWNER_CONTACT_ID`` is canonical. ``COLONY_HOST_CONTACT_ID``
|
|
48
|
+
is accepted with a deprecation warning so running deployments keep
|
|
49
|
+
their config. Returns None when neither is set — callers must treat
|
|
50
|
+
that as "owner unknown", never as a default string.
|
|
51
|
+
"""
|
|
52
|
+
owner = os.environ.get("COLONY_OWNER_CONTACT_ID")
|
|
53
|
+
if owner:
|
|
54
|
+
return owner
|
|
55
|
+
legacy = os.environ.get("COLONY_HOST_CONTACT_ID")
|
|
56
|
+
if legacy:
|
|
57
|
+
logger.warning(
|
|
58
|
+
"COLONY_HOST_CONTACT_ID is deprecated, use COLONY_OWNER_CONTACT_ID"
|
|
59
|
+
)
|
|
60
|
+
return legacy
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_owner_name(default: str = "the owner") -> str:
|
|
65
|
+
"""Human-readable owner name for prompts/messages.
|
|
66
|
+
|
|
67
|
+
Deployment-agnostic: the generic build must never hardcode a person's name.
|
|
68
|
+
Set ``COLONY_OWNER_NAME`` in the deployment env; otherwise a neutral term
|
|
69
|
+
("the owner") is used so the public code carries no personal identity.
|
|
70
|
+
"""
|
|
71
|
+
return os.environ.get("COLONY_OWNER_NAME") or default
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_persona_name(default: str = "the assistant") -> str:
|
|
75
|
+
"""Human-readable persona/agent name for tool descriptions and prompts.
|
|
76
|
+
|
|
77
|
+
Same rule: the deployment sets ``COLONY_PERSONA_NAME``; the generic default
|
|
78
|
+
is neutral so the public code names no specific product.
|
|
79
|
+
"""
|
|
80
|
+
return os.environ.get("COLONY_PERSONA_NAME") or default
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class IdentityResolver:
|
|
84
|
+
"""Resolve any identifier for a person to the set of all known forms.
|
|
85
|
+
|
|
86
|
+
Backed by the contact store (async API). When the contact store is
|
|
87
|
+
unavailable the resolver degrades to exact-match on the configured
|
|
88
|
+
value only — it never invents identifiers.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
contact_store: Any = None,
|
|
94
|
+
owner_id: Optional[str] = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
self._contact_store = contact_store
|
|
97
|
+
# Capture explicitly so tests can construct without env mutation.
|
|
98
|
+
self._owner_id = owner_id if owner_id is not None else get_owner_contact_id()
|
|
99
|
+
self._owner_set: Optional[frozenset] = None
|
|
100
|
+
self._identity_revision = None
|
|
101
|
+
# Small bounded cache of is_owner verdicts (one tick scans ~100
|
|
102
|
+
# contacts; name resolution is a full-table similarity scan).
|
|
103
|
+
self._is_owner_cache: dict = {}
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
# Public API
|
|
107
|
+
# ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
async def resolve(self, any_id: str) -> Set[str]:
|
|
110
|
+
"""Given any identifier for a person, return all known forms.
|
|
111
|
+
|
|
112
|
+
Returns an empty set when the identifier is unknown or ambiguous
|
|
113
|
+
(display names are not unique — an ambiguous name must not merge
|
|
114
|
+
two people into one).
|
|
115
|
+
"""
|
|
116
|
+
if not any_id or not isinstance(any_id, str):
|
|
117
|
+
return set()
|
|
118
|
+
contact = await self._lookup(any_id.strip())
|
|
119
|
+
if contact is None:
|
|
120
|
+
return set()
|
|
121
|
+
return await self._identity_set(contact)
|
|
122
|
+
|
|
123
|
+
async def is_owner(self, any_id: Any) -> bool:
|
|
124
|
+
"""True if ``any_id`` resolves into the owner's identity set.
|
|
125
|
+
|
|
126
|
+
Raises :class:`OwnerIdentityError` when the owner identity itself
|
|
127
|
+
cannot be established (missing config, or config that resolves to
|
|
128
|
+
nothing). Callers in generation paths must fail closed on that.
|
|
129
|
+
"""
|
|
130
|
+
if not any_id:
|
|
131
|
+
return False
|
|
132
|
+
owners = await self.owner_identities()
|
|
133
|
+
key = str(any_id).strip()
|
|
134
|
+
cached = self._is_owner_cache.get(key)
|
|
135
|
+
if cached is not None:
|
|
136
|
+
return cached
|
|
137
|
+
|
|
138
|
+
verdict = key in owners or key.lower() in owners
|
|
139
|
+
if not verdict:
|
|
140
|
+
# Cross-format check: resolve the candidate and intersect.
|
|
141
|
+
try:
|
|
142
|
+
forms = await self.resolve(key)
|
|
143
|
+
except Exception as exc: # resolution must never break filtering
|
|
144
|
+
logger.warning("Identity resolution failed for %r: %s", key, exc)
|
|
145
|
+
forms = set()
|
|
146
|
+
verdict = bool(forms & owners)
|
|
147
|
+
|
|
148
|
+
if len(self._is_owner_cache) > 512:
|
|
149
|
+
self._is_owner_cache.clear()
|
|
150
|
+
self._is_owner_cache[key] = verdict
|
|
151
|
+
return verdict
|
|
152
|
+
|
|
153
|
+
async def owner_identities(self) -> frozenset:
|
|
154
|
+
"""Resolve and cache the owner's full identity set.
|
|
155
|
+
|
|
156
|
+
Raises :class:`OwnerIdentityError` if no owner is configured, or if
|
|
157
|
+
the configured value resolves to nothing in the contact store.
|
|
158
|
+
"""
|
|
159
|
+
revision_reader = getattr(self._contact_store, 'identity_revision', None)
|
|
160
|
+
if revision_reader is not None:
|
|
161
|
+
revision = await revision_reader()
|
|
162
|
+
if revision != self._identity_revision:
|
|
163
|
+
self._owner_set = None
|
|
164
|
+
self._is_owner_cache.clear()
|
|
165
|
+
self._identity_revision = revision
|
|
166
|
+
if self._owner_set is not None:
|
|
167
|
+
return self._owner_set
|
|
168
|
+
|
|
169
|
+
owner_id = self._owner_id
|
|
170
|
+
if not owner_id:
|
|
171
|
+
raise OwnerIdentityError(
|
|
172
|
+
"COLONY_OWNER_CONTACT_ID is not set. Owner-exclusion filters "
|
|
173
|
+
"cannot run; set it to the owner's contact CID, Neo4j Person "
|
|
174
|
+
"ID, or an unambiguous display name."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if self._contact_store is None:
|
|
178
|
+
# No identity index available — exact-match on the configured
|
|
179
|
+
# value (plus case folding). This is the operator's own value,
|
|
180
|
+
# not a made-up default, so it is safe to use directly.
|
|
181
|
+
logger.warning(
|
|
182
|
+
"Contact store unavailable — owner exclusion limited to "
|
|
183
|
+
"exact matches on %r", owner_id,
|
|
184
|
+
)
|
|
185
|
+
self._owner_set = frozenset({owner_id, owner_id.lower()})
|
|
186
|
+
return self._owner_set
|
|
187
|
+
|
|
188
|
+
forms = await self.resolve(owner_id)
|
|
189
|
+
if not forms:
|
|
190
|
+
raise OwnerIdentityError(
|
|
191
|
+
f"COLONY_OWNER_CONTACT_ID={owner_id!r} does not resolve to "
|
|
192
|
+
"any contact. Fix the config — refusing to guess."
|
|
193
|
+
)
|
|
194
|
+
# Always include the configured literal so equality checks against
|
|
195
|
+
# raw config values keep working.
|
|
196
|
+
forms |= {owner_id, owner_id.lower()}
|
|
197
|
+
self._owner_set = frozenset(forms)
|
|
198
|
+
logger.info(
|
|
199
|
+
"Owner identity resolved: %d identifier forms cached", len(forms)
|
|
200
|
+
)
|
|
201
|
+
return self._owner_set
|
|
202
|
+
|
|
203
|
+
def invalidate(self) -> None:
|
|
204
|
+
"""Drop cached owner identity and verdicts (e.g. after a contact merge)."""
|
|
205
|
+
self._owner_set = None
|
|
206
|
+
self._is_owner_cache.clear()
|
|
207
|
+
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
# Internals
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
async def _lookup(self, any_id: str) -> Any:
|
|
213
|
+
"""Find the Contact record for an identifier of any format."""
|
|
214
|
+
store = self._contact_store
|
|
215
|
+
if store is None:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
# CID — primary key lookup.
|
|
219
|
+
if any_id.startswith("cid-"):
|
|
220
|
+
return await store.get(any_id)
|
|
221
|
+
|
|
222
|
+
# Email handle.
|
|
223
|
+
if "@" in any_id:
|
|
224
|
+
return await store.resolve_handle("email", any_id)
|
|
225
|
+
|
|
226
|
+
# Phone-like handle.
|
|
227
|
+
if _PHONE_RE.match(any_id):
|
|
228
|
+
for gateway in _get_phone_gateways():
|
|
229
|
+
contact = await store.resolve_handle(gateway, any_id)
|
|
230
|
+
if contact is not None:
|
|
231
|
+
return contact
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
# Neo4j Person node ID (UUID) — forward link lives on the contact.
|
|
235
|
+
contact = await store.find_by_person_node_id(any_id)
|
|
236
|
+
if contact is not None:
|
|
237
|
+
return contact
|
|
238
|
+
|
|
239
|
+
# Display name — only when unambiguous. Names are not unique.
|
|
240
|
+
try:
|
|
241
|
+
matches = await store.find_by_name(any_id, threshold=0.9)
|
|
242
|
+
except Exception as exc:
|
|
243
|
+
logger.warning("find_by_name failed for %r: %s", any_id, exc)
|
|
244
|
+
return None
|
|
245
|
+
wanted = any_id.lower()
|
|
246
|
+
exact = [
|
|
247
|
+
m for m in matches
|
|
248
|
+
if (m.display_name or "").strip().lower() == wanted
|
|
249
|
+
]
|
|
250
|
+
candidates = exact or matches
|
|
251
|
+
if len(candidates) == 1:
|
|
252
|
+
return candidates[0]
|
|
253
|
+
if len(candidates) > 1:
|
|
254
|
+
logger.warning(
|
|
255
|
+
"Ambiguous identity %r: %d contacts match — returning no "
|
|
256
|
+
"identity rather than merging people", any_id, len(candidates),
|
|
257
|
+
)
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
async def _identity_set(self, contact: Any) -> Set[str]:
|
|
261
|
+
"""All known identifier forms for a resolved contact."""
|
|
262
|
+
forms: Set[str] = {contact.contact_id}
|
|
263
|
+
|
|
264
|
+
display_name = getattr(contact, "display_name", None)
|
|
265
|
+
if display_name:
|
|
266
|
+
forms.add(display_name)
|
|
267
|
+
given = getattr(contact, "given_name", None)
|
|
268
|
+
family = getattr(contact, "family_name", None)
|
|
269
|
+
if given and family:
|
|
270
|
+
forms.add(f"{given} {family}")
|
|
271
|
+
person_node_id = getattr(contact, "person_node_id", None)
|
|
272
|
+
if person_node_id:
|
|
273
|
+
forms.add(person_node_id)
|
|
274
|
+
|
|
275
|
+
if self._contact_store is not None:
|
|
276
|
+
try:
|
|
277
|
+
handles = await self._contact_store.get_handles(contact.contact_id)
|
|
278
|
+
for handle in handles:
|
|
279
|
+
from apsimo.contacts.identity_links import usable_handle
|
|
280
|
+
if not usable_handle(handle):
|
|
281
|
+
continue
|
|
282
|
+
address = getattr(handle, "address", None)
|
|
283
|
+
if address:
|
|
284
|
+
forms.add(address)
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
logger.debug(
|
|
287
|
+
"Handle lookup failed for %s: %s", contact.contact_id, exc
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Case-folded variants for case-insensitive membership checks.
|
|
291
|
+
forms |= {f.lower() for f in list(forms)}
|
|
292
|
+
return forms
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
# Module-level singleton
|
|
297
|
+
# ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
_resolver: Optional[IdentityResolver] = None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_identity_resolver(contact_store: Any = None) -> IdentityResolver:
|
|
303
|
+
"""Return the process-wide resolver, creating it on first use.
|
|
304
|
+
|
|
305
|
+
When ``contact_store`` is not supplied, the contact store registered
|
|
306
|
+
with the host router is used (same late-binding pattern as
|
|
307
|
+
``SubsystemRegistry``).
|
|
308
|
+
"""
|
|
309
|
+
global _resolver
|
|
310
|
+
if contact_store is None:
|
|
311
|
+
try:
|
|
312
|
+
from apsimo.api.routers import host as _host_mod
|
|
313
|
+
contact_store = getattr(_host_mod, "_contacts_store", None)
|
|
314
|
+
except Exception:
|
|
315
|
+
contact_store = None
|
|
316
|
+
|
|
317
|
+
if _resolver is None:
|
|
318
|
+
_resolver = IdentityResolver(contact_store=contact_store)
|
|
319
|
+
elif contact_store is not None and _resolver._contact_store is None:
|
|
320
|
+
# The contact store comes up after the resolver in some boot orders.
|
|
321
|
+
_resolver._contact_store = contact_store
|
|
322
|
+
_resolver.invalidate()
|
|
323
|
+
return _resolver
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def reset_identity_resolver() -> None:
|
|
327
|
+
"""Reset the singleton (tests and reconfiguration)."""
|
|
328
|
+
global _resolver
|
|
329
|
+
_resolver = None
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Colony Identity Bootstrap — IdentityBootstrapBuilder.
|
|
2
|
+
|
|
3
|
+
Assembles a SelfKnowledgeCorpus at runtime by reading colony_id, version,
|
|
4
|
+
network info, and public key from the running system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from apsimo.identity_bootstrap.corpus import (
|
|
14
|
+
CORPUS_VERSION,
|
|
15
|
+
GATE_LAYERS,
|
|
16
|
+
INFERENCE_TIERS,
|
|
17
|
+
API_ENDPOINTS,
|
|
18
|
+
COGNITION_PHASES,
|
|
19
|
+
LAYERS,
|
|
20
|
+
InferenceTier,
|
|
21
|
+
SelfKnowledgeCorpus,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _colony_version() -> str:
|
|
28
|
+
"""Return Colony version from canonical source."""
|
|
29
|
+
# Canonical: colony_cli.__init__ (full_version_string returns "0.3.0-h6")
|
|
30
|
+
try:
|
|
31
|
+
from colony_cli import full_version_string
|
|
32
|
+
return full_version_string()
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
# Fallback: importlib metadata (reads pyproject.toml version)
|
|
36
|
+
try:
|
|
37
|
+
from importlib.metadata import version
|
|
38
|
+
return version("colony")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
try:
|
|
42
|
+
from importlib.metadata import version
|
|
43
|
+
return version("colony-ai")
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
return "0.3.0-h6"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _read_colony_id(chain_manager: Optional[Any]) -> str:
|
|
50
|
+
"""Read colony_id from ChainManager, chain.db, or environment.
|
|
51
|
+
|
|
52
|
+
Priority order:
|
|
53
|
+
1. ChainManager.colony_id (if ChainManager is initialized)
|
|
54
|
+
2. COLONY_ID environment variable
|
|
55
|
+
3. Genesis block genesis_colony_id from chain.db (crypto identity)
|
|
56
|
+
4. Hostname-based fallback (last resort)
|
|
57
|
+
"""
|
|
58
|
+
if chain_manager is not None:
|
|
59
|
+
try:
|
|
60
|
+
cid = chain_manager.colony_id
|
|
61
|
+
if cid and cid != "local":
|
|
62
|
+
return str(cid)
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
logger.debug("ChainManager.colony_id unavailable: %s", exc)
|
|
65
|
+
|
|
66
|
+
env_id = os.environ.get("COLONY_ID", "")
|
|
67
|
+
if env_id:
|
|
68
|
+
return env_id
|
|
69
|
+
|
|
70
|
+
# Read genesis_colony_id directly from chain.db — this is the
|
|
71
|
+
# cryptographic identity (sha256 of Ed25519 pubkey) that proves
|
|
72
|
+
# genesis admin authority. Avoids the hostname fallback when the
|
|
73
|
+
# ChainManager singleton hasn't been constructed yet.
|
|
74
|
+
import json
|
|
75
|
+
import sqlite3
|
|
76
|
+
colony_home = os.environ.get(
|
|
77
|
+
"COLONY_HOME",
|
|
78
|
+
os.path.join(os.path.expanduser("~"), ".colony"),
|
|
79
|
+
)
|
|
80
|
+
chain_db = os.path.join(colony_home, "chain.db")
|
|
81
|
+
if os.path.exists(chain_db):
|
|
82
|
+
try:
|
|
83
|
+
conn = sqlite3.connect(chain_db)
|
|
84
|
+
row = conn.execute(
|
|
85
|
+
"SELECT raw_json FROM chain_blocks WHERE block_index = 0"
|
|
86
|
+
).fetchone()
|
|
87
|
+
conn.close()
|
|
88
|
+
if row:
|
|
89
|
+
genesis_data = json.loads(row[0])
|
|
90
|
+
cid = genesis_data.get("metadata", {}).get("genesis_colony_id")
|
|
91
|
+
if cid:
|
|
92
|
+
return cid
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
logger.debug("_read_colony_id chain.db fallback failed: %s", exc)
|
|
95
|
+
|
|
96
|
+
# Last resort: hostname-based stable ID
|
|
97
|
+
import socket
|
|
98
|
+
import hashlib
|
|
99
|
+
raw = f"{socket.gethostname()}-colony"
|
|
100
|
+
return "col-" + hashlib.sha256(raw.encode()).hexdigest()[:16]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _read_network_id(chain_manager: Optional[Any]) -> str:
|
|
104
|
+
try:
|
|
105
|
+
if chain_manager is not None:
|
|
106
|
+
state = getattr(chain_manager, "state", None)
|
|
107
|
+
if state is not None:
|
|
108
|
+
nid = getattr(state, "network_id", None) or getattr(state, "genesis_hash", None)
|
|
109
|
+
if nid:
|
|
110
|
+
return str(nid)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return os.environ.get("COLONY_NETWORK_ID", "local")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _read_public_key(chain_manager: Optional[Any]) -> Optional[str]:
|
|
117
|
+
try:
|
|
118
|
+
if chain_manager is not None:
|
|
119
|
+
keys = getattr(chain_manager, "keys", None)
|
|
120
|
+
if keys is not None:
|
|
121
|
+
pub = getattr(keys, "public_key_hex", None) or getattr(keys, "public_key", None)
|
|
122
|
+
if pub:
|
|
123
|
+
return str(pub)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _resolve_inference_tiers() -> list[InferenceTier]:
|
|
130
|
+
"""Return INFERENCE_TIERS with model names populated from the live LLMRouter config.
|
|
131
|
+
|
|
132
|
+
The static corpus stores no provider-specific model names. This function
|
|
133
|
+
reads them from ``colony.router.tiers.DEFAULT_TIERS`` at runtime so the
|
|
134
|
+
corpus always reflects whichever models are actually configured.
|
|
135
|
+
Falls back to the static definitions (empty model lists) if the router
|
|
136
|
+
module is unavailable.
|
|
137
|
+
"""
|
|
138
|
+
# Tier index → ModelTier enum value in the router
|
|
139
|
+
try:
|
|
140
|
+
from apsimo.router.tiers import DEFAULT_TIERS, ModelTier # type: ignore[import]
|
|
141
|
+
|
|
142
|
+
tier_index_to_router: dict[int, ModelTier] = {
|
|
143
|
+
0: ModelTier.SMALL,
|
|
144
|
+
1: ModelTier.MEDIUM,
|
|
145
|
+
2: ModelTier.LARGE,
|
|
146
|
+
}
|
|
147
|
+
resolved: list[InferenceTier] = []
|
|
148
|
+
for tier in INFERENCE_TIERS:
|
|
149
|
+
router_tier = tier_index_to_router.get(tier.tier_index)
|
|
150
|
+
if router_tier is not None and router_tier in DEFAULT_TIERS:
|
|
151
|
+
model_id = DEFAULT_TIERS[router_tier].model_id
|
|
152
|
+
resolved.append(
|
|
153
|
+
InferenceTier(
|
|
154
|
+
name=tier.name,
|
|
155
|
+
description=tier.description,
|
|
156
|
+
complexity_range=tier.complexity_range,
|
|
157
|
+
models=[model_id],
|
|
158
|
+
tier_index=tier.tier_index,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
resolved.append(tier)
|
|
163
|
+
return resolved
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
logger.debug("Could not resolve inference tiers from router config: %s", exc)
|
|
166
|
+
return list(INFERENCE_TIERS)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class IdentityBootstrapBuilder:
|
|
170
|
+
"""Builds a SelfKnowledgeCorpus by introspecting the running Colony instance."""
|
|
171
|
+
|
|
172
|
+
def __init__(self, chain_manager: Optional[Any] = None) -> None:
|
|
173
|
+
self._chain_manager = chain_manager
|
|
174
|
+
|
|
175
|
+
def build(self) -> SelfKnowledgeCorpus:
|
|
176
|
+
colony_id = _read_colony_id(self._chain_manager)
|
|
177
|
+
colony_name = os.environ.get("COLONY_NAME", f"colony-{colony_id[:8]}")
|
|
178
|
+
colony_version = _colony_version()
|
|
179
|
+
network_id = _read_network_id(self._chain_manager)
|
|
180
|
+
public_key_hex = _read_public_key(self._chain_manager)
|
|
181
|
+
|
|
182
|
+
corpus = SelfKnowledgeCorpus(
|
|
183
|
+
colony_id=colony_id,
|
|
184
|
+
colony_name=colony_name,
|
|
185
|
+
colony_version=colony_version,
|
|
186
|
+
network_id=network_id,
|
|
187
|
+
public_key_hex=public_key_hex,
|
|
188
|
+
layers=list(LAYERS),
|
|
189
|
+
api_endpoints=list(API_ENDPOINTS),
|
|
190
|
+
cognition_phases=list(COGNITION_PHASES),
|
|
191
|
+
gate_layers=list(GATE_LAYERS),
|
|
192
|
+
inference_tiers=_resolve_inference_tiers(),
|
|
193
|
+
corpus_version=CORPUS_VERSION,
|
|
194
|
+
properties={
|
|
195
|
+
"colony_home": os.environ.get(
|
|
196
|
+
"COLONY_HOME",
|
|
197
|
+
os.path.join(os.path.expanduser("~"), ".colony"),
|
|
198
|
+
),
|
|
199
|
+
"python_version": _python_version(),
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
return corpus
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _python_version() -> str:
|
|
206
|
+
import sys
|
|
207
|
+
v = sys.version_info
|
|
208
|
+
return f"{v.major}.{v.minor}.{v.micro}"
|