Spaces:
Runtime error
Runtime error
| """ | |
| Runtime object factory for WorldSmithAI. | |
| This module converts validated DSL specifications into live runtime objects: | |
| World, Agent, Resource, Event, Behavior, Policy, and Scheduler instances. | |
| The factory is intentionally domain-agnostic. It does not know about sheep, | |
| wolves, farms, dragons, scientists, markets, civilizations, power grids, | |
| transport networks, or research ecosystems. It only resolves generic DSL | |
| records through registries and runtime constructors. | |
| Important compatibility notes: | |
| - DSL positions are JSON arrays, but the runtime world may expect NumPy | |
| arrays with attributes such as ``.size``. | |
| - Runtime Agent and Resource positions are always converted to NumPy arrays. | |
| - Runtime behavior IDs are generated automatically. | |
| - Runtime event IDs are always non-null because core.world.add_event may | |
| store events by ``event.id``. | |
| - World collections are initialized as dictionaries when add_* methods | |
| expect mapping-style storage. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import inspect | |
| import logging | |
| from collections.abc import Mapping, Sequence | |
| from dataclasses import dataclass, field | |
| from importlib import import_module | |
| from types import MappingProxyType | |
| from typing import Any | |
| import numpy as np | |
| from core.agent import Agent | |
| from core.behavior import Behavior | |
| from core.event import Event | |
| from core.resource import Resource | |
| from core.scheduler import Scheduler | |
| from core.world import World | |
| from dsl.schema import ( | |
| AgentSpec, | |
| BehaviorSpec, | |
| EventSpec, | |
| MetricSpec, | |
| PolicySpec, | |
| ResourceSpec, | |
| WorldSpec, | |
| ) | |
| from dsl.validator import ( | |
| ValidationConfig, | |
| ValidationReport, | |
| ValidationSeverity, | |
| validate_world_spec, | |
| ) | |
| from policies.base_policy import BasePolicy | |
| logger = logging.getLogger(__name__) | |
| DEFAULT_BEHAVIOR_MODULES: tuple[str, ...] = ( | |
| "behaviors.movement", | |
| "behaviors.consume", | |
| "behaviors.trade", | |
| "behaviors.attack", | |
| "behaviors.research", | |
| "behaviors.collaboration", | |
| "behaviors.transport", | |
| "behaviors.construction", | |
| "behaviors.governance", | |
| "behaviors.market", | |
| "behaviors.adoption", | |
| "behaviors.planning", | |
| "behaviors.memory", | |
| ) | |
| DEFAULT_POLICY_MODULES: tuple[str, ...] = ( | |
| "policies.rule_policy", | |
| "policies.contextual_bandit", | |
| ) | |
| BEHAVIOR_REGISTRY_ATTRIBUTES: tuple[str, ...] = ("BEHAVIOR_REGISTRY",) | |
| POLICY_REGISTRY_ATTRIBUTES: tuple[str, ...] = ( | |
| "POLICY_REGISTRY", | |
| "RULE_POLICY_REGISTRY", | |
| ) | |
| _CORE_AGENT_ALIASES: Mapping[str, tuple[str, ...]] = MappingProxyType( | |
| { | |
| "id": ("agent_id",), | |
| "type": ("agent_type", "kind"), | |
| } | |
| ) | |
| _CORE_RESOURCE_ALIASES: Mapping[str, tuple[str, ...]] = MappingProxyType( | |
| { | |
| "id": ("resource_id",), | |
| "type": ("resource_type", "kind"), | |
| "amount": ("quantity",), | |
| } | |
| ) | |
| _CORE_EVENT_ALIASES: Mapping[str, tuple[str, ...]] = MappingProxyType( | |
| { | |
| "id": ("event_id",), | |
| "name": ("type", "event_type"), | |
| "trigger_step": ("step", "at_step"), | |
| "payload": ("data",), | |
| } | |
| ) | |
| _CORE_WORLD_ALIASES: Mapping[str, tuple[str, ...]] = MappingProxyType({}) | |
| _CORE_SCHEDULER_ALIASES: Mapping[str, tuple[str, ...]] = MappingProxyType({}) | |
| class WorldFactoryError(RuntimeError): | |
| """Base exception raised by the runtime world factory.""" | |
| class RegistryResolutionError(WorldFactoryError): | |
| """Raised when a behavior, policy, or runtime class cannot be resolved.""" | |
| class ConstructorBindingError(WorldFactoryError): | |
| """Raised when DSL parameters cannot be bound to a constructor.""" | |
| class RuntimeObjectBuildError(WorldFactoryError): | |
| """Raised when a runtime object cannot be built.""" | |
| class WorldFactoryValidationError(WorldFactoryError): | |
| """Raised when semantic DSL validation fails before world construction.""" | |
| def __init__(self, report: ValidationReport) -> None: | |
| """Initialize the error from a validation report.""" | |
| self.report = report | |
| super().__init__(report.summary()) | |
| class RegistryLoadReport: | |
| """Report describing dynamically loaded registry entries.""" | |
| registry: Mapping[str, Any] | |
| import_errors: Mapping[str, str] = field(default_factory=dict) | |
| def names(self) -> tuple[str, ...]: | |
| """Return sorted registry names.""" | |
| return tuple(sorted(str(name) for name in self.registry.keys())) | |
| def to_dict(self) -> dict[str, Any]: | |
| """Return a JSON-friendly registry load report.""" | |
| return { | |
| "names": list(self.names), | |
| "count": len(self.registry), | |
| "import_errors": copy.deepcopy(dict(self.import_errors)), | |
| } | |
| class WorldBuildReport: | |
| """Structured report generated during world construction.""" | |
| world_id: str | |
| agent_ids: tuple[str, ...] | |
| resource_ids: tuple[str, ...] | |
| event_keys: tuple[str, ...] | |
| behavior_registry_count: int | |
| policy_registry_count: int | |
| import_errors: Mapping[str, str] = field(default_factory=dict) | |
| validation_report: ValidationReport | None = None | |
| metadata: Mapping[str, Any] = field(default_factory=dict) | |
| def valid(self) -> bool: | |
| """Return whether the optional validation report is valid.""" | |
| return self.validation_report is None or self.validation_report.is_valid | |
| def to_dict(self) -> dict[str, Any]: | |
| """Return a JSON-friendly build report.""" | |
| return { | |
| "world_id": self.world_id, | |
| "agent_ids": list(self.agent_ids), | |
| "resource_ids": list(self.resource_ids), | |
| "event_keys": list(self.event_keys), | |
| "agent_count": len(self.agent_ids), | |
| "resource_count": len(self.resource_ids), | |
| "event_count": len(self.event_keys), | |
| "behavior_registry_count": self.behavior_registry_count, | |
| "policy_registry_count": self.policy_registry_count, | |
| "import_errors": copy.deepcopy(dict(self.import_errors)), | |
| "validation_report": None | |
| if self.validation_report is None | |
| else self.validation_report.to_dict(), | |
| "metadata": copy.deepcopy(dict(self.metadata)), | |
| } | |
| class WorldBuildResult: | |
| """Factory result containing the runtime world and build report.""" | |
| world: World | |
| report: WorldBuildReport | |
| class WorldFactoryConfig: | |
| """Configuration for ``WorldFactory``.""" | |
| validate_before_build: bool = True | |
| strict_unknown_behaviors: bool = True | |
| strict_unknown_policies: bool = True | |
| strict_constructor_params: bool = True | |
| include_disabled_behaviors: bool = False | |
| include_disabled_events: bool = False | |
| attach_default_policy: bool = True | |
| default_policy_type: str = "rule_policy" | |
| attach_scheduler: bool = True | |
| attach_dsl_spec_to_world: bool = True | |
| behavior_modules: tuple[str, ...] = DEFAULT_BEHAVIOR_MODULES | |
| policy_modules: tuple[str, ...] = DEFAULT_POLICY_MODULES | |
| behavior_registry: Mapping[str, Any] | None = None | |
| policy_registry: Mapping[str, Any] | None = None | |
| agent_class: type[Agent] = Agent | |
| resource_class: type[Resource] = Resource | |
| event_class: type[Event] = Event | |
| world_class: type[World] = World | |
| scheduler_class: type[Scheduler] | None = Scheduler | |
| world_uses_mapping_collections: bool = True | |
| class GenericScheduledEvent: | |
| """Fallback event object used when ``core.event.Event`` is not instantiable.""" | |
| name: str | |
| trigger_step: int | |
| payload: Mapping[str, Any] = field(default_factory=dict) | |
| id: str | None = None | |
| enabled: bool = True | |
| repeat_interval: int | None = None | |
| target_agent_ids: tuple[str, ...] = () | |
| target_resource_ids: tuple[str, ...] = () | |
| metadata: Mapping[str, Any] = field(default_factory=dict) | |
| def execute(self, world: World) -> dict[str, Any]: | |
| """Execute the event in a domain-agnostic way.""" | |
| return { | |
| "event": self.name, | |
| "event_id": self.id, | |
| "trigger_step": self.trigger_step, | |
| "enabled": self.enabled, | |
| "payload": copy.deepcopy(dict(self.payload)), | |
| "target_agent_ids": list(self.target_agent_ids), | |
| "target_resource_ids": list(self.target_resource_ids), | |
| "world_step": getattr(world, "step_count", None), | |
| } | |
| class WorldFactory: | |
| """Factory that converts ``WorldSpec`` into a runtime ``World``.""" | |
| config: WorldFactoryConfig = field(default_factory=WorldFactoryConfig) | |
| _behavior_registry: dict[str, Any] = field(default_factory=dict, init=False, repr=False) | |
| _policy_registry: dict[str, Any] = field(default_factory=dict, init=False, repr=False) | |
| _registry_import_errors: dict[str, str] = field(default_factory=dict, init=False, repr=False) | |
| def __post_init__(self) -> None: | |
| """Load configured behavior and policy registries.""" | |
| self._behavior_registry = self._build_behavior_registry() | |
| self._policy_registry = self._build_policy_registry() | |
| def behavior_registry(self) -> Mapping[str, Any]: | |
| """Return a read-only view of the behavior registry.""" | |
| return MappingProxyType(self._behavior_registry) | |
| def policy_registry(self) -> Mapping[str, Any]: | |
| """Return a read-only view of the policy registry.""" | |
| return MappingProxyType(self._policy_registry) | |
| def registry_import_errors(self) -> Mapping[str, str]: | |
| """Return registry import errors collected during initialization.""" | |
| return MappingProxyType(self._registry_import_errors) | |
| def create_world(self, spec: WorldSpec | Mapping[str, Any]) -> World: | |
| """Create a runtime world from a validated or mappable world spec.""" | |
| return self.create_world_result(spec).world | |
| def create_world_result(self, spec: WorldSpec | Mapping[str, Any]) -> WorldBuildResult: | |
| """Create a world and return both runtime object and build report.""" | |
| world_spec = self._coerce_world_spec(spec) | |
| validation_report = self._validate_before_build(world_spec) | |
| space_dimensions = ( | |
| int(world_spec.space.dimensions) | |
| if world_spec.space is not None | |
| else None | |
| ) | |
| resources = tuple( | |
| self.create_resource(resource_spec, dimensions=space_dimensions) | |
| for resource_spec in world_spec.resources | |
| ) | |
| events = tuple( | |
| self.create_event(event_spec) | |
| for event_spec in world_spec.events | |
| if event_spec.enabled or self.config.include_disabled_events | |
| ) | |
| agents = tuple( | |
| self.create_agent(agent_spec, dimensions=space_dimensions) | |
| for agent_spec in world_spec.agents | |
| ) | |
| metrics = tuple( | |
| self.create_metric_config(metric_spec) | |
| for metric_spec in world_spec.metrics | |
| ) | |
| world = self._create_world_object( | |
| spec=world_spec, | |
| agents=agents, | |
| resources=resources, | |
| events=events, | |
| metrics=metrics, | |
| ) | |
| if self.config.attach_scheduler: | |
| self._attach_scheduler(world, world_spec) | |
| report = WorldBuildReport( | |
| world_id=world_spec.id, | |
| agent_ids=tuple(agent.id for agent in world_spec.agents), | |
| resource_ids=tuple(resource.id for resource in world_spec.resources), | |
| event_keys=tuple(event.event_key for event in world_spec.events), | |
| behavior_registry_count=len(self._behavior_registry), | |
| policy_registry_count=len(self._policy_registry), | |
| import_errors=copy.deepcopy(self._registry_import_errors), | |
| validation_report=validation_report, | |
| metadata={ | |
| "world_name": world_spec.name, | |
| "schema_version": world_spec.schema_version, | |
| "simulation_steps": world_spec.simulation.steps, | |
| }, | |
| ) | |
| logger.info( | |
| "Created world %s with %s agent(s), %s resource(s), and %s event(s)", | |
| world_spec.id, | |
| len(agents), | |
| len(resources), | |
| len(events), | |
| ) | |
| return WorldBuildResult(world=world, report=report) | |
| def create_agent( | |
| self, | |
| spec: AgentSpec, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> Agent: | |
| """Create a runtime ``Agent`` from an ``AgentSpec``.""" | |
| behavior_map: dict[str, Behavior] = {} | |
| for behavior_index, behavior_spec in enumerate(spec.behaviors): | |
| if not behavior_spec.enabled and not self.config.include_disabled_behaviors: | |
| continue | |
| behavior = self.create_behavior( | |
| behavior_spec, | |
| owner_agent_id=spec.id, | |
| behavior_index=behavior_index, | |
| ) | |
| behavior_id = str( | |
| getattr(behavior, "id", "") | |
| or self._behavior_runtime_id( | |
| behavior_spec, | |
| owner_agent_id=spec.id, | |
| behavior_index=behavior_index, | |
| ) | |
| ) | |
| if behavior_id in behavior_map: | |
| behavior_id = f"{behavior_id}#{behavior_index}" | |
| behavior_map[behavior_id] = behavior | |
| policy = self.create_policy_for_agent(spec) | |
| runtime_position = self._agent_runtime_position( | |
| spec.position, | |
| dimensions=dimensions, | |
| ) | |
| params = { | |
| "id": spec.id, | |
| "type": spec.type, | |
| "position": runtime_position.copy(), | |
| "state": copy.deepcopy(spec.state), | |
| "memory": copy.deepcopy(spec.memory), | |
| "goals": copy.deepcopy(spec.goals), | |
| "behaviors": behavior_map, | |
| "policy": policy, | |
| "alive": bool(spec.alive), | |
| "metadata": copy.deepcopy(spec.metadata), | |
| } | |
| agent = self._instantiate_runtime_object( | |
| self.config.agent_class, | |
| params, | |
| aliases=_CORE_AGENT_ALIASES, | |
| strict=False, | |
| path=f"agents.{spec.id}", | |
| item_kind="agent", | |
| ) | |
| self._safe_setattr(agent, "id", spec.id) | |
| self._safe_setattr(agent, "type", spec.type) | |
| self._force_setattr(agent, "position", runtime_position.copy()) | |
| self._safe_setattr(agent, "state", copy.deepcopy(spec.state)) | |
| self._safe_setattr(agent, "memory", copy.deepcopy(spec.memory)) | |
| self._safe_setattr(agent, "goals", copy.deepcopy(spec.goals)) | |
| self._safe_setattr(agent, "behaviors", behavior_map) | |
| self._safe_setattr(agent, "policy", policy) | |
| self._safe_setattr(agent, "alive", bool(spec.alive)) | |
| self._safe_setattr(agent, "metadata", copy.deepcopy(spec.metadata)) | |
| self._safe_setattr(agent, "dsl_spec", spec) | |
| self._ensure_agent_position_array(agent, dimensions=dimensions) | |
| return agent | |
| def create_behavior( | |
| self, | |
| spec: BehaviorSpec, | |
| *, | |
| owner_agent_id: str | None = None, | |
| behavior_index: int | None = None, | |
| ) -> Behavior: | |
| """Create a runtime behavior object from a ``BehaviorSpec``. | |
| The DSL behavior spec has this shape: | |
| {"name": "attack", "params": {...}} | |
| Runtime behavior objects should receive a deterministic runtime id, but | |
| the DSL behavior name should not be passed as a constructor argument named | |
| ``name`` because many behavior classes expose ``name`` as a read-only or | |
| class-level registry attribute. | |
| This method supports both behavior implementation styles used in the | |
| codebase: | |
| 1. Newer dataclass-style behaviors that accept params directly as | |
| constructor kwargs. | |
| 2. Rich core.behavior-style behaviors that expect: | |
| Behavior(id="...", parameters={...}) | |
| The second form is especially important for rich movement/conflict modules. | |
| """ | |
| target = self._resolve_registry_item( | |
| registry=self._behavior_registry, | |
| name=spec.name, | |
| strict=self.config.strict_unknown_behaviors, | |
| item_kind="behavior", | |
| path=f"behavior:{spec.name}", | |
| ) | |
| runtime_behavior_id = self._behavior_runtime_id( | |
| spec, | |
| owner_agent_id=owner_agent_id, | |
| behavior_index=behavior_index, | |
| ) | |
| raw_params = copy.deepcopy(spec.params) | |
| constructor_attempts: tuple[tuple[str, dict[str, Any], bool], ...] = ( | |
| ( | |
| "direct_params", | |
| { | |
| **copy.deepcopy(raw_params), | |
| "id": runtime_behavior_id, | |
| }, | |
| self.config.strict_constructor_params, | |
| ), | |
| ( | |
| "parameters_dict", | |
| { | |
| "id": runtime_behavior_id, | |
| "parameters": copy.deepcopy(raw_params), | |
| }, | |
| False, | |
| ), | |
| ( | |
| "behavior_id_parameters_dict", | |
| { | |
| "behavior_id": runtime_behavior_id, | |
| "parameters": copy.deepcopy(raw_params), | |
| }, | |
| False, | |
| ), | |
| ) | |
| behavior: Any | None = None | |
| attempt_errors: list[str] = [] | |
| for attempt_name, attempt_params, strict in constructor_attempts: | |
| try: | |
| behavior = self._instantiate_runtime_object( | |
| target, | |
| attempt_params, | |
| aliases={}, | |
| strict=strict, | |
| path=f"behavior:{spec.name}.params", | |
| item_kind="behavior", | |
| ) | |
| break | |
| except Exception as exc: | |
| attempt_errors.append( | |
| f"{attempt_name}: {exc.__class__.__name__}: {exc}" | |
| ) | |
| logger.debug( | |
| "Behavior construction attempt failed for behavior=%s attempt=%s", | |
| spec.name, | |
| attempt_name, | |
| exc_info=True, | |
| ) | |
| if behavior is None: | |
| raise RuntimeObjectBuildError( | |
| "Could not instantiate behavior " | |
| f"{spec.name!r} after {len(constructor_attempts)} constructor attempt(s). " | |
| f"Attempt errors: {attempt_errors}" | |
| ) | |
| if not isinstance(behavior, Behavior) and not callable(getattr(behavior, "execute", None)): | |
| raise RuntimeObjectBuildError( | |
| f"Behavior {spec.name!r} did not produce an executable behavior object" | |
| ) | |
| self._safe_setattr(behavior, "id", runtime_behavior_id) | |
| self._safe_setattr(behavior, "behavior_id", runtime_behavior_id) | |
| # Do not require this to succeed. Some behavior classes expose ``name`` as | |
| # a read-only/class-level registry attribute. | |
| self._safe_setattr(behavior, "name", spec.name) | |
| # These are safe diagnostics/metadata attachments. They may fail on highly | |
| # slotted/frozen classes, so _safe_setattr intentionally does not raise. | |
| self._safe_setattr(behavior, "enabled", bool(spec.enabled)) | |
| self._safe_setattr(behavior, "priority", float(spec.priority)) | |
| self._safe_setattr(behavior, "tags", tuple(spec.tags)) | |
| self._safe_setattr(behavior, "metadata", copy.deepcopy(spec.metadata)) | |
| self._safe_setattr(behavior, "dsl_spec", spec) | |
| # Rich behavior modules such as movement.py and attack.py use | |
| # ``self.parameters`` internally. Preserve the original DSL params there | |
| # whenever the object supports it. | |
| existing_parameters = getattr(behavior, "parameters", None) | |
| if not isinstance(existing_parameters, Mapping) or not existing_parameters: | |
| self._safe_setattr(behavior, "parameters", copy.deepcopy(raw_params)) | |
| return behavior | |
| def create_policy_for_agent(self, agent_spec: AgentSpec) -> BasePolicy | None: | |
| """Create the policy configured for an agent.""" | |
| policy_spec = agent_spec.policy | |
| if policy_spec is None and self.config.attach_default_policy and agent_spec.behaviors: | |
| policy_spec = PolicySpec(type=self.config.default_policy_type, params={}) | |
| if policy_spec is None: | |
| return None | |
| return self.create_policy(policy_spec) | |
| def create_policy(self, spec: PolicySpec) -> BasePolicy: | |
| """Create a runtime policy object from a ``PolicySpec``.""" | |
| target = self._resolve_registry_item( | |
| registry=self._policy_registry, | |
| name=spec.type, | |
| strict=self.config.strict_unknown_policies, | |
| item_kind="policy", | |
| path=f"policy:{spec.type}", | |
| ) | |
| params = copy.deepcopy(spec.params) | |
| params.setdefault("enabled", bool(spec.enabled)) | |
| if spec.metadata: | |
| params.setdefault("metadata", copy.deepcopy(spec.metadata)) | |
| policy = self._instantiate_runtime_object( | |
| target, | |
| params, | |
| aliases={}, | |
| strict=self.config.strict_constructor_params, | |
| path=f"policy:{spec.type}.params", | |
| item_kind="policy", | |
| ) | |
| if not isinstance(policy, BasePolicy) and not callable(getattr(policy, "choose_action", None)): | |
| raise RuntimeObjectBuildError( | |
| f"Policy {spec.type!r} did not produce a valid policy object" | |
| ) | |
| self._safe_setattr(policy, "enabled", bool(spec.enabled)) | |
| self._safe_setattr(policy, "metadata", copy.deepcopy(spec.metadata)) | |
| self._safe_setattr(policy, "dsl_spec", spec) | |
| return policy | |
| def create_resource( | |
| self, | |
| spec: ResourceSpec, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> Resource: | |
| """Create a runtime ``Resource`` from a ``ResourceSpec``.""" | |
| metadata = copy.deepcopy(spec.metadata) | |
| metadata.setdefault("regeneration_rate", spec.regeneration_rate) | |
| metadata.setdefault("max_amount", spec.max_amount) | |
| runtime_position = self._resource_runtime_position( | |
| spec.position, | |
| dimensions=dimensions, | |
| ) | |
| params = { | |
| "id": spec.id, | |
| "type": spec.type, | |
| "amount": float(spec.amount), | |
| "position": runtime_position.copy(), | |
| "metadata": metadata, | |
| "regeneration_rate": float(spec.regeneration_rate), | |
| "max_amount": spec.max_amount, | |
| } | |
| resource = self._instantiate_runtime_object( | |
| self.config.resource_class, | |
| params, | |
| aliases=_CORE_RESOURCE_ALIASES, | |
| strict=False, | |
| path=f"resources.{spec.id}", | |
| item_kind="resource", | |
| ) | |
| self._safe_setattr(resource, "id", spec.id) | |
| self._safe_setattr(resource, "type", spec.type) | |
| self._safe_setattr(resource, "amount", float(spec.amount)) | |
| self._force_setattr(resource, "position", runtime_position.copy()) | |
| self._safe_setattr(resource, "metadata", metadata) | |
| self._safe_setattr(resource, "regeneration_rate", float(spec.regeneration_rate)) | |
| self._safe_setattr(resource, "max_amount", spec.max_amount) | |
| self._safe_setattr(resource, "dsl_spec", spec) | |
| self._ensure_resource_position_array(resource, dimensions=dimensions) | |
| return resource | |
| def create_event(self, spec: EventSpec) -> Event | GenericScheduledEvent: | |
| """Create a runtime event object from an ``EventSpec``.""" | |
| runtime_event_id = spec.event_key | |
| params = { | |
| "id": runtime_event_id, | |
| "name": spec.name, | |
| "trigger_step": int(spec.trigger_step), | |
| "payload": copy.deepcopy(spec.payload), | |
| "enabled": bool(spec.enabled), | |
| "repeat_interval": spec.repeat_interval, | |
| "target_agent_ids": tuple(spec.target_agent_ids), | |
| "target_resource_ids": tuple(spec.target_resource_ids), | |
| "metadata": copy.deepcopy(spec.metadata), | |
| } | |
| event_class = self.config.event_class | |
| use_fallback = inspect.isabstract(event_class) | |
| if use_fallback: | |
| event: Event | GenericScheduledEvent = GenericScheduledEvent(**params) | |
| else: | |
| try: | |
| event = self._instantiate_runtime_object( | |
| event_class, | |
| params, | |
| aliases=_CORE_EVENT_ALIASES, | |
| strict=False, | |
| path=f"events.{spec.event_key}", | |
| item_kind="event", | |
| ) | |
| except WorldFactoryError: | |
| logger.debug( | |
| "Falling back to GenericScheduledEvent for event %s", | |
| spec.event_key, | |
| exc_info=True, | |
| ) | |
| event = GenericScheduledEvent(**params) | |
| self._safe_setattr(event, "id", runtime_event_id) | |
| self._safe_setattr(event, "name", spec.name) | |
| self._safe_setattr(event, "trigger_step", int(spec.trigger_step)) | |
| self._safe_setattr(event, "payload", copy.deepcopy(spec.payload)) | |
| self._safe_setattr(event, "enabled", bool(spec.enabled)) | |
| self._safe_setattr(event, "repeat_interval", spec.repeat_interval) | |
| self._safe_setattr(event, "target_agent_ids", tuple(spec.target_agent_ids)) | |
| self._safe_setattr(event, "target_resource_ids", tuple(spec.target_resource_ids)) | |
| self._safe_setattr(event, "metadata", copy.deepcopy(spec.metadata)) | |
| self._safe_setattr(event, "dsl_spec", spec) | |
| self._ensure_event_id(event) | |
| return event | |
| def create_metric_config(self, spec: MetricSpec) -> dict[str, Any]: | |
| """Create a metric configuration record.""" | |
| return { | |
| "name": spec.name, | |
| "params": copy.deepcopy(spec.params), | |
| "enabled": bool(spec.enabled), | |
| "metadata": copy.deepcopy(spec.metadata), | |
| } | |
| def _create_world_object( | |
| self, | |
| *, | |
| spec: WorldSpec, | |
| agents: Sequence[Agent], | |
| resources: Sequence[Resource], | |
| events: Sequence[Event | GenericScheduledEvent], | |
| metrics: Sequence[Mapping[str, Any]], | |
| ) -> World: | |
| """Instantiate and populate the runtime world object.""" | |
| agent_collection = { | |
| str(getattr(agent, "id")): agent | |
| for agent in agents | |
| } | |
| resource_collection = { | |
| str(getattr(resource, "id")): resource | |
| for resource in resources | |
| } | |
| event_collection = { | |
| str(getattr(event, "id")): event | |
| for event in events | |
| if getattr(event, "id", None) is not None | |
| } | |
| metric_collection = { | |
| str(metric["name"]): copy.deepcopy(dict(metric)) | |
| for metric in metrics | |
| if "name" in metric | |
| } | |
| params = { | |
| "agents": agent_collection | |
| if self.config.world_uses_mapping_collections | |
| else tuple(agents), | |
| "resources": resource_collection | |
| if self.config.world_uses_mapping_collections | |
| else tuple(resources), | |
| "events": event_collection, | |
| "metrics": metric_collection, | |
| "step_count": 0, | |
| "metadata": copy.deepcopy(spec.metadata), | |
| } | |
| try: | |
| world = self._instantiate_runtime_object( | |
| self.config.world_class, | |
| params, | |
| aliases=_CORE_WORLD_ALIASES, | |
| strict=False, | |
| path=f"world:{spec.id}", | |
| item_kind="world", | |
| ) | |
| except WorldFactoryError: | |
| logger.debug("Falling back to no-argument World constructor", exc_info=True) | |
| world = self.config.world_class() | |
| self._attach_world_metadata(world, spec) | |
| self._install_agents(world, agents) | |
| self._install_resources(world, resources) | |
| self._install_events(world, events) | |
| self._safe_setattr(world, "metrics", metric_collection) | |
| self._safe_setattr(world, "step_count", 0) | |
| return world | |
| def _install_agents(self, world: World, agents: Sequence[Agent]) -> None: | |
| """Install agents into the world using adders when available.""" | |
| self._safe_setattr(world, "agents", {} if self.config.world_uses_mapping_collections else []) | |
| add_agent = getattr(world, "add_agent", None) | |
| if callable(add_agent): | |
| for agent in agents: | |
| self._ensure_agent_position_array(agent) | |
| add_agent(agent) | |
| return | |
| if self.config.world_uses_mapping_collections: | |
| for agent in agents: | |
| self._ensure_agent_position_array(agent) | |
| self._safe_setattr( | |
| world, | |
| "agents", | |
| {str(getattr(agent, "id")): agent for agent in agents}, | |
| ) | |
| else: | |
| for agent in agents: | |
| self._ensure_agent_position_array(agent) | |
| self._safe_setattr(world, "agents", list(agents)) | |
| def _install_resources(self, world: World, resources: Sequence[Resource]) -> None: | |
| """Install resources into the world using adders when available.""" | |
| self._safe_setattr(world, "resources", {} if self.config.world_uses_mapping_collections else []) | |
| add_resource = getattr(world, "add_resource", None) | |
| if callable(add_resource): | |
| for resource in resources: | |
| self._ensure_resource_position_array(resource) | |
| add_resource(resource) | |
| return | |
| if self.config.world_uses_mapping_collections: | |
| for resource in resources: | |
| self._ensure_resource_position_array(resource) | |
| self._safe_setattr( | |
| world, | |
| "resources", | |
| {str(getattr(resource, "id")): resource for resource in resources}, | |
| ) | |
| else: | |
| for resource in resources: | |
| self._ensure_resource_position_array(resource) | |
| self._safe_setattr(world, "resources", list(resources)) | |
| def _install_events( | |
| self, | |
| world: World, | |
| events: Sequence[Event | GenericScheduledEvent], | |
| ) -> None: | |
| """Install events into the world using adders when available. | |
| core.world.add_event may assign events by id: | |
| self.events[event.id] = event | |
| Therefore ``world.events`` must be mapping-like before add_event(). | |
| """ | |
| self._safe_setattr(world, "events", {}) | |
| add_event = getattr(world, "add_event", None) | |
| if callable(add_event): | |
| for event in events: | |
| self._ensure_event_id(event) | |
| add_event(event) | |
| return | |
| self._safe_setattr( | |
| world, | |
| "events", | |
| { | |
| str(getattr(event, "id")): event | |
| for event in events | |
| if getattr(event, "id", None) is not None | |
| }, | |
| ) | |
| def _attach_scheduler(self, world: World, spec: WorldSpec) -> None: | |
| """Attach a scheduler to the world when a scheduler class is configured.""" | |
| scheduler_class = self.config.scheduler_class | |
| if scheduler_class is None: | |
| return | |
| params = { | |
| "world": world, | |
| "activation": spec.simulation.activation, | |
| "scheduler": spec.simulation.scheduler, | |
| "seed": spec.simulation.seed, | |
| } | |
| try: | |
| scheduler = self._instantiate_runtime_object( | |
| scheduler_class, | |
| params, | |
| aliases=_CORE_SCHEDULER_ALIASES, | |
| strict=False, | |
| path=f"world:{spec.id}.scheduler", | |
| item_kind="scheduler", | |
| ) | |
| except WorldFactoryError: | |
| logger.debug("Falling back to no-argument Scheduler constructor", exc_info=True) | |
| scheduler = scheduler_class() | |
| self._safe_setattr(scheduler, "world", world) | |
| self._safe_setattr(scheduler, "activation", spec.simulation.activation) | |
| self._safe_setattr(scheduler, "scheduler", spec.simulation.scheduler) | |
| self._safe_setattr(scheduler, "seed", spec.simulation.seed) | |
| self._safe_setattr(world, "scheduler", scheduler) | |
| def _attach_world_metadata(self, world: World, spec: WorldSpec) -> None: | |
| """Attach DSL metadata and simulation configuration to the runtime world.""" | |
| self._safe_setattr(world, "id", spec.id) | |
| self._safe_setattr(world, "name", spec.name) | |
| self._safe_setattr(world, "description", spec.description) | |
| self._safe_setattr(world, "metadata", copy.deepcopy(spec.metadata)) | |
| self._safe_setattr(world, "simulation", spec.simulation) | |
| self._safe_setattr(world, "simulation_config", spec.simulation.to_dict()) | |
| self._safe_setattr(world, "space", spec.space) | |
| self._safe_setattr(world, "space_config", None if spec.space is None else spec.space.to_dict()) | |
| self._safe_setattr(world, "schema_version", spec.schema_version) | |
| if spec.space is not None: | |
| self._safe_setattr(world, "dimensions", int(spec.space.dimensions)) | |
| self._safe_setattr(world, "dimension", int(spec.space.dimensions)) | |
| self._safe_setattr(world, "space_dimensions", int(spec.space.dimensions)) | |
| if self.config.attach_dsl_spec_to_world: | |
| self._safe_setattr(world, "dsl_spec", spec) | |
| def _validate_before_build(self, spec: WorldSpec) -> ValidationReport | None: | |
| """Optionally run semantic validation before object construction.""" | |
| if not self.config.validate_before_build: | |
| return None | |
| validation_config = ValidationConfig( | |
| behavior_registry=self._behavior_registry, | |
| policy_registry=self._policy_registry, | |
| require_known_behaviors=self.config.strict_unknown_behaviors, | |
| require_known_policies=self.config.strict_unknown_policies, | |
| validate_constructor_params=self.config.strict_constructor_params, | |
| unknown_registry_item_severity=ValidationSeverity.ERROR | |
| if self.config.strict_unknown_behaviors or self.config.strict_unknown_policies | |
| else ValidationSeverity.WARNING, | |
| constructor_param_severity=ValidationSeverity.ERROR | |
| if self.config.strict_constructor_params | |
| else ValidationSeverity.WARNING, | |
| ) | |
| report = validate_world_spec(spec, config=validation_config) | |
| if not report.is_valid: | |
| raise WorldFactoryValidationError(report) | |
| return report | |
| def _coerce_world_spec(self, spec: WorldSpec | Mapping[str, Any]) -> WorldSpec: | |
| """Return a ``WorldSpec`` from a spec-like object.""" | |
| if isinstance(spec, WorldSpec): | |
| return spec | |
| if isinstance(spec, Mapping): | |
| return WorldSpec.model_validate(dict(spec)) | |
| raise TypeError(f"Expected WorldSpec or mapping, got {spec.__class__.__name__}") | |
| def _build_behavior_registry(self) -> dict[str, Any]: | |
| """Build the behavior registry from defaults and custom entries.""" | |
| registry: dict[str, Any] = {} | |
| module_report = self._load_module_registries( | |
| self.config.behavior_modules, | |
| BEHAVIOR_REGISTRY_ATTRIBUTES, | |
| base_class=Behavior, | |
| ) | |
| registry.update(module_report.registry) | |
| self._registry_import_errors.update(module_report.import_errors) | |
| if self.config.behavior_registry is not None: | |
| registry.update({ | |
| str(key): value | |
| for key, value in self.config.behavior_registry.items() | |
| }) | |
| return self._with_class_aliases(registry) | |
| def _build_policy_registry(self) -> dict[str, Any]: | |
| """Build the policy registry from defaults and custom entries.""" | |
| registry: dict[str, Any] = {} | |
| module_report = self._load_module_registries( | |
| self.config.policy_modules, | |
| POLICY_REGISTRY_ATTRIBUTES, | |
| base_class=BasePolicy, | |
| ) | |
| registry.update(module_report.registry) | |
| self._registry_import_errors.update(module_report.import_errors) | |
| if self.config.policy_registry is not None: | |
| registry.update({ | |
| str(key): value | |
| for key, value in self.config.policy_registry.items() | |
| }) | |
| return self._with_class_aliases(registry) | |
| def _load_module_registries( | |
| self, | |
| module_names: Sequence[str], | |
| registry_attributes: Sequence[str], | |
| *, | |
| base_class: type[Any], | |
| ) -> RegistryLoadReport: | |
| """Load registry mappings from modules and discover named subclasses.""" | |
| registry: dict[str, Any] = {} | |
| import_errors: dict[str, str] = {} | |
| for module_name in module_names: | |
| try: | |
| module = import_module(module_name) | |
| except Exception as exc: | |
| import_errors[str(module_name)] = f"{exc.__class__.__name__}: {exc}" | |
| continue | |
| for attribute_name in registry_attributes: | |
| raw_registry = getattr(module, attribute_name, None) | |
| if isinstance(raw_registry, Mapping): | |
| registry.update({ | |
| str(key): value | |
| for key, value in raw_registry.items() | |
| }) | |
| registry.update(self._discover_named_subclasses(module, base_class=base_class)) | |
| return RegistryLoadReport(registry=registry, import_errors=import_errors) | |
| def _discover_named_subclasses(module: Any, *, base_class: type[Any]) -> dict[str, Any]: | |
| """Discover subclasses with a string ``name`` attribute from a module.""" | |
| discovered: dict[str, Any] = {} | |
| for attribute_value in vars(module).values(): | |
| if not inspect.isclass(attribute_value): | |
| continue | |
| if attribute_value is base_class: | |
| continue | |
| try: | |
| is_subclass = issubclass(attribute_value, base_class) | |
| except TypeError: | |
| is_subclass = False | |
| if not is_subclass: | |
| continue | |
| name = getattr(attribute_value, "name", None) | |
| if isinstance(name, str) and name: | |
| discovered.setdefault(name, attribute_value) | |
| return discovered | |
| def _with_class_aliases(registry: Mapping[str, Any]) -> dict[str, Any]: | |
| """Add class-name and ``name`` aliases to registry entries.""" | |
| expanded = {str(key): value for key, value in registry.items()} | |
| for key, value in list(expanded.items()): | |
| class_name = getattr(value, "__name__", None) | |
| if class_name: | |
| expanded.setdefault(str(class_name), value) | |
| runtime_name = getattr(value, "name", None) | |
| if isinstance(runtime_name, str) and runtime_name: | |
| expanded.setdefault(runtime_name, value) | |
| expanded.setdefault(str(key).lower(), value) | |
| return expanded | |
| def _resolve_registry_item( | |
| self, | |
| *, | |
| registry: Mapping[str, Any], | |
| name: str, | |
| strict: bool, | |
| item_kind: str, | |
| path: str, | |
| ) -> Any: | |
| """Resolve a registry item by exact or case-insensitive name.""" | |
| if name in registry: | |
| return registry[name] | |
| lower_name = name.lower() | |
| if lower_name in registry: | |
| return registry[lower_name] | |
| for registered_name, item in registry.items(): | |
| if registered_name.lower() == lower_name: | |
| return item | |
| message = ( | |
| f"Unknown {item_kind} {name!r} at {path}. " | |
| f"Known {item_kind}s: {sorted(str(key) for key in registry.keys())}" | |
| ) | |
| if strict: | |
| raise RegistryResolutionError(message) | |
| logger.warning(message) | |
| if item_kind == "behavior": | |
| return self._noop_behavior_factory(name) | |
| return self._noop_policy_factory(name) | |
| def _instantiate_runtime_object( | |
| self, | |
| target: Any, | |
| params: Mapping[str, Any], | |
| *, | |
| aliases: Mapping[str, Sequence[str]], | |
| strict: bool, | |
| path: str, | |
| item_kind: str, | |
| ) -> Any: | |
| """Instantiate a runtime object using signature-aware keyword binding.""" | |
| if not callable(target): | |
| try: | |
| return copy.deepcopy(target) | |
| except Exception as exc: | |
| raise RuntimeObjectBuildError( | |
| f"Registered {item_kind} at {path} is not callable and could not be copied" | |
| ) from exc | |
| kwargs = self._bind_constructor_kwargs( | |
| target, | |
| params, | |
| aliases=aliases, | |
| strict=strict, | |
| path=path, | |
| item_kind=item_kind, | |
| ) | |
| try: | |
| return target(**kwargs) | |
| except TypeError as exc: | |
| raise ConstructorBindingError( | |
| f"Could not instantiate {item_kind} at {path}: {exc}" | |
| ) from exc | |
| except Exception as exc: | |
| raise RuntimeObjectBuildError( | |
| f"Constructor for {item_kind} at {path} failed: {exc}" | |
| ) from exc | |
| def _bind_constructor_kwargs( | |
| self, | |
| target: Any, | |
| params: Mapping[str, Any], | |
| *, | |
| aliases: Mapping[str, Sequence[str]], | |
| strict: bool, | |
| path: str, | |
| item_kind: str, | |
| ) -> dict[str, Any]: | |
| """Filter and alias params according to a target constructor signature.""" | |
| working = copy.deepcopy(dict(params)) | |
| signature_info = self._constructor_signature_info(target) | |
| if signature_info is None: | |
| return working | |
| allowed_params, required_params, accepts_var_keyword = signature_info | |
| if accepts_var_keyword: | |
| return working | |
| for canonical_name, alias_names in aliases.items(): | |
| if canonical_name not in working: | |
| continue | |
| if canonical_name in allowed_params: | |
| continue | |
| for alias_name in alias_names: | |
| if alias_name in allowed_params and alias_name not in working: | |
| working[alias_name] = working[canonical_name] | |
| break | |
| unknown_params = set(working.keys()) - allowed_params | |
| if unknown_params and strict: | |
| raise ConstructorBindingError( | |
| f"Unknown constructor parameter(s) for {item_kind} at {path}: " | |
| f"{sorted(unknown_params)}. Allowed parameters: {sorted(allowed_params)}" | |
| ) | |
| filtered = { | |
| key: value | |
| for key, value in working.items() | |
| if key in allowed_params | |
| } | |
| missing_required = required_params - set(filtered.keys()) | |
| if missing_required: | |
| raise ConstructorBindingError( | |
| f"Missing required constructor parameter(s) for {item_kind} at {path}: " | |
| f"{sorted(missing_required)}" | |
| ) | |
| return filtered | |
| def _constructor_signature_info(target: Any) -> tuple[set[str], set[str], bool] | None: | |
| """Return constructor binding information for a callable target.""" | |
| try: | |
| signature = inspect.signature(target) | |
| except (TypeError, ValueError): | |
| return None | |
| allowed_params: set[str] = set() | |
| required_params: set[str] = set() | |
| accepts_var_keyword = False | |
| for name, parameter in signature.parameters.items(): | |
| if name == "self": | |
| continue | |
| if parameter.kind is inspect.Parameter.VAR_KEYWORD: | |
| accepts_var_keyword = True | |
| continue | |
| if parameter.kind is inspect.Parameter.VAR_POSITIONAL: | |
| continue | |
| if parameter.kind not in { | |
| inspect.Parameter.POSITIONAL_OR_KEYWORD, | |
| inspect.Parameter.KEYWORD_ONLY, | |
| }: | |
| continue | |
| allowed_params.add(name) | |
| if parameter.default is inspect.Parameter.empty: | |
| required_params.add(name) | |
| return allowed_params, required_params, accepts_var_keyword | |
| def _behavior_runtime_id( | |
| spec: BehaviorSpec, | |
| *, | |
| owner_agent_id: str | None, | |
| behavior_index: int | None, | |
| ) -> str: | |
| """Return a deterministic runtime id for a behavior spec.""" | |
| metadata_id = None | |
| if isinstance(spec.metadata, Mapping): | |
| metadata_id = spec.metadata.get("id") or spec.metadata.get("behavior_id") | |
| if metadata_id is not None and str(metadata_id).strip(): | |
| return str(metadata_id).strip() | |
| owner = owner_agent_id or "world" | |
| index = 0 if behavior_index is None else int(behavior_index) | |
| return f"{owner}:behavior:{index}:{spec.name}" | |
| def _agent_runtime_position( | |
| position: Any, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> np.ndarray: | |
| """Return a NumPy position vector compatible with runtime agents.""" | |
| return WorldFactory._runtime_position( | |
| position, | |
| dimensions=dimensions, | |
| object_label="Agent", | |
| ) | |
| def _resource_runtime_position( | |
| position: Any, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> np.ndarray: | |
| """Return a NumPy position vector compatible with runtime resources.""" | |
| return WorldFactory._runtime_position( | |
| position, | |
| dimensions=dimensions, | |
| object_label="Resource", | |
| ) | |
| def _runtime_position( | |
| position: Any, | |
| *, | |
| dimensions: int | None = None, | |
| object_label: str = "Object", | |
| ) -> np.ndarray: | |
| """Return a NumPy runtime position vector.""" | |
| if position is None: | |
| target_dimensions = ( | |
| int(dimensions) | |
| if dimensions is not None and int(dimensions) > 0 | |
| else 2 | |
| ) | |
| return np.zeros(target_dimensions, dtype=np.float64) | |
| try: | |
| array = np.asarray(position, dtype=np.float64).reshape(-1) | |
| except (TypeError, ValueError) as exc: | |
| raise RuntimeObjectBuildError( | |
| f"{object_label} position must be numeric, got {position!r}" | |
| ) from exc | |
| if array.size == 0: | |
| target_dimensions = ( | |
| int(dimensions) | |
| if dimensions is not None and int(dimensions) > 0 | |
| else 2 | |
| ) | |
| return np.zeros(target_dimensions, dtype=np.float64) | |
| if not np.all(np.isfinite(array)): | |
| raise RuntimeObjectBuildError( | |
| f"{object_label} position must contain only finite values, got {position!r}" | |
| ) | |
| if dimensions is not None and int(dimensions) > 0: | |
| target_dimensions = int(dimensions) | |
| if int(array.size) != target_dimensions: | |
| raise RuntimeObjectBuildError( | |
| f"{object_label} position has {int(array.size)} dimension(s), " | |
| f"but world space expects {target_dimensions}" | |
| ) | |
| return array.astype(np.float64, copy=True) | |
| def _ensure_agent_position_array( | |
| self, | |
| agent: Agent, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> None: | |
| """Ensure agent.position is a NumPy array before adding it to World.""" | |
| raw_position = getattr(agent, "position", None) | |
| runtime_position = self._agent_runtime_position( | |
| raw_position, | |
| dimensions=dimensions, | |
| ) | |
| self._force_setattr(agent, "position", runtime_position.copy()) | |
| repaired_position = getattr(agent, "position", None) | |
| if not hasattr(repaired_position, "size"): | |
| raise RuntimeObjectBuildError( | |
| "Agent.position could not be converted to a NumPy array before " | |
| f"world installation. Agent={getattr(agent, 'id', None)!r}, " | |
| f"position_type={type(repaired_position).__name__}" | |
| ) | |
| def _ensure_resource_position_array( | |
| self, | |
| resource: Resource, | |
| *, | |
| dimensions: int | None = None, | |
| ) -> None: | |
| """Ensure resource.position is a NumPy array before adding it to World.""" | |
| raw_position = getattr(resource, "position", None) | |
| runtime_position = self._resource_runtime_position( | |
| raw_position, | |
| dimensions=dimensions, | |
| ) | |
| self._force_setattr(resource, "position", runtime_position.copy()) | |
| repaired_position = getattr(resource, "position", None) | |
| if not hasattr(repaired_position, "size"): | |
| raise RuntimeObjectBuildError( | |
| "Resource.position could not be converted to a NumPy array before " | |
| f"world installation. Resource={getattr(resource, 'id', None)!r}, " | |
| f"position_type={type(repaired_position).__name__}" | |
| ) | |
| def _ensure_event_id(self, event: Event | GenericScheduledEvent) -> None: | |
| """Ensure an event has a stable id before installation into World.""" | |
| event_id = getattr(event, "id", None) | |
| if event_id is not None and str(event_id).strip(): | |
| self._safe_setattr(event, "id", str(event_id).strip()) | |
| return | |
| name = getattr(event, "name", None) | |
| trigger_step = getattr(event, "trigger_step", 0) | |
| fallback_id = f"{name or 'event'}:{trigger_step}" | |
| self._safe_setattr(event, "id", fallback_id) | |
| def _safe_setattr(instance: Any, name: str, value: Any) -> bool: | |
| """Set an attribute defensively.""" | |
| try: | |
| setattr(instance, name, value) | |
| return True | |
| except Exception: | |
| logger.debug( | |
| "Could not set attribute %s on %s", | |
| name, | |
| instance.__class__.__name__, | |
| exc_info=True, | |
| ) | |
| return False | |
| def _force_setattr(instance: Any, name: str, value: Any) -> None: | |
| """Set an attribute, using object.__setattr__ as a fallback.""" | |
| try: | |
| setattr(instance, name, value) | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| object.__setattr__(instance, name, value) | |
| return | |
| except Exception as exc: | |
| raise RuntimeObjectBuildError( | |
| f"Could not set attribute {name!r} on {instance.__class__.__name__}" | |
| ) from exc | |
| def _noop_behavior_factory(name: str) -> type[Behavior]: | |
| """Create a no-op behavior class for permissive unknown behavior mode.""" | |
| class NoOpBehavior(Behavior): | |
| """Fallback behavior used only when unknown behaviors are permissive.""" | |
| behavior_name = name | |
| def check_preconditions(self, agent: Agent, world: World) -> bool: | |
| """Return False so unknown behavior never executes.""" | |
| del agent, world | |
| return False | |
| def execute(self, agent: Agent, world: World) -> dict[str, Any]: | |
| """Return a structured no-op result.""" | |
| del world | |
| return { | |
| "behavior": self.behavior_name, | |
| "actor_id": getattr(agent, "id", None), | |
| "success": False, | |
| "details": {"reason": "unknown_behavior_noop"}, | |
| } | |
| NoOpBehavior.name = name # type: ignore[attr-defined] | |
| return NoOpBehavior | |
| def _noop_policy_factory(name: str) -> type[BasePolicy]: | |
| """Create a no-op policy class for permissive unknown policy mode.""" | |
| class NoOpPolicy(BasePolicy): | |
| """Fallback policy used only when unknown policies are permissive.""" | |
| policy_name_value = name | |
| def choose_action( | |
| self, | |
| agent: Agent, | |
| world: World, | |
| behaviors: Sequence[Behavior] | Mapping[str, Behavior] | None = None, | |
| ) -> Behavior | None: | |
| """Choose no behavior.""" | |
| del agent, world, behaviors | |
| return None | |
| NoOpPolicy.name = name # type: ignore[attr-defined] | |
| return NoOpPolicy | |
| def create_world( | |
| spec: WorldSpec | Mapping[str, Any], | |
| *, | |
| config: WorldFactoryConfig | None = None, | |
| ) -> World: | |
| """Convenience function that creates a runtime world from a spec.""" | |
| return WorldFactory(config=config or WorldFactoryConfig()).create_world(spec) | |
| def create_world_result( | |
| spec: WorldSpec | Mapping[str, Any], | |
| *, | |
| config: WorldFactoryConfig | None = None, | |
| ) -> WorldBuildResult: | |
| """Convenience function that creates a world and returns its build report.""" | |
| return WorldFactory(config=config or WorldFactoryConfig()).create_world_result(spec) | |
| __all__ = [ | |
| "BEHAVIOR_REGISTRY_ATTRIBUTES", | |
| "DEFAULT_BEHAVIOR_MODULES", | |
| "DEFAULT_POLICY_MODULES", | |
| "GenericScheduledEvent", | |
| "ConstructorBindingError", | |
| "POLICY_REGISTRY_ATTRIBUTES", | |
| "RegistryLoadReport", | |
| "RegistryResolutionError", | |
| "RuntimeObjectBuildError", | |
| "WorldBuildReport", | |
| "WorldBuildResult", | |
| "WorldFactory", | |
| "WorldFactoryConfig", | |
| "WorldFactoryError", | |
| "WorldFactoryValidationError", | |
| "create_world", | |
| "create_world_result", | |
| ] |