#!/usr/bin/env python3 """Disable automatic chat-to-memory and chat-to-skill reinforcement for leoclean.""" from __future__ import annotations import argparse import json import os import shutil import tempfile from pathlib import Path from typing import Any import yaml POLICY_VERSION = "teleo-leoclean-learning-policy-v1" BACKUP_SUFFIX = ".before-teleo-learning-policy" MEMORY_NUDGE_INTERVAL = 0 SKILL_CREATION_NUDGE_INTERVAL = 0 def _load_config(path: Path) -> dict[str, Any]: if path.is_symlink(): raise ValueError("refusing symlinked profile config") raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict): raise ValueError("profile config must be a YAML mapping") return raw def policy_projection(config: dict[str, Any]) -> dict[str, int | None]: memory = config.get("memory") if isinstance(config.get("memory"), dict) else {} skills = config.get("skills") if isinstance(config.get("skills"), dict) else {} return { "memory_nudge_interval": memory.get("nudge_interval"), "skill_creation_nudge_interval": skills.get("creation_nudge_interval"), } def enforce_policy(path: Path, *, check: bool = False) -> tuple[dict[str, Any], int]: config = _load_config(path) before = policy_projection(config) expected = { "memory_nudge_interval": MEMORY_NUDGE_INTERVAL, "skill_creation_nudge_interval": SKILL_CREATION_NUDGE_INTERVAL, } changed = before != expected if check: return { "policy_version": POLICY_VERSION, "status": "change_required" if changed else "compliant", "target": str(path), "policy": before, }, 1 if changed else 0 backup_path = path.with_name(path.name + BACKUP_SUFFIX) if changed: memory = config.setdefault("memory", {}) skills = config.setdefault("skills", {}) if not isinstance(memory, dict) or not isinstance(skills, dict): raise ValueError("memory and skills config sections must be YAML mappings") memory["nudge_interval"] = MEMORY_NUDGE_INTERVAL skills["creation_nudge_interval"] = SKILL_CREATION_NUDGE_INTERVAL if not backup_path.exists(): shutil.copy2(path, backup_path) mode = path.stat().st_mode & 0o777 rendered = yaml.safe_dump(config, sort_keys=False, allow_unicode=False) with tempfile.NamedTemporaryFile( "w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False ) as handle: handle.write(rendered) temp_path = Path(handle.name) try: temp_path.chmod(mode) os.replace(temp_path, path) finally: temp_path.unlink(missing_ok=True) after = policy_projection(_load_config(path)) if after != expected: raise ValueError("profile config did not retain the required learning policy") return { "policy_version": POLICY_VERSION, "status": "updated" if changed else "already_compliant", "target": str(path), "backup": str(backup_path) if changed else None, "policy": after, }, 0 def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("target", type=Path) parser.add_argument("--check", action="store_true") args = parser.parse_args() try: result, code = enforce_policy(args.target, check=args.check) except (OSError, ValueError, yaml.YAMLError) as exc: result = { "policy_version": POLICY_VERSION, "status": "error", "target": str(args.target), "error": type(exc).__name__, } code = 2 print(json.dumps(result, sort_keys=True)) return code if __name__ == "__main__": raise SystemExit(main())