File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -250,6 +250,7 @@ async def init_task_upload(
250250 org_id = auth .org_id ,
251251 content_hash = payload .content_hash ,
252252 message = payload .message ,
253+ force_new_version = payload .force_new_version ,
253254 )
254255
255256
Original file line number Diff line number Diff line change @@ -7,7 +7,7 @@ name = "oddish"
77version = " 0.1.12"
88description = " Run Harbor tasks in the cloud with scheduling, monitoring, and persistent state"
99readme = " README.md"
10- requires-python = " >=3.14"
10+ requires-python = " >=3.13,<3. 14"
1111license = { file = " LICENSE" }
1212authors = [
1313 { name = " Rishi Desai" , email = " rishi@abundant.ai" },
Original file line number Diff line number Diff line change @@ -379,6 +379,7 @@ def upload_task(
379379 message : str | None = None ,
380380 user : str | None = None ,
381381 priority : str | None = None ,
382+ force_new_version : bool = False ,
382383) -> dict :
383384 """Upload a task directory to the API.
384385
@@ -404,6 +405,8 @@ def upload_task(
404405 }
405406 if message :
406407 init_body ["message" ] = message
408+ if force_new_version :
409+ init_body ["force_new_version" ] = True
407410
408411 try :
409412 with httpx .Client (timeout = 600.0 , headers = get_auth_headers ()) as client :
@@ -481,6 +484,7 @@ def upload_tasks_with_progress(
481484 quiet : bool = False ,
482485 json_output : bool = False ,
483486 progress_label : str = "Uploading" ,
487+ force_new_version : bool = False ,
484488) -> list [dict ]:
485489 """Upload a batch of task directories with a shared progress bar.
486490
@@ -501,6 +505,7 @@ def _upload_one(task_path: Path) -> dict:
501505 message = message ,
502506 user = user ,
503507 priority = priority ,
508+ force_new_version = force_new_version ,
504509 )
505510
506511 show_progress = not quiet and not json_output
Original file line number Diff line number Diff line change @@ -258,6 +258,17 @@ def run(
258258 help = "Force rebuild the environment Docker image" ,
259259 ),
260260 ] = None ,
261+ force_new_version : Annotated [
262+ bool ,
263+ typer .Option (
264+ "--force-new-version" ,
265+ help = (
266+ "Allocate a new task version even when the local content is "
267+ "unchanged from the latest existing version. Useful when "
268+ "appending trials with a different run_analysis setting."
269+ ),
270+ ),
271+ ] = False ,
261272 agent_env : Annotated [
262273 Optional [list [str ]],
263274 typer .Option (
@@ -531,6 +542,7 @@ def submit_task(
531542 quiet = quiet ,
532543 json_output = json_output ,
533544 progress_label = "Uploading" ,
545+ force_new_version = force_new_version ,
534546 )
535547 for task_path , result in zip (task_paths , upload_results ):
536548 is_existing = bool (result .get ("existing_task" , False ))
Original file line number Diff line number Diff line change @@ -1644,11 +1644,16 @@ async def create_task_sweep_core(
16441644 task = await get_task_for_org_core (
16451645 session , task_id = submission .task_id , org_id = org_id
16461646 )
1647+ # Allow flipping task.run_analysis from False to True on append.
1648+ # ``run_analysis`` runs at trial-completion time, so updating the
1649+ # task-level flag does not retroactively analyze pre-existing
1650+ # trials, but new trials submitted with ``--run-analysis`` will be
1651+ # analyzed as the caller requested. This matches the documented
1652+ # purpose of ``--force-new-version`` (see ``TaskUploadInitRequest``)
1653+ # and lets a task that was first registered without analysis later
1654+ # opt in without manual intervention.
16471655 if submission .run_analysis and not task .run_analysis :
1648- raise HTTPException (
1649- status_code = 400 ,
1650- detail = "Cannot enable run_analysis when appending to a task that was created without it" ,
1651- )
1656+ task .run_analysis = True
16521657
16531658 new_experiment_id : str | None = None
16541659 experiment : ExperimentModel | None = None
Original file line number Diff line number Diff line change @@ -73,6 +73,7 @@ async def initialize_task_upload(
7373 org_id : str | None = None ,
7474 content_hash : str ,
7575 message : str | None = None ,
76+ force_new_version : bool = False ,
7677) -> TaskUploadInitResponse :
7778 """Prepare a task upload and return direct-upload details when supported."""
7879 normalized_name = _normalize_task_name (task_name )
@@ -86,7 +87,8 @@ async def initialize_task_upload(
8687 )
8788
8889 if (
89- latest is not None
90+ not force_new_version
91+ and latest is not None
9092 and latest .content_hash
9193 and latest .content_hash == content_hash
9294 ):
Original file line number Diff line number Diff line change @@ -314,6 +314,14 @@ class TaskUploadInitRequest(BaseModel):
314314 message : str | None = Field (
315315 None , description = "Optional description of what changed in this version"
316316 )
317+ force_new_version : bool = Field (
318+ False ,
319+ description = (
320+ "Allocate a new task version even when the content hash matches the "
321+ "latest existing version. Used when callers need a fresh version "
322+ "stamp (e.g. to flip run_analysis on)."
323+ ),
324+ )
317325
318326
319327class TaskUploadCompleteRequest (BaseModel ):
You can’t perform that action at this time.
0 commit comments