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