					pass
		# Re-run evaluation for the updated samples
		try:
			use_evalplus = bool(getattr(args, "use_evalplus", False)) and (evalplus_evaluate is not None)
			if use_evalplus:
				return evalplus_evaluate(sample_file=str(samples_path), k=k_list, n_workers=max(1, _safe_int(getattr(args, "max_workers", default_workers) or default_workers, default_workers)), timeout=_safe_float(getattr(args, "timeout", 15) or 15, 15.0))  # type: ignore
			else:
				return evaluate_functional_correctness(
					sample_file=str(samples_path),
					k=k_list,
					n_workers=max(1, _safe_int(getattr(args, "max_workers", default_workers) or default_workers, default_workers)),
					timeout=_safe_float(getattr(args, "timeout", 15) or 15, 15.0),
					ignore_incomplete=True,
				)
		except Exception:
			return None
	except Exception:
		return None

def emit_training_datasets(root: Path, suite: str, verbose_path: Path, results_path: Path) -> None:
	"""Emit coder_ds, wm_ds, verifier_ds based on verbose and results."""
	try:
		coder_ds = Path(root / "data" / "traces" / "coder_ds.jsonl")
		wm_ds = Path(root / "data" / "skills" / "wm_ds.jsonl")
		ver_ds = Path(root / "data" / "skills" / "verifier_ds.jsonl")
		coder_ds.parent.mkdir(parents=True, exist_ok=True)
		wm_ds.parent.mkdir(parents=True, exist_ok=True)
		ver_ds.parent.mkdir(parents=True, exist_ok=True)
		# Build quick lookup: (task_id, completion) -> passed
		passed_map: Dict[str, set] = {}
		for row in read_jsonl(results_path):
			_tid = str(row.get("task_id", ""))
			if not _tid:
				continue
			passed_map.setdefault(_tid, set())
			if bool(row.get("passed", False)):
				passed_map[_tid].add(str(row.get("completion", "")))
		# Append from verbose (includes chosen first, then alts)
		with coder_ds.open("a", encoding="utf-8") as cfw, wm_ds.open("a", encoding="utf-8") as wfw, ver_ds.open("a", encoding="utf-8") as vfw:
			for obj in read_jsonl(verbose_path):
				_tid = str(obj.get("task_id", ""))
				_prompt = str(obj.get("orig_prompt", ""))
				_body = str(obj.get("completion", ""))
				if not _tid or not _body:
					continue
				_ok = (_body in passed_map.get(_tid, set()))
				# coder_ds row (minimal)
				cdr = {
					"intent": {"intent_summary": _prompt},
					"candidate": {"size": len(_body), "files": 1},
					"diff_text": _body,
					"applied_ok": bool(_ok),
				}
				cfw.write(__import__("json").dumps(cdr, ensure_ascii=False) + "\n")
				# wm_ds row (code-domain prior)
				wm_input = {
					"obs": _prompt,
					"plan": "{}",
					"action": __import__("json").dumps({"completion": _body}, ensure_ascii=False),
					"effects": "",
				}
				wfw.write(__import__("json").dumps({"input": wm_input, "success": (1 if _ok else 0), "risk": (0.5)}, ensure_ascii=False) + "\n")
				# verifier calibration row
				vrow = {"trace": {"obs": {"kind": "code", "content": _prompt}, "plan": {}, "action": {"completion": _body}, "result": {"status": ("ok" if _ok else "error")}}, "label": (1 if _ok else 0)}
				vfw.write(__import__("json").dumps(vrow, ensure_ascii=False) + "\n")
	except Exception:
		pass

# ---- Code memory helpers ----
def store_passed_solutions_in_memory(code_memory: Any, results_path: Path, problems: Dict[str, Any], suite: str) -> None:
    """Store passing solutions into a provided code memory instance.

    Parameters
    - code_memory: Optional code memory object with store_solution API
    - results_path: Path to results JSONL containing task_id/completion/passed fields
    - problems: Mapping from task_id to problem dicts containing prompt
    - suite: Suite tag string for metadata
    """
    if code_memory is None:
        return
    try:
        for row in read_jsonl(results_path):
            try:
                if bool(row.get("passed", False)):
                    tid = str(row.get("task_id", ""))
                    completion = row.get("completion", "")


					"diff_text": _body,
					"applied_ok": bool(_ok),
				}
				cfw.write(__import__("json").dumps(cdr, ensure_ascii=False) + "\n")
				# wm_ds row (code-domain prior)
				wm_input = {
					"obs": _prompt,
					"plan": "{}",
					"action": __import__("json").dumps({"completion": _body}, ensure_ascii=False),
					"effects": "",
				}
				wfw.write(__import__("json").dumps({"input": wm_input, "success": (1 if _ok else 0), "risk": (0.5)}, ensure_ascii=False) + "\n")
				# verifier calibration row
				vrow = {"trace": {"obs": {"kind": "code", "content": _prompt}, "plan": {}, "action": {"completion": _body}, "result": {"status": ("ok" if _ok else "error")}}, "label": (1 if _ok else 0)}
				vfw.write(__import__("json").dumps(vrow, ensure_ascii=False) + "\n")
	except Exception:
		pass

# ---- Code memory helpers ----
def store_passed_solutions_in_memory(code_memory: Any, results_path: Path, problems: Dict[str, Any], suite: str) -> None:
    """Store passing solutions into a provided code memory instance.

    Parameters
    - code_memory: Optional code memory object with store_solution API
    - results_path: Path to results JSONL containing task_id/completion/passed fields
    - problems: Mapping from task_id to problem dicts containing prompt
    - suite: Suite tag string for metadata
    """
    if code_memory is None:
        return
    try:
        for row in read_jsonl(results_path):
            try:
                if bool(row.get("passed", False)):
                    tid = str(row.get("task_id", ""))
                    completion = row.get("completion", "")
                    if tid and completion:
                        code_memory.store_solution(
                            problem_id=tid,
                            prompt=problems.get(tid, {}).get("prompt"),
                            solution=completion,
                            success=True,
                            metadata={"tags": [suite, f"task:{tid}"], "source": "evaluation"},
                        )
            except Exception:
                continue
    except Exception:
        pass

