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/skills/loader.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Colony Skills — on-demand skill module loader."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import importlib
|
|
9
|
+
import importlib.util
|
|
10
|
+
import logging
|
|
11
|
+
import types
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from apsimo.skills.budget import ContextBudget
|
|
18
|
+
from apsimo.skills.index import SkillEntry, SkillIndex
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SecurityError(Exception):
|
|
24
|
+
"""Raised when a skill integrity or security check fails."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SchemaError(Exception):
|
|
28
|
+
"""Raised when a skill entry is missing required schema fields."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Only modules under these prefixes may be loaded via lazy_loader
|
|
32
|
+
_ALLOWED_LAZY_LOADER_PREFIXES = frozenset({"colony.skills.", "colony.plugins."})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _verify_skill_checksum(skill_path: Path, expected_sha256: str) -> bytes:
|
|
36
|
+
"""Verify SHA-256 of skill_path matches expected_sha256 before execution.
|
|
37
|
+
|
|
38
|
+
Returns the file bytes so the caller can compile from memory without a
|
|
39
|
+
second disk read, eliminating the TOCTOU window (SEC-14-C-03).
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
SecurityError: If checksum is missing or does not match.
|
|
43
|
+
"""
|
|
44
|
+
if not expected_sha256:
|
|
45
|
+
raise SecurityError(
|
|
46
|
+
"SkillEntry has no checksum — cannot load unverified skill"
|
|
47
|
+
)
|
|
48
|
+
file_bytes = skill_path.read_bytes()
|
|
49
|
+
actual = hashlib.sha256(file_bytes).hexdigest()
|
|
50
|
+
if not hmac.compare_digest(actual, expected_sha256):
|
|
51
|
+
raise SecurityError(
|
|
52
|
+
f"Skill checksum mismatch for {skill_path}: "
|
|
53
|
+
f"expected {expected_sha256!r}, got {actual!r}"
|
|
54
|
+
)
|
|
55
|
+
return file_bytes
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class LoadedSkill:
|
|
60
|
+
"""A skill that has been imported and is resident in memory."""
|
|
61
|
+
|
|
62
|
+
entry: SkillEntry
|
|
63
|
+
module: types.ModuleType
|
|
64
|
+
loaded_at: datetime
|
|
65
|
+
last_used_at: datetime
|
|
66
|
+
use_count: int = 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SkillLoader:
|
|
70
|
+
"""Imports and initializes skill modules on demand.
|
|
71
|
+
|
|
72
|
+
Maintains a loaded-skill cache keyed by skill_id.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, index: SkillIndex) -> None:
|
|
76
|
+
self._index = index
|
|
77
|
+
self._loaded: Dict[str, LoadedSkill] = {}
|
|
78
|
+
self._lock = asyncio.Lock()
|
|
79
|
+
|
|
80
|
+
async def load(self, skill_id: str) -> Optional[LoadedSkill]:
|
|
81
|
+
"""Import the skill module if not already loaded.
|
|
82
|
+
|
|
83
|
+
Records load timestamp for LRU eviction.
|
|
84
|
+
Returns None if the skill is not in the index.
|
|
85
|
+
"""
|
|
86
|
+
async with self._lock:
|
|
87
|
+
if skill_id in self._loaded:
|
|
88
|
+
ls = self._loaded[skill_id]
|
|
89
|
+
ls.last_used_at = datetime.now(timezone.utc)
|
|
90
|
+
ls.use_count += 1
|
|
91
|
+
return ls
|
|
92
|
+
|
|
93
|
+
entry = self._index.get(skill_id)
|
|
94
|
+
if entry is None:
|
|
95
|
+
logger.warning("SkillLoader.load: skill %r not in index", skill_id)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
module = await self._import_module(entry)
|
|
99
|
+
if module is None:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# Call initialize() hook if present
|
|
103
|
+
init_fn = getattr(module, "initialize", None)
|
|
104
|
+
if init_fn is not None:
|
|
105
|
+
try:
|
|
106
|
+
if asyncio.iscoroutinefunction(init_fn):
|
|
107
|
+
await init_fn()
|
|
108
|
+
else:
|
|
109
|
+
init_fn()
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
logger.warning("Skill %s initialize() failed: %s", skill_id, exc)
|
|
112
|
+
|
|
113
|
+
now = datetime.now(timezone.utc)
|
|
114
|
+
ls = LoadedSkill(
|
|
115
|
+
entry=entry,
|
|
116
|
+
module=module,
|
|
117
|
+
loaded_at=now,
|
|
118
|
+
last_used_at=now,
|
|
119
|
+
use_count=1,
|
|
120
|
+
)
|
|
121
|
+
self._loaded[skill_id] = ls
|
|
122
|
+
logger.debug("SkillLoader: loaded skill %s", skill_id)
|
|
123
|
+
return ls
|
|
124
|
+
|
|
125
|
+
async def unload(self, skill_id: str) -> None:
|
|
126
|
+
"""Remove skill from loaded cache.
|
|
127
|
+
|
|
128
|
+
Does NOT clean up sys.modules (Python limitation), but clears the
|
|
129
|
+
in-context reference so the agent no longer sees it.
|
|
130
|
+
"""
|
|
131
|
+
async with self._lock:
|
|
132
|
+
self._loaded.pop(skill_id, None)
|
|
133
|
+
logger.debug("SkillLoader: unloaded skill %s", skill_id)
|
|
134
|
+
|
|
135
|
+
async def load_for_event(
|
|
136
|
+
self, text: str, budget: ContextBudget
|
|
137
|
+
) -> List[LoadedSkill]:
|
|
138
|
+
"""Match text against index, load matched skills within budget.
|
|
139
|
+
|
|
140
|
+
Returns list of all loaded skills relevant to the event (including
|
|
141
|
+
already-loaded ones). Already-loaded skills do not consume budget again.
|
|
142
|
+
"""
|
|
143
|
+
candidates = self._index.match(text)
|
|
144
|
+
result: List[LoadedSkill] = []
|
|
145
|
+
current_used = self.token_footprint()
|
|
146
|
+
|
|
147
|
+
for entry in candidates:
|
|
148
|
+
if entry.skill_id in self._loaded:
|
|
149
|
+
ls = self._loaded[entry.skill_id]
|
|
150
|
+
ls.last_used_at = datetime.now(timezone.utc)
|
|
151
|
+
ls.use_count += 1
|
|
152
|
+
result.append(ls)
|
|
153
|
+
elif budget.has_capacity(entry.context_tokens_estimate, current_used):
|
|
154
|
+
ls = await self.load(entry.skill_id)
|
|
155
|
+
if ls is not None:
|
|
156
|
+
result.append(ls)
|
|
157
|
+
current_used += entry.context_tokens_estimate
|
|
158
|
+
else:
|
|
159
|
+
logger.debug(
|
|
160
|
+
"SkillLoader: budget full, skipping skill %s (needs %d, available %d)",
|
|
161
|
+
entry.skill_id,
|
|
162
|
+
entry.context_tokens_estimate,
|
|
163
|
+
budget.tokens_available(current_used),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
def loaded_ids(self) -> List[str]:
|
|
169
|
+
"""Return skill_ids currently in the loaded cache."""
|
|
170
|
+
return list(self._loaded.keys())
|
|
171
|
+
|
|
172
|
+
def token_footprint(self) -> int:
|
|
173
|
+
"""Sum of context_tokens_estimate for all loaded skills."""
|
|
174
|
+
return sum(ls.entry.context_tokens_estimate for ls in self._loaded.values())
|
|
175
|
+
|
|
176
|
+
def get_loaded(self, skill_id: str) -> Optional[LoadedSkill]:
|
|
177
|
+
"""Return a loaded skill by ID, or None."""
|
|
178
|
+
return self._loaded.get(skill_id)
|
|
179
|
+
|
|
180
|
+
def all_loaded(self) -> List[LoadedSkill]:
|
|
181
|
+
"""Return all currently loaded skills."""
|
|
182
|
+
return list(self._loaded.values())
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
async def _import_module(entry: SkillEntry) -> Optional[types.ModuleType]:
|
|
186
|
+
"""Import skill module using lazy_loader or default file-based import."""
|
|
187
|
+
if entry.lazy_loader:
|
|
188
|
+
# Dotted import path: "some.module:factory_fn" — allowlist enforced
|
|
189
|
+
try:
|
|
190
|
+
parts = entry.lazy_loader.split(":", 1)
|
|
191
|
+
mod_path = parts[0]
|
|
192
|
+
fn_name = parts[1] if len(parts) > 1 else None
|
|
193
|
+
|
|
194
|
+
if not any(mod_path.startswith(p) for p in _ALLOWED_LAZY_LOADER_PREFIXES):
|
|
195
|
+
raise SecurityError(
|
|
196
|
+
f"Disallowed lazy_loader module: {mod_path!r}"
|
|
197
|
+
)
|
|
198
|
+
if fn_name is not None and not fn_name.isidentifier():
|
|
199
|
+
raise SecurityError(
|
|
200
|
+
f"Invalid function name in lazy_loader: {fn_name!r}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
mod = importlib.import_module(mod_path)
|
|
204
|
+
if fn_name:
|
|
205
|
+
fn = getattr(mod, fn_name)
|
|
206
|
+
result = fn(entry)
|
|
207
|
+
if asyncio.iscoroutine(result):
|
|
208
|
+
return await result
|
|
209
|
+
return result
|
|
210
|
+
return mod
|
|
211
|
+
except SecurityError:
|
|
212
|
+
raise
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
logger.error(
|
|
215
|
+
"Skill %s: lazy_loader %r failed: %s", entry.skill_id, entry.lazy_loader, exc
|
|
216
|
+
)
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
# Default: load skill.py from skill_dir
|
|
220
|
+
skill_path = entry.skill_dir / "skill.py"
|
|
221
|
+
if not skill_path.exists():
|
|
222
|
+
logger.warning("Skill %s: no skill.py at %s", entry.skill_id, skill_path)
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
# Read once, verify checksum, compile from memory — no second disk read
|
|
227
|
+
# eliminates the TOCTOU window (SEC-14-C-03)
|
|
228
|
+
if not hasattr(entry, "checksum_sha256"):
|
|
229
|
+
raise SchemaError(
|
|
230
|
+
f"SkillEntry for {entry.skill_id!r} is missing required "
|
|
231
|
+
f"'checksum_sha256' field — refusing to load unverified skill"
|
|
232
|
+
)
|
|
233
|
+
file_bytes = _verify_skill_checksum(skill_path, entry.checksum_sha256)
|
|
234
|
+
|
|
235
|
+
# Compile and execute from the already-verified in-memory bytes
|
|
236
|
+
code = compile(file_bytes, str(skill_path), "exec")
|
|
237
|
+
module = types.ModuleType(f"_skill_{entry.skill_id}")
|
|
238
|
+
module.__file__ = str(skill_path)
|
|
239
|
+
module.__spec__ = None
|
|
240
|
+
exec(code, module.__dict__) # noqa: S102 # nosec B102 # bytes already verified above
|
|
241
|
+
return module
|
|
242
|
+
except (SecurityError, SchemaError):
|
|
243
|
+
raise
|
|
244
|
+
except Exception as exc:
|
|
245
|
+
logger.error("Skill %s: import from %s failed: %s", entry.skill_id, skill_path, exc)
|
|
246
|
+
return None
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Migration 002: progressive skill loading columns
|
|
2
|
+
-- Safe to run on existing databases; columns added only if absent.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE skills ADD COLUMN trigger_patterns TEXT NOT NULL DEFAULT '[]';
|
|
5
|
+
ALTER TABLE skills ADD COLUMN context_tokens_estimate INTEGER NOT NULL DEFAULT 2048;
|
|
6
|
+
ALTER TABLE skills ADD COLUMN lazy_loader TEXT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Backfill trigger_patterns for existing skills from their name and tags.
|
|
2
|
+
|
|
3
|
+
NOTE: This migration was written for a legacy SQLite-backed SkillRegistry.
|
|
4
|
+
The current SkillRegistry is in-memory and auto-loads built-in skills.
|
|
5
|
+
This script is kept for historical reference but is a no-op.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def backfill(db_path: Path) -> None:
|
|
15
|
+
print("INFO: Legacy migration skipped — current SkillRegistry is in-memory.")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
db = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("~/.colony/skills.db").expanduser()
|
|
20
|
+
backfill(db)
|
apsimo/skills/models.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Colony Skills — data models for the self-creating skills subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional, Set
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SkillStatus(str, Enum):
|
|
14
|
+
DRAFT = "draft"
|
|
15
|
+
ACTIVE = "active"
|
|
16
|
+
INACTIVE = "inactive"
|
|
17
|
+
QUARANTINED = "quarantined"
|
|
18
|
+
DEPRECATED = "deprecated"
|
|
19
|
+
ARCHIVED = "archived"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SkillCapability(str, Enum):
|
|
23
|
+
"""Capabilities a skill may declare. All are deny-by-default."""
|
|
24
|
+
NETWORK = "network"
|
|
25
|
+
FILESYSTEM_READ = "fs_read"
|
|
26
|
+
FILESYSTEM_WRITE = "fs_write"
|
|
27
|
+
SUBPROCESS = "subprocess"
|
|
28
|
+
ENV_READ = "env_read"
|
|
29
|
+
LLM_CALL = "llm_call"
|
|
30
|
+
TASK_POST = "task_post"
|
|
31
|
+
FEDERATION_EMIT = "fed_emit"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class SkillPermissions:
|
|
36
|
+
"""Declared resource access for a skill."""
|
|
37
|
+
capabilities: List[SkillCapability] = field(default_factory=list)
|
|
38
|
+
allowed_domains: List[str] = field(default_factory=list)
|
|
39
|
+
allowed_read_paths: List[str] = field(default_factory=list)
|
|
40
|
+
allowed_write_paths: List[str] = field(default_factory=list)
|
|
41
|
+
allowed_env_vars: List[str] = field(default_factory=list)
|
|
42
|
+
allowed_imports: List[str] = field(default_factory=list)
|
|
43
|
+
max_duration_secs: int = 60
|
|
44
|
+
max_memory_mb: int = 256
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class SkillManifest:
|
|
49
|
+
"""Complete metadata for a Colony skill.
|
|
50
|
+
|
|
51
|
+
Stored as colony.skill.json alongside the skill source.
|
|
52
|
+
"""
|
|
53
|
+
skill_id: str
|
|
54
|
+
name: str
|
|
55
|
+
version: str
|
|
56
|
+
description: str
|
|
57
|
+
author_colony_id: str
|
|
58
|
+
created_at: datetime
|
|
59
|
+
updated_at: datetime
|
|
60
|
+
status: SkillStatus = SkillStatus.DRAFT
|
|
61
|
+
entry_point: str = "skill:run"
|
|
62
|
+
permissions: SkillPermissions = field(default_factory=SkillPermissions)
|
|
63
|
+
tags: List[str] = field(default_factory=list)
|
|
64
|
+
dependencies: List[str] = field(default_factory=list)
|
|
65
|
+
input_schema: Dict[str, Any] = field(default_factory=dict)
|
|
66
|
+
output_schema: Dict[str, Any] = field(default_factory=dict)
|
|
67
|
+
checksum_sha256: str = ""
|
|
68
|
+
# Empty string means "no checksum recorded" (e.g. MCP-bridged or legacy skills).
|
|
69
|
+
# Any execution path that verifies integrity MUST reject an empty value — it is
|
|
70
|
+
# not a valid hash. See SkillExecutor._run_skill() which raises SecurityError
|
|
71
|
+
# when this field is falsy.
|
|
72
|
+
origin_task_id: Optional[str] = None
|
|
73
|
+
parent_skill_id: Optional[str] = None
|
|
74
|
+
trust_score: float = 0.0
|
|
75
|
+
execution_count: int = 0
|
|
76
|
+
last_executed_at: Optional[datetime] = None
|
|
77
|
+
signature: str = ""
|
|
78
|
+
skill_dir: Optional[str] = None
|
|
79
|
+
# MCP bridge fields — None for native skills
|
|
80
|
+
origin: Optional[str] = None # "mcp" for MCP-bridged tools
|
|
81
|
+
mcp_server: Optional[str] = None # server name (e.g. "github")
|
|
82
|
+
mcp_tool: Optional[str] = None # original tool name on the server
|
|
83
|
+
|
|
84
|
+
# Progressive loading additions
|
|
85
|
+
trigger_patterns: List[str] = field(default_factory=list)
|
|
86
|
+
context_tokens_estimate: int = 2048
|
|
87
|
+
lazy_loader: Optional[str] = None
|
|
88
|
+
|
|
89
|
+
def compute_checksum(self, source_code: str) -> str:
|
|
90
|
+
"""Compute and store SHA-256 checksum of skill source."""
|
|
91
|
+
digest = hashlib.sha256(source_code.encode()).hexdigest()
|
|
92
|
+
self.checksum_sha256 = digest
|
|
93
|
+
return digest
|
|
94
|
+
|
|
95
|
+
def to_json(self) -> str:
|
|
96
|
+
"""Serialize to JSON for storage as colony.skill.json."""
|
|
97
|
+
d = {
|
|
98
|
+
"skill_id": self.skill_id,
|
|
99
|
+
"name": self.name,
|
|
100
|
+
"version": self.version,
|
|
101
|
+
"description": self.description,
|
|
102
|
+
"author_colony_id": self.author_colony_id,
|
|
103
|
+
"created_at": self.created_at.isoformat(),
|
|
104
|
+
"updated_at": self.updated_at.isoformat(),
|
|
105
|
+
"status": self.status.value,
|
|
106
|
+
"entry_point": self.entry_point,
|
|
107
|
+
"permissions": {
|
|
108
|
+
"capabilities": [c.value for c in self.permissions.capabilities],
|
|
109
|
+
"allowed_domains": self.permissions.allowed_domains,
|
|
110
|
+
"allowed_read_paths": self.permissions.allowed_read_paths,
|
|
111
|
+
"allowed_write_paths": self.permissions.allowed_write_paths,
|
|
112
|
+
"allowed_env_vars": self.permissions.allowed_env_vars,
|
|
113
|
+
"max_duration_secs": self.permissions.max_duration_secs,
|
|
114
|
+
"max_memory_mb": self.permissions.max_memory_mb,
|
|
115
|
+
},
|
|
116
|
+
"tags": self.tags,
|
|
117
|
+
"dependencies": self.dependencies,
|
|
118
|
+
"input_schema": self.input_schema,
|
|
119
|
+
"output_schema": self.output_schema,
|
|
120
|
+
"checksum_sha256": self.checksum_sha256,
|
|
121
|
+
"origin_task_id": self.origin_task_id,
|
|
122
|
+
"parent_skill_id": self.parent_skill_id,
|
|
123
|
+
"trust_score": self.trust_score,
|
|
124
|
+
"execution_count": self.execution_count,
|
|
125
|
+
"signature": self.signature,
|
|
126
|
+
"trigger_patterns": self.trigger_patterns,
|
|
127
|
+
"context_tokens_estimate": self.context_tokens_estimate,
|
|
128
|
+
"lazy_loader": self.lazy_loader,
|
|
129
|
+
"origin": self.origin,
|
|
130
|
+
"mcp_server": self.mcp_server,
|
|
131
|
+
"mcp_tool": self.mcp_tool,
|
|
132
|
+
}
|
|
133
|
+
return json.dumps(d, indent=2, sort_keys=True)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_json(cls, raw: str) -> "SkillManifest":
|
|
137
|
+
"""Deserialize from JSON."""
|
|
138
|
+
d = json.loads(raw)
|
|
139
|
+
perms_raw = d.get("permissions", {})
|
|
140
|
+
perms = SkillPermissions(
|
|
141
|
+
capabilities=[SkillCapability(c) for c in perms_raw.get("capabilities", [])],
|
|
142
|
+
allowed_domains=perms_raw.get("allowed_domains", []),
|
|
143
|
+
allowed_read_paths=perms_raw.get("allowed_read_paths", []),
|
|
144
|
+
allowed_write_paths=perms_raw.get("allowed_write_paths", []),
|
|
145
|
+
allowed_env_vars=perms_raw.get("allowed_env_vars", []),
|
|
146
|
+
max_duration_secs=perms_raw.get("max_duration_secs", 60),
|
|
147
|
+
max_memory_mb=perms_raw.get("max_memory_mb", 256),
|
|
148
|
+
)
|
|
149
|
+
return cls(
|
|
150
|
+
skill_id=d["skill_id"],
|
|
151
|
+
name=d["name"],
|
|
152
|
+
version=d["version"],
|
|
153
|
+
description=d["description"],
|
|
154
|
+
author_colony_id=d["author_colony_id"],
|
|
155
|
+
created_at=datetime.fromisoformat(d["created_at"]),
|
|
156
|
+
updated_at=datetime.fromisoformat(d["updated_at"]),
|
|
157
|
+
status=SkillStatus(d.get("status", "draft")),
|
|
158
|
+
entry_point=d.get("entry_point", "skill:run"),
|
|
159
|
+
permissions=perms,
|
|
160
|
+
tags=d.get("tags", []),
|
|
161
|
+
dependencies=d.get("dependencies", []),
|
|
162
|
+
input_schema=d.get("input_schema", {}),
|
|
163
|
+
output_schema=d.get("output_schema", {}),
|
|
164
|
+
checksum_sha256=d.get("checksum_sha256", ""),
|
|
165
|
+
origin_task_id=d.get("origin_task_id"),
|
|
166
|
+
parent_skill_id=d.get("parent_skill_id"),
|
|
167
|
+
trust_score=d.get("trust_score", 0.0),
|
|
168
|
+
execution_count=d.get("execution_count", 0),
|
|
169
|
+
signature=d.get("signature", ""),
|
|
170
|
+
trigger_patterns=d.get("trigger_patterns", []),
|
|
171
|
+
context_tokens_estimate=d.get("context_tokens_estimate", 2048),
|
|
172
|
+
lazy_loader=d.get("lazy_loader"),
|
|
173
|
+
origin=d.get("origin"),
|
|
174
|
+
mcp_server=d.get("mcp_server"),
|
|
175
|
+
mcp_tool=d.get("mcp_tool"),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class TaskSolution:
|
|
181
|
+
"""A completed task solution that may be captured as a skill."""
|
|
182
|
+
task_id: str
|
|
183
|
+
task_description: str
|
|
184
|
+
inputs: Dict[str, Any]
|
|
185
|
+
output: Any
|
|
186
|
+
trace: List[Dict[str, Any]]
|
|
187
|
+
dependencies: List[str]
|
|
188
|
+
embedding: Optional[List[float]]
|
|
189
|
+
step_fingerprint: Optional[List[str]]
|
|
190
|
+
duration_secs: float
|
|
191
|
+
completed_at: datetime
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclass
|
|
195
|
+
class SkillSummary:
|
|
196
|
+
"""Lightweight skill summary for novelty scoring and search."""
|
|
197
|
+
skill_id: str
|
|
198
|
+
description: str
|
|
199
|
+
tags: List[str]
|
|
200
|
+
dependencies: List[str]
|
|
201
|
+
embedding: Optional[List[float]] = None
|
|
202
|
+
step_fingerprint: Optional[List[str]] = None
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Colony Skills — packaging extracted patterns as skill directories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from apsimo.skills.learning.pattern_extractor import ExtractedPattern
|
|
13
|
+
from apsimo.skills.learning.triggers import TriggerSource
|
|
14
|
+
from apsimo.skills.models import SkillCapability, SkillManifest, SkillPermissions, SkillStatus
|
|
15
|
+
from apsimo.skills.registry import SkillRegistry
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
_SLUG_RE = re.compile(r"[^a-z0-9-]")
|
|
20
|
+
_SKILL_LIBRARY_PATH = pathlib.Path("colony/skills/library")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SkillPackager:
|
|
24
|
+
"""Packages an ExtractedPattern into a skill directory and registers it."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
registry: SkillRegistry,
|
|
29
|
+
colony_id: str,
|
|
30
|
+
library_root: pathlib.Path = _SKILL_LIBRARY_PATH,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._registry = registry
|
|
33
|
+
self._colony_id = colony_id
|
|
34
|
+
self._library = library_root
|
|
35
|
+
|
|
36
|
+
async def package(
|
|
37
|
+
self,
|
|
38
|
+
solution: "TaskSolution", # noqa: F821
|
|
39
|
+
pattern: ExtractedPattern,
|
|
40
|
+
source: TriggerSource,
|
|
41
|
+
) -> str:
|
|
42
|
+
"""Create a skill directory from an extracted pattern.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The new skill_id string.
|
|
46
|
+
"""
|
|
47
|
+
now = datetime.now(timezone.utc)
|
|
48
|
+
slug = self._slugify(solution.task_description[:48])
|
|
49
|
+
uid = uuid.uuid4().hex[:8]
|
|
50
|
+
skill_id = f"{slug}_{uid}"
|
|
51
|
+
version = "1.0.0"
|
|
52
|
+
skill_dir = self._library / f"{skill_id}_v{version}"
|
|
53
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
permissions = self._build_permissions(pattern)
|
|
56
|
+
manifest = SkillManifest(
|
|
57
|
+
skill_id=skill_id,
|
|
58
|
+
name=slug.replace("-", " ").title(),
|
|
59
|
+
version=version,
|
|
60
|
+
description=pattern.docstring.split("\n")[0],
|
|
61
|
+
author_colony_id=self._colony_id,
|
|
62
|
+
created_at=now,
|
|
63
|
+
updated_at=now,
|
|
64
|
+
status=SkillStatus.DRAFT,
|
|
65
|
+
entry_point="skill:run",
|
|
66
|
+
permissions=permissions,
|
|
67
|
+
tags=pattern.tags,
|
|
68
|
+
dependencies=pattern.dependencies,
|
|
69
|
+
input_schema=pattern.input_schema,
|
|
70
|
+
output_schema=pattern.output_schema,
|
|
71
|
+
origin_task_id=solution.task_id,
|
|
72
|
+
skill_dir=str(skill_dir),
|
|
73
|
+
)
|
|
74
|
+
manifest.compute_checksum(pattern.source_code)
|
|
75
|
+
|
|
76
|
+
(skill_dir / "skill.py").write_text(pattern.source_code, encoding="utf-8")
|
|
77
|
+
(skill_dir / "colony.skill.json").write_text(manifest.to_json(), encoding="utf-8")
|
|
78
|
+
(skill_dir / "requirements.txt").write_text(
|
|
79
|
+
"\n".join(pattern.dependencies), encoding="utf-8"
|
|
80
|
+
)
|
|
81
|
+
self._write_smoke_test(skill_dir, slug, pattern)
|
|
82
|
+
|
|
83
|
+
await self._registry.register(manifest, skill_dir)
|
|
84
|
+
logger.info("Packaged skill %s → %s", skill_id, skill_dir)
|
|
85
|
+
return skill_id
|
|
86
|
+
|
|
87
|
+
def _build_permissions(self, pattern: ExtractedPattern) -> SkillPermissions:
|
|
88
|
+
caps: list[SkillCapability] = []
|
|
89
|
+
if pattern.network_domains:
|
|
90
|
+
caps.append(SkillCapability.NETWORK)
|
|
91
|
+
if pattern.file_paths:
|
|
92
|
+
caps.append(SkillCapability.FILESYSTEM_READ)
|
|
93
|
+
if pattern.env_vars:
|
|
94
|
+
caps.append(SkillCapability.ENV_READ)
|
|
95
|
+
return SkillPermissions(
|
|
96
|
+
capabilities=caps,
|
|
97
|
+
allowed_domains=pattern.network_domains,
|
|
98
|
+
allowed_read_paths=pattern.file_paths,
|
|
99
|
+
allowed_env_vars=list(pattern.env_vars),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _slugify(text: str) -> str:
|
|
104
|
+
return _SLUG_RE.sub("-", text.lower().strip()).strip("-")[:48]
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _write_smoke_test(
|
|
108
|
+
skill_dir: pathlib.Path, slug: str, pattern: ExtractedPattern
|
|
109
|
+
) -> None:
|
|
110
|
+
fn_name = slug.replace("-", "_")
|
|
111
|
+
test_code = f"""\
|
|
112
|
+
import pytest
|
|
113
|
+
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_{fn_name}_runs():
|
|
116
|
+
# Smoke test: verify skill file exists and run() is callable.
|
|
117
|
+
import importlib.util
|
|
118
|
+
import pathlib
|
|
119
|
+
skill_path = pathlib.Path("{skill_dir}") / "skill.py"
|
|
120
|
+
if skill_path.exists():
|
|
121
|
+
spec = importlib.util.spec_from_file_location("_skill", skill_path)
|
|
122
|
+
module = importlib.util.module_from_spec(spec)
|
|
123
|
+
spec.loader.exec_module(module)
|
|
124
|
+
assert callable(getattr(module, "run", None))
|
|
125
|
+
"""
|
|
126
|
+
tests_dir = skill_dir / "tests"
|
|
127
|
+
tests_dir.mkdir(exist_ok=True)
|
|
128
|
+
(tests_dir / "test_skill.py").write_text(test_code, encoding="utf-8")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Colony Skills — typed Protocol interfaces for dependency injection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from apsimo.skills.models import SkillManifest, SkillStatus
|
|
8
|
+
from apsimo.skills.learning.novelty_detector import NoveltyResult
|
|
9
|
+
from apsimo.skills.learning.pattern_extractor import ExtractedPattern
|
|
10
|
+
from apsimo.skills.security.scanner import ASTScanResult
|
|
11
|
+
from apsimo.skills.executor import ExecutionResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_checkable
|
|
15
|
+
class ISkillRegistry(Protocol):
|
|
16
|
+
"""Read/write interface to the skill registry."""
|
|
17
|
+
|
|
18
|
+
async def register(self, manifest: SkillManifest, skill_dir: Any) -> None: ...
|
|
19
|
+
async def get(self, skill_id: str) -> Optional[SkillManifest]: ...
|
|
20
|
+
async def search(
|
|
21
|
+
self,
|
|
22
|
+
query: str = "",
|
|
23
|
+
tags: Optional[List[str]] = None,
|
|
24
|
+
status: Optional[SkillStatus] = None,
|
|
25
|
+
limit: int = 50,
|
|
26
|
+
) -> List[SkillManifest]: ...
|
|
27
|
+
async def activate(self, skill_id: str) -> None: ...
|
|
28
|
+
async def deactivate(self, skill_id: str) -> None: ...
|
|
29
|
+
async def quarantine(self, skill_id: str, reason: str) -> None: ...
|
|
30
|
+
async def list_summaries(self) -> List[Any]: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@runtime_checkable
|
|
34
|
+
class INoveltyDetector(Protocol):
|
|
35
|
+
"""Scores how novel a task solution is relative to the skill registry."""
|
|
36
|
+
|
|
37
|
+
async def score(self, solution: Any) -> NoveltyResult: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@runtime_checkable
|
|
41
|
+
class IPatternExtractor(Protocol):
|
|
42
|
+
"""Extracts a generalizable pattern from a task solution."""
|
|
43
|
+
|
|
44
|
+
def extract(self, solution: Any) -> ExtractedPattern: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@runtime_checkable
|
|
48
|
+
class ISkillPackager(Protocol):
|
|
49
|
+
"""Packages an extracted pattern as a skill directory."""
|
|
50
|
+
|
|
51
|
+
async def package(self, solution: Any, pattern: ExtractedPattern, source: Any) -> str: ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class IASTScanner(Protocol):
|
|
56
|
+
"""Statically analyzes skill Python source for security issues."""
|
|
57
|
+
|
|
58
|
+
def scan(self, source: str, skill_id: str) -> ASTScanResult: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@runtime_checkable
|
|
62
|
+
class ISkillExecutor(Protocol):
|
|
63
|
+
"""Executes a skill by ID inside a capability-gated sandbox."""
|
|
64
|
+
|
|
65
|
+
async def invoke(
|
|
66
|
+
self,
|
|
67
|
+
skill_id: str,
|
|
68
|
+
inputs: Dict[str, Any],
|
|
69
|
+
caller_context: Optional[str] = None,
|
|
70
|
+
) -> ExecutionResult: ...
|