open-range / src /open_range /resolve.py
Aaron Brown
Remove hardcoded fallbacks, add snapshot-driven service lifecycle
7fedc25
Raw
History Blame Contribute Delete
3.85 kB
"""Dynamic component resolution for OpenRange.
Loads Builder, NPC, and Validator implementations at startup from
dotted class paths specified in YAML config.
"""
from __future__ import annotations
import importlib
import inspect
from pathlib import Path
from typing import Any, Type
import yaml
from open_range.protocols import (
NPCBehavior,
SnapshotBuilder,
ValidatorCheck,
)
# Default validator checks when none specified in config
DEFAULT_CHECKS: list[dict[str, Any]] = [
{"class": "open_range.validator.build_boot.BuildBootCheck"},
{"class": "open_range.validator.exploitability.ExploitabilityCheck"},
{"class": "open_range.validator.patchability.PatchabilityCheck"},
{"class": "open_range.validator.evidence.EvidenceCheck"},
{"class": "open_range.validator.reward_grounding.RewardGroundingCheck"},
{"class": "open_range.validator.isolation.IsolationCheck"},
]
def _missing_methods(instance: Any, protocol: Type) -> list[str]:
"""Return protocol method names missing from instance."""
missing = []
for name, method in inspect.getmembers(protocol, predicate=inspect.isfunction):
if name.startswith("_"):
continue
if not hasattr(instance, name) or not callable(getattr(instance, name)):
missing.append(name)
return missing
def resolve_component(class_path: str, kwargs: dict, protocol: Type) -> Any:
"""Import class by dotted path, instantiate, verify protocol compliance.
Args:
class_path: Dotted Python class path (e.g. "open_range.builder.builder.LLMSnapshotBuilder")
kwargs: Constructor keyword arguments
protocol: Protocol class to check against
Returns:
Instantiated component satisfying the protocol.
Raises:
TypeError: If the class doesn't satisfy the protocol.
ImportError: If the module can't be imported.
AttributeError: If the class doesn't exist in the module.
"""
module_name, _, class_name = class_path.rpartition(".")
module = importlib.import_module(module_name)
cls = getattr(module, class_name)
instance = cls(**kwargs)
if not isinstance(instance, protocol):
missing = _missing_methods(instance, protocol)
raise TypeError(
f"{class_path} does not satisfy {protocol.__name__} protocol. "
f"Missing methods: {missing}"
)
return instance
def load_agent_config(config_path: str) -> dict:
"""Load agent configuration from YAML file.
Returns the 'agents' block from the config, or empty dict.
"""
path = Path(config_path)
if not path.exists():
return {}
with open(path) as f:
config = yaml.safe_load(f) or {}
return config.get("agents", {})
def build_components(
config: dict,
) -> tuple[SnapshotBuilder, NPCBehavior, list[ValidatorCheck]]:
"""Resolve all infrastructure components from config dict.
Args:
config: The 'agents' block from openrange.yaml
Returns:
(builder, npc_behavior, validator_checks) tuple
"""
builder_cfg = config.get("builder", {})
builder = resolve_component(
builder_cfg.get("class", "open_range.builder.builder.LLMSnapshotBuilder"),
builder_cfg.get("kwargs", {}),
SnapshotBuilder,
)
npc_cfg = config.get("npc_behavior", {})
npc = resolve_component(
npc_cfg.get("class", "open_range.builder.npc.npc_agent.NullNPCBehavior"),
npc_cfg.get("kwargs", {}),
NPCBehavior,
)
checks: list[ValidatorCheck] = []
for check_cfg in config.get("validator_checks", DEFAULT_CHECKS):
checks.append(
resolve_component(
check_cfg["class"],
check_cfg.get("kwargs", {}),
ValidatorCheck,
)
)
return builder, npc, checks