def build_critic_from_args(args: Any) -> Optional[Any]:
    """Construct a critic instance based on args. Supports backend='verifier' shim and default get_critic."""
    try:
        from agi_dw.core.utils.critic import get_critic  # type: ignore
    except Exception:
        get_critic = None  # type: ignore
    if not bool(getattr(args, "critic", False)):
        return None
    backend = str(getattr(args, "critic_backend", "code")).strip().lower()
    if backend == "verifier":
        try:
            from agi_dw.core.verifier.llm_verifier import verify_trace_snippet  # type: ignore
            threshold_sp = float(getattr(args, "critic_min_success", 0.5) or 0.5)
            threshold_risk = float(getattr(args, "critic_max_risk", 0.6) or 0.6)
            def _verifier_review_generic(body: str):
                try:
                    trace = {"steps": [{"state": {}, "observation": {"content": body}}]}
                    res = verify_trace_snippet(


    try:
        for row in read_jsonl(results_path):
            try:
                if bool(row.get("passed", False)):
                    tid = str(row.get("task_id", ""))
                    completion = row.get("completion", "")
                    if tid and completion:
                        code_memory.store_solution(
                            problem_id=tid,
                            prompt=problems.get(tid, {}).get("prompt"),
                            solution=completion,
                            success=True,
                            metadata={"tags": [suite, f"task:{tid}"], "source": "evaluation"},
                        )
            except Exception:
                continue
    except Exception:
        pass

def build_critic_from_args(args: Any) -> Optional[Any]:
    """Construct a critic instance based on args. Supports backend='verifier' shim and default get_critic."""
    try:
        from agi_dw.core.utils.critic import get_critic  # type: ignore
    except Exception:
        get_critic = None  # type: ignore
    if not bool(getattr(args, "critic", False)):
        return None
    backend = str(getattr(args, "critic_backend", "code")).strip().lower()
    if backend == "verifier":
        try:
            from agi_dw.core.verifier.llm_verifier import verify_trace_snippet  # type: ignore
            threshold_sp = float(getattr(args, "critic_min_success", 0.5) or 0.5)
            threshold_risk = float(getattr(args, "critic_max_risk", 0.6) or 0.6)
            def _verifier_review_generic(body: str):
                try:
                    trace = {"steps": [{"state": {}, "observation": {"content": body}}]}
                    res = verify_trace_snippet(
                        trace=trace,
                        model=str(getattr(args, "critic_model", "meta-llama/Llama-3.2-3B")),
                        backend="hf",
                        use_llm=True,
                        log_prompts=bool(getattr(args, "log_prompts", False)),
                    )
                    ok = bool(res.get("success_prob", 0.0) >= threshold_sp and res.get("risk", 1.0) <= threshold_risk)
                    return ok, res  # type: ignore
                except Exception as e:
                    return False, {"error": str(e)}
            class _CriticShim:
                def review(self, code: str, lints: str = ""):
                    return _verifier_review_generic(code)
            return _CriticShim()
        except Exception:
            pass
    if get_critic is not None:
        cm = getattr(args, "critic_model", None)
        if isinstance(cm, str) and cm.strip().lower() in ("", "none", "null"):
            cm = None
        try:
            return get_critic(cm)
        except Exception:
            return None
    return None

# ---- Indirect learning dataset emission (trace-based, no labels) ----
def emit_indirect_trace_datasets(
    root: Path,
    suite: str,
    trace_path: Path,
    results_by_task: Dict[str, bool],
) -> None:
    """Emit a trace-only indirect learning dataset from run traces.

    This writes JSONL rows containing only (prompt, completion, reward),
    using the model's own outputs and a binary reward derived from pass/fail.
    It does NOT include any ground-truth labels or reference solutions.
    """
    try:
        out_dir = Path(root / "data" / "traces" / "indirect")
        out_dir.mkdir(parents=True, exist_ok=True)
        out_path = out_dir / f"{suite}.jsonl"
        # Read traces sequentially; append rows with reward from results_by_task


                except Exception as e:
                    return False, {"error": str(e)}
            class _CriticShim:
                def review(self, code: str, lints: str = ""):
                    return _verifier_review_generic(code)
            return _CriticShim()
        except Exception:
            pass
    if get_critic is not None:
        cm = getattr(args, "critic_model", None)
        if isinstance(cm, str) and cm.strip().lower() in ("", "none", "null"):
            cm = None
        try:
            return get_critic(cm)
        except Exception:
            return None
    return None

# ---- Indirect learning dataset emission (trace-based, no labels) ----
def emit_indirect_trace_datasets(
    root: Path,
    suite: str,
    trace_path: Path,
    results_by_task: Dict[str, bool],
) -> None:
    """Emit a trace-only indirect learning dataset from run traces.

    This writes JSONL rows containing only (prompt, completion, reward),
    using the model's own outputs and a binary reward derived from pass/fail.
    It does NOT include any ground-truth labels or reference solutions.
    """
    try:
        out_dir = Path(root / "data" / "traces" / "indirect")
        out_dir.mkdir(parents=True, exist_ok=True)
        out_path = out_dir / f"{suite}.jsonl"
        # Read traces sequentially; append rows with reward from results_by_task
        with trace_path.open("r", encoding="utf-8") as rf, out_path.open("a", encoding="utf-8") as wf:
            for line in rf:
                li = (line or "").strip()
                if not li:
                    continue
                try:
                    obj = __import__("json").loads(li)
                except Exception:
                    continue
                tid = str(obj.get("task_id", ""))
                if not tid:
                    continue
                reward = 1 if bool(results_by_task.get(tid, False)) else 0
                rec = {
                    "suite": suite,
                    "task_id": tid,
                    "prompt": str(obj.get("prompt", "")),
                    "completion": str(obj.get("completion", "")),
                    "reward": int(reward),
                }
                try:
                    wf.write(__import__("json").dumps(rec, ensure_ascii=False) + "\n")
                except Exception:
                    continue
    except Exception:
        # Best-effort; never fail the run on dataset emission
        pass

def maybe_short_circuit_from_pass_cache(
    suite: str,
    tid: str,
    inp: str,
    pass_cache: Dict[str, Dict[str, str]],
    trace_path: Path,
    env_fp: Dict[str, Any],
    args: Any,
) -> Optional[List[Dict[str, Any]]]:
    """If short_circuit_pass_cache is enabled and a cached pass exists, write trace and return rows."""
    try:
        if bool(getattr(args, "short_circuit_pass_cache", True)):
            cached = pass_cache.get(tid)
            if cached and str(cached.get("completion", "")).strip():
                clean = str(cached.get("completion", ""))
                write_trace(trace_path, suite, tid, 0, None, (int(getattr(args, "seed")) if getattr(args, "seed", None) is not None else None), env_fp, inp, clean)
                return [{"task_id": tid, "completion": clean, "orig_prompt": "", "input": inp, "role": "chosen", "nbest_rank": 0, "latency_ms": None, "attempt_idx": 0}]
    except Exception:
        return None


                    continue
                tid = str(obj.get("task_id", ""))
                if not tid:
                    continue
                reward = 1 if bool(results_by_task.get(tid, False)) else 0
                rec = {
                    "suite": suite,
                    "task_id": tid,
                    "prompt": str(obj.get("prompt", "")),
                    "completion": str(obj.get("completion", "")),
                    "reward": int(reward),
                }
                try:
                    wf.write(__import__("json").dumps(rec, ensure_ascii=False) + "\n")
                except Exception:
                    continue
    except Exception:
        # Best-effort; never fail the run on dataset emission
        pass

def maybe_short_circuit_from_pass_cache(
    suite: str,
    tid: str,
    inp: str,
    pass_cache: Dict[str, Dict[str, str]],
    trace_path: Path,
    env_fp: Dict[str, Any],
    args: Any,
) -> Optional[List[Dict[str, Any]]]:
    """If short_circuit_pass_cache is enabled and a cached pass exists, write trace and return rows."""
    try:
        if bool(getattr(args, "short_circuit_pass_cache", True)):
            cached = pass_cache.get(tid)
            if cached and str(cached.get("completion", "")).strip():
                clean = str(cached.get("completion", ""))
                write_trace(trace_path, suite, tid, 0, None, (int(getattr(args, "seed")) if getattr(args, "seed", None) is not None else None), env_fp, inp, clean)
                return [{"task_id": tid, "completion": clean, "orig_prompt": "", "input": inp, "role": "chosen", "nbest_rank": 0, "latency_ms": None, "attempt_idx": 0}]
    except Exception:
        return None
    return None

def annotate_prompt_with_cached_solution(base: str, cached_completion: str) -> str:
    """Append a commented reference solution block to the prompt base."""
    try:
        ref_lines = ["# Reference solution (from cache):"]
        for ln in str(cached_completion).splitlines():
            ref_lines.append("# " + ln)
        return base + "\n\n" + "\n".join(ref_lines) + "\n"
    except Exception:
        return base

# ---- Code memory loading ----
def load_code_memory_from_args(args: Any, root: Path) -> Optional[Any]:
    """Load CodeMemory from args.memory_path or default data/memory directory if present.

    Returns the loaded CodeMemory instance or None on failure/missing.
    """
    memory_path = str(getattr(args, "memory_path", "") or "") or None


    tid: str,
    inp: str,
    pass_cache: Dict[str, Dict[str, str]],
    trace_path: Path,
    env_fp: Dict[str, Any],
    args: Any,
) -> Optional[List[Dict[str, Any]]]:
    """If short_circuit_pass_cache is enabled and a cached pass exists, write trace and return rows."""
    try:
        if bool(getattr(args, "short_circuit_pass_cache", True)):
            cached = pass_cache.get(tid)
            if cached and str(cached.get("completion", "")).strip():
                clean = str(cached.get("completion", ""))
                write_trace(trace_path, suite, tid, 0, None, (int(getattr(args, "seed")) if getattr(args, "seed", None) is not None else None), env_fp, inp, clean)
                return [{"task_id": tid, "completion": clean, "orig_prompt": "", "input": inp, "role": "chosen", "nbest_rank": 0, "latency_ms": None, "attempt_idx": 0}]
    except Exception:
        return None
    return None

def annotate_prompt_with_cached_solution(base: str, cached_completion: str) -> str:
    """Append a commented reference solution block to the prompt base."""
    try:
        ref_lines = ["# Reference solution (from cache):"]
        for ln in str(cached_completion).splitlines():
            ref_lines.append("# " + ln)
        return base + "\n\n" + "\n".join(ref_lines) + "\n"
    except Exception:
        return base

# ---- Code memory loading ----
def load_code_memory_from_args(args: Any, root: Path) -> Optional[Any]:
    """Load CodeMemory from args.memory_path or default data/memory directory if present.

    Returns the loaded CodeMemory instance or None on failure/missing.
    """
    memory_path = str(getattr(args, "memory_path", "") or "") or None
    if memory_path:
        try:
            from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
            return CodeMemory.load(memory_path)
        except Exception:
            return None
    try:
        from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
        _mem_dir = Path(root / "data" / "memory")
        if _mem_dir.exists():
            return CodeMemory.load(str(_mem_dir))


                clean = str(cached.get("completion", ""))
                write_trace(trace_path, suite, tid, 0, None, (int(getattr(args, "seed")) if getattr(args, "seed", None) is not None else None), env_fp, inp, clean)
                return [{"task_id": tid, "completion": clean, "orig_prompt": "", "input": inp, "role": "chosen", "nbest_rank": 0, "latency_ms": None, "attempt_idx": 0}]
    except Exception:
        return None
    return None

def annotate_prompt_with_cached_solution(base: str, cached_completion: str) -> str:
    """Append a commented reference solution block to the prompt base."""
    try:
        ref_lines = ["# Reference solution (from cache):"]
        for ln in str(cached_completion).splitlines():
            ref_lines.append("# " + ln)
        return base + "\n\n" + "\n".join(ref_lines) + "\n"
    except Exception:
        return base

# ---- Code memory loading ----
def load_code_memory_from_args(args: Any, root: Path) -> Optional[Any]:
    """Load CodeMemory from args.memory_path or default data/memory directory if present.

    Returns the loaded CodeMemory instance or None on failure/missing.
    """
    memory_path = str(getattr(args, "memory_path", "") or "") or None
    if memory_path:
        try:
            from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
            return CodeMemory.load(memory_path)
        except Exception:
            return None
    try:
        from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
        _mem_dir = Path(root / "data" / "memory")
        if _mem_dir.exists():
            return CodeMemory.load(str(_mem_dir))
    except Exception:
        return None
    return None

