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,159 @@
1
+ """Colony Vector — image content classifier.
2
+
3
+ Validates images before embedding: format, dimensions, and optional
4
+ content classification.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ImageCheckLevel(str, Enum):
18
+ """Image validation strictness."""
19
+
20
+ OFF = "off" # No checks
21
+ BASIC = "basic" # Format, dimension, and size validation only
22
+ STRICT = "strict" # Format + dimensions + content classification
23
+
24
+
25
+ @dataclass
26
+ class ImageCheckResult:
27
+ """Result of an image content check."""
28
+
29
+ safe: bool = True
30
+ level: ImageCheckLevel = ImageCheckLevel.BASIC
31
+ errors: list[str] = field(default_factory=list)
32
+ warnings: list[str] = field(default_factory=list)
33
+ classification: Optional[dict[str, float]] = None # content scores for strict mode
34
+
35
+ @property
36
+ def reason(self) -> str:
37
+ return "; ".join(self.errors) if self.errors else ""
38
+
39
+
40
+ async def check_image(
41
+ data: bytes,
42
+ mime_type: str = "",
43
+ width: int = 0,
44
+ height: int = 0,
45
+ level: ImageCheckLevel = ImageCheckLevel.BASIC,
46
+ ) -> ImageCheckResult:
47
+ """Check image content.
48
+
49
+ Parameters
50
+ ----------
51
+ data : bytes
52
+ Raw image bytes.
53
+ mime_type : str
54
+ Detected or provided MIME type.
55
+ width, height : int
56
+ Image dimensions (0 if unknown).
57
+ level : ImageCheckLevel
58
+ How strict to check.
59
+
60
+ Returns
61
+ -------
62
+ ImageCheckResult
63
+ """
64
+ result = ImageCheckResult(level=level)
65
+
66
+ if level == ImageCheckLevel.OFF:
67
+ return result
68
+
69
+ # --- Basic checks ---
70
+
71
+ # Format
72
+ valid_mimes = {"image/jpeg", "image/png", "image/webp", "image/gif"}
73
+ if mime_type and mime_type not in valid_mimes:
74
+ result.errors.append(f"Unsupported image format: {mime_type}")
75
+ result.safe = False
76
+
77
+ # Size
78
+ max_size = 20 * 1024 * 1024 # 20 MB
79
+ if len(data) > max_size:
80
+ result.errors.append(f"Image size {len(data)} exceeds 20MB limit")
81
+ result.safe = False
82
+
83
+ if len(data) == 0:
84
+ result.errors.append("Empty image data")
85
+ result.safe = False
86
+
87
+ # Dimensions (if known)
88
+ max_dim = 4096
89
+ if width > max_dim or height > max_dim:
90
+ result.warnings.append(f"Image dimensions {width}x{height} exceed {max_dim}px — will be resized")
91
+
92
+ # Minimum dimensions (avoid 1px images, etc.)
93
+ if width > 0 and height > 0 and (width < 16 or height < 16):
94
+ result.warnings.append(f"Very small image: {width}x{height}px")
95
+
96
+ if level == ImageCheckLevel.BASIC:
97
+ return result
98
+
99
+ # --- Strict checks (content classification) ---
100
+
101
+ try:
102
+ classification = await _classify_content(data, mime_type)
103
+ result.classification = classification
104
+
105
+ # Flag content with high explicit scores
106
+ explicit_score = classification.get("explicit", 0.0)
107
+ if explicit_score > 0.8:
108
+ result.errors.append(f"Image classified as explicit content (score: {explicit_score:.2f})")
109
+ result.safe = False
110
+ elif explicit_score > 0.5:
111
+ result.warnings.append(f"Image may contain explicit content (score: {explicit_score:.2f})")
112
+
113
+ except Exception as exc:
114
+ logger.warning("Content classification failed: %s — allowing image (basic checks passed)", exc)
115
+ result.warnings.append("Content classification unavailable")
116
+
117
+ return result
118
+
119
+
120
+ async def _classify_content(data: bytes, mime_type: str) -> dict[str, float]:
121
+ """Classify image content.
122
+
123
+ Tries a local content classifier (falconsai/nsfw_image_detection via
124
+ transformers). Falls back to safe scores if the model isn't available.
125
+ """
126
+ try:
127
+ import asyncio
128
+ from PIL import Image as PILImage
129
+ import io
130
+
131
+ img = PILImage.open(io.BytesIO(data))
132
+ if img.mode in ("RGBA", "P"):
133
+ img = img.convert("RGB")
134
+
135
+ def _run_classifier():
136
+ from transformers import pipeline as hf_pipeline
137
+ classifier = hf_pipeline("image-classification", model="falconsai/nsfw_image_detection", device=-1)
138
+ return classifier(img)
139
+
140
+ results = await asyncio.get_running_loop().run_in_executor(None, _run_classifier)
141
+
142
+ # Parse results into score dict
143
+ scores = {"explicit": 0.0, "safe": 1.0}
144
+ for r in results:
145
+ label = r.get("label", "").lower()
146
+ score = r.get("score", 0.0)
147
+ if "nsfw" in label or "explicit" in label:
148
+ scores["explicit"] = score
149
+ if "safe" in label or "normal" in label:
150
+ scores["safe"] = score
151
+
152
+ return scores
153
+
154
+ except ImportError:
155
+ logger.debug("transformers not available for image classification — returning safe defaults")
156
+ return {"explicit": 0.0, "safe": 1.0}
157
+ except Exception as exc:
158
+ logger.debug("Image classification failed: %s — returning safe defaults", exc)
159
+ return {"explicit": 0.0, "safe": 1.0}
@@ -0,0 +1,197 @@
1
+ """System scanner — detect GPU, VRAM, RAM to select embedding tier.
2
+
3
+ Scans the host hardware and returns a ``HardwareProfile`` that the
4
+ tier selector uses to pick the right models.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import os
11
+ import platform
12
+ import shutil
13
+ import subprocess
14
+ from dataclasses import dataclass
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class HardwareProfile:
21
+ """Detected hardware capabilities."""
22
+
23
+ gpu_type: str # "cuda", "mlx", "none"
24
+ gpu_name: str # e.g. "NVIDIA GB10"
25
+ vram_gb: int # GPU memory in GB (0 if no GPU)
26
+ ram_gb: int # System RAM in GB
27
+ cuda_version: str | None = None
28
+ os: str = "" # "linux", "darwin", "windows"
29
+
30
+
31
+ def scan() -> HardwareProfile:
32
+ """Scan the system and return a HardwareProfile."""
33
+ gpu_type = "none"
34
+ gpu_name = ""
35
+ vram_gb = 0
36
+ cuda_version = None
37
+
38
+ # Detect CUDA
39
+ if _has_cuda():
40
+ gpu_type = "cuda"
41
+ gpu_name, vram_gb, cuda_version = _cuda_info()
42
+
43
+ # Detect MLX (Apple Silicon)
44
+ if gpu_type == "none" and _has_mlx():
45
+ gpu_type = "mlx"
46
+ gpu_name, vram_gb = _mlx_info()
47
+
48
+ ram_gb = _ram_gb()
49
+ system = platform.system().lower()
50
+
51
+ profile = HardwareProfile(
52
+ gpu_type=gpu_type,
53
+ gpu_name=gpu_name,
54
+ vram_gb=vram_gb,
55
+ ram_gb=ram_gb,
56
+ cuda_version=cuda_version,
57
+ os=system,
58
+ )
59
+
60
+ logger.info(
61
+ "Hardware scan: gpu=%s (%s, %dGB VRAM), ram=%dGB, cuda=%s, os=%s",
62
+ profile.gpu_type, profile.gpu_name, profile.vram_gb,
63
+ profile.ram_gb, profile.cuda_version, profile.os,
64
+ )
65
+
66
+ return profile
67
+
68
+
69
+ def _has_cuda() -> bool:
70
+ """Check if CUDA is available."""
71
+ try:
72
+ result = subprocess.run(
73
+ ["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
74
+ capture_output=True, text=True, timeout=5,
75
+ )
76
+ return result.returncode == 0
77
+ except Exception:
78
+ return False
79
+
80
+
81
+ def _cuda_info() -> tuple[str, int, str | None]:
82
+ """Get CUDA GPU name, VRAM, and driver version.
83
+
84
+ Handles unified memory GPUs (Grace Blackwell, Grace Hopper, Tegra)
85
+ where nvidia-smi reports [N/A] for memory.total. On these chips
86
+ the GPU uses system RAM, so we fall back to total system RAM.
87
+ """
88
+ name = "Unknown NVIDIA GPU"
89
+ vram = 0
90
+ cuda_ver = None
91
+
92
+ try:
93
+ result = subprocess.run(
94
+ ["nvidia-smi",
95
+ "--query-gpu=name,memory.total",
96
+ "--format=csv,noheader,nounits"],
97
+ capture_output=True, text=True, timeout=5,
98
+ )
99
+ if result.returncode == 0:
100
+ parts = result.stdout.strip().split(",")
101
+ if len(parts) >= 2:
102
+ name = parts[0].strip()
103
+ mem_str = parts[1].strip()
104
+ # Handle [N/A] for unified memory GPUs
105
+ if mem_str.lower() in ("[n/a]", "n/a", ""):
106
+ vram = _ram_gb() # Unified memory: GPU RAM == system RAM
107
+ else:
108
+ vram = int(float(mem_str))
109
+ except Exception:
110
+ pass
111
+
112
+ try:
113
+ result = subprocess.run(
114
+ ["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"],
115
+ capture_output=True, text=True, timeout=5,
116
+ )
117
+ if result.returncode == 0:
118
+ cuda_ver = result.stdout.strip()
119
+ except Exception:
120
+ pass
121
+
122
+ return name, vram, cuda_ver
123
+
124
+
125
+ def _has_mlx() -> bool:
126
+ """Check if Apple MLX is available (M-series chip)."""
127
+ if platform.system() != "Darwin":
128
+ return False
129
+ try:
130
+ # Check for Apple Silicon
131
+ result = subprocess.run(
132
+ ["sysctl", "-n", "machdep.cpu.brand_string"],
133
+ capture_output=True, text=True, timeout=5,
134
+ )
135
+ if result.returncode == 0 and "Apple" in result.stdout:
136
+ return True
137
+ except Exception:
138
+ pass
139
+ return False
140
+
141
+
142
+ def _mlx_info() -> tuple[str, int]:
143
+ """Get Apple Silicon GPU name and unified memory."""
144
+ name = "Apple Silicon"
145
+ vram = 0
146
+
147
+ try:
148
+ result = subprocess.run(
149
+ ["sysctl", "-n", "machdep.cpu.brand_string"],
150
+ capture_output=True, text=True, timeout=5,
151
+ )
152
+ if result.returncode == 0:
153
+ name = result.stdout.strip()
154
+ except Exception:
155
+ pass
156
+
157
+ # On Apple Silicon, VRAM == RAM (unified memory)
158
+ vram = _ram_gb()
159
+
160
+ return name, vram
161
+
162
+
163
+ def _ram_gb() -> int:
164
+ """Get system RAM in GB."""
165
+ system = platform.system().lower()
166
+
167
+ try:
168
+ if system == "linux":
169
+ result = subprocess.run(
170
+ ["free", "-g", "--si"],
171
+ capture_output=True, text=True, timeout=5,
172
+ )
173
+ if result.returncode == 0:
174
+ for line in result.stdout.splitlines():
175
+ if line.startswith("Mem:"):
176
+ return int(line.split()[1])
177
+ elif system == "darwin":
178
+ result = subprocess.run(
179
+ ["sysctl", "-n", "hw.memsize"],
180
+ capture_output=True, text=True, timeout=5,
181
+ )
182
+ if result.returncode == 0:
183
+ return int(result.stdout.strip()) // (1024 ** 3)
184
+ except Exception:
185
+ pass
186
+
187
+ # Fallback: parse /proc/meminfo
188
+ try:
189
+ with open("/proc/meminfo") as f:
190
+ for line in f:
191
+ if line.startswith("MemTotal:"):
192
+ kb = int(line.split()[1])
193
+ return kb // (1024 * 1024) + 1
194
+ except Exception:
195
+ pass
196
+
197
+ return 8 # Safe default
apsimo/vector/setup.py ADDED
@@ -0,0 +1,289 @@
1
+ """Colony Vector Store — hardware detection, config recommendation, and CLI.
2
+
3
+ Provides:
4
+ - detect_hardware() — probe CUDA, MLX, CPU resources
5
+ - recommend_config() — select best EmbeddingConfig for detected hardware
6
+ - ensure_embed_config() — resolve config from env or auto-detect
7
+ - ensure_model_ready() — download model weights if missing
8
+ - CLI entry point for ``colony vector setup`` and ``colony vector rebuild``
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import os
15
+ import sys
16
+ from typing import Optional
17
+
18
+ from apsimo.vector.config import EmbeddingConfig, HardwareProfile, VectorStoreConfig
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Hardware detection
25
+ # ---------------------------------------------------------------------------
26
+
27
+ def detect_hardware() -> HardwareProfile:
28
+ """Probe available compute resources. Returns a structured profile."""
29
+ profile = HardwareProfile()
30
+
31
+ # CUDA
32
+ try:
33
+ import torch
34
+ if torch.cuda.is_available():
35
+ profile.cuda_available = True
36
+ profile.cuda_device_count = torch.cuda.device_count()
37
+ profile.cuda_vram_gb = sum(
38
+ torch.cuda.get_device_properties(i).total_memory
39
+ for i in range(profile.cuda_device_count)
40
+ ) / 1e9
41
+ except ImportError:
42
+ pass
43
+
44
+ # Apple Silicon (MLX)
45
+ try:
46
+ import mlx.core as mx # noqa: F401
47
+ profile.mlx_available = True
48
+ profile.mlx_memory_gb = _probe_mlx_memory()
49
+ except ImportError:
50
+ pass
51
+
52
+ # CPU fallback — always available
53
+ try:
54
+ import psutil
55
+ profile.cpu_ram_gb = psutil.virtual_memory().total / 1e9
56
+ profile.cpu_cores = psutil.cpu_count(logical=False) or 1
57
+ except ImportError:
58
+ import multiprocessing
59
+ profile.cpu_cores = multiprocessing.cpu_count() or 1
60
+
61
+ return profile
62
+
63
+
64
+ def _probe_mlx_memory() -> float:
65
+ """Attempt to detect unified memory on Apple Silicon."""
66
+ try:
67
+ import subprocess
68
+ result = subprocess.run(
69
+ ["sysctl", "-n", "hw.memsize"],
70
+ capture_output=True, text=True, timeout=5,
71
+ )
72
+ if result.returncode == 0:
73
+ return int(result.stdout.strip()) / 1e9
74
+ except Exception:
75
+ pass
76
+ return 0.0
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Model recommendation
81
+ # ---------------------------------------------------------------------------
82
+
83
+ # Tiered recommendation matrix from spec §4.4.
84
+ # Each entry: (provider, model_id, dimensions, quantization)
85
+ _TIER_MATRIX = {
86
+ "gpu-high": ("cuda", "Qwen/Qwen3-Embedding-8B", 4096, "fp8"),
87
+ "gpu-balanced": ("cuda", "BAAI/bge-m3", 1024, None),
88
+ "gpu-fast": ("cuda", "nomic-ai/nomic-embed-text-v1.5", 768, None),
89
+ "native-mlx-high": ("native_mlx", "Qwen/Qwen3-Embedding-8B", 4096, None),
90
+ "native-mlx-balanced": ("native_mlx", "BAAI/bge-m3", 1024, None),
91
+ "mlx": ("mlx", "nomic-ai/nomic-embed-text-v1.5", 768, None),
92
+ "cpu-quality": ("cpu", "nomic-ai/nomic-embed-text-v1.5", 768, "int8"),
93
+ "cpu-lightweight": ("cpu", "BAAI/bge-small-en-v1.5", 384, None),
94
+ }
95
+
96
+
97
+ def _detect_tier(profile: HardwareProfile) -> str:
98
+ """Select the best tier for the given hardware profile."""
99
+ if profile.cuda_available:
100
+ if profile.cuda_vram_gb >= 24:
101
+ return "gpu-high"
102
+ if profile.cuda_vram_gb >= 8:
103
+ return "gpu-balanced"
104
+ return "gpu-fast"
105
+ if profile.mlx_available:
106
+ # Use high tier for machines with 64GB+ unified memory
107
+ if profile.mlx_memory_gb >= 64:
108
+ return "native-mlx-high"
109
+ if profile.mlx_memory_gb >= 32:
110
+ return "native-mlx-balanced"
111
+ return "mlx"
112
+ if profile.cpu_ram_gb >= 16:
113
+ return "cpu-quality"
114
+ return "cpu-lightweight"
115
+
116
+
117
+ def recommend_config(profile: HardwareProfile) -> EmbeddingConfig:
118
+ """Return the best EmbeddingConfig for the detected hardware profile."""
119
+ tier = _detect_tier(profile)
120
+ provider, model_id, dims, quant = _TIER_MATRIX[tier]
121
+ return EmbeddingConfig(
122
+ provider=provider,
123
+ model_id=model_id,
124
+ dimensions=dims,
125
+ quantization=quant,
126
+ )
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Config resolution
131
+ # ---------------------------------------------------------------------------
132
+
133
+ async def ensure_embed_config() -> Optional[EmbeddingConfig]:
134
+ """Resolve embedding config from env vars or auto-detect.
135
+
136
+ Returns ``None`` if no config is set and the process is non-interactive,
137
+ allowing Colony to start without embeddings (graceful degradation).
138
+ """
139
+ config = EmbeddingConfig.from_env()
140
+ if config is not None:
141
+ return config
142
+
143
+ # Auto-detect hardware and recommend
144
+ profile = detect_hardware()
145
+ recommended = recommend_config(profile)
146
+
147
+ # Non-interactive: degrade gracefully
148
+ if not sys.stdin.isatty():
149
+ logger.warning(
150
+ "Vector search disabled — no COLONY_EMBED_MODEL configured. "
151
+ "Run 'colony vector setup' to enable. "
152
+ "Recommended: COLONY_EMBED_MODEL=%s COLONY_EMBED_DIMS=%d COLONY_EMBED_PROVIDER=%s",
153
+ recommended.model_id,
154
+ recommended.dimensions,
155
+ recommended.provider,
156
+ )
157
+ return None
158
+
159
+ # Interactive: print recommendation
160
+ print("\n--- Colony Vector Store Setup ---")
161
+ print(f"Detected hardware: {profile}")
162
+ print(f"Recommended model: {recommended.model_id}")
163
+ print(f"Provider: {recommended.provider}, Dimensions: {recommended.dimensions}")
164
+ print("\nSet these environment variables to enable vector search:")
165
+ print(f" COLONY_EMBED_PROVIDER={recommended.provider}")
166
+ print(f" COLONY_EMBED_MODEL={recommended.model_id}")
167
+ print(f" COLONY_EMBED_DIMS={recommended.dimensions}")
168
+ if recommended.quantization:
169
+ print(f" COLONY_EMBED_QUANTIZATION={recommended.quantization}")
170
+ print()
171
+ return None
172
+
173
+
174
+ async def ensure_model_ready(config: EmbeddingConfig) -> None:
175
+ """Download model weights if not present in cache_dir. Run warmup."""
176
+ try:
177
+ from huggingface_hub import snapshot_download
178
+
179
+ cache_dir = config.cache_dir or None
180
+ snapshot_download(config.model_id, cache_dir=cache_dir)
181
+ logger.info("Model %s ready (cache=%s)", config.model_id, cache_dir or "default")
182
+ except ImportError:
183
+ logger.warning("huggingface_hub not installed — skipping model download check")
184
+ except Exception as exc:
185
+ logger.warning("Model download check failed (may already be cached): %s", exc)
186
+
187
+
188
+ # ---------------------------------------------------------------------------
189
+ # CLI entry points
190
+ # ---------------------------------------------------------------------------
191
+
192
+ def cli_setup(args: list[str] | None = None) -> None:
193
+ """CLI handler for ``colony vector setup``."""
194
+ import argparse
195
+
196
+ parser = argparse.ArgumentParser(description="Colony vector store setup")
197
+ parser.add_argument("--model", help="HuggingFace model ID to use")
198
+ parser.add_argument(
199
+ "--tier",
200
+ choices=list(_TIER_MATRIX.keys()),
201
+ help="Hardware tier (auto-detected if not specified)",
202
+ )
203
+ parsed = parser.parse_args(args if args is not None else [])
204
+
205
+ profile = detect_hardware()
206
+ print(f"\nHardware profile:")
207
+ print(f" CUDA: {'Yes' if profile.cuda_available else 'No'}"
208
+ + (f" ({profile.cuda_device_count} device(s), {profile.cuda_vram_gb:.1f}GB VRAM)" if profile.cuda_available else ""))
209
+ print(f" MLX: {'Yes' if profile.mlx_available else 'No'}"
210
+ + (f" ({profile.mlx_memory_gb:.1f}GB unified)" if profile.mlx_available else ""))
211
+ print(f" CPU: {profile.cpu_cores} cores, {profile.cpu_ram_gb:.1f}GB RAM")
212
+
213
+ if parsed.tier:
214
+ tier = parsed.tier
215
+ else:
216
+ tier = _detect_tier(profile)
217
+ print(f"\nSelected tier: {tier}")
218
+
219
+ if parsed.model:
220
+ provider, _, dims, quant = _TIER_MATRIX[tier]
221
+ config = EmbeddingConfig(
222
+ provider=provider,
223
+ model_id=parsed.model,
224
+ dimensions=dims,
225
+ quantization=quant,
226
+ )
227
+ else:
228
+ config = recommend_config(profile)
229
+
230
+ print(f"Model: {config.model_id}")
231
+ print(f"Provider: {config.provider}, Dimensions: {config.dimensions}")
232
+ if config.quantization:
233
+ print(f"Quantization: {config.quantization}")
234
+
235
+ print("\nAdd to your .env:")
236
+ print(f" COLONY_EMBED_PROVIDER={config.provider}")
237
+ print(f" COLONY_EMBED_MODEL={config.model_id}")
238
+ print(f" COLONY_EMBED_DIMS={config.dimensions}")
239
+ if config.quantization:
240
+ print(f" COLONY_EMBED_QUANTIZATION={config.quantization}")
241
+
242
+ # Download model
243
+ try:
244
+ print(f"\nDownloading model weights for {config.model_id}...")
245
+ from huggingface_hub import snapshot_download
246
+ cache_dir = config.cache_dir or None
247
+ snapshot_download(config.model_id, cache_dir=cache_dir)
248
+ print("Model downloaded successfully.")
249
+ except ImportError:
250
+ print("huggingface_hub not installed — install with: pip install huggingface-hub")
251
+ except Exception as exc:
252
+ print(f"Download failed (model may already be cached): {exc}")
253
+
254
+
255
+ def cli_rebuild(args: list[str] | None = None) -> None:
256
+ """CLI handler for ``colony vector rebuild``."""
257
+ import argparse
258
+
259
+ parser = argparse.ArgumentParser(description="Rebuild vector collections")
260
+ parser.add_argument(
261
+ "--collection",
262
+ default="all",
263
+ help="Collection to rebuild (default: all)",
264
+ )
265
+ parsed = parser.parse_args(args if args is not None else [])
266
+
267
+ print(f"Rebuilding vector collection(s): {parsed.collection}")
268
+ print("NOTE: Vector search requires COLONY_EMBED_MODEL to be set.")
269
+ print(" Run 'colony vector setup' first, then use scripts/vector_backfill.py")
270
+ print(" for the full backfill process.")
271
+
272
+
273
+ def cli_main() -> None:
274
+ """Entry point for ``colony vector <subcommand>``."""
275
+ if len(sys.argv) < 3:
276
+ print("Usage: colony vector {setup|rebuild}")
277
+ sys.exit(1)
278
+
279
+ subcommand = sys.argv[2]
280
+ remaining = sys.argv[3:]
281
+
282
+ if subcommand == "setup":
283
+ cli_setup(remaining)
284
+ elif subcommand == "rebuild":
285
+ cli_rebuild(remaining)
286
+ else:
287
+ print(f"Unknown subcommand: {subcommand}")
288
+ print("Usage: colony vector {setup|rebuild}")
289
+ sys.exit(1)