1010import bittensor
1111from compute_horde_core .executor_class import ExecutorClass
1212from compute_horde_core .output_upload import OutputUpload
13+ from compute_horde_core .streaming import StreamingDetails
1314from compute_horde_core .volume import Volume
1415from pydantic import TypeAdapter
1516
@@ -176,7 +177,10 @@ async def notify_volumes_ready(self, msg: V0VolumesReadyRequest) -> None:
176177 """This method is called when miner sends executor ready message"""
177178
178179 async def notify_execution_done (self , msg : V0ExecutionDoneRequest ) -> None :
179- """This method is called when miner sends executor ready message"""
180+ """This method is called when miner sends execution done message"""
181+
182+ async def notify_streaming_readiness (self , msg : V0StreamingJobReadyRequest ) -> None :
183+ """This method is called when miner sends streaming ready message"""
180184
181185 async def handle_manifest_request (self , msg : V0ExecutorManifestRequest ) -> None :
182186 try :
@@ -396,9 +400,11 @@ async def send_initial_job_request(
396400 download_time_limit = job_details .job_timing .download_time_limit ,
397401 execution_time_limit = job_details .job_timing .execution_time_limit ,
398402 upload_time_limit = job_details .job_timing .upload_time_limit ,
403+ streaming_start_time_limit = job_details .job_timing .streaming_start_time_limit ,
399404 )
400405 if job_details .job_timing is not None
401406 else None ,
407+ streaming_details = job_details .streaming_details ,
402408 ),
403409 )
404410
@@ -428,6 +434,7 @@ class FailureReason(enum.Enum):
428434 EXECUTOR_FAILED = enum .auto ()
429435 STREAMING_JOB_READY_TIMED_OUT = enum .auto ()
430436 JOB_FAILED = enum .auto ()
437+ STREAMING_FAILED = enum .auto ()
431438
432439
433440class OrganicJobError (Exception ):
@@ -458,6 +465,7 @@ class TimingDetails:
458465 download_time_limit : int
459466 execution_time_limit : int
460467 upload_time_limit : int
468+ streaming_start_time_limit : int
461469
462470 @property
463471 def total (self ):
@@ -473,6 +481,7 @@ def total(self):
473481 volume : Volume | None = None
474482 output : OutputUpload | None = None
475483 artifacts_dir : str | None = None
484+ streaming_details : StreamingDetails | None = None
476485
477486
478487async def execute_organic_job_on_miner (
@@ -483,7 +492,6 @@ async def execute_organic_job_on_miner(
483492) -> tuple [str , str , dict [str , str ], dict [str , str ]]: # stdout, stderr, artifacts, upload_results
484493 """
485494 Run an organic job. This is a simpler way to use OrganicMinerClient.
486-
487495 :param client: the organic miner client
488496 :param job_details: details specific to the job that needs to be run
489497 :param reservation_time_limit: time for the miner to report reservation success (or decline the job)
@@ -514,7 +522,6 @@ async def execute_organic_job_on_miner(
514522 executor_class = job_details .executor_class ,
515523 ttl = reservation_time_limit ,
516524 )
517-
518525 await client .send_initial_job_request (job_details , receipt_payload , receipt_signature )
519526 logger .debug ("Sent initial job request" )
520527 await JobStartedReceipt .from_payload (
@@ -583,7 +590,7 @@ async def execute_organic_job_on_miner(
583590 )
584591 )
585592
586- ## STAGE: volume download
593+ ## STAGE: Volume download
587594 try :
588595 if executor_timing :
589596 logger .debug (
@@ -604,6 +611,26 @@ async def execute_organic_job_on_miner(
604611 raise OrganicJobError (FailureReason .VOLUMES_TIMED_OUT ) from exc
605612 await client .notify_volumes_ready (volumes_ready_response )
606613
614+ ## STAGE: Start streaming
615+ if job_details .streaming_details :
616+ try :
617+ if executor_timing :
618+ logger .debug (
619+ f"Extending deadline by streaming_start_time_limit: +{ executor_timing .streaming_start_time_limit } s"
620+ )
621+ deadline .extend_timeout (executor_timing .streaming_start_time_limit )
622+ logger .debug (f"Waiting for streaming (time left: { deadline .time_left ():.2f} s)" )
623+ streaming_response = await asyncio .wait_for (
624+ client .streaming_job_ready_or_not_future ,
625+ timeout = deadline .time_left (),
626+ )
627+ except TimeoutError as exc :
628+ raise OrganicJobError (FailureReason .STREAMING_JOB_READY_TIMED_OUT ) from exc
629+ if isinstance (streaming_response , V0StreamingJobNotReadyRequest ):
630+ raise OrganicJobError (FailureReason .STREAMING_FAILED , streaming_response )
631+
632+ await client .notify_streaming_readiness (streaming_response )
633+
607634 ## STAGE: execution
608635 try :
609636 if executor_timing :
0 commit comments