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