Brain-5D-Space / src /experience /composition.py
github-actions[bot]
Sync: publish Space API fix
5e0b58b
Raw History Blame Contribute Delete
4.86 kB
"""Configuration-driven composition for the controlled experience loop."""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, cast
from src.embodiment import (
ActionCommand,
ActuatorResult,
ConnectionDescriptor,
ConnectionKind,
ConnectionStatus,
ControlledEmbodimentAgent,
DeterministicTargetEnvironment,
RelationshipClass,
SystemSensorAdapter,
host_system_readings,
)
from src.experience.engine import ExperienceEngine
from src.learning.learning_engine import LearningEngine
@dataclass(slots=True)
class DeterministicActuator:
"""Actuator boundary for the fully controlled digital environment."""
actuator_id: str = "target-actuator"
active: bool = True
def apply(self, command: ActionCommand) -> ActuatorResult:
return ActuatorResult(True, command.action)
def _numeric(value: Any) -> float:
return float(value) if isinstance(value, (int, float)) else 0.0
def _system_encoder(frame: Any) -> Mapping[int, float]:
payload = frame.payload
if not isinstance(payload, dict):
return {}
values = (
_numeric(payload.get("cpu_percent")),
_numeric(payload.get("memory_percent")),
_numeric(payload.get("temperature_c")),
1.0 if payload.get("network_up") is True else 0.0,
)
return {index: value / 100.0 for index, value in enumerate(values)}
def _controlled_decoder(result: Any, frame: Any) -> ActionCommand | None:
spikes = getattr(result, "output_spike_ids", ())
if not spikes:
return None
return ActionCommand("target-actuator", frame.tick, "right")
def build_experience_subsystem(
config: Mapping[str, Any],
network: Any,
learning: LearningEngine | None,
) -> ExperienceEngine | None:
"""Build the configured experience subsystem, or return ``None`` disabled."""
raw = config.get("experience", {})
if not isinstance(raw, Mapping):
raise TypeError("experience config must be a mapping")
if not bool(raw.get("enabled", False)):
return None
sensor_config = raw.get("sensor", {})
encoder_config = raw.get("encoder", {})
decoder_config = raw.get("decoder", {})
environment_config = raw.get("environment", {})
for name, value in (
("experience.sensor", sensor_config),
("experience.encoder", encoder_config),
("experience.decoder", decoder_config),
("experience.environment", environment_config),
):
if not isinstance(value, Mapping):
raise TypeError(f"{name} config must be a mapping")
if sensor_config.get("type", "system") != "system":
raise ValueError("unsupported experience sensor type")
provider_name = sensor_config.get("provider", "host")
if provider_name == "host":
provider = host_system_readings
elif provider_name == "deterministic_trace":
trace = sensor_config.get("trace")
if not isinstance(trace, list):
raise ValueError("deterministic_trace requires a list trace")
def provider(tick: int) -> Mapping[str, Any]:
if tick >= len(trace) or not isinstance(trace[tick], Mapping):
raise ValueError("deterministic_trace has no mapping for this tick")
return cast(Mapping[str, Any], trace[tick])
else:
raise ValueError("unknown experience sensor provider")
if encoder_config.get("type", "system_v1") != "system_v1":
raise ValueError("unknown experience encoder type")
if decoder_config.get("type", "controlled_v1") != "controlled_v1":
raise ValueError("unknown experience decoder type")
if environment_config.get("type", "deterministic_target") != "deterministic_target":
raise ValueError("unknown experience environment type")
if learning is None:
raise RuntimeError("enabled experience requires the learning engine")
descriptor = ConnectionDescriptor(
connection_id="target-actuator",
name="Deterministic target actuator",
kind=ConnectionKind.ACTUATOR,
relationship=RelationshipClass.CONTROLLABLE,
status=ConnectionStatus.CONNECTED,
capabilities=("right",),
available=True,
authorized=True,
active=True,
)
embodiment = ControlledEmbodimentAgent(
environment=DeterministicTargetEnvironment(),
actuator=DeterministicActuator(),
descriptor=descriptor,
)
embodiment.reset(seed=int(config.get("seed", 42)))
return ExperienceEngine(
sensor=SystemSensorAdapter(provider),
network=network,
encoder=_system_encoder,
decoder=_controlled_decoder,
embodiment=embodiment,
learning=learning,
)
__all__ = ["DeterministicActuator", "build_experience_subsystem"]