			if env_allow:
				obs_aug["policy"] = obs_aug.get("policy", {})
				obs_aug["policy"]["env_allowlist"] = [s.strip() for s in str(env_allow).split(",") if s.strip()]
		if cfg.inject_caps:
			pr_path = root_dir / "data" / "sandbox" / "tmp" / "plan_risk.json"
			if pr_path.exists():
				obj = json.loads(pr_path.read_text(encoding="utf-8"))
				caps = obj.get("caps", {}) if isinstance(obj, dict) else {}
				if isinstance(caps, dict) and caps:
					obs_aug["policy"] = obs_aug.get("policy", {})
					obs_aug["policy"]["caps"] = {
						k: int(v) if isinstance(v, (int, float)) and k != "max_risk" else float(v) if k == "max_risk" else v
						for k, v in caps.items()
					}
	except Exception:
		pass

	return obs_aug, snippets, query_ms

def match_skill_action(
	domain: str,
	root_dir: Path,
	obs: Dict[str, Any],
	action: Dict[str, Any],
) -> Tuple[Dict[str, Any], Dict[str, Optional[str]]]:
	"""
	Try to match a promoted skill and return possibly replaced action and any adapter overrides
	(e.g., {"verifier": path, "planner": path}).
	"""
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		adapters: Dict[str, Optional[str]] = {"verifier": None, "planner": None}
		if domain == "cli":
			tool = (action or {}).get("tool") if isinstance(action, dict) else None
			argv0 = None
			try:
				argv0 = (action or {}).get("args", {}).get("argv", [None])[0]
			except Exception:
				argv0 = None
			sig: Dict[str, str] = {}
			if tool:
				sig["tool"] = str(tool)
			if argv0:
				sig["argv0"] = str(argv0)
			if sig:
				sk = lib.match("cli", sig)
				if sk is not None and isinstance(sk.action, dict):
					action = sk.action
					action["skill_id"] = sk.id
					try:
						sk_obj = lib.get_by_id(sk.id)
						if sk_obj and sk_obj.adapters:
							adapters["verifier"] = sk_obj.adapters.get("verifier")
							adapters["planner"] = sk_obj.adapters.get("planner")
					except Exception:
						pass
			return action, adapters
		elif domain == "dom":
			meta = obs.get("meta", {}) if isinstance(obs, dict) else {}
			url_sig = meta.get("url") or (action.get("args", {}).get("url") if isinstance(action, dict) else None)
			sel_sig = meta.get("selector") or (action.get("args", {}).get("selector") if isinstance(action, dict) else None)
			sig = {"url": str(url_sig or ""), "selector": str(sel_sig or "")}
			sk = lib.match("dom", sig)
			if sk is not None and isinstance(sk.action, dict):
				action = sk.action
				action["skill_id"] = sk.id
				try:
					sk_obj = lib.get_by_id(sk.id)
					if sk_obj and sk_obj.adapters:
						adapters["verifier"] = sk_obj.adapters.get("verifier")
						adapters["planner"] = sk_obj.adapters.get("planner")
				except Exception:
					pass
			return action, adapters
		else:
			return action, {"verifier": None, "planner": None}
	except Exception:
		return action, {"verifier": None, "planner": None}

