# -*- coding: utf-8 -*- """Run the versus scenarios against Hermes. python run_hermes.py --only V5 --max-turns 1 # plumbing smoke test, cents python run_hermes.py --reps 3 # the real run Registers HermesAdapter by wrapping versus.adapters.build_adapter IN THIS PROCESS. Neither the axium repo nor the hermes clone is modified on disk. One equalisation, applied in memory and stated because it changes a score: `graders.IGNORE_DIRS` already excludes `.axium` and `.orange` so that an agent's own state is not counted as a project edit. `.hermes` is added to that set here, which is the same treatment, not a favour. Without it every Hermes turn would report a modified tree and V2's "touched nothing" could never pass. Results are written to eval/logs/hermes.jsonl inside this disposable root, NOT to the axium repo's versus/logs. """ import argparse import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) AXIUM_PY = r'C:\xampp\htdocs\axium\python' sys.path.insert(0, HERE) sys.path.insert(0, AXIUM_PY) from versus import adapters, graders, runner # noqa: E402 from hermes_adapter import HermesAdapter # noqa: E402 # same treatment as .axium / .orange graders.IGNORE_DIRS = set(graders.IGNORE_DIRS) | {'.hermes', '.hermes-session'} # A file literally named `nul` (a `>nul` redirect under MSYS) makes os.path.relpath # raise and kills the run mid-session. Guarded here; the underlying fix is in # whitepapers/.../data/fix_graders.py, deliberately not applied to the repo. _relpath = os.path.relpath _hashlib = __import__('hashlib') def _tree_hash(build): out = {} for dirpath, dirs, files in os.walk(build): dirs[:] = [d for d in dirs if d not in graders.IGNORE_DIRS] for fn in files: if fn.endswith(graders.IGNORE_SUFFIX): continue p = os.path.join(dirpath, fn) try: rel = _relpath(p, build).replace('\\', '/') except ValueError: continue # reserved device name try: with open(p, 'rb') as f: out[rel] = _hashlib.sha1(f.read()).hexdigest() except OSError: out[rel] = 'unreadable' return out graders.tree_hash = _tree_hash adapters.G.tree_hash = _tree_hash _orig_build = adapters.build_adapter def build_adapter(which, args): if which == 'hermes': return HermesAdapter(model=getattr(args, 'hermes_model', None) or 'deepseek-v4-pro') return _orig_build(which, args) adapters.build_adapter = build_adapter runner.build_adapter = build_adapter # keep results out of the axium repo LOGS = os.path.join(HERE, 'logs') os.makedirs(LOGS, exist_ok=True) assert hasattr(runner, 'LOGS'), 'versus.runner.LOGS moved; check runner.py:29' runner.LOGS = LOGS def main(): ap = argparse.ArgumentParser() ap.add_argument('--only', default=None) ap.add_argument('--reps', type=int, default=1) ap.add_argument('--max-turns', type=int, default=0) ap.add_argument('--model', default='deepseek-v4-pro') ap.add_argument('--keep', action='store_true') ap.add_argument('--verbose', action='store_true') a = ap.parse_args() argv = ['versus.runner', '--agents', 'hermes', '--reps', str(a.reps)] if a.only: argv += ['--only', a.only] if a.max_turns: argv += ['--max-turns', str(a.max_turns)] if a.keep: argv += ['--keep'] if a.verbose: argv += ['--verbose'] sys.argv = argv try: runner.main() except SystemExit as e: return e.code or 0 return 0 if __name__ == '__main__': raise SystemExit(main())