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,493 @@
|
|
|
1
|
+
"""Colony Skills — skill executor with capability-gated sandbox."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import importlib
|
|
9
|
+
import inspect
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import pathlib
|
|
14
|
+
import sys
|
|
15
|
+
import types
|
|
16
|
+
import uuid
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from apsimo.skills.base import (
|
|
22
|
+
InitiativeExecutionContext,
|
|
23
|
+
InitiativeExecutorSkill,
|
|
24
|
+
)
|
|
25
|
+
from apsimo.skills.models import SkillManifest, SkillStatus
|
|
26
|
+
from apsimo.skills.registry import SkillRegistry
|
|
27
|
+
from apsimo.skills.security.scanner import ASTScanner
|
|
28
|
+
from apsimo.skills.security.guards import CapabilityGuard
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_DEFAULT_SANDBOX_MODE = "subprocess" if sys.platform.startswith("linux") else "inprocess"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_sandbox_mode() -> str:
|
|
37
|
+
"""Return the configured sandbox mode, falling back to the platform default."""
|
|
38
|
+
override = (os.environ.get("COLONY_SKILL_SANDBOX") or "").strip().lower()
|
|
39
|
+
if override in ("subprocess", "inprocess"):
|
|
40
|
+
if override == "inprocess" and _DEFAULT_SANDBOX_MODE == "subprocess":
|
|
41
|
+
logger.warning(
|
|
42
|
+
"COLONY_SKILL_SANDBOX=inprocess overrides the default subprocess "
|
|
43
|
+
"isolation — skills will run in the sidecar process."
|
|
44
|
+
)
|
|
45
|
+
return override
|
|
46
|
+
return _DEFAULT_SANDBOX_MODE
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SecurityError(RuntimeError):
|
|
50
|
+
"""Raised when a skill fails integrity or security checks."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ExecutionResult:
|
|
55
|
+
"""Result of a skill invocation."""
|
|
56
|
+
execution_id: str
|
|
57
|
+
skill_id: str
|
|
58
|
+
status: str # "success" | "failed" | "violated" | "timeout"
|
|
59
|
+
output: Any
|
|
60
|
+
error: Optional[str]
|
|
61
|
+
duration_ms: int
|
|
62
|
+
peak_memory_mb: Optional[float]
|
|
63
|
+
violations: List[str] = field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SkillExecutor:
|
|
67
|
+
"""Executes Colony skills inside a capability-gated sandbox.
|
|
68
|
+
|
|
69
|
+
Execution pipeline:
|
|
70
|
+
1. Load manifest and verify skill status.
|
|
71
|
+
2. Run capability guard checks.
|
|
72
|
+
3. Verify on-disk source checksum.
|
|
73
|
+
4. Execute either in a hardened subprocess (default on Linux) or
|
|
74
|
+
in-process (rollback path, controlled by COLONY_SKILL_SANDBOX).
|
|
75
|
+
5. Log result to execution log; quarantine on timeout.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
registry: SkillRegistry,
|
|
81
|
+
guard: CapabilityGuard,
|
|
82
|
+
scanner: ASTScanner,
|
|
83
|
+
execution_timeout_secs: float = 60.0,
|
|
84
|
+
sandbox_mode: Optional[str] = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
self._registry = registry
|
|
87
|
+
self._guard = guard
|
|
88
|
+
self._scanner = scanner
|
|
89
|
+
self._timeout = execution_timeout_secs
|
|
90
|
+
self._sandbox_mode = (sandbox_mode or _resolve_sandbox_mode()).lower()
|
|
91
|
+
|
|
92
|
+
async def invoke(
|
|
93
|
+
self,
|
|
94
|
+
skill_id: str,
|
|
95
|
+
inputs: Dict[str, Any],
|
|
96
|
+
caller_context: Optional[str] = None,
|
|
97
|
+
) -> ExecutionResult:
|
|
98
|
+
"""Invoke a skill by ID with the given inputs."""
|
|
99
|
+
execution_id = f"exec-{uuid.uuid4().hex[:12]}"
|
|
100
|
+
start = datetime.now(timezone.utc)
|
|
101
|
+
|
|
102
|
+
# The wired registry may be the async manifest store OR the built-in
|
|
103
|
+
# runtime SkillRegistry, whose get() is synchronous and returns the
|
|
104
|
+
# executor skill object itself. Awaiting that object was the bug that
|
|
105
|
+
# made every built-in skill 500 ("object ... can't be used in 'await'
|
|
106
|
+
# expression") while the registry still advertised them as active.
|
|
107
|
+
manifest = self._registry.get(skill_id)
|
|
108
|
+
if inspect.isawaitable(manifest):
|
|
109
|
+
manifest = await manifest
|
|
110
|
+
if not manifest:
|
|
111
|
+
return ExecutionResult(
|
|
112
|
+
execution_id=execution_id,
|
|
113
|
+
skill_id=skill_id,
|
|
114
|
+
status="failed",
|
|
115
|
+
output=None,
|
|
116
|
+
error=f"Skill '{skill_id}' not found in registry.",
|
|
117
|
+
duration_ms=0,
|
|
118
|
+
peak_memory_mb=None,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if isinstance(manifest, InitiativeExecutorSkill):
|
|
122
|
+
# Built-in runtime executor: trusted in-process code with no
|
|
123
|
+
# on-disk manifest/source — run its own execute() directly.
|
|
124
|
+
return await self._invoke_builtin(
|
|
125
|
+
skill_id, manifest, inputs, execution_id, start,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if manifest.status != SkillStatus.ACTIVE:
|
|
129
|
+
return ExecutionResult(
|
|
130
|
+
execution_id=execution_id,
|
|
131
|
+
skill_id=skill_id,
|
|
132
|
+
status="failed",
|
|
133
|
+
output=None,
|
|
134
|
+
error=f"Skill '{skill_id}' is not active (status: {manifest.status.value}).",
|
|
135
|
+
duration_ms=0,
|
|
136
|
+
peak_memory_mb=None,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
guard_result = await self._guard.check(manifest, inputs)
|
|
140
|
+
if not guard_result.allowed:
|
|
141
|
+
return ExecutionResult(
|
|
142
|
+
execution_id=execution_id,
|
|
143
|
+
skill_id=skill_id,
|
|
144
|
+
status="failed",
|
|
145
|
+
output=None,
|
|
146
|
+
error=f"Capability guard denied execution: {guard_result.reason}",
|
|
147
|
+
duration_ms=0,
|
|
148
|
+
peak_memory_mb=None,
|
|
149
|
+
violations=guard_result.violations,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
skill_dir_str = manifest.skill_dir or ""
|
|
153
|
+
skill_dir = pathlib.Path(skill_dir_str)
|
|
154
|
+
skill_path = skill_dir / "skill.py"
|
|
155
|
+
if not skill_path.exists():
|
|
156
|
+
return ExecutionResult(
|
|
157
|
+
execution_id=execution_id,
|
|
158
|
+
skill_id=skill_id,
|
|
159
|
+
status="failed",
|
|
160
|
+
output=None,
|
|
161
|
+
error=f"Skill '{skill_id}' source file not found.",
|
|
162
|
+
duration_ms=0,
|
|
163
|
+
peak_memory_mb=None,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
verified_source = self._read_verified_source(skill_path, manifest)
|
|
168
|
+
if self._sandbox_mode == "subprocess":
|
|
169
|
+
raw = await asyncio.wait_for(
|
|
170
|
+
self._run_subprocess(verified_source, inputs, manifest),
|
|
171
|
+
timeout=manifest.permissions.max_duration_secs,
|
|
172
|
+
)
|
|
173
|
+
end = datetime.now(timezone.utc)
|
|
174
|
+
duration_ms = int((end - start).total_seconds() * 1000)
|
|
175
|
+
return await self._finalize_subprocess_result(
|
|
176
|
+
skill_id=skill_id,
|
|
177
|
+
execution_id=execution_id,
|
|
178
|
+
raw=raw,
|
|
179
|
+
duration_ms=duration_ms,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# inprocess rollback path — kept verbatim so operators can flip
|
|
183
|
+
# back to the pre-sandbox behaviour without redeploying.
|
|
184
|
+
output = await asyncio.wait_for(
|
|
185
|
+
self._run_skill_inprocess(verified_source, inputs),
|
|
186
|
+
timeout=manifest.permissions.max_duration_secs,
|
|
187
|
+
)
|
|
188
|
+
end = datetime.now(timezone.utc)
|
|
189
|
+
duration_ms = int((end - start).total_seconds() * 1000)
|
|
190
|
+
await self._registry.record_execution(skill_id, execution_id, "success", duration_ms)
|
|
191
|
+
return ExecutionResult(
|
|
192
|
+
execution_id=execution_id,
|
|
193
|
+
skill_id=skill_id,
|
|
194
|
+
status="success",
|
|
195
|
+
output=output,
|
|
196
|
+
error=None,
|
|
197
|
+
duration_ms=duration_ms,
|
|
198
|
+
peak_memory_mb=None,
|
|
199
|
+
)
|
|
200
|
+
except asyncio.TimeoutError:
|
|
201
|
+
await self._registry.quarantine(skill_id, f"Execution timeout in {execution_id}")
|
|
202
|
+
end = datetime.now(timezone.utc)
|
|
203
|
+
duration_ms = int((end - start).total_seconds() * 1000)
|
|
204
|
+
await self._registry.record_execution(
|
|
205
|
+
skill_id, execution_id, "timeout", duration_ms, ["timeout"]
|
|
206
|
+
)
|
|
207
|
+
return ExecutionResult(
|
|
208
|
+
execution_id=execution_id,
|
|
209
|
+
skill_id=skill_id,
|
|
210
|
+
status="timeout",
|
|
211
|
+
output=None,
|
|
212
|
+
error="Execution exceeded maximum duration.",
|
|
213
|
+
duration_ms=duration_ms,
|
|
214
|
+
peak_memory_mb=None,
|
|
215
|
+
violations=["timeout"],
|
|
216
|
+
)
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
end = datetime.now(timezone.utc)
|
|
219
|
+
duration_ms = int((end - start).total_seconds() * 1000)
|
|
220
|
+
await self._registry.record_execution(
|
|
221
|
+
skill_id, execution_id, "failed", duration_ms
|
|
222
|
+
)
|
|
223
|
+
logger.debug("Skill '%s' execution error (%s)", skill_id, exc, exc_info=True)
|
|
224
|
+
return ExecutionResult(
|
|
225
|
+
execution_id=execution_id,
|
|
226
|
+
skill_id=skill_id,
|
|
227
|
+
status="failed",
|
|
228
|
+
output=None,
|
|
229
|
+
error=f"Skill '{skill_id}' execution failed: {type(exc).__name__}",
|
|
230
|
+
duration_ms=duration_ms,
|
|
231
|
+
peak_memory_mb=None,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# ------------------------------------------------------------------
|
|
235
|
+
# Built-in runtime executor skills
|
|
236
|
+
# ------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
async def _invoke_builtin(
|
|
239
|
+
self,
|
|
240
|
+
skill_id: str,
|
|
241
|
+
skill: InitiativeExecutorSkill,
|
|
242
|
+
inputs: Dict[str, Any],
|
|
243
|
+
execution_id: str,
|
|
244
|
+
start: datetime,
|
|
245
|
+
) -> ExecutionResult:
|
|
246
|
+
"""Execute a built-in InitiativeExecutorSkill registered in the
|
|
247
|
+
runtime SkillRegistry.
|
|
248
|
+
|
|
249
|
+
These are trusted in-process objects (no manifest, checksum, or
|
|
250
|
+
sandbox source file); the InitiativeExecutionContext is built from
|
|
251
|
+
the caller's inputs. The skill's own outcome enum maps onto the
|
|
252
|
+
ExecutionResult status so a FAILED initiative is never reported as
|
|
253
|
+
a successful execution.
|
|
254
|
+
"""
|
|
255
|
+
def _elapsed_ms() -> int:
|
|
256
|
+
return int(
|
|
257
|
+
(datetime.now(timezone.utc) - start).total_seconds() * 1000
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
context = InitiativeExecutionContext(
|
|
262
|
+
initiative_id=str(inputs.get("initiative_id") or execution_id),
|
|
263
|
+
category_id=str(inputs.get("category_id") or "api"),
|
|
264
|
+
category_name=str(
|
|
265
|
+
inputs.get("category_name")
|
|
266
|
+
or getattr(skill, "skill_name", skill_id)
|
|
267
|
+
),
|
|
268
|
+
entity_id=inputs.get("entity_id"),
|
|
269
|
+
entity_type=inputs.get("entity_type"),
|
|
270
|
+
trigger_data=dict(inputs.get("trigger_data") or {}),
|
|
271
|
+
)
|
|
272
|
+
outcome = await asyncio.wait_for(
|
|
273
|
+
skill.execute(context), timeout=self._timeout,
|
|
274
|
+
)
|
|
275
|
+
except asyncio.TimeoutError:
|
|
276
|
+
return ExecutionResult(
|
|
277
|
+
execution_id=execution_id,
|
|
278
|
+
skill_id=skill_id,
|
|
279
|
+
status="timeout",
|
|
280
|
+
output=None,
|
|
281
|
+
error="Execution exceeded maximum duration.",
|
|
282
|
+
duration_ms=_elapsed_ms(),
|
|
283
|
+
peak_memory_mb=None,
|
|
284
|
+
violations=["timeout"],
|
|
285
|
+
)
|
|
286
|
+
except Exception as exc:
|
|
287
|
+
logger.debug(
|
|
288
|
+
"Built-in skill '%s' execution error (%s)",
|
|
289
|
+
skill_id, exc, exc_info=True,
|
|
290
|
+
)
|
|
291
|
+
return ExecutionResult(
|
|
292
|
+
execution_id=execution_id,
|
|
293
|
+
skill_id=skill_id,
|
|
294
|
+
status="failed",
|
|
295
|
+
output=None,
|
|
296
|
+
error=f"Skill '{skill_id}' execution failed: {type(exc).__name__}",
|
|
297
|
+
duration_ms=_elapsed_ms(),
|
|
298
|
+
peak_memory_mb=None,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
outcome_value = getattr(outcome, "value", outcome)
|
|
302
|
+
failed = str(outcome_value) == "failed"
|
|
303
|
+
return ExecutionResult(
|
|
304
|
+
execution_id=execution_id,
|
|
305
|
+
skill_id=skill_id,
|
|
306
|
+
status="failed" if failed else "success",
|
|
307
|
+
output={"result": outcome_value},
|
|
308
|
+
error="skill reported failure" if failed else None,
|
|
309
|
+
duration_ms=_elapsed_ms(),
|
|
310
|
+
peak_memory_mb=None,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# ------------------------------------------------------------------
|
|
314
|
+
# Integrity
|
|
315
|
+
# ------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _read_verified_source(
|
|
319
|
+
skill_path: pathlib.Path,
|
|
320
|
+
manifest: SkillManifest,
|
|
321
|
+
) -> bytes:
|
|
322
|
+
"""Read + checksum-verify the skill source in one shot.
|
|
323
|
+
|
|
324
|
+
Returns the in-memory bytes on success; raises SecurityError on
|
|
325
|
+
missing or mismatched checksum. Reading once and returning bytes
|
|
326
|
+
eliminates the TOCTOU window between verification and execution —
|
|
327
|
+
both the in-process and subprocess paths operate on the returned
|
|
328
|
+
buffer, never touching the filesystem again.
|
|
329
|
+
"""
|
|
330
|
+
if not manifest.checksum_sha256:
|
|
331
|
+
raise SecurityError(
|
|
332
|
+
"Skill has no checksum — integrity cannot be verified."
|
|
333
|
+
)
|
|
334
|
+
file_bytes = skill_path.read_bytes()
|
|
335
|
+
actual_digest = hashlib.sha256(file_bytes).hexdigest()
|
|
336
|
+
if not hmac.compare_digest(actual_digest, manifest.checksum_sha256):
|
|
337
|
+
raise SecurityError(
|
|
338
|
+
"Skill checksum mismatch — file may have been tampered with."
|
|
339
|
+
)
|
|
340
|
+
return file_bytes
|
|
341
|
+
|
|
342
|
+
# ------------------------------------------------------------------
|
|
343
|
+
# Subprocess path
|
|
344
|
+
# ------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
async def _run_subprocess(
|
|
347
|
+
self,
|
|
348
|
+
source_bytes: bytes,
|
|
349
|
+
inputs: Dict[str, Any],
|
|
350
|
+
manifest: SkillManifest,
|
|
351
|
+
) -> Dict[str, Any]:
|
|
352
|
+
"""Run the skill in a hardened subprocess via sandbox_runner.
|
|
353
|
+
|
|
354
|
+
Returns the parsed JSON payload from the runner's stdout, or a
|
|
355
|
+
synthesized failure dict on malformed output / nonzero exit.
|
|
356
|
+
"""
|
|
357
|
+
payload = json.dumps({
|
|
358
|
+
"source": source_bytes.decode("utf-8", errors="replace"),
|
|
359
|
+
"inputs": inputs,
|
|
360
|
+
"allowed_imports": manifest.permissions.allowed_imports if hasattr(manifest.permissions, 'allowed_imports') else [],
|
|
361
|
+
"limits": {
|
|
362
|
+
"mem_mb": manifest.permissions.max_memory_mb,
|
|
363
|
+
"cpu_secs": manifest.permissions.max_duration_secs,
|
|
364
|
+
"fsize_mb": 8,
|
|
365
|
+
},
|
|
366
|
+
}).encode("utf-8")
|
|
367
|
+
|
|
368
|
+
proc = await asyncio.create_subprocess_exec(
|
|
369
|
+
sys.executable,
|
|
370
|
+
"-m",
|
|
371
|
+
"apsimo.skills.sandbox_runner",
|
|
372
|
+
stdin=asyncio.subprocess.PIPE,
|
|
373
|
+
stdout=asyncio.subprocess.PIPE,
|
|
374
|
+
stderr=asyncio.subprocess.PIPE,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
stdout_bytes, stderr_bytes = await proc.communicate(input=payload)
|
|
379
|
+
except asyncio.CancelledError:
|
|
380
|
+
# Parent timeout — kill the child and re-raise so asyncio.wait_for
|
|
381
|
+
# sees TimeoutError in the caller.
|
|
382
|
+
try:
|
|
383
|
+
proc.kill()
|
|
384
|
+
except ProcessLookupError:
|
|
385
|
+
pass
|
|
386
|
+
await proc.wait()
|
|
387
|
+
raise
|
|
388
|
+
|
|
389
|
+
stdout = stdout_bytes.decode("utf-8", errors="replace").strip()
|
|
390
|
+
stderr = stderr_bytes.decode("utf-8", errors="replace").strip()
|
|
391
|
+
returncode = proc.returncode or 0
|
|
392
|
+
|
|
393
|
+
if not stdout:
|
|
394
|
+
# The runner always emits exactly one JSON line on success or
|
|
395
|
+
# handled failure. An empty stdout means the kernel killed
|
|
396
|
+
# the process before it could report (RLIMIT_AS, RLIMIT_CPU,
|
|
397
|
+
# segfault, OOM killer). Surface stderr excerpt for operators.
|
|
398
|
+
return {
|
|
399
|
+
"status": "failed",
|
|
400
|
+
"error": f"runner_killed (rc={returncode}): {stderr[-200:]}",
|
|
401
|
+
"peak_memory_kb": 0,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
parsed = json.loads(stdout.splitlines()[-1])
|
|
406
|
+
except json.JSONDecodeError:
|
|
407
|
+
return {
|
|
408
|
+
"status": "failed",
|
|
409
|
+
"error": f"runner_malformed_output: {stdout[-200:]}",
|
|
410
|
+
"peak_memory_kb": 0,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if not isinstance(parsed, dict) or "status" not in parsed:
|
|
414
|
+
return {
|
|
415
|
+
"status": "failed",
|
|
416
|
+
"error": "runner returned an unexpected payload shape",
|
|
417
|
+
"peak_memory_kb": 0,
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Defense in depth: runner claimed success but exited nonzero.
|
|
421
|
+
if parsed.get("status") == "success" and returncode != 0:
|
|
422
|
+
parsed = {
|
|
423
|
+
"status": "failed",
|
|
424
|
+
"error": f"runner exited nonzero ({returncode}) despite success payload",
|
|
425
|
+
"peak_memory_kb": parsed.get("peak_memory_kb", 0),
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return parsed
|
|
429
|
+
|
|
430
|
+
async def _finalize_subprocess_result(
|
|
431
|
+
self,
|
|
432
|
+
skill_id: str,
|
|
433
|
+
execution_id: str,
|
|
434
|
+
raw: Dict[str, Any],
|
|
435
|
+
duration_ms: int,
|
|
436
|
+
) -> ExecutionResult:
|
|
437
|
+
"""Map the runner's JSON payload onto ExecutionResult + persist log."""
|
|
438
|
+
status = raw.get("status", "failed")
|
|
439
|
+
peak_kb = int(raw.get("peak_memory_kb") or 0)
|
|
440
|
+
peak_mb: Optional[float] = (peak_kb / 1024.0) if peak_kb else None
|
|
441
|
+
|
|
442
|
+
if status == "success":
|
|
443
|
+
await self._registry.record_execution(
|
|
444
|
+
skill_id, execution_id, "success", duration_ms
|
|
445
|
+
)
|
|
446
|
+
return ExecutionResult(
|
|
447
|
+
execution_id=execution_id,
|
|
448
|
+
skill_id=skill_id,
|
|
449
|
+
status="success",
|
|
450
|
+
output=raw.get("output"),
|
|
451
|
+
error=None,
|
|
452
|
+
duration_ms=duration_ms,
|
|
453
|
+
peak_memory_mb=peak_mb,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
error = raw.get("error") or "skill failed with no error message"
|
|
457
|
+
await self._registry.record_execution(
|
|
458
|
+
skill_id, execution_id, "failed", duration_ms
|
|
459
|
+
)
|
|
460
|
+
return ExecutionResult(
|
|
461
|
+
execution_id=execution_id,
|
|
462
|
+
skill_id=skill_id,
|
|
463
|
+
status="failed",
|
|
464
|
+
output=None,
|
|
465
|
+
error=str(error),
|
|
466
|
+
duration_ms=duration_ms,
|
|
467
|
+
peak_memory_mb=peak_mb,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# ------------------------------------------------------------------
|
|
471
|
+
# In-process rollback path
|
|
472
|
+
# ------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
@staticmethod
|
|
475
|
+
async def _run_skill_inprocess(
|
|
476
|
+
source_bytes: bytes,
|
|
477
|
+
inputs: Dict[str, Any],
|
|
478
|
+
) -> Any:
|
|
479
|
+
"""Legacy in-process execution. Used only when COLONY_SKILL_SANDBOX=inprocess."""
|
|
480
|
+
code = compile(source_bytes, "<skill>", "exec")
|
|
481
|
+
module = types.ModuleType("_skill_module")
|
|
482
|
+
exec(code, module.__dict__) # noqa: S102 # in-memory, checksum verified
|
|
483
|
+
run_fn = getattr(module, "run", None)
|
|
484
|
+
if run_fn is None:
|
|
485
|
+
raise AttributeError("Skill has no 'run' function.")
|
|
486
|
+
# Inject colony runtime if the skill signature expects it
|
|
487
|
+
import inspect
|
|
488
|
+
sig = inspect.signature(run_fn)
|
|
489
|
+
if "colony" in sig.parameters:
|
|
490
|
+
from apsimo.skills.runtime import ColonyRuntime
|
|
491
|
+
colony_rt = ColonyRuntime(base_url=os.environ.get("COLONY_URL", "http://127.0.0.1:7777"))
|
|
492
|
+
inputs = {"colony": colony_rt, **inputs}
|
|
493
|
+
return await run_fn(**inputs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Built-in executor skills
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Behavioral correction executor skill.
|
|
2
|
+
|
|
3
|
+
Applies learned preferences when recurring patterns are detected.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
from apsimo.skills.base import (
|
|
10
|
+
ExecutionResult,
|
|
11
|
+
InitiativeExecutionContext,
|
|
12
|
+
InitiativeExecutorSkill,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BehavioralCorrectionSkill(InitiativeExecutorSkill):
|
|
19
|
+
"""Skill for applying learned behavioral corrections."""
|
|
20
|
+
|
|
21
|
+
skill_name = "behavioral_correction"
|
|
22
|
+
skill_version = "1.0.0"
|
|
23
|
+
|
|
24
|
+
async def can_execute(
|
|
25
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
26
|
+
) -> bool:
|
|
27
|
+
return category.get("executor_skill") == self.skill_name
|
|
28
|
+
|
|
29
|
+
async def execute(
|
|
30
|
+
self, initiative: InitiativeExecutionContext
|
|
31
|
+
) -> ExecutionResult:
|
|
32
|
+
entity_id = initiative.entity_id or "unknown"
|
|
33
|
+
trigger = initiative.trigger_data.get("trigger", "")
|
|
34
|
+
expected_action = initiative.trigger_data.get("action", "")
|
|
35
|
+
recurrence_count = initiative.trigger_data.get("recurrence_count", 0)
|
|
36
|
+
self._log(
|
|
37
|
+
"info",
|
|
38
|
+
"Behavioral correction: %s (recurred %d times)",
|
|
39
|
+
trigger[:80],
|
|
40
|
+
recurrence_count,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Store preference in graph for future reference
|
|
44
|
+
if self.graph:
|
|
45
|
+
try:
|
|
46
|
+
await self._store_preference(trigger, expected_action, entity_id)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
self._log("warning", "Failed to store preference: %s", e)
|
|
49
|
+
|
|
50
|
+
# For v0.11.1, create a proposal. Full auto-application requires
|
|
51
|
+
# deeper integration with the reasoning pipeline.
|
|
52
|
+
return ExecutionResult.PROPOSAL_CREATED
|
|
53
|
+
|
|
54
|
+
async def _store_preference(
|
|
55
|
+
self, trigger: str, expected: str, entity_id: str
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Store a learned preference in the graph."""
|
|
58
|
+
if not hasattr(self.graph, "driver"):
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
import uuid
|
|
62
|
+
|
|
63
|
+
async with self.graph.driver.session(database=self.graph.database) as session:
|
|
64
|
+
await session.run(
|
|
65
|
+
"""
|
|
66
|
+
MERGE (p:Preference {trigger: $trigger})
|
|
67
|
+
SET p.id = coalesce(p.id, $new_id),
|
|
68
|
+
p.expected = $expected,
|
|
69
|
+
p.source = "behavioral_correction",
|
|
70
|
+
p.updated_at = datetime()
|
|
71
|
+
""",
|
|
72
|
+
trigger=trigger[:200],
|
|
73
|
+
expected=expected[:200],
|
|
74
|
+
new_id=str(uuid.uuid4()),
|
|
75
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Capability gap executor skill.
|
|
2
|
+
|
|
3
|
+
Registers missing capabilities and creates proposals for fixes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
from apsimo.skills.base import (
|
|
10
|
+
ExecutionResult,
|
|
11
|
+
InitiativeExecutionContext,
|
|
12
|
+
InitiativeExecutorSkill,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CapabilityGapSkill(InitiativeExecutorSkill):
|
|
19
|
+
"""Skill for handling missing or broken capabilities."""
|
|
20
|
+
|
|
21
|
+
skill_name = "capability_gap"
|
|
22
|
+
skill_version = "1.0.0"
|
|
23
|
+
|
|
24
|
+
async def can_execute(
|
|
25
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
26
|
+
) -> bool:
|
|
27
|
+
return category.get("executor_skill") == self.skill_name
|
|
28
|
+
|
|
29
|
+
async def execute(
|
|
30
|
+
self, initiative: InitiativeExecutionContext
|
|
31
|
+
) -> ExecutionResult:
|
|
32
|
+
entity_id = initiative.entity_id or "unknown"
|
|
33
|
+
failure_mode = initiative.trigger_data.get("failure_mode", "unknown")
|
|
34
|
+
failure_count = initiative.trigger_data.get("failure_count", 0)
|
|
35
|
+
self._log("info", "Capability gap: %s (%s, %d failures)", entity_id, failure_mode, failure_count)
|
|
36
|
+
|
|
37
|
+
# For v0.11.1, create a proposal. Auto-fix requires tool sandboxing.
|
|
38
|
+
return ExecutionResult.PROPOSAL_CREATED
|