        if s.lower().startswith("python"):
            return False
        # Heuristic: must contain either a top import or a def solve line
        return bool(re.search(r"^\s*(from\s+\w|import\s+\w|def\s+solve\s*\(", s, flags=re.MULTILINE))
    except Exception:
        return False

def signature_matches(src: str, signature: str) -> bool:
    try:
        want_name = "solve"
        m = re.search(r"def\s+(\w+)\s*\(", signature or "")
        if m:
            want_name = m.group(1)
        m2 = re.search(r"^\s*def\s+(\w+)\s*\(", src or "", flags=re.MULTILINE)
        return bool(m2 and m2.group(1) == want_name)
    except Exception:
        return False

def _static_checks(src: str, *, allowed_stdlib: bool = True) -> tuple[float, list[str]]:
    import ast as _ast
    try:
        tree = _ast.parse(src)
    except Exception as e:
        return 0.0, [f"parse_error: {e}"]
    names: set[str] = set()
    imports: list[str] = []
    banned_names = {"open", "subprocess", "os.system", "sys.exit", "eval", "exec"}
    hints: list[str] = []
    for n in _ast.walk(tree):
        if isinstance(n, _ast.Name):
            names.add(n.id)
        if isinstance(n, (_ast.Import, _ast.ImportFrom)):
            if isinstance(n, _ast.ImportFrom):
                if n.module:
                    imports.append(n.module.split(".")[0])
            else:
                for a in n.names:
                    imports.append(a.name.split(".")[0])
        if isinstance(n, _ast.Call) and isinstance(getattr(n, "func", None), _ast.Name) and getattr(n.func, "id", "") in {"eval", "exec"}:
            return 0.0, ["banned: eval/exec"]
    try:
        if any(b in src for b in banned_names):
            return 0.0, ["banned name used"]
    except Exception:
        pass
    if allowed_stdlib:
        try:
            import sys as _sys
            allowed = set(getattr(_sys, "stdlib_module_names", set()) or set())
        except Exception:
            allowed = {"math", "functools", "itertools", "collections", "heapq", "bisect", "random"}
        for m in imports:
            if m and m not in allowed:
                hints.append(f"non-stdlib import: {m}")
                return 0.0, hints
    # Undefined names hinting (best-effort)
    try:
        import builtins as _bi
        undefined = [n for n in names if not hasattr(_bi, n)]
        if undefined:
            hints.append("undefined: " + ",".join(sorted(undefined)))
    except Exception:
        pass
    return 1.0, hints

def _safe_compile(src: str) -> tuple[bool, Any]:
    g: Dict[str, Any] = {"__builtins__": {}}
    loc: Dict[str, Any] = {}
    for k in [
        "abs","all","any","enumerate","len","range","min","max","sum","map","filter",
        "zip","sorted","reversed","list","tuple","dict","set","bool","int","float","str","print","__import__",
    ]:
        try:
            g["__builtins__"][k] = getattr(__builtins__, k)
        except Exception:
            pass
    try:
        exec(src, g, loc)
        return True, loc
    except Exception as e:
        return False, str(e)


        try:
            import sys as _sys
            allowed = set(getattr(_sys, "stdlib_module_names", set()) or set())
        except Exception:
            allowed = {"math", "functools", "itertools", "collections", "heapq", "bisect", "random"}
        for m in imports:
            if m and m not in allowed:
                hints.append(f"non-stdlib import: {m}")
                return 0.0, hints
    # Undefined names hinting (best-effort)
    try:
        import builtins as _bi
        undefined = [n for n in names if not hasattr(_bi, n)]
        if undefined:
            hints.append("undefined: " + ",".join(sorted(undefined)))
    except Exception:
        pass
    return 1.0, hints

def _safe_compile(src: str) -> tuple[bool, Any]:
    g: Dict[str, Any] = {"__builtins__": {}}
    loc: Dict[str, Any] = {}
    for k in [
        "abs","all","any","enumerate","len","range","min","max","sum","map","filter",
        "zip","sorted","reversed","list","tuple","dict","set","bool","int","float","str","print","__import__",
    ]:
        try:
            g["__builtins__"][k] = getattr(__builtins__, k)
        except Exception:
            pass
    try:
        exec(src, g, loc)
        return True, loc
    except Exception as e:
        return False, str(e)

def _run_tests(compiled_loc: Dict[str, Any] | None, tests: list[dict]) -> float:
    if not isinstance(compiled_loc, dict):
        return 0.0
    fn = compiled_loc.get("solve")
    if not callable(fn):
        return 0.0
    passed = 0
    total = max(1, len(tests or []))
    try:
        for t in (tests or []):
            ok = False
            try:
                out = fn(*t.get("inp", []))
                ok = (out == t.get("out"))
            except Exception:
                ok = False
            if ok:
                passed += 1


def _safe_compile(src: str) -> tuple[bool, Any]:
    g: Dict[str, Any] = {"__builtins__": {}}
    loc: Dict[str, Any] = {}
    for k in [
        "abs","all","any","enumerate","len","range","min","max","sum","map","filter",
        "zip","sorted","reversed","list","tuple","dict","set","bool","int","float","str","print","__import__",
    ]:
        try:
            g["__builtins__"][k] = getattr(__builtins__, k)
        except Exception:
            pass
    try:
        exec(src, g, loc)
        return True, loc
    except Exception as e:
        return False, str(e)

def _run_tests(compiled_loc: Dict[str, Any] | None, tests: list[dict]) -> float:
    if not isinstance(compiled_loc, dict):
        return 0.0
    fn = compiled_loc.get("solve")
    if not callable(fn):
        return 0.0
    passed = 0
    total = max(1, len(tests or []))
    try:
        for t in (tests or []):
            ok = False
            try:
                out = fn(*t.get("inp", []))
                ok = (out == t.get("out"))
            except Exception:
                ok = False
            if ok:
                passed += 1
    except Exception:
        return 0.0
    return float(passed) / float(total)

def grade_staged(src: str, task: Dict[str, Any]) -> tuple[StageScore, list[str]]:
    fmt = 1.0 if looks_like_pure_code(src) else 0.0
    sig_ok = 1.0 if signature_matches(src, str(task.get("signature", "def solve():"))) else 0.0
    style_ok, hints = _static_checks(src, allowed_stdlib=True)
    comp_ok, mod_or_err = _safe_compile(src)
    tests_pass = _run_tests(mod_or_err if comp_ok else None, task.get("tests", [])) if comp_ok else 0.0
    return StageScore(fmt, 1.0 if comp_ok else 0.0, sig_ok, float(style_ok), float(tests_pass)), hints

def stage_reward(sc: StageScore) -> float:
    return (
        0.10 * sc.format_ok
        + 0.10 * sc.signature_ok
        + 0.20 * sc.style_ok
        + 0.20 * sc.compile_ok
        + 0.40 * sc.tests_pass
    )


    fn = compiled_loc.get("solve")
    if not callable(fn):
        return 0.0
    passed = 0
    total = max(1, len(tests or []))
    try:
        for t in (tests or []):
            ok = False
            try:
                out = fn(*t.get("inp", []))
                ok = (out == t.get("out"))
            except Exception:
                ok = False
            if ok:
                passed += 1
    except Exception:
        return 0.0
    return float(passed) / float(total)

def grade_staged(src: str, task: Dict[str, Any]) -> tuple[StageScore, list[str]]:
    fmt = 1.0 if looks_like_pure_code(src) else 0.0
    sig_ok = 1.0 if signature_matches(src, str(task.get("signature", "def solve():"))) else 0.0
    style_ok, hints = _static_checks(src, allowed_stdlib=True)
    comp_ok, mod_or_err = _safe_compile(src)
    tests_pass = _run_tests(mod_or_err if comp_ok else None, task.get("tests", [])) if comp_ok else 0.0
    return StageScore(fmt, 1.0 if comp_ok else 0.0, sig_ok, float(style_ok), float(tests_pass)), hints

def stage_reward(sc: StageScore) -> float:
    return (
        0.10 * sc.format_ok
        + 0.10 * sc.signature_ok
        + 0.20 * sc.style_ok
        + 0.20 * sc.compile_ok
        + 0.40 * sc.tests_pass
    )


                out = fn(*t.get("inp", []))
                ok = (out == t.get("out"))
            except Exception:
                ok = False
            if ok:
                passed += 1
    except Exception:
        return 0.0
    return float(passed) / float(total)

def grade_staged(src: str, task: Dict[str, Any]) -> tuple[StageScore, list[str]]:
    fmt = 1.0 if looks_like_pure_code(src) else 0.0
    sig_ok = 1.0 if signature_matches(src, str(task.get("signature", "def solve():"))) else 0.0
    style_ok, hints = _static_checks(src, allowed_stdlib=True)
    comp_ok, mod_or_err = _safe_compile(src)
    tests_pass = _run_tests(mod_or_err if comp_ok else None, task.get("tests", [])) if comp_ok else 0.0
    return StageScore(fmt, 1.0 if comp_ok else 0.0, sig_ok, float(style_ok), float(tests_pass)), hints

def stage_reward(sc: StageScore) -> float:
    return (
        0.10 * sc.format_ok
        + 0.10 * sc.signature_ok
        + 0.20 * sc.style_ok
        + 0.20 * sc.compile_ok
        + 0.40 * sc.tests_pass
    )


from .common_imports import *
from .generation import generate_text
from .train import sft_step

def mutate_prompt(prompt: str) -> str:
    muts = [
        " Optimize for speed.",
        " Make it memory efficient.",
        " Add error handling.",
        " Make it object-oriented.",
        " Include unit tests.",
    ]
    try:
        return (prompt or "").strip() + random.choice(muts)
    except Exception:
        return prompt

def heuristic_reward(output: str, prompt: str, max_gen_len: int) -> float:
    try:
        score = 0.0
        keywords = ["def", "class", "return", ":", "(", ")"]
        score += sum((1 if (k in output) else 0) for k in keywords) / float(len(keywords))
        try:
            first_token = (prompt or "").strip().split()[0].lower()
        except Exception:
            first_token = ""
        if first_token and (first_token in (output or "").lower()):
            score += 0.3
        try:
            score += 0.2 * (len(output or "") / float(max(1, max_gen_len * 4)))
        except Exception:
            pass
        return float(min(1.0, max(0.0, score)))
    except Exception:
        return 0.0

def evolve_population(prompts: List[str], scores: List[float], top_k: int, pop: int) -> List[str]:
    try:
        idxs = list(range(len(prompts)))
        idxs.sort(key=lambda i: scores[i])
        top = idxs[-int(max(1, top_k)) :]
        survivors = [prompts[i] for i in top]
    except Exception:
        survivors = prompts[: max(1, top_k)]
    new_gen: List[str] = list(survivors)
    while len(new_gen) < int(pop):
        try:
            parent = random.choice(survivors)
        except Exception:
            parent = survivors[0] if survivors else ""
        child = mutate_prompt(parent)
        new_gen.append(child)
    return new_gen[: int(pop)]

