"""LLM control plane (STAGE K): typed, schema-validated commands. The LLM scientist may SPAWN, START, PAUSE, STOP, SAVE, LOAD, CONFIGURE, REQUEST_EXPERIMENTS and PROPOSE hypotheses/curricula — through this strictly validated command surface only. No shell, no code mutation, no direct weight or memory edits, no fabricated results. Every execution is logged with provenance; every rejection states the exact reason. """ import hashlib import json import re import time from dataclasses import dataclass, field from typing import Any, Dict, List, Optional COMMAND_SCHEMA_VERSION = "control_v1" # command -> {required params: {name: type}, optional params, constraints} COMMAND_SPECS: Dict[str, Dict[str, Any]] = { "SPAWN_POPULATION": { "required": {"size": int}, "optional": {"world_seed": int, "config_name": str}, "constraints": {"size": (1, 64)}, }, "START_RUN": {"required": {}, "optional": {"ticks": int}, "constraints": {"ticks": (1, 10000)}}, "PAUSE_RUN": {"required": {}, "optional": {}, "constraints": {}}, "STOP_RUN": {"required": {}, "optional": {}, "constraints": {}}, "SAVE_CHECKPOINT": {"required": {"name": str}, "optional": {}, "constraints": {"name": (1, 200)}}, "LOAD_CHECKPOINT": {"required": {"name": str}, "optional": {}, "constraints": {"name": (1, 200)}}, "SET_WORLD_CONFIG": { "required": {"n_resources": int}, "optional": {"n_hazards": int, "regrow_interval": int}, "constraints": {"n_resources": (4, 512), "n_hazards": (0, 64), "regrow_interval": (1, 100)}, }, "SET_EVOLUTION_CONFIG": { "required": {"offspring_per_generation": int}, "optional": {"reproduction_mode": str}, "constraints": {"offspring_per_generation": (0, 32)}, "enum": {"reproduction_mode": ["sexual", "asexual"]}, }, "REQUEST_EXPERIMENT": { "required": {"experiment_type": str, "seed": int}, "optional": {"ticks": int, "population_size": int}, "constraints": {"seed": (0, 2 ** 31), "ticks": (1, 2000), "population_size": (1, 32)}, "enum": {"experiment_type": ["baseline", "ablation_no_teaching", "ablation_no_growth", "comparison"]}, }, "REQUEST_COMPARISON": { "required": {"experiment_a": str, "experiment_b": str}, "optional": {}, "constraints": {}, }, "REQUEST_REPLAY": {"required": {"checkpoint_name": str}, "optional": {"ticks": int}, "constraints": {"ticks": (1, 2000)}}, "PROPOSE_HYPOTHESIS": { "required": {"text": str, "based_on_experiments": list}, "optional": {}, "constraints": {"text": (1, 2000)}, }, "PROPOSE_TASK": { "required": {"description": str, "success_criterion": str}, "optional": {}, "constraints": {"description": (1, 500), "success_criterion": (1, 500)}, }, "PROPOSE_CURRICULUM": { "required": {"stages": list}, "optional": {}, "constraints": {"stages": (1, 8)}, }, } # Capability allowlist: role -> commands the role may invoke. Typed dispatch is # the security boundary (text params are data, never executed). Identifier # params (checkpoint names, experiment ids, ...) must additionally match # _IDENTIFIER_RE; free-text fields (hypothesis text, descriptions, curriculum # stages) are never scanned and never executed. CAPABILITY_ROLES: Dict[str, frozenset] = { "llm-scientist": frozenset(COMMAND_SPECS.keys()), "viewer": frozenset({"REQUEST_COMPARISON", "REQUEST_REPLAY", "PROPOSE_HYPOTHESIS"}), } # Identifier-shaped params: strict allowlist, no shell metachars possible. _IDENTIFIER_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,199}") IDENTIFIER_PARAMS = {"name", "checkpoint_name", "experiment_a", "experiment_b", "config_name", "reproduction_mode", "experiment_type"} # Deprecated: whole-blob substring blacklists were fragile (false positives on # legitimate scientific text, false negatives via obfuscation). Kept as an # empty tuple for backward-compatible imports; enforcement is capability-based. FORBIDDEN_SUBSTRINGS: tuple = () @dataclass class CommandEnvelope: command: str params: Dict[str, Any] = field(default_factory=dict) requested_by: str = "llm-scientist" schema_version: str = COMMAND_SCHEMA_VERSION timestamp: float = 0.0 def to_dict(self) -> Dict[str, Any]: return {"command": self.command, "params": self.params, "requested_by": self.requested_by, "schema_version": self.schema_version, "timestamp": self.timestamp or time.time()} def validate_envelope(envelope: Any, role: str = "llm-scientist") -> tuple: """Returns (ok, error). Structural + schema + capability validation, no execution.""" if not isinstance(envelope, CommandEnvelope): return False, "payload is not a CommandEnvelope" if envelope.schema_version != COMMAND_SCHEMA_VERSION: return False, f"unsupported schema version {envelope.schema_version!r}" spec = COMMAND_SPECS.get(envelope.command) if spec is None: return False, f"unknown command {envelope.command!r}" allowed_cmds = CAPABILITY_ROLES.get(role, frozenset()) if envelope.command not in allowed_cmds: return False, f"command {envelope.command!r} not permitted for role {role!r}" params = envelope.params if not isinstance(params, dict): return False, "params must be a dict" for name, typ in spec["required"].items(): if name not in params: return False, f"missing required param {name!r}" if typ is int and isinstance(params[name], bool): return False, f"param {name!r} must be int" if typ is int and not isinstance(params[name], int): return False, f"param {name!r} must be int" if typ is str and not isinstance(params[name], str): return False, f"param {name!r} must be str" if typ is list and not isinstance(params[name], list): return False, f"param {name!r} must be list" allowed = set(spec["required"]) | set(spec["optional"]) extra = set(params) - allowed if extra: return False, f"unknown params {sorted(extra)}" for name, (lo, hi) in spec["constraints"].items(): if name in params: v = params[name] if isinstance(v, str): if not (lo <= len(v) <= hi): return False, f"param {name!r} length outside [{lo},{hi}]" elif isinstance(v, list): if not (lo <= len(v) <= hi): return False, f"param {name!r} list length outside [{lo},{hi}]" elif not (lo <= v <= hi): return False, f"param {name!r}={v} outside [{lo},{hi}]" for name, allowed_vals in spec.get("enum", {}).items(): if name in params and params[name] not in allowed_vals: return False, f"param {name!r} must be one of {allowed_vals}" # Capability-based identifier guard: identifier params must match the # strict allowlist (no shell metachars can pass). Free-text params # (text/description/success_criterion/stages) are data, never executed, # and are intentionally NOT scanned. for name in IDENTIFIER_PARAMS: if name in params and isinstance(params[name], str): if _IDENTIFIER_RE.fullmatch(params[name]) is None: return False, f"param {name!r} is not a valid identifier" return True, "" class ResearchRuntime: """Headless research facade the control plane operates on. Owns the population, checkpoints and the experiment ledger. NO source mutation.""" def __init__(self, experiment_seed: int = 42): self.experiment_seed = int(experiment_seed) self.population = None self.running = False self.checkpoints: Dict[str, Dict[str, Any]] = {} self.experiment_ledger: Dict[str, Dict[str, Any]] = {} self.hypotheses: List[Dict[str, Any]] = [] self.curricula: List[Dict[str, Any]] = [] self.tasks: List[Dict[str, Any]] = [] self.execution_log: List[Dict[str, Any]] = [] self._ticks_target = 0 self._ticks_done = 0 # ---- operations invoked by the control plane ---- def op_spawn_population(self, size: int, world_seed: int = 47, **_): from src.common.determinism import SeedBundle from src.population.population import Population from src.connectome.types import GraphMode if self.population is not None: return {"status": "FAILED", "reason": "population already exists; STOP+RESET first"} seeds = SeedBundle(experiment_seed=self.experiment_seed, generation_seed=self.experiment_seed + 1, organism_seed=self.experiment_seed + 2, development_seed=self.experiment_seed + 3, mutation_seed=self.experiment_seed + 4, world_seed=world_seed, teacher_seed=self.experiment_seed + 6) self.population = Population(size, seeds, GraphMode.SYNTHETIC_TEST, 32, experiment_seed=self.experiment_seed, autonomy_mode=True, genome_version="2.0") return {"status": "EXECUTED", "population_size": size, "population_hash": self.population.population_hash()} def op_start_run(self, ticks: int = 10, **_): if self.population is None: return {"status": "FAILED", "reason": "no population"} self.running = True self._ticks_target = int(ticks) self._ticks_done = 0 self.population.step(int(ticks)) self._ticks_done = int(ticks) self.running = False return {"status": "EXECUTED", "ticks_run": self._ticks_done, "population_hash": self.population.population_hash()} def op_pause_run(self, **_): self.running = False return {"status": "EXECUTED", "paused": True} def op_stop_run(self, **_): self.running = False return {"status": "EXECUTED", "stopped": True, "tick": self.population.tick if self.population else 0} def op_save_checkpoint(self, name: str, **_): if self.population is None: return {"status": "FAILED", "reason": "no population"} self.checkpoints[name] = self.population.snapshot() return {"status": "EXECUTED", "checkpoint": name, "population_hash": self.population.population_hash()} def op_load_checkpoint(self, name: str, **_): if name not in self.checkpoints: return {"status": "FAILED", "reason": f"unknown checkpoint {name!r}"} from src.common.determinism import SeedBundle from src.population.population import Population seeds = SeedBundle(experiment_seed=self.experiment_seed, generation_seed=self.experiment_seed + 1, organism_seed=self.experiment_seed + 2, development_seed=self.experiment_seed + 3, mutation_seed=self.experiment_seed + 4, world_seed=self.experiment_seed + 5, teacher_seed=self.experiment_seed + 6) self.population = Population.restore(self.checkpoints[name], seeds) return {"status": "EXECUTED", "checkpoint": name, "population_hash": self.population.population_hash()} def op_request_experiment(self, experiment_type: str, seed: int, ticks: int = 20, population_size: int = 4, **_): from src.common.determinism import SeedBundle from src.population.population import Population from src.connectome.types import GraphMode exp_id = f"exp-{experiment_type}-{seed}" seeds = SeedBundle(experiment_seed=seed, generation_seed=seed + 1, organism_seed=seed + 2, development_seed=seed + 3, mutation_seed=seed + 4, world_seed=seed + 5, teacher_seed=seed + 6) pop = Population(population_size, seeds, GraphMode.SYNTHETIC_TEST, 32, experiment_seed=seed, autonomy_mode=(experiment_type != "baseline"), genome_version="2.0") pop.step(int(ticks)) pop.reproduce(2) result = { "experiment_id": exp_id, "type": experiment_type, "seed": seed, "ticks": ticks, "population_size": population_size, "final_population_hash": pop.population_hash(), "teaching_sessions": len(pop.teaching_sessions), "living": len(pop.living()), "total_organisms": len(pop.organisms), "generations": sorted({o.generation for o in pop.organisms}), } self.experiment_ledger[exp_id] = result return {"status": "EXECUTED", "result": result} def op_request_comparison(self, experiment_a: str, experiment_b: str, **_): ra, rb = self.experiment_ledger.get(experiment_a), self.experiment_ledger.get(experiment_b) if ra is None or rb is None: return {"status": "FAILED", "reason": "unknown experiment id(s)"} comparison = { "a": experiment_a, "b": experiment_b, "hash_equal": ra["final_population_hash"] == rb["final_population_hash"], "teaching_sessions": {"a": ra["teaching_sessions"], "b": rb["teaching_sessions"]}, "living": {"a": ra["living"], "b": rb["living"]}, "generations": {"a": ra["generations"], "b": rb["generations"]}, } return {"status": "EXECUTED", "comparison": comparison} def op_propose_hypothesis(self, text: str, based_on_experiments: list, **_): known = [e for e in based_on_experiments if e in self.experiment_ledger] unknown = [e for e in based_on_experiments if e not in self.experiment_ledger] # Deterministic research identity (V4 §34): content + sequence, never wall-clock. hid = hashlib.sha256( f"{text}|{len(self.hypotheses)}|{self.experiment_seed}".encode()).hexdigest()[:12] rec = {"hypothesis_id": hid, "text": text, "based_on_experiments": known, "unknown_references": unknown, "status": "HYPOTHESIS"} self.hypotheses.append(rec) return {"status": "EXECUTED", "hypothesis": rec} def op_propose_task(self, description: str, success_criterion: str, **_): tid = hashlib.sha256( f"{description}|{success_criterion}|{len(self.tasks)}|{self.experiment_seed}" .encode()).hexdigest()[:12] rec = {"task_id": tid, "description": description, "success_criterion": success_criterion, "status": "PROPOSED"} self.tasks.append(rec) return {"status": "EXECUTED", "task": rec} def op_propose_curriculum(self, stages: list, **_): if not all(isinstance(s, str) for s in stages): return {"status": "REJECTED", "reason": "curriculum stages must be strings"} cid = hashlib.sha256( f"{'|'.join(stages)}|{len(self.curricula)}|{self.experiment_seed}" .encode()).hexdigest()[:12] rec = {"curriculum_id": cid, "stages": stages, "status": "PROPOSED"} self.curricula.append(rec) return {"status": "EXECUTED", "curriculum": rec} class ControlPlane: """Validates and executes LLM command envelopes against a ResearchRuntime.""" def __init__(self, runtime: Optional[ResearchRuntime] = None, role: str = "llm-scientist"): self.runtime = runtime if runtime is not None else ResearchRuntime() self.role = role def execute(self, envelope: Any) -> Dict[str, Any]: ok, err = validate_envelope(envelope, role=self.role) if not ok: result = {"status": "REJECTED", "reason": err, "command": getattr(envelope, "command", str(envelope)[:80])} self.runtime.execution_log.append({**result, "ts": time.time()}) return result op = getattr(self.runtime, f"op_{envelope.command.lower()}", None) if op is None: result = {"status": "REJECTED", "reason": "command has no executor", "command": envelope.command} else: try: result = op(**envelope.params) except Exception as e: # noqa: BLE001 result = {"status": "FAILED", "command": envelope.command, "reason": f"{type(e).__name__}: {e}"} self.runtime.execution_log.append({**result, "command": envelope.command, "ts": time.time()}) return result