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,191 @@
|
|
|
1
|
+
"""Skill registry for loading and managing initiative executor skills.
|
|
2
|
+
|
|
3
|
+
Skills are discovered dynamically and can be hot-reloaded.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any, Dict, List, Optional, Type
|
|
11
|
+
|
|
12
|
+
from .base import InitiativeExecutorSkill
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _SkillView:
|
|
18
|
+
"""Read-only adapter exposing a registered executor skill in the shape the
|
|
19
|
+
host/API layer expects (name/description/skill_id/status).
|
|
20
|
+
|
|
21
|
+
Built-in executor skills are runtime executors, not persisted skill
|
|
22
|
+
manifests awaiting approval, so they are always 'active' and never appear
|
|
23
|
+
under a DRAFT filter.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, skill: "InitiativeExecutorSkill"):
|
|
27
|
+
name = (
|
|
28
|
+
getattr(skill, "skill_name", None)
|
|
29
|
+
or getattr(skill, "skill_id", None)
|
|
30
|
+
or skill.__class__.__name__
|
|
31
|
+
)
|
|
32
|
+
self.skill_id = name
|
|
33
|
+
self.name = name
|
|
34
|
+
doc = (getattr(skill, "__doc__", "") or "").strip()
|
|
35
|
+
self.description = doc.split("\n", 1)[0].strip() if doc else name
|
|
36
|
+
self.status = "active"
|
|
37
|
+
self.created_at = None
|
|
38
|
+
self.tags: List[str] = []
|
|
39
|
+
self.trigger_patterns: List[str] = []
|
|
40
|
+
self.version = getattr(skill, "skill_version", None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SkillRegistry:
|
|
45
|
+
"""Registry of initiative executor skills.
|
|
46
|
+
|
|
47
|
+
Loads built-in skills automatically. Additional skills can be
|
|
48
|
+
registered manually or discovered from directories.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, graph_client=None, event_bus=None, telemetry=None):
|
|
52
|
+
self._skills: Dict[str, InitiativeExecutorSkill] = {}
|
|
53
|
+
self._graph = graph_client
|
|
54
|
+
self._events = event_bus
|
|
55
|
+
self._telemetry = telemetry
|
|
56
|
+
|
|
57
|
+
# Auto-load built-in skills
|
|
58
|
+
self._load_builtin_skills()
|
|
59
|
+
|
|
60
|
+
def _load_builtin_skills(self) -> None:
|
|
61
|
+
"""Load all built-in executor skills."""
|
|
62
|
+
builtin_skills = [
|
|
63
|
+
"apsimo.skills.executors.subsystem_health",
|
|
64
|
+
"apsimo.skills.executors.data_quality",
|
|
65
|
+
"apsimo.skills.executors.operational_hygiene",
|
|
66
|
+
"apsimo.skills.executors.capability_gap",
|
|
67
|
+
"apsimo.skills.executors.knowledge_acquisition",
|
|
68
|
+
"apsimo.skills.executors.behavioral_correction",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
for module_path in builtin_skills:
|
|
72
|
+
try:
|
|
73
|
+
module = importlib.import_module(module_path)
|
|
74
|
+
# Find skill classes in the module
|
|
75
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
76
|
+
if (
|
|
77
|
+
issubclass(obj, InitiativeExecutorSkill)
|
|
78
|
+
and obj is not InitiativeExecutorSkill
|
|
79
|
+
and not getattr(obj, "__abstractmethods__", None)
|
|
80
|
+
):
|
|
81
|
+
skill = obj(
|
|
82
|
+
graph_client=self._graph,
|
|
83
|
+
event_bus=self._events,
|
|
84
|
+
telemetry=self._telemetry,
|
|
85
|
+
)
|
|
86
|
+
self.register(skill)
|
|
87
|
+
logger.info("Loaded built-in skill: %s", skill.skill_name)
|
|
88
|
+
except ImportError as e:
|
|
89
|
+
logger.warning("Failed to load built-in skill module %s: %s", module_path, e)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error("Error loading built-in skill %s: %s", module_path, e)
|
|
92
|
+
|
|
93
|
+
def register(self, skill: InitiativeExecutorSkill) -> None:
|
|
94
|
+
"""Register a skill instance."""
|
|
95
|
+
self._skills[skill.skill_name] = skill
|
|
96
|
+
logger.debug("Registered skill: %s", skill.skill_name)
|
|
97
|
+
|
|
98
|
+
def unregister(self, skill_name: str) -> None:
|
|
99
|
+
"""Unregister a skill by name."""
|
|
100
|
+
if skill_name in self._skills:
|
|
101
|
+
del self._skills[skill_name]
|
|
102
|
+
logger.debug("Unregistered skill: %s", skill_name)
|
|
103
|
+
|
|
104
|
+
def get(self, skill_name: str) -> Optional[InitiativeExecutorSkill]:
|
|
105
|
+
"""Get a skill by name."""
|
|
106
|
+
return self._skills.get(skill_name)
|
|
107
|
+
|
|
108
|
+
def list_skills(self) -> List[str]:
|
|
109
|
+
"""List all registered skill names."""
|
|
110
|
+
return list(self._skills.keys())
|
|
111
|
+
|
|
112
|
+
async def list_all(self, status=None) -> List["_SkillView"]:
|
|
113
|
+
"""List all registered skills as lightweight views.
|
|
114
|
+
|
|
115
|
+
Mirrors the persisted-skill API shape so host context-assembly and the
|
|
116
|
+
/skills endpoints can enumerate the runtime executor registry. A status
|
|
117
|
+
filter (e.g. SkillStatus.DRAFT) returns only matching skills; built-in
|
|
118
|
+
executors are always 'active', so a DRAFT filter yields an empty list.
|
|
119
|
+
"""
|
|
120
|
+
views = [_SkillView(s) for s in self._skills.values()]
|
|
121
|
+
if status is not None:
|
|
122
|
+
wanted = getattr(status, "value", status)
|
|
123
|
+
views = [v for v in views if v.status == wanted]
|
|
124
|
+
return views
|
|
125
|
+
|
|
126
|
+
async def find_skill_for_category(
|
|
127
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
128
|
+
) -> Optional[InitiativeExecutorSkill]:
|
|
129
|
+
"""Find the first skill that can execute a given category.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
category: InitiativeCategory node data
|
|
133
|
+
context: Execution context
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
The matching skill, or None if no skill can handle it
|
|
137
|
+
"""
|
|
138
|
+
executor_skill_name = category.get("executor_skill")
|
|
139
|
+
|
|
140
|
+
for skill in self._skills.values():
|
|
141
|
+
# Fast path: exact skill name match
|
|
142
|
+
if executor_skill_name and skill.skill_name == executor_skill_name:
|
|
143
|
+
if await skill.can_execute(category, context):
|
|
144
|
+
return skill
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# Slow path: ask each skill
|
|
148
|
+
try:
|
|
149
|
+
if await skill.can_execute(category, context):
|
|
150
|
+
return skill
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.warning(
|
|
153
|
+
"Skill %s.can_execute() failed: %s", skill.skill_name, e
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
async def execute(
|
|
159
|
+
self,
|
|
160
|
+
skill_name: str,
|
|
161
|
+
initiative_context: Any, # InitiativeExecutionContext
|
|
162
|
+
) -> Any: # ExecutionResult
|
|
163
|
+
"""Execute an initiative using a specific skill.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
skill_name: Name of the skill to use
|
|
167
|
+
initiative_context: The initiative execution context
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
ExecutionResult
|
|
171
|
+
"""
|
|
172
|
+
skill = self._skills.get(skill_name)
|
|
173
|
+
if not skill:
|
|
174
|
+
logger.error("Skill not found: %s", skill_name)
|
|
175
|
+
return None # type: ignore[return-value]
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
return await skill.execute(initiative_context)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error("Skill %s execution failed: %s", skill_name, e)
|
|
181
|
+
return None # type: ignore[return-value]
|
|
182
|
+
|
|
183
|
+
def health_check(self) -> Dict[str, Any]:
|
|
184
|
+
"""Return health status of all registered skills."""
|
|
185
|
+
return {
|
|
186
|
+
"skills": {
|
|
187
|
+
name: skill.health_check()
|
|
188
|
+
for name, skill in self._skills.items()
|
|
189
|
+
},
|
|
190
|
+
"count": len(self._skills),
|
|
191
|
+
}
|
apsimo/skills/runtime.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Colony skill runtime handle.
|
|
2
|
+
|
|
3
|
+
Provides synthesized skills with a ``colony`` object that exposes
|
|
4
|
+
tool invocation via the sidecar HTTP API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _ToolProxy:
|
|
18
|
+
"""Namespace that lets skills call ``colony.tools.invoke(name, args)``."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, base_url: str, headers: dict[str, str]) -> None:
|
|
21
|
+
self._base_url = base_url
|
|
22
|
+
self._headers = headers
|
|
23
|
+
|
|
24
|
+
async def invoke(self, name: str, args: dict[str, Any] | None = None) -> Any:
|
|
25
|
+
"""Invoke a Colony MCP tool by name through the sidecar API.
|
|
26
|
+
|
|
27
|
+
Routes to the internal tool-dispatch endpoint so the skill
|
|
28
|
+
runs with the same auth context as the sidecar itself.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
32
|
+
resp = await client.post(
|
|
33
|
+
f"{self._base_url}/v1/host/tools/invoke",
|
|
34
|
+
headers=self._headers,
|
|
35
|
+
json={"tool": name, "args": args or {}},
|
|
36
|
+
)
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
return resp.json()
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
logger.error("colony.tools.invoke(%s) failed: %s", name, exc)
|
|
41
|
+
raise
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ColonyRuntime:
|
|
45
|
+
"""Thin runtime handle injected into synthesized skills that declare
|
|
46
|
+
a ``colony`` parameter in their ``run()`` signature.
|
|
47
|
+
|
|
48
|
+
Usage inside a skill::
|
|
49
|
+
|
|
50
|
+
async def run(colony, query: str) -> str:
|
|
51
|
+
result = await colony.tools.invoke("colony_lookup_facts", {"query": query})
|
|
52
|
+
return str(result)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, base_url: str = "http://127.0.0.1:7777") -> None:
|
|
56
|
+
self._base_url = base_url.rstrip("/")
|
|
57
|
+
self._headers: dict[str, str] = {}
|
|
58
|
+
self.tools = _ToolProxy(self._base_url, self._headers)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Standalone skill-execution runner.
|
|
2
|
+
|
|
3
|
+
Invoked as ``python -m apsimo.skills.sandbox_runner``. Reads a
|
|
4
|
+
single JSON object from stdin describing the skill source, inputs, and
|
|
5
|
+
resource limits; executes the skill's ``run`` function inside a process
|
|
6
|
+
that has been hardened with ``resource.setrlimit``; emits a single JSON
|
|
7
|
+
object on stdout with the result.
|
|
8
|
+
|
|
9
|
+
Parent-side contract (``skills.executor.SkillExecutor``):
|
|
10
|
+
in (stdin) : {"source": str, "inputs": dict, "limits":
|
|
11
|
+
{"mem_mb": int, "cpu_secs": int, "fsize_mb": int}}
|
|
12
|
+
out (stdout): {"status": "success"|"failed",
|
|
13
|
+
"output"?: Any, "error"?: str,
|
|
14
|
+
"peak_memory_kb": int}
|
|
15
|
+
|
|
16
|
+
All errors inside this runner are captured and reported on stdout — the
|
|
17
|
+
runner never prints a stack trace to stderr as part of its contract, and
|
|
18
|
+
exits 0 on normal completion so the parent can distinguish runner
|
|
19
|
+
failures from kernel kills.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from typing import Any, Dict, Optional
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _fallback_repr(value: Any) -> Any:
|
|
32
|
+
"""Best-effort conversion to something JSON can encode."""
|
|
33
|
+
try:
|
|
34
|
+
json.dumps(value)
|
|
35
|
+
return value
|
|
36
|
+
except Exception:
|
|
37
|
+
return repr(value)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _apply_limits(mem_mb: int, cpu_secs: int, fsize_mb: int) -> None:
|
|
41
|
+
"""Apply process-level resource caps. Best-effort per platform."""
|
|
42
|
+
import resource
|
|
43
|
+
|
|
44
|
+
# Memory. Linux has RLIMIT_AS (total virtual memory); Darwin does not
|
|
45
|
+
# enforce RLIMIT_AS reliably — fall back to RLIMIT_DATA on that
|
|
46
|
+
# platform so heap allocations still hit a wall.
|
|
47
|
+
mem_bytes = mem_mb * 1024 * 1024
|
|
48
|
+
mem_set = False
|
|
49
|
+
if sys.platform.startswith("linux"):
|
|
50
|
+
try:
|
|
51
|
+
resource.setrlimit(resource.RLIMIT_AS, (mem_bytes, mem_bytes))
|
|
52
|
+
mem_set = True
|
|
53
|
+
except (ValueError, OSError):
|
|
54
|
+
pass
|
|
55
|
+
if not mem_set:
|
|
56
|
+
try:
|
|
57
|
+
resource.setrlimit(resource.RLIMIT_DATA, (mem_bytes, mem_bytes))
|
|
58
|
+
except (ValueError, OSError, AttributeError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# Wall-clock timeout is enforced by the parent via asyncio.wait_for;
|
|
62
|
+
# RLIMIT_CPU is a belt-and-braces backstop in case the parent's kill
|
|
63
|
+
# signal is delayed or ignored. A generous cushion avoids racing the
|
|
64
|
+
# parent — the parent should always win under normal operation, and
|
|
65
|
+
# this rlimit only fires if the parent's kill is itself blocked.
|
|
66
|
+
try:
|
|
67
|
+
resource.setrlimit(
|
|
68
|
+
resource.RLIMIT_CPU, (cpu_secs + 10, cpu_secs + 15),
|
|
69
|
+
)
|
|
70
|
+
except (ValueError, OSError):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# Writable output size — stops `print(huge_thing)` from filling disks.
|
|
74
|
+
fsize_bytes = fsize_mb * 1024 * 1024
|
|
75
|
+
try:
|
|
76
|
+
resource.setrlimit(resource.RLIMIT_FSIZE, (fsize_bytes, fsize_bytes))
|
|
77
|
+
except (ValueError, OSError):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
# No fork / exec. Blocks `os.fork()`, `os.system()`, subprocess.Popen,
|
|
81
|
+
# multiprocessing — cuts off the broadest class of escape.
|
|
82
|
+
try:
|
|
83
|
+
resource.setrlimit(resource.RLIMIT_NPROC, (1, 1))
|
|
84
|
+
except (ValueError, OSError, AttributeError):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Cap file descriptors so a skill can't exhaust the process table.
|
|
88
|
+
try:
|
|
89
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (64, 64))
|
|
90
|
+
except (ValueError, OSError):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _peak_memory_kb() -> int:
|
|
95
|
+
"""Return peak resident-set size in KB for the current process."""
|
|
96
|
+
try:
|
|
97
|
+
import resource
|
|
98
|
+
ru = resource.getrusage(resource.RUSAGE_SELF)
|
|
99
|
+
# Linux reports ru_maxrss in KB; macOS in bytes. Normalize to KB.
|
|
100
|
+
val = int(ru.ru_maxrss)
|
|
101
|
+
if sys.platform == "darwin" and val > 10 * 1024 * 1024:
|
|
102
|
+
val = val // 1024
|
|
103
|
+
return val
|
|
104
|
+
except Exception:
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def _invoke(source: str, inputs: Dict[str, Any], allowed_imports: List[str] | None = None) -> Any:
|
|
109
|
+
"""Compile + exec the skill in a minimal namespace and call run()."""
|
|
110
|
+
# Minimal builtins. The scanner already rejects the loudest offenders
|
|
111
|
+
# at upload time; stripping them again at runtime is belt-and-braces
|
|
112
|
+
# in case a skill reached the runner through a non-standard path.
|
|
113
|
+
#
|
|
114
|
+
# ``__import__`` and ``__build_class__`` must stay reachable — Python's
|
|
115
|
+
# ``import`` statement and ``class`` statement compile down to calls to
|
|
116
|
+
# them, so without them even well-behaved skills can't run.
|
|
117
|
+
import builtins as _bi
|
|
118
|
+
import importlib as _importlib
|
|
119
|
+
|
|
120
|
+
_banned = {
|
|
121
|
+
"eval", "exec", "compile", "open", "breakpoint", "input",
|
|
122
|
+
}
|
|
123
|
+
_safe_dunders = {"__build_class__", "__name__", "__doc__"}
|
|
124
|
+
|
|
125
|
+
safe_builtins: Dict[str, Any] = {}
|
|
126
|
+
for name in dir(_bi):
|
|
127
|
+
if name in _banned:
|
|
128
|
+
continue
|
|
129
|
+
if name.startswith("_") and name not in _safe_dunders:
|
|
130
|
+
continue
|
|
131
|
+
safe_builtins[name] = getattr(_bi, name)
|
|
132
|
+
|
|
133
|
+
# Replace bare __import__ with a whitelist-enforcing wrapper.
|
|
134
|
+
# Pre-load declared modules so "from X import Y" works without
|
|
135
|
+
# hitting __import__ for declared modules.
|
|
136
|
+
declared_modules: set[str] = set(allowed_imports or [])
|
|
137
|
+
_original_import = _bi.__import__
|
|
138
|
+
preloaded: Dict[str, Any] = {}
|
|
139
|
+
for mod_name in declared_modules:
|
|
140
|
+
try:
|
|
141
|
+
preloaded[mod_name] = _importlib.import_module(mod_name)
|
|
142
|
+
except ImportError:
|
|
143
|
+
pass # will fail at use-time if the skill actually needs it
|
|
144
|
+
|
|
145
|
+
def _restricted_import(name: str, *args: Any, **kwargs: Any) -> Any:
|
|
146
|
+
if name in preloaded:
|
|
147
|
+
return preloaded[name]
|
|
148
|
+
if name in declared_modules:
|
|
149
|
+
return _original_import(name, *args, **kwargs)
|
|
150
|
+
raise ImportError(f"Import of '{name}' not allowed (not in skill manifest)")
|
|
151
|
+
|
|
152
|
+
safe_builtins["__import__"] = _restricted_import
|
|
153
|
+
|
|
154
|
+
namespace: Dict[str, Any] = {"__builtins__": safe_builtins, "__name__": "__skill__"}
|
|
155
|
+
# Inject pre-loaded modules into namespace so top-level imports resolve
|
|
156
|
+
namespace.update(preloaded)
|
|
157
|
+
code = compile(source, "<skill>", "exec")
|
|
158
|
+
exec(code, namespace) # noqa: S102
|
|
159
|
+
|
|
160
|
+
run_fn = namespace.get("run")
|
|
161
|
+
if run_fn is None:
|
|
162
|
+
raise AttributeError("skill has no 'run' function")
|
|
163
|
+
|
|
164
|
+
result = run_fn(**inputs)
|
|
165
|
+
if asyncio.iscoroutine(result):
|
|
166
|
+
result = await result
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _emit(payload: Dict[str, Any]) -> None:
|
|
171
|
+
sys.stdout.write(json.dumps(payload))
|
|
172
|
+
sys.stdout.write("\n")
|
|
173
|
+
sys.stdout.flush()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main() -> int:
|
|
177
|
+
try:
|
|
178
|
+
raw = sys.stdin.read()
|
|
179
|
+
request = json.loads(raw)
|
|
180
|
+
except Exception as exc:
|
|
181
|
+
_emit({
|
|
182
|
+
"status": "failed",
|
|
183
|
+
"error": f"runner: invalid stdin payload: {type(exc).__name__}",
|
|
184
|
+
"peak_memory_kb": 0,
|
|
185
|
+
})
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
source: str = request.get("source", "")
|
|
189
|
+
inputs: Dict[str, Any] = request.get("inputs") or {}
|
|
190
|
+
allowed_imports: List[str] = request.get("allowed_imports") or []
|
|
191
|
+
limits: Dict[str, Any] = request.get("limits") or {}
|
|
192
|
+
|
|
193
|
+
mem_mb = int(limits.get("mem_mb") or 256)
|
|
194
|
+
cpu_secs = int(limits.get("cpu_secs") or 60)
|
|
195
|
+
fsize_mb = int(limits.get("fsize_mb") or 8)
|
|
196
|
+
|
|
197
|
+
_apply_limits(mem_mb=mem_mb, cpu_secs=cpu_secs, fsize_mb=fsize_mb)
|
|
198
|
+
|
|
199
|
+
# Close any inherited fds above the standard three. Best-effort —
|
|
200
|
+
# a skill shouldn't have access to parent sockets or pipes.
|
|
201
|
+
try:
|
|
202
|
+
os.closerange(3, 64)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
output = asyncio.run(_invoke(source, inputs, allowed_imports))
|
|
208
|
+
_emit({
|
|
209
|
+
"status": "success",
|
|
210
|
+
"output": _fallback_repr(output),
|
|
211
|
+
"peak_memory_kb": _peak_memory_kb(),
|
|
212
|
+
})
|
|
213
|
+
except MemoryError:
|
|
214
|
+
_emit({
|
|
215
|
+
"status": "failed",
|
|
216
|
+
"error": "memory_limit_exceeded",
|
|
217
|
+
"peak_memory_kb": _peak_memory_kb(),
|
|
218
|
+
})
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
_emit({
|
|
221
|
+
"status": "failed",
|
|
222
|
+
"error": f"{type(exc).__name__}: {exc}",
|
|
223
|
+
"peak_memory_kb": _peak_memory_kb(),
|
|
224
|
+
})
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
sys.exit(main())
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Colony Skills — skill scheduler with LRU-Cold eviction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from apsimo.skills.budget import ContextBudget
|
|
11
|
+
from apsimo.skills.index import SkillIndex
|
|
12
|
+
from apsimo.skills.loader import LoadedSkill, SkillLoader
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class SkillsConfig:
|
|
19
|
+
"""Configuration for skill scheduling."""
|
|
20
|
+
|
|
21
|
+
context_budget_tokens: int = 8192
|
|
22
|
+
retention_policy: str = "lru" # lru | ttl | manual
|
|
23
|
+
hot_skill_threshold: int = 5 # use_count above which a skill is "hot"
|
|
24
|
+
cold_ttl_secs: int = 1800 # 30 min TTL for cold skills when policy=ttl
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SkillScheduler:
|
|
28
|
+
"""Evaluates trigger patterns, manages skill load/evict lifecycle,
|
|
29
|
+
and enforces the context budget.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
index: SkillIndex,
|
|
35
|
+
loader: SkillLoader,
|
|
36
|
+
budget: ContextBudget,
|
|
37
|
+
config: SkillsConfig,
|
|
38
|
+
) -> None:
|
|
39
|
+
self._index = index
|
|
40
|
+
self._loader = loader
|
|
41
|
+
self._budget = budget
|
|
42
|
+
self._config = config
|
|
43
|
+
|
|
44
|
+
async def evaluate(self, event_text: str) -> List[LoadedSkill]:
|
|
45
|
+
"""Called once per autonomy loop tick.
|
|
46
|
+
|
|
47
|
+
1. Match event_text against SkillIndex (no imports).
|
|
48
|
+
2. For each match, if not loaded and budget permits: load.
|
|
49
|
+
3. If budget full: evict least-recently-used cold skill first.
|
|
50
|
+
4. Return list of all currently loaded skills relevant to event.
|
|
51
|
+
"""
|
|
52
|
+
candidates = self._index.match(event_text)
|
|
53
|
+
|
|
54
|
+
for entry in candidates:
|
|
55
|
+
if entry.skill_id in self._loader.loaded_ids():
|
|
56
|
+
continue # Already loaded; load_for_event will update last_used_at
|
|
57
|
+
|
|
58
|
+
needed = entry.context_tokens_estimate
|
|
59
|
+
current_used = self._loader.token_footprint()
|
|
60
|
+
|
|
61
|
+
if not self._budget.has_capacity(needed, current_used):
|
|
62
|
+
freed = await self.maybe_evict_for(needed)
|
|
63
|
+
if not freed:
|
|
64
|
+
logger.debug(
|
|
65
|
+
"SkillScheduler: cannot load %s — budget exhausted", entry.skill_id
|
|
66
|
+
)
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
loaded = await self._loader.load_for_event(event_text, self._budget)
|
|
70
|
+
return loaded
|
|
71
|
+
|
|
72
|
+
async def evict_cold(self) -> int:
|
|
73
|
+
"""Evict all cold skills (use_count < hot_threshold AND
|
|
74
|
+
last_used_at older than cold_ttl).
|
|
75
|
+
|
|
76
|
+
Returns number of skills evicted.
|
|
77
|
+
"""
|
|
78
|
+
if self._config.retention_policy == "manual":
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
cold_deadline = datetime.now(timezone.utc) - timedelta(
|
|
82
|
+
seconds=self._config.cold_ttl_secs
|
|
83
|
+
)
|
|
84
|
+
evicted = 0
|
|
85
|
+
for ls in self._lru_cold():
|
|
86
|
+
if ls.last_used_at < cold_deadline:
|
|
87
|
+
await self._loader.unload(ls.entry.skill_id)
|
|
88
|
+
evicted += 1
|
|
89
|
+
logger.debug(
|
|
90
|
+
"SkillScheduler: evicted cold skill %s (last_used=%s)",
|
|
91
|
+
ls.entry.skill_id,
|
|
92
|
+
ls.last_used_at.isoformat(),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if evicted:
|
|
96
|
+
logger.info("SkillScheduler: evicted %d cold skill(s)", evicted)
|
|
97
|
+
return evicted
|
|
98
|
+
|
|
99
|
+
async def maybe_evict_for(self, needed_tokens: int) -> bool:
|
|
100
|
+
"""Free at least needed_tokens by evicting cold skills in LRU order.
|
|
101
|
+
|
|
102
|
+
Returns True if sufficient space was freed.
|
|
103
|
+
"""
|
|
104
|
+
if self._config.retention_policy == "manual":
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
candidates = self._lru_cold()
|
|
108
|
+
freed = 0
|
|
109
|
+
for ls in candidates:
|
|
110
|
+
if freed >= needed_tokens:
|
|
111
|
+
break
|
|
112
|
+
freed += ls.entry.context_tokens_estimate
|
|
113
|
+
await self._loader.unload(ls.entry.skill_id)
|
|
114
|
+
logger.debug(
|
|
115
|
+
"SkillScheduler: LRU-evicted skill %s to make room (freed %d tokens)",
|
|
116
|
+
ls.entry.skill_id,
|
|
117
|
+
ls.entry.context_tokens_estimate,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return freed >= needed_tokens
|
|
121
|
+
|
|
122
|
+
def _is_hot(self, skill: LoadedSkill) -> bool:
|
|
123
|
+
return skill.use_count >= self._config.hot_skill_threshold
|
|
124
|
+
|
|
125
|
+
def _lru_cold(self) -> List[LoadedSkill]:
|
|
126
|
+
"""Cold skills sorted oldest last_used_at first."""
|
|
127
|
+
cold = [ls for ls in self._loader.all_loaded() if not self._is_hot(ls)]
|
|
128
|
+
cold.sort(key=lambda ls: ls.last_used_at)
|
|
129
|
+
return cold
|
apsimo/skills/schema.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Colony Skills — SQLite schema for the skill registry."""
|
|
2
|
+
|
|
3
|
+
SKILLS_SCHEMA = """
|
|
4
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
5
|
+
skill_id TEXT PRIMARY KEY,
|
|
6
|
+
name TEXT NOT NULL,
|
|
7
|
+
version TEXT NOT NULL,
|
|
8
|
+
description TEXT NOT NULL DEFAULT '',
|
|
9
|
+
author_colony_id TEXT NOT NULL,
|
|
10
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
11
|
+
tags TEXT NOT NULL DEFAULT '',
|
|
12
|
+
dependencies TEXT NOT NULL DEFAULT '',
|
|
13
|
+
entry_point TEXT NOT NULL DEFAULT 'skill:run',
|
|
14
|
+
checksum_sha256 TEXT NOT NULL DEFAULT '',
|
|
15
|
+
origin_task_id TEXT,
|
|
16
|
+
parent_skill_id TEXT,
|
|
17
|
+
trust_score REAL NOT NULL DEFAULT 0.0,
|
|
18
|
+
execution_count INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
last_executed_at TEXT,
|
|
20
|
+
skill_dir TEXT,
|
|
21
|
+
trigger_patterns TEXT NOT NULL DEFAULT '[]',
|
|
22
|
+
context_tokens_estimate INTEGER NOT NULL DEFAULT 2048,
|
|
23
|
+
lazy_loader TEXT,
|
|
24
|
+
created_at TEXT NOT NULL,
|
|
25
|
+
updated_at TEXT NOT NULL,
|
|
26
|
+
FOREIGN KEY (parent_skill_id) REFERENCES skills(skill_id)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_skills_status ON skills(status);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_skills_tags ON skills(tags);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_skills_trust ON skills(trust_score DESC);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_skills_author ON skills(author_colony_id);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS skill_versions (
|
|
35
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
36
|
+
skill_id TEXT NOT NULL,
|
|
37
|
+
version TEXT NOT NULL,
|
|
38
|
+
checksum_sha256 TEXT NOT NULL,
|
|
39
|
+
skill_dir TEXT NOT NULL,
|
|
40
|
+
promoted_at TEXT NOT NULL,
|
|
41
|
+
promoted_by TEXT NOT NULL,
|
|
42
|
+
FOREIGN KEY (skill_id) REFERENCES skills(skill_id)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS skill_attestations (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
skill_id TEXT NOT NULL,
|
|
48
|
+
attesting_colony TEXT NOT NULL,
|
|
49
|
+
risk_level TEXT NOT NULL,
|
|
50
|
+
days_clean INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
total_executions INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
violations INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
signature TEXT NOT NULL,
|
|
54
|
+
attested_at TEXT NOT NULL,
|
|
55
|
+
FOREIGN KEY (skill_id) REFERENCES skills(skill_id)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS skill_quarantine_log (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
+
skill_id TEXT NOT NULL,
|
|
61
|
+
reason TEXT NOT NULL,
|
|
62
|
+
resolved_by TEXT,
|
|
63
|
+
resolved_at TEXT,
|
|
64
|
+
created_at TEXT NOT NULL,
|
|
65
|
+
FOREIGN KEY (skill_id) REFERENCES skills(skill_id)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS skill_execution_log (
|
|
69
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
skill_id TEXT NOT NULL,
|
|
71
|
+
execution_id TEXT NOT NULL UNIQUE,
|
|
72
|
+
status TEXT NOT NULL,
|
|
73
|
+
duration_ms INTEGER,
|
|
74
|
+
peak_memory_mb REAL,
|
|
75
|
+
violations TEXT,
|
|
76
|
+
executed_at TEXT NOT NULL,
|
|
77
|
+
FOREIGN KEY (skill_id) REFERENCES skills(skill_id)
|
|
78
|
+
);
|
|
79
|
+
"""
|