def evolution_loop(tok, model, mem: "Memory", cfg: Dict[str, Any]) -> None:
    try:
        num_agents = int(cfg.get("evo_num_agents", 5))
        num_generations = int(cfg.get("evo_num_generations", 10))
        top_k = int(cfg.get("evo_top_k_survivors", 2))
        max_prompt_len = int(cfg.get("evo_max_prompt_len", 80))
        max_gen_len = int(cfg.get("evo_max_new_tokens", 100))
    except Exception:
        num_agents, num_generations, top_k, max_prompt_len, max_gen_len = (5, 10, 2, 80, 100)
    base_tasks = list(cfg.get("evo_base_tasks", [])) if isinstance(cfg.get("evo_base_tasks"), list) else [
        "Write a Python function to reverse a string.",
        "Create a class in Python that tracks bank account transactions.",
        "Write a program that sorts a list using bubble sort.",
        "Create a Python script to calculate Fibonacci numbers recursively.",
        "Implement a to-do list manager using Python dictionaries.",
    ]
    # Seed initial population
    prompts: List[str] = (base_tasks[: num_agents] if len(base_tasks) >= num_agents else (base_tasks + [random.choice(base_tasks) for _ in range(max(0, num_agents - len(base_tasks)))]))
    # Per-agent memory for novelty/feedback
    agent_memory: Dict[str, List[str]] = {f"agent_{i}": [] for i in range(num_agents)}
    # Optional meta prompting via MemoryAugmentConfig
    def maybe_augment(p: str) -> str:
        try:
            if bool(cfg.get("use_memory", False)):
                from agi_dw.core.memory.service import MemoryAugmentConfig, augment_observation  # type: ignore
                mem_cfg = MemoryAugmentConfig(use_memory=True, mem_path=str(cfg.get("episodic_mem_path", "") or "") or None, mem_topk=int(cfg.get("episodic_mem_topk", 3)), mem_recency=float(cfg.get("episodic_mem_recency", 0.0)), mem_query=str(cfg.get("episodic_mem_query", "") or "") or None, index_k=int(cfg.get("index_k", 0)), index_path=str(cfg.get("index_path", "") or "") or None)
                obs = {"kind": "text", "content": p, "meta": {}}
                root_dir = Path(__file__).resolve().parents[3]
                obs_aug, _, _ = augment_observation(obs, root_dir, mem_cfg)
                mem_lines: List[str] = []
                for txt in (obs_aug.get("memory", []) if isinstance(obs_aug, dict) else []):
                    if not txt:
                        continue
                    for ln in str(txt).splitlines()[:40]:
                        mem_lines.append("# MEM " + ln)
                if mem_lines:
                    return (p or "").strip() + "\n\n" + "\n".join(mem_lines) + "\n"
        except Exception:
            pass
        return p
    for gen in range(num_generations):
        outputs: List[str] = []
        scores: List[float] = []
        for i, p in enumerate(prompts):
            try:
                p_trim = (p or "")[:max_prompt_len]
                p_aug = maybe_augment(p_trim)
                out = generate_text(tok, model, p_aug, max_new_tokens=max_gen_len)
                sc = heuristic_reward(out, p_trim, max_gen_len)
                outputs.append(out)
                scores.append(sc)
                try:
                    agent_memory[f"agent_{i}"].append(out)
                except Exception:
                    pass
                try:
                    mem.log({"mode": "evolution", "gen": gen, "agent": i, "prompt": p_trim, "score": float(sc), "output_head": (out or "")})
                except Exception:
                    pass
            except Exception as _e:
                outputs.append("")
                scores.append(0.0)
        # Optionally train on survivors
        try:
            if bool(cfg.get("evo_train_survivors", False)):
                # Select top-k and SFT each (few steps)
                idxs = list(range(len(prompts)))
                idxs.sort(key=lambda i: scores[i])
                top = idxs[-int(max(1, top_k)) :]
                for j in top:
                    # Use augmented prompt for consistency
                    p_aug = maybe_augment((prompts[j] or "")[:max_prompt_len])
                    _ = sft_step(tok, model, p_aug, outputs[j], lr=float(cfg.get("lr", 1e-4)), steps=int(cfg.get("evo_train_steps", 3)))
        except Exception:
            pass
        # Evolve next generation
        prompts = evolve_population(prompts, scores, top_k=top_k, pop=num_agents)
    # Final log of population
    try:
        for i, p in enumerate(prompts):
            mem.log({"mode": "evolution_final", "agent": i, "prompt": p})
    except Exception:
        pass


from .common_imports import *
from .generation import generate_text
from .train import sft_step

def mutate_prompt(prompt: str) -> str:
    muts = [
        " Optimize for speed.",
        " Make it memory efficient.",
        " Add error handling.",
        " Make it object-oriented.",
        " Include unit tests.",
    ]
    try:
        return (prompt or "").strip() + random.choice(muts)
    except Exception:
        return prompt

def heuristic_reward(output: str, prompt: str, max_gen_len: int) -> float:
    try:
        score = 0.0
        keywords = ["def", "class", "return", ":", "(", ")"]
        score += sum((1 if (k in output) else 0) for k in keywords) / float(len(keywords))
        try:
            first_token = (prompt or "").strip().split()[0].lower()
        except Exception:
            first_token = ""
        if first_token and (first_token in (output or "").lower()):
            score += 0.3
        try:
            score += 0.2 * (len(output or "") / float(max(1, max_gen_len * 4)))
        except Exception:
            pass
        return float(min(1.0, max(0.0, score)))
    except Exception:
        return 0.0


from .common_imports import *
from .generation import generate_text
from .train import sft_step

def mutate_prompt(prompt: str) -> str:
    muts = [
        " Optimize for speed.",
        " Make it memory efficient.",
        " Add error handling.",
        " Make it object-oriented.",
        " Include unit tests.",
    ]
    try:
        return (prompt or "").strip() + random.choice(muts)
    except Exception:
        return prompt

def heuristic_reward(output: str, prompt: str, max_gen_len: int) -> float:
    try:
        score = 0.0
        keywords = ["def", "class", "return", ":", "(", ")"]
        score += sum((1 if (k in output) else 0) for k in keywords) / float(len(keywords))
        try:
            first_token = (prompt or "").strip().split()[0].lower()
        except Exception:
            first_token = ""
        if first_token and (first_token in (output or "").lower()):
            score += 0.3
        try:
            score += 0.2 * (len(output or "") / float(max(1, max_gen_len * 4)))
        except Exception:
            pass
        return float(min(1.0, max(0.0, score)))
    except Exception:
        return 0.0

def evolve_population(prompts: List[str], scores: List[float], top_k: int, pop: int) -> List[str]:
    try:
        idxs = list(range(len(prompts)))
        idxs.sort(key=lambda i: scores[i])
        top = idxs[-int(max(1, top_k)) :]
        survivors = [prompts[i] for i in top]
    except Exception:
        survivors = prompts[: max(1, top_k)]
    new_gen: List[str] = list(survivors)
    while len(new_gen) < int(pop):
        try:
            parent = random.choice(survivors)
        except Exception:
            parent = survivors[0] if survivors else ""
        child = mutate_prompt(parent)
        new_gen.append(child)
    return new_gen[: int(pop)]

def evolution_loop(tok, model, mem: "Memory", cfg: Dict[str, Any]) -> None:


def heuristic_reward(output: str, prompt: str, max_gen_len: int) -> float:
    try:
        score = 0.0
        keywords = ["def", "class", "return", ":", "(", ")"]
        score += sum((1 if (k in output) else 0) for k in keywords) / float(len(keywords))
        try:
            first_token = (prompt or "").strip().split()[0].lower()
        except Exception:
            first_token = ""
        if first_token and (first_token in (output or "").lower()):
            score += 0.3
        try:
            score += 0.2 * (len(output or "") / float(max(1, max_gen_len * 4)))
        except Exception:
            pass
        return float(min(1.0, max(0.0, score)))
    except Exception:
        return 0.0

def evolve_population(prompts: List[str], scores: List[float], top_k: int, pop: int) -> List[str]:
    try:
        idxs = list(range(len(prompts)))
        idxs.sort(key=lambda i: scores[i])
        top = idxs[-int(max(1, top_k)) :]
        survivors = [prompts[i] for i in top]
    except Exception:
        survivors = prompts[: max(1, top_k)]
    new_gen: List[str] = list(survivors)
    while len(new_gen) < int(pop):
        try:
            parent = random.choice(survivors)
        except Exception:
            parent = survivors[0] if survivors else ""
        child = mutate_prompt(parent)
        new_gen.append(child)
    return new_gen[: int(pop)]

def evolution_loop(tok, model, mem: "Memory", cfg: Dict[str, Any]) -> None:
    try:
        num_agents = int(cfg.get("evo_num_agents", 5))
        num_generations = int(cfg.get("evo_num_generations", 10))
        top_k = int(cfg.get("evo_top_k_survivors", 2))
        max_prompt_len = int(cfg.get("evo_max_prompt_len", 80))
        max_gen_len = int(cfg.get("evo_max_new_tokens", 100))
    except Exception:
        num_agents, num_generations, top_k, max_prompt_len, max_gen_len = (5, 10, 2, 80, 100)
    base_tasks = list(cfg.get("evo_base_tasks", [])) if isinstance(cfg.get("evo_base_tasks"), list) else [
        "Write a Python function to reverse a string.",
        "Create a class in Python that tracks bank account transactions.",
        "Write a program that sorts a list using bubble sort.",
        "Create a Python script to calculate Fibonacci numbers recursively.",
        "Implement a to-do list manager using Python dictionaries.",
    ]
    # Seed initial population
    prompts: List[str] = (base_tasks[: num_agents] if len(base_tasks) >= num_agents else (base_tasks + [random.choice(base_tasks) for _ in range(max(0, num_agents - len(base_tasks)))]))
    # Per-agent memory for novelty/feedback


        return 0.0

def evolve_population(prompts: List[str], scores: List[float], top_k: int, pop: int) -> List[str]:
    try:
        idxs = list(range(len(prompts)))
        idxs.sort(key=lambda i: scores[i])
        top = idxs[-int(max(1, top_k)) :]
        survivors = [prompts[i] for i in top]
    except Exception:
        survivors = prompts[: max(1, top_k)]
    new_gen: List[str] = list(survivors)
    while len(new_gen) < int(pop):
        try:
            parent = random.choice(survivors)
        except Exception:
            parent = survivors[0] if survivors else ""
        child = mutate_prompt(parent)
        new_gen.append(child)
    return new_gen[: int(pop)]

