1111
1212:func:`load` parses a task dir (or a dataset of them) into rows sharing one
1313env name per distinct ``environment/`` build context — no codegen, no
14- roundtrip. Like every row, the result is runnable
15- once a placement is supplied (``runtime=Runtime(url)`` against a served substrate
16- today). Providers receive the row being placed, so a docker provider that
17- builds and runs each row's ``environment/`` image is the named follow-up —
18- expressible without engine changes.
14+ roundtrip. Like every row, the result is runnable once a placement is supplied.
15+ Use :class:`HarborRuntime` for local Docker-backed execution of Harbor tasks, or
16+ ``runtime=Runtime(url)`` to attach to a substrate served elsewhere.
1917
2018:func:`export` is the reverse direction: turn a HUD task source into
2119self-contained Harbor task folders (``task.toml`` + ``instruction.md`` +
4038
4139from __future__ import annotations
4240
43- import hashlib
4441import json
4542import logging
46- import re
4743import shutil
48- import tomllib
49- from dataclasses import dataclass
5044from pathlib import Path
5145from typing import TYPE_CHECKING , Any
5246
5347from hud .environment import Environment
5448from hud .environment .server import TaskRunner
5549from hud .eval import Task , Taskset
50+ from integrations .harbor_common import (
51+ _HarborTask ,
52+ _is_harbor_task ,
53+ _parse_task ,
54+ _slugify ,
55+ _task_dirs ,
56+ )
57+ from integrations .harbor_runtime import HarborRuntime
5658
5759if TYPE_CHECKING :
5860 from collections .abc import Callable
7476 "__pycache__" , "*.pyc" , ".git" , ".venv" , "venv" , "*.egg-info" , ".pytest_cache"
7577)
7678
77-
7879# ─── load: Harbor dirs -> Taskset ──────────────────────────────────────
7980
8081
8182def detect (path : str | Path ) -> bool :
8283 """True when *path* is a Harbor task dir or a dataset of them."""
83- root = Path (path )
84- if _is_harbor_task (root ):
85- return True
86- if root .is_dir ():
87- return any (_is_harbor_task (d ) for d in root .iterdir () if d .is_dir ())
88- return False
84+ return bool (_task_dirs (path ))
8985
9086
9187def load (path : str | Path ) -> Taskset :
@@ -96,12 +92,8 @@ def load(path: str | Path) -> Taskset:
9692 context (content-hashed), derived from the dataset name.
9793 """
9894 root = Path (path ).resolve ()
99- if _is_harbor_task (root ):
100- task_dirs = [root ]
101- dataset_name = root .parent .name
102- else :
103- task_dirs = sorted (d for d in root .iterdir () if d .is_dir () and _is_harbor_task (d ))
104- dataset_name = root .name
95+ task_dirs = _task_dirs (root )
96+ dataset_name = root .parent .name if _is_harbor_task (root ) else root .name
10597 if not task_dirs :
10698 raise ValueError (f"no Harbor tasks found in { path } " )
10799
@@ -126,54 +118,6 @@ def load(path: str | Path) -> Taskset:
126118 return Taskset (base_name , tasks )
127119
128120
129- def _slugify (name : str ) -> str :
130- """A valid env name (lowercase ``[a-z0-9-]``) from a dataset dir name."""
131- normalized = re .sub (r"[^a-z0-9-]" , "" , name .strip ().lower ().replace (" " , "-" ).replace ("_" , "-" ))
132- return re .sub (r"-+" , "-" , normalized ).strip ("-" ) or "harbor"
133-
134-
135- def _is_harbor_task (path : Path ) -> bool :
136- return path .is_dir () and (path / "task.toml" ).exists () and (path / "instruction.md" ).exists ()
137-
138-
139- def _hash_directory (path : Path ) -> str :
140- """Content-hash a directory for grouping tasks by identical environments."""
141- hasher = hashlib .sha256 ()
142- if not path .exists ():
143- return "empty"
144- for file_path in sorted (path .rglob ("*" )):
145- if file_path .is_file ():
146- hasher .update (str (file_path .relative_to (path )).encode ())
147- hasher .update (file_path .read_bytes ())
148- return hasher .hexdigest ()[:16 ]
149-
150-
151- @dataclass (frozen = True , slots = True )
152- class _HarborTask :
153- """One parsed Harbor task dir."""
154-
155- task_id : str
156- config : dict [str , Any ]
157- env_hash : str
158-
159-
160- def _parse_task (task_dir : Path ) -> _HarborTask | None :
161- if not (task_dir / "instruction.md" ).is_file ():
162- LOGGER .warning ("failed to read instruction.md in %s" , task_dir )
163- return None
164- try :
165- config : dict [str , Any ] = tomllib .loads ((task_dir / "task.toml" ).read_text ("utf-8" ))
166- except (OSError , tomllib .TOMLDecodeError ):
167- LOGGER .warning ("failed to parse task.toml in %s" , task_dir )
168- config = {}
169- env_dir = task_dir / "environment"
170- return _HarborTask (
171- task_id = task_dir .name ,
172- config = config ,
173- env_hash = _hash_directory (env_dir ) if env_dir .exists () else "no-env" ,
174- )
175-
176-
177121# ─── export: HUD tasks -> Harbor task folders ───────────────────────────
178122
179123
@@ -443,6 +387,7 @@ async def export(
443387 "ALLOWED_PROTOCOLS" ,
444388 "CONTROL_PORT" ,
445389 "DEFAULT_ANSWER_FILE" ,
390+ "HarborRuntime" ,
446391 "detect" ,
447392 "export" ,
448393 "load" ,
0 commit comments