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,159 @@
|
|
|
1
|
+
"""Colony Vector — image content classifier.
|
|
2
|
+
|
|
3
|
+
Validates images before embedding: format, dimensions, and optional
|
|
4
|
+
content classification.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ImageCheckLevel(str, Enum):
|
|
18
|
+
"""Image validation strictness."""
|
|
19
|
+
|
|
20
|
+
OFF = "off" # No checks
|
|
21
|
+
BASIC = "basic" # Format, dimension, and size validation only
|
|
22
|
+
STRICT = "strict" # Format + dimensions + content classification
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ImageCheckResult:
|
|
27
|
+
"""Result of an image content check."""
|
|
28
|
+
|
|
29
|
+
safe: bool = True
|
|
30
|
+
level: ImageCheckLevel = ImageCheckLevel.BASIC
|
|
31
|
+
errors: list[str] = field(default_factory=list)
|
|
32
|
+
warnings: list[str] = field(default_factory=list)
|
|
33
|
+
classification: Optional[dict[str, float]] = None # content scores for strict mode
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def reason(self) -> str:
|
|
37
|
+
return "; ".join(self.errors) if self.errors else ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def check_image(
|
|
41
|
+
data: bytes,
|
|
42
|
+
mime_type: str = "",
|
|
43
|
+
width: int = 0,
|
|
44
|
+
height: int = 0,
|
|
45
|
+
level: ImageCheckLevel = ImageCheckLevel.BASIC,
|
|
46
|
+
) -> ImageCheckResult:
|
|
47
|
+
"""Check image content.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
data : bytes
|
|
52
|
+
Raw image bytes.
|
|
53
|
+
mime_type : str
|
|
54
|
+
Detected or provided MIME type.
|
|
55
|
+
width, height : int
|
|
56
|
+
Image dimensions (0 if unknown).
|
|
57
|
+
level : ImageCheckLevel
|
|
58
|
+
How strict to check.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
ImageCheckResult
|
|
63
|
+
"""
|
|
64
|
+
result = ImageCheckResult(level=level)
|
|
65
|
+
|
|
66
|
+
if level == ImageCheckLevel.OFF:
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
# --- Basic checks ---
|
|
70
|
+
|
|
71
|
+
# Format
|
|
72
|
+
valid_mimes = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
|
73
|
+
if mime_type and mime_type not in valid_mimes:
|
|
74
|
+
result.errors.append(f"Unsupported image format: {mime_type}")
|
|
75
|
+
result.safe = False
|
|
76
|
+
|
|
77
|
+
# Size
|
|
78
|
+
max_size = 20 * 1024 * 1024 # 20 MB
|
|
79
|
+
if len(data) > max_size:
|
|
80
|
+
result.errors.append(f"Image size {len(data)} exceeds 20MB limit")
|
|
81
|
+
result.safe = False
|
|
82
|
+
|
|
83
|
+
if len(data) == 0:
|
|
84
|
+
result.errors.append("Empty image data")
|
|
85
|
+
result.safe = False
|
|
86
|
+
|
|
87
|
+
# Dimensions (if known)
|
|
88
|
+
max_dim = 4096
|
|
89
|
+
if width > max_dim or height > max_dim:
|
|
90
|
+
result.warnings.append(f"Image dimensions {width}x{height} exceed {max_dim}px — will be resized")
|
|
91
|
+
|
|
92
|
+
# Minimum dimensions (avoid 1px images, etc.)
|
|
93
|
+
if width > 0 and height > 0 and (width < 16 or height < 16):
|
|
94
|
+
result.warnings.append(f"Very small image: {width}x{height}px")
|
|
95
|
+
|
|
96
|
+
if level == ImageCheckLevel.BASIC:
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
# --- Strict checks (content classification) ---
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
classification = await _classify_content(data, mime_type)
|
|
103
|
+
result.classification = classification
|
|
104
|
+
|
|
105
|
+
# Flag content with high explicit scores
|
|
106
|
+
explicit_score = classification.get("explicit", 0.0)
|
|
107
|
+
if explicit_score > 0.8:
|
|
108
|
+
result.errors.append(f"Image classified as explicit content (score: {explicit_score:.2f})")
|
|
109
|
+
result.safe = False
|
|
110
|
+
elif explicit_score > 0.5:
|
|
111
|
+
result.warnings.append(f"Image may contain explicit content (score: {explicit_score:.2f})")
|
|
112
|
+
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
logger.warning("Content classification failed: %s — allowing image (basic checks passed)", exc)
|
|
115
|
+
result.warnings.append("Content classification unavailable")
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def _classify_content(data: bytes, mime_type: str) -> dict[str, float]:
|
|
121
|
+
"""Classify image content.
|
|
122
|
+
|
|
123
|
+
Tries a local content classifier (falconsai/nsfw_image_detection via
|
|
124
|
+
transformers). Falls back to safe scores if the model isn't available.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
import asyncio
|
|
128
|
+
from PIL import Image as PILImage
|
|
129
|
+
import io
|
|
130
|
+
|
|
131
|
+
img = PILImage.open(io.BytesIO(data))
|
|
132
|
+
if img.mode in ("RGBA", "P"):
|
|
133
|
+
img = img.convert("RGB")
|
|
134
|
+
|
|
135
|
+
def _run_classifier():
|
|
136
|
+
from transformers import pipeline as hf_pipeline
|
|
137
|
+
classifier = hf_pipeline("image-classification", model="falconsai/nsfw_image_detection", device=-1)
|
|
138
|
+
return classifier(img)
|
|
139
|
+
|
|
140
|
+
results = await asyncio.get_running_loop().run_in_executor(None, _run_classifier)
|
|
141
|
+
|
|
142
|
+
# Parse results into score dict
|
|
143
|
+
scores = {"explicit": 0.0, "safe": 1.0}
|
|
144
|
+
for r in results:
|
|
145
|
+
label = r.get("label", "").lower()
|
|
146
|
+
score = r.get("score", 0.0)
|
|
147
|
+
if "nsfw" in label or "explicit" in label:
|
|
148
|
+
scores["explicit"] = score
|
|
149
|
+
if "safe" in label or "normal" in label:
|
|
150
|
+
scores["safe"] = score
|
|
151
|
+
|
|
152
|
+
return scores
|
|
153
|
+
|
|
154
|
+
except ImportError:
|
|
155
|
+
logger.debug("transformers not available for image classification — returning safe defaults")
|
|
156
|
+
return {"explicit": 0.0, "safe": 1.0}
|
|
157
|
+
except Exception as exc:
|
|
158
|
+
logger.debug("Image classification failed: %s — returning safe defaults", exc)
|
|
159
|
+
return {"explicit": 0.0, "safe": 1.0}
|
apsimo/vector/scanner.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""System scanner — detect GPU, VRAM, RAM to select embedding tier.
|
|
2
|
+
|
|
3
|
+
Scans the host hardware and returns a ``HardwareProfile`` that the
|
|
4
|
+
tier selector uses to pick the right models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class HardwareProfile:
|
|
21
|
+
"""Detected hardware capabilities."""
|
|
22
|
+
|
|
23
|
+
gpu_type: str # "cuda", "mlx", "none"
|
|
24
|
+
gpu_name: str # e.g. "NVIDIA GB10"
|
|
25
|
+
vram_gb: int # GPU memory in GB (0 if no GPU)
|
|
26
|
+
ram_gb: int # System RAM in GB
|
|
27
|
+
cuda_version: str | None = None
|
|
28
|
+
os: str = "" # "linux", "darwin", "windows"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def scan() -> HardwareProfile:
|
|
32
|
+
"""Scan the system and return a HardwareProfile."""
|
|
33
|
+
gpu_type = "none"
|
|
34
|
+
gpu_name = ""
|
|
35
|
+
vram_gb = 0
|
|
36
|
+
cuda_version = None
|
|
37
|
+
|
|
38
|
+
# Detect CUDA
|
|
39
|
+
if _has_cuda():
|
|
40
|
+
gpu_type = "cuda"
|
|
41
|
+
gpu_name, vram_gb, cuda_version = _cuda_info()
|
|
42
|
+
|
|
43
|
+
# Detect MLX (Apple Silicon)
|
|
44
|
+
if gpu_type == "none" and _has_mlx():
|
|
45
|
+
gpu_type = "mlx"
|
|
46
|
+
gpu_name, vram_gb = _mlx_info()
|
|
47
|
+
|
|
48
|
+
ram_gb = _ram_gb()
|
|
49
|
+
system = platform.system().lower()
|
|
50
|
+
|
|
51
|
+
profile = HardwareProfile(
|
|
52
|
+
gpu_type=gpu_type,
|
|
53
|
+
gpu_name=gpu_name,
|
|
54
|
+
vram_gb=vram_gb,
|
|
55
|
+
ram_gb=ram_gb,
|
|
56
|
+
cuda_version=cuda_version,
|
|
57
|
+
os=system,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
logger.info(
|
|
61
|
+
"Hardware scan: gpu=%s (%s, %dGB VRAM), ram=%dGB, cuda=%s, os=%s",
|
|
62
|
+
profile.gpu_type, profile.gpu_name, profile.vram_gb,
|
|
63
|
+
profile.ram_gb, profile.cuda_version, profile.os,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return profile
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _has_cuda() -> bool:
|
|
70
|
+
"""Check if CUDA is available."""
|
|
71
|
+
try:
|
|
72
|
+
result = subprocess.run(
|
|
73
|
+
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
|
|
74
|
+
capture_output=True, text=True, timeout=5,
|
|
75
|
+
)
|
|
76
|
+
return result.returncode == 0
|
|
77
|
+
except Exception:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _cuda_info() -> tuple[str, int, str | None]:
|
|
82
|
+
"""Get CUDA GPU name, VRAM, and driver version.
|
|
83
|
+
|
|
84
|
+
Handles unified memory GPUs (Grace Blackwell, Grace Hopper, Tegra)
|
|
85
|
+
where nvidia-smi reports [N/A] for memory.total. On these chips
|
|
86
|
+
the GPU uses system RAM, so we fall back to total system RAM.
|
|
87
|
+
"""
|
|
88
|
+
name = "Unknown NVIDIA GPU"
|
|
89
|
+
vram = 0
|
|
90
|
+
cuda_ver = None
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
result = subprocess.run(
|
|
94
|
+
["nvidia-smi",
|
|
95
|
+
"--query-gpu=name,memory.total",
|
|
96
|
+
"--format=csv,noheader,nounits"],
|
|
97
|
+
capture_output=True, text=True, timeout=5,
|
|
98
|
+
)
|
|
99
|
+
if result.returncode == 0:
|
|
100
|
+
parts = result.stdout.strip().split(",")
|
|
101
|
+
if len(parts) >= 2:
|
|
102
|
+
name = parts[0].strip()
|
|
103
|
+
mem_str = parts[1].strip()
|
|
104
|
+
# Handle [N/A] for unified memory GPUs
|
|
105
|
+
if mem_str.lower() in ("[n/a]", "n/a", ""):
|
|
106
|
+
vram = _ram_gb() # Unified memory: GPU RAM == system RAM
|
|
107
|
+
else:
|
|
108
|
+
vram = int(float(mem_str))
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"],
|
|
115
|
+
capture_output=True, text=True, timeout=5,
|
|
116
|
+
)
|
|
117
|
+
if result.returncode == 0:
|
|
118
|
+
cuda_ver = result.stdout.strip()
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
return name, vram, cuda_ver
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _has_mlx() -> bool:
|
|
126
|
+
"""Check if Apple MLX is available (M-series chip)."""
|
|
127
|
+
if platform.system() != "Darwin":
|
|
128
|
+
return False
|
|
129
|
+
try:
|
|
130
|
+
# Check for Apple Silicon
|
|
131
|
+
result = subprocess.run(
|
|
132
|
+
["sysctl", "-n", "machdep.cpu.brand_string"],
|
|
133
|
+
capture_output=True, text=True, timeout=5,
|
|
134
|
+
)
|
|
135
|
+
if result.returncode == 0 and "Apple" in result.stdout:
|
|
136
|
+
return True
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _mlx_info() -> tuple[str, int]:
|
|
143
|
+
"""Get Apple Silicon GPU name and unified memory."""
|
|
144
|
+
name = "Apple Silicon"
|
|
145
|
+
vram = 0
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
result = subprocess.run(
|
|
149
|
+
["sysctl", "-n", "machdep.cpu.brand_string"],
|
|
150
|
+
capture_output=True, text=True, timeout=5,
|
|
151
|
+
)
|
|
152
|
+
if result.returncode == 0:
|
|
153
|
+
name = result.stdout.strip()
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# On Apple Silicon, VRAM == RAM (unified memory)
|
|
158
|
+
vram = _ram_gb()
|
|
159
|
+
|
|
160
|
+
return name, vram
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _ram_gb() -> int:
|
|
164
|
+
"""Get system RAM in GB."""
|
|
165
|
+
system = platform.system().lower()
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
if system == "linux":
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
["free", "-g", "--si"],
|
|
171
|
+
capture_output=True, text=True, timeout=5,
|
|
172
|
+
)
|
|
173
|
+
if result.returncode == 0:
|
|
174
|
+
for line in result.stdout.splitlines():
|
|
175
|
+
if line.startswith("Mem:"):
|
|
176
|
+
return int(line.split()[1])
|
|
177
|
+
elif system == "darwin":
|
|
178
|
+
result = subprocess.run(
|
|
179
|
+
["sysctl", "-n", "hw.memsize"],
|
|
180
|
+
capture_output=True, text=True, timeout=5,
|
|
181
|
+
)
|
|
182
|
+
if result.returncode == 0:
|
|
183
|
+
return int(result.stdout.strip()) // (1024 ** 3)
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
# Fallback: parse /proc/meminfo
|
|
188
|
+
try:
|
|
189
|
+
with open("/proc/meminfo") as f:
|
|
190
|
+
for line in f:
|
|
191
|
+
if line.startswith("MemTotal:"):
|
|
192
|
+
kb = int(line.split()[1])
|
|
193
|
+
return kb // (1024 * 1024) + 1
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
return 8 # Safe default
|
apsimo/vector/setup.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Colony Vector Store — hardware detection, config recommendation, and CLI.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- detect_hardware() — probe CUDA, MLX, CPU resources
|
|
5
|
+
- recommend_config() — select best EmbeddingConfig for detected hardware
|
|
6
|
+
- ensure_embed_config() — resolve config from env or auto-detect
|
|
7
|
+
- ensure_model_ready() — download model weights if missing
|
|
8
|
+
- CLI entry point for ``colony vector setup`` and ``colony vector rebuild``
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from apsimo.vector.config import EmbeddingConfig, HardwareProfile, VectorStoreConfig
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Hardware detection
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
def detect_hardware() -> HardwareProfile:
|
|
28
|
+
"""Probe available compute resources. Returns a structured profile."""
|
|
29
|
+
profile = HardwareProfile()
|
|
30
|
+
|
|
31
|
+
# CUDA
|
|
32
|
+
try:
|
|
33
|
+
import torch
|
|
34
|
+
if torch.cuda.is_available():
|
|
35
|
+
profile.cuda_available = True
|
|
36
|
+
profile.cuda_device_count = torch.cuda.device_count()
|
|
37
|
+
profile.cuda_vram_gb = sum(
|
|
38
|
+
torch.cuda.get_device_properties(i).total_memory
|
|
39
|
+
for i in range(profile.cuda_device_count)
|
|
40
|
+
) / 1e9
|
|
41
|
+
except ImportError:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# Apple Silicon (MLX)
|
|
45
|
+
try:
|
|
46
|
+
import mlx.core as mx # noqa: F401
|
|
47
|
+
profile.mlx_available = True
|
|
48
|
+
profile.mlx_memory_gb = _probe_mlx_memory()
|
|
49
|
+
except ImportError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# CPU fallback — always available
|
|
53
|
+
try:
|
|
54
|
+
import psutil
|
|
55
|
+
profile.cpu_ram_gb = psutil.virtual_memory().total / 1e9
|
|
56
|
+
profile.cpu_cores = psutil.cpu_count(logical=False) or 1
|
|
57
|
+
except ImportError:
|
|
58
|
+
import multiprocessing
|
|
59
|
+
profile.cpu_cores = multiprocessing.cpu_count() or 1
|
|
60
|
+
|
|
61
|
+
return profile
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _probe_mlx_memory() -> float:
|
|
65
|
+
"""Attempt to detect unified memory on Apple Silicon."""
|
|
66
|
+
try:
|
|
67
|
+
import subprocess
|
|
68
|
+
result = subprocess.run(
|
|
69
|
+
["sysctl", "-n", "hw.memsize"],
|
|
70
|
+
capture_output=True, text=True, timeout=5,
|
|
71
|
+
)
|
|
72
|
+
if result.returncode == 0:
|
|
73
|
+
return int(result.stdout.strip()) / 1e9
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
return 0.0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Model recommendation
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
# Tiered recommendation matrix from spec §4.4.
|
|
84
|
+
# Each entry: (provider, model_id, dimensions, quantization)
|
|
85
|
+
_TIER_MATRIX = {
|
|
86
|
+
"gpu-high": ("cuda", "Qwen/Qwen3-Embedding-8B", 4096, "fp8"),
|
|
87
|
+
"gpu-balanced": ("cuda", "BAAI/bge-m3", 1024, None),
|
|
88
|
+
"gpu-fast": ("cuda", "nomic-ai/nomic-embed-text-v1.5", 768, None),
|
|
89
|
+
"native-mlx-high": ("native_mlx", "Qwen/Qwen3-Embedding-8B", 4096, None),
|
|
90
|
+
"native-mlx-balanced": ("native_mlx", "BAAI/bge-m3", 1024, None),
|
|
91
|
+
"mlx": ("mlx", "nomic-ai/nomic-embed-text-v1.5", 768, None),
|
|
92
|
+
"cpu-quality": ("cpu", "nomic-ai/nomic-embed-text-v1.5", 768, "int8"),
|
|
93
|
+
"cpu-lightweight": ("cpu", "BAAI/bge-small-en-v1.5", 384, None),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _detect_tier(profile: HardwareProfile) -> str:
|
|
98
|
+
"""Select the best tier for the given hardware profile."""
|
|
99
|
+
if profile.cuda_available:
|
|
100
|
+
if profile.cuda_vram_gb >= 24:
|
|
101
|
+
return "gpu-high"
|
|
102
|
+
if profile.cuda_vram_gb >= 8:
|
|
103
|
+
return "gpu-balanced"
|
|
104
|
+
return "gpu-fast"
|
|
105
|
+
if profile.mlx_available:
|
|
106
|
+
# Use high tier for machines with 64GB+ unified memory
|
|
107
|
+
if profile.mlx_memory_gb >= 64:
|
|
108
|
+
return "native-mlx-high"
|
|
109
|
+
if profile.mlx_memory_gb >= 32:
|
|
110
|
+
return "native-mlx-balanced"
|
|
111
|
+
return "mlx"
|
|
112
|
+
if profile.cpu_ram_gb >= 16:
|
|
113
|
+
return "cpu-quality"
|
|
114
|
+
return "cpu-lightweight"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def recommend_config(profile: HardwareProfile) -> EmbeddingConfig:
|
|
118
|
+
"""Return the best EmbeddingConfig for the detected hardware profile."""
|
|
119
|
+
tier = _detect_tier(profile)
|
|
120
|
+
provider, model_id, dims, quant = _TIER_MATRIX[tier]
|
|
121
|
+
return EmbeddingConfig(
|
|
122
|
+
provider=provider,
|
|
123
|
+
model_id=model_id,
|
|
124
|
+
dimensions=dims,
|
|
125
|
+
quantization=quant,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Config resolution
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
async def ensure_embed_config() -> Optional[EmbeddingConfig]:
|
|
134
|
+
"""Resolve embedding config from env vars or auto-detect.
|
|
135
|
+
|
|
136
|
+
Returns ``None`` if no config is set and the process is non-interactive,
|
|
137
|
+
allowing Colony to start without embeddings (graceful degradation).
|
|
138
|
+
"""
|
|
139
|
+
config = EmbeddingConfig.from_env()
|
|
140
|
+
if config is not None:
|
|
141
|
+
return config
|
|
142
|
+
|
|
143
|
+
# Auto-detect hardware and recommend
|
|
144
|
+
profile = detect_hardware()
|
|
145
|
+
recommended = recommend_config(profile)
|
|
146
|
+
|
|
147
|
+
# Non-interactive: degrade gracefully
|
|
148
|
+
if not sys.stdin.isatty():
|
|
149
|
+
logger.warning(
|
|
150
|
+
"Vector search disabled — no COLONY_EMBED_MODEL configured. "
|
|
151
|
+
"Run 'colony vector setup' to enable. "
|
|
152
|
+
"Recommended: COLONY_EMBED_MODEL=%s COLONY_EMBED_DIMS=%d COLONY_EMBED_PROVIDER=%s",
|
|
153
|
+
recommended.model_id,
|
|
154
|
+
recommended.dimensions,
|
|
155
|
+
recommended.provider,
|
|
156
|
+
)
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
# Interactive: print recommendation
|
|
160
|
+
print("\n--- Colony Vector Store Setup ---")
|
|
161
|
+
print(f"Detected hardware: {profile}")
|
|
162
|
+
print(f"Recommended model: {recommended.model_id}")
|
|
163
|
+
print(f"Provider: {recommended.provider}, Dimensions: {recommended.dimensions}")
|
|
164
|
+
print("\nSet these environment variables to enable vector search:")
|
|
165
|
+
print(f" COLONY_EMBED_PROVIDER={recommended.provider}")
|
|
166
|
+
print(f" COLONY_EMBED_MODEL={recommended.model_id}")
|
|
167
|
+
print(f" COLONY_EMBED_DIMS={recommended.dimensions}")
|
|
168
|
+
if recommended.quantization:
|
|
169
|
+
print(f" COLONY_EMBED_QUANTIZATION={recommended.quantization}")
|
|
170
|
+
print()
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async def ensure_model_ready(config: EmbeddingConfig) -> None:
|
|
175
|
+
"""Download model weights if not present in cache_dir. Run warmup."""
|
|
176
|
+
try:
|
|
177
|
+
from huggingface_hub import snapshot_download
|
|
178
|
+
|
|
179
|
+
cache_dir = config.cache_dir or None
|
|
180
|
+
snapshot_download(config.model_id, cache_dir=cache_dir)
|
|
181
|
+
logger.info("Model %s ready (cache=%s)", config.model_id, cache_dir or "default")
|
|
182
|
+
except ImportError:
|
|
183
|
+
logger.warning("huggingface_hub not installed — skipping model download check")
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
logger.warning("Model download check failed (may already be cached): %s", exc)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# CLI entry points
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
def cli_setup(args: list[str] | None = None) -> None:
|
|
193
|
+
"""CLI handler for ``colony vector setup``."""
|
|
194
|
+
import argparse
|
|
195
|
+
|
|
196
|
+
parser = argparse.ArgumentParser(description="Colony vector store setup")
|
|
197
|
+
parser.add_argument("--model", help="HuggingFace model ID to use")
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"--tier",
|
|
200
|
+
choices=list(_TIER_MATRIX.keys()),
|
|
201
|
+
help="Hardware tier (auto-detected if not specified)",
|
|
202
|
+
)
|
|
203
|
+
parsed = parser.parse_args(args if args is not None else [])
|
|
204
|
+
|
|
205
|
+
profile = detect_hardware()
|
|
206
|
+
print(f"\nHardware profile:")
|
|
207
|
+
print(f" CUDA: {'Yes' if profile.cuda_available else 'No'}"
|
|
208
|
+
+ (f" ({profile.cuda_device_count} device(s), {profile.cuda_vram_gb:.1f}GB VRAM)" if profile.cuda_available else ""))
|
|
209
|
+
print(f" MLX: {'Yes' if profile.mlx_available else 'No'}"
|
|
210
|
+
+ (f" ({profile.mlx_memory_gb:.1f}GB unified)" if profile.mlx_available else ""))
|
|
211
|
+
print(f" CPU: {profile.cpu_cores} cores, {profile.cpu_ram_gb:.1f}GB RAM")
|
|
212
|
+
|
|
213
|
+
if parsed.tier:
|
|
214
|
+
tier = parsed.tier
|
|
215
|
+
else:
|
|
216
|
+
tier = _detect_tier(profile)
|
|
217
|
+
print(f"\nSelected tier: {tier}")
|
|
218
|
+
|
|
219
|
+
if parsed.model:
|
|
220
|
+
provider, _, dims, quant = _TIER_MATRIX[tier]
|
|
221
|
+
config = EmbeddingConfig(
|
|
222
|
+
provider=provider,
|
|
223
|
+
model_id=parsed.model,
|
|
224
|
+
dimensions=dims,
|
|
225
|
+
quantization=quant,
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
config = recommend_config(profile)
|
|
229
|
+
|
|
230
|
+
print(f"Model: {config.model_id}")
|
|
231
|
+
print(f"Provider: {config.provider}, Dimensions: {config.dimensions}")
|
|
232
|
+
if config.quantization:
|
|
233
|
+
print(f"Quantization: {config.quantization}")
|
|
234
|
+
|
|
235
|
+
print("\nAdd to your .env:")
|
|
236
|
+
print(f" COLONY_EMBED_PROVIDER={config.provider}")
|
|
237
|
+
print(f" COLONY_EMBED_MODEL={config.model_id}")
|
|
238
|
+
print(f" COLONY_EMBED_DIMS={config.dimensions}")
|
|
239
|
+
if config.quantization:
|
|
240
|
+
print(f" COLONY_EMBED_QUANTIZATION={config.quantization}")
|
|
241
|
+
|
|
242
|
+
# Download model
|
|
243
|
+
try:
|
|
244
|
+
print(f"\nDownloading model weights for {config.model_id}...")
|
|
245
|
+
from huggingface_hub import snapshot_download
|
|
246
|
+
cache_dir = config.cache_dir or None
|
|
247
|
+
snapshot_download(config.model_id, cache_dir=cache_dir)
|
|
248
|
+
print("Model downloaded successfully.")
|
|
249
|
+
except ImportError:
|
|
250
|
+
print("huggingface_hub not installed — install with: pip install huggingface-hub")
|
|
251
|
+
except Exception as exc:
|
|
252
|
+
print(f"Download failed (model may already be cached): {exc}")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def cli_rebuild(args: list[str] | None = None) -> None:
|
|
256
|
+
"""CLI handler for ``colony vector rebuild``."""
|
|
257
|
+
import argparse
|
|
258
|
+
|
|
259
|
+
parser = argparse.ArgumentParser(description="Rebuild vector collections")
|
|
260
|
+
parser.add_argument(
|
|
261
|
+
"--collection",
|
|
262
|
+
default="all",
|
|
263
|
+
help="Collection to rebuild (default: all)",
|
|
264
|
+
)
|
|
265
|
+
parsed = parser.parse_args(args if args is not None else [])
|
|
266
|
+
|
|
267
|
+
print(f"Rebuilding vector collection(s): {parsed.collection}")
|
|
268
|
+
print("NOTE: Vector search requires COLONY_EMBED_MODEL to be set.")
|
|
269
|
+
print(" Run 'colony vector setup' first, then use scripts/vector_backfill.py")
|
|
270
|
+
print(" for the full backfill process.")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def cli_main() -> None:
|
|
274
|
+
"""Entry point for ``colony vector <subcommand>``."""
|
|
275
|
+
if len(sys.argv) < 3:
|
|
276
|
+
print("Usage: colony vector {setup|rebuild}")
|
|
277
|
+
sys.exit(1)
|
|
278
|
+
|
|
279
|
+
subcommand = sys.argv[2]
|
|
280
|
+
remaining = sys.argv[3:]
|
|
281
|
+
|
|
282
|
+
if subcommand == "setup":
|
|
283
|
+
cli_setup(remaining)
|
|
284
|
+
elif subcommand == "rebuild":
|
|
285
|
+
cli_rebuild(remaining)
|
|
286
|
+
else:
|
|
287
|
+
print(f"Unknown subcommand: {subcommand}")
|
|
288
|
+
print("Usage: colony vector {setup|rebuild}")
|
|
289
|
+
sys.exit(1)
|