def evolution_loop(tok, model, mem: "Memory", cfg: Dict[str, Any]) -> None:
    try:
        num_agents = int(cfg.get("evo_num_agents", 5))
        num_generations = int(cfg.get("evo_num_generations", 10))
        top_k = int(cfg.get("evo_top_k_survivors", 2))
        max_prompt_len = int(cfg.get("evo_max_prompt_len", 80))
        max_gen_len = int(cfg.get("evo_max_new_tokens", 100))
    except Exception:
        num_agents, num_generations, top_k, max_prompt_len, max_gen_len = (5, 10, 2, 80, 100)
    base_tasks = list(cfg.get("evo_base_tasks", [])) if isinstance(cfg.get("evo_base_tasks"), list) else [
        "Write a Python function to reverse a string.",
        "Create a class in Python that tracks bank account transactions.",
        "Write a program that sorts a list using bubble sort.",
        "Create a Python script to calculate Fibonacci numbers recursively.",
        "Implement a to-do list manager using Python dictionaries.",
    ]
    # Seed initial population
    prompts: List[str] = (base_tasks[: num_agents] if len(base_tasks) >= num_agents else (base_tasks + [random.choice(base_tasks) for _ in range(max(0, num_agents - len(base_tasks)))]))
    # Per-agent memory for novelty/feedback
    agent_memory: Dict[str, List[str]] = {f"agent_{i}": [] for i in range(num_agents)}
    # Optional meta prompting via MemoryAugmentConfig
    def maybe_augment(p: str) -> str:
        try:
            if bool(cfg.get("use_memory", False)):
                from agi_dw.core.memory.service import MemoryAugmentConfig, augment_observation  # type: ignore
                mem_cfg = MemoryAugmentConfig(use_memory=True, mem_path=str(cfg.get("episodic_mem_path", "") or "") or None, mem_topk=int(cfg.get("episodic_mem_topk", 3)), mem_recency=float(cfg.get("episodic_mem_recency", 0.0)), mem_query=str(cfg.get("episodic_mem_query", "") or "") or None, index_k=int(cfg.get("index_k", 0)), index_path=str(cfg.get("index_path", "") or "") or None)
                obs = {"kind": "text", "content": p, "meta": {}}
                root_dir = Path(__file__).resolve().parents[3]
                obs_aug, _, _ = augment_observation(obs, root_dir, mem_cfg)
                mem_lines: List[str] = []
                for txt in (obs_aug.get("memory", []) if isinstance(obs_aug, dict) else []):
                    if not txt:
                        continue
                    for ln in str(txt).splitlines()[:40]:
                        mem_lines.append("# MEM " + ln)
                if mem_lines:
                    return (p or "").strip() + "\n\n" + "\n".join(mem_lines) + "\n"
        except Exception:
            pass
        return p
    for gen in range(num_generations):
        outputs: List[str] = []
        scores: List[float] = []
        for i, p in enumerate(prompts):
            try:
                p_trim = (p or "")[:max_prompt_len]
                p_aug = maybe_augment(p_trim)
                out = generate_text(tok, model, p_aug, max_new_tokens=max_gen_len)
                sc = heuristic_reward(out, p_trim, max_gen_len)
                outputs.append(out)
                scores.append(sc)
                try:
                    agent_memory[f"agent_{i}"].append(out)
                except Exception:
                    pass
                try:
                    mem.log({"mode": "evolution", "gen": gen, "agent": i, "prompt": p_trim, "score": float(sc), "output_head": (out or "")})
                except Exception:
                    pass
            except Exception as _e:
                outputs.append("")
                scores.append(0.0)
        # Optionally train on survivors
        try:
            if bool(cfg.get("evo_train_survivors", False)):
                # Select top-k and SFT each (few steps)
                idxs = list(range(len(prompts)))
                idxs.sort(key=lambda i: scores[i])
                top = idxs[-int(max(1, top_k)) :]
                for j in top:
                    # Use augmented prompt for consistency
                    p_aug = maybe_augment((prompts[j] or "")[:max_prompt_len])
                    _ = sft_step(tok, model, p_aug, outputs[j], lr=float(cfg.get("lr", 1e-4)), steps=int(cfg.get("evo_train_steps", 3)))
        except Exception:
            pass
        # Evolve next generation
        prompts = evolve_population(prompts, scores, top_k=top_k, pop=num_agents)
    # Final log of population
    try:
        for i, p in enumerate(prompts):
            mem.log({"mode": "evolution_final", "agent": i, "prompt": p})
    except Exception:
        pass


    try:
        num_agents = int(cfg.get("evo_num_agents", 5))
        num_generations = int(cfg.get("evo_num_generations", 10))
        top_k = int(cfg.get("evo_top_k_survivors", 2))
        max_prompt_len = int(cfg.get("evo_max_prompt_len", 80))
        max_gen_len = int(cfg.get("evo_max_new_tokens", 100))
    except Exception:
        num_agents, num_generations, top_k, max_prompt_len, max_gen_len = (5, 10, 2, 80, 100)
    base_tasks = list(cfg.get("evo_base_tasks", [])) if isinstance(cfg.get("evo_base_tasks"), list) else [
        "Write a Python function to reverse a string.",
        "Create a class in Python that tracks bank account transactions.",
        "Write a program that sorts a list using bubble sort.",
        "Create a Python script to calculate Fibonacci numbers recursively.",
        "Implement a to-do list manager using Python dictionaries.",
    ]
    # Seed initial population
    prompts: List[str] = (base_tasks[: num_agents] if len(base_tasks) >= num_agents else (base_tasks + [random.choice(base_tasks) for _ in range(max(0, num_agents - len(base_tasks)))]))
    # Per-agent memory for novelty/feedback
    agent_memory: Dict[str, List[str]] = {f"agent_{i}": [] for i in range(num_agents)}
    # Optional meta prompting via MemoryAugmentConfig
    def maybe_augment(p: str) -> str:
        try:
            if bool(cfg.get("use_memory", False)):
                from agi_dw.core.memory.service import MemoryAugmentConfig, augment_observation  # type: ignore
                mem_cfg = MemoryAugmentConfig(use_memory=True, mem_path=str(cfg.get("episodic_mem_path", "") or "") or None, mem_topk=int(cfg.get("episodic_mem_topk", 3)), mem_recency=float(cfg.get("episodic_mem_recency", 0.0)), mem_query=str(cfg.get("episodic_mem_query", "") or "") or None, index_k=int(cfg.get("index_k", 0)), index_path=str(cfg.get("index_path", "") or "") or None)
                obs = {"kind": "text", "content": p, "meta": {}}
                root_dir = Path(__file__).resolve().parents[3]
                obs_aug, _, _ = augment_observation(obs, root_dir, mem_cfg)
                mem_lines: List[str] = []
                for txt in (obs_aug.get("memory", []) if isinstance(obs_aug, dict) else []):
                    if not txt:
                        continue
                    for ln in str(txt).splitlines()[:40]:
                        mem_lines.append("# MEM " + ln)
                if mem_lines:
                    return (p or "").strip() + "\n\n" + "\n".join(mem_lines) + "\n"
        except Exception:
            pass
        return p
    for gen in range(num_generations):
        outputs: List[str] = []
        scores: List[float] = []
        for i, p in enumerate(prompts):
            try:
                p_trim = (p or "")[:max_prompt_len]
                p_aug = maybe_augment(p_trim)
                out = generate_text(tok, model, p_aug, max_new_tokens=max_gen_len)
                sc = heuristic_reward(out, p_trim, max_gen_len)
                outputs.append(out)
                scores.append(sc)
                try:
                    agent_memory[f"agent_{i}"].append(out)
                except Exception:
                    pass
                try:
                    mem.log({"mode": "evolution", "gen": gen, "agent": i, "prompt": p_trim, "score": float(sc), "output_head": (out or "")})
                except Exception:
                    pass
            except Exception as _e:


from .common_imports import *

def _dev_episode(repo: str, extra_args: str | None = None, timeout_sec: int = 1200) -> Tuple[bool, Dict[str, Any]]:
    """Run one dev loop episode on a repo; return (ok, info)."""
    root = Path(__file__).resolve().parents[3]
    dev_script = root / 'scripts' / 'loops' / 'run_loop_dev.py'
    if not dev_script.exists():
        return False, {"error": "dev_script_not_found", "path": str(dev_script)}
    cmd = f"python {dev_script} --repo {repo} --apply-meta"
    if extra_args and extra_args.strip():
        cmd = f"{cmd} {extra_args.strip()}"
    try:
        p = subprocess.run(shlex.split(cmd), cwd=str(root), capture_output=True, text=True, timeout=timeout_sec)
        ok = (p.returncode == 0)
        info = {"returncode": p.returncode, "stdout": p.stdout[-2000:], "stderr": p.stderr[-1000:]}
        return ok, info
    except Exception as e:
        return False, {"error": str(e)}

def _ensure_local_repo(local_uri: str) -> str:
    """Ensure a local:/abs/path repo directory exists; return normalized URI."""
    try:
        if local_uri.startswith("local:"):
            p = Path(local_uri.split(":", 1)[1]).resolve()
            p.mkdir(parents=True, exist_ok=True)
            # Optionally seed a README to keep directory non-empty
            readme = p / "README.md"
            if not readme.exists():
                try:
                    readme.write_text("selfplay scratch repo\n", encoding="utf-8")
                except Exception:
                    pass
            # Initialize a minimal git repo so dev loop patch/apply works
            try:
                if not (p / ".git").exists():
                    import subprocess as _sp  # type: ignore
                    _sp.run(["git", "init"], cwd=str(p), check=False, capture_output=True)
                    # Configure a local identity if not set
                    _sp.run(["git", "config", "user.email", "selfplay@peytontolbert.com"], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "config", "user.name", "Selfplay"], cwd=str(p), check=False, capture_output=True)
                    # Seed a trivial file to allow diffs (no tests to avoid masking failures)
                    seed = p / "main.py"
                    if not seed.exists():
                        try:
                            seed.write_text("def hello():\n    return 'hello'\n", encoding="utf-8")
                        except Exception:
                            pass
                    _sp.run(["git", "add", "."], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "commit", "-m", "init selfplay scratch"], cwd=str(p), check=False, capture_output=True)
            except Exception:
                pass
            # Do not auto-seed tests; leave repo empty so real tests must be provided
            return f"local:{str(p)}"
    except Exception:
        pass
    return local_uri

def _is_local_repo_usable(local_uri: str) -> bool:
    """Heuristic: a local repo is usable if it contains a .git dir or more than a trivial README."""
    try:
        if not local_uri.startswith("local:"):
            return True
        p = Path(local_uri.split(":", 1)[1]).resolve()
        if not p.exists():
            return False
        if (p / ".git").exists():
            return True
        # Count non-hidden files
        count = 0
        for child in p.iterdir():
            if child.name.startswith("."):
                continue
            if child.is_file() and child.name.lower() == "readme.md":
                # ignore seed readme
                continue
            count += 1
            if count >= 1:
                return True
        return False
    except Exception:
        return False


