| """Immutable experiment specifications and configuration validation. |
| |
| The project intentionally uses the Python standard library for its initial |
| schema layer. TOML inputs are converted into frozen dataclasses, validated, and |
| hashed using a canonical JSON representation. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import asdict, dataclass |
| from hashlib import sha256 |
| import json |
| from pathlib import Path |
| import re |
| import tomllib |
| from typing import Any, Iterable |
|
|
|
|
| class SpecError(ValueError): |
| """Raised when a configuration cannot represent a valid treatment.""" |
|
|
|
|
| def project_root() -> Path: |
| return Path(__file__).resolve().parents[2] |
|
|
|
|
| def _read_toml(path: Path) -> dict[str, Any]: |
| try: |
| with path.open("rb") as handle: |
| return tomllib.load(handle) |
| except (OSError, tomllib.TOMLDecodeError) as exc: |
| raise SpecError(f"Cannot read TOML configuration {path}: {exc}") from exc |
|
|
|
|
| def _canonical_hash(value: Any) -> str: |
| payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) |
| return sha256(payload.encode("utf-8")).hexdigest() |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class HarnessSpec: |
| schema_version: int |
| harness_id: str |
| name: str |
| description: str |
| exact_search: bool |
| lexical: bool |
| syntax: str |
| dense: bool |
| graph_hops: int |
| query_policy: str |
| interface: str |
| packing: str |
| fusion: str |
| control: str |
| adaptive: bool |
|
|
| @classmethod |
| def from_mapping(cls, value: dict[str, Any], source: Path | None = None) -> "HarnessSpec": |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| harness_id=str(value["harness_id"]), |
| name=str(value["name"]), |
| description=str(value.get("description", "")), |
| exact_search=bool(value["exact_search"]), |
| lexical=bool(value["lexical"]), |
| syntax=str(value["syntax"]), |
| dense=bool(value["dense"]), |
| graph_hops=int(value["graph_hops"]), |
| query_policy=str(value["query_policy"]), |
| interface=str(value["interface"]), |
| packing=str(value["packing"]), |
| fusion=str(value["fusion"]), |
| control=str(value.get("control", "none")), |
| adaptive=bool(value.get("adaptive", False)), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| location = f" in {source}" if source else "" |
| raise SpecError(f"Malformed harness specification{location}: {exc}") from exc |
| spec.validate(source) |
| return spec |
|
|
| @classmethod |
| def load(cls, path: Path) -> "HarnessSpec": |
| return cls.from_mapping(_read_toml(path), path) |
|
|
| def validate(self, source: Path | None = None) -> None: |
| errors: list[str] = [] |
| if self.schema_version != 1: |
| errors.append("schema_version must be 1") |
| if not re.fullmatch(r"H\d{3}", self.harness_id): |
| errors.append("harness_id must match H000-style identifiers") |
| if not re.fullmatch(r"[a-z][a-z0-9_]*", self.name): |
| errors.append("name must be a lowercase semantic slug") |
| if self.syntax not in {"raw", "tree_sitter"}: |
| errors.append("syntax must be raw or tree_sitter") |
| if self.graph_hops not in {0, 1, 2}: |
| errors.append("graph_hops must be 0, 1, or 2") |
| if self.graph_hops and self.syntax != "tree_sitter": |
| errors.append("graph expansion requires the tree_sitter structural index") |
| if self.query_policy not in {"one_shot", "iterative"}: |
| errors.append("query_policy must be one_shot or iterative") |
| if self.interface not in {"unified", "specialized"}: |
| errors.append("interface must be unified or specialized") |
| if self.packing not in {"ranked_snippets", "skeletons", "whole_files", "role_summaries"}: |
| errors.append("unsupported context packing strategy") |
| if self.packing == "skeletons" and self.syntax != "tree_sitter": |
| errors.append("skeleton packing requires tree_sitter syntax") |
| if self.fusion not in {"none", "rrf"}: |
| errors.append("fusion must be none or rrf") |
|
|
| active_advanced_sources = int(self.lexical) + int(self.syntax == "tree_sitter") + int(self.dense) |
| expected_fusion = "rrf" if active_advanced_sources >= 2 else "none" |
| if self.control == "none" and self.fusion != expected_fusion: |
| errors.append( |
| f"fusion must be {expected_fusion} for {active_advanced_sources} advanced retrieval sources" |
| ) |
|
|
| allowed_controls = {"none", "no_search", "random_context", "oracle_file", "oracle_function"} |
| if self.control not in allowed_controls: |
| errors.append(f"control must be one of {sorted(allowed_controls)}") |
| if self.control != "none": |
| if self.exact_search or self.lexical or self.dense or self.syntax != "raw" or self.graph_hops: |
| errors.append("control harnesses cannot enable repository retrieval capabilities") |
| if self.fusion != "none" or self.adaptive: |
| errors.append("control harnesses cannot enable fusion or adaptive routing") |
| elif not self.exact_search: |
| errors.append("non-control harnesses must retain the exact/regex baseline") |
|
|
| if self.adaptive: |
| if not (self.lexical and self.dense and self.syntax == "tree_sitter"): |
| errors.append("adaptive routing requires lexical, syntax, and dense retrieval") |
| if self.graph_hops < 1 or self.query_policy != "iterative": |
| errors.append("adaptive routing requires graph expansion and iterative queries") |
|
|
| if errors: |
| location = f" ({source})" if source else "" |
| raise SpecError(f"Invalid harness {self.harness_id}{location}: " + "; ".join(errors)) |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
| @property |
| def treatment_hash(self) -> str: |
| """Hash only causal treatment fields, excluding labels and prose.""" |
|
|
| value = asdict(self) |
| for field in ("harness_id", "name", "description"): |
| value.pop(field) |
| return _canonical_hash(value) |
|
|
| @property |
| def uses_embedding(self) -> bool: |
| return self.dense |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class EditInterfaceSpec: |
| """Immutable action-interface treatment for protocol-normalized studies.""" |
|
|
| schema_version: int |
| interface_id: str |
| name: str |
| description: str |
| edit_tool: str |
| prompt_contract: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "EditInterfaceSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| interface_id=str(value["interface_id"]), |
| name=str(value["name"]), |
| description=str(value.get("description", "")), |
| edit_tool=str(value["edit_tool"]), |
| prompt_contract=str(value["prompt_contract"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed edit-interface specification {path}: {exc}") from exc |
| spec.validate(path) |
| return spec |
|
|
| def validate(self, source: Path | None = None) -> None: |
| errors: list[str] = [] |
| if self.schema_version != 1: |
| errors.append("schema_version must be 1") |
| if not re.fullmatch(r"P\d{3}", self.interface_id): |
| errors.append("interface_id must match P000-style identifiers") |
| if not re.fullmatch(r"[a-z][a-z0-9_]*", self.name): |
| errors.append("name must be a lowercase semantic slug") |
| if self.edit_tool not in {"apply_patch", "replace_text", "write_file"}: |
| errors.append("unsupported edit tool") |
| if not self.prompt_contract.strip(): |
| errors.append("prompt_contract must be non-empty") |
| if errors: |
| location = f" ({source})" if source else "" |
| raise SpecError( |
| f"Invalid edit interface {self.interface_id}{location}: " |
| + "; ".join(errors) |
| ) |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
| @property |
| def treatment_hash(self) -> str: |
| value = asdict(self) |
| for field in ("interface_id", "name", "description"): |
| value.pop(field) |
| return _canonical_hash(value) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class ModelSpec: |
| schema_version: int |
| model_id: str |
| canonical_name: str |
| expected_identity: str |
| expected_inference_key: str |
| expected_variant: str |
| expected_format: str |
| expected_quantization: str |
| provider: str |
| base_url: str |
| api_token_env: str |
| discovery_endpoint: str |
| native_discovery_endpoint: str |
| inference_endpoint: str |
| temperature: float |
| top_p: float |
| max_tokens: int |
| seed: int |
| context_length: int |
| reasoning_mode: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "ModelSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| model_id=str(value["model_id"]), |
| canonical_name=str(value["canonical_name"]), |
| expected_identity=str(value["expected_identity"]), |
| expected_inference_key=str(value["expected_inference_key"]), |
| expected_variant=str(value["expected_variant"]), |
| expected_format=str(value["expected_format"]), |
| expected_quantization=str(value["expected_quantization"]), |
| provider=str(value["provider"]), |
| base_url=str(value["base_url"]).rstrip("/"), |
| api_token_env=str(value.get("api_token_env", "LM_STUDIO_API_TOKEN")), |
| discovery_endpoint=str(value.get("discovery_endpoint", "/v1/models")), |
| native_discovery_endpoint=str(value.get("native_discovery_endpoint", "/api/v1/models")), |
| inference_endpoint=str(value.get("inference_endpoint", "/v1/chat/completions")), |
| temperature=float(value["temperature"]), |
| top_p=float(value["top_p"]), |
| max_tokens=int(value["max_tokens"]), |
| seed=int(value["seed"]), |
| context_length=int(value["context_length"]), |
| reasoning_mode=str(value["reasoning_mode"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed model specification {path}: {exc}") from exc |
| spec.validate(path) |
| return spec |
|
|
| def validate(self, source: Path | None = None) -> None: |
| errors: list[str] = [] |
| if self.schema_version != 1: |
| errors.append("schema_version must be 1") |
| if not re.fullmatch(r"M\d{3}", self.model_id): |
| errors.append("model_id must match M000-style identifiers") |
| if not self.canonical_name.strip() or not self.expected_identity.strip(): |
| errors.append("canonical_name and expected_identity must be pinned") |
| if not self.expected_inference_key or not self.expected_variant: |
| errors.append("the LM Studio model key and selected variant must be pinned") |
| if not self.expected_format or not self.expected_quantization: |
| errors.append("model format and quantization must be pinned") |
| if self.provider != "lm_studio_local": |
| errors.append("provider must be lm_studio_local") |
| if self.base_url not in {"http://127.0.0.1:1234", "http://localhost:1234"}: |
| errors.append("LM Studio must be configured on local port 1234") |
| if not (0.0 <= self.temperature <= 2.0 and 0.0 < self.top_p <= 1.0): |
| errors.append("invalid sampling parameters") |
| if self.max_tokens <= 0: |
| errors.append("max_tokens must be positive") |
| if self.context_length <= 0: |
| errors.append("context_length must be positive") |
| if self.reasoning_mode not in {"none", "off", "on", "low", "medium", "high"}: |
| errors.append("unsupported reasoning_mode") |
| if errors: |
| location = f" ({source})" if source else "" |
| raise SpecError("Invalid model specification" + location + ": " + "; ".join(errors)) |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class EmbeddingSpec: |
| schema_version: int |
| embedding_id: str |
| status: str |
| provider: str |
| base_url: str |
| api_token_env: str |
| discovery_endpoint: str |
| inference_endpoint: str |
| model_key: str |
| expected_display_name: str |
| expected_format: str |
| expected_quantization: str |
| expected_size_bytes: int |
| max_context_length: int |
| loaded_context_length: int |
| vector_dimension: int |
| normalized: bool |
| query_instruction: str |
| document_prefix_template: str |
| chunk_lines: int |
| chunk_overlap_lines: int |
| chunk_char_limit: int |
| batch_size: int |
| notes: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "EmbeddingSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| embedding_id=str(value["embedding_id"]), |
| status=str(value["status"]), |
| provider=str(value["provider"]), |
| base_url=str(value["base_url"]).rstrip("/"), |
| api_token_env=str(value.get("api_token_env", "LM_STUDIO_API_TOKEN")), |
| discovery_endpoint=str(value.get("discovery_endpoint", "/api/v1/models")), |
| inference_endpoint=str(value.get("inference_endpoint", "/v1/embeddings")), |
| model_key=str(value["model_key"]), |
| expected_display_name=str(value["expected_display_name"]), |
| expected_format=str(value["expected_format"]), |
| expected_quantization=str(value["expected_quantization"]), |
| expected_size_bytes=int(value["expected_size_bytes"]), |
| max_context_length=int(value["max_context_length"]), |
| loaded_context_length=int(value["loaded_context_length"]), |
| vector_dimension=int(value["vector_dimension"]), |
| normalized=bool(value["normalized"]), |
| query_instruction=str(value["query_instruction"]), |
| document_prefix_template=str(value["document_prefix_template"]), |
| chunk_lines=int(value["chunk_lines"]), |
| chunk_overlap_lines=int(value["chunk_overlap_lines"]), |
| chunk_char_limit=int(value["chunk_char_limit"]), |
| batch_size=int(value["batch_size"]), |
| notes=str(value.get("notes", "")), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed embedding specification {path}: {exc}") from exc |
| if spec.schema_version != 1 or not re.fullmatch(r"EMB\d{3}", spec.embedding_id): |
| raise SpecError(f"Invalid embedding specification {path}") |
| errors: list[str] = [] |
| if spec.status != "ready": |
| errors.append("the pinned embedding profile must have status ready") |
| if spec.provider != "lm_studio_local": |
| errors.append("embedding provider must be lm_studio_local") |
| if spec.base_url not in {"http://127.0.0.1:1234", "http://localhost:1234"}: |
| errors.append("embedding service must use local LM Studio on port 1234") |
| if not all( |
| ( |
| spec.model_key, |
| spec.expected_display_name, |
| spec.expected_format, |
| spec.expected_quantization, |
| ) |
| ): |
| errors.append("embedding identity, format, and quantization must be pinned") |
| if min( |
| spec.expected_size_bytes, |
| spec.max_context_length, |
| spec.loaded_context_length, |
| spec.vector_dimension, |
| ) <= 0: |
| errors.append("embedding size, context length, and vector dimension must be positive") |
| if spec.loaded_context_length > spec.max_context_length: |
| errors.append("loaded embedding context cannot exceed the model maximum") |
| if not spec.normalized: |
| errors.append("the initial cosine-similarity protocol requires normalized embeddings") |
| if not spec.query_instruction or "{path}" not in spec.document_prefix_template: |
| errors.append("embedding query instruction and path-aware document prefix must be frozen") |
| if min(spec.chunk_lines, spec.chunk_char_limit, spec.batch_size) <= 0: |
| errors.append("embedding chunk size and batch size must be positive") |
| if not 0 <= spec.chunk_overlap_lines < spec.chunk_lines: |
| errors.append("embedding chunk overlap must be non-negative and smaller than the chunk") |
| if errors: |
| raise SpecError(f"Invalid embedding specification {path}: " + "; ".join(errors)) |
| return spec |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class TaskSpec: |
| schema_version: int |
| task_id: str |
| repository_url: str |
| base_commit: str |
| gold_commit: str |
| language: str |
| statement: str |
| gold_patch: str |
| test_patch: str |
| gold_files: tuple[str, ...] |
| gold_symbols: tuple[str, ...] |
| fail_to_pass_tests: tuple[str, ...] |
| pass_to_pass_tests: tuple[str, ...] |
| difficulty: str |
| provenance: str |
| validation_status: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "TaskSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| task_id=str(value["task_id"]), |
| repository_url=str(value["repository_url"]), |
| base_commit=str(value["base_commit"]), |
| gold_commit=str(value["gold_commit"]), |
| language=str(value["language"]), |
| statement=str(value["statement"]), |
| gold_patch=str(value["gold_patch"]), |
| test_patch=str(value.get("test_patch", "")), |
| gold_files=tuple(str(item) for item in value["gold_files"]), |
| gold_symbols=tuple(str(item) for item in value["gold_symbols"]), |
| fail_to_pass_tests=tuple(str(item) for item in value["fail_to_pass_tests"]), |
| pass_to_pass_tests=tuple(str(item) for item in value["pass_to_pass_tests"]), |
| difficulty=str(value["difficulty"]), |
| provenance=str(value["provenance"]), |
| validation_status=str(value["validation_status"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed task specification {path}: {exc}") from exc |
| spec.validate(path) |
| return spec |
|
|
| def validate(self, source: Path | None = None) -> None: |
| errors: list[str] = [] |
| if self.schema_version != 1: |
| errors.append("schema_version must be 1") |
| if not re.fullmatch(r"TASK_[A-Z0-9_]+", self.task_id): |
| errors.append("task_id must match TASK_UPPERCASE_STYLE") |
| if not self.repository_url.startswith("https://gitlab.com/"): |
| errors.append("initial benchmark tasks must use a version-pinned GitLab repository") |
| if not re.fullmatch(r"[0-9a-fA-F]{40}", self.base_commit): |
| errors.append("base_commit must be a full 40-character Git commit SHA") |
| if not re.fullmatch(r"[0-9a-fA-F]{40}", self.gold_commit): |
| errors.append("gold_commit must be a full 40-character Git commit SHA") |
| if not self.language or not self.statement.strip(): |
| errors.append("language and statement must be non-empty") |
| if not self.gold_files or not self.gold_symbols: |
| errors.append("gold_files and gold_symbols must be non-empty") |
| allowed_statuses = {"template_only", "retrieval_ready", "end_to_end_ready"} |
| if self.validation_status not in allowed_statuses: |
| errors.append(f"validation_status must be one of {sorted(allowed_statuses)}") |
| if self.validation_status == "end_to_end_ready": |
| if not self.gold_patch: |
| errors.append("end-to-end-ready tasks require a gold patch") |
| if not self.test_patch: |
| errors.append("end-to-end-ready tasks require a hidden test patch") |
| if not self.fail_to_pass_tests or not self.pass_to_pass_tests: |
| errors.append("end-to-end-ready tasks require both test sets") |
| if not self.provenance.strip() or not self.difficulty.strip(): |
| errors.append("difficulty and provenance must be documented") |
| for repository_path in ( |
| *self.gold_files, |
| *((self.gold_patch,) if self.gold_patch else ()), |
| *((self.test_patch,) if self.test_patch else ()), |
| ): |
| path = Path(repository_path) |
| if path.is_absolute() or ".." in path.parts: |
| errors.append(f"repository path must be safe and relative: {repository_path}") |
| if errors: |
| location = f" ({source})" if source else "" |
| raise SpecError(f"Invalid task {self.task_id}{location}: " + "; ".join(errors)) |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class ExperimentSpec: |
| schema_version: int |
| experiment_id: str |
| name: str |
| mode: str |
| description: str |
| harness_ids: tuple[str, ...] |
| edit_interface_ids: tuple[str, ...] |
| agent_system_ids: tuple[str, ...] |
| repository_ids: tuple[str, ...] |
| backend_ids: tuple[str, ...] |
| model_ids: tuple[str, ...] |
| embedding_id: str |
| task_split: str |
| context_budgets: tuple[int, ...] |
| seeds: tuple[int, ...] |
| repetitions: int |
| max_tool_calls: int |
| max_test_runs: int |
| timeout_seconds: int |
|
|
| @classmethod |
| def load(cls, path: Path) -> "ExperimentSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| experiment_id=str(value["experiment_id"]), |
| name=str(value["name"]), |
| mode=str(value["mode"]), |
| description=str(value.get("description", "")), |
| harness_ids=tuple(str(item) for item in value["harness_ids"]), |
| edit_interface_ids=tuple( |
| str(item) for item in value.get("edit_interface_ids", []) |
| ), |
| agent_system_ids=tuple(str(item) for item in value.get("agent_system_ids", [])), |
| repository_ids=tuple(str(item) for item in value.get("repository_ids", [])), |
| backend_ids=tuple(str(item) for item in value.get("backend_ids", [])), |
| model_ids=tuple(str(item) for item in value["model_ids"]), |
| embedding_id=str(value["embedding_id"]), |
| task_split=str(value["task_split"]), |
| context_budgets=tuple(int(item) for item in value["context_budgets"]), |
| seeds=tuple(int(item) for item in value["seeds"]), |
| repetitions=int(value["repetitions"]), |
| max_tool_calls=int(value["max_tool_calls"]), |
| max_test_runs=int(value["max_test_runs"]), |
| timeout_seconds=int(value["timeout_seconds"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed experiment specification {path}: {exc}") from exc |
| spec.validate(path) |
| return spec |
|
|
| def validate(self, source: Path | None = None) -> None: |
| errors: list[str] = [] |
| if self.schema_version != 1: |
| errors.append("schema_version must be 1") |
| if not re.fullmatch(r"E\d{2}", self.experiment_id): |
| errors.append("experiment_id must match E00-style identifiers") |
| if not self.harness_ids or len(set(self.harness_ids)) != len(self.harness_ids): |
| errors.append("harness_ids must be non-empty and unique") |
| if len(set(self.edit_interface_ids)) != len(self.edit_interface_ids): |
| errors.append("edit_interface_ids must be unique") |
| if len(set(self.agent_system_ids)) != len(self.agent_system_ids): |
| errors.append("agent_system_ids must be unique") |
| if len(set(self.repository_ids)) != len(self.repository_ids): |
| errors.append("repository_ids must be unique") |
| is_study2 = self.mode in {"study2_live_agent", "study2_reliability"} |
| is_protocol = self.mode == "protocol_interface" |
| if is_study2 and (not self.agent_system_ids or not self.repository_ids): |
| errors.append("Study 2 experiments must enumerate agent systems and repositories") |
| if is_protocol and (not self.edit_interface_ids or not self.repository_ids): |
| errors.append("protocol experiments must enumerate edit interfaces and repositories") |
| if not is_protocol and self.edit_interface_ids: |
| errors.append("only protocol experiments may enumerate edit interfaces") |
| if not (is_study2 or is_protocol) and (self.agent_system_ids or self.repository_ids): |
| errors.append( |
| "only repository-scale Study 2/protocol experiments may enumerate " |
| "agent systems or repositories" |
| ) |
| if is_protocol and self.agent_system_ids: |
| errors.append("protocol experiments cannot enumerate agent systems") |
| if len(set(self.backend_ids)) != len(self.backend_ids): |
| errors.append("backend_ids must be unique") |
| if self.mode == "index_backend" and not self.backend_ids: |
| errors.append("index_backend experiments must enumerate backend_ids") |
| if self.mode != "index_backend" and self.backend_ids: |
| errors.append("only index_backend experiments may enumerate backend_ids") |
| if not self.model_ids or len(set(self.model_ids)) != len(self.model_ids): |
| errors.append("model_ids must be non-empty and unique") |
| if not self.context_budgets or any(value <= 0 for value in self.context_budgets): |
| errors.append("context budgets must be positive") |
| if not self.seeds or self.repetitions <= 0: |
| errors.append("seeds and repetitions must be positive/non-empty") |
| if min(self.max_tool_calls, self.max_test_runs, self.timeout_seconds) <= 0: |
| errors.append("execution limits must be positive") |
| if errors: |
| location = f" ({source})" if source else "" |
| raise SpecError("Invalid experiment specification" + location + ": " + "; ".join(errors)) |
|
|
| def cells_per_task(self) -> int: |
| return ( |
| (len(self.harness_ids) + len(self.agent_system_ids)) |
| * max(len(self.edit_interface_ids), 1) |
| * max(len(self.backend_ids), 1) |
| * len(self.model_ids) |
| * len(self.context_budgets) |
| * len(self.seeds) |
| * self.repetitions |
| ) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class BackendSpec: |
| schema_version: int |
| backend_id: str |
| name: str |
| kind: str |
| distance: str |
| description: str |
| library: str |
| version: str |
| index_type: str |
| query_repetitions: int |
| neighbors: int | None = None |
| ef_construction: int | None = None |
| ef_search: int | None = None |
|
|
| @classmethod |
| def load(cls, path: Path) -> "BackendSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| backend_id=str(value["backend_id"]), |
| name=str(value["name"]), |
| kind=str(value["kind"]), |
| distance=str(value["distance"]), |
| description=str(value.get("description", "")), |
| library=str(value["library"]), |
| version=str(value["version"]), |
| index_type=str(value["index_type"]), |
| query_repetitions=int(value["query_repetitions"]), |
| neighbors=int(value["neighbors"]) if "neighbors" in value else None, |
| ef_construction=int(value["ef_construction"]) if "ef_construction" in value else None, |
| ef_search=int(value["ef_search"]) if "ef_search" in value else None, |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed backend specification {path}: {exc}") from exc |
| errors: list[str] = [] |
| if spec.schema_version != 1 or not re.fullmatch(r"B\d{3}", spec.backend_id): |
| errors.append("invalid backend schema or identifier") |
| if not all((spec.library, spec.version, spec.index_type)) or spec.query_repetitions < 2: |
| errors.append("backend version, index type, and at least two repetitions are required") |
| if spec.backend_id == "B002" and None in ( |
| spec.neighbors, spec.ef_construction, spec.ef_search |
| ): |
| errors.append("B002 must freeze HNSW parameters") |
| if errors: |
| raise SpecError(f"Invalid backend specification {path}: " + "; ".join(errors)) |
| return spec |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class AgentSystemSpec: |
| schema_version: int |
| system_id: str |
| name: str |
| family: str |
| description: str |
| implementation: str |
| model_calls: int |
| max_tool_calls: int |
| max_test_runs: int |
| interactive: bool |
| same_model_required: bool |
| reference: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "AgentSystemSpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| system_id=str(value["system_id"]), |
| name=str(value["name"]), |
| family=str(value["family"]), |
| description=str(value["description"]), |
| implementation=str(value["implementation"]), |
| model_calls=int(value["model_calls"]), |
| max_tool_calls=int(value["max_tool_calls"]), |
| max_test_runs=int(value["max_test_runs"]), |
| interactive=bool(value["interactive"]), |
| same_model_required=bool(value["same_model_required"]), |
| reference=str(value["reference"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed agent-system specification {path}: {exc}") from exc |
| errors: list[str] = [] |
| if spec.schema_version != 1 or not re.fullmatch(r"A\d{3}", spec.system_id): |
| errors.append("invalid agent-system schema or identifier") |
| if not re.fullmatch(r"[a-z][a-z0-9_]*", spec.name): |
| errors.append("agent-system name must be a lowercase semantic slug") |
| if spec.implementation != "local_reimplementation": |
| errors.append("system baselines must be explicitly labeled local_reimplementation") |
| if spec.model_calls <= 0 or min(spec.max_tool_calls, spec.max_test_runs) < 0: |
| errors.append("agent-system budgets must be nonnegative and model_calls positive") |
| if not spec.same_model_required or not spec.reference.startswith("https://"): |
| errors.append("same-model control and a public reference are required") |
| if errors: |
| raise SpecError(f"Invalid agent-system specification {path}: " + "; ".join(errors)) |
| return spec |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class RepositorySpec: |
| schema_version: int |
| repository_id: str |
| name: str |
| repository_url: str |
| local_path: str |
| language: str |
| source_suffixes: tuple[str, ...] |
| test_pattern: str |
| test_runner: str |
| pinned_head: str |
|
|
| @classmethod |
| def load(cls, path: Path) -> "RepositorySpec": |
| value = _read_toml(path) |
| try: |
| spec = cls( |
| schema_version=int(value["schema_version"]), |
| repository_id=str(value["repository_id"]), |
| name=str(value["name"]), |
| repository_url=str(value["repository_url"]), |
| local_path=str(value["local_path"]), |
| language=str(value["language"]), |
| source_suffixes=tuple(str(item) for item in value["source_suffixes"]), |
| test_pattern=str(value["test_pattern"]), |
| test_runner=str(value["test_runner"]), |
| pinned_head=str(value["pinned_head"]), |
| ) |
| except (KeyError, TypeError, ValueError) as exc: |
| raise SpecError(f"Malformed repository specification {path}: {exc}") from exc |
| errors: list[str] = [] |
| if spec.schema_version != 1 or not re.fullmatch(r"R\d{3}", spec.repository_id): |
| errors.append("invalid repository schema or identifier") |
| if not spec.repository_url.startswith("https://gitlab.com/"): |
| errors.append("Study 2 repositories must be public GitLab URLs") |
| local = Path(spec.local_path) |
| if local.is_absolute() or ".." in local.parts: |
| errors.append("local_path must be a safe project-relative path") |
| if spec.language not in {"go", "python"}: |
| errors.append("unsupported Study 2 language") |
| if not spec.source_suffixes or any(not item.startswith(".") for item in spec.source_suffixes): |
| errors.append("source_suffixes must be non-empty file suffixes") |
| if not re.fullmatch(r"[0-9a-f]{40}", spec.pinned_head): |
| errors.append("pinned_head must be a lowercase full Git SHA") |
| if errors: |
| raise SpecError(f"Invalid repository specification {path}: " + "; ".join(errors)) |
| return spec |
|
|
| @property |
| def config_hash(self) -> str: |
| return _canonical_hash(asdict(self)) |
|
|
|
|
| def _load_specs(directory: Path, loader: Any, identifier_field: str) -> dict[str, Any]: |
| result: dict[str, Any] = {} |
| for path in sorted(directory.glob("*.toml")): |
| spec = loader(path) |
| identifier = getattr(spec, identifier_field) |
| if identifier in result: |
| raise SpecError(f"Duplicate specification identifier {identifier} in {directory}") |
| result[identifier] = spec |
| return result |
|
|
|
|
| def load_harnesses(root: Path | None = None) -> dict[str, HarnessSpec]: |
| base = root or project_root() |
| return _load_specs(base / "configs" / "harnesses", HarnessSpec.load, "harness_id") |
|
|
|
|
| def load_edit_interfaces(root: Path | None = None) -> dict[str, EditInterfaceSpec]: |
| base = root or project_root() |
| return _load_specs( |
| base / "configs" / "edit_interfaces", |
| EditInterfaceSpec.load, |
| "interface_id", |
| ) |
|
|
|
|
| def load_models(root: Path | None = None) -> dict[str, ModelSpec]: |
| base = root or project_root() |
| return _load_specs(base / "configs" / "models", ModelSpec.load, "model_id") |
|
|
|
|
| def load_embeddings(root: Path | None = None) -> dict[str, EmbeddingSpec]: |
| base = root or project_root() |
| return _load_specs(base / "configs" / "embeddings", EmbeddingSpec.load, "embedding_id") |
|
|
|
|
| def load_experiments(root: Path | None = None) -> dict[str, ExperimentSpec]: |
| base = root or project_root() |
| return _load_specs(base / "configs" / "experiments", ExperimentSpec.load, "experiment_id") |
|
|
|
|
| def load_backends(root: Path | None = None) -> dict[str, BackendSpec]: |
| base = root or project_root() |
| return _load_specs(base / "configs" / "backends", BackendSpec.load, "backend_id") |
|
|
|
|
| def load_agent_systems(root: Path | None = None) -> dict[str, AgentSystemSpec]: |
| base = root or project_root() |
| return _load_specs( |
| base / "configs" / "agent_systems", AgentSystemSpec.load, "system_id" |
| ) |
|
|
|
|
| def load_repositories(root: Path | None = None) -> dict[str, RepositorySpec]: |
| base = root or project_root() |
| return _load_specs( |
| base / "configs" / "repositories", RepositorySpec.load, "repository_id" |
| ) |
|
|
|
|
| def load_tasks(root: Path | None = None) -> dict[str, TaskSpec]: |
| base = root or project_root() |
| return _load_specs(base / "tasks" / "manifests", TaskSpec.load, "task_id") |
|
|
|
|
| def load_task_split(path: Path) -> tuple[str, ...]: |
| try: |
| identifiers = tuple( |
| line.strip() |
| for line in path.read_text(encoding="utf-8").splitlines() |
| if line.strip() and not line.lstrip().startswith("#") |
| ) |
| except OSError as exc: |
| raise SpecError(f"Cannot read task split {path}: {exc}") from exc |
| if len(set(identifiers)) != len(identifiers): |
| raise SpecError(f"Task split contains duplicate identifiers: {path}") |
| return identifiers |
|
|
|
|
| def _validate_plain_toml(paths: Iterable[Path], errors: list[str]) -> None: |
| for path in paths: |
| try: |
| _read_toml(path) |
| except SpecError as exc: |
| errors.append(str(exc)) |
|
|
|
|
| def validate_configuration_tree(root: Path | None = None) -> tuple[list[str], list[str]]: |
| base = root or project_root() |
| errors: list[str] = [] |
| warnings: list[str] = [] |
| try: |
| harnesses = load_harnesses(base) |
| edit_interfaces = load_edit_interfaces(base) |
| models = load_models(base) |
| embeddings = load_embeddings(base) |
| experiments = load_experiments(base) |
| backends = load_backends(base) |
| agent_systems = load_agent_systems(base) |
| repositories = load_repositories(base) |
| tasks = load_tasks(base) |
| except SpecError as exc: |
| return [str(exc)], [] |
|
|
| expected_harness_ids = {f"H{number:03d}" for number in range(21)} |
| if set(harnesses) != expected_harness_ids: |
| errors.append( |
| "Initial catalog must contain exactly H000-H020; found " |
| + ", ".join(sorted(harnesses)) |
| ) |
| if set(models) != {"M001", "M002", "M003", "M004"}: |
| errors.append("The study must define exactly the pinned runtime profiles M001-M004") |
| if set(edit_interfaces) != {"P001", "P002", "P003"}: |
| errors.append("Study 3 must define exactly edit interfaces P001-P003") |
| if set(embeddings) != {"EMB001", "EMB002"}: |
| errors.append( |
| "The study must define exactly EMB001 (Study 1) and EMB002 (Study 2)" |
| ) |
| if set(backends) != {"B001", "B002", "B003"}: |
| errors.append("The backend study must define exactly B001-B003") |
| if set(agent_systems) != {"A001", "A002"}: |
| errors.append("Study 2 must define exactly controlled agent systems A001-A002") |
| if set(repositories) != {"R001", "R002", "R003"}: |
| errors.append("Study 2 must define exactly repositories R001-R003") |
|
|
| for experiment in experiments.values(): |
| missing_harnesses = set(experiment.harness_ids) - set(harnesses) |
| missing_edit_interfaces = set(experiment.edit_interface_ids) - set(edit_interfaces) |
| missing_models = set(experiment.model_ids) - set(models) |
| missing_systems = set(experiment.agent_system_ids) - set(agent_systems) |
| missing_repositories = set(experiment.repository_ids) - set(repositories) |
| if missing_harnesses: |
| errors.append(f"{experiment.experiment_id} references missing harnesses {sorted(missing_harnesses)}") |
| if missing_edit_interfaces: |
| errors.append( |
| f"{experiment.experiment_id} references missing edit interfaces " |
| f"{sorted(missing_edit_interfaces)}" |
| ) |
| if missing_models: |
| errors.append(f"{experiment.experiment_id} references missing models {sorted(missing_models)}") |
| if missing_systems: |
| errors.append( |
| f"{experiment.experiment_id} references missing agent systems {sorted(missing_systems)}" |
| ) |
| if missing_repositories: |
| errors.append( |
| f"{experiment.experiment_id} references missing repositories {sorted(missing_repositories)}" |
| ) |
| missing_backends = set(experiment.backend_ids) - set(backends) |
| if missing_backends: |
| errors.append( |
| f"{experiment.experiment_id} references missing backends {sorted(missing_backends)}" |
| ) |
| if experiment.embedding_id not in embeddings: |
| errors.append( |
| f"{experiment.experiment_id} references missing embedding profile {experiment.embedding_id}" |
| ) |
| continue |
| uses_dense = any(harnesses[item].uses_embedding for item in experiment.harness_ids) |
| if uses_dense and embeddings[experiment.embedding_id].status != "ready": |
| warnings.append( |
| f"{experiment.experiment_id} includes dense retrieval but {experiment.embedding_id} " |
| "is not configured; execution must remain blocked" |
| ) |
|
|
| split_path = base / "tasks" / "splits" / f"{experiment.task_split}.txt" |
| try: |
| split = load_task_split(split_path) |
| except SpecError as exc: |
| errors.append(str(exc)) |
| continue |
| missing_tasks = set(split) - set(tasks) |
| if missing_tasks: |
| errors.append( |
| f"{experiment.experiment_id} split references missing tasks {sorted(missing_tasks)}" |
| ) |
| if "TASK_EXAMPLE" in split: |
| errors.append(f"{experiment.experiment_id} cannot include the template TASK_EXAMPLE") |
| if not split: |
| warnings.append( |
| f"{experiment.experiment_id} task split {experiment.task_split} is empty; " |
| "execution must remain blocked" |
| ) |
|
|
| if experiment.mode in { |
| "study2_live_agent", |
| "study2_reliability", |
| "protocol_interface", |
| }: |
| allowed_urls = { |
| repositories[item].repository_url for item in experiment.repository_ids |
| } |
| unexpected = { |
| tasks[item].repository_url |
| for item in split |
| if item in tasks and tasks[item].repository_url not in allowed_urls |
| } |
| if unexpected: |
| errors.append( |
| f"{experiment.experiment_id} split contains repositories outside its registry: " |
| f"{sorted(unexpected)}" |
| ) |
|
|
| hashes: dict[str, str] = {} |
| for harness in harnesses.values(): |
| if harness.treatment_hash in hashes: |
| errors.append( |
| f"{harness.harness_id} duplicates the causal treatment of " |
| f"{hashes[harness.treatment_hash]}" |
| ) |
| hashes[harness.treatment_hash] = harness.harness_id |
|
|
| interface_hashes: dict[str, str] = {} |
| for interface in edit_interfaces.values(): |
| if interface.treatment_hash in interface_hashes: |
| errors.append( |
| f"{interface.interface_id} duplicates the causal treatment of " |
| f"{interface_hashes[interface.treatment_hash]}" |
| ) |
| interface_hashes[interface.treatment_hash] = interface.interface_id |
|
|
| _validate_plain_toml((base / "configs" / "scenarios").glob("*.toml"), errors) |
| _validate_plain_toml((base / "configs" / "backends").glob("*.toml"), errors) |
| return errors, warnings |
|
|