# ---- Code index resolution ----
def resolve_code_index(args: Any, root: Path) -> Tuple[Optional[Dict[str, Any]], int, Optional[str]]:
    """Resolve code index (dictionary) and settings (index_k, index_path) from args or defaults.

    - If planner_index_k/path not provided, try data/sandbox/inspiration/index.json when present.
    - Attempts to load via bench_utils.load_code_index; falls back to json parsing.
    """
    index_k = _safe_int(getattr(args, "planner_index_k", None) or 0, 0)
    index_path = str(getattr(args, "planner_index_path", "") or "") or None
    if index_k <= 0 and not index_path:
        try:
            _insp = Path(root / "data" / "sandbox" / "inspiration" / "index.json")
            if _insp.exists():
                index_k = 5
                index_path = str(_insp)
        except Exception:
            pass
    code_index: Optional[Dict[str, Any]] = None


    Returns the loaded CodeMemory instance or None on failure/missing.
    """
    memory_path = str(getattr(args, "memory_path", "") or "") or None
    if memory_path:
        try:
            from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
            return CodeMemory.load(memory_path)
        except Exception:
            return None
    try:
        from agi_dw.core.memory.code_memory import CodeMemory  # type: ignore
        _mem_dir = Path(root / "data" / "memory")
        if _mem_dir.exists():
            return CodeMemory.load(str(_mem_dir))
    except Exception:
        return None
    return None

# ---- Code index resolution ----
def resolve_code_index(args: Any, root: Path) -> Tuple[Optional[Dict[str, Any]], int, Optional[str]]:
    """Resolve code index (dictionary) and settings (index_k, index_path) from args or defaults.

    - If planner_index_k/path not provided, try data/sandbox/inspiration/index.json when present.
    - Attempts to load via bench_utils.load_code_index; falls back to json parsing.
    """
    index_k = _safe_int(getattr(args, "planner_index_k", None) or 0, 0)
    index_path = str(getattr(args, "planner_index_path", "") or "") or None
    if index_k <= 0 and not index_path:
        try:
            _insp = Path(root / "data" / "sandbox" / "inspiration" / "index.json")
            if _insp.exists():
                index_k = 5
                index_path = str(_insp)
        except Exception:
            pass
    code_index: Optional[Dict[str, Any]] = None
    if index_k > 0 and index_path:
        try:
            from agi_dw.core.utils.bench_utils import load_code_index as _load_ci  # type: ignore
            code_index = _load_ci(index_path)
        except Exception:
            code_index = None
        if code_index is None:
            try:
                ip = Path(index_path)
                if ip.exists():
                    code_index = __import__("json").loads(ip.read_text(encoding="utf-8"))
            except Exception:
                code_index = None
    return code_index, index_k, (index_path if index_path else None)

# ---- Sample writing ----
def write_samples_and_verbose(all_rows: List[Dict[str, Any]], samples_path: Path, verbose_path: Path) -> None:
    """Write all rows to verbose path; chosen-only minimal records to samples path."""
    with samples_path.open("w", encoding="utf-8") as sf, verbose_path.open("w", encoding="utf-8") as vf:
        for row in all_rows:
            try:
                vf.write(__import__("json").dumps(row) + "\n")
            except Exception:
                pass
            if str(row.get("role", "chosen")) == "chosen":
                mini = {"task_id": row.get("task_id"), "completion": row.get("completion")}
                try:
                    sf.write(__import__("json").dumps(mini) + "\n")
                except Exception:
                    pass

# ---- Candidate evaluation and refinement helpers ----
def evaluate_candidate_signals(


        except Exception:
            pass
    code_index: Optional[Dict[str, Any]] = None
    if index_k > 0 and index_path:
        try:
            from agi_dw.core.utils.bench_utils import load_code_index as _load_ci  # type: ignore
            code_index = _load_ci(index_path)
        except Exception:
            code_index = None
        if code_index is None:
            try:
                ip = Path(index_path)
                if ip.exists():
                    code_index = __import__("json").loads(ip.read_text(encoding="utf-8"))
            except Exception:
                code_index = None
    return code_index, index_k, (index_path if index_path else None)

# ---- Sample writing ----
def write_samples_and_verbose(all_rows: List[Dict[str, Any]], samples_path: Path, verbose_path: Path) -> None:
    """Write all rows to verbose path; chosen-only minimal records to samples path."""
    with samples_path.open("w", encoding="utf-8") as sf, verbose_path.open("w", encoding="utf-8") as vf:
        for row in all_rows:
            try:
                vf.write(__import__("json").dumps(row) + "\n")
            except Exception:
                pass
            if str(row.get("role", "chosen")) == "chosen":
                mini = {"task_id": row.get("task_id"), "completion": row.get("completion")}
                try:
                    sf.write(__import__("json").dumps(mini) + "\n")
                except Exception:
                    pass

# ---- Candidate evaluation and refinement helpers ----
def evaluate_candidate_signals(
	body: str,
	prompt: str,
	precheck_code: Any,
	critic: Any,
	verify_trace_snippet: Any,
	args: Any,
) -> Tuple[bool, bool, float]:
	"""Compute (precheck_ok, critic_ok, verifier_risk) for a candidate body.

	- Safely runs precheck and critic if available
	- Uses verifier backend when provided to estimate risk
	"""
	pre_ok: bool = False
	crit_ok: bool = False
	v_risk: float = 0.5
	try:
		if body:


    return code_index, index_k, (index_path if index_path else None)

# ---- Sample writing ----
def write_samples_and_verbose(all_rows: List[Dict[str, Any]], samples_path: Path, verbose_path: Path) -> None:
    """Write all rows to verbose path; chosen-only minimal records to samples path."""
    with samples_path.open("w", encoding="utf-8") as sf, verbose_path.open("w", encoding="utf-8") as vf:
        for row in all_rows:
            try:
                vf.write(__import__("json").dumps(row) + "\n")
            except Exception:
                pass
            if str(row.get("role", "chosen")) == "chosen":
                mini = {"task_id": row.get("task_id"), "completion": row.get("completion")}
                try:
                    sf.write(__import__("json").dumps(mini) + "\n")
                except Exception:
                    pass

# ---- Candidate evaluation and refinement helpers ----
def evaluate_candidate_signals(
	body: str,
	prompt: str,
	precheck_code: Any,
	critic: Any,
	verify_trace_snippet: Any,
	args: Any,
) -> Tuple[bool, bool, float]:
	"""Compute (precheck_ok, critic_ok, verifier_risk) for a candidate body.

	- Safely runs precheck and critic if available
	- Uses verifier backend when provided to estimate risk
	"""
	pre_ok: bool = False
	crit_ok: bool = False
	v_risk: float = 0.5
	try:
		if body:
			try:
				pre_ok, _ = precheck_code(body)
			except Exception:
				pre_ok = False
			if critic and body:
				try:
					crit_ok, _ = critic.review(body)
				except Exception:
					crit_ok = False
			if verify_trace_snippet is not None and body:
				try:
					trace = {"obs": {"kind": "code", "content": prompt}, "plan": {}, "action": {"completion": body}, "result": {"status": "pending"}}
					vres = verify_trace_snippet(
						trace=trace,
						model=str(getattr(args, "verifier_model", "meta-llama/Llama-3.2-3B")),
						backend=str(getattr(args, "verifier_backend", "hf")),
						use_llm=True,
						timeout=_safe_int(getattr(args, "timeout", 15) or 15, 15),
					)
					v_risk = float(vres.get("risk", 0.5))
				except Exception:
					v_risk = 0.5
	except Exception:
		pass
	return bool(pre_ok), bool(crit_ok), float(v_risk)

def maybe_self_refine_body(
	llm: Any,
	prompt: str,
	body: str,
	params: Dict[str, Any],
	precheck_code: Any,
	critic: Any,
	verify_trace_snippet: Any,
	args: Any,
) -> Tuple[str, Dict[str, Any]]:
	"""Optionally self-refine a candidate body once using lightweight review signals.

	Returns (possibly_revised_body, meta) where meta contains rev_pre_ok and rev_v_risk.
	Does not write traces directly so callers can decide how to log.
	"""
	try:
		review_system = "You are a Python coding assistant. Improve the function body minimally to pass the tests. Output ONLY the corrected function body."
		review_user = (


				except Exception:
					crit_ok = False
			if verify_trace_snippet is not None and body:
				try:
					trace = {"obs": {"kind": "code", "content": prompt}, "plan": {}, "action": {"completion": body}, "result": {"status": "pending"}}
					vres = verify_trace_snippet(
						trace=trace,
						model=str(getattr(args, "verifier_model", "meta-llama/Llama-3.2-3B")),
						backend=str(getattr(args, "verifier_backend", "hf")),
						use_llm=True,
						timeout=_safe_int(getattr(args, "timeout", 15) or 15, 15),
					)
					v_risk = float(vres.get("risk", 0.5))
				except Exception:
					v_risk = 0.5
	except Exception:
		pass
	return bool(pre_ok), bool(crit_ok), float(v_risk)

def maybe_self_refine_body(
	llm: Any,
	prompt: str,
	body: str,
	params: Dict[str, Any],
	precheck_code: Any,
	critic: Any,
	verify_trace_snippet: Any,
	args: Any,
) -> Tuple[str, Dict[str, Any]]:
	"""Optionally self-refine a candidate body once using lightweight review signals.

	Returns (possibly_revised_body, meta) where meta contains rev_pre_ok and rev_v_risk.
	Does not write traces directly so callers can decide how to log.
	"""
	try:
		review_system = "You are a Python coding assistant. Improve the function body minimally to pass the tests. Output ONLY the corrected function body."
		review_user = (
			"Task prompt (do not rewrite def):\n" + prompt
			+ "\n\nCurrent attempt body:\n" + (body or "")
			+ "\n\nInstruction: Return a corrected function body only."
		)
		try:
			messages = [{"role": "system", "content": review_system}, {"role": "user", "content": review_user}]
			text = str(llm.chat(messages, max_new_tokens=params.get("max_new_tokens", 256), temperature=params.get("temperature", 0.2), top_p=params.get("top_p"), top_k=params.get("top_k")))
		except Exception:
			text = str(llm.generate(review_user, **params))
		return text, {}
	except Exception:
		return body, {}

def wm_rerank_candidates(
	wm_model: Any,
	prompt: str,
	candidates: List[Dict[str, Any]],
	chosen: str,
	wm_threshold: float,
) -> str:
	"""Use world model scoring to possibly switch to a lower-risk alternative."""
	if wm_model is None:
		return chosen
	try:
		obs_obj = {"kind": "code", "content": prompt}
		plan_obj: Dict[str, Any] = {}
		actions: List[Dict[str, Any]] = []
		for c in candidates:
			body = str(c.get("body", ""))
			if body:
				actions.append({"completion": body})


	Returns (possibly_revised_body, meta) where meta contains rev_pre_ok and rev_v_risk.
	Does not write traces directly so callers can decide how to log.
	"""
	try:
		review_system = "You are a Python coding assistant. Improve the function body minimally to pass the tests. Output ONLY the corrected function body."
		review_user = (
			"Task prompt (do not rewrite def):\n" + prompt
			+ "\n\nCurrent attempt body:\n" + (body or "")
			+ "\n\nInstruction: Return a corrected function body only."
		)
		try:
			messages = [{"role": "system", "content": review_system}, {"role": "user", "content": review_user}]
			text = str(llm.chat(messages, max_new_tokens=params.get("max_new_tokens", 256), temperature=params.get("temperature", 0.2), top_p=params.get("top_p"), top_k=params.get("top_k")))
		except Exception:
			text = str(llm.generate(review_user, **params))
		return text, {}
	except Exception:
		return body, {}

def wm_rerank_candidates(
	wm_model: Any,
	prompt: str,
	candidates: List[Dict[str, Any]],
	chosen: str,
	wm_threshold: float,
) -> str:
	"""Use world model scoring to possibly switch to a lower-risk alternative."""
	if wm_model is None:
		return chosen
	try:
		obs_obj = {"kind": "code", "content": prompt}
		plan_obj: Dict[str, Any] = {}
		actions: List[Dict[str, Any]] = []
		for c in candidates:
			body = str(c.get("body", ""))
			if body:
				actions.append({"completion": body})
		if not actions:
			return chosen
		scored = wm_model.rank_actions(obs_obj, plan_obj, actions)
		thr = float(wm_threshold)
		risk_chosen = None
		for sc in scored:
			if str(sc.get("action", {}).get("completion", "")) == chosen:
				risk_chosen = float(sc.get("risk", 0.5))
				break
		if (risk_chosen is None or risk_chosen >= thr) and scored:
			best = scored[0]
			best_body = str(best.get("action", {}).get("completion", ""))
			if best_body:
				return best_body
	except Exception:
		pass
	return chosen


            except Exception:
                continue
    except Exception:
        pass