from .common_imports import *

def _dev_episode(repo: str, extra_args: str | None = None, timeout_sec: int = 1200) -> Tuple[bool, Dict[str, Any]]:
    """Run one dev loop episode on a repo; return (ok, info)."""
    root = Path(__file__).resolve().parents[3]
    dev_script = root / 'scripts' / 'loops' / 'run_loop_dev.py'
    if not dev_script.exists():
        return False, {"error": "dev_script_not_found", "path": str(dev_script)}
    cmd = f"python {dev_script} --repo {repo} --apply-meta"
    if extra_args and extra_args.strip():
        cmd = f"{cmd} {extra_args.strip()}"
    try:
        p = subprocess.run(shlex.split(cmd), cwd=str(root), capture_output=True, text=True, timeout=timeout_sec)
        ok = (p.returncode == 0)
        info = {"returncode": p.returncode, "stdout": p.stdout[-2000:], "stderr": p.stderr[-1000:]}
        return ok, info
    except Exception as e:
        return False, {"error": str(e)}

def _ensure_local_repo(local_uri: str) -> str:
    """Ensure a local:/abs/path repo directory exists; return normalized URI."""
    try:
        if local_uri.startswith("local:"):
            p = Path(local_uri.split(":", 1)[1]).resolve()
            p.mkdir(parents=True, exist_ok=True)
            # Optionally seed a README to keep directory non-empty
            readme = p / "README.md"
            if not readme.exists():
                try:
                    readme.write_text("selfplay scratch repo\n", encoding="utf-8")
                except Exception:
                    pass
            # Initialize a minimal git repo so dev loop patch/apply works
            try:
                if not (p / ".git").exists():
                    import subprocess as _sp  # type: ignore
                    _sp.run(["git", "init"], cwd=str(p), check=False, capture_output=True)
                    # Configure a local identity if not set


from .common_imports import *

def _dev_episode(repo: str, extra_args: str | None = None, timeout_sec: int = 1200) -> Tuple[bool, Dict[str, Any]]:
    """Run one dev loop episode on a repo; return (ok, info)."""
    root = Path(__file__).resolve().parents[3]
    dev_script = root / 'scripts' / 'loops' / 'run_loop_dev.py'
    if not dev_script.exists():
        return False, {"error": "dev_script_not_found", "path": str(dev_script)}
    cmd = f"python {dev_script} --repo {repo} --apply-meta"
    if extra_args and extra_args.strip():
        cmd = f"{cmd} {extra_args.strip()}"
    try:
        p = subprocess.run(shlex.split(cmd), cwd=str(root), capture_output=True, text=True, timeout=timeout_sec)
        ok = (p.returncode == 0)
        info = {"returncode": p.returncode, "stdout": p.stdout[-2000:], "stderr": p.stderr[-1000:]}
        return ok, info
    except Exception as e:
        return False, {"error": str(e)}

def _ensure_local_repo(local_uri: str) -> str:
    """Ensure a local:/abs/path repo directory exists; return normalized URI."""
    try:
        if local_uri.startswith("local:"):
            p = Path(local_uri.split(":", 1)[1]).resolve()
            p.mkdir(parents=True, exist_ok=True)
            # Optionally seed a README to keep directory non-empty
            readme = p / "README.md"
            if not readme.exists():
                try:
                    readme.write_text("selfplay scratch repo\n", encoding="utf-8")
                except Exception:
                    pass
            # Initialize a minimal git repo so dev loop patch/apply works
            try:
                if not (p / ".git").exists():
                    import subprocess as _sp  # type: ignore
                    _sp.run(["git", "init"], cwd=str(p), check=False, capture_output=True)
                    # Configure a local identity if not set
                    _sp.run(["git", "config", "user.email", "selfplay@peytontolbert.com"], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "config", "user.name", "Selfplay"], cwd=str(p), check=False, capture_output=True)
                    # Seed a trivial file to allow diffs (no tests to avoid masking failures)
                    seed = p / "main.py"
                    if not seed.exists():
                        try:
                            seed.write_text("def hello():\n    return 'hello'\n", encoding="utf-8")
                        except Exception:
                            pass
                    _sp.run(["git", "add", "."], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "commit", "-m", "init selfplay scratch"], cwd=str(p), check=False, capture_output=True)
            except Exception:
                pass
            # Do not auto-seed tests; leave repo empty so real tests must be provided
            return f"local:{str(p)}"
    except Exception:
        pass
    return local_uri

def _is_local_repo_usable(local_uri: str) -> bool:
    """Heuristic: a local repo is usable if it contains a .git dir or more than a trivial README."""
    try:
        if not local_uri.startswith("local:"):
            return True
        p = Path(local_uri.split(":", 1)[1]).resolve()
        if not p.exists():
            return False
        if (p / ".git").exists():
            return True
        # Count non-hidden files
        count = 0
        for child in p.iterdir():
            if child.name.startswith("."):
                continue
            if child.is_file() and child.name.lower() == "readme.md":
                # ignore seed readme
                continue
            count += 1


                    # Configure a local identity if not set
                    _sp.run(["git", "config", "user.email", "selfplay@peytontolbert.com"], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "config", "user.name", "Selfplay"], cwd=str(p), check=False, capture_output=True)
                    # Seed a trivial file to allow diffs (no tests to avoid masking failures)
                    seed = p / "main.py"
                    if not seed.exists():
                        try:
                            seed.write_text("def hello():\n    return 'hello'\n", encoding="utf-8")
                        except Exception:
                            pass
                    _sp.run(["git", "add", "."], cwd=str(p), check=False, capture_output=True)
                    _sp.run(["git", "commit", "-m", "init selfplay scratch"], cwd=str(p), check=False, capture_output=True)
            except Exception:
                pass
            # Do not auto-seed tests; leave repo empty so real tests must be provided
            return f"local:{str(p)}"
    except Exception:
        pass
    return local_uri

def _is_local_repo_usable(local_uri: str) -> bool:
    """Heuristic: a local repo is usable if it contains a .git dir or more than a trivial README."""
    try:
        if not local_uri.startswith("local:"):
            return True
        p = Path(local_uri.split(":", 1)[1]).resolve()
        if not p.exists():
            return False
        if (p / ".git").exists():
            return True
        # Count non-hidden files
        count = 0
        for child in p.iterdir():
            if child.name.startswith("."):
                continue
            if child.is_file() and child.name.lower() == "readme.md":
                # ignore seed readme
                continue
            count += 1
            if count >= 1:
                return True
        return False
    except Exception:
        return False


from .common_imports import *
try:
    from .verifier_stages import StageScore  # type: ignore
except Exception:
    StageScore = None  # type: ignore