def get_adapter_bank(root_dir: Path, bank_name: Optional[str]) -> Dict[str, str]:
	"""Return an adapter mapping for roles (e.g., {"planner": dir, "verifier": dir}) from a named bank.

	If bank_name is None or missing, returns an empty mapping.
	"""
	if not bank_name:
		return {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		bank = lib.get_adapter_bank(str(bank_name)) or {}
		out: Dict[str, str] = {}
		for role in ("planner", "verifier"):
			val = bank.get(role)
			if isinstance(val, str) and val.strip():
				out[role] = val.strip()
		return out
	except Exception:


			sel_sig = meta.get("selector") or (action.get("args", {}).get("selector") if isinstance(action, dict) else None)
			sig = {"url": str(url_sig or ""), "selector": str(sel_sig or "")}
			sk = lib.match("dom", sig)
			if sk is not None and isinstance(sk.action, dict):
				action = sk.action
				action["skill_id"] = sk.id
				try:
					sk_obj = lib.get_by_id(sk.id)
					if sk_obj and sk_obj.adapters:
						adapters["verifier"] = sk_obj.adapters.get("verifier")
						adapters["planner"] = sk_obj.adapters.get("planner")
				except Exception:
					pass
			return action, adapters
		else:
			return action, {"verifier": None, "planner": None}
	except Exception:
		return action, {"verifier": None, "planner": None}

def get_adapter_bank(root_dir: Path, bank_name: Optional[str]) -> Dict[str, str]:
	"""Return an adapter mapping for roles (e.g., {"planner": dir, "verifier": dir}) from a named bank.

	If bank_name is None or missing, returns an empty mapping.
	"""
	if not bank_name:
		return {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		bank = lib.get_adapter_bank(str(bank_name)) or {}
		out: Dict[str, str] = {}
		for role in ("planner", "verifier"):
			val = bank.get(role)
			if isinstance(val, str) and val.strip():
				out[role] = val.strip()
		return out
	except Exception:
		return {}

def pick_skill_adapters(root_dir: Path, domain: str, signature: Dict[str, str]) -> Dict[str, str]:
	"""Attempt to find a matching skill and return its adapters mapping if present."""
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		sk = lib.match(domain, signature)
		if sk and sk.adapters:
			out: Dict[str, str] = {}
			for role in ("planner", "verifier"):
				val = sk.adapters.get(role) if isinstance(sk.adapters, dict) else None
				if isinstance(val, str) and val.strip():
					out[role] = val.strip()
			return out
	except Exception:
		pass
	return {}


	"""Return an adapter mapping for roles (e.g., {"planner": dir, "verifier": dir}) from a named bank.

	If bank_name is None or missing, returns an empty mapping.
	"""
	if not bank_name:
		return {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		bank = lib.get_adapter_bank(str(bank_name)) or {}
		out: Dict[str, str] = {}
		for role in ("planner", "verifier"):
			val = bank.get(role)
			if isinstance(val, str) and val.strip():
				out[role] = val.strip()
		return out
	except Exception:
		return {}

def pick_skill_adapters(root_dir: Path, domain: str, signature: Dict[str, str]) -> Dict[str, str]:
	"""Attempt to find a matching skill and return its adapters mapping if present."""
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root_dir))
		sk = lib.match(domain, signature)
		if sk and sk.adapters:
			out: Dict[str, str] = {}
			for role in ("planner", "verifier"):
				val = sk.adapters.get(role) if isinstance(sk.adapters, dict) else None
				if isinstance(val, str) and val.strip():
					out[role] = val.strip()
			return out
	except Exception:
		pass
	return {}


from __future__ import annotations
import logging

import json
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

@dataclass
class Skill:
	id: str
	domain: str  # "cli" | "dom" | "code" | ...
	description: str
	signature: Dict[str, Any]  # minimal inputs needed to reuse (e.g., {"tool":"grep"} or {"url": "..."})
	action: Dict[str, Any]  # canonical action payload (tool+args)
	metrics: Dict[str, Any]  # success_count, attempts, success_rate, last_used
	adapters: Dict[str, str] | None = None  # optional PEFT adapters per role: {"planner": dir, "verifier": dir}
	version: str = "0.1"

class SkillLibrary:
	"""Lightweight skill registry stored under data/skills/lib.

	Structure:
	- data/skills/lib/skills.jsonl : one JSON per skill
	- data/skills/lib/registry.json : aux info (adapters registry, promotion thresholds)
	- data/skills/lib/versions/<name>/skills.jsonl : versioned snapshots
	"""

	def __init__(self, root: str) -> None:
		self.root = Path(root)
		self.lib_dir = self.root / "data" / "skills" / "lib"
		self.lib_dir.mkdir(parents=True, exist_ok=True)
		self.skills_path = self.lib_dir / "skills.jsonl"
		self.registry_path = self.lib_dir / "registry.json"
		self.versions_dir = self.lib_dir / "versions"
		self.versions_dir.mkdir(parents=True, exist_ok=True)
		self.skills: List[Skill] = []
		self.registry: Dict[str, Any] = {}
		self._load()

	def _load(self) -> None:
		self.skills = []
		if self.skills_path.exists():
			with self.skills_path.open("r", encoding="utf-8") as f:
				for line in f:
					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:
			if s.domain != domain:
				continue
			try:
				if _match(signature, s.signature):
					return s
			except Exception:
				continue
		return None


Skill

from __future__ import annotations
import logging

import json
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

@dataclass
class Skill:
	id: str
	domain: str  # "cli" | "dom" | "code" | ...
	description: str
	signature: Dict[str, Any]  # minimal inputs needed to reuse (e.g., {"tool":"grep"} or {"url": "..."})
	action: Dict[str, Any]  # canonical action payload (tool+args)
	metrics: Dict[str, Any]  # success_count, attempts, success_rate, last_used
	adapters: Dict[str, str] | None = None  # optional PEFT adapters per role: {"planner": dir, "verifier": dir}
	version: str = "0.1"

class SkillLibrary:
	"""Lightweight skill registry stored under data/skills/lib.

	Structure:
	- data/skills/lib/skills.jsonl : one JSON per skill
	- data/skills/lib/registry.json : aux info (adapters registry, promotion thresholds)
	- data/skills/lib/versions/<name>/skills.jsonl : versioned snapshots
	"""

	def __init__(self, root: str) -> None:
		self.root = Path(root)
		self.lib_dir = self.root / "data" / "skills" / "lib"
		self.lib_dir.mkdir(parents=True, exist_ok=True)
		self.skills_path = self.lib_dir / "skills.jsonl"
		self.registry_path = self.lib_dir / "registry.json"
		self.versions_dir = self.lib_dir / "versions"
		self.versions_dir.mkdir(parents=True, exist_ok=True)
		self.skills: List[Skill] = []


SkillLibrary

import logging

import json
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

@dataclass
class Skill:
	id: str
	domain: str  # "cli" | "dom" | "code" | ...
	description: str
	signature: Dict[str, Any]  # minimal inputs needed to reuse (e.g., {"tool":"grep"} or {"url": "..."})
	action: Dict[str, Any]  # canonical action payload (tool+args)
	metrics: Dict[str, Any]  # success_count, attempts, success_rate, last_used
	adapters: Dict[str, str] | None = None  # optional PEFT adapters per role: {"planner": dir, "verifier": dir}
	version: str = "0.1"

class SkillLibrary:
	"""Lightweight skill registry stored under data/skills/lib.

	Structure:
	- data/skills/lib/skills.jsonl : one JSON per skill
	- data/skills/lib/registry.json : aux info (adapters registry, promotion thresholds)
	- data/skills/lib/versions/<name>/skills.jsonl : versioned snapshots
	"""

	def __init__(self, root: str) -> None:
		self.root = Path(root)
		self.lib_dir = self.root / "data" / "skills" / "lib"
		self.lib_dir.mkdir(parents=True, exist_ok=True)
		self.skills_path = self.lib_dir / "skills.jsonl"
		self.registry_path = self.lib_dir / "registry.json"
		self.versions_dir = self.lib_dir / "versions"
		self.versions_dir.mkdir(parents=True, exist_ok=True)
		self.skills: List[Skill] = []
		self.registry: Dict[str, Any] = {}
		self._load()

	def _load(self) -> None:
		self.skills = []
		if self.skills_path.exists():
			with self.skills_path.open("r", encoding="utf-8") as f:
				for line in f:
					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:
			if s.domain != domain:
				continue
			try:
				if _match(signature, s.signature):
					return s
			except Exception:
				continue
		return None


class Skill:
	id: str
	domain: str  # "cli" | "dom" | "code" | ...
	description: str
	signature: Dict[str, Any]  # minimal inputs needed to reuse (e.g., {"tool":"grep"} or {"url": "..."})
	action: Dict[str, Any]  # canonical action payload (tool+args)
	metrics: Dict[str, Any]  # success_count, attempts, success_rate, last_used
	adapters: Dict[str, str] | None = None  # optional PEFT adapters per role: {"planner": dir, "verifier": dir}
	version: str = "0.1"

class SkillLibrary:
	"""Lightweight skill registry stored under data/skills/lib.

	Structure:
	- data/skills/lib/skills.jsonl : one JSON per skill
	- data/skills/lib/registry.json : aux info (adapters registry, promotion thresholds)
	- data/skills/lib/versions/<name>/skills.jsonl : versioned snapshots
	"""

	def __init__(self, root: str) -> None:
		self.root = Path(root)
		self.lib_dir = self.root / "data" / "skills" / "lib"
		self.lib_dir.mkdir(parents=True, exist_ok=True)
		self.skills_path = self.lib_dir / "skills.jsonl"
		self.registry_path = self.lib_dir / "registry.json"
		self.versions_dir = self.lib_dir / "versions"
		self.versions_dir.mkdir(parents=True, exist_ok=True)
		self.skills: List[Skill] = []
		self.registry: Dict[str, Any] = {}
		self._load()

	def _load(self) -> None:
		self.skills = []
		if self.skills_path.exists():
			with self.skills_path.open("r", encoding="utf-8") as f:
				for line in f:
					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})


	"""Lightweight skill registry stored under data/skills/lib.

	Structure:
	- data/skills/lib/skills.jsonl : one JSON per skill
	- data/skills/lib/registry.json : aux info (adapters registry, promotion thresholds)
	- data/skills/lib/versions/<name>/skills.jsonl : versioned snapshots
	"""

	def __init__(self, root: str) -> None:
		self.root = Path(root)
		self.lib_dir = self.root / "data" / "skills" / "lib"
		self.lib_dir.mkdir(parents=True, exist_ok=True)
		self.skills_path = self.lib_dir / "skills.jsonl"
		self.registry_path = self.lib_dir / "registry.json"
		self.versions_dir = self.lib_dir / "versions"
		self.versions_dir.mkdir(parents=True, exist_ok=True)
		self.skills: List[Skill] = []
		self.registry: Dict[str, Any] = {}
		self._load()

	def _load(self) -> None:
		self.skills = []
		if self.skills_path.exists():
			with self.skills_path.open("r", encoding="utf-8") as f:
				for line in f:
					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:


		self.skills = []
		if self.skills_path.exists():
			with self.skills_path.open("r", encoding="utf-8") as f:
				for line in f:
					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (


					line = line.strip()
					if not line:
						continue
					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)


					obj = json.loads(line)
					self.skills.append(Skill(**obj))
		self.registry = {}
		if self.registry_path.exists():
			try:
				self.registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)


