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
apsimo/vector/indexes.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Embedding identity and rebuild metadata in the existing source ledger.
|
|
2
|
+
|
|
3
|
+
Lance files are projections. The small active pointer and deletion fences live
|
|
4
|
+
with canonical source state, so retaining an old index does not undo forgetting.
|
|
5
|
+
No identity field asserts that remotely served weights have been inspected.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict, dataclass
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IncompatibleIndex(ValueError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class EmbeddingIdentity:
|
|
21
|
+
requested_model: str
|
|
22
|
+
served_model: str
|
|
23
|
+
declared_revision: str
|
|
24
|
+
dimensions: int
|
|
25
|
+
document_format: str = "raw-text-v1"
|
|
26
|
+
query_format: str = "raw-text-v1"
|
|
27
|
+
normalization: str = "unspecified"
|
|
28
|
+
quantization: str = "unspecified"
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def fingerprint(self) -> str:
|
|
32
|
+
return hashlib.sha256(json.dumps(asdict(self), sort_keys=True,
|
|
33
|
+
separators=(",", ":")).encode()).hexdigest()
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_pipeline(cls, pipeline):
|
|
37
|
+
provider = pipeline._provider
|
|
38
|
+
config = provider._config
|
|
39
|
+
return cls(
|
|
40
|
+
requested_model=config.model_id,
|
|
41
|
+
served_model=getattr(provider, "_served_model", "") or "unknown",
|
|
42
|
+
declared_revision=getattr(config, "revision", None) or "unknown",
|
|
43
|
+
dimensions=pipeline.dimensions,
|
|
44
|
+
query_format='prefix-v1:' + pipeline.query_instruction,
|
|
45
|
+
normalization="provider-l2" if config.provider in {"cpu", "cuda", "mlx"} else "unspecified",
|
|
46
|
+
quantization=config.quantization or "unspecified",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class IndexCatalog:
|
|
51
|
+
"""One deployment's primary vector index, backed by its source ledger."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, ledger):
|
|
54
|
+
self.ledger = ledger
|
|
55
|
+
with ledger._connect() as conn:
|
|
56
|
+
conn.executescript('''
|
|
57
|
+
CREATE TABLE IF NOT EXISTS vector_generations (
|
|
58
|
+
id TEXT PRIMARY KEY, fingerprint TEXT, identity_json TEXT,
|
|
59
|
+
status TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
error TEXT);
|
|
61
|
+
CREATE TABLE IF NOT EXISTS vector_active (
|
|
62
|
+
slot INTEGER PRIMARY KEY CHECK(slot=1), generation_id TEXT NOT NULL);
|
|
63
|
+
CREATE TABLE IF NOT EXISTS vector_projection_deletions (
|
|
64
|
+
collection TEXT NOT NULL, entry_id TEXT NOT NULL,
|
|
65
|
+
PRIMARY KEY(collection, entry_id));
|
|
66
|
+
''')
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _record(row):
|
|
70
|
+
if row is None:
|
|
71
|
+
return None
|
|
72
|
+
result = dict(row)
|
|
73
|
+
result['identity'] = json.loads(result.pop('identity_json') or 'null')
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
def active(self):
|
|
77
|
+
with self.ledger._connect() as conn:
|
|
78
|
+
return self._record(conn.execute('''SELECT g.* FROM vector_generations g
|
|
79
|
+
JOIN vector_active a ON a.generation_id=g.id WHERE a.slot=1''').fetchone())
|
|
80
|
+
|
|
81
|
+
def generations(self):
|
|
82
|
+
with self.ledger._connect() as conn:
|
|
83
|
+
return [self._record(row) for row in conn.execute('SELECT * FROM vector_generations ORDER BY created_at,id')]
|
|
84
|
+
|
|
85
|
+
def initialize(self, identity: EmbeddingIdentity, *, legacy_exists: bool):
|
|
86
|
+
with self.ledger._connect() as conn:
|
|
87
|
+
conn.execute('BEGIN IMMEDIATE')
|
|
88
|
+
if conn.execute('SELECT 1 FROM vector_active').fetchone():
|
|
89
|
+
return
|
|
90
|
+
if legacy_exists:
|
|
91
|
+
generation, fingerprint, payload, status = 'legacy', None, None, 'unverified'
|
|
92
|
+
else:
|
|
93
|
+
generation = uuid.uuid4().hex
|
|
94
|
+
fingerprint, payload, status = identity.fingerprint, json.dumps(asdict(identity)), 'ready'
|
|
95
|
+
conn.execute('INSERT INTO vector_generations(id,fingerprint,identity_json,status) VALUES(?,?,?,?)',
|
|
96
|
+
(generation, fingerprint, payload, status))
|
|
97
|
+
conn.execute('INSERT INTO vector_active(slot,generation_id) VALUES(1,?)', (generation,))
|
|
98
|
+
|
|
99
|
+
def begin(self, identity: EmbeddingIdentity):
|
|
100
|
+
with self.ledger._connect() as conn:
|
|
101
|
+
conn.execute('BEGIN IMMEDIATE')
|
|
102
|
+
row = conn.execute("SELECT * FROM vector_generations WHERE fingerprint=? AND status='building'",
|
|
103
|
+
(identity.fingerprint,)).fetchone()
|
|
104
|
+
if row is not None:
|
|
105
|
+
return self._record(row)
|
|
106
|
+
generation = uuid.uuid4().hex
|
|
107
|
+
conn.execute('INSERT INTO vector_generations(id,fingerprint,identity_json,status) VALUES(?,?,?,?)',
|
|
108
|
+
(generation, identity.fingerprint, json.dumps(asdict(identity)), 'building'))
|
|
109
|
+
return self._record(conn.execute('SELECT * FROM vector_generations WHERE id=?', (generation,)).fetchone())
|
|
110
|
+
|
|
111
|
+
def write_generation(self, identity: EmbeddingIdentity):
|
|
112
|
+
# New writes follow a matching rebuild, including a same-model rebuild.
|
|
113
|
+
# This keeps new arrivals in the generation that will be promoted.
|
|
114
|
+
with self.ledger._connect() as conn:
|
|
115
|
+
row = conn.execute("SELECT * FROM vector_generations WHERE fingerprint=? AND status='building'",
|
|
116
|
+
(identity.fingerprint,)).fetchone()
|
|
117
|
+
if row is not None:
|
|
118
|
+
return self._record(row)
|
|
119
|
+
active = self.active()
|
|
120
|
+
if active and active['fingerprint'] == identity.fingerprint:
|
|
121
|
+
return active
|
|
122
|
+
raise IncompatibleIndex('Embedding identity has no compatible index; rebuild before semantic use')
|
|
123
|
+
|
|
124
|
+
def read_generation(self, identity: EmbeddingIdentity):
|
|
125
|
+
active = self.active()
|
|
126
|
+
if not active or active['fingerprint'] != identity.fingerprint:
|
|
127
|
+
raise IncompatibleIndex('Active embedding index is unverified or incompatible; lexical recall remains available')
|
|
128
|
+
return active
|
|
129
|
+
|
|
130
|
+
def finish(self, generation_id: str, *, error: str):
|
|
131
|
+
with self.ledger._connect() as conn:
|
|
132
|
+
conn.execute("UPDATE vector_generations SET error=? WHERE id=? AND status='building'",
|
|
133
|
+
(error, generation_id))
|
|
134
|
+
|
|
135
|
+
def promote(self, generation_id: str, identity: EmbeddingIdentity):
|
|
136
|
+
with self.ledger._connect() as conn:
|
|
137
|
+
conn.execute('BEGIN IMMEDIATE')
|
|
138
|
+
row = conn.execute('SELECT * FROM vector_generations WHERE id=?', (generation_id,)).fetchone()
|
|
139
|
+
if row is None or row['status'] != 'building' or row['fingerprint'] != identity.fingerprint:
|
|
140
|
+
raise IncompatibleIndex('Only a complete generation matching the selected embedding identity can be promoted')
|
|
141
|
+
# The caller has finished writing all collections. Completion and
|
|
142
|
+
# pointer movement are one commit: interruption cannot strand a
|
|
143
|
+
# ready generation containing arrivals absent from the old index.
|
|
144
|
+
conn.execute("UPDATE vector_generations SET status='ready',error=NULL WHERE id=?", (generation_id,))
|
|
145
|
+
conn.execute("UPDATE vector_generations SET status='retained' WHERE id=(SELECT generation_id FROM vector_active WHERE slot=1) AND id<>?", (generation_id,))
|
|
146
|
+
conn.execute('UPDATE vector_active SET generation_id=? WHERE slot=1', (generation_id,))
|
|
147
|
+
|
|
148
|
+
def delete(self, collection: str, entry_id: str):
|
|
149
|
+
with self.ledger._connect() as conn:
|
|
150
|
+
conn.execute('INSERT OR IGNORE INTO vector_projection_deletions VALUES(?,?)', (collection, entry_id))
|
|
151
|
+
|
|
152
|
+
def deleted(self, collection: str, entry_id: str) -> bool:
|
|
153
|
+
with self.ledger._connect() as conn:
|
|
154
|
+
return conn.execute('SELECT 1 FROM vector_projection_deletions WHERE collection=? AND entry_id=?',
|
|
155
|
+
(collection, entry_id)).fetchone() is not None
|
|
156
|
+
|
|
157
|
+
def source_erased(self, metadata) -> bool:
|
|
158
|
+
if (metadata or {}).get('source_projection'):
|
|
159
|
+
from apsimo.turns.source_vectors import hydrate
|
|
160
|
+
return hydrate(self.ledger, metadata) is None
|
|
161
|
+
source = (metadata or {}).get('source_uri', '')
|
|
162
|
+
return isinstance(source, str) and source.startswith('turn:') and self.ledger.is_projection_erased(source[5:])
|
apsimo/vector/migrate.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""Colony Vector Store — tier migration.
|
|
2
|
+
|
|
3
|
+
Migrates all vectors from an old embedding model to a new one.
|
|
4
|
+
Re-embeds every vector with the current pipeline, updates metadata,
|
|
5
|
+
and optionally updates the .env file with the new configuration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MigrationResult:
|
|
23
|
+
"""Result of a tier migration run."""
|
|
24
|
+
|
|
25
|
+
collections_migrated: int = 0
|
|
26
|
+
vectors_migrated: int = 0
|
|
27
|
+
vectors_failed: int = 0
|
|
28
|
+
duration_s: float = 0.0
|
|
29
|
+
errors: list[str] = field(default_factory=list)
|
|
30
|
+
generation_id: str = ""
|
|
31
|
+
fingerprint: str = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class MigrationState:
|
|
36
|
+
"""Persisted state of an in-progress or completed migration."""
|
|
37
|
+
|
|
38
|
+
from_model: str = ""
|
|
39
|
+
to_model: str = ""
|
|
40
|
+
collections_done: list[str] = field(default_factory=list)
|
|
41
|
+
collections_remaining: list[str] = field(default_factory=list)
|
|
42
|
+
started_at: float = 0.0
|
|
43
|
+
completed_at: Optional[float] = None
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict:
|
|
46
|
+
return {
|
|
47
|
+
"from_model": self.from_model,
|
|
48
|
+
"to_model": self.to_model,
|
|
49
|
+
"collections_done": self.collections_done,
|
|
50
|
+
"collections_remaining": self.collections_remaining,
|
|
51
|
+
"started_at": self.started_at,
|
|
52
|
+
"completed_at": self.completed_at,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_dict(cls, d: dict) -> MigrationState:
|
|
57
|
+
return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _migration_state_path() -> Path:
|
|
61
|
+
state_dir = os.environ.get("COLONY_STATE_DIR", ".")
|
|
62
|
+
return Path(state_dir) / "migration_state.json"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_migration_state() -> Optional[MigrationState]:
|
|
66
|
+
"""Load persisted migration state, or None if no migration in progress."""
|
|
67
|
+
path = _migration_state_path()
|
|
68
|
+
if not path.exists():
|
|
69
|
+
return None
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads(path.read_text())
|
|
72
|
+
return MigrationState.from_dict(data)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
logger.warning("Failed to load migration state: %s", exc)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def save_migration_state(state: MigrationState) -> None:
|
|
79
|
+
"""Persist migration state to disk."""
|
|
80
|
+
path = _migration_state_path()
|
|
81
|
+
try:
|
|
82
|
+
path.write_text(json.dumps(state.to_dict(), indent=2))
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
logger.warning("Failed to save migration state: %s", exc)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def clear_migration_state() -> None:
|
|
88
|
+
"""Remove migration state file."""
|
|
89
|
+
path = _migration_state_path()
|
|
90
|
+
try:
|
|
91
|
+
path.unlink(missing_ok=True)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def migrate_tier(
|
|
97
|
+
store,
|
|
98
|
+
pipeline,
|
|
99
|
+
old_model_id: Optional[str] = None,
|
|
100
|
+
batch_size: int = 64,
|
|
101
|
+
graph=None,
|
|
102
|
+
) -> MigrationResult:
|
|
103
|
+
"""Migrate all vectors to the current embedding model.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
store : VectorStore
|
|
108
|
+
The LanceDB vector store.
|
|
109
|
+
pipeline : EmbeddingPipeline
|
|
110
|
+
The new embedding pipeline to use.
|
|
111
|
+
old_model_id : str, optional
|
|
112
|
+
The old model ID to migrate from. If None, re-embeds all vectors
|
|
113
|
+
regardless of their current model_id.
|
|
114
|
+
batch_size : int
|
|
115
|
+
Number of rows to embed per batch.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
MigrationResult
|
|
120
|
+
"""
|
|
121
|
+
if getattr(store, 'catalog', None) is not None:
|
|
122
|
+
return await _migrate_generation(store, pipeline, graph=graph,
|
|
123
|
+
old_model_id=old_model_id, batch_size=batch_size)
|
|
124
|
+
from apsimo.vector.collections import Collection
|
|
125
|
+
from apsimo.vector.backfill import _backfill_collection
|
|
126
|
+
|
|
127
|
+
start = time.monotonic()
|
|
128
|
+
result = MigrationResult()
|
|
129
|
+
|
|
130
|
+
# Determine current model
|
|
131
|
+
current_model_id = ""
|
|
132
|
+
if hasattr(pipeline, "_provider") and hasattr(pipeline._provider, "_config"):
|
|
133
|
+
current_model_id = pipeline._provider._config.model_id
|
|
134
|
+
|
|
135
|
+
# Set up migration state
|
|
136
|
+
state = MigrationState(
|
|
137
|
+
from_model=old_model_id or "unknown",
|
|
138
|
+
to_model=current_model_id,
|
|
139
|
+
collections_remaining=[c.value for c in Collection],
|
|
140
|
+
started_at=start,
|
|
141
|
+
)
|
|
142
|
+
save_migration_state(state)
|
|
143
|
+
|
|
144
|
+
for col in Collection:
|
|
145
|
+
try:
|
|
146
|
+
# Use backfill logic with forced re-embedding
|
|
147
|
+
rows = await store.scan_all(col)
|
|
148
|
+
to_embed = []
|
|
149
|
+
for row in rows:
|
|
150
|
+
meta_str = row.get("metadata", "{}")
|
|
151
|
+
try:
|
|
152
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
|
|
153
|
+
except (json.JSONDecodeError, TypeError):
|
|
154
|
+
meta = {}
|
|
155
|
+
# If old_model_id specified, only migrate vectors from that model
|
|
156
|
+
if old_model_id and meta.get("model_id") != old_model_id:
|
|
157
|
+
continue
|
|
158
|
+
to_embed.append(row)
|
|
159
|
+
|
|
160
|
+
# Re-embed in batches
|
|
161
|
+
now = time.time()
|
|
162
|
+
for i in range(0, len(to_embed), batch_size):
|
|
163
|
+
batch = to_embed[i : i + batch_size]
|
|
164
|
+
texts = [row.get("text", "") for row in batch]
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
vectors = await pipeline.embed_batch(texts)
|
|
168
|
+
except Exception as exc:
|
|
169
|
+
result.vectors_failed += len(batch)
|
|
170
|
+
result.errors.append(f"embed batch failed: {exc}")
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
for row, vector in zip(batch, vectors):
|
|
174
|
+
try:
|
|
175
|
+
meta_str = row.get("metadata", "{}")
|
|
176
|
+
try:
|
|
177
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
|
|
178
|
+
except (json.JSONDecodeError, TypeError):
|
|
179
|
+
meta = {}
|
|
180
|
+
meta["model_id"] = current_model_id
|
|
181
|
+
meta["embedded_at"] = now
|
|
182
|
+
|
|
183
|
+
await store.update(col, id=row["id"], text=row.get("text", ""), vector=vector, metadata=meta)
|
|
184
|
+
result.vectors_migrated += 1
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
result.vectors_failed += 1
|
|
187
|
+
result.errors.append(f"update {row.get('id', '?')} failed: {exc}")
|
|
188
|
+
|
|
189
|
+
result.collections_migrated += 1
|
|
190
|
+
state.collections_done.append(col.value)
|
|
191
|
+
state.collections_remaining.remove(col.value)
|
|
192
|
+
save_migration_state(state)
|
|
193
|
+
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
msg = f"Collection {col.value} migration failed: {exc}"
|
|
196
|
+
logger.error(msg)
|
|
197
|
+
result.errors.append(msg)
|
|
198
|
+
|
|
199
|
+
state.completed_at = time.monotonic()
|
|
200
|
+
save_migration_state(state)
|
|
201
|
+
|
|
202
|
+
# Update .env if migration succeeded
|
|
203
|
+
if result.vectors_migrated > 0 and result.errors == []:
|
|
204
|
+
_update_env(current_model_id, pipeline)
|
|
205
|
+
|
|
206
|
+
result.duration_s = time.monotonic() - start
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def _migrate_generation(store, pipeline, *, graph, old_model_id, batch_size):
|
|
211
|
+
"""Rebuild from retained evidence; the selected generation is never rewritten."""
|
|
212
|
+
from apsimo.vector.collections import Collection
|
|
213
|
+
from apsimo.vector.indexes import IncompatibleIndex
|
|
214
|
+
if store.identity != pipeline.index_identity:
|
|
215
|
+
raise IncompatibleIndex('The embedding pipeline and index identity do not match')
|
|
216
|
+
if old_model_id:
|
|
217
|
+
raise ValueError('A generation rebuild must include all retained rows, not one model subset')
|
|
218
|
+
if not 1 <= batch_size <= 128:
|
|
219
|
+
raise ValueError('Rebuild batch size must be between 1 and 128')
|
|
220
|
+
started = time.monotonic()
|
|
221
|
+
generation = store.catalog.begin(store.identity)
|
|
222
|
+
result = MigrationResult(generation_id=generation['id'], fingerprint=store.identity.fingerprint)
|
|
223
|
+
source_generation = store.catalog.active()
|
|
224
|
+
|
|
225
|
+
async def source_rows(collection):
|
|
226
|
+
if collection == Collection.MEMORIES and graph is not None:
|
|
227
|
+
# The graph is authoritative for memories, including rows missed by
|
|
228
|
+
# the old index during embedding outages. This is not a restore.
|
|
229
|
+
async for row in graph.iter_indexable_memories():
|
|
230
|
+
yield row
|
|
231
|
+
else:
|
|
232
|
+
db = await store._generation_db(source_generation)
|
|
233
|
+
if collection.value in await db.table_names():
|
|
234
|
+
table = await db.open_table(collection.value)
|
|
235
|
+
for row in (await table.query().select(['id', 'text', 'metadata']).to_pandas()).to_dict('records'):
|
|
236
|
+
yield row
|
|
237
|
+
|
|
238
|
+
async def write_batch(collection, rows):
|
|
239
|
+
vectors = await pipeline.embed_batch([row['text'] for row in rows])
|
|
240
|
+
if len(vectors) != len(rows):
|
|
241
|
+
raise ValueError('Embedding batch response does not match retained inputs')
|
|
242
|
+
for vector in vectors:
|
|
243
|
+
store._validate_vector(vector)
|
|
244
|
+
table = await store._table(collection, write=True, generation=generation)
|
|
245
|
+
projected_rows, eligible, projected_ids = [], [], set()
|
|
246
|
+
for row, vector in zip(rows, vectors):
|
|
247
|
+
meta = row['metadata']
|
|
248
|
+
if row['id'] in projected_ids or not store._eligible(collection, row['id'], meta):
|
|
249
|
+
continue
|
|
250
|
+
now = time.time()
|
|
251
|
+
meta = {**meta, 'embedding_fingerprint': store.identity.fingerprint,
|
|
252
|
+
'model_id': store.identity.requested_model, 'served_model': store.identity.served_model,
|
|
253
|
+
'declared_revision': store.identity.declared_revision, 'embedded_at': now}
|
|
254
|
+
projected = {'id': row['id'], 'text': row['text'], 'vector': vector,
|
|
255
|
+
'metadata': json.dumps(meta, default=str),
|
|
256
|
+
'scope_key': meta.get('scope_key'),
|
|
257
|
+
'modality': meta.get('modality', 'text'), 'image_hash': meta.get('image_hash', ''),
|
|
258
|
+
'image_ref': meta.get('image_ref', ''), 'thumbnail_ref': meta.get('thumbnail_ref', ''),
|
|
259
|
+
'caption': meta.get('caption', ''), 'created_at': now, 'updated_at': now}
|
|
260
|
+
projected_rows.append(projected)
|
|
261
|
+
eligible.append((row['id'], meta))
|
|
262
|
+
# Legacy Lance collections can contain repeated IDs. Preserve the
|
|
263
|
+
# first eligible row, as the earlier per-row insert-only merge did.
|
|
264
|
+
projected_ids.add(row['id'])
|
|
265
|
+
if not projected_rows:
|
|
266
|
+
return
|
|
267
|
+
# One Lance commit per embedding batch. Existing staged rows include
|
|
268
|
+
# concurrent live writes and survive the insert-only merge unchanged.
|
|
269
|
+
await table.merge_insert('id').when_not_matched_insert_all().execute(projected_rows)
|
|
270
|
+
for entry_id, meta in eligible:
|
|
271
|
+
# A source can be erased while the batch commit is in flight.
|
|
272
|
+
if not store._eligible(collection, entry_id, meta):
|
|
273
|
+
await table.delete('id = ' + store._quoted(entry_id))
|
|
274
|
+
else:
|
|
275
|
+
result.vectors_migrated += 1
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
for collection in Collection:
|
|
279
|
+
table = await store._table(collection, write=True, generation=generation)
|
|
280
|
+
present = {str(row['id']) for row in await table.query().select(['id']).to_list()}
|
|
281
|
+
batch = []
|
|
282
|
+
async for row in source_rows(collection):
|
|
283
|
+
meta = row.get('metadata') or {}
|
|
284
|
+
if isinstance(meta, str):
|
|
285
|
+
meta = json.loads(meta)
|
|
286
|
+
if row['id'] in present or not row.get('text') or not store._eligible(collection, row['id'], meta):
|
|
287
|
+
continue
|
|
288
|
+
# Text re-embedding cannot recreate an image vector space.
|
|
289
|
+
# Preserve the old generation; require a qualified multimodal
|
|
290
|
+
# rebuild rather than silently encoding an empty/caption text.
|
|
291
|
+
if meta.get('modality') == 'image':
|
|
292
|
+
raise ValueError('Retained image vectors require a qualified multimodal reindex')
|
|
293
|
+
batch.append({**row, 'metadata': meta})
|
|
294
|
+
if len(batch) >= batch_size:
|
|
295
|
+
await write_batch(collection, batch)
|
|
296
|
+
batch = []
|
|
297
|
+
if batch:
|
|
298
|
+
await write_batch(collection, batch)
|
|
299
|
+
result.collections_migrated += 1
|
|
300
|
+
store.catalog.promote(generation['id'], store.identity)
|
|
301
|
+
except Exception as exc:
|
|
302
|
+
# A retry resumes this generation. No deleted row is restored, and the
|
|
303
|
+
# active pointer has not moved on a partial rebuild.
|
|
304
|
+
message = f'{type(exc).__name__}: {exc}'
|
|
305
|
+
result.errors.append(message)
|
|
306
|
+
result.vectors_failed += len(locals().get('batch', []))
|
|
307
|
+
store.catalog.finish(generation['id'], error=message)
|
|
308
|
+
result.duration_s = time.monotonic() - started
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _update_env(model_id: str, pipeline) -> None:
|
|
313
|
+
"""Update .env file with the new model configuration."""
|
|
314
|
+
env_path = Path(os.environ.get("COLONY_STATE_DIR", ".")) / ".env"
|
|
315
|
+
if not env_path.exists():
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
lines = env_path.read_text().splitlines()
|
|
320
|
+
updated = {}
|
|
321
|
+
for i, line in enumerate(lines):
|
|
322
|
+
if line.startswith("COLONY_EMBED_MODEL="):
|
|
323
|
+
lines[i] = f"COLONY_EMBED_MODEL={model_id}"
|
|
324
|
+
updated["model"] = True
|
|
325
|
+
elif line.startswith("COLONY_EMBED_DIMS="):
|
|
326
|
+
dims = pipeline.dimensions if hasattr(pipeline, "dimensions") else ""
|
|
327
|
+
lines[i] = f"COLONY_EMBED_DIMS={dims}"
|
|
328
|
+
updated["dims"] = True
|
|
329
|
+
|
|
330
|
+
env_path.write_text("\n".join(lines) + "\n")
|
|
331
|
+
if updated:
|
|
332
|
+
logger.info("Updated .env with new model config: %s", updated)
|
|
333
|
+
except Exception as exc:
|
|
334
|
+
logger.warning("Failed to update .env: %s", exc)
|