def _compute_reward(meta: Dict[str, Any]) -> Tuple[float, Dict[str, float]]:
    """Compute composite reward from rep/verifier/WM/latency/policy signals.

    Returns (reward, components) with reward in [0,1].
    """
    rep = meta.get("rep", {}) if isinstance(meta, dict) else {}
    passed = float(rep.get("passed", 0) or 0)
    total = float(rep.get("total", 0) or 0)
    pass_ratio = (passed / total) if total > 0 else 0.0
    wm_risk = float(meta.get("wm_risk", meta.get("v_risk", 0.5) or 0.5))
    verifier_sp = float(meta.get("v_sp", meta.get("success_prob", 0.5) or 0.5))
    timeouts = float(rep.get("timeouts", 0) or 0)
    timeout_pen = (timeouts / total) if total > 0 else 0.0
    try:
        timeout_sec = float(os.environ.get("SELFPLAY_TEST_TIMEOUT_SEC", "1.5") or 1.5)
    except Exception:
        timeout_sec = 1.5
    elapsed_ms = float(rep.get("elapsed_ms", 0) or 0)
    denom_ms = max(1.0, timeout_sec * 1000.0 * max(1.0, total))
    latency_pen = min(1.0, max(0.0, elapsed_ms / denom_ms))
    policy_pen = 1.0 if meta.get("policy_reason") else 0.0
    # Optional assimilation gain between two in-context phases
    try:
        score_before = float(meta.get("score_before", None)) if meta.get("score_before") is not None else None
        score_after = float(meta.get("score_after", None)) if meta.get("score_after") is not None else None
        assimilation_gain = float(max(0.0, (score_after - score_before))) if (score_before is not None and score_after is not None) else 0.0
    except Exception:
        assimilation_gain = 0.0
    # Optional enriched terms for governed composite reward
    try:
        diff_loc = float(meta.get("diff_loc", 0.0) or 0.0)
    except Exception:
        diff_loc = 0.0
    try:
        risk_score = float(meta.get("risk_score", meta.get("v_risk", 0.0) or 0.0))
    except Exception:
        risk_score = 0.0
    try:
        lint_score = float(meta.get("lint_score", 0.0) or 0.0)
    except Exception:
        lint_score = 0.0
    try:
        perf_delta = float(meta.get("perf_delta", 0.0) or 0.0)
    except Exception:
        perf_delta = 0.0
    comps = {
        "pass_ratio": float(pass_ratio),
        "wm_term": float(1.0 - wm_risk),
        "verifier_term": float(verifier_sp),
        "timeout_penalty": float(timeout_pen),
        "latency_penalty": float(latency_pen),
        "policy_penalty": float(policy_pen),
        "assimilation_gain": float(assimilation_gain),
        # New governed terms
        "diff_loc": float(diff_loc),
        "risk_score": float(risk_score),
        "lint_score": float(lint_score),
        "perf_delta": float(perf_delta),
    }
    # Weights configurable via env
    def _w(name: str, default: float) -> float:
        try:
            return float(os.environ.get(name, str(default)) or default)
        except Exception:
            return default
    # Classic terms
    w_pass = _w("REWARD_W_PASS", 0.8)
    w_wm = _w("REWARD_W_WM", 0.15)
    w_v = _w("REWARD_W_VERIFIER", 0.15)
    w_to = _w("REWARD_W_TIMEOUT", 0.05)
    w_lat = _w("REWARD_W_LATENCY", 0.05)
    w_pol = _w("REWARD_W_POLICY", 0.05)
    w_assim = _w("REWARD_W_ASSIM", 0.10)
    # Governed additions
    w_diff = _w("REWARD_W_DIFF", 0.02)
    w_risk = _w("REWARD_W_RISK", 0.10)
    w_style = _w("REWARD_W_STYLE", 0.02)
    w_perf = _w("REWARD_W_PERF", 0.02)
    # Hard gate option: zero out base positive mass unless all tests pass
    def _b(name: str, default: bool) -> bool:
        try:
            v = os.environ.get(name)
            if v is None or str(v).strip() == "":
                return default
            return str(v).strip().lower() not in ("0", "false", "no", "off")
        except Exception:
            return default
    hard_gate = _b("REWARD_HARD_GATE", False)
    base_signal = comps["pass_ratio"] if not hard_gate else (1.0 if (passed == total and total > 0) else 0.0)
    # Optional staged reward shaping
    try:
        stage_reward_val = float(meta.get("stage_reward", 0.0) or 0.0)
    except Exception:
        stage_reward_val = 0.0
    reward = (
        w_pass * base_signal
        + w_wm * comps["wm_term"]
        + w_v * comps["verifier_term"]
        - w_to * comps["timeout_penalty"]
        - w_lat * comps["latency_penalty"]
        - w_pol * comps["policy_penalty"]
        + w_assim * comps["assimilation_gain"]
        + 0.20 * float(stage_reward_val)
        # Governed terms from design: -lambda_diff*LOCd - lambda_risk*risk + lambda_style*lint + lambda_perf*Δbench
        - w_diff * comps["diff_loc"]
        - w_risk * comps["risk_score"]
        + w_style * comps["lint_score"]
        + w_perf * comps["perf_delta"]
    )
    reward = float(max(0.0, min(1.0, reward)))
    return reward, comps


        lint_score = 0.0
    try:
        perf_delta = float(meta.get("perf_delta", 0.0) or 0.0)
    except Exception:
        perf_delta = 0.0
    comps = {
        "pass_ratio": float(pass_ratio),
        "wm_term": float(1.0 - wm_risk),
        "verifier_term": float(verifier_sp),
        "timeout_penalty": float(timeout_pen),
        "latency_penalty": float(latency_pen),
        "policy_penalty": float(policy_pen),
        "assimilation_gain": float(assimilation_gain),
        # New governed terms
        "diff_loc": float(diff_loc),
        "risk_score": float(risk_score),
        "lint_score": float(lint_score),
        "perf_delta": float(perf_delta),
    }
    # Weights configurable via env
    def _w(name: str, default: float) -> float:
        try:
            return float(os.environ.get(name, str(default)) or default)
        except Exception:
            return default
    # Classic terms
    w_pass = _w("REWARD_W_PASS", 0.8)
    w_wm = _w("REWARD_W_WM", 0.15)
    w_v = _w("REWARD_W_VERIFIER", 0.15)
    w_to = _w("REWARD_W_TIMEOUT", 0.05)
    w_lat = _w("REWARD_W_LATENCY", 0.05)
    w_pol = _w("REWARD_W_POLICY", 0.05)
    w_assim = _w("REWARD_W_ASSIM", 0.10)
    # Governed additions
    w_diff = _w("REWARD_W_DIFF", 0.02)
    w_risk = _w("REWARD_W_RISK", 0.10)
    w_style = _w("REWARD_W_STYLE", 0.02)
    w_perf = _w("REWARD_W_PERF", 0.02)
    # Hard gate option: zero out base positive mass unless all tests pass
    def _b(name: str, default: bool) -> bool:
        try:
            v = os.environ.get(name)
            if v is None or str(v).strip() == "":
                return default
            return str(v).strip().lower() not in ("0", "false", "no", "off")


    # Weights configurable via env
    def _w(name: str, default: float) -> float:
        try:
            return float(os.environ.get(name, str(default)) or default)
        except Exception:
            return default
    # Classic terms
    w_pass = _w("REWARD_W_PASS", 0.8)
    w_wm = _w("REWARD_W_WM", 0.15)
    w_v = _w("REWARD_W_VERIFIER", 0.15)
    w_to = _w("REWARD_W_TIMEOUT", 0.05)
    w_lat = _w("REWARD_W_LATENCY", 0.05)
    w_pol = _w("REWARD_W_POLICY", 0.05)
    w_assim = _w("REWARD_W_ASSIM", 0.10)
    # Governed additions
    w_diff = _w("REWARD_W_DIFF", 0.02)
    w_risk = _w("REWARD_W_RISK", 0.10)
    w_style = _w("REWARD_W_STYLE", 0.02)
    w_perf = _w("REWARD_W_PERF", 0.02)
    # Hard gate option: zero out base positive mass unless all tests pass
    def _b(name: str, default: bool) -> bool:
        try:
            v = os.environ.get(name)
            if v is None or str(v).strip() == "":
                return default
            return str(v).strip().lower() not in ("0", "false", "no", "off")
        except Exception:
            return default
    hard_gate = _b("REWARD_HARD_GATE", False)
    base_signal = comps["pass_ratio"] if not hard_gate else (1.0 if (passed == total and total > 0) else 0.0)
    # Optional staged reward shaping
    try:
        stage_reward_val = float(meta.get("stage_reward", 0.0) or 0.0)
    except Exception:
        stage_reward_val = 0.0
    reward = (
        w_pass * base_signal
        + w_wm * comps["wm_term"]
        + w_v * comps["verifier_term"]
        - w_to * comps["timeout_penalty"]
        - w_lat * comps["latency_penalty"]
        - w_pol * comps["policy_penalty"]
        + w_assim * comps["assimilation_gain"]
        + 0.20 * float(stage_reward_val)
        # Governed terms from design: -lambda_diff*LOCd - lambda_risk*risk + lambda_style*lint + lambda_perf*Δbench
        - w_diff * comps["diff_loc"]
        - w_risk * comps["risk_score"]
        + w_style * comps["lint_score"]


from .common_imports import *

class Memory:
    def __init__(self, path: str) -> None:
        self.path = path

    def log(self, obj: Dict[str, Any]) -> None:
        try:
            p = Path(self.path)
            p.parent.mkdir(parents=True, exist_ok=True)
        except Exception:
            pass
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(obj, ensure_ascii=False) + "\n")

class Curriculum:
    def __init__(self) -> None:
        from collections import deque

        self.level = 0
        self.win_hist = deque(maxlen=100)
        self._ema: float | None = None
        # Dwell mechanics to stabilize level changes
        self._since_change: int = 0
        self._dwell_min: int = 5

    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:
            self._ema = (1 - alpha) * float(self._ema) + alpha * x
        # Use EMA-driven thresholds
        p = float(self._ema)
        self._since_change += 1
        if self._since_change >= self._dwell_min:
            if p > 0.7:
                self.level += 1
                self._since_change = 0
            elif p < 0.2 and self.level > 0:
                self.level -= 1
                self._since_change = 0

def _maybe_gate_curriculum(level_before: int, level_after: int, metrics: Dict[str, Any], require_approval: bool, timeout_sec: int) -> int:
    """If approval required and level changed, enqueue HITL approval and wait. Return final level."""
    if (not require_approval) or (level_before == level_after):
        return level_after
    try:
        # Reuse HITL queue if available
        from agi_dw.core.hitl.approval_queue import ApprovalQueue, ApprovalItem  # type: ignore
        root = Path(__file__).resolve().parents[3]
        q = ApprovalQueue(root)
        if not q.exists():  # type: ignore[attr-defined]
            return level_after
        ts = __import__("datetime").datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
        meta = {"from": int(level_before), "to": int(level_after), "metrics": metrics}
        item = ApprovalItem(id=f"curriculum_{ts}", ts=ts, status="pending", kind="curriculum", preview_path="", repo="", meta=meta)
        q.write(item)
        dec = q.wait_for_decision(item.id, timeout_sec=int(timeout_sec))
        if dec and str(dec.get("decision", "")).lower() in ("approved", "approve"):
            return level_after
        return level_before
    except Exception:
        # If HITL infra not present, fall back to proposed level
        return level_after


Memory

from .common_imports import *

class Memory:
    def __init__(self, path: str) -> None:
        self.path = path

    def log(self, obj: Dict[str, Any]) -> None:
        try:
            p = Path(self.path)
            p.parent.mkdir(parents=True, exist_ok=True)
        except Exception:
            pass
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(obj, ensure_ascii=False) + "\n")

class Curriculum:
    def __init__(self) -> None:
        from collections import deque

        self.level = 0
        self.win_hist = deque(maxlen=100)
        self._ema: float | None = None
        # Dwell mechanics to stabilize level changes
        self._since_change: int = 0
        self._dwell_min: int = 5

    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:


Curriculum

from .common_imports import *

class Memory:
    def __init__(self, path: str) -> None:
        self.path = path

    def log(self, obj: Dict[str, Any]) -> None:
        try:
            p = Path(self.path)
            p.parent.mkdir(parents=True, exist_ok=True)
        except Exception:
            pass
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(obj, ensure_ascii=False) + "\n")

class Curriculum:
    def __init__(self) -> None:
        from collections import deque

        self.level = 0
        self.win_hist = deque(maxlen=100)
        self._ema: float | None = None
        # Dwell mechanics to stabilize level changes
        self._since_change: int = 0
        self._dwell_min: int = 5

    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:
            self._ema = (1 - alpha) * float(self._ema) + alpha * x
        # Use EMA-driven thresholds
        p = float(self._ema)
        self._since_change += 1
        if self._since_change >= self._dwell_min:
            if p > 0.7:
                self.level += 1
                self._since_change = 0
            elif p < 0.2 and self.level > 0:
                self.level -= 1
                self._since_change = 0

def _maybe_gate_curriculum(level_before: int, level_after: int, metrics: Dict[str, Any], require_approval: bool, timeout_sec: int) -> int:
    """If approval required and level changed, enqueue HITL approval and wait. Return final level."""
    if (not require_approval) or (level_before == level_after):
        return level_after
    try:
        # Reuse HITL queue if available
        from agi_dw.core.hitl.approval_queue import ApprovalQueue, ApprovalItem  # type: ignore
        root = Path(__file__).resolve().parents[3]
        q = ApprovalQueue(root)
        if not q.exists():  # type: ignore[attr-defined]
            return level_after
        ts = __import__("datetime").datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
        meta = {"from": int(level_before), "to": int(level_after), "metrics": metrics}
        item = ApprovalItem(id=f"curriculum_{ts}", ts=ts, status="pending", kind="curriculum", preview_path="", repo="", meta=meta)
        q.write(item)
        dec = q.wait_for_decision(item.id, timeout_sec=int(timeout_sec))
        if dec and str(dec.get("decision", "")).lower() in ("approved", "approve"):
            return level_after
        return level_before


    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:
            self._ema = (1 - alpha) * float(self._ema) + alpha * x
        # Use EMA-driven thresholds
        p = float(self._ema)
        self._since_change += 1
        if self._since_change >= self._dwell_min:
            if p > 0.7:
                self.level += 1
                self._since_change = 0
            elif p < 0.2 and self.level > 0:
                self.level -= 1
                self._since_change = 0