def build_critic_from_args(args: Any) -> Optional[Any]:
    """Construct a critic instance based on args. Supports backend='verifier' shim and default get_critic."""
    try:
        from agi_dw.core.utils.critic import get_critic  # type: ignore
    except Exception:
        get_critic = None  # type: ignore
    if not bool(getattr(args, "critic", False)):
        return None
    backend = str(getattr(args, "critic_backend", "code")).strip().lower()
    if backend == "verifier":
        try:
            from agi_dw.core.verifier.llm_verifier import verify_trace_snippet  # type: ignore
            threshold_sp = float(getattr(args, "critic_min_success", 0.5) or 0.5)
            threshold_risk = float(getattr(args, "critic_max_risk", 0.6) or 0.6)
            def _verifier_review_generic(body: str):
                try:
                    trace = {"steps": [{"state": {}, "observation": {"content": body}}]}
                    res = verify_trace_snippet(
                        trace=trace,
                        model=str(getattr(args, "critic_model", "meta-llama/Llama-3.2-3B")),
                        backend="hf",
                        use_llm=True,
                        log_prompts=bool(getattr(args, "log_prompts", False)),
                    )
                    ok = bool(res.get("success_prob", 0.0) >= threshold_sp and res.get("risk", 1.0) <= threshold_risk)
                    return ok, res  # type: ignore
                except Exception as e:
                    return False, {"error": str(e)}
            class _CriticShim:
                def review(self, code: str, lints: str = ""):
                    return _verifier_review_generic(code)
            return _CriticShim()
        except Exception:
            pass
    if get_critic is not None:
        cm = getattr(args, "critic_model", None)
        if isinstance(cm, str) and cm.strip().lower() in ("", "none", "null"):
            cm = None
        try:
            return get_critic(cm)
        except Exception:
            return None
    return None

