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.
Files changed (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
@@ -0,0 +1,246 @@
1
+ """Colony Skills — on-demand skill module loader."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import hashlib
7
+ import hmac
8
+ import importlib
9
+ import importlib.util
10
+ import logging
11
+ import types
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Dict, List, Optional
16
+
17
+ from apsimo.skills.budget import ContextBudget
18
+ from apsimo.skills.index import SkillEntry, SkillIndex
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class SecurityError(Exception):
24
+ """Raised when a skill integrity or security check fails."""
25
+
26
+
27
+ class SchemaError(Exception):
28
+ """Raised when a skill entry is missing required schema fields."""
29
+
30
+
31
+ # Only modules under these prefixes may be loaded via lazy_loader
32
+ _ALLOWED_LAZY_LOADER_PREFIXES = frozenset({"colony.skills.", "colony.plugins."})
33
+
34
+
35
+ def _verify_skill_checksum(skill_path: Path, expected_sha256: str) -> bytes:
36
+ """Verify SHA-256 of skill_path matches expected_sha256 before execution.
37
+
38
+ Returns the file bytes so the caller can compile from memory without a
39
+ second disk read, eliminating the TOCTOU window (SEC-14-C-03).
40
+
41
+ Raises:
42
+ SecurityError: If checksum is missing or does not match.
43
+ """
44
+ if not expected_sha256:
45
+ raise SecurityError(
46
+ "SkillEntry has no checksum — cannot load unverified skill"
47
+ )
48
+ file_bytes = skill_path.read_bytes()
49
+ actual = hashlib.sha256(file_bytes).hexdigest()
50
+ if not hmac.compare_digest(actual, expected_sha256):
51
+ raise SecurityError(
52
+ f"Skill checksum mismatch for {skill_path}: "
53
+ f"expected {expected_sha256!r}, got {actual!r}"
54
+ )
55
+ return file_bytes
56
+
57
+
58
+ @dataclass
59
+ class LoadedSkill:
60
+ """A skill that has been imported and is resident in memory."""
61
+
62
+ entry: SkillEntry
63
+ module: types.ModuleType
64
+ loaded_at: datetime
65
+ last_used_at: datetime
66
+ use_count: int = 0
67
+
68
+
69
+ class SkillLoader:
70
+ """Imports and initializes skill modules on demand.
71
+
72
+ Maintains a loaded-skill cache keyed by skill_id.
73
+ """
74
+
75
+ def __init__(self, index: SkillIndex) -> None:
76
+ self._index = index
77
+ self._loaded: Dict[str, LoadedSkill] = {}
78
+ self._lock = asyncio.Lock()
79
+
80
+ async def load(self, skill_id: str) -> Optional[LoadedSkill]:
81
+ """Import the skill module if not already loaded.
82
+
83
+ Records load timestamp for LRU eviction.
84
+ Returns None if the skill is not in the index.
85
+ """
86
+ async with self._lock:
87
+ if skill_id in self._loaded:
88
+ ls = self._loaded[skill_id]
89
+ ls.last_used_at = datetime.now(timezone.utc)
90
+ ls.use_count += 1
91
+ return ls
92
+
93
+ entry = self._index.get(skill_id)
94
+ if entry is None:
95
+ logger.warning("SkillLoader.load: skill %r not in index", skill_id)
96
+ return None
97
+
98
+ module = await self._import_module(entry)
99
+ if module is None:
100
+ return None
101
+
102
+ # Call initialize() hook if present
103
+ init_fn = getattr(module, "initialize", None)
104
+ if init_fn is not None:
105
+ try:
106
+ if asyncio.iscoroutinefunction(init_fn):
107
+ await init_fn()
108
+ else:
109
+ init_fn()
110
+ except Exception as exc:
111
+ logger.warning("Skill %s initialize() failed: %s", skill_id, exc)
112
+
113
+ now = datetime.now(timezone.utc)
114
+ ls = LoadedSkill(
115
+ entry=entry,
116
+ module=module,
117
+ loaded_at=now,
118
+ last_used_at=now,
119
+ use_count=1,
120
+ )
121
+ self._loaded[skill_id] = ls
122
+ logger.debug("SkillLoader: loaded skill %s", skill_id)
123
+ return ls
124
+
125
+ async def unload(self, skill_id: str) -> None:
126
+ """Remove skill from loaded cache.
127
+
128
+ Does NOT clean up sys.modules (Python limitation), but clears the
129
+ in-context reference so the agent no longer sees it.
130
+ """
131
+ async with self._lock:
132
+ self._loaded.pop(skill_id, None)
133
+ logger.debug("SkillLoader: unloaded skill %s", skill_id)
134
+
135
+ async def load_for_event(
136
+ self, text: str, budget: ContextBudget
137
+ ) -> List[LoadedSkill]:
138
+ """Match text against index, load matched skills within budget.
139
+
140
+ Returns list of all loaded skills relevant to the event (including
141
+ already-loaded ones). Already-loaded skills do not consume budget again.
142
+ """
143
+ candidates = self._index.match(text)
144
+ result: List[LoadedSkill] = []
145
+ current_used = self.token_footprint()
146
+
147
+ for entry in candidates:
148
+ if entry.skill_id in self._loaded:
149
+ ls = self._loaded[entry.skill_id]
150
+ ls.last_used_at = datetime.now(timezone.utc)
151
+ ls.use_count += 1
152
+ result.append(ls)
153
+ elif budget.has_capacity(entry.context_tokens_estimate, current_used):
154
+ ls = await self.load(entry.skill_id)
155
+ if ls is not None:
156
+ result.append(ls)
157
+ current_used += entry.context_tokens_estimate
158
+ else:
159
+ logger.debug(
160
+ "SkillLoader: budget full, skipping skill %s (needs %d, available %d)",
161
+ entry.skill_id,
162
+ entry.context_tokens_estimate,
163
+ budget.tokens_available(current_used),
164
+ )
165
+
166
+ return result
167
+
168
+ def loaded_ids(self) -> List[str]:
169
+ """Return skill_ids currently in the loaded cache."""
170
+ return list(self._loaded.keys())
171
+
172
+ def token_footprint(self) -> int:
173
+ """Sum of context_tokens_estimate for all loaded skills."""
174
+ return sum(ls.entry.context_tokens_estimate for ls in self._loaded.values())
175
+
176
+ def get_loaded(self, skill_id: str) -> Optional[LoadedSkill]:
177
+ """Return a loaded skill by ID, or None."""
178
+ return self._loaded.get(skill_id)
179
+
180
+ def all_loaded(self) -> List[LoadedSkill]:
181
+ """Return all currently loaded skills."""
182
+ return list(self._loaded.values())
183
+
184
+ @staticmethod
185
+ async def _import_module(entry: SkillEntry) -> Optional[types.ModuleType]:
186
+ """Import skill module using lazy_loader or default file-based import."""
187
+ if entry.lazy_loader:
188
+ # Dotted import path: "some.module:factory_fn" — allowlist enforced
189
+ try:
190
+ parts = entry.lazy_loader.split(":", 1)
191
+ mod_path = parts[0]
192
+ fn_name = parts[1] if len(parts) > 1 else None
193
+
194
+ if not any(mod_path.startswith(p) for p in _ALLOWED_LAZY_LOADER_PREFIXES):
195
+ raise SecurityError(
196
+ f"Disallowed lazy_loader module: {mod_path!r}"
197
+ )
198
+ if fn_name is not None and not fn_name.isidentifier():
199
+ raise SecurityError(
200
+ f"Invalid function name in lazy_loader: {fn_name!r}"
201
+ )
202
+
203
+ mod = importlib.import_module(mod_path)
204
+ if fn_name:
205
+ fn = getattr(mod, fn_name)
206
+ result = fn(entry)
207
+ if asyncio.iscoroutine(result):
208
+ return await result
209
+ return result
210
+ return mod
211
+ except SecurityError:
212
+ raise
213
+ except Exception as exc:
214
+ logger.error(
215
+ "Skill %s: lazy_loader %r failed: %s", entry.skill_id, entry.lazy_loader, exc
216
+ )
217
+ return None
218
+
219
+ # Default: load skill.py from skill_dir
220
+ skill_path = entry.skill_dir / "skill.py"
221
+ if not skill_path.exists():
222
+ logger.warning("Skill %s: no skill.py at %s", entry.skill_id, skill_path)
223
+ return None
224
+
225
+ try:
226
+ # Read once, verify checksum, compile from memory — no second disk read
227
+ # eliminates the TOCTOU window (SEC-14-C-03)
228
+ if not hasattr(entry, "checksum_sha256"):
229
+ raise SchemaError(
230
+ f"SkillEntry for {entry.skill_id!r} is missing required "
231
+ f"'checksum_sha256' field — refusing to load unverified skill"
232
+ )
233
+ file_bytes = _verify_skill_checksum(skill_path, entry.checksum_sha256)
234
+
235
+ # Compile and execute from the already-verified in-memory bytes
236
+ code = compile(file_bytes, str(skill_path), "exec")
237
+ module = types.ModuleType(f"_skill_{entry.skill_id}")
238
+ module.__file__ = str(skill_path)
239
+ module.__spec__ = None
240
+ exec(code, module.__dict__) # noqa: S102 # nosec B102 # bytes already verified above
241
+ return module
242
+ except (SecurityError, SchemaError):
243
+ raise
244
+ except Exception as exc:
245
+ logger.error("Skill %s: import from %s failed: %s", entry.skill_id, skill_path, exc)
246
+ return None
@@ -0,0 +1,6 @@
1
+ -- Migration 002: progressive skill loading columns
2
+ -- Safe to run on existing databases; columns added only if absent.
3
+
4
+ ALTER TABLE skills ADD COLUMN trigger_patterns TEXT NOT NULL DEFAULT '[]';
5
+ ALTER TABLE skills ADD COLUMN context_tokens_estimate INTEGER NOT NULL DEFAULT 2048;
6
+ ALTER TABLE skills ADD COLUMN lazy_loader TEXT;
@@ -0,0 +1,20 @@
1
+ """Backfill trigger_patterns for existing skills from their name and tags.
2
+
3
+ NOTE: This migration was written for a legacy SQLite-backed SkillRegistry.
4
+ The current SkillRegistry is in-memory and auto-loads built-in skills.
5
+ This script is kept for historical reference but is a no-op.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def backfill(db_path: Path) -> None:
15
+ print("INFO: Legacy migration skipped — current SkillRegistry is in-memory.")
16
+
17
+
18
+ if __name__ == "__main__":
19
+ db = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("~/.colony/skills.db").expanduser()
20
+ backfill(db)
@@ -0,0 +1,202 @@
1
+ """Colony Skills — data models for the self-creating skills subsystem."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timezone
9
+ from enum import Enum
10
+ from typing import Any, Dict, List, Optional, Set
11
+
12
+
13
+ class SkillStatus(str, Enum):
14
+ DRAFT = "draft"
15
+ ACTIVE = "active"
16
+ INACTIVE = "inactive"
17
+ QUARANTINED = "quarantined"
18
+ DEPRECATED = "deprecated"
19
+ ARCHIVED = "archived"
20
+
21
+
22
+ class SkillCapability(str, Enum):
23
+ """Capabilities a skill may declare. All are deny-by-default."""
24
+ NETWORK = "network"
25
+ FILESYSTEM_READ = "fs_read"
26
+ FILESYSTEM_WRITE = "fs_write"
27
+ SUBPROCESS = "subprocess"
28
+ ENV_READ = "env_read"
29
+ LLM_CALL = "llm_call"
30
+ TASK_POST = "task_post"
31
+ FEDERATION_EMIT = "fed_emit"
32
+
33
+
34
+ @dataclass
35
+ class SkillPermissions:
36
+ """Declared resource access for a skill."""
37
+ capabilities: List[SkillCapability] = field(default_factory=list)
38
+ allowed_domains: List[str] = field(default_factory=list)
39
+ allowed_read_paths: List[str] = field(default_factory=list)
40
+ allowed_write_paths: List[str] = field(default_factory=list)
41
+ allowed_env_vars: List[str] = field(default_factory=list)
42
+ allowed_imports: List[str] = field(default_factory=list)
43
+ max_duration_secs: int = 60
44
+ max_memory_mb: int = 256
45
+
46
+
47
+ @dataclass
48
+ class SkillManifest:
49
+ """Complete metadata for a Colony skill.
50
+
51
+ Stored as colony.skill.json alongside the skill source.
52
+ """
53
+ skill_id: str
54
+ name: str
55
+ version: str
56
+ description: str
57
+ author_colony_id: str
58
+ created_at: datetime
59
+ updated_at: datetime
60
+ status: SkillStatus = SkillStatus.DRAFT
61
+ entry_point: str = "skill:run"
62
+ permissions: SkillPermissions = field(default_factory=SkillPermissions)
63
+ tags: List[str] = field(default_factory=list)
64
+ dependencies: List[str] = field(default_factory=list)
65
+ input_schema: Dict[str, Any] = field(default_factory=dict)
66
+ output_schema: Dict[str, Any] = field(default_factory=dict)
67
+ checksum_sha256: str = ""
68
+ # Empty string means "no checksum recorded" (e.g. MCP-bridged or legacy skills).
69
+ # Any execution path that verifies integrity MUST reject an empty value — it is
70
+ # not a valid hash. See SkillExecutor._run_skill() which raises SecurityError
71
+ # when this field is falsy.
72
+ origin_task_id: Optional[str] = None
73
+ parent_skill_id: Optional[str] = None
74
+ trust_score: float = 0.0
75
+ execution_count: int = 0
76
+ last_executed_at: Optional[datetime] = None
77
+ signature: str = ""
78
+ skill_dir: Optional[str] = None
79
+ # MCP bridge fields — None for native skills
80
+ origin: Optional[str] = None # "mcp" for MCP-bridged tools
81
+ mcp_server: Optional[str] = None # server name (e.g. "github")
82
+ mcp_tool: Optional[str] = None # original tool name on the server
83
+
84
+ # Progressive loading additions
85
+ trigger_patterns: List[str] = field(default_factory=list)
86
+ context_tokens_estimate: int = 2048
87
+ lazy_loader: Optional[str] = None
88
+
89
+ def compute_checksum(self, source_code: str) -> str:
90
+ """Compute and store SHA-256 checksum of skill source."""
91
+ digest = hashlib.sha256(source_code.encode()).hexdigest()
92
+ self.checksum_sha256 = digest
93
+ return digest
94
+
95
+ def to_json(self) -> str:
96
+ """Serialize to JSON for storage as colony.skill.json."""
97
+ d = {
98
+ "skill_id": self.skill_id,
99
+ "name": self.name,
100
+ "version": self.version,
101
+ "description": self.description,
102
+ "author_colony_id": self.author_colony_id,
103
+ "created_at": self.created_at.isoformat(),
104
+ "updated_at": self.updated_at.isoformat(),
105
+ "status": self.status.value,
106
+ "entry_point": self.entry_point,
107
+ "permissions": {
108
+ "capabilities": [c.value for c in self.permissions.capabilities],
109
+ "allowed_domains": self.permissions.allowed_domains,
110
+ "allowed_read_paths": self.permissions.allowed_read_paths,
111
+ "allowed_write_paths": self.permissions.allowed_write_paths,
112
+ "allowed_env_vars": self.permissions.allowed_env_vars,
113
+ "max_duration_secs": self.permissions.max_duration_secs,
114
+ "max_memory_mb": self.permissions.max_memory_mb,
115
+ },
116
+ "tags": self.tags,
117
+ "dependencies": self.dependencies,
118
+ "input_schema": self.input_schema,
119
+ "output_schema": self.output_schema,
120
+ "checksum_sha256": self.checksum_sha256,
121
+ "origin_task_id": self.origin_task_id,
122
+ "parent_skill_id": self.parent_skill_id,
123
+ "trust_score": self.trust_score,
124
+ "execution_count": self.execution_count,
125
+ "signature": self.signature,
126
+ "trigger_patterns": self.trigger_patterns,
127
+ "context_tokens_estimate": self.context_tokens_estimate,
128
+ "lazy_loader": self.lazy_loader,
129
+ "origin": self.origin,
130
+ "mcp_server": self.mcp_server,
131
+ "mcp_tool": self.mcp_tool,
132
+ }
133
+ return json.dumps(d, indent=2, sort_keys=True)
134
+
135
+ @classmethod
136
+ def from_json(cls, raw: str) -> "SkillManifest":
137
+ """Deserialize from JSON."""
138
+ d = json.loads(raw)
139
+ perms_raw = d.get("permissions", {})
140
+ perms = SkillPermissions(
141
+ capabilities=[SkillCapability(c) for c in perms_raw.get("capabilities", [])],
142
+ allowed_domains=perms_raw.get("allowed_domains", []),
143
+ allowed_read_paths=perms_raw.get("allowed_read_paths", []),
144
+ allowed_write_paths=perms_raw.get("allowed_write_paths", []),
145
+ allowed_env_vars=perms_raw.get("allowed_env_vars", []),
146
+ max_duration_secs=perms_raw.get("max_duration_secs", 60),
147
+ max_memory_mb=perms_raw.get("max_memory_mb", 256),
148
+ )
149
+ return cls(
150
+ skill_id=d["skill_id"],
151
+ name=d["name"],
152
+ version=d["version"],
153
+ description=d["description"],
154
+ author_colony_id=d["author_colony_id"],
155
+ created_at=datetime.fromisoformat(d["created_at"]),
156
+ updated_at=datetime.fromisoformat(d["updated_at"]),
157
+ status=SkillStatus(d.get("status", "draft")),
158
+ entry_point=d.get("entry_point", "skill:run"),
159
+ permissions=perms,
160
+ tags=d.get("tags", []),
161
+ dependencies=d.get("dependencies", []),
162
+ input_schema=d.get("input_schema", {}),
163
+ output_schema=d.get("output_schema", {}),
164
+ checksum_sha256=d.get("checksum_sha256", ""),
165
+ origin_task_id=d.get("origin_task_id"),
166
+ parent_skill_id=d.get("parent_skill_id"),
167
+ trust_score=d.get("trust_score", 0.0),
168
+ execution_count=d.get("execution_count", 0),
169
+ signature=d.get("signature", ""),
170
+ trigger_patterns=d.get("trigger_patterns", []),
171
+ context_tokens_estimate=d.get("context_tokens_estimate", 2048),
172
+ lazy_loader=d.get("lazy_loader"),
173
+ origin=d.get("origin"),
174
+ mcp_server=d.get("mcp_server"),
175
+ mcp_tool=d.get("mcp_tool"),
176
+ )
177
+
178
+
179
+ @dataclass
180
+ class TaskSolution:
181
+ """A completed task solution that may be captured as a skill."""
182
+ task_id: str
183
+ task_description: str
184
+ inputs: Dict[str, Any]
185
+ output: Any
186
+ trace: List[Dict[str, Any]]
187
+ dependencies: List[str]
188
+ embedding: Optional[List[float]]
189
+ step_fingerprint: Optional[List[str]]
190
+ duration_secs: float
191
+ completed_at: datetime
192
+
193
+
194
+ @dataclass
195
+ class SkillSummary:
196
+ """Lightweight skill summary for novelty scoring and search."""
197
+ skill_id: str
198
+ description: str
199
+ tags: List[str]
200
+ dependencies: List[str]
201
+ embedding: Optional[List[float]] = None
202
+ step_fingerprint: Optional[List[str]] = None
@@ -0,0 +1,128 @@
1
+ """Colony Skills — packaging extracted patterns as skill directories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import pathlib
7
+ import re
8
+ import uuid
9
+ from datetime import datetime, timezone
10
+ from typing import Optional
11
+
12
+ from apsimo.skills.learning.pattern_extractor import ExtractedPattern
13
+ from apsimo.skills.learning.triggers import TriggerSource
14
+ from apsimo.skills.models import SkillCapability, SkillManifest, SkillPermissions, SkillStatus
15
+ from apsimo.skills.registry import SkillRegistry
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _SLUG_RE = re.compile(r"[^a-z0-9-]")
20
+ _SKILL_LIBRARY_PATH = pathlib.Path("colony/skills/library")
21
+
22
+
23
+ class SkillPackager:
24
+ """Packages an ExtractedPattern into a skill directory and registers it."""
25
+
26
+ def __init__(
27
+ self,
28
+ registry: SkillRegistry,
29
+ colony_id: str,
30
+ library_root: pathlib.Path = _SKILL_LIBRARY_PATH,
31
+ ) -> None:
32
+ self._registry = registry
33
+ self._colony_id = colony_id
34
+ self._library = library_root
35
+
36
+ async def package(
37
+ self,
38
+ solution: "TaskSolution", # noqa: F821
39
+ pattern: ExtractedPattern,
40
+ source: TriggerSource,
41
+ ) -> str:
42
+ """Create a skill directory from an extracted pattern.
43
+
44
+ Returns:
45
+ The new skill_id string.
46
+ """
47
+ now = datetime.now(timezone.utc)
48
+ slug = self._slugify(solution.task_description[:48])
49
+ uid = uuid.uuid4().hex[:8]
50
+ skill_id = f"{slug}_{uid}"
51
+ version = "1.0.0"
52
+ skill_dir = self._library / f"{skill_id}_v{version}"
53
+ skill_dir.mkdir(parents=True, exist_ok=True)
54
+
55
+ permissions = self._build_permissions(pattern)
56
+ manifest = SkillManifest(
57
+ skill_id=skill_id,
58
+ name=slug.replace("-", " ").title(),
59
+ version=version,
60
+ description=pattern.docstring.split("\n")[0],
61
+ author_colony_id=self._colony_id,
62
+ created_at=now,
63
+ updated_at=now,
64
+ status=SkillStatus.DRAFT,
65
+ entry_point="skill:run",
66
+ permissions=permissions,
67
+ tags=pattern.tags,
68
+ dependencies=pattern.dependencies,
69
+ input_schema=pattern.input_schema,
70
+ output_schema=pattern.output_schema,
71
+ origin_task_id=solution.task_id,
72
+ skill_dir=str(skill_dir),
73
+ )
74
+ manifest.compute_checksum(pattern.source_code)
75
+
76
+ (skill_dir / "skill.py").write_text(pattern.source_code, encoding="utf-8")
77
+ (skill_dir / "colony.skill.json").write_text(manifest.to_json(), encoding="utf-8")
78
+ (skill_dir / "requirements.txt").write_text(
79
+ "\n".join(pattern.dependencies), encoding="utf-8"
80
+ )
81
+ self._write_smoke_test(skill_dir, slug, pattern)
82
+
83
+ await self._registry.register(manifest, skill_dir)
84
+ logger.info("Packaged skill %s → %s", skill_id, skill_dir)
85
+ return skill_id
86
+
87
+ def _build_permissions(self, pattern: ExtractedPattern) -> SkillPermissions:
88
+ caps: list[SkillCapability] = []
89
+ if pattern.network_domains:
90
+ caps.append(SkillCapability.NETWORK)
91
+ if pattern.file_paths:
92
+ caps.append(SkillCapability.FILESYSTEM_READ)
93
+ if pattern.env_vars:
94
+ caps.append(SkillCapability.ENV_READ)
95
+ return SkillPermissions(
96
+ capabilities=caps,
97
+ allowed_domains=pattern.network_domains,
98
+ allowed_read_paths=pattern.file_paths,
99
+ allowed_env_vars=list(pattern.env_vars),
100
+ )
101
+
102
+ @staticmethod
103
+ def _slugify(text: str) -> str:
104
+ return _SLUG_RE.sub("-", text.lower().strip()).strip("-")[:48]
105
+
106
+ @staticmethod
107
+ def _write_smoke_test(
108
+ skill_dir: pathlib.Path, slug: str, pattern: ExtractedPattern
109
+ ) -> None:
110
+ fn_name = slug.replace("-", "_")
111
+ test_code = f"""\
112
+ import pytest
113
+
114
+ @pytest.mark.asyncio
115
+ async def test_{fn_name}_runs():
116
+ # Smoke test: verify skill file exists and run() is callable.
117
+ import importlib.util
118
+ import pathlib
119
+ skill_path = pathlib.Path("{skill_dir}") / "skill.py"
120
+ if skill_path.exists():
121
+ spec = importlib.util.spec_from_file_location("_skill", skill_path)
122
+ module = importlib.util.module_from_spec(spec)
123
+ spec.loader.exec_module(module)
124
+ assert callable(getattr(module, "run", None))
125
+ """
126
+ tests_dir = skill_dir / "tests"
127
+ tests_dir.mkdir(exist_ok=True)
128
+ (tests_dir / "test_skill.py").write_text(test_code, encoding="utf-8")
@@ -0,0 +1,70 @@
1
+ """Colony Skills — typed Protocol interfaces for dependency injection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
6
+
7
+ from apsimo.skills.models import SkillManifest, SkillStatus
8
+ from apsimo.skills.learning.novelty_detector import NoveltyResult
9
+ from apsimo.skills.learning.pattern_extractor import ExtractedPattern
10
+ from apsimo.skills.security.scanner import ASTScanResult
11
+ from apsimo.skills.executor import ExecutionResult
12
+
13
+
14
+ @runtime_checkable
15
+ class ISkillRegistry(Protocol):
16
+ """Read/write interface to the skill registry."""
17
+
18
+ async def register(self, manifest: SkillManifest, skill_dir: Any) -> None: ...
19
+ async def get(self, skill_id: str) -> Optional[SkillManifest]: ...
20
+ async def search(
21
+ self,
22
+ query: str = "",
23
+ tags: Optional[List[str]] = None,
24
+ status: Optional[SkillStatus] = None,
25
+ limit: int = 50,
26
+ ) -> List[SkillManifest]: ...
27
+ async def activate(self, skill_id: str) -> None: ...
28
+ async def deactivate(self, skill_id: str) -> None: ...
29
+ async def quarantine(self, skill_id: str, reason: str) -> None: ...
30
+ async def list_summaries(self) -> List[Any]: ...
31
+
32
+
33
+ @runtime_checkable
34
+ class INoveltyDetector(Protocol):
35
+ """Scores how novel a task solution is relative to the skill registry."""
36
+
37
+ async def score(self, solution: Any) -> NoveltyResult: ...
38
+
39
+
40
+ @runtime_checkable
41
+ class IPatternExtractor(Protocol):
42
+ """Extracts a generalizable pattern from a task solution."""
43
+
44
+ def extract(self, solution: Any) -> ExtractedPattern: ...
45
+
46
+
47
+ @runtime_checkable
48
+ class ISkillPackager(Protocol):
49
+ """Packages an extracted pattern as a skill directory."""
50
+
51
+ async def package(self, solution: Any, pattern: ExtractedPattern, source: Any) -> str: ...
52
+
53
+
54
+ @runtime_checkable
55
+ class IASTScanner(Protocol):
56
+ """Statically analyzes skill Python source for security issues."""
57
+
58
+ def scan(self, source: str, skill_id: str) -> ASTScanResult: ...
59
+
60
+
61
+ @runtime_checkable
62
+ class ISkillExecutor(Protocol):
63
+ """Executes a skill by ID inside a capability-gated sandbox."""
64
+
65
+ async def invoke(
66
+ self,
67
+ skill_id: str,
68
+ inputs: Dict[str, Any],
69
+ caller_context: Optional[str] = None,
70
+ ) -> ExecutionResult: ...