def _maybe_gate_curriculum(level_before: int, level_after: int, metrics: Dict[str, Any], require_approval: bool, timeout_sec: int) -> int:
    """If approval required and level changed, enqueue HITL approval and wait. Return final level."""
    if (not require_approval) or (level_before == level_after):
        return level_after
    try:
        # Reuse HITL queue if available
        from agi_dw.core.hitl.approval_queue import ApprovalQueue, ApprovalItem  # type: ignore
        root = Path(__file__).resolve().parents[3]
        q = ApprovalQueue(root)
        if not q.exists():  # type: ignore[attr-defined]
            return level_after
        ts = __import__("datetime").datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
        meta = {"from": int(level_before), "to": int(level_after), "metrics": metrics}
        item = ApprovalItem(id=f"curriculum_{ts}", ts=ts, status="pending", kind="curriculum", preview_path="", repo="", meta=meta)
        q.write(item)
        dec = q.wait_for_decision(item.id, timeout_sec=int(timeout_sec))
        if dec and str(dec.get("decision", "")).lower() in ("approved", "approve"):
            return level_after
        return level_before
    except Exception:
        # If HITL infra not present, fall back to proposed level
        return level_after


from .common_imports import *

class Memory:
    def __init__(self, path: str) -> None:
        self.path = path

    def log(self, obj: Dict[str, Any]) -> None:
        try:
            p = Path(self.path)
            p.parent.mkdir(parents=True, exist_ok=True)
        except Exception:
            pass
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(obj, ensure_ascii=False) + "\n")

class Curriculum:
    def __init__(self) -> None:
        from collections import deque

        self.level = 0
        self.win_hist = deque(maxlen=100)
        self._ema: float | None = None
        # Dwell mechanics to stabilize level changes
        self._since_change: int = 0
        self._dwell_min: int = 5

    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:
            self._ema = (1 - alpha) * float(self._ema) + alpha * x
        # Use EMA-driven thresholds
        p = float(self._ema)
        self._since_change += 1
        if self._since_change >= self._dwell_min:
            if p > 0.7:
                self.level += 1
                self._since_change = 0
            elif p < 0.2 and self.level > 0:
                self.level -= 1
                self._since_change = 0


observe

    def log(self, obj: Dict[str, Any]) -> None:
        try:
            p = Path(self.path)
            p.parent.mkdir(parents=True, exist_ok=True)
        except Exception:
            pass
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(obj, ensure_ascii=False) + "\n")

class Curriculum:
    def __init__(self) -> None:
        from collections import deque

        self.level = 0
        self.win_hist = deque(maxlen=100)
        self._ema: float | None = None
        # Dwell mechanics to stabilize level changes
        self._since_change: int = 0
        self._dwell_min: int = 5

    def observe(self, pass1: bool, score: float) -> None:
        self.win_hist.append(1.0 if pass1 else 0.0)  # type: ignore[attr-defined]
        # Exponential moving average to smooth curriculum transitions
        x = (1.0 if pass1 else 0.0)
        alpha = 0.1
        if self._ema is None:
            self._ema = x
        else:
            self._ema = (1 - alpha) * float(self._ema) + alpha * x
        # Use EMA-driven thresholds
        p = float(self._ema)
        self._since_change += 1
        if self._since_change >= self._dwell_min:
            if p > 0.7:
                self.level += 1
                self._since_change = 0
            elif p < 0.2 and self.level > 0:
                self.level -= 1
                self._since_change = 0

def _maybe_gate_curriculum(level_before: int, level_after: int, metrics: Dict[str, Any], require_approval: bool, timeout_sec: int) -> int:
    """If approval required and level changed, enqueue HITL approval and wait. Return final level."""
    if (not require_approval) or (level_before == level_after):
        return level_after
    try:
        # Reuse HITL queue if available
        from agi_dw.core.hitl.approval_queue import ApprovalQueue, ApprovalItem  # type: ignore
        root = Path(__file__).resolve().parents[3]
        q = ApprovalQueue(root)
        if not q.exists():  # type: ignore[attr-defined]
            return level_after
        ts = __import__("datetime").datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
        meta = {"from": int(level_before), "to": int(level_after), "metrics": metrics}
        item = ApprovalItem(id=f"curriculum_{ts}", ts=ts, status="pending", kind="curriculum", preview_path="", repo="", meta=meta)
        q.write(item)
        dec = q.wait_for_decision(item.id, timeout_sec=int(timeout_sec))
        if dec and str(dec.get("decision", "")).lower() in ("approved", "approve"):
            return level_after
        return level_before


from __future__ import annotations

from typing import Callable, Dict, Any, List, Tuple, Optional
import random

Rand = random.Random

class TaskSpec(Dict[str, Any]):
    """Keeps same shape: {name, signature, tests}."""
    pass

class TaskTemplate:
    def __init__(
        self,
        name: str,
        signature: str,
        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):
        m = n + d
        if lo <= m <= hi:
            c.append(m)
    # dedup preserve order
    seen = set()
    out: List[int] = []
    for v in c:
        if v in seen:
            continue
        seen.add(v)
        out.append(v)
    return out

def _fuzz_ints(inputs: List[List[Any]], lo: int = -10**6, hi: int = 10**6) -> List[List[Any]]:
    out: List[List[Any]] = []
    for args in inputs:
        new_rows: List[List[Any]] = [args]
        for i, a in enumerate(args):
            if isinstance(a, int):
                for v in _edge_bump_int(int(a), lo, hi):
                    b = list(args)
                    b[i] = v
                    new_rows.append(b)
        out.extend(new_rows)
    # de-dup and cap size
    dedup: List[List[Any]] = []
    seen: set[Tuple[Any, ...]] = set()
    for row in out:
        key = tuple(row)
        if key in seen:
            continue
        seen.add(key)
        dedup.append(list(row))
    random.shuffle(dedup)
    return dedup[: max(12, min(40, len(dedup)))]

# ---------------- Templates ---------------- #

def _sum_to_n_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    N = rng.randint(1, 10_000)
    xs = [[rng.randint(0, N)] for _ in range(12)]

    def ref(n: int) -> int:
        return int(n) * (int(n) + 1) // 2

    return xs, ref

def _sum_to_n_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        k = n // 2
        extra.append(([k], ref(k)))
        extra.append(([n - k], ref(n - k)))
        # MR: f(n) == f(k)+f(n-k)
        extra.append(([n], ref(k) + ref(n - k)))
    return extra

register(
    TaskTemplate(
        name="sum_to_n",
        signature="def solve(n:int)->int:",
        sampler=_sum_to_n_sampler,
        tags=["math", "closed_form", "io-free"],
        difficulty=0,
        metamorphics=[_sum_to_n_mr],
    )
)

def _is_prime_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs = [[rng.randint(0, 5000)] for _ in range(10)]
    xs = _fuzz_ints(xs, lo=0, hi=5000)

    def ref(n: int) -> bool:
        n = int(n)
        if n <= 1:
            return False
        if n % 2 == 0:
            return n == 2
        d = 3
        while d * d <= n:
            if n % d == 0:
                return False
            d += 2
        return True

    return xs, ref

def _is_prime_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        # neighbors
        extra.append(([max(0, n - 1)], ref(max(0, n - 1))))
        extra.append(([n + 1], ref(n + 1)))
    return extra

register(
    TaskTemplate(
        name="is_prime_adv",
        signature="def solve(n:int)->bool:",
        sampler=_is_prime_sampler,
        tags=["math", "predicate", "io-free", "adversarial"],
        difficulty=1,
        metamorphics=[_is_prime_mr],
    )
)

def _rev_str_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    alphabet = "abcxyz"
    xs: List[List[Any]] = [["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))]]
    for _ in range(9):
        xs.append(["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))])
    xs.extend([[""], ["a"], ["aa"], ["ab"], ["aba"]])

    def ref(s: str) -> str:
        return str(s)[::-1]

    return xs, ref

def _rev_str_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:8]:
        s = str(row[0])
        extra.append(([ref(s)], s))  # involution: rev(rev(s)) == s
    return extra

register(
    TaskTemplate(
        name="rev_str_involution",
        signature="def solve(s:str)->str:",
        sampler=_rev_str_sampler,
        tags=["string", "involution", "io-free"],
        difficulty=0,
        metamorphics=[_rev_str_mr],
    )
)

def _dedup_sort_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 40)
        arr = [rng.randint(-5, 5) for _ in range(n)]
        xs.append([arr])

    def ref(arr: List[int]) -> List[int]:
        seen = {}
        for v in arr:
            seen[v] = True
        out = sorted(seen.keys())
        return list(out)

    return xs, ref

register(
    TaskTemplate(
        name="dedup_sorted",
        signature="def solve(arr:list[int])->list[int]:",
        sampler=_dedup_sort_sampler,
        tags=["array", "set", "sort", "composed"],
        difficulty=1,
    )
)

# --- Pipeline(map(+1), filter(even), sum) --- #

def _pipeline_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 60)
        arr = [rng.randint(-50, 50) for __ in range(n)]
        xs.append([arr])
    def ref(arr: List[int]) -> int:
        return sum(x for x in (a + 1 for a in arr) if x % 2 == 0)
    return xs, ref

register(
    TaskTemplate(
        name="pipeline_map_filter_reduce",
        signature="def solve(arr:list[int])->int:",
        sampler=_pipeline_sampler,
        tags=["array", "map", "filter", "reduce", "composed"],
        difficulty=2,
    )
)

# --- GCD (Euclid) with metamorphics --- #

def _gcd_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = [[rng.randint(0, 10_000), rng.randint(0, 10_000)] for _ in range(12)]
    def _g(a: int, b: int) -> int:
        a = int(a); b = int(b)
        while b:
            a, b = b, a % b
        return abs(a)
    return xs, _g

def _gcd_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for a, b in inputs[:8]:
        a = int(a); b = int(b)
        extra.append(([b, a], ref(b, a)))  # symmetry
        extra.append(([a, 0], abs(a)))     # boundary
        if a and b:
            k = random.randint(2, 5)
            extra.append(([a * k, b * k], ref(a * k, b * k)))  # scaling
    return extra

register(
    TaskTemplate(
        name="gcd_euclid",
        signature="def solve(a:int,b:int)->int:",
        sampler=_gcd_sampler,
        tags=["math", "recursion", "number-theory"],
        difficulty=2,
        metamorphics=[_gcd_mr],
    )
)

# ---- Additional metamorphics for existing templates ---- #

def _dedup_sort_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    import random as _r
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # permutation invariance
        arr2 = list(arr)
        _r.shuffle(arr2)
        extra.append(([arr2], ref(arr)))
        # idempotence: ref(ref(arr)) == ref(arr)
        out = ref(arr)
        extra.append(([out], out))
        # duplicate injection shouldn't change output
        if arr:
            arr3 = arr + arr
            extra.append(([arr3], ref(arr)))
    return extra

def _pipeline_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # partition/concatenation: f(a+b) == f(a)+f(b)
        k = len(arr) // 2
        a, b = arr[:k], arr[k:]
        extra.append(([a + b], ref(a) + ref(b)))
        # neutral elements: adding numbers that map to odd after +1 should not change sum
        extra.append(([arr + [1, 3, 5]], ref(arr)))
    return extra

# Attach added MR to templates (if present)
for _t in list(_REGISTRY):
    if _t.name == "dedup_sorted":
        _t.metamorphics = (_t.metamorphics or []) + [_dedup_sort_mr]
    if _t.name == "pipeline_map_filter_reduce":
        _t.metamorphics = (_t.metamorphics or []) + [_pipeline_mr]


TaskSpec

from __future__ import annotations

from typing import Callable, Dict, Any, List, Tuple, Optional
import random

Rand = random.Random

class TaskSpec(Dict[str, Any]):
    """Keeps same shape: {name, signature, tests}."""
    pass

class TaskTemplate:
    def __init__(
        self,
        name: str,
        signature: str,
        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []


TaskTemplate

from __future__ import annotations

from typing import Callable, Dict, Any, List, Tuple, Optional
import random

Rand = random.Random

class TaskSpec(Dict[str, Any]):
    """Keeps same shape: {name, signature, tests}."""
    pass

class TaskTemplate:
    def __init__(
        self,
        name: str,
        signature: str,
        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):


register

    def __init__(
        self,
        name: str,
        signature: str,
        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):
        m = n + d
        if lo <= m <= hi:
            c.append(m)
    # dedup preserve order
    seen = set()
    out: List[int] = []
    for v in c:


        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):
        m = n + d
        if lo <= m <= hi:
            c.append(m)
    # dedup preserve order
    seen = set()
    out: List[int] = []
    for v in c:
        if v in seen:
            continue
        seen.add(v)
        out.append(v)
    return out

def _fuzz_ints(inputs: List[List[Any]], lo: int = -10**6, hi: int = 10**6) -> List[List[Any]]:


        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):
        m = n + d
        if lo <= m <= hi:
            c.append(m)
    # dedup preserve order
    seen = set()
    out: List[int] = []
    for v in c:
        if v in seen:
            continue
        seen.add(v)
        out.append(v)
    return out

