diff --git a/microtensor_subnet/cli/epoch.py b/microtensor_subnet/cli/epoch.py index 9ecf96c..3bc7aa0 100644 --- a/microtensor_subnet/cli/epoch.py +++ b/microtensor_subnet/cli/epoch.py @@ -7,6 +7,7 @@ import logging import os import random +import sys import time from pathlib import Path from typing import Any @@ -229,10 +230,26 @@ def _relay_token(args: argparse.Namespace) -> int: return 0 +def _show_logs() -> None: + package = logging.getLogger("microtensor_subnet") + if not any(getattr(h, "_mt_visible", False) for h in package.handlers): + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")) + handler._mt_visible = True # type: ignore[attr-defined] + package.addHandler(handler) + package.setLevel(logging.INFO) + package.propagate = False + package.disabled = False + for name, found in list(logging.Logger.manager.loggerDict.items()): + if isinstance(found, logging.Logger) and name.startswith("microtensor_subnet"): + found.disabled = False + + def _run(args: argparse.Namespace) -> int: config = chain_config(args) wallet = open_wallet(config) client = open_client(config, wallet) + _show_logs() server = ServerClient(args.server, hotkey_address(wallet), wallet) try: block = client.block() @@ -558,6 +575,7 @@ def _loop(args: argparse.Namespace) -> int: config = chain_config(args) wallet = open_wallet(config) client = open_client(config, wallet) + _show_logs() server = ServerClient(args.server, hotkey_address(wallet), wallet) state = Path(args.home) / "state" state.mkdir(parents=True, exist_ok=True) diff --git a/microtensor_subnet/validator/epoch.py b/microtensor_subnet/validator/epoch.py index fcff494..ede12c7 100644 --- a/microtensor_subnet/validator/epoch.py +++ b/microtensor_subnet/validator/epoch.py @@ -162,7 +162,7 @@ def load_context( files = items.open_bundle(manifest, bundle, hotkey, private) work_dir = config.work_dir / str(epoch_index) epoch_items, trees = items.unpack(files, seed=seed, work_dir=work_dir, limit=config.item_limit) - ref = items.reference_components(trees, definition, config.cache_dir) + ref = items.reference_components(trees, definition, config.cache_dir, config.chain.network) snapshot = client.ledger_snapshot(config.arena_id, epoch_index) roster_doc = client.roster(config.arena_id, epoch_index) charged = dict(dict(dict(roster_doc or {}).get("body") or {}).get("fee") or {}) diff --git a/microtensor_subnet/validator/items.py b/microtensor_subnet/validator/items.py index d1e4f0f..971dacc 100644 --- a/microtensor_subnet/validator/items.py +++ b/microtensor_subnet/validator/items.py @@ -3,6 +3,8 @@ import hashlib import json import logging +import shutil +import subprocess from pathlib import Path from typing import Any @@ -133,8 +135,43 @@ def unpack( ), trees +TESTNET = "test" +TEMPLATE_REPO = "https://github.com/microtensor-io/microtensor-miner-template" +TEMPLATE_FILES = ("main.py", "config.json", "requirements.txt") +TEMPLATE_DIRS = ("harness", "escalation") + + +def template_harness(task: str, cache_dir: Path) -> Path: + clone = cache_dir / "reference-template" + if not (clone / "main.py").is_file(): + shutil.rmtree(clone, ignore_errors=True) + try: + subprocess.run( # noqa: S603 + ["git", "clone", "--quiet", "--depth", "1", TEMPLATE_REPO, str(clone)], # noqa: S607 + check=True, + capture_output=True, + timeout=300, + ) + except (OSError, subprocess.SubprocessError) as exc: + reason = f"the reference harness could not be built from the template: {exc}" + raise ReleaseError(reason) from exc + into = cache_dir / f"reference-harness-{task or 'generic'}" + shutil.rmtree(into, ignore_errors=True) + into.mkdir(parents=True) + for name in TEMPLATE_FILES: + shutil.copy2(clone / name, into / name) + for name in TEMPLATE_DIRS: + shutil.copytree(clone / name, into / name, ignore=shutil.ignore_patterns("__pycache__")) + if task and (into / "harness" / "prompts" / task).is_dir(): + config = json.loads((into / "config.json").read_text(encoding="utf-8")) + config["adapter"] = task + (into / "config.json").write_text(json.dumps(config, indent=2) + chr(10), encoding="utf-8") + log.warning("testnet: the bundle has no reference harness; built one from the miner template") + return into + + def reference_components( - trees: dict[str, Path], definition: dict[str, Any], cache_dir: Path + trees: dict[str, Path], definition: dict[str, Any], cache_dir: Path, network: str = "" ) -> Reference: references = dict(definition.get("references") or {}) slm_repo = str(references.get("slm", "")) @@ -147,6 +184,8 @@ def reference_components( except ParameterError as exc: raise ReleaseError(f"the reference slm has no readable weights: {exc}") from exc harness = trees.get("harness") + if harness is None and network == TESTNET: + harness = template_harness(str(definition.get("task") or ""), cache_dir) if harness is None: raise ReleaseError("the bundle carries no reference harness") variants = tuple(path for name, path in sorted(trees.items()) if name.startswith("variant-"))