99use std:: collections:: HashMap ;
1010use std:: ops:: Deref ;
1111use std:: path:: PathBuf ;
12+ use std:: sync:: OnceLock ;
1213
14+ use hyperactor:: Instance ;
15+ use hyperactor:: Proc ;
1316use hyperactor_mesh:: bootstrap:: BootstrapCommand ;
17+ use hyperactor_mesh:: bootstrap:: host;
18+ use hyperactor_mesh:: proc_mesh:: default_transport;
19+ use hyperactor_mesh:: proc_mesh:: mesh_agent:: GetProcClient ;
1420use hyperactor_mesh:: shared_cell:: SharedCell ;
21+ use hyperactor_mesh:: v1:: ProcMeshRef ;
1522use hyperactor_mesh:: v1:: host_mesh:: HostMesh ;
1623use hyperactor_mesh:: v1:: host_mesh:: HostMeshRef ;
24+ use hyperactor_mesh:: v1:: host_mesh:: mesh_agent:: GetLocalProcClient ;
25+ use hyperactor_mesh:: v1:: proc_mesh:: ProcRef ;
1726use ndslice:: View ;
1827use ndslice:: view:: RankedSliceable ;
1928use pyo3:: IntoPyObjectExt ;
@@ -24,6 +33,7 @@ use pyo3::prelude::*;
2433use pyo3:: types:: PyBytes ;
2534use pyo3:: types:: PyType ;
2635
36+ use crate :: actor:: PythonActor ;
2737use crate :: actor:: to_py_error;
2838use crate :: alloc:: PyAlloc ;
2939use crate :: context:: PyInstance ;
@@ -240,6 +250,76 @@ impl PyHostMeshRefImpl {
240250 }
241251}
242252
253+ /// Static storage for the root client instance when using host-based bootstrap.
254+ static ROOT_CLIENT_INSTANCE_FOR_HOST : OnceLock < Instance < PythonActor > > = OnceLock :: new ( ) ;
255+
256+ /// Bootstrap the client host and root client actor.
257+ ///
258+ /// This creates a proper Host with BootstrapProcManager, spawns the root client
259+ /// actor on the Host's local_proc.
260+ ///
261+ /// Returns a tuple of (HostMesh, ProcMesh, PyInstance) where:
262+ /// - PyHostMesh: the bootstrapped (local) host mesh; and
263+ /// - PyProcMesh: the local ProcMesh on this HostMesh; and
264+ /// - PyInstance: the root client actor instance, on the ProcMesh.
265+ ///
266+ /// The HostMesh is served on the default transport.
267+ ///
268+ /// This should be called only once, at process initialization
269+ #[ pyfunction]
270+ fn bootstrap_host ( bootstrap_cmd : Option < PyBootstrapCommand > ) -> PyResult < PyPythonTask > {
271+ let bootstrap_cmd = match bootstrap_cmd {
272+ Some ( cmd) => cmd. to_rust ( ) ,
273+ None => BootstrapCommand :: current ( ) . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?,
274+ } ;
275+
276+ PyPythonTask :: new ( async move {
277+ let host_mesh_agent = host ( default_transport ( ) . any ( ) , Some ( bootstrap_cmd) , None )
278+ . await
279+ . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?;
280+
281+ let host_mesh_name = hyperactor_mesh:: v1:: Name :: new_reserved ( "local" ) . unwrap ( ) ;
282+ let host_mesh = HostMeshRef :: from_host_agents ( host_mesh_name, vec ! [ host_mesh_agent. bind( ) ] )
283+ . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?;
284+
285+ // We require a temporary instance to make a call to the host/proc agent.
286+ let temp_proc = Proc :: local ( ) ;
287+ let ( temp_instance, _) = temp_proc
288+ . instance ( "temp" )
289+ . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?;
290+
291+ let local_proc_agent = host_mesh_agent
292+ . get_local_proc ( & temp_instance)
293+ . await
294+ . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?;
295+
296+ let proc_mesh_name = hyperactor_mesh:: v1:: Name :: new_reserved ( "local" ) . unwrap ( ) ;
297+ let proc_mesh = ProcMeshRef :: new_singleton (
298+ proc_mesh_name,
299+ ProcRef :: new (
300+ local_proc_agent. actor_id ( ) . proc_id ( ) . clone ( ) ,
301+ 0 ,
302+ local_proc_agent. bind ( ) ,
303+ ) ,
304+ ) ;
305+
306+ let local_proc = local_proc_agent
307+ . get_proc ( & temp_instance)
308+ . await
309+ . map_err ( |e| PyException :: new_err ( e. to_string ( ) ) ) ?;
310+
311+ let ( instance, _handle) = Python :: with_gil ( |py| {
312+ PythonActor :: bootstrap_client_inner ( py, local_proc, & ROOT_CLIENT_INSTANCE_FOR_HOST )
313+ } ) ;
314+
315+ Ok ( (
316+ PyHostMesh :: new_ref ( host_mesh) ,
317+ PyProcMesh :: new_ref ( proc_mesh) ,
318+ PyInstance :: from ( instance) ,
319+ ) )
320+ } )
321+ }
322+
243323#[ pyfunction]
244324fn py_host_mesh_from_bytes ( bytes : & Bound < ' _ , PyBytes > ) -> PyResult < PyHostMesh > {
245325 let r: PyResult < HostMeshRef > = bincode:: deserialize ( bytes. as_bytes ( ) )
@@ -254,6 +334,14 @@ pub fn register_python_bindings(hyperactor_mod: &Bound<'_, PyModule>) -> PyResul
254334 "monarch._rust_bindings.monarch_hyperactor.v1.host_mesh" ,
255335 ) ?;
256336 hyperactor_mod. add_function ( f) ?;
337+
338+ let f2 = wrap_pyfunction ! ( bootstrap_host, hyperactor_mod) ?;
339+ f2. setattr (
340+ "__module__" ,
341+ "monarch._rust_bindings.monarch_hyperactor.v1.host_mesh" ,
342+ ) ?;
343+ hyperactor_mod. add_function ( f2) ?;
344+
257345 hyperactor_mod. add_class :: < PyHostMesh > ( ) ?;
258346 hyperactor_mod. add_class :: < PyBootstrapCommand > ( ) ?;
259347 Ok ( ( ) )
0 commit comments