def _fuzz_ints(inputs: List[List[Any]], lo: int = -10**6, hi: int = 10**6) -> List[List[Any]]:
    out: List[List[Any]] = []
    for args in inputs:
        new_rows: List[List[Any]] = [args]
        for i, a in enumerate(args):
            if isinstance(a, int):
                for v in _edge_bump_int(int(a), lo, hi):
                    b = list(args)
                    b[i] = v
                    new_rows.append(b)
        out.extend(new_rows)
    # de-dup and cap size
    dedup: List[List[Any]] = []
    seen: set[Tuple[Any, ...]] = set()
    for row in out:
        key = tuple(row)
        if key in seen:
            continue


    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):
        m = n + d
        if lo <= m <= hi:
            c.append(m)
    # dedup preserve order
    seen = set()
    out: List[int] = []
    for v in c:
        if v in seen:
            continue
        seen.add(v)
        out.append(v)
    return out

def _fuzz_ints(inputs: List[List[Any]], lo: int = -10**6, hi: int = 10**6) -> List[List[Any]]:
    out: List[List[Any]] = []
    for args in inputs:
        new_rows: List[List[Any]] = [args]
        for i, a in enumerate(args):
            if isinstance(a, int):
                for v in _edge_bump_int(int(a), lo, hi):
                    b = list(args)
                    b[i] = v
                    new_rows.append(b)
        out.extend(new_rows)
    # de-dup and cap size
    dedup: List[List[Any]] = []
    seen: set[Tuple[Any, ...]] = set()
    for row in out:
        key = tuple(row)
        if key in seen:
            continue
        seen.add(key)
        dedup.append(list(row))
    random.shuffle(dedup)
    return dedup[: max(12, min(40, len(dedup)))]

# ---------------- Templates ---------------- #

def _sum_to_n_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    N = rng.randint(1, 10_000)
    xs = [[rng.randint(0, N)] for _ in range(12)]

    def ref(n: int) -> int:
        return int(n) * (int(n) + 1) // 2

    return xs, ref

def _sum_to_n_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        k = n // 2


                    b = list(args)
                    b[i] = v
                    new_rows.append(b)
        out.extend(new_rows)
    # de-dup and cap size
    dedup: List[List[Any]] = []
    seen: set[Tuple[Any, ...]] = set()
    for row in out:
        key = tuple(row)
        if key in seen:
            continue
        seen.add(key)
        dedup.append(list(row))
    random.shuffle(dedup)
    return dedup[: max(12, min(40, len(dedup)))]

# ---------------- Templates ---------------- #

def _sum_to_n_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    N = rng.randint(1, 10_000)
    xs = [[rng.randint(0, N)] for _ in range(12)]

    def ref(n: int) -> int:
        return int(n) * (int(n) + 1) // 2

    return xs, ref

def _sum_to_n_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        k = n // 2
        extra.append(([k], ref(k)))
        extra.append(([n - k], ref(n - k)))
        # MR: f(n) == f(k)+f(n-k)
        extra.append(([n], ref(k) + ref(n - k)))
    return extra

