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/goals/priority.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Goal Priority Scoring and Progress Tracking.
|
|
2
|
+
|
|
3
|
+
GoalPriorityScorer: composite priority score with urgency decay, importance,
|
|
4
|
+
user preferences, blocking bonus, and age decay.
|
|
5
|
+
|
|
6
|
+
GoalProgressTracker: weighted completion fraction and completion time estimation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from apsimo.goals.models import Goal, GoalDAG, GoalPriority, SubtaskStatus
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PriorityScore:
|
|
21
|
+
"""Composite priority score with component breakdown."""
|
|
22
|
+
total: float # 0.0–100.0 final score
|
|
23
|
+
urgency: float # Deadline proximity component
|
|
24
|
+
importance: float # User-stated importance component
|
|
25
|
+
preference_bonus: float # Learned preference modifier
|
|
26
|
+
blocking_penalty: float # Bonus if goal is blocking other goals
|
|
27
|
+
age_decay: float # Score decay for stale goals
|
|
28
|
+
breakdown: Dict[str, float] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class UserPreferenceProfile:
|
|
33
|
+
"""Learned user preferences influencing goal prioritization.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
domain_weights: Per-domain priority multipliers (e.g., "health": 1.3).
|
|
37
|
+
preferred_hours: Hours of day when the user prefers work to execute.
|
|
38
|
+
completion_patterns: Historical goal completion rates by category.
|
|
39
|
+
avoidance_domains: Domains the user tends to defer or abandon.
|
|
40
|
+
"""
|
|
41
|
+
domain_weights: Dict[str, float] = field(default_factory=dict)
|
|
42
|
+
preferred_hours: List[int] = field(default_factory=list)
|
|
43
|
+
completion_patterns: Dict[str, float] = field(default_factory=dict)
|
|
44
|
+
avoidance_domains: List[str] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
# EMA smoothing factor for learning (α = 0.3 → weights recent behavior ~3× more)
|
|
47
|
+
ema_alpha: float = 0.3
|
|
48
|
+
|
|
49
|
+
def update_domain_completion(self, domain: str, success: bool) -> None:
|
|
50
|
+
"""Update completion pattern for a domain using EMA."""
|
|
51
|
+
value = 1.0 if success else 0.0
|
|
52
|
+
if domain in self.completion_patterns:
|
|
53
|
+
old = self.completion_patterns[domain]
|
|
54
|
+
self.completion_patterns[domain] = self.ema_alpha * value + (1 - self.ema_alpha) * old
|
|
55
|
+
else:
|
|
56
|
+
self.completion_patterns[domain] = value
|
|
57
|
+
|
|
58
|
+
def update_domain_weight(self, domain: str, delta: float) -> None:
|
|
59
|
+
"""Adjust domain weight using EMA."""
|
|
60
|
+
current = self.domain_weights.get(domain, 1.0)
|
|
61
|
+
new_weight = self.ema_alpha * (current + delta) + (1 - self.ema_alpha) * current
|
|
62
|
+
self.domain_weights[domain] = max(0.1, min(3.0, new_weight))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _deadline_urgency(goal: Goal) -> float:
|
|
66
|
+
"""Compute urgency in [0, 1] based on deadline proximity.
|
|
67
|
+
|
|
68
|
+
urgency(t) = 1 / (1 + exp(-k * (t_remaining_fraction - 0.5)))
|
|
69
|
+
where k = 10 (steepness constant)
|
|
70
|
+
"""
|
|
71
|
+
if goal.deadline is None:
|
|
72
|
+
return 0.0
|
|
73
|
+
|
|
74
|
+
now = datetime.now(timezone.utc)
|
|
75
|
+
if now >= goal.deadline:
|
|
76
|
+
return 1.0
|
|
77
|
+
|
|
78
|
+
total_window = (goal.deadline - goal.created_at).total_seconds()
|
|
79
|
+
if total_window <= 0:
|
|
80
|
+
return 1.0
|
|
81
|
+
|
|
82
|
+
remaining = (goal.deadline - now).total_seconds()
|
|
83
|
+
t_remaining_fraction = remaining / total_window
|
|
84
|
+
|
|
85
|
+
k = 10.0
|
|
86
|
+
urgency = 1.0 / (1.0 + math.exp(-k * (t_remaining_fraction - 0.5)))
|
|
87
|
+
# Invert so that low remaining fraction = high urgency
|
|
88
|
+
return round(1.0 - urgency, 4)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _base_importance(goal: Goal) -> float:
|
|
92
|
+
"""Derive importance from goal's explicit priority (normalised to [0, 1])."""
|
|
93
|
+
return goal.priority.value / 100.0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _preference_modifier(goal: Goal, preferences: UserPreferenceProfile) -> float:
|
|
97
|
+
"""Compute preference bonus in [0, 1] from user preference profile."""
|
|
98
|
+
# Use goal tags to determine domain
|
|
99
|
+
domain = goal.tags.get("domain", "")
|
|
100
|
+
if not domain:
|
|
101
|
+
return 0.0
|
|
102
|
+
|
|
103
|
+
weight = preferences.domain_weights.get(domain, 1.0)
|
|
104
|
+
# Map weight (typically 0.1–3.0) to a bonus in [0, 1]
|
|
105
|
+
bonus = min(1.0, max(0.0, (weight - 1.0) / 2.0))
|
|
106
|
+
return round(bonus, 4)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _blocking_bonus(goal: Goal, all_goals: Optional[List[Goal]] = None) -> float:
|
|
110
|
+
"""Return 1.0 if this goal is a dependency (parent) for other active goals."""
|
|
111
|
+
if all_goals is None:
|
|
112
|
+
return 0.0
|
|
113
|
+
for other in all_goals:
|
|
114
|
+
if other.parent_goal_id == goal.goal_id and not other.is_terminal():
|
|
115
|
+
return 1.0
|
|
116
|
+
return 0.0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _age_decay(goal: Goal) -> float:
|
|
120
|
+
"""Compute staleness penalty in [0, 1] for old untouched goals."""
|
|
121
|
+
age = goal.age_hours()
|
|
122
|
+
# Penalty grows logarithmically; at 168 hours (1 week) = ~0.5
|
|
123
|
+
if age < 1:
|
|
124
|
+
return 0.0
|
|
125
|
+
return round(min(1.0, math.log(age) / math.log(168.0)), 4)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class GoalPriorityScorer:
|
|
129
|
+
"""Compute composite priority scores for goals."""
|
|
130
|
+
|
|
131
|
+
WEIGHTS = {
|
|
132
|
+
"urgency": 0.35,
|
|
133
|
+
"importance": 0.30,
|
|
134
|
+
"preference_bonus": 0.15,
|
|
135
|
+
"blocking": 0.10,
|
|
136
|
+
"age_decay": -0.10, # Negative — reduces priority for very old stale goals
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def score(
|
|
140
|
+
self,
|
|
141
|
+
goal: Goal,
|
|
142
|
+
preferences: Optional[UserPreferenceProfile] = None,
|
|
143
|
+
all_goals: Optional[List[Goal]] = None,
|
|
144
|
+
) -> PriorityScore:
|
|
145
|
+
"""Compute a composite priority score for a goal.
|
|
146
|
+
|
|
147
|
+
Components:
|
|
148
|
+
- urgency: deadline_urgency(goal) — 0.0 if no deadline, 1.0 if overdue
|
|
149
|
+
- importance: base_importance(goal) — from user tags, source, explicit priority
|
|
150
|
+
- preference: preference_modifier(goal, preferences) — domain preference bonus
|
|
151
|
+
- blocking: is_blocking_bonus(goal) — if other goals depend on this one
|
|
152
|
+
- age_decay: stale_penalty(goal) — reduces priority for untouched old goals
|
|
153
|
+
"""
|
|
154
|
+
prefs = preferences or UserPreferenceProfile()
|
|
155
|
+
|
|
156
|
+
urgency = _deadline_urgency(goal)
|
|
157
|
+
importance = _base_importance(goal)
|
|
158
|
+
preference = _preference_modifier(goal, prefs)
|
|
159
|
+
blocking = _blocking_bonus(goal, all_goals)
|
|
160
|
+
decay = _age_decay(goal)
|
|
161
|
+
|
|
162
|
+
components = {
|
|
163
|
+
"urgency": urgency,
|
|
164
|
+
"importance": importance,
|
|
165
|
+
"preference_bonus": preference,
|
|
166
|
+
"blocking": blocking,
|
|
167
|
+
"age_decay": decay,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
total = 100.0 * (
|
|
171
|
+
self.WEIGHTS["urgency"] * urgency
|
|
172
|
+
+ self.WEIGHTS["importance"] * importance
|
|
173
|
+
+ self.WEIGHTS["preference_bonus"] * preference
|
|
174
|
+
+ self.WEIGHTS["blocking"] * blocking
|
|
175
|
+
+ self.WEIGHTS["age_decay"] * decay # this is negative
|
|
176
|
+
)
|
|
177
|
+
total = max(0.0, min(100.0, total))
|
|
178
|
+
|
|
179
|
+
return PriorityScore(
|
|
180
|
+
total=round(total, 2),
|
|
181
|
+
urgency=urgency,
|
|
182
|
+
importance=importance,
|
|
183
|
+
preference_bonus=preference,
|
|
184
|
+
blocking_penalty=blocking,
|
|
185
|
+
age_decay=decay,
|
|
186
|
+
breakdown=components,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def rank_goals(
|
|
190
|
+
self,
|
|
191
|
+
goals: List[Goal],
|
|
192
|
+
preferences: Optional[UserPreferenceProfile] = None,
|
|
193
|
+
) -> List[Goal]:
|
|
194
|
+
"""Return goals sorted by descending priority score."""
|
|
195
|
+
scored = [(g, self.score(g, preferences, goals).total) for g in goals]
|
|
196
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
197
|
+
return [g for g, _ in scored]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class GoalProgressTracker:
|
|
201
|
+
"""Track and report goal progress."""
|
|
202
|
+
|
|
203
|
+
def update_progress(self, goal_id: str, dag: GoalDAG) -> float:
|
|
204
|
+
"""Recompute progress fraction from subtask statuses.
|
|
205
|
+
|
|
206
|
+
Weights subtasks on the critical path more heavily (2× weight).
|
|
207
|
+
|
|
208
|
+
Returns updated progress_pct (0.0–1.0).
|
|
209
|
+
"""
|
|
210
|
+
total_weight = 0.0
|
|
211
|
+
done_weight = 0.0
|
|
212
|
+
|
|
213
|
+
for s in dag.subtasks.values():
|
|
214
|
+
weight = 2.0 if s.is_critical_path else 1.0
|
|
215
|
+
total_weight += weight
|
|
216
|
+
if s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}:
|
|
217
|
+
done_weight += weight
|
|
218
|
+
|
|
219
|
+
return done_weight / total_weight if total_weight > 0 else 0.0
|
|
220
|
+
|
|
221
|
+
def estimate_completion(
|
|
222
|
+
self,
|
|
223
|
+
goal: Goal,
|
|
224
|
+
dag: GoalDAG,
|
|
225
|
+
historical_perf: Optional[Dict[str, float]] = None,
|
|
226
|
+
) -> Optional[datetime]:
|
|
227
|
+
"""Estimate completion time based on critical path remaining work.
|
|
228
|
+
|
|
229
|
+
Uses three-point estimation:
|
|
230
|
+
estimated_remaining = sum(subtask.estimated_hours * perf_factor)
|
|
231
|
+
for remaining critical path subtasks.
|
|
232
|
+
|
|
233
|
+
Returns estimated completion datetime, or None if not estimable.
|
|
234
|
+
"""
|
|
235
|
+
if not dag.critical_path:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
perf = historical_perf or {}
|
|
239
|
+
remaining_hours = 0.0
|
|
240
|
+
for sid in dag.critical_path:
|
|
241
|
+
subtask = dag.subtasks.get(sid)
|
|
242
|
+
if subtask is None:
|
|
243
|
+
continue
|
|
244
|
+
if subtask.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}:
|
|
245
|
+
continue
|
|
246
|
+
est = subtask.estimated_hours if subtask.estimated_hours is not None else 1.0
|
|
247
|
+
pf = perf.get(subtask.job_type, 1.0)
|
|
248
|
+
remaining_hours += est * pf
|
|
249
|
+
|
|
250
|
+
if remaining_hours <= 0:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
from datetime import timedelta
|
|
254
|
+
return datetime.now(timezone.utc) + timedelta(hours=remaining_hours)
|
|
255
|
+
|
|
256
|
+
def check_blocked(self, goal: "Goal", dag: GoalDAG) -> Optional[str]:
|
|
257
|
+
"""Return a reason string if the goal appears blocked, else None."""
|
|
258
|
+
ready = dag.ready_subtasks()
|
|
259
|
+
if ready:
|
|
260
|
+
return None # Work available
|
|
261
|
+
|
|
262
|
+
# Check if there are non-terminal subtasks with unmet deps
|
|
263
|
+
for s in dag.subtasks.values():
|
|
264
|
+
if s.status in {SubtaskStatus.PENDING, SubtaskStatus.BLOCKED}:
|
|
265
|
+
# All deps failed?
|
|
266
|
+
for dep_id in s.depends_on:
|
|
267
|
+
dep = dag.subtasks.get(dep_id)
|
|
268
|
+
if dep and dep.status == SubtaskStatus.FAILED:
|
|
269
|
+
return f"Subtask {s.subtask_id} blocked by failed dependency {dep_id}"
|
|
270
|
+
return None
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""GoalQueueBridge — integrate Goal Engine with the distributed task queue.
|
|
2
|
+
|
|
3
|
+
Converts Subtask nodes into task queue Jobs, dispatches ready subtasks,
|
|
4
|
+
and processes job results to update goal/subtask state.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Protocol
|
|
11
|
+
|
|
12
|
+
from apsimo.goals.models import (
|
|
13
|
+
Goal,
|
|
14
|
+
GoalDAG,
|
|
15
|
+
GoalPriority,
|
|
16
|
+
GoalStatus,
|
|
17
|
+
Subtask,
|
|
18
|
+
SubtaskStatus,
|
|
19
|
+
)
|
|
20
|
+
from apsimo.task_queue.models import (
|
|
21
|
+
Job,
|
|
22
|
+
JobCapabilityRequirement,
|
|
23
|
+
JobPriority,
|
|
24
|
+
JobResult,
|
|
25
|
+
JobType,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Priority mapping: GoalPriority → JobPriority
|
|
31
|
+
_PRIORITY_MAP: Dict[int, JobPriority] = {
|
|
32
|
+
GoalPriority.BACKGROUND: JobPriority.BACKGROUND,
|
|
33
|
+
GoalPriority.LOW: JobPriority.LOW,
|
|
34
|
+
GoalPriority.NORMAL: JobPriority.NORMAL,
|
|
35
|
+
GoalPriority.HIGH: JobPriority.HIGH,
|
|
36
|
+
GoalPriority.CRITICAL: JobPriority.CRITICAL,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _map_priority(goal_priority: GoalPriority) -> JobPriority:
|
|
41
|
+
return _PRIORITY_MAP.get(goal_priority.value, JobPriority.NORMAL)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _job_type_from_string(job_type: str) -> JobType:
|
|
45
|
+
"""Convert subtask job_type string to JobType enum (with fallback)."""
|
|
46
|
+
try:
|
|
47
|
+
return JobType(job_type)
|
|
48
|
+
except ValueError:
|
|
49
|
+
return JobType.CUSTOM
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class QueueBackend(Protocol):
|
|
53
|
+
"""Minimal interface expected from the task queue."""
|
|
54
|
+
|
|
55
|
+
def post(self, job: Job) -> str:
|
|
56
|
+
"""Post a job and return its job_id."""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class InMemoryQueueBackend:
|
|
61
|
+
"""Simple in-memory queue backend for testing without a real queue."""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
self._jobs: Dict[str, Job] = {}
|
|
65
|
+
self._posted_order: List[str] = []
|
|
66
|
+
|
|
67
|
+
def post(self, job: Job) -> str:
|
|
68
|
+
self._jobs[job.job_id] = job
|
|
69
|
+
self._posted_order.append(job.job_id)
|
|
70
|
+
logger.debug("InMemoryQueue: posted job %s (type=%s)", job.job_id, job.job_type)
|
|
71
|
+
return job.job_id
|
|
72
|
+
|
|
73
|
+
def get_job(self, job_id: str) -> Optional[Job]:
|
|
74
|
+
return self._jobs.get(job_id)
|
|
75
|
+
|
|
76
|
+
def list_jobs(self) -> List[Job]:
|
|
77
|
+
return [self._jobs[jid] for jid in self._posted_order if jid in self._jobs]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class GoalQueueBridge:
|
|
81
|
+
"""Bridge between the Goal Engine and the distributed task queue."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, queue: Optional[QueueBackend] = None) -> None:
|
|
84
|
+
# A missing production binding must not silently become a test queue.
|
|
85
|
+
self._queue = queue
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def available(self) -> bool:
|
|
89
|
+
"""Whether a caller supplied a backend, not proof of worker liveness."""
|
|
90
|
+
return self._queue is not None
|
|
91
|
+
|
|
92
|
+
def subtask_to_job(self, goal: Goal, subtask: Subtask) -> Job:
|
|
93
|
+
"""Convert a ready Subtask into a task queue Job.
|
|
94
|
+
|
|
95
|
+
Priority mapping:
|
|
96
|
+
GoalPriority.CRITICAL → JobPriority.CRITICAL
|
|
97
|
+
GoalPriority.HIGH → JobPriority.HIGH
|
|
98
|
+
GoalPriority.NORMAL → JobPriority.NORMAL
|
|
99
|
+
GoalPriority.LOW → JobPriority.LOW
|
|
100
|
+
GoalPriority.BACKGROUND → JobPriority.BACKGROUND
|
|
101
|
+
|
|
102
|
+
Deadline: goal.deadline is forwarded if set.
|
|
103
|
+
"""
|
|
104
|
+
return Job(
|
|
105
|
+
job_type=_job_type_from_string(subtask.job_type),
|
|
106
|
+
payload={
|
|
107
|
+
"goal_id": goal.goal_id,
|
|
108
|
+
"subtask_id": subtask.subtask_id,
|
|
109
|
+
**subtask.payload,
|
|
110
|
+
},
|
|
111
|
+
priority=_map_priority(goal.priority),
|
|
112
|
+
capabilities=[
|
|
113
|
+
JobCapabilityRequirement(name=cap)
|
|
114
|
+
for cap in subtask.capabilities
|
|
115
|
+
],
|
|
116
|
+
deadline=goal.deadline,
|
|
117
|
+
tags={
|
|
118
|
+
"goal_id": goal.goal_id,
|
|
119
|
+
"goal_title": goal.title[:64],
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def dispatch_ready_subtasks(self, goal: Goal, dag: GoalDAG) -> int:
|
|
124
|
+
"""Dispatch all ready subtasks to the task queue.
|
|
125
|
+
|
|
126
|
+
Returns the number of jobs posted.
|
|
127
|
+
"""
|
|
128
|
+
if self._queue is None:
|
|
129
|
+
return 0
|
|
130
|
+
count = 0
|
|
131
|
+
for subtask in dag.ready_subtasks():
|
|
132
|
+
job = self.subtask_to_job(goal, subtask)
|
|
133
|
+
job_id = self._queue.post(job)
|
|
134
|
+
subtask.job_id = job_id
|
|
135
|
+
subtask.status = SubtaskStatus.DISPATCHED
|
|
136
|
+
count += 1
|
|
137
|
+
logger.info(
|
|
138
|
+
"Dispatched subtask %s (job_id=%s) for goal %s",
|
|
139
|
+
subtask.subtask_id, job_id, goal.goal_id,
|
|
140
|
+
)
|
|
141
|
+
return count
|
|
142
|
+
|
|
143
|
+
def cancel_all_jobs(self, dag: GoalDAG) -> None:
|
|
144
|
+
"""Cancel all dispatched/running jobs in the DAG (best-effort)."""
|
|
145
|
+
for subtask in dag.subtasks.values():
|
|
146
|
+
if subtask.status in {SubtaskStatus.DISPATCHED, SubtaskStatus.RUNNING}:
|
|
147
|
+
subtask.status = SubtaskStatus.FAILED
|
|
148
|
+
subtask.error = "Job cancelled: goal abandoned"
|
|
149
|
+
logger.info("Cancelled job for subtask %s", subtask.subtask_id)
|