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,431 @@
|
|
|
1
|
+
"""Colony Skills — render approved skills as Hermes SKILL.md documents (v0.18.0).
|
|
2
|
+
|
|
3
|
+
The Hermes↔Colony skills bridge, outbound half: when a captured DRAFT
|
|
4
|
+
skill is approved (DRAFT→ACTIVE), Colony can also publish it as an
|
|
5
|
+
*instructional* Hermes skill — a ``SKILL.md`` with YAML frontmatter
|
|
6
|
+
under ``~/.hermes/skills/colony/<skill-slug>/`` — so the agent host can
|
|
7
|
+
load the procedure as guidance, independent of Colony's sandboxed
|
|
8
|
+
executable skill.
|
|
9
|
+
|
|
10
|
+
Procedural-export heuristic (see :func:`is_procedural`):
|
|
11
|
+
|
|
12
|
+
* MCP-bridged skills (``manifest.origin == "mcp"``) are **never**
|
|
13
|
+
exported — they wrap a remote tool endpoint; there is no procedure
|
|
14
|
+
to teach.
|
|
15
|
+
* Skills whose pattern/source carries trace-derived steps — a
|
|
16
|
+
non-empty ``step_sequence``, ``colony.tools.invoke`` replay calls in
|
|
17
|
+
the synthesized source, ``Capture steps:`` in the docstring, or
|
|
18
|
+
``# Step:`` scaffold comments — are procedural and exported.
|
|
19
|
+
* Skills whose source is clearly pure computation (parsable Python
|
|
20
|
+
with a ``run()`` and none of the procedural markers above) are
|
|
21
|
+
skipped: rendering them as a step-by-step guide would be noise.
|
|
22
|
+
* Anything ambiguous (no source, unreadable skill dir, bare manifest)
|
|
23
|
+
is exported anyway — emission is already double-gated by the
|
|
24
|
+
``COLONY_EMIT_HERMES_SKILLS`` env switch and human approval.
|
|
25
|
+
|
|
26
|
+
Everything here is best-effort: callers at the approval transition
|
|
27
|
+
must log-and-continue on failure, never block activation.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import ast
|
|
33
|
+
import json
|
|
34
|
+
import logging
|
|
35
|
+
import os
|
|
36
|
+
import re
|
|
37
|
+
import tempfile
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Any, Dict, List, Optional
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
#: Author string stamped into every exported frontmatter.
|
|
45
|
+
HERMES_AUTHOR = "Colony (captured from agent work)"
|
|
46
|
+
|
|
47
|
+
#: Marker embedded in the frontmatter provenance comment block. A
|
|
48
|
+
#: pre-existing SKILL.md is only ever overwritten when its frontmatter
|
|
49
|
+
#: contains this marker — files authored by anyone else are sacred.
|
|
50
|
+
PROVENANCE_MARKER = "colony:provenance"
|
|
51
|
+
|
|
52
|
+
#: Env switch — exports are off unless this is truthy.
|
|
53
|
+
EMIT_ENV = "COLONY_EMIT_HERMES_SKILLS"
|
|
54
|
+
|
|
55
|
+
#: Env override for the export base directory.
|
|
56
|
+
BASE_DIR_ENV = "COLONY_HERMES_SKILLS_DIR"
|
|
57
|
+
|
|
58
|
+
_DEFAULT_BASE = Path("~/.hermes/skills/colony")
|
|
59
|
+
|
|
60
|
+
_SLUG_RE = re.compile(r"[^a-z0-9-]+")
|
|
61
|
+
_TOOL_INVOKE_RE = re.compile(r"colony\.tools\.invoke\(\s*['\"]([^'\"]+)['\"]")
|
|
62
|
+
_TRUTHY = {"1", "true", "yes", "on"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Small helpers
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
def _slugify(value: str) -> str:
|
|
70
|
+
slug = _SLUG_RE.sub("-", (value or "").strip().lower()).strip("-")
|
|
71
|
+
return slug or "unnamed-skill"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _yaml_str(value: Any) -> str:
|
|
75
|
+
"""Quote a scalar for YAML. JSON string quoting is valid YAML."""
|
|
76
|
+
return json.dumps(str(value if value is not None else ""))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get(manifest: Any, attr: str, default: Any = None) -> Any:
|
|
80
|
+
"""getattr that also tolerates plain dicts (test fakes)."""
|
|
81
|
+
if isinstance(manifest, dict):
|
|
82
|
+
return manifest.get(attr, default)
|
|
83
|
+
return getattr(manifest, attr, default)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def hermes_export_enabled() -> bool:
|
|
87
|
+
return os.environ.get(EMIT_ENV, "false").strip().lower() in _TRUTHY
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def hermes_base_dir(base_dir: Optional[Path] = None) -> Path:
|
|
91
|
+
if base_dir is not None:
|
|
92
|
+
return Path(base_dir).expanduser()
|
|
93
|
+
env = os.environ.get(BASE_DIR_ENV, "").strip()
|
|
94
|
+
if env:
|
|
95
|
+
return Path(env).expanduser()
|
|
96
|
+
# Track the sync/poller root (HERMES_SKILLS_DIR) so a deployment that
|
|
97
|
+
# relocates the skills root doesn't silently split the export dir away
|
|
98
|
+
# from where skills_sync/agent_bridge read (they'd never see exports).
|
|
99
|
+
sync_root = os.environ.get("HERMES_SKILLS_DIR", "").strip()
|
|
100
|
+
if sync_root:
|
|
101
|
+
return (Path(sync_root) / "colony").expanduser()
|
|
102
|
+
return _DEFAULT_BASE.expanduser()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Pattern/source recovery
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def load_body_source(manifest: Any) -> Dict[str, Any]:
|
|
110
|
+
"""Recover renderable pattern data from a packaged skill directory.
|
|
111
|
+
|
|
112
|
+
The ``ExtractedPattern`` itself is not persisted; what survives is
|
|
113
|
+
``skill.py`` (synthesized source whose ``run()`` docstring carries
|
|
114
|
+
the original task and whose body replays captured tool calls via
|
|
115
|
+
``colony.tools.invoke``). Returns a dict shaped like the pattern
|
|
116
|
+
fields ``render_skill_md`` understands — empty when nothing can be
|
|
117
|
+
recovered.
|
|
118
|
+
"""
|
|
119
|
+
out: Dict[str, Any] = {}
|
|
120
|
+
skill_dir = _get(manifest, "skill_dir")
|
|
121
|
+
if not skill_dir:
|
|
122
|
+
return out
|
|
123
|
+
source_path = Path(str(skill_dir)) / "skill.py"
|
|
124
|
+
try:
|
|
125
|
+
source = source_path.read_text(encoding="utf-8")
|
|
126
|
+
except OSError:
|
|
127
|
+
return out
|
|
128
|
+
out["source_code"] = source
|
|
129
|
+
|
|
130
|
+
# run() docstring (or module docstring) → body prose.
|
|
131
|
+
try:
|
|
132
|
+
tree = ast.parse(source)
|
|
133
|
+
doc = ast.get_docstring(tree) or ""
|
|
134
|
+
for node in ast.walk(tree):
|
|
135
|
+
if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)) and node.name == "run":
|
|
136
|
+
doc = ast.get_docstring(node) or doc
|
|
137
|
+
break
|
|
138
|
+
if doc:
|
|
139
|
+
out["docstring"] = doc
|
|
140
|
+
except SyntaxError:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
# Replayed tool calls → step sequence.
|
|
144
|
+
steps = [f"Invoke the `{tool}` tool" for tool in _TOOL_INVOKE_RE.findall(source)]
|
|
145
|
+
if steps:
|
|
146
|
+
out["step_sequence"] = steps
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Procedural heuristic
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def is_procedural(manifest: Any, pattern_or_source: Optional[Dict[str, Any]]) -> bool:
|
|
155
|
+
"""Decide whether a skill carries teachable *procedure*.
|
|
156
|
+
|
|
157
|
+
See the module docstring for the full decision table. In short:
|
|
158
|
+
MCP wrappers → no; trace-derived steps/tool calls → yes; clearly
|
|
159
|
+
pure computation → no; ambiguous → yes (export anyway).
|
|
160
|
+
"""
|
|
161
|
+
if _get(manifest, "origin") == "mcp":
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
p = pattern_or_source or {}
|
|
165
|
+
steps = p.get("step_sequence") or []
|
|
166
|
+
source = p.get("source_code") or ""
|
|
167
|
+
docstring = p.get("docstring") or ""
|
|
168
|
+
|
|
169
|
+
if steps:
|
|
170
|
+
return True
|
|
171
|
+
if "colony.tools.invoke" in source:
|
|
172
|
+
return True
|
|
173
|
+
if "Capture steps:" in docstring or "# Step:" in source:
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
# Clearly pure computation: real source with a run() and zero
|
|
177
|
+
# procedural markers. (A NotImplementedError scaffold without
|
|
178
|
+
# steps also lands here — there is nothing to teach yet.)
|
|
179
|
+
if source:
|
|
180
|
+
try:
|
|
181
|
+
tree = ast.parse(source)
|
|
182
|
+
except SyntaxError:
|
|
183
|
+
return True # unparseable → in doubt → export
|
|
184
|
+
has_run = any(
|
|
185
|
+
isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)) and node.name == "run"
|
|
186
|
+
for node in ast.walk(tree)
|
|
187
|
+
)
|
|
188
|
+
if has_run:
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# No usable pattern/source data at all → in doubt → export.
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Rendering
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
def _render_frontmatter(manifest: Any, slug: str) -> str:
|
|
200
|
+
description = str(_get(manifest, "description") or "").strip() or (
|
|
201
|
+
f"Colony-captured skill {slug}"
|
|
202
|
+
)
|
|
203
|
+
version = str(_get(manifest, "version") or "1.0.0")
|
|
204
|
+
tags: List[str] = [str(t) for t in (_get(manifest, "tags") or [])]
|
|
205
|
+
|
|
206
|
+
lines = [
|
|
207
|
+
"---",
|
|
208
|
+
f"name: {slug}",
|
|
209
|
+
f"description: {_yaml_str(description.splitlines()[0])}",
|
|
210
|
+
f"version: {_yaml_str(version)}",
|
|
211
|
+
f"author: {_yaml_str(HERMES_AUTHOR)}",
|
|
212
|
+
]
|
|
213
|
+
if tags:
|
|
214
|
+
lines.append("metadata:")
|
|
215
|
+
lines.append(" hermes:")
|
|
216
|
+
lines.append(" tags:")
|
|
217
|
+
lines.extend(f" - {_yaml_str(t)}" for t in tags)
|
|
218
|
+
|
|
219
|
+
# Provenance comment block — the marker below is what allows a
|
|
220
|
+
# future re-export to overwrite this file (and nothing else).
|
|
221
|
+
lines.append(f"# {PROVENANCE_MARKER}")
|
|
222
|
+
lines.append(f"# colony_skill_id: {_yaml_str(_get(manifest, 'skill_id') or slug)}")
|
|
223
|
+
origin_task_id = _get(manifest, "origin_task_id")
|
|
224
|
+
if origin_task_id:
|
|
225
|
+
lines.append(f"# origin_task_id: {_yaml_str(origin_task_id)}")
|
|
226
|
+
lines.append(
|
|
227
|
+
f"# exported_at: {_yaml_str(datetime.now(timezone.utc).isoformat())}"
|
|
228
|
+
)
|
|
229
|
+
lines.append("---")
|
|
230
|
+
return "\n".join(lines)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _schema_usage_notes(manifest: Any) -> List[str]:
|
|
234
|
+
notes: List[str] = []
|
|
235
|
+
input_schema = _get(manifest, "input_schema") or {}
|
|
236
|
+
props = input_schema.get("properties") or {} if isinstance(input_schema, dict) else {}
|
|
237
|
+
required = set(input_schema.get("required") or []) if isinstance(input_schema, dict) else set()
|
|
238
|
+
for key, spec in props.items():
|
|
239
|
+
typ = (spec or {}).get("type", "any") if isinstance(spec, dict) else "any"
|
|
240
|
+
req = "required" if key in required else "optional"
|
|
241
|
+
notes.append(f"- Input `{key}` ({typ}, {req})")
|
|
242
|
+
output_schema = _get(manifest, "output_schema") or {}
|
|
243
|
+
if isinstance(output_schema, dict) and output_schema.get("type"):
|
|
244
|
+
notes.append(f"- Produces a {output_schema['type']} result")
|
|
245
|
+
deps = _get(manifest, "dependencies") or []
|
|
246
|
+
if deps:
|
|
247
|
+
notes.append(f"- Depends on: {', '.join(str(d) for d in deps)}")
|
|
248
|
+
return notes
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _docstring_steps(docstring: str) -> List[str]:
|
|
252
|
+
"""Pull ``1. tool(summary)`` style lines out of a captured docstring."""
|
|
253
|
+
steps: List[str] = []
|
|
254
|
+
for line in docstring.splitlines():
|
|
255
|
+
m = re.match(r"\s*\d+\.\s+(.*\S)", line)
|
|
256
|
+
if m:
|
|
257
|
+
steps.append(m.group(1))
|
|
258
|
+
return steps
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def render_skill_md(manifest: Any, pattern_or_source: Dict[str, Any]) -> str:
|
|
262
|
+
"""Render a Hermes-faithful SKILL.md for an approved Colony skill.
|
|
263
|
+
|
|
264
|
+
``manifest`` is a ``SkillManifest`` (or anything duck-typed like
|
|
265
|
+
one); ``pattern_or_source`` is a dict carrying whatever pattern
|
|
266
|
+
data is available (``docstring``, ``step_sequence``, ``source_code``
|
|
267
|
+
— see :func:`load_body_source`).
|
|
268
|
+
"""
|
|
269
|
+
p = pattern_or_source or {}
|
|
270
|
+
skill_id = str(_get(manifest, "skill_id") or _get(manifest, "name") or "unnamed")
|
|
271
|
+
slug = _slugify(skill_id)
|
|
272
|
+
name = str(_get(manifest, "name") or slug.replace("-", " ").title())
|
|
273
|
+
description = str(_get(manifest, "description") or "").strip()
|
|
274
|
+
docstring = str(p.get("docstring") or "").strip()
|
|
275
|
+
tags = [str(t) for t in (_get(manifest, "tags") or [])]
|
|
276
|
+
|
|
277
|
+
body: List[str] = ["", f"# {name}", ""]
|
|
278
|
+
|
|
279
|
+
# --- What the skill does ---
|
|
280
|
+
body.append("## What this skill does")
|
|
281
|
+
body.append("")
|
|
282
|
+
body.append(description or f"Procedure captured by Colony from agent task work ({skill_id}).")
|
|
283
|
+
if docstring and docstring.splitlines()[0] != description:
|
|
284
|
+
body.append("")
|
|
285
|
+
body.append(docstring.splitlines()[0])
|
|
286
|
+
body.append("")
|
|
287
|
+
|
|
288
|
+
# --- When to use it ---
|
|
289
|
+
body.append("## When to use this skill")
|
|
290
|
+
body.append("")
|
|
291
|
+
body.append(
|
|
292
|
+
"Use this skill when the task at hand resembles the work it was "
|
|
293
|
+
"captured from"
|
|
294
|
+
+ (f" — topics: {', '.join(tags)}." if tags else ".")
|
|
295
|
+
)
|
|
296
|
+
trigger_patterns = _get(manifest, "trigger_patterns") or []
|
|
297
|
+
if trigger_patterns:
|
|
298
|
+
body.append("")
|
|
299
|
+
body.append("Trigger hints:")
|
|
300
|
+
body.extend(f"- `{t}`" for t in trigger_patterns)
|
|
301
|
+
body.append("")
|
|
302
|
+
|
|
303
|
+
# --- Procedure ---
|
|
304
|
+
steps: List[str] = [str(s) for s in (p.get("step_sequence") or [])]
|
|
305
|
+
if not steps and docstring:
|
|
306
|
+
steps = _docstring_steps(docstring)
|
|
307
|
+
body.append("## Procedure")
|
|
308
|
+
body.append("")
|
|
309
|
+
if steps:
|
|
310
|
+
body.extend(f"{i}. {step}" for i, step in enumerate(steps, start=1))
|
|
311
|
+
else:
|
|
312
|
+
body.append(
|
|
313
|
+
"No step-by-step trace was captured; follow the description "
|
|
314
|
+
"above and adapt to the task context."
|
|
315
|
+
)
|
|
316
|
+
body.append("")
|
|
317
|
+
|
|
318
|
+
# --- Usage notes from the schemas ---
|
|
319
|
+
notes = _schema_usage_notes(manifest)
|
|
320
|
+
if notes:
|
|
321
|
+
body.append("## Usage notes")
|
|
322
|
+
body.append("")
|
|
323
|
+
body.extend(notes)
|
|
324
|
+
body.append("")
|
|
325
|
+
|
|
326
|
+
body.append("---")
|
|
327
|
+
body.append(
|
|
328
|
+
f"*Captured by Colony from agent work (colony skill `{skill_id}`"
|
|
329
|
+
+ (
|
|
330
|
+
f", origin task `{_get(manifest, 'origin_task_id')}`"
|
|
331
|
+
if _get(manifest, "origin_task_id")
|
|
332
|
+
else ""
|
|
333
|
+
)
|
|
334
|
+
+ "). The executable form runs sandboxed inside Colony; this "
|
|
335
|
+
"document is the human/agent-readable procedure.*"
|
|
336
|
+
)
|
|
337
|
+
body.append("")
|
|
338
|
+
|
|
339
|
+
return _render_frontmatter(manifest, slug) + "\n".join(body)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
# Export
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
def _frontmatter_block(text: str) -> str:
|
|
347
|
+
"""Return the YAML frontmatter region of a SKILL.md ('' if none)."""
|
|
348
|
+
stripped = text.lstrip("\r\n ")
|
|
349
|
+
if not stripped.startswith("---"):
|
|
350
|
+
return ""
|
|
351
|
+
end = stripped.find("\n---", 3)
|
|
352
|
+
if end == -1:
|
|
353
|
+
return ""
|
|
354
|
+
return stripped[: end + 4]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def export_to_hermes(
|
|
358
|
+
manifest: Any,
|
|
359
|
+
body_source: Optional[Dict[str, Any]] = None,
|
|
360
|
+
base_dir: Optional[Path] = None,
|
|
361
|
+
) -> Optional[Path]:
|
|
362
|
+
"""Write ``<base>/<skill-slug>/SKILL.md`` for an approved skill.
|
|
363
|
+
|
|
364
|
+
Returns the written path, or ``None`` when emission is disabled
|
|
365
|
+
(``COLONY_EMIT_HERMES_SKILLS`` defaults to off) or the target file
|
|
366
|
+
exists and was not authored by Colony (no provenance marker in its
|
|
367
|
+
frontmatter — never clobber a hand-written Hermes skill).
|
|
368
|
+
|
|
369
|
+
The write is atomic: rendered to a temp file in the target
|
|
370
|
+
directory, then ``os.replace``d into place.
|
|
371
|
+
"""
|
|
372
|
+
if not hermes_export_enabled():
|
|
373
|
+
logger.debug("Hermes export disabled (%s is not truthy)", EMIT_ENV)
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
skill_id = str(_get(manifest, "skill_id") or _get(manifest, "name") or "unnamed")
|
|
377
|
+
slug = _slugify(skill_id)
|
|
378
|
+
target_dir = hermes_base_dir(base_dir) / slug
|
|
379
|
+
target = target_dir / "SKILL.md"
|
|
380
|
+
|
|
381
|
+
if target.exists():
|
|
382
|
+
try:
|
|
383
|
+
existing = target.read_text(encoding="utf-8")
|
|
384
|
+
except OSError as exc:
|
|
385
|
+
logger.warning("Hermes export: cannot read existing %s: %s", target, exc)
|
|
386
|
+
return None
|
|
387
|
+
if PROVENANCE_MARKER not in _frontmatter_block(existing):
|
|
388
|
+
logger.warning(
|
|
389
|
+
"Hermes export: %s exists and is not colony-authored — refusing to overwrite",
|
|
390
|
+
target,
|
|
391
|
+
)
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
rendered = render_skill_md(manifest, body_source or {})
|
|
395
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
396
|
+
fd, tmp_path = tempfile.mkstemp(prefix=".skill-", suffix=".md.tmp", dir=target_dir)
|
|
397
|
+
try:
|
|
398
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
399
|
+
fh.write(rendered)
|
|
400
|
+
os.replace(tmp_path, target)
|
|
401
|
+
except BaseException:
|
|
402
|
+
try:
|
|
403
|
+
os.unlink(tmp_path)
|
|
404
|
+
except OSError:
|
|
405
|
+
pass
|
|
406
|
+
raise
|
|
407
|
+
logger.info("Hermes export: wrote %s (skill %s)", target, skill_id)
|
|
408
|
+
return target
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def export_approved_skill(
|
|
412
|
+
manifest: Any,
|
|
413
|
+
pattern_or_source: Optional[Dict[str, Any]] = None,
|
|
414
|
+
) -> Optional[Path]:
|
|
415
|
+
"""Approval-transition hook: export a freshly ACTIVE skill.
|
|
416
|
+
|
|
417
|
+
Best-effort by contract — the caller (the DRAFT→ACTIVE transition)
|
|
418
|
+
wraps this in try/except and never lets a failure block activation.
|
|
419
|
+
Returns the written path or ``None`` (disabled, non-procedural, or
|
|
420
|
+
overwrite-protected).
|
|
421
|
+
"""
|
|
422
|
+
if not hermes_export_enabled():
|
|
423
|
+
return None
|
|
424
|
+
body = dict(pattern_or_source) if pattern_or_source else load_body_source(manifest)
|
|
425
|
+
if not is_procedural(manifest, body):
|
|
426
|
+
logger.info(
|
|
427
|
+
"Hermes export: skill %s is not procedural — skipping",
|
|
428
|
+
_get(manifest, "skill_id"),
|
|
429
|
+
)
|
|
430
|
+
return None
|
|
431
|
+
return export_to_hermes(manifest, body)
|
apsimo/skills/index.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Colony Skills — lightweight in-memory skill index for progressive loading."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from apsimo.skills.models import SkillManifest, SkillStatus
|
|
13
|
+
from apsimo.skills.registry import SkillRegistry
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class SkillEntry:
|
|
20
|
+
"""Minimal in-memory representation of one skill."""
|
|
21
|
+
|
|
22
|
+
skill_id: str
|
|
23
|
+
name: str
|
|
24
|
+
description: str
|
|
25
|
+
trigger_patterns: List[str]
|
|
26
|
+
context_tokens_estimate: int
|
|
27
|
+
dependencies: List[str]
|
|
28
|
+
lazy_loader: Optional[str]
|
|
29
|
+
skill_dir: Path
|
|
30
|
+
trust_score: float
|
|
31
|
+
execution_count: int
|
|
32
|
+
_patterns: List[re.Pattern] = field(default_factory=list, repr=False)
|
|
33
|
+
|
|
34
|
+
def compile_patterns(self) -> None:
|
|
35
|
+
"""Compile trigger_patterns strings into re.Pattern objects."""
|
|
36
|
+
compiled = []
|
|
37
|
+
for pat in self.trigger_patterns:
|
|
38
|
+
try:
|
|
39
|
+
compiled.append(re.compile(pat, re.IGNORECASE))
|
|
40
|
+
except re.error as exc:
|
|
41
|
+
logger.warning(
|
|
42
|
+
"Skill %s: invalid trigger pattern %r: %s", self.skill_id, pat, exc
|
|
43
|
+
)
|
|
44
|
+
self._patterns = compiled
|
|
45
|
+
|
|
46
|
+
def matches(self, text: str) -> bool:
|
|
47
|
+
"""Return True if any compiled pattern matches text."""
|
|
48
|
+
return any(p.search(text) for p in self._patterns)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SkillIndex:
|
|
52
|
+
"""Lightweight, always-resident index of skill manifests.
|
|
53
|
+
|
|
54
|
+
Built from the SQLite registry without importing skill modules.
|
|
55
|
+
Thread-safe for concurrent reads; uses asyncio.Lock for mutations.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, registry: SkillRegistry) -> None:
|
|
59
|
+
self._registry = registry
|
|
60
|
+
self._entries: dict[str, SkillEntry] = {}
|
|
61
|
+
self._lock = asyncio.Lock()
|
|
62
|
+
|
|
63
|
+
async def build(self) -> None:
|
|
64
|
+
"""Scan registry for ACTIVE skills; build SkillEntry list."""
|
|
65
|
+
manifests = await self._registry.list_all(status=SkillStatus.ACTIVE)
|
|
66
|
+
async with self._lock:
|
|
67
|
+
self._entries = {}
|
|
68
|
+
for m in manifests:
|
|
69
|
+
entry = self._manifest_to_entry(m)
|
|
70
|
+
entry.compile_patterns()
|
|
71
|
+
self._entries[entry.skill_id] = entry
|
|
72
|
+
logger.info("SkillIndex: built %d entries", len(self._entries))
|
|
73
|
+
|
|
74
|
+
def match(self, text: str) -> List[SkillEntry]:
|
|
75
|
+
"""Return SkillEntries whose trigger_patterns match text.
|
|
76
|
+
|
|
77
|
+
Pure regex — O(skills × patterns), no I/O.
|
|
78
|
+
Results sorted by trust_score DESC.
|
|
79
|
+
"""
|
|
80
|
+
matched = [e for e in self._entries.values() if e.matches(text)]
|
|
81
|
+
matched.sort(key=lambda e: e.trust_score, reverse=True)
|
|
82
|
+
return matched
|
|
83
|
+
|
|
84
|
+
async def register(self, manifest: SkillManifest, skill_dir: Path) -> None:
|
|
85
|
+
"""Add or update entry after a new skill is packaged."""
|
|
86
|
+
entry = self._manifest_to_entry(manifest, skill_dir)
|
|
87
|
+
entry.compile_patterns()
|
|
88
|
+
async with self._lock:
|
|
89
|
+
self._entries[entry.skill_id] = entry
|
|
90
|
+
|
|
91
|
+
async def evict(self, skill_id: str) -> None:
|
|
92
|
+
"""Remove entry (called when a skill is archived/quarantined)."""
|
|
93
|
+
async with self._lock:
|
|
94
|
+
self._entries.pop(skill_id, None)
|
|
95
|
+
|
|
96
|
+
def all_entries(self) -> List[SkillEntry]:
|
|
97
|
+
"""Return all entries for budget planning."""
|
|
98
|
+
return list(self._entries.values())
|
|
99
|
+
|
|
100
|
+
def get(self, skill_id: str) -> Optional[SkillEntry]:
|
|
101
|
+
"""Return entry by skill_id, or None."""
|
|
102
|
+
return self._entries.get(skill_id)
|
|
103
|
+
|
|
104
|
+
def __len__(self) -> int:
|
|
105
|
+
return len(self._entries)
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _manifest_to_entry(
|
|
109
|
+
m: SkillManifest, skill_dir: Optional[Path] = None
|
|
110
|
+
) -> SkillEntry:
|
|
111
|
+
resolved_dir = skill_dir or Path(m.skill_dir or "")
|
|
112
|
+
return SkillEntry(
|
|
113
|
+
skill_id=m.skill_id,
|
|
114
|
+
name=m.name,
|
|
115
|
+
description=m.description,
|
|
116
|
+
trigger_patterns=list(m.trigger_patterns),
|
|
117
|
+
context_tokens_estimate=m.context_tokens_estimate,
|
|
118
|
+
dependencies=list(m.dependencies),
|
|
119
|
+
lazy_loader=m.lazy_loader,
|
|
120
|
+
skill_dir=resolved_dir,
|
|
121
|
+
trust_score=m.trust_score,
|
|
122
|
+
execution_count=m.execution_count,
|
|
123
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Colony Skills — learning pipeline."""
|
|
2
|
+
|
|
3
|
+
from apsimo.skills.learning.novelty_detector import NoveltyDetector, NoveltyResult
|
|
4
|
+
from apsimo.skills.learning.pattern_extractor import PatternExtractor, ExtractedPattern
|
|
5
|
+
from apsimo.skills.learning.triggers import (
|
|
6
|
+
SkillLearningService,
|
|
7
|
+
LearningTriggerEvent,
|
|
8
|
+
TriggerCoordinator,
|
|
9
|
+
TriggerSource,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"NoveltyDetector",
|
|
14
|
+
"NoveltyResult",
|
|
15
|
+
"PatternExtractor",
|
|
16
|
+
"ExtractedPattern",
|
|
17
|
+
"SkillLearningService",
|
|
18
|
+
"LearningTriggerEvent",
|
|
19
|
+
"TriggerCoordinator",
|
|
20
|
+
"TriggerSource",
|
|
21
|
+
]
|