list

			except Exception:
				self.registry = {}
		# Defaults
		self.registry.setdefault("promotion_thresholds", {"min_success": 3, "min_success_rate": 0.7})
		self.registry.setdefault("adapters", {})
		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False


promote

		self.registry.setdefault("versions", {"current": "main", "list": ["main"]})

	def _save_skill(self, sk: Skill) -> None:
		with self.skills_path.open("a", encoding="utf-8") as f:
			f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def save_registry(self) -> None:
		self.registry_path.write_text(json.dumps(self.registry, ensure_ascii=False, indent=2), encoding="utf-8")

	def _rewrite_all(self) -> None:
		# Rewrite entire skills file from current self.skills
		with self.skills_path.open("w", encoding="utf-8") as f:
			for sk in self.skills:
				f.write(json.dumps(asdict(sk), ensure_ascii=False) + "\n")

	def list(self, domain: Optional[str] = None) -> List[Skill]:
		if domain:
			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)


			return [s for s in self.skills if s.domain == domain]
		return list(self.skills)

	def promote(self, sk: Skill, force: bool = False) -> bool:
		thr = self.registry.get("promotion_thresholds", {})
		min_succ = int(thr.get("min_success", 3))
		min_rate = float(thr.get("min_success_rate", 0.7))
		ok = force or (
			int(sk.metrics.get("success_count", 0)) >= min_succ and
			float(sk.metrics.get("success_rate", 0.0)) >= min_rate
		)
		if not ok:
			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):


			return False
		# Avoid duplicates by id
		if any(s.id == sk.id for s in self.skills):
			return False
		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)


		self.skills.append(sk)
		self._save_skill(sk)
		return True

	def update_adapters(self, skill_id: str, adapters: Dict[str, str]) -> bool:
		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False


		updated = False
		for i, s in enumerate(self.skills):
			if s.id == skill_id:
				s.adapters = adapters
				self.skills[i] = s
				updated = True
				break
		if updated:
			self._rewrite_all()
		return updated

	def get_adapter_bank(self, name: str) -> Dict[str, str]:
		banks = self.registry.setdefault("adapter_banks", {})
		return dict(banks.get(name, {}))

	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:


	def set_adapter_bank(self, name: str, bank: Dict[str, str]) -> None:
		banks = self.registry.setdefault("adapter_banks", {})
		banks[name] = dict(bank)
		self.save_registry()

	def snapshot_version(self, name: str) -> str:
		"""Snapshot current skills.jsonl into versions/<name>/skills.jsonl and set current to name."""
		ver_dir = self.versions_dir / name
		ver_dir.mkdir(parents=True, exist_ok=True)
		dst = ver_dir / "skills.jsonl"
		if self.skills_path.exists():
			dst.write_text(self.skills_path.read_text(encoding="utf-8"), encoding="utf-8")
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)


		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:


		return str(dst)

	def restore_version(self, name: str) -> bool:
		"""Restore skills from a saved version into the active skills.jsonl."""
		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:


		src = self.versions_dir / name / "skills.jsonl"
		if not src.exists():
			return False
		self.skills_path.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
		self._load()
		vers = self.registry.setdefault("versions", {"current": name, "list": []})
		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:
			if s.domain != domain:
				continue
			try:
				if _match(signature, s.signature):
					return s
			except Exception:


match

		vers["current"] = name
		if name not in vers.get("list", []):
			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:
			if s.domain != domain:
				continue
			try:
				if _match(signature, s.signature):
					return s
			except Exception:
				continue
		return None


			vers.setdefault("list", []).append(name)
		self.save_registry()
		return True

	def current_version(self) -> str:
		vers = self.registry.get("versions", {})
		return str(vers.get("current", "main"))

	def list_versions(self) -> List[str]:
		vers = self.registry.get("versions", {})
		return list(vers.get("list", []) or [])

	def get_by_id(self, skill_id: str) -> Optional[Skill]:
		for s in self.skills:
			if s.id == skill_id:
				return s
		return None

	def match(self, domain: str, signature: Dict[str, Any]) -> Optional[Skill]:
		# Very simple match: requires all signature keys to be substrings/equality in candidate
		def _match(sig: Dict[str, Any], cand: Dict[str, Any]) -> bool:
			for k, v in sig.items():
				cv = cand.get(k)
				if isinstance(v, str) and isinstance(cv, str):
					if v and (v not in cv):
						return False
				else:
					if v != cv:
						return False
			return True
		for s in self.skills:
			if s.domain != domain:
				continue
			try:
				if _match(signature, s.signature):
					return s
			except Exception:
				continue
		return None


"""Code-specific memory operations for HumanEval and similar tasks."""

from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from pathlib import Path
import json

from .hybrid import HybridMemory, MemoryConfig

# Optional utilities; provide robust fallbacks if unavailable
try:
    from ..utils.code_utils import extract_function_signature, normalize_code  # type: ignore
except Exception:  # Fallbacks to avoid hard dependency
    try:
        # Reuse existing helper to extract target function name
        from ..utils.bench_utils import extract_target_function_name  # type: ignore
    except Exception:
        extract_target_function_name = None  # type: ignore

    def extract_function_signature(prompt: str):  # type: ignore
        try:
            if extract_target_function_name is not None:
                name = extract_target_function_name(prompt)  # type: ignore
                if name:
                    return f"def {name}("
        except Exception:
            pass
        return None

    def normalize_code(text: str):  # type: ignore
        # Identity fallback – leave code unchanged
        return text