# ---- Indirect learning dataset emission (trace-based, no labels) ----
def emit_indirect_trace_datasets(
    root: Path,


    backend = str(getattr(args, "critic_backend", "code")).strip().lower()
    if backend == "verifier":
        try:
            from agi_dw.core.verifier.llm_verifier import verify_trace_snippet  # type: ignore
            threshold_sp = float(getattr(args, "critic_min_success", 0.5) or 0.5)
            threshold_risk = float(getattr(args, "critic_max_risk", 0.6) or 0.6)
            def _verifier_review_generic(body: str):
                try:
                    trace = {"steps": [{"state": {}, "observation": {"content": body}}]}
                    res = verify_trace_snippet(
                        trace=trace,
                        model=str(getattr(args, "critic_model", "meta-llama/Llama-3.2-3B")),
                        backend="hf",
                        use_llm=True,
                        log_prompts=bool(getattr(args, "log_prompts", False)),
                    )
                    ok = bool(res.get("success_prob", 0.0) >= threshold_sp and res.get("risk", 1.0) <= threshold_risk)
                    return ok, res  # type: ignore
                except Exception as e:
                    return False, {"error": str(e)}
            class _CriticShim:
                def review(self, code: str, lints: str = ""):
                    return _verifier_review_generic(code)
            return _CriticShim()
        except Exception:
            pass
    if get_critic is not None:
        cm = getattr(args, "critic_model", None)
        if isinstance(cm, str) and cm.strip().lower() in ("", "none", "null"):
            cm = None
        try:
            return get_critic(cm)
        except Exception:
            return None
    return None

# ---- Indirect learning dataset emission (trace-based, no labels) ----
def emit_indirect_trace_datasets(
    root: Path,
    suite: str,
    trace_path: Path,
    results_by_task: Dict[str, bool],


review

    if backend == "verifier":
        try:
            from agi_dw.core.verifier.llm_verifier import verify_trace_snippet  # type: ignore
            threshold_sp = float(getattr(args, "critic_min_success", 0.5) or 0.5)
            threshold_risk = float(getattr(args, "critic_max_risk", 0.6) or 0.6)
            def _verifier_review_generic(body: str):
                try:
                    trace = {"steps": [{"state": {}, "observation": {"content": body}}]}
                    res = verify_trace_snippet(
                        trace=trace,
                        model=str(getattr(args, "critic_model", "meta-llama/Llama-3.2-3B")),
                        backend="hf",
                        use_llm=True,
                        log_prompts=bool(getattr(args, "log_prompts", False)),
                    )
                    ok = bool(res.get("success_prob", 0.0) >= threshold_sp and res.get("risk", 1.0) <= threshold_risk)
                    return ok, res  # type: ignore
                except Exception as e:
                    return False, {"error": str(e)}
            class _CriticShim:
                def review(self, code: str, lints: str = ""):
                    return _verifier_review_generic(code)
            return _CriticShim()
        except Exception:
            pass
    if get_critic is not None:
        cm = getattr(args, "critic_model", None)
        if isinstance(cm, str) and cm.strip().lower() in ("", "none", "null"):
            cm = None
        try:
            return get_critic(cm)
        except Exception:
            return None
    return None

# ---- Indirect learning dataset emission (trace-based, no labels) ----
def emit_indirect_trace_datasets(
    root: Path,
    suite: str,
    trace_path: Path,
    results_by_task: Dict[str, bool],


import logging
import json
import os
import re
from pathlib import Path
from typing import Any, Dict, List, Set
import hashlib

def build_trace(task_id: str, obs: Dict[str, Any], plan: Dict[str, Any], action: Dict[str, Any], result: Dict[str, Any], reward: Dict[str, Any], critique: Dict[str, Any]) -> Dict[str, Any]:
	# Optionally carry intent fields if present (non-breaking for existing readers)
	intent = {}
	try:
		if isinstance(plan, dict):
			for k in ("intent_summary", "target_symbols", "constraints", "risk_budget"):
				if k in plan:
					intent[k] = plan.get(k)
		if isinstance(action, dict):
			ai = action.get("intent") if isinstance(action.get("intent"), dict) else {}
			if ai:
				intent.update({f"action_{k}": v for k, v in ai.items()})
	except Exception:
		intent = {}
	return {
		"task_id": task_id,
		"obs": obs,
		"plan": plan,
		"action": action,
		"result": result,
		"reward": reward,
		"critique": critique,
		"intent": intent,
	}

_SEEN_SIGS: Set[str] = set()

def _stable_signature(obj: Dict[str, Any]) -> str:
	try:
		obs = obj.get("obs", {}) if isinstance(obj.get("obs"), dict) else {}
		kind = str(obs.get("kind", ""))
		meta = obs.get("meta", {}) if isinstance(obs, dict) else {}
		url = str((meta or {}).get("url", ""))
		sel = str((meta or {}).get("selector", ""))
		plan = obj.get("plan", {}) if isinstance(obj.get("plan"), dict) else {}
		action = obj.get("action", {}) if isinstance(obj.get("action"), dict) else {}
		result = obj.get("result", {}) if isinstance(obj.get("result"), dict) else {}
		status = str(result.get("status", ""))
		core = {"kind": kind, "url": url, "selector": sel, "plan": plan, "action": action, "status": status}
		raw = json.dumps(core, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
		return hashlib.sha256(raw.encode("utf-8")).hexdigest()
	except Exception:
		try:
			raw = json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
			return hashlib.sha256(raw.encode("utf-8")).hexdigest()
		except Exception:
			return ""

def write_jsonl(path: str, obj: Dict[str, Any]) -> None:
	p = Path(path)
	p.parent.mkdir(parents=True, exist_ok=True)
	# Optional source-level deduplication (default on). Control with AGI_TRACE_DEDUPE=0 to disable.
	import os as _os  # type: ignore
	dedupe_on = str(_os.environ.get("AGI_TRACE_DEDUPE", "1")).strip() not in ("0", "false", "False")
	if dedupe_on:
		sig = _stable_signature(obj)
		if sig:
			# Load sidecar index once per file to seed seen set
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				if idx.exists() and not getattr(write_jsonl, "_idx_loaded", False):  # type: ignore[attr-defined]
					for line in idx.read_text(encoding="utf-8").splitlines():
						ls = line.strip()
						if ls:
							_SEEN_SIGS.add(ls)
					setattr(write_jsonl, "_idx_loaded", True)  # type: ignore[attr-defined]
			except Exception:
				pass
			if sig in _SEEN_SIGS:
				return
			_SEEN_SIGS.add(sig)
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				with idx.open("a", encoding="utf-8") as fi:
					fi.write(sig + "\n")
			except Exception:
				pass
	with p.open("a", encoding="utf-8") as f:
		f.write(json.dumps(_redact_obj(obj), ensure_ascii=False) + "\n")

def _secret_patterns() -> List[re.Pattern[str]]:
	pats: List[str] = [
		r"sk-[A-Za-z0-9]{16,}",
		r"ghp_[A-Za-z0-9]{20,}",
		r"ya29\.[A-Za-z0-9\-_]+",
		r"xox[baprs]-[A-Za-z0-9\-]+",
		r"AKIA[0-9A-Z]{16}",
		r"ASIA[0-9A-Z]{16}",
		r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",
		r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
	]
	extra = os.environ.get("AGI_SECRET_PATTERNS", "")
	if extra:
		for raw in re.split(r"[\n,]", extra):
			raw = raw.strip()
			if raw:
				pats.append(raw)
	comp: List[re.Pattern[str]] = []
	for p in pats:
		try:
			comp.append(re.compile(p))
		except Exception:
			pass
	return comp

def _secret_name_hints() -> List[str]:
	hints = [
		"SECRET",
		"TOKEN",
		"KEY",
		"PASSWORD",
		"CREDENTIAL",
		"SESSION",
		"AUTH",
		"API",
	]
	extra = os.environ.get("AGI_SECRET_NAME_HINTS", "")
	for raw in extra.split(","):
		raw = raw.strip()
		if raw:
			hints.append(raw.upper())
	return hints

def _redact_text(text: str) -> str:
	if not text:
		return text
	red = text
	# Value-based redaction from env
	hints = _secret_name_hints()
	for name, val in os.environ.items():
		try:
			if val and isinstance(val, str) and len(val) >= 6:
				if any(h in name.upper() for h in hints):
					red = red.replace(val, "[REDACTED]")
		except Exception:
			pass
	for rx in _secret_patterns():
		try:
			red = rx.sub("[REDACTED]", red)
		except Exception:
			pass
	return red

def _redact_obj(obj: Any) -> Any:
	try:
		if obj is None:
			return obj
		if isinstance(obj, str):
			return _redact_text(obj)
		if isinstance(obj, list):
			return [_redact_obj(x) for x in obj]
		if isinstance(obj, dict):
			out: Dict[str, Any] = {}
			for k, v in obj.items():
				ks = str(k)
				if any(h in ks.upper() for h in _secret_name_hints()):
					out[ks] = "[REDACTED]"
				else:
					out[ks] = _redact_obj(v)
			return out
		return obj
	except Exception:
		return obj


import logging
import json
import os
import re
from pathlib import Path
from typing import Any, Dict, List, Set
import hashlib

def build_trace(task_id: str, obs: Dict[str, Any], plan: Dict[str, Any], action: Dict[str, Any], result: Dict[str, Any], reward: Dict[str, Any], critique: Dict[str, Any]) -> Dict[str, Any]:
	# Optionally carry intent fields if present (non-breaking for existing readers)
	intent = {}
	try:
		if isinstance(plan, dict):
			for k in ("intent_summary", "target_symbols", "constraints", "risk_budget"):
				if k in plan:
					intent[k] = plan.get(k)
		if isinstance(action, dict):
			ai = action.get("intent") if isinstance(action.get("intent"), dict) else {}
			if ai:
				intent.update({f"action_{k}": v for k, v in ai.items()})
	except Exception:
		intent = {}
	return {
		"task_id": task_id,
		"obs": obs,
		"plan": plan,
		"action": action,
		"result": result,
		"reward": reward,
		"critique": critique,
		"intent": intent,
	}

_SEEN_SIGS: Set[str] = set()

def _stable_signature(obj: Dict[str, Any]) -> str:
	try:
		obs = obj.get("obs", {}) if isinstance(obj.get("obs"), dict) else {}
		kind = str(obs.get("kind", ""))
		meta = obs.get("meta", {}) if isinstance(obs, dict) else {}
		url = str((meta or {}).get("url", ""))
		sel = str((meta or {}).get("selector", ""))
		plan = obj.get("plan", {}) if isinstance(obj.get("plan"), dict) else {}
		action = obj.get("action", {}) if isinstance(obj.get("action"), dict) else {}
		result = obj.get("result", {}) if isinstance(obj.get("result"), dict) else {}
		status = str(result.get("status", ""))
		core = {"kind": kind, "url": url, "selector": sel, "plan": plan, "action": action, "status": status}
		raw = json.dumps(core, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
		return hashlib.sha256(raw.encode("utf-8")).hexdigest()
	except Exception:


			ai = action.get("intent") if isinstance(action.get("intent"), dict) else {}
			if ai:
				intent.update({f"action_{k}": v for k, v in ai.items()})
	except Exception:
		intent = {}
	return {
		"task_id": task_id,
		"obs": obs,
		"plan": plan,
		"action": action,
		"result": result,
		"reward": reward,
		"critique": critique,
		"intent": intent,
	}

_SEEN_SIGS: Set[str] = set()

def _stable_signature(obj: Dict[str, Any]) -> str:
	try:
		obs = obj.get("obs", {}) if isinstance(obj.get("obs"), dict) else {}
		kind = str(obs.get("kind", ""))
		meta = obs.get("meta", {}) if isinstance(obs, dict) else {}
		url = str((meta or {}).get("url", ""))
		sel = str((meta or {}).get("selector", ""))
		plan = obj.get("plan", {}) if isinstance(obj.get("plan"), dict) else {}
		action = obj.get("action", {}) if isinstance(obj.get("action"), dict) else {}
		result = obj.get("result", {}) if isinstance(obj.get("result"), dict) else {}
		status = str(result.get("status", ""))
		core = {"kind": kind, "url": url, "selector": sel, "plan": plan, "action": action, "status": status}
		raw = json.dumps(core, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
		return hashlib.sha256(raw.encode("utf-8")).hexdigest()
	except Exception:
		try:
			raw = json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
			return hashlib.sha256(raw.encode("utf-8")).hexdigest()
		except Exception:
			return ""

def write_jsonl(path: str, obj: Dict[str, Any]) -> None:
	p = Path(path)
	p.parent.mkdir(parents=True, exist_ok=True)
	# Optional source-level deduplication (default on). Control with AGI_TRACE_DEDUPE=0 to disable.
	import os as _os  # type: ignore
	dedupe_on = str(_os.environ.get("AGI_TRACE_DEDUPE", "1")).strip() not in ("0", "false", "False")
	if dedupe_on:
		sig = _stable_signature(obj)
		if sig:
			# Load sidecar index once per file to seed seen set
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				if idx.exists() and not getattr(write_jsonl, "_idx_loaded", False):  # type: ignore[attr-defined]
					for line in idx.read_text(encoding="utf-8").splitlines():
						ls = line.strip()
						if ls:
							_SEEN_SIGS.add(ls)
					setattr(write_jsonl, "_idx_loaded", True)  # type: ignore[attr-defined]


		obs = obj.get("obs", {}) if isinstance(obj.get("obs"), dict) else {}
		kind = str(obs.get("kind", ""))
		meta = obs.get("meta", {}) if isinstance(obs, dict) else {}
		url = str((meta or {}).get("url", ""))
		sel = str((meta or {}).get("selector", ""))
		plan = obj.get("plan", {}) if isinstance(obj.get("plan"), dict) else {}
		action = obj.get("action", {}) if isinstance(obj.get("action"), dict) else {}
		result = obj.get("result", {}) if isinstance(obj.get("result"), dict) else {}
		status = str(result.get("status", ""))
		core = {"kind": kind, "url": url, "selector": sel, "plan": plan, "action": action, "status": status}
		raw = json.dumps(core, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
		return hashlib.sha256(raw.encode("utf-8")).hexdigest()
	except Exception:
		try:
			raw = json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
			return hashlib.sha256(raw.encode("utf-8")).hexdigest()
		except Exception:
			return ""

def write_jsonl(path: str, obj: Dict[str, Any]) -> None:
	p = Path(path)
	p.parent.mkdir(parents=True, exist_ok=True)
	# Optional source-level deduplication (default on). Control with AGI_TRACE_DEDUPE=0 to disable.
	import os as _os  # type: ignore
	dedupe_on = str(_os.environ.get("AGI_TRACE_DEDUPE", "1")).strip() not in ("0", "false", "False")
	if dedupe_on:
		sig = _stable_signature(obj)
		if sig:
			# Load sidecar index once per file to seed seen set
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				if idx.exists() and not getattr(write_jsonl, "_idx_loaded", False):  # type: ignore[attr-defined]
					for line in idx.read_text(encoding="utf-8").splitlines():
						ls = line.strip()
						if ls:
							_SEEN_SIGS.add(ls)
					setattr(write_jsonl, "_idx_loaded", True)  # type: ignore[attr-defined]
			except Exception:
				pass
			if sig in _SEEN_SIGS:
				return
			_SEEN_SIGS.add(sig)
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				with idx.open("a", encoding="utf-8") as fi:
					fi.write(sig + "\n")
			except Exception:
				pass
	with p.open("a", encoding="utf-8") as f:
		f.write(json.dumps(_redact_obj(obj), ensure_ascii=False) + "\n")

def _secret_patterns() -> List[re.Pattern[str]]:
	pats: List[str] = [
		r"sk-[A-Za-z0-9]{16,}",
		r"ghp_[A-Za-z0-9]{20,}",
		r"ya29\.[A-Za-z0-9\-_]+",
		r"xox[baprs]-[A-Za-z0-9\-]+",
		r"AKIA[0-9A-Z]{16}",
		r"ASIA[0-9A-Z]{16}",
		r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",
		r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
	]
	extra = os.environ.get("AGI_SECRET_PATTERNS", "")
	if extra:
		for raw in re.split(r"[\n,]", extra):
			raw = raw.strip()
			if raw:
				pats.append(raw)
	comp: List[re.Pattern[str]] = []


					for line in idx.read_text(encoding="utf-8").splitlines():
						ls = line.strip()
						if ls:
							_SEEN_SIGS.add(ls)
					setattr(write_jsonl, "_idx_loaded", True)  # type: ignore[attr-defined]
			except Exception:
				pass
			if sig in _SEEN_SIGS:
				return
			_SEEN_SIGS.add(sig)
			try:
				idx = p.with_suffix(p.suffix + ".sigindex")
				with idx.open("a", encoding="utf-8") as fi:
					fi.write(sig + "\n")
			except Exception:
				pass
	with p.open("a", encoding="utf-8") as f:
		f.write(json.dumps(_redact_obj(obj), ensure_ascii=False) + "\n")

def _secret_patterns() -> List[re.Pattern[str]]:
	pats: List[str] = [
		r"sk-[A-Za-z0-9]{16,}",
		r"ghp_[A-Za-z0-9]{20,}",
		r"ya29\.[A-Za-z0-9\-_]+",
		r"xox[baprs]-[A-Za-z0-9\-]+",
		r"AKIA[0-9A-Z]{16}",
		r"ASIA[0-9A-Z]{16}",
		r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",
		r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
	]
	extra = os.environ.get("AGI_SECRET_PATTERNS", "")
	if extra:
		for raw in re.split(r"[\n,]", extra):
			raw = raw.strip()
			if raw:
				pats.append(raw)
	comp: List[re.Pattern[str]] = []
	for p in pats:
		try:
			comp.append(re.compile(p))
		except Exception:
			pass
	return comp

def _secret_name_hints() -> List[str]:
	hints = [
		"SECRET",
		"TOKEN",
		"KEY",
		"PASSWORD",
		"CREDENTIAL",
		"SESSION",
		"AUTH",
		"API",
	]
	extra = os.environ.get("AGI_SECRET_NAME_HINTS", "")
	for raw in extra.split(","):
		raw = raw.strip()
		if raw:
			hints.append(raw.upper())
	return hints


		r"AKIA[0-9A-Z]{16}",
		r"ASIA[0-9A-Z]{16}",
		r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",
		r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
	]
	extra = os.environ.get("AGI_SECRET_PATTERNS", "")
	if extra:
		for raw in re.split(r"[\n,]", extra):
			raw = raw.strip()
			if raw:
				pats.append(raw)
	comp: List[re.Pattern[str]] = []
	for p in pats:
		try:
			comp.append(re.compile(p))
		except Exception:
			pass
	return comp

def _secret_name_hints() -> List[str]:
	hints = [
		"SECRET",
		"TOKEN",
		"KEY",
		"PASSWORD",
		"CREDENTIAL",
		"SESSION",
		"AUTH",
		"API",
	]
	extra = os.environ.get("AGI_SECRET_NAME_HINTS", "")
	for raw in extra.split(","):
		raw = raw.strip()
		if raw:
			hints.append(raw.upper())
	return hints

def _redact_text(text: str) -> str:
	if not text:
		return text
	red = text
	# Value-based redaction from env
	hints = _secret_name_hints()
	for name, val in os.environ.items():
		try:
			if val and isinstance(val, str) and len(val) >= 6:
				if any(h in name.upper() for h in hints):
					red = red.replace(val, "[REDACTED]")
		except Exception:
			pass
	for rx in _secret_patterns():
		try:
			red = rx.sub("[REDACTED]", red)
		except Exception:
			pass


def _secret_name_hints() -> List[str]:
	hints = [
		"SECRET",
		"TOKEN",
		"KEY",
		"PASSWORD",
		"CREDENTIAL",
		"SESSION",
		"AUTH",
		"API",
	]
	extra = os.environ.get("AGI_SECRET_NAME_HINTS", "")
	for raw in extra.split(","):
		raw = raw.strip()
		if raw:
			hints.append(raw.upper())
	return hints

def _redact_text(text: str) -> str:
	if not text:
		return text
	red = text
	# Value-based redaction from env
	hints = _secret_name_hints()
	for name, val in os.environ.items():
		try:
			if val and isinstance(val, str) and len(val) >= 6:
				if any(h in name.upper() for h in hints):
					red = red.replace(val, "[REDACTED]")
		except Exception:
			pass
	for rx in _secret_patterns():
		try:
			red = rx.sub("[REDACTED]", red)
		except Exception:
			pass
	return red

def _redact_obj(obj: Any) -> Any:
	try:
		if obj is None:
			return obj
		if isinstance(obj, str):
			return _redact_text(obj)
		if isinstance(obj, list):
			return [_redact_obj(x) for x in obj]
		if isinstance(obj, dict):
			out: Dict[str, Any] = {}
			for k, v in obj.items():
				ks = str(k)
				if any(h in ks.upper() for h in _secret_name_hints()):
					out[ks] = "[REDACTED]"
				else:
					out[ks] = _redact_obj(v)
			return out
		return obj


	if not text:
		return text
	red = text
	# Value-based redaction from env
	hints = _secret_name_hints()
	for name, val in os.environ.items():
		try:
			if val and isinstance(val, str) and len(val) >= 6:
				if any(h in name.upper() for h in hints):
					red = red.replace(val, "[REDACTED]")
		except Exception:
			pass
	for rx in _secret_patterns():
		try:
			red = rx.sub("[REDACTED]", red)
		except Exception:
			pass
	return red

def _redact_obj(obj: Any) -> Any:
	try:
		if obj is None:
			return obj
		if isinstance(obj, str):
			return _redact_text(obj)
		if isinstance(obj, list):
			return [_redact_obj(x) for x in obj]
		if isinstance(obj, dict):
			out: Dict[str, Any] = {}
			for k, v in obj.items():
				ks = str(k)
				if any(h in ks.upper() for h in _secret_name_hints()):
					out[ks] = "[REDACTED]"
				else:
					out[ks] = _redact_obj(v)
			return out
		return obj
	except Exception:
		return obj


import logging
import os
import re
import shlex
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple, Iterable

ALLOWED_CMDS: Tuple[str, ...] = (
	"ls",
	"cat",
	"wc",
	"head",
	"tail",
	"grep",
	"cut",
	"sort",
	"uniq",
	"mkdir",
	"rm",
	"mv",
	"cp",
	"pwd",
	"date",
	"touch",
)

@dataclass
class CommandResult:
	returncode: int
	stdout: str
	stderr: str
	truncated: bool = False

class SafeShellRunner:
	def __init__(self, sandbox_dir: str) -> None:
		self.sandbox_path = Path(sandbox_dir).resolve()
		self.sandbox_path.mkdir(parents=True, exist_ok=True)
		# Optional env allowlist from environment. Defaults to minimal safe set.
		allowlist_env = os.environ.get("AGI_RUNNER_ENV_ALLOWLIST", "PATH,LANG,LC_ALL").split(",")
		self._env_allowlist = tuple(n.strip() for n in allowlist_env if n.strip())
		# Optional additional secret name hints (comma separated, case-insensitive)
		extra_hints = os.environ.get("AGI_SECRET_NAME_HINTS", "").split(",")
		self._secret_name_hints = tuple(
			[
				"SECRET",
				"TOKEN",
				"KEY",
				"PASSWORD",
				"CREDENTIAL",
				"SESSION",
				"AUTH",
				"API",
			]
			+ [h.strip() for h in extra_hints if h.strip()]
		)
		# Compile static secret-like regexes
		self._secret_patterns = self._compile_secret_patterns()

	def _validate(self, argv: List[str]) -> None:
		if not argv:
			raise ValueError("Empty command")
		exe = Path(argv[0]).name
		if exe not in ALLOWED_CMDS:
			raise ValueError(f"Command not allowed: {exe}")
		# Reject suspicious tokens
		for token in argv[1:]:
			if token in {"|", ">", ">>", "<", "&&", ";"}:
				raise ValueError("Pipelines/redirection not allowed")
			if token.startswith("/"):
				raise ValueError("Absolute paths not allowed")
			if ".." in token:
				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,
			cwd=str(self.sandbox_path),
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
			timeout=timeout_sec,
			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env

	def _compile_secret_patterns(self) -> List[re.Pattern[str]]:
		patterns: List[str] = []
		# Common API key/token formats
		patterns += [
			r"sk-[A-Za-z0-9]{16,}",  # OpenAI-style
			r"ghp_[A-Za-z0-9]{20,}",  # GitHub token
			r"ya29\.[A-Za-z0-9\-_]+",  # Google OAuth
			r"xox[baprs]-[A-Za-z0-9\-]+",  # Slack tokens
			r"AKIA[0-9A-Z]{16}",  # AWS access key id
			r"ASIA[0-9A-Z]{16}",  # AWS temp key id
			r"(?i)aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*[A-Za-z0-9/+]{30,}",
			r"(?i)(bearer\s+)[A-Za-z0-9\-_.~+/]+=*",  # Authorization: Bearer ...
			r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",  # JWT-like
			r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
		]
		# Extra patterns from env (comma- or newline-separated)
		extra = os.environ.get("AGI_SECRET_PATTERNS", "")
		if extra:
			for raw in re.split(r"[\n,]", extra):
				raw = raw.strip()
				if raw:
					patterns.append(raw)
		compiled: List[re.Pattern[str]] = []
		for p in patterns:
			try:
				compiled.append(re.compile(p))
			except Exception:
				# Skip invalid regex
				pass
		return compiled

	def _redact(self, text: str) -> str:
		if not text:
			return text
		redacted = text
		# Redact environment-derived secret values
		for name, value in self._iter_potential_secrets(os.environ.items()):
			if value and isinstance(value, str) and len(value) >= 6:
				try:
					redacted = redacted.replace(value, "[REDACTED]")
				except Exception:
					pass
		# Redact by regex patterns
		for rx in self._secret_patterns:
			try:
				redacted = rx.sub("[REDACTED]", redacted)
			except Exception:
				pass
		return redacted

	def _iter_potential_secrets(self, items: Iterable[Tuple[str, str]]) -> Iterable[Tuple[str, str]]:
		for name, value in items:
			upper = name.upper()
			if any(h in upper for h in self._secret_name_hints):
				yield name, value
		# Also check common config file paths for inline secrets is out-of-scope here
		return


CommandResult

	"ls",
	"cat",
	"wc",
	"head",
	"tail",
	"grep",
	"cut",
	"sort",
	"uniq",
	"mkdir",
	"rm",
	"mv",
	"cp",
	"pwd",
	"date",
	"touch",
)

@dataclass
class CommandResult:
	returncode: int
	stdout: str
	stderr: str
	truncated: bool = False

class SafeShellRunner:
	def __init__(self, sandbox_dir: str) -> None:
		self.sandbox_path = Path(sandbox_dir).resolve()
		self.sandbox_path.mkdir(parents=True, exist_ok=True)
		# Optional env allowlist from environment. Defaults to minimal safe set.
		allowlist_env = os.environ.get("AGI_RUNNER_ENV_ALLOWLIST", "PATH,LANG,LC_ALL").split(",")
		self._env_allowlist = tuple(n.strip() for n in allowlist_env if n.strip())
		# Optional additional secret name hints (comma separated, case-insensitive)
		extra_hints = os.environ.get("AGI_SECRET_NAME_HINTS", "").split(",")
		self._secret_name_hints = tuple(
			[
				"SECRET",
				"TOKEN",
				"KEY",
				"PASSWORD",
				"CREDENTIAL",
				"SESSION",
				"AUTH",


SafeShellRunner

	"sort",
	"uniq",
	"mkdir",
	"rm",
	"mv",
	"cp",
	"pwd",
	"date",
	"touch",
)

@dataclass
class CommandResult:
	returncode: int
	stdout: str
	stderr: str
	truncated: bool = False

class SafeShellRunner:
	def __init__(self, sandbox_dir: str) -> None:
		self.sandbox_path = Path(sandbox_dir).resolve()
		self.sandbox_path.mkdir(parents=True, exist_ok=True)
		# Optional env allowlist from environment. Defaults to minimal safe set.
		allowlist_env = os.environ.get("AGI_RUNNER_ENV_ALLOWLIST", "PATH,LANG,LC_ALL").split(",")
		self._env_allowlist = tuple(n.strip() for n in allowlist_env if n.strip())
		# Optional additional secret name hints (comma separated, case-insensitive)
		extra_hints = os.environ.get("AGI_SECRET_NAME_HINTS", "").split(",")
		self._secret_name_hints = tuple(
			[
				"SECRET",
				"TOKEN",
				"KEY",
				"PASSWORD",
				"CREDENTIAL",
				"SESSION",
				"AUTH",
				"API",
			]
			+ [h.strip() for h in extra_hints if h.strip()]
		)
		# Compile static secret-like regexes
		self._secret_patterns = self._compile_secret_patterns()

	def _validate(self, argv: List[str]) -> None:
		if not argv:
			raise ValueError("Empty command")
		exe = Path(argv[0]).name
		if exe not in ALLOWED_CMDS:
			raise ValueError(f"Command not allowed: {exe}")
		# Reject suspicious tokens
		for token in argv[1:]:
			if token in {"|", ">", ">>", "<", "&&", ";"}:
				raise ValueError("Pipelines/redirection not allowed")
			if token.startswith("/"):
				raise ValueError("Absolute paths not allowed")
			if ".." in token:
				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,
			cwd=str(self.sandbox_path),
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
			timeout=timeout_sec,
			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env

	def _compile_secret_patterns(self) -> List[re.Pattern[str]]:
		patterns: List[str] = []
		# Common API key/token formats
		patterns += [
			r"sk-[A-Za-z0-9]{16,}",  # OpenAI-style
			r"ghp_[A-Za-z0-9]{20,}",  # GitHub token
			r"ya29\.[A-Za-z0-9\-_]+",  # Google OAuth
			r"xox[baprs]-[A-Za-z0-9\-]+",  # Slack tokens
			r"AKIA[0-9A-Z]{16}",  # AWS access key id
			r"ASIA[0-9A-Z]{16}",  # AWS temp key id
			r"(?i)aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*[A-Za-z0-9/+]{30,}",
			r"(?i)(bearer\s+)[A-Za-z0-9\-_.~+/]+=*",  # Authorization: Bearer ...
			r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",  # JWT-like
			r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
		]
		# Extra patterns from env (comma- or newline-separated)
		extra = os.environ.get("AGI_SECRET_PATTERNS", "")
		if extra:
			for raw in re.split(r"[\n,]", extra):
				raw = raw.strip()
				if raw:
					patterns.append(raw)
		compiled: List[re.Pattern[str]] = []
		for p in patterns:
			try:
				compiled.append(re.compile(p))
			except Exception:
				# Skip invalid regex
				pass
		return compiled

	def _redact(self, text: str) -> str:
		if not text:
			return text
		redacted = text
		# Redact environment-derived secret values
		for name, value in self._iter_potential_secrets(os.environ.items()):
			if value and isinstance(value, str) and len(value) >= 6:
				try:
					redacted = redacted.replace(value, "[REDACTED]")
				except Exception:
					pass
		# Redact by regex patterns
		for rx in self._secret_patterns:
			try:
				redacted = rx.sub("[REDACTED]", redacted)
			except Exception:
				pass
		return redacted

	def _iter_potential_secrets(self, items: Iterable[Tuple[str, str]]) -> Iterable[Tuple[str, str]]:
		for name, value in items:
			upper = name.upper()
			if any(h in upper for h in self._secret_name_hints):
				yield name, value
		# Also check common config file paths for inline secrets is out-of-scope here
		return


	"uniq",
	"mkdir",
	"rm",
	"mv",
	"cp",
	"pwd",
	"date",
	"touch",
)

@dataclass
class CommandResult:
	returncode: int
	stdout: str
	stderr: str
	truncated: bool = False

class SafeShellRunner:
	def __init__(self, sandbox_dir: str) -> None:
		self.sandbox_path = Path(sandbox_dir).resolve()
		self.sandbox_path.mkdir(parents=True, exist_ok=True)
		# Optional env allowlist from environment. Defaults to minimal safe set.
		allowlist_env = os.environ.get("AGI_RUNNER_ENV_ALLOWLIST", "PATH,LANG,LC_ALL").split(",")
		self._env_allowlist = tuple(n.strip() for n in allowlist_env if n.strip())
		# Optional additional secret name hints (comma separated, case-insensitive)
		extra_hints = os.environ.get("AGI_SECRET_NAME_HINTS", "").split(",")
		self._secret_name_hints = tuple(
			[
				"SECRET",
				"TOKEN",
				"KEY",
				"PASSWORD",
				"CREDENTIAL",
				"SESSION",
				"AUTH",
				"API",
			]
			+ [h.strip() for h in extra_hints if h.strip()]
		)
		# Compile static secret-like regexes
		self._secret_patterns = self._compile_secret_patterns()

	def _validate(self, argv: List[str]) -> None:
		if not argv:
			raise ValueError("Empty command")
		exe = Path(argv[0]).name
		if exe not in ALLOWED_CMDS:
			raise ValueError(f"Command not allowed: {exe}")
		# Reject suspicious tokens
		for token in argv[1:]:
			if token in {"|", ">", ">>", "<", "&&", ";"}:
				raise ValueError("Pipelines/redirection not allowed")
			if token.startswith("/"):
				raise ValueError("Absolute paths not allowed")
			if ".." in token:
				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,


		allowlist_env = os.environ.get("AGI_RUNNER_ENV_ALLOWLIST", "PATH,LANG,LC_ALL").split(",")
		self._env_allowlist = tuple(n.strip() for n in allowlist_env if n.strip())
		# Optional additional secret name hints (comma separated, case-insensitive)
		extra_hints = os.environ.get("AGI_SECRET_NAME_HINTS", "").split(",")
		self._secret_name_hints = tuple(
			[
				"SECRET",
				"TOKEN",
				"KEY",
				"PASSWORD",
				"CREDENTIAL",
				"SESSION",
				"AUTH",
				"API",
			]
			+ [h.strip() for h in extra_hints if h.strip()]
		)
		# Compile static secret-like regexes
		self._secret_patterns = self._compile_secret_patterns()

	def _validate(self, argv: List[str]) -> None:
		if not argv:
			raise ValueError("Empty command")
		exe = Path(argv[0]).name
		if exe not in ALLOWED_CMDS:
			raise ValueError(f"Command not allowed: {exe}")
		# Reject suspicious tokens
		for token in argv[1:]:
			if token in {"|", ">", ">>", "<", "&&", ";"}:
				raise ValueError("Pipelines/redirection not allowed")
			if token.startswith("/"):
				raise ValueError("Absolute paths not allowed")
			if ".." in token:
				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,
			cwd=str(self.sandbox_path),
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
			timeout=timeout_sec,
			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:


run

			+ [h.strip() for h in extra_hints if h.strip()]
		)
		# Compile static secret-like regexes
		self._secret_patterns = self._compile_secret_patterns()

	def _validate(self, argv: List[str]) -> None:
		if not argv:
			raise ValueError("Empty command")
		exe = Path(argv[0]).name
		if exe not in ALLOWED_CMDS:
			raise ValueError(f"Command not allowed: {exe}")
		# Reject suspicious tokens
		for token in argv[1:]:
			if token in {"|", ">", ">>", "<", "&&", ";"}:
				raise ValueError("Pipelines/redirection not allowed")
			if token.startswith("/"):
				raise ValueError("Absolute paths not allowed")
			if ".." in token:
				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,
			cwd=str(self.sandbox_path),
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
			timeout=timeout_sec,
			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env


split

			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env

	def _compile_secret_patterns(self) -> List[re.Pattern[str]]:
		patterns: List[str] = []
		# Common API key/token formats


		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env

	def _compile_secret_patterns(self) -> List[re.Pattern[str]]:
		patterns: List[str] = []
		# Common API key/token formats
		patterns += [
			r"sk-[A-Za-z0-9]{16,}",  # OpenAI-style
			r"ghp_[A-Za-z0-9]{20,}",  # GitHub token
			r"ya29\.[A-Za-z0-9\-_]+",  # Google OAuth
			r"xox[baprs]-[A-Za-z0-9\-]+",  # Slack tokens
			r"AKIA[0-9A-Z]{16}",  # AWS access key id
			r"ASIA[0-9A-Z]{16}",  # AWS temp key id
			r"(?i)aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*[A-Za-z0-9/+]{30,}",
			r"(?i)(bearer\s+)[A-Za-z0-9\-_.~+/]+=*",  # Authorization: Bearer ...
			r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",  # JWT-like
			r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
		]
		# Extra patterns from env (comma- or newline-separated)
		extra = os.environ.get("AGI_SECRET_PATTERNS", "")
		if extra:
			for raw in re.split(r"[\n,]", extra):


	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:
			env["PATH"] = os.environ.get("PATH", "/usr/bin:/bin:/usr/local/bin")
		# Set HOME to sandbox to avoid leaking user home
		env.setdefault("HOME", str(self.sandbox_path))
		# Locale defaults for deterministic behavior
		env.setdefault("LANG", "C.UTF-8")
		env.setdefault("LC_ALL", "C.UTF-8")
		return env

	def _compile_secret_patterns(self) -> List[re.Pattern[str]]:
		patterns: List[str] = []
		# Common API key/token formats
		patterns += [
			r"sk-[A-Za-z0-9]{16,}",  # OpenAI-style
			r"ghp_[A-Za-z0-9]{20,}",  # GitHub token
			r"ya29\.[A-Za-z0-9\-_]+",  # Google OAuth
			r"xox[baprs]-[A-Za-z0-9\-]+",  # Slack tokens
			r"AKIA[0-9A-Z]{16}",  # AWS access key id
			r"ASIA[0-9A-Z]{16}",  # AWS temp key id
			r"(?i)aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*[A-Za-z0-9/+]{30,}",
			r"(?i)(bearer\s+)[A-Za-z0-9\-_.~+/]+=*",  # Authorization: Bearer ...
			r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",  # JWT-like
			r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
		]
		# Extra patterns from env (comma- or newline-separated)
		extra = os.environ.get("AGI_SECRET_PATTERNS", "")
		if extra:
			for raw in re.split(r"[\n,]", extra):
				raw = raw.strip()
				if raw:
					patterns.append(raw)
		compiled: List[re.Pattern[str]] = []
		for p in patterns:
			try:
				compiled.append(re.compile(p))
			except Exception:
				# Skip invalid regex
				pass
		return compiled

	def _redact(self, text: str) -> str:
		if not text:
			return text
		redacted = text
		# Redact environment-derived secret values
		for name, value in self._iter_potential_secrets(os.environ.items()):
			if value and isinstance(value, str) and len(value) >= 6:
				try:
					redacted = redacted.replace(value, "[REDACTED]")
				except Exception:
					pass
		# Redact by regex patterns
		for rx in self._secret_patterns:
			try:
				redacted = rx.sub("[REDACTED]", redacted)
			except Exception:
				pass
		return redacted


			r"(?i)(bearer\s+)[A-Za-z0-9\-_.~+/]+=*",  # Authorization: Bearer ...
			r"eyJ[\w\-]{10,}\.[\w\-]{10,}\.[\w\-]{10,}",  # JWT-like
			r"(?i)(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s\"']{6,}",
		]
		# Extra patterns from env (comma- or newline-separated)
		extra = os.environ.get("AGI_SECRET_PATTERNS", "")
		if extra:
			for raw in re.split(r"[\n,]", extra):
				raw = raw.strip()
				if raw:
					patterns.append(raw)
		compiled: List[re.Pattern[str]] = []
		for p in patterns:
			try:
				compiled.append(re.compile(p))
			except Exception:
				# Skip invalid regex
				pass
		return compiled

	def _redact(self, text: str) -> str:
		if not text:
			return text
		redacted = text
		# Redact environment-derived secret values
		for name, value in self._iter_potential_secrets(os.environ.items()):
			if value and isinstance(value, str) and len(value) >= 6:
				try:
					redacted = redacted.replace(value, "[REDACTED]")
				except Exception:
					pass
		# Redact by regex patterns
		for rx in self._secret_patterns:
			try:
				redacted = rx.sub("[REDACTED]", redacted)
			except Exception:
				pass
		return redacted

	def _iter_potential_secrets(self, items: Iterable[Tuple[str, str]]) -> Iterable[Tuple[str, str]]:
		for name, value in items:
			upper = name.upper()
			if any(h in upper for h in self._secret_name_hints):
				yield name, value
		# Also check common config file paths for inline secrets is out-of-scope here
		return


	def _redact(self, text: str) -> str:
		if not text:
			return text
		redacted = text
		# Redact environment-derived secret values
		for name, value in self._iter_potential_secrets(os.environ.items()):
			if value and isinstance(value, str) and len(value) >= 6:
				try:
					redacted = redacted.replace(value, "[REDACTED]")
				except Exception:
					pass
		# Redact by regex patterns
		for rx in self._secret_patterns:
			try:
				redacted = rx.sub("[REDACTED]", redacted)
			except Exception:
				pass
		return redacted

	def _iter_potential_secrets(self, items: Iterable[Tuple[str, str]]) -> Iterable[Tuple[str, str]]:
		for name, value in items:
			upper = name.upper()
			if any(h in upper for h in self._secret_name_hints):
				yield name, value
		# Also check common config file paths for inline secrets is out-of-scope here
		return


				raise ValueError("Parent path traversal not allowed")

	def run(self, argv: List[str], timeout_sec: int = 10) -> CommandResult:
		self._validate(argv)
		proc = subprocess.run(
			argv,
			cwd=str(self.sandbox_path),
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
			timeout=timeout_sec,
			env=self._build_env(),
		)
		stdout = self._redact(proc.stdout)
		stderr = self._redact(proc.stderr)
		# Optional truncation to cap very large outputs (env AGI_RUNNER_MAX_OUTPUT_CHARS)
		truncated = False
		try:
			max_chars = int(os.environ.get("AGI_RUNNER_MAX_OUTPUT_CHARS", "0") or 0)
			if max_chars and max_chars > 0:
				def _truncate(s: str) -> str:
					return (s[:max_chars] + "\n...[TRUNCATED]...") if len(s) > max_chars else s
				new_stdout = _truncate(stdout)
				new_stderr = _truncate(stderr)
				truncated = (new_stdout != stdout) or (new_stderr != stderr)
				stdout, stderr = new_stdout, new_stderr
		except Exception:
			pass
		return CommandResult(proc.returncode, stdout, stderr, truncated)

	@staticmethod
	def split(cmd: str) -> List[str]:
		return shlex.split(cmd)

	def _build_env(self) -> Dict[str, str]:
		# Build a minimal environment, preserving an allowlisted subset.
		env: Dict[str, str] = {}
		for name in self._env_allowlist:
			if name and name in os.environ:
				env[name] = os.environ[name]
		# Ensure PATH exists minimally to run coreutils
		if "PATH" not in env:


from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Tuple, Optional

import time

from agi_dw.bench.common.adapters import BenchmarkAdapter

def run_with_adapter(args: Any, adapter: BenchmarkAdapter) -> int:
	"""Run a benchmark end-to-end using the provided adapter and shared harness utilities.

	This wires in our LLM client, caching, critic/verifier, tracing, and run artifacts.
	"""
	from agi_dw.bench.common.harness import get_llm_and_basics  # type: ignore
	from agi_dw.bench.common.pipeline import (
		init_trace_paths,
		build_env_fingerprint,
		seed_everything,
		get_shard_tag,
		init_suite_temp_paths,
		write_run_artifact,
		write_samples_and_verbose,
		summarize_telemetry,
		compute_sharded_outpath,
		write_trace,
		read_pass_cache,
		update_pass_cache,
		maybe_short_circuit_from_pass_cache,
		emit_indirect_trace_datasets,
	)
	from agi_dw.bench.common.harness import generate_candidates_for_prompt  # type: ignore
	from agi_dw.core.utils.bench_utils import ensure_safe_env  # type: ignore
	from agi_dw.core.ops.tracing import trace_span, meter_cost  # type: ignore

	root = Path(__file__).resolve().parents[2]
	ensure_safe_env()

	suite = adapter.suite()
	run_started_ts = time.time()
	traces_dir, trace_path, ts_str = init_trace_paths(root, suite, run_started_ts)
	try:
		seed_everything(getattr(args, "seed", None))
	except Exception:
		pass

	# Env fingerprint
	mods = ["torch", "datasets", "transformers"] + list(adapter.env_modules() or [])
	env_fp = build_env_fingerprint(mods)

	# Shard tag for temp output paths
	shard_tag = get_shard_tag(args)
	samples_path, verbose_path, _errors_path = init_suite_temp_paths(root, suite, shard_tag)

	# Load tasks and shard if needed
	with trace_span("adapter.load_tasks", {"suite": suite}):
		tasks = adapter.load_tasks(args)
	task_ids: List[str] = sorted(list(tasks.keys()))
	try:
		lim = int(getattr(args, "limit", 0) or 0)
		if lim > 0:
			task_ids = task_ids[: lim]
	except Exception:
		pass

	# LLM, cache, logger, critic
	llm, cache, logger, critic = get_llm_and_basics(args, root, suite, default_adapter=None)

	# Pass-cache short-circuit support
	pass_cache, pass_cache_path = read_pass_cache(root, suite)

	all_rows: List[Dict[str, Any]] = []
	for tid in task_ids:
		task = tasks[tid]
		user_inp = adapter.build_input(tid, task)
		base_prompt = user_inp
		if logger is not None:
			try:
				logger.log_prompt(suite=suite, task_id=tid, prompt=base_prompt)
			except Exception:
				pass

		# Short-circuit from cache if enabled
		sc_rows = maybe_short_circuit_from_pass_cache(suite, tid, base_prompt, pass_cache, trace_path, env_fp, args)
		if sc_rows is not None:
			all_rows.extend(sc_rows)
			continue

		# Generate candidates using shared harness
		with trace_span("generate_candidates", {"suite": suite, "task_id": tid}):
			cand_rows, chosen = generate_candidates_for_prompt(
				llm=llm,
				cache=cache,
				logger=logger,
				critic=critic,
				prompt=adapter.system_prompt(),
				inp=base_prompt,
				sanitize_fn=lambda body, _prompt: adapter.sanitize_completion(body, base_prompt, task),
				task_id=tid,
				args=args,
				suite=suite,
				env_fp=env_fp,
				trace_path=trace_path,
				_prompts_hasher=__import__("hashlib").sha256(),
			)
		all_rows.extend(cand_rows)

	# Write samples and verbose sidecar
	write_samples_and_verbose(all_rows, samples_path, verbose_path)

	# Adapter may provide external predictions/evaluator; otherwise, treat as no-op evaluation
	results_path: Optional[Path] = None
	results_by_task: Dict[str, bool] = {tid: False for tid in task_ids}
	try:
		pred_path = adapter.build_predictions(samples_path, args, root, task_ids, tasks)
		if pred_path is not None:
			_ = adapter.run_official_evaluator(pred_path, args, root, task_ids)
	except Exception:
		pass
	try:
		# If adapter implements evaluate (legacy path), allow it to return per-task flags
		maybe_eval = getattr(adapter, "evaluate", None)
		if callable(maybe_eval):
			results_by_task, results_path = adapter.evaluate(samples_path, args, root, task_ids, tasks)  # type: ignore
	except Exception:
		pass
	telemetry, pass1 = summarize_telemetry(task_ids, results_by_task)

	# Update pass cache with chosen completions (skip for benchmarks by default)
	try:
		if not bool(getattr(args, "benchmark_mode", True)):
			latest: Dict[str, Dict[str, str]] = {}
			for obj in all_rows:
				if str(obj.get("role", "chosen")) != "chosen":
					continue
				tid = str(obj.get("task_id", ""))
				if not tid:
					continue
				latest[tid] = {"completion": str(obj.get("completion", "")), "prompt": str(obj.get("orig_prompt", ""))}
			update_pass_cache(pass_cache_path, pass_cache, latest)
	except Exception:
		pass

	# Suite-specific final result file mirrors other runners
	from agi_dw.bench.common.pipeline import write_suite_out  # type: ignore
	outp = write_suite_out(str(getattr(args, "out")), args, suite, task_ids, results_by_task)

	# Optional: emit indirect learning dataset (trace-only) if enabled (default True)
	try:
		if bool(getattr(args, "emit_indirect", True)):
			emit_indirect_trace_datasets(root, suite, trace_path, results_by_task)
	except Exception:
		pass

	# Run artifact
	try:
		write_run_artifact(
			root=root,
			suite=suite,
			run_started_ts=run_started_ts,
			env=env_fp,
			prompts_hasher=__import__("hashlib").sha256(),
			args_summary=adapter.args_summary(args),
			paths={"samples": str(samples_path), "verbose": str(verbose_path), "results": (str(results_path) if results_path else "")},
			aggregate={"n": len(task_ids)},
			metrics={"pass1": (float(pass1) if pass1 is not None else None), **telemetry},
			model=str(getattr(args, "model", "")),
			adapter_dir=str(getattr(args, "adapter_dir", "") or ""),
		)
	except Exception:
		pass

	print(__import__("json").dumps({"ok": True, "out": str(outp), "n": len(task_ids)}))
	return 0