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,25 @@
|
|
|
1
|
+
"""World Model relationship dataclass."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class WorldRelationship:
|
|
9
|
+
"""A directed relationship between two world model entities."""
|
|
10
|
+
id: str # wr-<timestamp>-<random7>
|
|
11
|
+
source_id: str
|
|
12
|
+
target_id: str
|
|
13
|
+
relationship_type: str # WM_ prefixed type from constants
|
|
14
|
+
confidence: float = 0.5 # 0.0–1.0
|
|
15
|
+
valid_from: Optional[str] = None # ISO8601; when relationship began
|
|
16
|
+
valid_to: Optional[str] = None # ISO8601; None = currently active
|
|
17
|
+
properties: Dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
source_observation_id: Optional[str] = None # extraction provenance
|
|
19
|
+
created_at: Optional[str] = None
|
|
20
|
+
updated_at: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def is_active(self) -> bool:
|
|
24
|
+
"""True if the relationship has no end date (currently active)."""
|
|
25
|
+
return self.valid_to is None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Entity resolution and merge workflow."""
|
|
2
|
+
from .entity_resolver import EntityResolver, ResolutionAction, ResolutionResult
|
|
3
|
+
from .merge_workflow import MergeWorkflow, MergeProposal
|
|
4
|
+
from .merge_audit import MergeAuditRecord
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"EntityResolver",
|
|
8
|
+
"ResolutionAction",
|
|
9
|
+
"ResolutionResult",
|
|
10
|
+
"MergeWorkflow",
|
|
11
|
+
"MergeProposal",
|
|
12
|
+
"MergeAuditRecord",
|
|
13
|
+
]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Entity resolver: resolves extraction candidates against the world model.
|
|
2
|
+
|
|
3
|
+
Resolution priority order (first match wins):
|
|
4
|
+
1. Exact external ID match → MERGE (confidence ≥ 0.98)
|
|
5
|
+
2. Exact canonical name match (case-insensitive) → MERGE (confidence ≥ 0.90)
|
|
6
|
+
3. Alias list match → MERGE (confidence ≥ 0.85)
|
|
7
|
+
4. High string similarity (≥ 0.92) + same type → PROPOSE MERGE
|
|
8
|
+
5. Property match (domain, email, ticker) → MERGE (confidence ≥ 0.90)
|
|
9
|
+
6. Moderate similarity (0.75–0.92) + same type → FLAG
|
|
10
|
+
7. No match → CREATE NEW ENTITY
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..store import WorldModelStore
|
|
19
|
+
from ..extraction.conversation_extractor import ExtractionCandidate
|
|
20
|
+
from ..entities import BaseEntity
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _jaro_winkler(s1: str, s2: str) -> float:
|
|
24
|
+
"""Compute Jaro-Winkler similarity between two strings.
|
|
25
|
+
|
|
26
|
+
Returns a value in [0.0, 1.0].
|
|
27
|
+
Pure Python implementation; no external dependencies.
|
|
28
|
+
"""
|
|
29
|
+
if s1 == s2:
|
|
30
|
+
return 1.0
|
|
31
|
+
if not s1 or not s2:
|
|
32
|
+
return 0.0
|
|
33
|
+
|
|
34
|
+
s1 = s1.lower()
|
|
35
|
+
s2 = s2.lower()
|
|
36
|
+
len1, len2 = len(s1), len(s2)
|
|
37
|
+
|
|
38
|
+
match_distance = max(len1, len2) // 2 - 1
|
|
39
|
+
match_distance = max(match_distance, 0)
|
|
40
|
+
|
|
41
|
+
s1_matches = [False] * len1
|
|
42
|
+
s2_matches = [False] * len2
|
|
43
|
+
|
|
44
|
+
matches = 0
|
|
45
|
+
transpositions = 0
|
|
46
|
+
|
|
47
|
+
for i in range(len1):
|
|
48
|
+
start = max(0, i - match_distance)
|
|
49
|
+
end = min(i + match_distance + 1, len2)
|
|
50
|
+
for j in range(start, end):
|
|
51
|
+
if s2_matches[j] or s1[i] != s2[j]:
|
|
52
|
+
continue
|
|
53
|
+
s1_matches[i] = True
|
|
54
|
+
s2_matches[j] = True
|
|
55
|
+
matches += 1
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
if matches == 0:
|
|
59
|
+
return 0.0
|
|
60
|
+
|
|
61
|
+
k = 0
|
|
62
|
+
for i in range(len1):
|
|
63
|
+
if not s1_matches[i]:
|
|
64
|
+
continue
|
|
65
|
+
while not s2_matches[k]:
|
|
66
|
+
k += 1
|
|
67
|
+
if s1[i] != s2[k]:
|
|
68
|
+
transpositions += 1
|
|
69
|
+
k += 1
|
|
70
|
+
|
|
71
|
+
jaro = (
|
|
72
|
+
matches / len1 + matches / len2 + (matches - transpositions / 2) / matches
|
|
73
|
+
) / 3
|
|
74
|
+
|
|
75
|
+
# Winkler prefix bonus (up to 4 chars)
|
|
76
|
+
prefix = 0
|
|
77
|
+
for i in range(min(4, len1, len2)):
|
|
78
|
+
if s1[i] == s2[i]:
|
|
79
|
+
prefix += 1
|
|
80
|
+
else:
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
return jaro + prefix * 0.1 * (1 - jaro)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ResolutionAction(str, Enum):
|
|
87
|
+
CREATE = "create" # no match found; create new entity
|
|
88
|
+
MERGE = "merge" # confident match; merge immediately
|
|
89
|
+
PROPOSE_MERGE = "propose" # uncertain match; queue for review
|
|
90
|
+
FLAG = "flag" # possible match; log and continue as new
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class ResolutionResult:
|
|
95
|
+
action: ResolutionAction
|
|
96
|
+
matched_entity_id: Optional[str] # existing entity ID if matched
|
|
97
|
+
match_confidence: float
|
|
98
|
+
match_reason: str # human-readable explanation
|
|
99
|
+
candidate_id: Optional[str] = None # new entity ID if CREATE
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class EntityResolver:
|
|
103
|
+
"""Resolves extraction candidates against existing world model entities."""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
store: "WorldModelStore",
|
|
108
|
+
auto_merge_threshold: float = 0.85,
|
|
109
|
+
propose_merge_threshold: float = 0.70,
|
|
110
|
+
similarity_auto_merge: float = 0.92,
|
|
111
|
+
similarity_propose: float = 0.75,
|
|
112
|
+
) -> None:
|
|
113
|
+
self._store = store
|
|
114
|
+
self._auto_merge_threshold = auto_merge_threshold
|
|
115
|
+
self._propose_threshold = propose_merge_threshold
|
|
116
|
+
self._sim_auto = similarity_auto_merge
|
|
117
|
+
self._sim_propose = similarity_propose
|
|
118
|
+
|
|
119
|
+
async def resolve(
|
|
120
|
+
self,
|
|
121
|
+
candidate: ExtractionCandidate,
|
|
122
|
+
entity_type: str,
|
|
123
|
+
) -> ResolutionResult:
|
|
124
|
+
"""Attempt to resolve a candidate to an existing entity.
|
|
125
|
+
|
|
126
|
+
Runs the priority-ordered resolution algorithm.
|
|
127
|
+
"""
|
|
128
|
+
text = candidate.text.strip()
|
|
129
|
+
|
|
130
|
+
# ── Step 1: External ID match ─────────────────────────────────────
|
|
131
|
+
if "@" in text:
|
|
132
|
+
existing = await self._store.get_entity_by_external_id("email", text)
|
|
133
|
+
if existing:
|
|
134
|
+
return ResolutionResult(
|
|
135
|
+
action=ResolutionAction.MERGE,
|
|
136
|
+
matched_entity_id=existing.id,
|
|
137
|
+
match_confidence=0.98,
|
|
138
|
+
match_reason="Exact external ID match (email)",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# ── Step 2: Exact canonical name match ────────────────────────────
|
|
142
|
+
candidates = await self._store.find_entities(
|
|
143
|
+
query=text, entity_type=entity_type, min_confidence=0.0, limit=50
|
|
144
|
+
)
|
|
145
|
+
for entity in candidates:
|
|
146
|
+
if entity.name.lower() == text.lower():
|
|
147
|
+
return ResolutionResult(
|
|
148
|
+
action=ResolutionAction.MERGE,
|
|
149
|
+
matched_entity_id=entity.id,
|
|
150
|
+
match_confidence=0.90,
|
|
151
|
+
match_reason="Exact canonical name match",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# ── Step 3: Alias list match ──────────────────────────────────────
|
|
155
|
+
for entity in candidates:
|
|
156
|
+
for alias in entity.aliases:
|
|
157
|
+
if alias.lower() == text.lower():
|
|
158
|
+
return ResolutionResult(
|
|
159
|
+
action=ResolutionAction.MERGE,
|
|
160
|
+
matched_entity_id=entity.id,
|
|
161
|
+
match_confidence=0.85,
|
|
162
|
+
match_reason=f"Alias match: '{alias}'",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# ── Steps 4 & 6: String similarity ───────────────────────────────
|
|
166
|
+
best_sim = 0.0
|
|
167
|
+
best_entity: Optional[BaseEntity] = None
|
|
168
|
+
for entity in candidates:
|
|
169
|
+
sim = await self.compute_string_similarity(text, entity.name)
|
|
170
|
+
if sim > best_sim:
|
|
171
|
+
best_sim = sim
|
|
172
|
+
best_entity = entity
|
|
173
|
+
|
|
174
|
+
if best_entity and best_sim >= self._sim_auto:
|
|
175
|
+
return ResolutionResult(
|
|
176
|
+
action=ResolutionAction.PROPOSE_MERGE,
|
|
177
|
+
matched_entity_id=best_entity.id,
|
|
178
|
+
match_confidence=best_sim,
|
|
179
|
+
match_reason=f"High string similarity ({best_sim:.2f})",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if best_entity and best_sim >= self._sim_propose:
|
|
183
|
+
return ResolutionResult(
|
|
184
|
+
action=ResolutionAction.FLAG,
|
|
185
|
+
matched_entity_id=best_entity.id,
|
|
186
|
+
match_confidence=best_sim,
|
|
187
|
+
match_reason=f"Moderate string similarity ({best_sim:.2f})",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# ── Step 7: No match — create new ────────────────────────────────
|
|
191
|
+
return ResolutionResult(
|
|
192
|
+
action=ResolutionAction.CREATE,
|
|
193
|
+
matched_entity_id=None,
|
|
194
|
+
match_confidence=0.0,
|
|
195
|
+
match_reason="No match found",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
async def compute_string_similarity(self, a: str, b: str) -> float:
|
|
199
|
+
"""Jaro-Winkler similarity between two name strings."""
|
|
200
|
+
return _jaro_winkler(a, b)
|
|
201
|
+
|
|
202
|
+
async def resolve_entity_against_store(
|
|
203
|
+
self,
|
|
204
|
+
entity: BaseEntity,
|
|
205
|
+
) -> ResolutionResult:
|
|
206
|
+
"""Resolve a full BaseEntity against the store.
|
|
207
|
+
|
|
208
|
+
More thorough than ExtractionCandidate resolution: checks external IDs,
|
|
209
|
+
domain, ticker, etc.
|
|
210
|
+
"""
|
|
211
|
+
# External ID matches
|
|
212
|
+
for key, value in entity.external_ids.items():
|
|
213
|
+
existing = await self._store.get_entity_by_external_id(key, value)
|
|
214
|
+
if existing and existing.id != entity.id:
|
|
215
|
+
return ResolutionResult(
|
|
216
|
+
action=ResolutionAction.MERGE,
|
|
217
|
+
matched_entity_id=existing.id,
|
|
218
|
+
match_confidence=0.98,
|
|
219
|
+
match_reason=f"Exact external ID match ({key}={value})",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Name-based resolution
|
|
223
|
+
from ..extraction.conversation_extractor import ExtractionCandidate
|
|
224
|
+
candidate = ExtractionCandidate(
|
|
225
|
+
text=entity.name,
|
|
226
|
+
entity_type=entity.entity_type,
|
|
227
|
+
start_char=0,
|
|
228
|
+
end_char=len(entity.name),
|
|
229
|
+
confidence=entity.confidence,
|
|
230
|
+
context_window="",
|
|
231
|
+
)
|
|
232
|
+
return await self.resolve(candidate, entity.entity_type)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Merge audit record dataclass."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class MergeAuditRecord:
|
|
9
|
+
id: str # ma-<timestamp>-<random7>
|
|
10
|
+
surviving_id: str # entity that survived
|
|
11
|
+
retired_id: str # entity that was merged away
|
|
12
|
+
relationships_repointed: int
|
|
13
|
+
properties_updated: int
|
|
14
|
+
executed_by: str # "auto" | "owner_approved"
|
|
15
|
+
merge_proposal_id: Optional[str]
|
|
16
|
+
created_at: str
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Merge workflow: automatic and proposed entity merges."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..store import WorldModelStore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class MergeProposal:
|
|
12
|
+
id: str # mp-<timestamp>-<random7>
|
|
13
|
+
candidate_id: str # the incoming entity
|
|
14
|
+
existing_id: str # the entity to merge into
|
|
15
|
+
match_confidence: float
|
|
16
|
+
match_reason: str
|
|
17
|
+
evidence: Dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
status: str = "pending" # "pending" | "approved" | "rejected" | "expired"
|
|
19
|
+
created_at: Optional[str] = None
|
|
20
|
+
resolved_at: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MergeWorkflow:
|
|
24
|
+
"""Manages proposed and automatic entity merges."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, store: "WorldModelStore") -> None:
|
|
27
|
+
self._store = store
|
|
28
|
+
|
|
29
|
+
async def propose_merge(
|
|
30
|
+
self,
|
|
31
|
+
candidate_id: str,
|
|
32
|
+
existing_id: str,
|
|
33
|
+
confidence: float,
|
|
34
|
+
reason: str,
|
|
35
|
+
evidence: Optional[Dict[str, Any]] = None,
|
|
36
|
+
) -> MergeProposal:
|
|
37
|
+
"""Create a merge proposal and enqueue for owner review."""
|
|
38
|
+
proposal_id = await self._store._backend.create_merge_proposal(
|
|
39
|
+
candidate_id=candidate_id,
|
|
40
|
+
existing_id=existing_id,
|
|
41
|
+
confidence=confidence,
|
|
42
|
+
reason=reason,
|
|
43
|
+
evidence=evidence,
|
|
44
|
+
)
|
|
45
|
+
return MergeProposal(
|
|
46
|
+
id=proposal_id,
|
|
47
|
+
candidate_id=candidate_id,
|
|
48
|
+
existing_id=existing_id,
|
|
49
|
+
match_confidence=confidence,
|
|
50
|
+
match_reason=reason,
|
|
51
|
+
evidence=evidence or {},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def execute_merge(self, proposal_id: str) -> None:
|
|
55
|
+
"""Execute an approved merge. The candidate entity is retired.
|
|
56
|
+
|
|
57
|
+
All relationships pointing to candidate_id are repointed to
|
|
58
|
+
existing_id. A merge audit record is created.
|
|
59
|
+
"""
|
|
60
|
+
proposal = await self._store._backend.get_merge_proposal(proposal_id)
|
|
61
|
+
if not proposal:
|
|
62
|
+
raise ValueError(f"Merge proposal not found: {proposal_id}")
|
|
63
|
+
if proposal["status"] not in ("pending", "approved"):
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Cannot execute merge in status '{proposal['status']}'"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
await self._store._backend.execute_merge(
|
|
69
|
+
surviving_id=proposal["existing_id"],
|
|
70
|
+
retired_id=proposal["candidate_id"],
|
|
71
|
+
executed_by="owner_approved",
|
|
72
|
+
proposal_id=proposal_id,
|
|
73
|
+
)
|
|
74
|
+
await self._store._backend.update_merge_proposal_status(
|
|
75
|
+
proposal_id, "approved"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def execute_auto_merge(
|
|
79
|
+
self,
|
|
80
|
+
surviving_id: str,
|
|
81
|
+
retired_id: str,
|
|
82
|
+
) -> str:
|
|
83
|
+
"""Execute an automatic high-confidence merge without owner review.
|
|
84
|
+
|
|
85
|
+
Returns the merge audit record ID.
|
|
86
|
+
"""
|
|
87
|
+
return await self._store._backend.execute_merge(
|
|
88
|
+
surviving_id=surviving_id,
|
|
89
|
+
retired_id=retired_id,
|
|
90
|
+
executed_by="auto",
|
|
91
|
+
proposal_id=None,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def reject_merge(self, proposal_id: str) -> None:
|
|
95
|
+
"""Reject a merge proposal.
|
|
96
|
+
|
|
97
|
+
Both entities remain separate. The candidate's aliases list is
|
|
98
|
+
updated to avoid re-proposing the same merge.
|
|
99
|
+
"""
|
|
100
|
+
proposal = await self._store._backend.get_merge_proposal(proposal_id)
|
|
101
|
+
if not proposal:
|
|
102
|
+
raise ValueError(f"Merge proposal not found: {proposal_id}")
|
|
103
|
+
|
|
104
|
+
await self._store._backend.update_merge_proposal_status(
|
|
105
|
+
proposal_id, "rejected"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Add candidate's name as alias on existing to avoid re-proposing
|
|
109
|
+
candidate = await self._store.get_entity(proposal["candidate_id"])
|
|
110
|
+
if candidate:
|
|
111
|
+
await self._store.add_entity_alias(
|
|
112
|
+
proposal["existing_id"], candidate.name
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def get_pending_proposals(self) -> list:
|
|
116
|
+
"""Return all pending merge proposals."""
|
|
117
|
+
return await self._store._backend.get_pending_merge_proposals()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Existing world batch extraction driven by attributed canonical user sources."""
|
|
2
|
+
from contextlib import closing
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from apsimo.turns.idempotency import canonical_turn_digest, source_message_hash
|
|
9
|
+
from apsimo.turns.source_attribution import is_invalidated
|
|
10
|
+
from .observations import canonical
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# This first current-state producer abstains on dated/modally qualified
|
|
14
|
+
# messages. The canonical memory retains them for its existing valid-time
|
|
15
|
+
# parser; receipt time must never turn a historical/future claim into now.
|
|
16
|
+
_NONCURRENT = re.compile(r"\b(will|would|was|were|had|used to|tomorrow|yesterday|last|next|previously|formerly|"
|
|
17
|
+
r"soon|later|eventually|before|after|until|since|starting|ended|stopped|might|may|could|should|"
|
|
18
|
+
r"if|unless|not|never|isn't|aren't|doesn't|didn't|weren't|wasn't|"
|
|
19
|
+
r"monday|tuesday|wednesday|thursday|friday|saturday|sunday|"
|
|
20
|
+
r"january|february|march|april|june|july|august|september|october|november|december)\b|"
|
|
21
|
+
r"\b\d{4}-\d{2}-\d{2}\b|\?", re.I)
|
|
22
|
+
_PRESENT = re.compile(r"\b(is|are|runs|uses|has|needs|depends on)\b", re.I)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def current_source(ledger, source):
|
|
26
|
+
with closing(ledger._connect()) as conn:
|
|
27
|
+
row = conn.execute("SELECT contact_id,scope,messages_json FROM turn_sources WHERE turn_id=?",
|
|
28
|
+
(source['source_id'],)).fetchone()
|
|
29
|
+
return bool(row and row['contact_id'] == source['contact_id'] and row['scope'] == 'person'
|
|
30
|
+
and not is_invalidated(conn, source['source_id'])
|
|
31
|
+
and canonical_turn_digest(json.loads(row['messages_json'])) == source['source_version'])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def recent_batches(ledger, *, hours=24, limit=30):
|
|
35
|
+
"""Same bounded batch job, with one participant per batch and exact lineage.
|
|
36
|
+
|
|
37
|
+
Only ordinary learning-admitted user sources are eligible. Historical
|
|
38
|
+
source-only imports and unattributed checkpoints remain source-only.
|
|
39
|
+
"""
|
|
40
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
|
|
41
|
+
with closing(ledger._connect()) as conn:
|
|
42
|
+
rows = conn.execute('''SELECT s.* FROM turn_sources s JOIN source_claim_jobs j ON j.turn_id=s.turn_id
|
|
43
|
+
WHERE s.scope='person' AND julianday(s.ingested_at)>=julianday(?)
|
|
44
|
+
ORDER BY s.ingested_at DESC,s.turn_id LIMIT ?''', (cutoff, max(1, min(limit, 100)))).fetchall()
|
|
45
|
+
grouped = {}
|
|
46
|
+
for row in rows:
|
|
47
|
+
if is_invalidated(conn, row['turn_id']):
|
|
48
|
+
continue
|
|
49
|
+
messages = json.loads(row['messages_json'])
|
|
50
|
+
version = canonical_turn_digest(messages)
|
|
51
|
+
for message in messages:
|
|
52
|
+
if message.get('role') != 'user':
|
|
53
|
+
continue
|
|
54
|
+
content = message.get('content', '')
|
|
55
|
+
if isinstance(content, list):
|
|
56
|
+
content = '\n'.join(b.get('text', '') for b in content if isinstance(b, dict)
|
|
57
|
+
and b.get('type') in {'text', 'input_text'})
|
|
58
|
+
if not isinstance(content, str) or not content.strip():
|
|
59
|
+
continue
|
|
60
|
+
grouped.setdefault(row['contact_id'], []).append(dict(content=content[:600],
|
|
61
|
+
current_time_eligible=not bool(_NONCURRENT.search(content)),
|
|
62
|
+
source_id=row['turn_id'], source_version=version, contact_id=row['contact_id'],
|
|
63
|
+
message_hash=source_message_hash(row['session_id'], message),
|
|
64
|
+
observed_at=row['occurred_at'] or row['ingested_at'],
|
|
65
|
+
time_basis='occurred_at' if row['occurred_at'] else 'received_at'))
|
|
66
|
+
return [items[start:start+10] for items in grouped.values() for start in range(0, len(items), 10)]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def record_reports(extractor, proposals, sources, name_to_id, mode, report):
|
|
70
|
+
"""Quote-qualified reports, never sensor observations or proven properties."""
|
|
71
|
+
if not sources or extractor._source_ledger is None or mode != 'live':
|
|
72
|
+
return
|
|
73
|
+
report.setdefault('property_observations', [])
|
|
74
|
+
report.setdefault('property_skipped', 0)
|
|
75
|
+
for item in proposals[:25] if isinstance(proposals, list) else []:
|
|
76
|
+
if not isinstance(item, dict):
|
|
77
|
+
continue
|
|
78
|
+
name, key, quote = str(item.get('entity', '')).strip(), str(item.get('property', '')).strip(), item.get('evidence')
|
|
79
|
+
value = item.get('value')
|
|
80
|
+
eid = name_to_id.get(name.lower())
|
|
81
|
+
matches = [s for s in sources if isinstance(quote, str) and len(quote) >= 8 and quote in s['content']
|
|
82
|
+
and s['current_time_eligible'] and item.get('temporal_status') == 'current'
|
|
83
|
+
and _PRESENT.search(quote) and name.lower() in quote.lower()
|
|
84
|
+
and isinstance(value, str) and value.lower() in quote.lower()]
|
|
85
|
+
# Ambiguous identical quotations across messages need explicit source
|
|
86
|
+
# selection in a later extraction, rather than an invented attribution.
|
|
87
|
+
if not eid or not key or len(matches) != 1 or not current_source(extractor._source_ledger, matches[0]):
|
|
88
|
+
report['property_skipped'] += 1
|
|
89
|
+
continue
|
|
90
|
+
source = matches[0]
|
|
91
|
+
oid = 'wo-report-' + hashlib.sha256(canonical([source['source_id'], source['source_version'], source['contact_id'],
|
|
92
|
+
source['message_hash'], eid, key, quote]).encode()).hexdigest()
|
|
93
|
+
at = datetime.fromisoformat(source['observed_at'].replace('Z', '+00:00'))
|
|
94
|
+
try:
|
|
95
|
+
result = await extractor._store.record_property_observation(observation_id=oid, entity_id=eid,
|
|
96
|
+
property_key=key, value=value, kind='reported', producer=f"contact:{source['contact_id']}",
|
|
97
|
+
evidence_refs=[f"source:{source['source_id']}", f"message:{source['message_hash']}"],
|
|
98
|
+
source_refs=[dict(source_id=source['source_id'], source_version=source['source_version'])],
|
|
99
|
+
observed_at=at.isoformat(), fresh_until=(at + timedelta(days=1)).isoformat(),
|
|
100
|
+
subject_person_id=source['contact_id'], viewer_scope=f"person:{source['contact_id']}",
|
|
101
|
+
shareability='subject_private')
|
|
102
|
+
if not current_source(extractor._source_ledger, source):
|
|
103
|
+
await extractor._store.erase_property_evidence([f"source:{source['source_id']}"],
|
|
104
|
+
subject_person_id=source['contact_id'])
|
|
105
|
+
report['property_skipped'] += 1
|
|
106
|
+
continue
|
|
107
|
+
report['property_observations'].append(dict(observation_id=result['observation_id'],
|
|
108
|
+
entity_id=eid, property_key=key, kind='reported', time_basis=source['time_basis']))
|
|
109
|
+
report['writes'] += 1
|
|
110
|
+
except (ValueError, NotImplementedError):
|
|
111
|
+
report['property_skipped'] += 1
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def validate_reports(records, ledger):
|
|
115
|
+
"""Hydration checks close the cross-store correction/erasure race."""
|
|
116
|
+
result = []
|
|
117
|
+
for row in records:
|
|
118
|
+
refs = row.get('source_refs', [])
|
|
119
|
+
valid = all(current_source(ledger, ref | {'contact_id': row['subject_person_id']}) for ref in refs) if ledger else not refs
|
|
120
|
+
result.append(row if valid else row | {'invalidated': True, 'value': None})
|
|
121
|
+
return result
|