diff --git a/scm-perf/poulpe-scheduler-bin/auto-cases b/scm-perf/poulpe-scheduler-bin/auto-cases
old mode 100755
new mode 120000
index fd08c9a05eac524a75510991a2b6764a6dcced5f_c2NtLXBlcmYvcG91bHBlLXNjaGVkdWxlci1iaW4vYXV0by1jYXNlcw==..cf4d47dfbed0ec90943dbcfe834afb8dfb818db8_c2NtLXBlcmYvcG91bHBlLXNjaGVkdWxlci1iaW4vYXV0by1jYXNlcw==
--- a/scm-perf/poulpe-scheduler-bin/auto-cases
+++ b/scm-perf/poulpe-scheduler-bin/auto-cases
@@ -1,169 +1,1 @@
-#!/usr/bin/env python3
-
-from pathlib import Path
-import itertools
-import os
-import random
-import subprocess
-import sys
-
-x = os.path.abspath(sys.argv[0])
-x = os.path.dirname(x)
-x = os.path.dirname(x)
-poulpe_root = os.path.dirname(x)
-sys.path.insert(0, os.path.join(poulpe_root, 'python-libs'))
-
-import poulpe
-from poulpe import benchmarks as bench_mod
-
-TARGET_PYTHON_VERSIONS = [
-    "3.7"
-]
-
-PY_VERSION_COMPATIBILITY = {
-    "3.7": 'tagged("5.2")::',
-}
-
-FLAVOR_COMPATIBILITY = {
-    "rust": '(9183b7dcfa8d::)',
-    "rhg": '(tagged("6.1")::)',
-}
-
-
-class Case:
-
-    def __init__(
-        self,
-        hg_hash,
-        hg_variant,
-        data_env,
-        benchmark,
-    ):
-        self.hg_hash = hg_hash
-        self.hg_variant = hg_variant
-        self.data_env = data_env
-        self.benchmark = benchmark
-
-    def to_scheduler_line(self):
-        return (
-            f"RUN"
-            f" {self.hg_hash}"
-            f" {self.hg_variant}"
-            f" {self.data_env}"
-            f" {self.benchmark}"
-        )
-
-def mercurial_repository_path(base_dir):
-    return base_dir / "repos" / "mercurial"
-
-def list_mercurial_variants(base_dir, changeset):
-    """return a list of possible mercurial variants"""
-    variants = ["default", "pure"] # lets keep it simple for now
-    for k, v in FLAVOR_COMPATIBILITY.items():
-        revs = list_mercurial_hashes(base_dir, v)
-        if changeset in revs:
-            variants.append(k)
-    return variants
-
-
-
-def list_mercurial_hashes(base_dir, interval="all()"):
-    """return a list of possible mercurial hashes"""
-    repo_dir =  mercurial_repository_path(base_dir)
-    cmd = [
-        "hg",
-        "log",
-        "-T",
-        "{node}\n",
-        "-R",
-        str(repo_dir),
-        "--rev",
-        interval,
-    ]
-    p = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf8")
-    return p.stdout.splitlines()
-
-def list_data_envs(base_dir):
-    """return a list of possible data_envs"""
-    data_dir =  base_dir / "data-envs"
-    cmd = [
-        "find",
-        "-L",
-        ".",
-        "-maxdepth",
-        "3",
-        "-name",
-        "data-env.poulpe",
-    ]
-    p = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf8", cwd=str(data_dir))
-    raws = [Path(l) for l in p.stdout.splitlines()]
-    return [Path(*p.parts[:-1]) for p in raws]
-
-
-compat_cache = {}
-
-
-def compatible_with(base_dir, data_env, changeset):
-    """check if a data_env is compatible with the selected revision"""
-    path = base_dir / "data-envs" / data_env / "data-env.poulpe"
-    data_args = poulpe.get_data(path)
-    assert data_args is not None, path
-    ds2_key = "data-env-vars.mercurial.repo.format.dirstate-v2"
-    if poulpe.get_one_value(data_args, ds2_key) == "yes":
-        if "dirstate-v2" not in compat_cache:
-            revs = list_mercurial_hashes(base_dir, 'tagged("6.0")::')
-            compat_cache['dirstate-v2'] = set(revs)
-        return changeset in compat_cache['dirstate-v2']
-    return True
-
-
-def list_benchmarks(base_dir):
-    """return a list of possible benchmarks"""
-    bench_dir = base_dir / "benchmarks"
-    prefix = len(bench_dir.parts)
-    all_files = [Path(*p.parts[prefix:]) for p in bench_dir.rglob("*.pbd")]
-    all_benchmarks = []
-    for f in all_files:
-        benchmark = bench_mod.get_benchmark(bench_dir / f)
-        all_dimensions = []
-        for d, v in benchmark.all_dimensions.items():
-            all_dimensions.append([f'{d}={k}' for k in list(v.keys())])
-        if not all_dimensions:
-            all_benchmarks.append(f)
-        else:
-            for variants in itertools.product(*all_dimensions):
-                p = [str(f)]
-                p.extend(variants)
-                all_benchmarks.append(';'.join(p))
-    return all_benchmarks
-
-
-def main(base_dir=".", size="10"):
-    size = int(size)
-    base_dir = Path(base_dir).resolve()
-    for case in _main(base_dir, size):
-        print(case.to_scheduler_line())
-    return 0
-
-
-def _main(base_dir, size=10):
-    python_version = random.choice(TARGET_PYTHON_VERSIONS)
-
-    mercurial_intervals = PY_VERSION_COMPATIBILITY.get(python_version, "all()")
-
-    hashes = list_mercurial_hashes(base_dir, mercurial_intervals)
-    datas = list_data_envs(base_dir)
-    benchmarks = list_benchmarks(base_dir)
-    for i in range(size):
-        h = random.choice(hashes)
-        variants = list_mercurial_variants(base_dir, h)
-        for v in variants:
-            for d in datas:
-                if not compatible_with(base_dir, d, h):
-                    continue
-                for b in benchmarks:
-                    yield Case(h, v, d, b)
-
-
-if __name__ == "__main__":
-    sys.exit(main(*sys.argv[1:]))
+../../suites/hg/scheduling/auto-cases
\ No newline at end of file
diff --git a/scm-perf/poulpe-scheduler-bin/loop b/scm-perf/poulpe-scheduler-bin/loop
old mode 100755
new mode 120000
index fd08c9a05eac524a75510991a2b6764a6dcced5f_c2NtLXBlcmYvcG91bHBlLXNjaGVkdWxlci1iaW4vbG9vcA==..cf4d47dfbed0ec90943dbcfe834afb8dfb818db8_c2NtLXBlcmYvcG91bHBlLXNjaGVkdWxlci1iaW4vbG9vcA==
--- a/scm-perf/poulpe-scheduler-bin/loop
+++ b/scm-perf/poulpe-scheduler-bin/loop
@@ -1,191 +1,1 @@
-#!/usr/bin/env python3
-
-import hashlib
-import json
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-
-from pathlib import Path
-
-import poulpe.process_result as resultlib
-
-ACTION_FILE = "current.poulpe-schedule"
-
-RESULT_DIR = "results"
-BIN_ENV_DIR = "bin-envs"
-DATA_ENV_DIR = "data-envs"
-BENCHMARK_DIR = "benchmarks"
-
-def get_first(base_dir):
-    return next(iter(_get_all_actions(base_dir)), None)
-
-def drop_first(base_dir, action):
-    # only overwrite if no race are detected
-    # (A small race windows still exists within this function)
-    actions = _get_all_actions(base_dir)
-    if actions[0] == action:
-        actions.pop(0)
-        _write_all_actions(base_dir, actions)
-
-def _get_all_actions(base_dir):
-    actions_file = base_dir / ACTION_FILE
-    actions = actions_file.read_text()
-    return [a.strip() for a in actions.splitlines()]
-
-def _write_all_actions(base_dir, actions):
-    actions_file = base_dir / ACTION_FILE
-    actions_file.write_text('\n'.join(actions) + '\n')
-
-
-def process_action(base_dir, action):
-    parts = action.split(' ', 1)
-    cmd = parts[0]
-    if len(parts) > 1:
-        args = parts[1]
-    else:
-        args = ''
-    if cmd == "#":
-        pass
-    elif cmd == "RUN":
-        action_run(base_dir, *args.split())
-    elif cmd == "AUTOFILL":
-        action_autofill(base_dir, *[int(x) for x in args.split()])
-    elif cmd == "UPLOAD":
-        action_upload(base_dir)
-    else:
-        print("unknown action %s" % action, file=sys.stderr)
-
-
-def action_run(base_dir, mercurial_nodeid, mercurial_variant, data_env, benchmark):
-    print('running:', mercurial_nodeid, mercurial_variant, data_env, benchmark)
-    bin_env = ensure_bin_env(base_dir, mercurial_nodeid, mercurial_variant)
-    if bin_env is None:
-        print("could not get running bin-env", file=sys.stderr)
-        return
-
-    result_desc = f"{bin_env} {mercurial_variant} {data_env} {benchmark}"
-    result_id = hashlib.sha256(result_desc.encode("utf8")).hexdigest()
-    result = base_dir / RESULT_DIR / f"{result_id}.pbr"
-
-    data_env = base_dir / DATA_ENV_DIR / data_env
-    benchmark = base_dir / BENCHMARK_DIR / benchmark
-
-    run_script = base_dir / "bin/run-util"
-    cmd = [
-            str(run_script),
-            str(bin_env),
-            str(data_env),
-            str(benchmark),
-            str(result),
-    ]
-    subprocess.call(cmd)
-
-
-def is_ready(bin_env):
-    """wtf, need better"""
-    desc = (bin_env / "bin-env.poulpe")
-    if not desc.exists():
-        return False
-    return "ready = true" in desc.read_text()
-
-
-MAX_BIN_ENV = 50
-
-def ensure_bin_env(base_dir, nodeid, variant):
-    bin_env_dir = base_dir / BIN_ENV_DIR
-    target = bin_env_dir / f"mercurial-{nodeid}-{variant}"
-
-    all_existing = [p for p in bin_env_dir.iterdir()]
-    all_existing.sort(key=lambda x: x.stat().st_mtime)
-
-    assert MAX_BIN_ENV > 1
-    if len(all_existing) >  MAX_BIN_ENV:
-        for delete_me in all_existing:
-            if delete_me != target:
-                break
-        print(f"Bin-envs {MAX_BIN_ENV} limit, deleting: {delete_me}")
-        shutil.rmtree(delete_me)
-
-    # that check is too simple, we need to make sure this is a valid bin-env too
-    if target.exists() and not is_ready(target):
-        shutil.rmtree(target)
-    if not target.exists():
-        setup_cmd = base_dir / "bin" / "bin-env-util"
-        setup_script = base_dir / "bin" / "mercurial.poulpe-setup.sh"
-        os.makedirs(target, exist_ok=True)
-
-        env = os.environ.copy()
-        env['MERCURIAL_VERSION'] = nodeid
-        env['MERCURIAL_FLAVOR'] = variant
-        # XXX check the result of this at some point…
-        cmd = [
-            setup_cmd,
-            "setup-one",
-            target,
-            setup_script,
-        ]
-        subprocess.call(cmd, env=env)
-
-    if is_ready(target):
-        return target
-    else:
-        shutil.rmtree(target)
-        return None
-
-
-def action_autofill(base_dir, count=None):
-    fill_script = base_dir / "bin/auto-cases"
-    cmd = [str(fill_script)]
-    if count:
-        cmd.append(str(count))
-    p = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf8")
-    new_actions = p.stdout.splitlines()
-    print("auto-fill: adding %d new actions" % len(new_actions))
-    loop = ["AUTOFILL"]
-    if count:
-        loop.append(str(count))
-    new_actions.append(' '.join(loop))
-    # tiny race window here
-    actions = _get_all_actions(base_dir)
-    actions.extend(new_actions)
-    _write_all_actions(base_dir, actions)
-
-
-def action_upload(base_dir):
-    results = resultlib.load_results_from_dir(base_dir / "results")
-    print("upload: gather and upload %d results" % len(results))
-    dispatched = resultlib.group_results(results)
-
-    with tempfile.NamedTemporaryFile(mode="w") as j:
-        json.dump(dispatched, j)
-        j.flush()
-        cmd = [base_dir / "bin" / "upload", j.name]
-        subprocess.check_call(cmd)
-
-    # tiny race window here
-    actions = _get_all_actions(base_dir)
-    actions.append('UPLOAD')
-    _write_all_actions(base_dir, actions)
-
-
-def loop(base_dir="."):
-    base_dir = Path(base_dir).resolve()
-    _loop(base_dir)
-    return 0
-
-
-def _loop(base_dir):
-
-    action = get_first(base_dir)
-    while action is not None:
-        if action:
-            process_action(base_dir, action)
-            drop_first(base_dir, action)
-        action = get_first(base_dir)
-
-
-if __name__ == "__main__":
-    sys.exit(loop(*sys.argv[1:]))
+../../suites/hg/scheduling/loop
\ No newline at end of file
diff --git a/scm-perf/poulpe-scheduler-bin/auto-cases b/suites/hg/scheduling/auto-cases
similarity index 100%
copy from scm-perf/poulpe-scheduler-bin/auto-cases
copy to suites/hg/scheduling/auto-cases
diff --git a/scm-perf/poulpe-scheduler-bin/loop b/suites/hg/scheduling/loop
similarity index 100%
copy from scm-perf/poulpe-scheduler-bin/loop
copy to suites/hg/scheduling/loop