@@ -63,6 +63,7 @@ def _resolve_model_from_catalog(model_id: str) -> tuple[AgentType, str] | None:
6363
6464_CONFIG_PATH = ".hud_eval.toml"
6565_PLACEMENT_CONFLICT_ERROR = "--runtime and --remote are mutually exclusive placement options"
66+ _SOURCE_FORMATS = ("hud" , "harbor" )
6667
6768
6869def _resolve_env_vars (obj : Any ) -> Any :
@@ -167,6 +168,7 @@ class AgentPreset:
167168# very_verbose = true
168169# auto_respond = true
169170# gateway = false # Route LLM API calls through HUD Gateway
171+ # format = "hud" # hud or harbor
170172# runtime = "local" # local, hud, or tcp://host:port
171173# remote = false # Run the whole rollout remotely on HUD
172174
@@ -264,6 +266,7 @@ class EvalConfig(BaseModel):
264266 "group_size" ,
265267 "auto_respond" ,
266268 "gateway" ,
269+ "format" ,
267270 "runtime" ,
268271 "remote" ,
269272 }
@@ -279,6 +282,9 @@ class EvalConfig(BaseModel):
279282 auto_respond : bool | None = None
280283 group_size : int = 1
281284 gateway : bool = False
285+ #: Source format. ``None``/``hud`` means normal HUD task source loading;
286+ #: ``harbor`` opts into the Harbor integration loader/runtime.
287+ format : str | None = None
282288 #: Placement: "local" (spawn each row's env from the source), "hud"
283289 #: (HUD runtime tunnel), or a tcp:// url of an already-served env.
284290 #: ``None`` means "infer from the source": a local file runs locally, a
@@ -306,6 +312,20 @@ def _parse_agent_type(cls, v: Any) -> AgentType | None:
306312 ) from None
307313 return v
308314
315+ @field_validator ("format" , mode = "before" )
316+ @classmethod
317+ def _parse_format (cls , v : Any ) -> str | None :
318+ if v is None :
319+ return None
320+ if not isinstance (v , str ):
321+ return v
322+ normalized = v .strip ().lower ()
323+ if normalized in ("" , "hud" ):
324+ return None
325+ if normalized in _SOURCE_FORMATS :
326+ return normalized
327+ raise ValueError (f"Invalid format: { v } . Must be one of: { ', ' .join (_SOURCE_FORMATS )} " )
328+
309329 def source_is_local_file (self ) -> bool :
310330 """Whether ``source`` points at an on-disk taskset (vs. a platform slug/id)."""
311331 return self .source is not None and Path (self .source ).exists ()
@@ -319,6 +339,13 @@ def resolve_runtime(self) -> EvalConfig:
319339 ``--runtime`` is always honored, except ``local`` against a platform
320340 taskset, which has no env to spawn.
321341 """
342+ if self .format == "harbor" :
343+ if not self .source_is_local_file ():
344+ hud_console .error ("--format harbor requires a local Harbor task directory" )
345+ raise typer .Exit (1 )
346+ if self .remote or (self .runtime is not None and self .runtime != "local" ):
347+ hud_console .error ("--format harbor currently supports only local runtime placement" )
348+ raise typer .Exit (1 )
322349 if self .runtime is None :
323350 if self .source_is_local_file ():
324351 return self .model_copy (update = {"runtime" : "local" })
@@ -502,6 +529,7 @@ def merge_cli(
502529 gateway : bool = False ,
503530 config : list [str ] | None = None ,
504531 task_ids : str | None = None ,
532+ format : str | None = None ,
505533 runtime : str | None = None ,
506534 remote : bool = False ,
507535 ) -> EvalConfig :
@@ -517,6 +545,7 @@ def merge_cli(
517545 "max_concurrent" : max_concurrent ,
518546 "max_steps" : max_steps ,
519547 "group_size" : group_size ,
548+ "format" : format ,
520549 "runtime" : runtime ,
521550 }.items ()
522551 if value is not None
@@ -604,6 +633,8 @@ def display(self) -> None:
604633 table .add_column ("Value" , style = "green" )
605634
606635 table .add_row ("source" , str (self .source or "-" ))
636+ if self .format :
637+ table .add_row ("format" , self .format )
607638 table .add_row ("runtime" , str (self .runtime or "-" ))
608639 table .add_row ("agent" , self .agent_type .value if self .agent_type else "-" )
609640 if self .task_ids :
@@ -728,32 +759,20 @@ def _spawn_target(source: Path) -> Path:
728759 return resolved .parent
729760
730761
731- def _is_harbor_source (source_path : Path | None ) -> bool :
732- if source_path is None or not source_path .exists ():
733- return False
734- if not source_path .is_dir ():
735- return False
736- from integrations .harbor import detect
737-
738- return detect (source_path )
739-
740-
741- def _load_local_taskset (source_path : Path ) -> tuple [Any , str ]:
762+ def _load_local_taskset (source_path : Path , source_format : str | None ) -> Any :
742763 from hud .eval import Taskset
743764
744- if _is_harbor_source (source_path ):
765+ format_name = source_format or "hud"
766+ if format_name == "hud" :
767+ return Taskset .from_file (source_path )
768+ if format_name == "harbor" :
745769 from integrations .harbor import load
746770
747- return load (source_path ), "harbor"
748- return Taskset . from_file ( source_path ), "hud"
771+ return load (source_path )
772+ raise ValueError ( f"unsupported task source format: { format_name } " )
749773
750774
751- def _resolve_placement (
752- cfg : EvalConfig ,
753- source_path : Path | None ,
754- * ,
755- source_kind : str = "hud" ,
756- ) -> Any :
775+ def _resolve_placement (cfg : EvalConfig , source_path : Path | None ) -> Any :
757776 """Map the config's ``runtime`` onto a placement for ``Taskset.run``.
758777
759778 "local" spawns each row's env from the source next to the tasks file;
@@ -769,7 +788,7 @@ def _resolve_placement(
769788 if cfg .runtime == "local" :
770789 if source_path is None :
771790 raise ValueError ("local placement requires a local source path" )
772- if source_kind == "harbor" :
791+ if cfg . format == "harbor" :
773792 from integrations .harbor import HarborRuntime
774793
775794 return HarborRuntime (source_path )
@@ -798,11 +817,10 @@ async def _run_evaluation(cfg: EvalConfig) -> Any:
798817
799818 source_path = Path (cfg .source )
800819 is_local = source_path .exists ()
801- source_kind = "api"
802820 if is_local :
803821 hud_console .info (f"Loading tasks from: { cfg .source } " )
804822 try :
805- taskset , source_kind = _load_local_taskset (source_path )
823+ taskset = _load_local_taskset (source_path , cfg . format )
806824 except Exception as e :
807825 hud_console .error (f"Failed to load tasks from { cfg .source } : { e } " )
808826 raise typer .Exit (1 ) from e
@@ -862,11 +880,7 @@ async def _run_evaluation(cfg: EvalConfig) -> Any:
862880 )
863881
864882 agent = _build_agent (cfg )
865- placement = _resolve_placement (
866- cfg ,
867- source_path if is_local else None ,
868- source_kind = source_kind ,
869- )
883+ placement = _resolve_placement (cfg , source_path if is_local else None )
870884
871885 job = await taskset .run (
872886 agent ,
@@ -922,6 +936,11 @@ def eval_command(
922936 gateway : bool = typer .Option (
923937 False , "--gateway" , "-g" , help = "Route LLM API calls through HUD Gateway"
924938 ),
939+ format : str | None = typer .Option (
940+ None ,
941+ "--format" ,
942+ help = "Task source format: hud (default) or harbor." ,
943+ ),
925944 runtime : str | None = typer .Option (
926945 None ,
927946 "--runtime" ,
@@ -942,6 +961,7 @@ def eval_command(
942961 hud eval "My Tasks" claude-sonnet-4-6 --full # Platform taskset, run on the platform
943962 hud eval tasks.json claude --config max_tokens=32768
944963 hud eval tasks.json claude --gateway # Route LLM calls through HUD Gateway
964+ hud eval ./harbor_tasks claude --format harbor # Run Harbor task dirs locally
945965 hud eval tasks.json claude-sonnet-4-6 --runtime hud # Use HUD runtime tunnel
946966 hud eval tasks.json claude-sonnet-4-6 --remote # Execute rollout remotely
947967 """
@@ -972,6 +992,7 @@ def eval_command(
972992 group_size = group_size ,
973993 config = config ,
974994 gateway = gateway ,
995+ format = format ,
975996 runtime = runtime ,
976997 remote = remote ,
977998 )
0 commit comments