class CodeMemory:
    """Specialized memory operations for code tasks."""

    def __init__(
        self,
        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )

        self.memory = HybridMemory(config)
        if memory_dir:
            memory_dir = Path(memory_dir)
            if memory_dir.exists():
                self.memory = HybridMemory.load(str(memory_dir), config)

        # Cache for function signatures
        self.signature_cache: Dict[str, str] = {}

    def store_solution(
        self,
        problem_id: str,
        prompt: str,
        solution: str,
        success: bool = True,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        """Store a solution with its problem context."""
        if not solution.strip():
            return

        # Extract and cache function signature
        signature = self.get_signature(prompt)
        if signature:
            self.signature_cache[problem_id] = signature

        # Store with appropriate tags and quality
        quality = 1.0 if success else 0.3
        tags = ["code", "solution", f"problem:{problem_id}"]
        if metadata:
            tags.extend(metadata.get("tags", []))

        # Store in memory systems
        self.memory.add(
            uid=f"{problem_id}:{hash(solution)}",
            text=solution,
            tags=tags,
            quality=quality,
            memory_type="product_key"  # Prefer product key for code
        )

        # Also store problem-solution pair
        context = f"Problem:\n{prompt}\n\nSolution:\n{solution}"
        self.memory.add(
            uid=f"{problem_id}:context",
            text=context,
            tags=tags + ["context"],
            quality=quality,
            memory_type="hybrid"  # Store in both for different query types
        )

    def get_signature(self, prompt: str) -> Optional[str]:
        """Extract function signature from prompt."""
        try:
            return extract_function_signature(prompt)
        except Exception:
            return None

    def find_similar_solutions(
        self,
        prompt: str,
        k: int = 5,
        problem_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Find similar solutions for a given problem."""
        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,
                k=k,
                memory_type="product_key",
                quality_weight=0.4
            )
            if sig_results:
                return sig_results

        # Fall back to semantic search
        return self.memory.query(
            prompt,
            k=k,
            memory_type="hybrid",
            quality_weight=0.3
        )

    def enhance_prompt(
        self,
        prompt: str,
        problem_id: Optional[str] = None,
        max_examples: int = 2
    ) -> str:
        """Enhance prompt with relevant examples."""
        similar = self.find_similar_solutions(prompt, k=max_examples, problem_id=problem_id)

        if not similar:
            return prompt

        # Add examples as comments
        examples = []
        for item in similar:
            if "text" in item:
                code = normalize_code(item["text"])
                examples.append(f"# Similar example (score: {item['score']:.2f}):\n{code}")

        if examples:
            enhanced = f"{prompt}\n\n# Reference examples:\n{'#' * 40}\n"
            enhanced += "\n\n".join(examples)
            enhanced += f"\n{'#' * 40}\n\n"
            return enhanced

        return prompt

    def save(self, out_dir: str) -> None:
        """Save memory state."""
        out_dir = Path(out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)

        # Save memory systems
        self.memory.save(str(out_dir))

        # Save signature cache
        with open(out_dir / "signatures.json", "w") as f:
            json.dump(self.signature_cache, f, indent=2)

    @classmethod
    def load(
        cls,
        in_dir: str,
        config: Optional[MemoryConfig] = None
    ) -> "CodeMemory":
        """Load memory state."""
        mem = cls(memory_dir=in_dir, config=config)

        # Load signature cache
        try:
            with open(Path(in_dir) / "signatures.json") as f:
                mem.signature_cache = json.load(f)
        except Exception:
            mem.signature_cache = {}

        return mem


CodeMemory

    try:
        # Reuse existing helper to extract target function name
        from ..utils.bench_utils import extract_target_function_name  # type: ignore
    except Exception:
        extract_target_function_name = None  # type: ignore

    def extract_function_signature(prompt: str):  # type: ignore
        try:
            if extract_target_function_name is not None:
                name = extract_target_function_name(prompt)  # type: ignore
                if name:
                    return f"def {name}("
        except Exception:
            pass
        return None

    def normalize_code(text: str):  # type: ignore
        # Identity fallback – leave code unchanged
        return text

class CodeMemory:
    """Specialized memory operations for code tasks."""

    def __init__(
        self,
        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )

        self.memory = HybridMemory(config)
        if memory_dir:
            memory_dir = Path(memory_dir)
            if memory_dir.exists():
                self.memory = HybridMemory.load(str(memory_dir), config)

        # Cache for function signatures
        self.signature_cache: Dict[str, str] = {}

    def store_solution(
        self,
        problem_id: str,
        prompt: str,
        solution: str,
        success: bool = True,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        """Store a solution with its problem context."""
        if not solution.strip():
            return

        # Extract and cache function signature
        signature = self.get_signature(prompt)
        if signature:
            self.signature_cache[problem_id] = signature

        # Store with appropriate tags and quality
        quality = 1.0 if success else 0.3
        tags = ["code", "solution", f"problem:{problem_id}"]
        if metadata:
            tags.extend(metadata.get("tags", []))

        # Store in memory systems
        self.memory.add(
            uid=f"{problem_id}:{hash(solution)}",
            text=solution,
            tags=tags,
            quality=quality,
            memory_type="product_key"  # Prefer product key for code
        )

        # Also store problem-solution pair
        context = f"Problem:\n{prompt}\n\nSolution:\n{solution}"
        self.memory.add(
            uid=f"{problem_id}:context",
            text=context,
            tags=tags + ["context"],
            quality=quality,
            memory_type="hybrid"  # Store in both for different query types
        )

    def get_signature(self, prompt: str) -> Optional[str]:
        """Extract function signature from prompt."""
        try:
            return extract_function_signature(prompt)
        except Exception:
            return None

    def find_similar_solutions(
        self,
        prompt: str,
        k: int = 5,
        problem_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Find similar solutions for a given problem."""
        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,
                k=k,
                memory_type="product_key",
                quality_weight=0.4
            )
            if sig_results:
                return sig_results

        # Fall back to semantic search
        return self.memory.query(
            prompt,
            k=k,
            memory_type="hybrid",
            quality_weight=0.3
        )

    def enhance_prompt(
        self,
        prompt: str,
        problem_id: Optional[str] = None,
        max_examples: int = 2
    ) -> str:
        """Enhance prompt with relevant examples."""
        similar = self.find_similar_solutions(prompt, k=max_examples, problem_id=problem_id)

        if not similar:
            return prompt

        # Add examples as comments
        examples = []
        for item in similar:
            if "text" in item:
                code = normalize_code(item["text"])
                examples.append(f"# Similar example (score: {item['score']:.2f}):\n{code}")

        if examples:
            enhanced = f"{prompt}\n\n# Reference examples:\n{'#' * 40}\n"
            enhanced += "\n\n".join(examples)
            enhanced += f"\n{'#' * 40}\n\n"
            return enhanced

        return prompt

    def save(self, out_dir: str) -> None:
        """Save memory state."""
        out_dir = Path(out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)

        # Save memory systems
        self.memory.save(str(out_dir))

        # Save signature cache
        with open(out_dir / "signatures.json", "w") as f:
            json.dump(self.signature_cache, f, indent=2)

    @classmethod
    def load(
        cls,
        in_dir: str,
        config: Optional[MemoryConfig] = None
    ) -> "CodeMemory":
        """Load memory state."""
        mem = cls(memory_dir=in_dir, config=config)

        # Load signature cache
        try:
            with open(Path(in_dir) / "signatures.json") as f:
                mem.signature_cache = json.load(f)
        except Exception:
            mem.signature_cache = {}

        return mem


    except Exception:
        extract_target_function_name = None  # type: ignore

    def extract_function_signature(prompt: str):  # type: ignore
        try:
            if extract_target_function_name is not None:
                name = extract_target_function_name(prompt)  # type: ignore
                if name:
                    return f"def {name}("
        except Exception:
            pass
        return None

    def normalize_code(text: str):  # type: ignore
        # Identity fallback – leave code unchanged
        return text

class CodeMemory:
    """Specialized memory operations for code tasks."""

    def __init__(
        self,
        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )

        self.memory = HybridMemory(config)
        if memory_dir:
            memory_dir = Path(memory_dir)
            if memory_dir.exists():
                self.memory = HybridMemory.load(str(memory_dir), config)

        # Cache for function signatures
        self.signature_cache: Dict[str, str] = {}

    def store_solution(
        self,
        problem_id: str,
        prompt: str,
        solution: str,
        success: bool = True,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        """Store a solution with its problem context."""
        if not solution.strip():
            return

        # Extract and cache function signature
        signature = self.get_signature(prompt)
        if signature:
            self.signature_cache[problem_id] = signature

        # Store with appropriate tags and quality
        quality = 1.0 if success else 0.3


        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )

        self.memory = HybridMemory(config)
        if memory_dir:
            memory_dir = Path(memory_dir)
            if memory_dir.exists():
                self.memory = HybridMemory.load(str(memory_dir), config)

        # Cache for function signatures
        self.signature_cache: Dict[str, str] = {}

    def store_solution(
        self,
        problem_id: str,
        prompt: str,
        solution: str,
        success: bool = True,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        """Store a solution with its problem context."""
        if not solution.strip():
            return

        # Extract and cache function signature
        signature = self.get_signature(prompt)
        if signature:
            self.signature_cache[problem_id] = signature

        # Store with appropriate tags and quality
        quality = 1.0 if success else 0.3
        tags = ["code", "solution", f"problem:{problem_id}"]
        if metadata:
            tags.extend(metadata.get("tags", []))

        # Store in memory systems
        self.memory.add(
            uid=f"{problem_id}:{hash(solution)}",
            text=solution,
            tags=tags,
            quality=quality,
            memory_type="product_key"  # Prefer product key for code
        )

        # Also store problem-solution pair
        context = f"Problem:\n{prompt}\n\nSolution:\n{solution}"
        self.memory.add(
            uid=f"{problem_id}:context",
            text=context,
            tags=tags + ["context"],
            quality=quality,
            memory_type="hybrid"  # Store in both for different query types
        )

    def get_signature(self, prompt: str) -> Optional[str]:
        """Extract function signature from prompt."""
        try:
            return extract_function_signature(prompt)
        except Exception:
            return None

    def find_similar_solutions(
        self,
        prompt: str,
        k: int = 5,
        problem_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Find similar solutions for a given problem."""
        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,


        # Store in memory systems
        self.memory.add(
            uid=f"{problem_id}:{hash(solution)}",
            text=solution,
            tags=tags,
            quality=quality,
            memory_type="product_key"  # Prefer product key for code
        )

        # Also store problem-solution pair
        context = f"Problem:\n{prompt}\n\nSolution:\n{solution}"
        self.memory.add(
            uid=f"{problem_id}:context",
            text=context,
            tags=tags + ["context"],
            quality=quality,
            memory_type="hybrid"  # Store in both for different query types
        )

    def get_signature(self, prompt: str) -> Optional[str]:
        """Extract function signature from prompt."""
        try:
            return extract_function_signature(prompt)
        except Exception:
            return None

    def find_similar_solutions(
        self,
        prompt: str,
        k: int = 5,
        problem_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Find similar solutions for a given problem."""
        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,
                k=k,
                memory_type="product_key",
                quality_weight=0.4
            )
            if sig_results:
                return sig_results


            memory_type="product_key"  # Prefer product key for code
        )

        # Also store problem-solution pair
        context = f"Problem:\n{prompt}\n\nSolution:\n{solution}"
        self.memory.add(
            uid=f"{problem_id}:context",
            text=context,
            tags=tags + ["context"],
            quality=quality,
            memory_type="hybrid"  # Store in both for different query types
        )

    def get_signature(self, prompt: str) -> Optional[str]:
        """Extract function signature from prompt."""
        try:
            return extract_function_signature(prompt)
        except Exception:
            return None

    def find_similar_solutions(
        self,
        prompt: str,
        k: int = 5,
        problem_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Find similar solutions for a given problem."""
        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,
                k=k,
                memory_type="product_key",
                quality_weight=0.4
            )
            if sig_results:
                return sig_results

        # Fall back to semantic search
        return self.memory.query(
            prompt,
            k=k,
            memory_type="hybrid",
            quality_weight=0.3
        )

    def enhance_prompt(
        self,
        prompt: str,
        problem_id: Optional[str] = None,
        max_examples: int = 2
    ) -> str:
        """Enhance prompt with relevant examples."""
        similar = self.find_similar_solutions(prompt, k=max_examples, problem_id=problem_id)

        if not similar:
            return prompt

        # Add examples as comments
        examples = []
        for item in similar:
            if "text" in item:
                code = normalize_code(item["text"])
                examples.append(f"# Similar example (score: {item['score']:.2f}):\n{code}")


        # Try signature-based lookup first
        signature = self.get_signature(prompt)
        if signature:
            sig_results = self.memory.query(
                signature,
                k=k,
                memory_type="product_key",
                quality_weight=0.4
            )
            if sig_results:
                return sig_results

        # Fall back to semantic search
        return self.memory.query(
            prompt,
            k=k,
            memory_type="hybrid",
            quality_weight=0.3
        )

    def enhance_prompt(
        self,
        prompt: str,
        problem_id: Optional[str] = None,
        max_examples: int = 2
    ) -> str:
        """Enhance prompt with relevant examples."""
        similar = self.find_similar_solutions(prompt, k=max_examples, problem_id=problem_id)

        if not similar:
            return prompt

        # Add examples as comments
        examples = []
        for item in similar:
            if "text" in item:
                code = normalize_code(item["text"])
                examples.append(f"# Similar example (score: {item['score']:.2f}):\n{code}")

        if examples:
            enhanced = f"{prompt}\n\n# Reference examples:\n{'#' * 40}\n"
            enhanced += "\n\n".join(examples)
            enhanced += f"\n{'#' * 40}\n\n"
            return enhanced

        return prompt

    def save(self, out_dir: str) -> None:
        """Save memory state."""
        out_dir = Path(out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)

        # Save memory systems
        self.memory.save(str(out_dir))

        # Save signature cache
        with open(out_dir / "signatures.json", "w") as f:
            json.dump(self.signature_cache, f, indent=2)

    @classmethod
    def load(
        cls,
        in_dir: str,
        config: Optional[MemoryConfig] = None
    ) -> "CodeMemory":
        """Load memory state."""


save

        similar = self.find_similar_solutions(prompt, k=max_examples, problem_id=problem_id)

        if not similar:
            return prompt

        # Add examples as comments
        examples = []
        for item in similar:
            if "text" in item:
                code = normalize_code(item["text"])
                examples.append(f"# Similar example (score: {item['score']:.2f}):\n{code}")

        if examples:
            enhanced = f"{prompt}\n\n# Reference examples:\n{'#' * 40}\n"
            enhanced += "\n\n".join(examples)
            enhanced += f"\n{'#' * 40}\n\n"
            return enhanced

        return prompt

    def save(self, out_dir: str) -> None:
        """Save memory state."""
        out_dir = Path(out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)

        # Save memory systems
        self.memory.save(str(out_dir))

        # Save signature cache
        with open(out_dir / "signatures.json", "w") as f:
            json.dump(self.signature_cache, f, indent=2)

    @classmethod
    def load(
        cls,
        in_dir: str,
        config: Optional[MemoryConfig] = None
    ) -> "CodeMemory":
        """Load memory state."""
        mem = cls(memory_dir=in_dir, config=config)

        # Load signature cache
        try:
            with open(Path(in_dir) / "signatures.json") as f:
                mem.signature_cache = json.load(f)
        except Exception:
            mem.signature_cache = {}

        return mem


load

            enhanced = f"{prompt}\n\n# Reference examples:\n{'#' * 40}\n"
            enhanced += "\n\n".join(examples)
            enhanced += f"\n{'#' * 40}\n\n"
            return enhanced

        return prompt

    def save(self, out_dir: str) -> None:
        """Save memory state."""
        out_dir = Path(out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)

        # Save memory systems
        self.memory.save(str(out_dir))

        # Save signature cache
        with open(out_dir / "signatures.json", "w") as f:
            json.dump(self.signature_cache, f, indent=2)

    @classmethod
    def load(
        cls,
        in_dir: str,
        config: Optional[MemoryConfig] = None
    ) -> "CodeMemory":
        """Load memory state."""
        mem = cls(memory_dir=in_dir, config=config)

        # Load signature cache
        try:
            with open(Path(in_dir) / "signatures.json") as f:
                mem.signature_cache = json.load(f)
        except Exception:
            mem.signature_cache = {}

        return mem


"""Code-specific memory operations for HumanEval and similar tasks."""

from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from pathlib import Path
import json

from .hybrid import HybridMemory, MemoryConfig

# Optional utilities; provide robust fallbacks if unavailable
try:
    from ..utils.code_utils import extract_function_signature, normalize_code  # type: ignore
except Exception:  # Fallbacks to avoid hard dependency
    try:
        # Reuse existing helper to extract target function name
        from ..utils.bench_utils import extract_target_function_name  # type: ignore
    except Exception:
        extract_target_function_name = None  # type: ignore

    def extract_function_signature(prompt: str):  # type: ignore
        try:
            if extract_target_function_name is not None:
                name = extract_target_function_name(prompt)  # type: ignore
                if name:
                    return f"def {name}("
        except Exception:
            pass
        return None

    def normalize_code(text: str):  # type: ignore
        # Identity fallback – leave code unchanged
        return text

class CodeMemory:
    """Specialized memory operations for code tasks."""

    def __init__(
        self,
        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )


# Optional utilities; provide robust fallbacks if unavailable
try:
    from ..utils.code_utils import extract_function_signature, normalize_code  # type: ignore
except Exception:  # Fallbacks to avoid hard dependency
    try:
        # Reuse existing helper to extract target function name
        from ..utils.bench_utils import extract_target_function_name  # type: ignore
    except Exception:
        extract_target_function_name = None  # type: ignore

    def extract_function_signature(prompt: str):  # type: ignore
        try:
            if extract_target_function_name is not None:
                name = extract_target_function_name(prompt)  # type: ignore
                if name:
                    return f"def {name}("
        except Exception:
            pass
        return None

    def normalize_code(text: str):  # type: ignore
        # Identity fallback – leave code unchanged
        return text

class CodeMemory:
    """Specialized memory operations for code tasks."""

    def __init__(
        self,
        memory_dir: Optional[str] = None,
        config: Optional[MemoryConfig] = None
    ):
        if config is None:
            config = MemoryConfig(
                query_dim=512,
                key_dim=64,
                value_dim=64,
                num_heads=4
            )

        self.memory = HybridMemory(config)
        if memory_dir:
            memory_dir = Path(memory_dir)


from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, Any, List

class MemoryRegistry:
    """
    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        try:
            out["repo_root"] = str(self.root)
            tests = list((self.root).rglob("test_*.py"))
            out["num_tests"] = len(tests)
        except Exception:
            pass
        return out


from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, Any, List

class MemoryRegistry:
    """
    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:


from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, Any, List

class MemoryRegistry:
    """
    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}


from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, Any, List

class MemoryRegistry:
    """
    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        try:
            out["repo_root"] = str(self.root)
            tests = list((self.root).rglob("test_*.py"))
            out["num_tests"] = len(tests)
        except Exception:


class MemoryRegistry:
    """
    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        try:
            out["repo_root"] = str(self.root)
            tests = list((self.root).rglob("test_*.py"))
            out["num_tests"] = len(tests)
        except Exception:
            pass
        return out


    Provides unified access to episodic/procedural/conceptual/contextual memory.
    """

    def __init__(self, root: Path) -> None:
        self.root = root

    # Episodic: task traces
    def list_traces(self) -> List[Path]:
        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        try:
            out["repo_root"] = str(self.root)
            tests = list((self.root).rglob("test_*.py"))
            out["num_tests"] = len(tests)
        except Exception:
            pass
        return out


        base = self.root / "data" / "traces"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Procedural: curated skills datasets
    def list_skills(self) -> List[Path]:
        base = self.root / "data" / "skills"
        return sorted(base.glob("*.jsonl")) if base.exists() else []

    # Conceptual: code index/graph
    def code_index_path(self) -> Path:
        return self.root / "data" / "sandbox" / "tmp" / "index.json"

    def load_code_index(self) -> Dict[str, Any]:
        p = self.code_index_path()
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except Exception:
            return {}

    # Contextual: repo/test inventory
    def context_summary(self) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        try:
            out["repo_root"] = str(self.root)
            tests = list((self.root).rglob("test_*.py"))
            out["num_tests"] = len(tests)
        except Exception:
            pass
        return out


"""Hybrid memory system combining episodic and product key memories."""

from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from dataclasses import dataclass

from .episodic import EpisodicMemory, MemoryItem
from .product_key import ProductKeyMemory

@dataclass
class MemoryConfig:
    """Configuration for hybrid memory system."""
    embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
    query_dim: int = 512
    key_dim: int = 64
    value_dim: int = 64
    num_heads: int = 4
    device: Optional[str] = None

class MemoryRouter:
    """Routes queries to appropriate memory systems."""

    def __init__(self):
        self.code_indicators = {
            "def ", "class ", "import ", "return", "print(",
            "for ", "while ", "if ", "else:", "try:",
            ".py", "```python"
        }

    def route(self, text: str) -> str:
        """Route text to appropriate memory system."""
        if self._is_code_related(text):
            return "product_key"
        if self._is_natural_language(text):
            return "episodic"
        return "hybrid"

    def _is_code_related(self, text: str) -> bool:
        """Check if text appears to be code-related."""
        text = text.lower()
        return any(indicator in text for indicator in self.code_indicators)

    def _is_natural_language(self, text: str) -> bool:
        """Check if text appears to be natural language."""
        # Simple heuristic: longer sentences, fewer special characters
        words = text.split()
        if not words:
            return False
        avg_word_len = sum(len(w) for w in words) / len(words)
        special_char_ratio = sum(1 for c in text if not c.isalnum()) / len(text)
        return avg_word_len > 3 and special_char_ratio < 0.2

    def merge_results(
        self,
        episodic_results: List[Dict[str, Any]],
        product_key_results: List[Dict[str, Any]],
        weights: Optional[Dict[str, float]] = None
    ) -> List[Dict[str, Any]]:
        """Merge results from different memory systems."""
        if weights is None:
            weights = {"episodic": 0.5, "product_key": 0.5}

        # Normalize scores within each system
        def normalize_scores(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
            if not results:
                return results
            max_score = max(r["score"] for r in results)
            min_score = min(r["score"] for r in results)
            score_range = max_score - min_score
            if score_range == 0:
                score_range = 1
            for r in results:
                r["score"] = (r["score"] - min_score) / score_range
            return results

        episodic_results = normalize_scores(episodic_results)
        product_key_results = normalize_scores(product_key_results)

        # Apply weights
        for r in episodic_results:
            r["score"] *= weights["episodic"]
        for r in product_key_results:
            r["score"] *= weights["product_key"]

        # Merge and sort
        all_results = episodic_results + product_key_results
        return sorted(all_results, key=lambda x: x["score"], reverse=True)

class HybridMemory:
    """Combines episodic and product key memories with intelligent routing."""

    def __init__(self, config: Optional[MemoryConfig] = None):
        if config is None:
            config = MemoryConfig()

        self.config = config
        self.episodic = EpisodicMemory(
            model_name=config.embedding_model,
            device=config.device
        )
        self.product_key = ProductKeyMemory(
            query_dim=config.query_dim,
            key_dim=config.key_dim,
            value_dim=config.value_dim,
            num_heads=config.num_heads
        )
        self.router = MemoryRouter()

    def add(
        self,
        uid: str,
        text: str,
        memory_type: str = "auto",
        tags: Optional[List[str]] = None,
        quality: float = 0.5,
        **kwargs
    ) -> None:
        """Add item to appropriate memory system."""
        if memory_type == "auto":
            memory_type = self.router.route(text)

        if memory_type in ("episodic", "hybrid"):
            self.episodic.add(
                uid=uid,
                text=text,
                tags=tags,
                quality=quality,
                **kwargs
            )

        if memory_type in ("product_key", "hybrid"):
            # Convert to tensor format for product key memory
            self.product_key.store(
                text,
                quality=quality,
                metadata={"uid": uid, "tags": tags}
            )

    def query(
        self,
        text: str,
        memory_type: str = "auto",
        k: int = 5,
        quality_weight: float = 0.3,
        **kwargs
    ) -> List[Dict[str, Any]]:
        """Query from appropriate memory system."""
        if memory_type == "auto":
            memory_type = self.router.route(text)

        if memory_type == "episodic":
            return self.episodic.query(
                text,
                k=k,
                quality_weight=quality_weight,
                **kwargs
            )

        if memory_type == "product_key":
            return self.product_key.query(
                text,
                k=k,
                **kwargs
            )

        # Hybrid: query both and merge results
        episodic_results = self.episodic.query(
            text,
            k=k,
            quality_weight=quality_weight,
            **kwargs
        )
        product_key_results = self.product_key.query(
            text,
            k=k,
            **kwargs
        )

        return self.router.merge_results(
            episodic_results,
            product_key_results
        )

    def save(self, out_dir: str) -> None:
        """Save both memory systems."""
        self.episodic.save(f"{out_dir}/episodic")
        self.product_key.save(f"{out_dir}/product_key")

    @classmethod
    def load(cls, in_dir: str, config: Optional[MemoryConfig] = None) -> "HybridMemory":
        """Load both memory systems."""
        mem = cls(config)
        mem.episodic = EpisodicMemory.load(f"{in_dir}/episodic")
        mem.product_key = ProductKeyMemory.load(f"{in_dir}/product_key")
        return mem


MemoryConfig

"""Hybrid memory system combining episodic and product key memories."""

from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from dataclasses import dataclass

from .episodic import EpisodicMemory, MemoryItem
from .product_key import ProductKeyMemory

@dataclass
class MemoryConfig:
    """Configuration for hybrid memory system."""
    embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
    query_dim: int = 512
    key_dim: int = 64
    value_dim: int = 64
    num_heads: int = 4
    device: Optional[str] = None

class MemoryRouter:
    """Routes queries to appropriate memory systems."""

    def __init__(self):
        self.code_indicators = {
            "def ", "class ", "import ", "return", "print(",
            "for ", "while ", "if ", "else:", "try:",
            ".py", "```python"
        }

    def route(self, text: str) -> str:
        """Route text to appropriate memory system."""
        if self._is_code_related(text):
            return "product_key"
        if self._is_natural_language(text):
            return "episodic"
        return "hybrid"

    def _is_code_related(self, text: str) -> bool:


MemoryRouter

"""Hybrid memory system combining episodic and product key memories."""

from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from dataclasses import dataclass

from .episodic import EpisodicMemory, MemoryItem
from .product_key import ProductKeyMemory

@dataclass
class MemoryConfig:
    """Configuration for hybrid memory system."""
    embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
    query_dim: int = 512
    key_dim: int = 64
    value_dim: int = 64
    num_heads: int = 4
    device: Optional[str] = None

class MemoryRouter:
    """Routes queries to appropriate memory systems."""

    def __init__(self):
        self.code_indicators = {
            "def ", "class ", "import ", "return", "print(",
            "for ", "while ", "if ", "else:", "try:",
            ".py", "```python"
        }

    def route(self, text: str) -> str:
        """Route text to appropriate memory system."""
        if self._is_code_related(text):
            return "product_key"
        if self._is_natural_language(text):
            return "episodic"
        return "hybrid"

    def _is_code_related(self, text: str) -> bool:
        """Check if text appears to be code-related."""
        text = text.lower()
        return any(indicator in text for indicator in self.code_indicators)

    def _is_natural_language(self, text: str) -> bool:
        """Check if text appears to be natural language."""
        # Simple heuristic: longer sentences, fewer special characters
        words = text.split()
        if not words:
            return False
        avg_word_len = sum(len(w) for w in words) / len(words)
        special_char_ratio = sum(1 for c in text if not c.isalnum()) / len(text)
        return avg_word_len > 3 and special_char_ratio < 0.2

    def merge_results(
        self,
        episodic_results: List[Dict[str, Any]],
        product_key_results: List[Dict[str, Any]],
        weights: Optional[Dict[str, float]] = None
    ) -> List[Dict[str, Any]]:
        """Merge results from different memory systems."""
        if weights is None:
            weights = {"episodic": 0.5, "product_key": 0.5}

        # Normalize scores within each system
        def normalize_scores(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
            if not results:
                return results
            max_score = max(r["score"] for r in results)
            min_score = min(r["score"] for r in results)
            score_range = max_score - min_score
            if score_range == 0:
                score_range = 1
            for r in results:
                r["score"] = (r["score"] - min_score) / score_range
            return results

        episodic_results = normalize_scores(episodic_results)
        product_key_results = normalize_scores(product_key_results)

        # Apply weights
        for r in episodic_results:
            r["score"] *= weights["episodic"]
        for r in product_key_results:
            r["score"] *= weights["product_key"]

        # Merge and sort
        all_results = episodic_results + product_key_results
        return sorted(all_results, key=lambda x: x["score"], reverse=True)

class HybridMemory:
    """Combines episodic and product key memories with intelligent routing."""

    def __init__(self, config: Optional[MemoryConfig] = None):
        if config is None:
            config = MemoryConfig()

        self.config = config
        self.episodic = EpisodicMemory(
            model_name=config.embedding_model,
            device=config.device
        )
        self.product_key = ProductKeyMemory(
            query_dim=config.query_dim,
            key_dim=config.key_dim,
            value_dim=config.value_dim,
            num_heads=config.num_heads
        )
        self.router = MemoryRouter()


HybridMemory

            score_range = max_score - min_score
            if score_range == 0:
                score_range = 1
            for r in results:
                r["score"] = (r["score"] - min_score) / score_range
            return results

        episodic_results = normalize_scores(episodic_results)
        product_key_results = normalize_scores(product_key_results)

        # Apply weights
        for r in episodic_results:
            r["score"] *= weights["episodic"]
        for r in product_key_results:
            r["score"] *= weights["product_key"]

        # Merge and sort
        all_results = episodic_results + product_key_results
        return sorted(all_results, key=lambda x: x["score"], reverse=True)

class HybridMemory:
    """Combines episodic and product key memories with intelligent routing."""

    def __init__(self, config: Optional[MemoryConfig] = None):
        if config is None:
            config = MemoryConfig()

        self.config = config
        self.episodic = EpisodicMemory(
            model_name=config.embedding_model,
            device=config.device
        )
        self.product_key = ProductKeyMemory(
            query_dim=config.query_dim,
            key_dim=config.key_dim,
            value_dim=config.value_dim,
            num_heads=config.num_heads
        )
        self.router = MemoryRouter()

    def add(
        self,
        uid: str,
        text: str,
        memory_type: str = "auto",
        tags: Optional[List[str]] = None,
        quality: float = 0.5,
        **kwargs
    ) -> None:
        """Add item to appropriate memory system."""
        if memory_type == "auto":
            memory_type = self.router.route(text)

        if memory_type in ("episodic", "hybrid"):
            self.episodic.add(
                uid=uid,
                text=text,
                tags=tags,
                quality=quality,
                **kwargs
            )

        if memory_type in ("product_key", "hybrid"):
            # Convert to tensor format for product key memory
            self.product_key.store(
                text,
                quality=quality,
                metadata={"uid": uid, "tags": tags}
            )

    def query(
        self,
        text: str,
        memory_type: str = "auto",
        k: int = 5,
        quality_weight: float = 0.3,
        **kwargs
    ) -> List[Dict[str, Any]]:
        """Query from appropriate memory system."""
        if memory_type == "auto":
            memory_type = self.router.route(text)

        if memory_type == "episodic":
            return self.episodic.query(
                text,
                k=k,
                quality_weight=quality_weight,
                **kwargs
            )

        if memory_type == "product_key":
            return self.product_key.query(
                text,
                k=k,
                **kwargs
            )

        # Hybrid: query both and merge results
        episodic_results = self.episodic.query(
            text,
            k=k,
            quality_weight=quality_weight,
            **kwargs
        )
        product_key_results = self.product_key.query(
            text,
            k=k,
            **kwargs
        )

        return self.router.merge_results(
            episodic_results,
            product_key_results
        )

    def save(self, out_dir: str) -> None:
        """Save both memory systems."""
        self.episodic.save(f"{out_dir}/episodic")
        self.product_key.save(f"{out_dir}/product_key")

    @classmethod
    def load(cls, in_dir: str, config: Optional[MemoryConfig] = None) -> "HybridMemory":
        """Load both memory systems."""
        mem = cls(config)
        mem.episodic = EpisodicMemory.load(f"{in_dir}/episodic")
        mem.product_key = ProductKeyMemory.load(f"{in_dir}/product_key")
        return mem


            for r in results:
                r["score"] = (r["score"] - min_score) / score_range
            return results

        episodic_results = normalize_scores(episodic_results)
        product_key_results = normalize_scores(product_key_results)

        # Apply weights
        for r in episodic_results:
            r["score"] *= weights["episodic"]
        for r in product_key_results:
            r["score"] *= weights["product_key"]

        # Merge and sort
        all_results = episodic_results + product_key_results
        return sorted(all_results, key=lambda x: x["score"], reverse=True)

class HybridMemory:
    """Combines episodic and product key memories with intelligent routing."""

    def __init__(self, config: Optional[MemoryConfig] = None):
        if config is None:
            config = MemoryConfig()

        self.config = config
        self.episodic = EpisodicMemory(
            model_name=config.embedding_model,
            device=config.device
        )
        self.product_key = ProductKeyMemory(
            query_dim=config.query_dim,
            key_dim=config.key_dim,
            value_dim=config.value_dim,
            num_heads=config.num_heads
        )
        self.router = MemoryRouter()

    def add(
        self,
        uid: str,
        text: str,
        memory_type: str = "auto",
        tags: Optional[List[str]] = None,
        quality: float = 0.5,
        **kwargs
    ) -> None:
        """Add item to appropriate memory system."""
        if memory_type == "auto":
            memory_type = self.router.route(text)

        if memory_type in ("episodic", "hybrid"):
            self.episodic.add(
                uid=uid,
                text=text,
                tags=tags,
                quality=quality,