register(
    TaskTemplate(
        name="sum_to_n",
        signature="def solve(n:int)->int:",
        sampler=_sum_to_n_sampler,
        tags=["math", "closed_form", "io-free"],


            continue
        seen.add(key)
        dedup.append(list(row))
    random.shuffle(dedup)
    return dedup[: max(12, min(40, len(dedup)))]

# ---------------- Templates ---------------- #

def _sum_to_n_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    N = rng.randint(1, 10_000)
    xs = [[rng.randint(0, N)] for _ in range(12)]

    def ref(n: int) -> int:
        return int(n) * (int(n) + 1) // 2

    return xs, ref

def _sum_to_n_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        k = n // 2
        extra.append(([k], ref(k)))
        extra.append(([n - k], ref(n - k)))
        # MR: f(n) == f(k)+f(n-k)
        extra.append(([n], ref(k) + ref(n - k)))
    return extra

register(
    TaskTemplate(
        name="sum_to_n",
        signature="def solve(n:int)->int:",
        sampler=_sum_to_n_sampler,
        tags=["math", "closed_form", "io-free"],
        difficulty=0,
        metamorphics=[_sum_to_n_mr],
    )
)

def _is_prime_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs = [[rng.randint(0, 5000)] for _ in range(10)]
    xs = _fuzz_ints(xs, lo=0, hi=5000)

    def ref(n: int) -> bool:
        n = int(n)


        k = n // 2
        extra.append(([k], ref(k)))
        extra.append(([n - k], ref(n - k)))
        # MR: f(n) == f(k)+f(n-k)
        extra.append(([n], ref(k) + ref(n - k)))
    return extra

register(
    TaskTemplate(
        name="sum_to_n",
        signature="def solve(n:int)->int:",
        sampler=_sum_to_n_sampler,
        tags=["math", "closed_form", "io-free"],
        difficulty=0,
        metamorphics=[_sum_to_n_mr],
    )
)

def _is_prime_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs = [[rng.randint(0, 5000)] for _ in range(10)]
    xs = _fuzz_ints(xs, lo=0, hi=5000)

    def ref(n: int) -> bool:
        n = int(n)
        if n <= 1:
            return False
        if n % 2 == 0:
            return n == 2
        d = 3
        while d * d <= n:
            if n % d == 0:
                return False
            d += 2
        return True

    return xs, ref

def _is_prime_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        # neighbors
        extra.append(([max(0, n - 1)], ref(max(0, n - 1))))
        extra.append(([n + 1], ref(n + 1)))
    return extra

register(
    TaskTemplate(
        name="is_prime_adv",
        signature="def solve(n:int)->bool:",
        sampler=_is_prime_sampler,
        tags=["math", "predicate", "io-free", "adversarial"],
        difficulty=1,
        metamorphics=[_is_prime_mr],


def _is_prime_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs = [[rng.randint(0, 5000)] for _ in range(10)]
    xs = _fuzz_ints(xs, lo=0, hi=5000)

    def ref(n: int) -> bool:
        n = int(n)
        if n <= 1:
            return False
        if n % 2 == 0:
            return n == 2
        d = 3
        while d * d <= n:
            if n % d == 0:
                return False
            d += 2
        return True

    return xs, ref

def _is_prime_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        n = int(row[0])
        # neighbors
        extra.append(([max(0, n - 1)], ref(max(0, n - 1))))
        extra.append(([n + 1], ref(n + 1)))
    return extra

register(
    TaskTemplate(
        name="is_prime_adv",
        signature="def solve(n:int)->bool:",
        sampler=_is_prime_sampler,
        tags=["math", "predicate", "io-free", "adversarial"],
        difficulty=1,
        metamorphics=[_is_prime_mr],
    )
)

def _rev_str_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    alphabet = "abcxyz"
    xs: List[List[Any]] = [["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))]]
    for _ in range(9):
        xs.append(["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))])
    xs.extend([[""], ["a"], ["aa"], ["ab"], ["aba"]])


    for row in inputs[:6]:
        n = int(row[0])
        # neighbors
        extra.append(([max(0, n - 1)], ref(max(0, n - 1))))
        extra.append(([n + 1], ref(n + 1)))
    return extra

register(
    TaskTemplate(
        name="is_prime_adv",
        signature="def solve(n:int)->bool:",
        sampler=_is_prime_sampler,
        tags=["math", "predicate", "io-free", "adversarial"],
        difficulty=1,
        metamorphics=[_is_prime_mr],
    )
)

def _rev_str_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    alphabet = "abcxyz"
    xs: List[List[Any]] = [["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))]]
    for _ in range(9):
        xs.append(["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))])
    xs.extend([[""], ["a"], ["aa"], ["ab"], ["aba"]])

    def ref(s: str) -> str:
        return str(s)[::-1]

    return xs, ref

def _rev_str_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:8]:
        s = str(row[0])
        extra.append(([ref(s)], s))  # involution: rev(rev(s)) == s
    return extra

register(
    TaskTemplate(
        name="rev_str_involution",
        signature="def solve(s:str)->str:",
        sampler=_rev_str_sampler,
        tags=["string", "involution", "io-free"],
        difficulty=0,
        metamorphics=[_rev_str_mr],
    )
)


        tags=["math", "predicate", "io-free", "adversarial"],
        difficulty=1,
        metamorphics=[_is_prime_mr],
    )
)

def _rev_str_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    alphabet = "abcxyz"
    xs: List[List[Any]] = [["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))]]
    for _ in range(9):
        xs.append(["".join(rng.choice(alphabet) for _ in range(rng.randint(0, 50)))])
    xs.extend([[""], ["a"], ["aa"], ["ab"], ["aba"]])

    def ref(s: str) -> str:
        return str(s)[::-1]

    return xs, ref

def _rev_str_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:8]:
        s = str(row[0])
        extra.append(([ref(s)], s))  # involution: rev(rev(s)) == s
    return extra

register(
    TaskTemplate(
        name="rev_str_involution",
        signature="def solve(s:str)->str:",
        sampler=_rev_str_sampler,
        tags=["string", "involution", "io-free"],
        difficulty=0,
        metamorphics=[_rev_str_mr],
    )
)

def _dedup_sort_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 40)
        arr = [rng.randint(-5, 5) for _ in range(n)]
        xs.append([arr])


def _rev_str_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:8]:
        s = str(row[0])
        extra.append(([ref(s)], s))  # involution: rev(rev(s)) == s
    return extra

register(
    TaskTemplate(
        name="rev_str_involution",
        signature="def solve(s:str)->str:",
        sampler=_rev_str_sampler,
        tags=["string", "involution", "io-free"],
        difficulty=0,
        metamorphics=[_rev_str_mr],
    )
)

def _dedup_sort_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 40)
        arr = [rng.randint(-5, 5) for _ in range(n)]
        xs.append([arr])

    def ref(arr: List[int]) -> List[int]:
        seen = {}
        for v in arr:
            seen[v] = True
        out = sorted(seen.keys())
        return list(out)

    return xs, ref

register(
    TaskTemplate(
        name="dedup_sorted",
        signature="def solve(arr:list[int])->list[int]:",
        sampler=_dedup_sort_sampler,
        tags=["array", "set", "sort", "composed"],
        difficulty=1,
    )
)

# --- Pipeline(map(+1), filter(even), sum) --- #

def _pipeline_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 60)
        arr = [rng.randint(-50, 50) for __ in range(n)]


            seen[v] = True
        out = sorted(seen.keys())
        return list(out)

    return xs, ref

register(
    TaskTemplate(
        name="dedup_sorted",
        signature="def solve(arr:list[int])->list[int]:",
        sampler=_dedup_sort_sampler,
        tags=["array", "set", "sort", "composed"],
        difficulty=1,
    )
)

# --- Pipeline(map(+1), filter(even), sum) --- #

def _pipeline_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 60)
        arr = [rng.randint(-50, 50) for __ in range(n)]
        xs.append([arr])
    def ref(arr: List[int]) -> int:
        return sum(x for x in (a + 1 for a in arr) if x % 2 == 0)
    return xs, ref

register(
    TaskTemplate(
        name="pipeline_map_filter_reduce",
        signature="def solve(arr:list[int])->int:",
        sampler=_pipeline_sampler,
        tags=["array", "map", "filter", "reduce", "composed"],
        difficulty=2,
    )
)

# --- GCD (Euclid) with metamorphics --- #

def _gcd_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = [[rng.randint(0, 10_000), rng.randint(0, 10_000)] for _ in range(12)]
    def _g(a: int, b: int) -> int:
        a = int(a); b = int(b)
        while b:


        arr = [rng.randint(-50, 50) for __ in range(n)]
        xs.append([arr])
    def ref(arr: List[int]) -> int:
        return sum(x for x in (a + 1 for a in arr) if x % 2 == 0)
    return xs, ref

register(
    TaskTemplate(
        name="pipeline_map_filter_reduce",
        signature="def solve(arr:list[int])->int:",
        sampler=_pipeline_sampler,
        tags=["array", "map", "filter", "reduce", "composed"],
        difficulty=2,
    )
)

# --- GCD (Euclid) with metamorphics --- #

def _gcd_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = [[rng.randint(0, 10_000), rng.randint(0, 10_000)] for _ in range(12)]
    def _g(a: int, b: int) -> int:
        a = int(a); b = int(b)
        while b:
            a, b = b, a % b
        return abs(a)
    return xs, _g

def _gcd_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for a, b in inputs[:8]:
        a = int(a); b = int(b)
        extra.append(([b, a], ref(b, a)))  # symmetry
        extra.append(([a, 0], abs(a)))     # boundary
        if a and b:
            k = random.randint(2, 5)
            extra.append(([a * k, b * k], ref(a * k, b * k)))  # scaling
    return extra

register(
    TaskTemplate(
        name="gcd_euclid",
        signature="def solve(a:int,b:int)->int:",
        sampler=_gcd_sampler,
        tags=["math", "recursion", "number-theory"],


        signature="def solve(arr:list[int])->int:",
        sampler=_pipeline_sampler,
        tags=["array", "map", "filter", "reduce", "composed"],
        difficulty=2,
    )
)

# --- GCD (Euclid) with metamorphics --- #

def _gcd_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = [[rng.randint(0, 10_000), rng.randint(0, 10_000)] for _ in range(12)]
    def _g(a: int, b: int) -> int:
        a = int(a); b = int(b)
        while b:
            a, b = b, a % b
        return abs(a)
    return xs, _g

def _gcd_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for a, b in inputs[:8]:
        a = int(a); b = int(b)
        extra.append(([b, a], ref(b, a)))  # symmetry
        extra.append(([a, 0], abs(a)))     # boundary
        if a and b:
            k = random.randint(2, 5)
            extra.append(([a * k, b * k], ref(a * k, b * k)))  # scaling
    return extra

register(
    TaskTemplate(
        name="gcd_euclid",
        signature="def solve(a:int,b:int)->int:",
        sampler=_gcd_sampler,
        tags=["math", "recursion", "number-theory"],
        difficulty=2,
        metamorphics=[_gcd_mr],
    )
)

# ---- Additional metamorphics for existing templates ---- #

def _dedup_sort_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    import random as _r
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:


        if a and b:
            k = random.randint(2, 5)
            extra.append(([a * k, b * k], ref(a * k, b * k)))  # scaling
    return extra

register(
    TaskTemplate(
        name="gcd_euclid",
        signature="def solve(a:int,b:int)->int:",
        sampler=_gcd_sampler,
        tags=["math", "recursion", "number-theory"],
        difficulty=2,
        metamorphics=[_gcd_mr],
    )
)

# ---- Additional metamorphics for existing templates ---- #

def _dedup_sort_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    import random as _r
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # permutation invariance
        arr2 = list(arr)
        _r.shuffle(arr2)
        extra.append(([arr2], ref(arr)))
        # idempotence: ref(ref(arr)) == ref(arr)
        out = ref(arr)
        extra.append(([out], out))
        # duplicate injection shouldn't change output
        if arr:
            arr3 = arr + arr
            extra.append(([arr3], ref(arr)))
    return extra

def _pipeline_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # partition/concatenation: f(a+b) == f(a)+f(b)
        k = len(arr) // 2
        a, b = arr[:k], arr[k:]
        extra.append(([a + b], ref(a) + ref(b)))
        # neutral elements: adding numbers that map to odd after +1 should not change sum
        extra.append(([arr + [1, 3, 5]], ref(arr)))
    return extra

# Attach added MR to templates (if present)
for _t in list(_REGISTRY):
    if _t.name == "dedup_sorted":
        _t.metamorphics = (_t.metamorphics or []) + [_dedup_sort_mr]
    if _t.name == "pipeline_map_filter_reduce":


def _dedup_sort_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    import random as _r
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # permutation invariance
        arr2 = list(arr)
        _r.shuffle(arr2)
        extra.append(([arr2], ref(arr)))
        # idempotence: ref(ref(arr)) == ref(arr)
        out = ref(arr)
        extra.append(([out], out))
        # duplicate injection shouldn't change output
        if arr:
            arr3 = arr + arr
            extra.append(([arr3], ref(arr)))
    return extra

def _pipeline_mr(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Tuple[List[Any], Any]]:
    extra: List[Tuple[List[Any], Any]] = []
    for row in inputs[:6]:
        arr = list(row[0]) if row and isinstance(row[0], list) else []
        # partition/concatenation: f(a+b) == f(a)+f(b)
        k = len(arr) // 2
        a, b = arr[:k], arr[k:]
        extra.append(([a + b], ref(a) + ref(b)))
        # neutral elements: adding numbers that map to odd after +1 should not change sum
        extra.append(([arr + [1, 3, 5]], ref(arr)))
    return extra

# Attach added MR to templates (if present)
for _t in list(_REGISTRY):
    if _t.name == "dedup_sorted":
        _t.metamorphics = (_t.metamorphics or []) + [_dedup_sort_mr]
    if _t.name == "pipeline_map_filter_reduce":
        _t.metamorphics = (_t.metamorphics or []) + [_pipeline_mr]


from __future__ import annotations

from typing import Callable, Dict, Any, List, Tuple, Optional
import random

Rand = random.Random

class TaskSpec(Dict[str, Any]):
    """Keeps same shape: {name, signature, tests}."""
    pass

class TaskTemplate:
    def __init__(
        self,
        name: str,
        signature: str,
        sampler: Callable[[Rand], Tuple[List[List[Any]], Callable[..., Any]]],
        tags: List[str],
        difficulty: int = 0,
        metamorphics: Optional[List[Callable[[List[List[Any]], Callable[..., Any]], List[Tuple[List[Any], Any]]]]] = None,
    ) -> None:
        self.name = name
        self.signature = signature
        self.sampler = sampler
        self.tags = tags
        self.difficulty = int(difficulty)
        self.metamorphics = metamorphics or []

_REGISTRY: List[TaskTemplate] = []

def register(template: TaskTemplate) -> None:
    _REGISTRY.append(template)

def _mk_tests(inputs: List[List[Any]], ref: Callable[..., Any]) -> List[Dict[str, Any]]:
    tests: List[Dict[str, Any]] = []
    for args in inputs:
        out = ref(*args)
        tests.append({"inp": list(args), "out": out})
    return tests

def _edge_bump_int(n: int, lo: int, hi: int) -> List[int]:
    c = [n]
    for d in (-1, +1):


ref

register(
    TaskTemplate(
        name="dedup_sorted",
        signature="def solve(arr:list[int])->list[int]:",
        sampler=_dedup_sort_sampler,
        tags=["array", "set", "sort", "composed"],
        difficulty=1,
    )
)

# --- Pipeline(map(+1), filter(even), sum) --- #

def _pipeline_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = []
    for _ in range(10):
        n = rng.randint(0, 60)
        arr = [rng.randint(-50, 50) for __ in range(n)]
        xs.append([arr])
    def ref(arr: List[int]) -> int:
        return sum(x for x in (a + 1 for a in arr) if x % 2 == 0)
    return xs, ref

register(
    TaskTemplate(
        name="pipeline_map_filter_reduce",
        signature="def solve(arr:list[int])->int:",
        sampler=_pipeline_sampler,
        tags=["array", "map", "filter", "reduce", "composed"],
        difficulty=2,
    )
)

# --- GCD (Euclid) with metamorphics --- #

def _gcd_sampler(rng: Rand) -> Tuple[List[List[Any]], Callable[..., Any]]:
    xs: List[List[Any]] = [[rng.randint(0, 10_000), rng.randint(0, 10_000)] for _ in range(12)]
    def _g(a: int, b: int) -> int:
        a = int(a); b = int(b)