API Reference: agent-core
The agent-core library provides foundational, world-agnostic classes and interfaces that form the backbone of all ARLA simulations. These abstractions ensure consistency and enable modular development across different simulation environments.
Package Overview
agent-core defines the contracts and base implementations that all ARLA simulations must follow. Think of it as the "constitution" for the ARLA ecosystem, establishing the fundamental patterns that enable modularity and extensibility.
Core ECS Classes
The Entity-Component-System pattern forms the architectural foundation of ARLA. These classes provide the essential building blocks for simulation state management and agent behavior.
SimulationState
The central hub for all simulation data, managing entities and their components with efficient queries and updates.
agent_core.core.ecs.abstractions.AbstractSimulationState
Bases: ABC
The abstract interface for the simulation's state container. This contract ensures that any system can interact with the state in a predictable way without knowing the concrete implementation.
event_bus: Optional[Any]
abstractmethod
property
Provides access to the simulation's event bus.
get_component(entity_id, component_type)
abstractmethod
Retrieves a component of a specific type for a given entity.
get_entities_with_components(component_types)
abstractmethod
Retrieves all entities that have a specific set of components.
Core Capabilities:
- Entity Management: Create, destroy, and query entities efficiently
- Component Storage: Type-safe component attachment and retrieval
- Batch Operations: Optimized queries for entities with specific component combinations
- State Persistence: Serialization support for checkpointing and analysis
Component Base Class
Pure data containers that define entity properties and state.
agent_core.core.ecs.component.Component
Bases: CognitiveComponent
Base class for all components with validation interface.
auto_fix(entity_id, config)
Attempts to automatically fix validation errors.
to_dict()
abstractmethod
Converts the component's data to a dictionary for serialization/logging.
validate(entity_id)
abstractmethod
Validates component state and returns (is_valid, error_list).
Design Principles:
- Components should contain only data, no business logic
- Implement
to_dict()for serialization and persistence - Include
validate()for data integrity checking - Use type hints for all attributes
Example Component:
class HealthComponent(Component):
"""Manages agent health and damage tracking."""
def __init__(self, max_health: int = 100):
self.max_health = max_health
self.current_health = max_health
self.last_damage_tick = 0
self.damage_history: List[int] = []
@property
def health_percentage(self) -> float:
"""Current health as percentage of maximum."""
return self.current_health / self.max_health
@property
def is_critical(self) -> bool:
"""True if health is below 25% of maximum."""
return self.health_percentage < 0.25
def to_dict(self) -> Dict[str, Any]:
return {
"max_health": self.max_health,
"current_health": self.current_health,
"last_damage_tick": self.last_damage_tick,
"damage_history": self.damage_history
}
def validate(self, entity_id: str) -> Tuple[bool, List[str]]:
errors = []
if self.current_health < 0:
errors.append("Health cannot be negative")
if self.max_health <= 0:
errors.append("Max health must be positive")
return len(errors) == 0, errors
Action Interfaces
Actions define the behaviors available to agents. The action system enables dynamic behavior generation and supports machine learning integration.
ActionInterface
The contract that all agent actions must implement, ensuring consistent integration with the simulation engine.
agent_core.agents.actions.action_interface.ActionInterface
Bases: ABC
Abstract Base Class for all actions. Any new action created for the simulation must inherit from this class and implement its abstract methods and properties.
action_id: str
abstractmethod
property
A unique string identifier for the action, e.g., 'move', 'extract_resources'.
name: str
abstractmethod
property
A human-readable name for the action, e.g., 'Move'.
execute(entity_id, simulation_state, params, current_tick)
abstractmethod
Executes the action's logic and modifies the simulation state.
generate_possible_params(entity_id, simulation_state, current_tick)
abstractmethod
Generates all possible valid parameter combinations for this action for a given entity.
get_base_cost(simulation_state)
abstractmethod
The base time budget cost to perform the action.
get_feature_vector(entity_id, simulation_state, params)
abstractmethod
Generates the feature vector for this specific action variant.
Action Lifecycle:
graph LR
A[Discovery] --> B[Parameter Generation]
B --> C[Action Selection]
C --> D[Execution]
D --> E[System Processing]
E --> F[Outcome Calculation]
- Discovery:
@action_registry.registermakes actions available - Parameter Generation:
generate_possible_params()creates action variants - Selection: Decision systems choose from available actions
- Execution:
execute()returns outcome, systems handle state changes - Learning:
get_feature_vector()enables ML model training
Implementation Guidelines:
- Keep
execute()lightweight - delegate actual work to Systems - Generate parameters based on current world state
- Return meaningful ActionOutcome messages for debugging
- Design feature vectors for your learning algorithms
Provider Interfaces
Provider interfaces enable world-agnostic systems to access world-specific data through dependency injection, maintaining the separation between engine and simulation logic.
Core Provider Pattern
The provider pattern bridges the gap between world-agnostic cognitive systems and world-specific data:
class VitalityMetricsProvider(VitalityMetricsProviderInterface):
"""Bridges health/energy systems with cognitive architecture."""
def get_normalized_vitality_metrics(
self,
entity_id: str,
components: Dict[Type[Component], Component],
config: Dict[str, Any]
) -> Dict[str, float]:
health_comp = components.get(HealthComponent)
energy_comp = components.get(EnergyComponent)
if not health_comp or not energy_comp:
return {"health_norm": 0.5, "energy_norm": 0.5, "fatigue_norm": 0.5}
return {
"health_norm": health_comp.current / health_comp.max_health,
"energy_norm": energy_comp.current / energy_comp.max_energy,
"fatigue_norm": 1.0 - (energy_comp.current / energy_comp.max_energy)
}
Available Provider Interfaces:
-
VitalityMetricsProvider
Provides normalized health and energy data for cognitive systems.
-
NarrativeContextProvider
Supplies contextual information for LLM-based reflection and planning.
-
StateEncoderProvider
Encodes simulation state into feature vectors for machine learning.
-
MemoryAccessProvider
Manages filtered access to agent memories and experiences.
System Base Classes
Foundation classes for implementing simulation logic and cognitive systems.
System Base Class
The parent class for all simulation systems, providing event bus access and lifecycle management.
agent_engine.simulation.system.System
Bases: CognitiveSystem, SystemProtocol
Abstract base class for all systems in the engine. It now directly inherits the asynchronous contract from CognitiveSystem.
update(current_tick)
abstractmethod
async
Processes the system's logic for the current simulation tick. The system is responsible for fetching the entities it needs to operate on from self.simulation_state, typically using self.REQUIRED_COMPONENTS.
System Development Pattern:
class ExampleSystem(System):
"""Example system demonstrating best practices."""
def __init__(self, simulation_state, config, cognitive_scaffold):
super().__init__(simulation_state, config, cognitive_scaffold)
# Subscribe to relevant events
if self.event_bus:
self.event_bus.subscribe("target_event", self.handle_event)
self.event_bus.subscribe("tick_completed", self.on_tick_complete)
def handle_event(self, event_data: Dict[str, Any]) -> None:
"""Process specific events with proper error handling."""
try:
entity_id = event_data.get("entity_id")
if not entity_id:
return
# Process event logic here
self._process_event_logic(entity_id, event_data)
except Exception as e:
print(f"Error handling event in {self.__class__.__name__}: {e}")
async def update(self, current_tick: int) -> None:
"""Main system update loop called each simulation tick."""
# Periodic processing logic here
entities = self.simulation_state.get_entities_with_components([
RequiredComponent
])
for entity_id, components in entities.items():
await self._process_entity(entity_id, components, current_tick)
def _process_entity(self, entity_id: str, components: Dict, tick: int):
"""Process individual entity - separate method for testing."""
pass
Configuration Management
ARLA uses Pydantic for type-safe, validated configuration management across all simulations.
Configuration Schema Pattern
from pydantic import BaseModel, Field
from typing import Optional, List
class AgentConfig(BaseModel):
"""Configuration for agent behavior and properties."""
max_health: int = Field(default=100, gt=0, description="Maximum health points")
learning_rate: float = Field(default=0.01, gt=0, le=1, description="Learning rate for Q-learning")
memory_capacity: int = Field(default=1000, gt=0, description="Size of agent memory buffer")
class EnvironmentConfig(BaseModel):
"""Configuration for world environment settings."""
grid_size: tuple[int, int] = Field(default=(50, 50), description="World dimensions")
resource_spawn_rate: float = Field(default=0.02, ge=0, le=1, description="Resource spawn probability per tick")
class SimulationConfig(BaseModel):
"""Top-level simulation configuration."""
agent: AgentConfig = Field(default_factory=AgentConfig)
environment: EnvironmentConfig = Field(default_factory=EnvironmentConfig)
max_ticks: int = Field(default=1000, gt=0, description="Maximum simulation duration")
random_seed: Optional[int] = Field(default=42, description="Random seed for reproducibility")
debug_mode: bool = Field(default=False, description="Enable debug logging")
Configuration Benefits:
- Type Safety: Catch configuration errors at startup
- Validation: Automatic constraint checking with clear error messages
- Documentation: Self-documenting configuration schemas
- IDE Support: Autocompletion and type checking during development
Migration Guide
From Dictionary-Based Configuration
Before:
# Fragile dictionary access with no validation
health = config.get("agent", {}).get("health", {}).get("max", 100)
learning_rate = config.get("learning", {}).get("rate", 0.01)
After:
# Type-safe access with IDE support
health = config.agent.max_health
learning_rate = config.agent.learning_rate
Component Design Best Practices
Avoid Logic in Components:
class BadComponent(Component):
def update_health(self, damage): # Logic belongs in Systems!
self.health -= damage
if self.health <= 0:
self.trigger_death() # Side effects in components are bad
Prefer Pure Data:
class GoodComponent(Component):
def __init__(self, max_health: int = 100):
self.max_health = max_health
self.current_health = max_health # Just data storage
self.damage_taken = 0
# Properties for computed values are acceptable
@property
def is_alive(self) -> bool:
return self.current_health > 0
Performance Considerations
Component Queries
- Use
get_entities_with_components()for bulk operations - Cache component references when processing multiple entities
- Prefer specific component queries over entity iteration
# Efficient bulk processing
entities = simulation_state.get_entities_with_components([
HealthComponent, PositionComponent
])
for entity_id, components in entities.items():
health_comp = components[HealthComponent]
pos_comp = components[PositionComponent]
# Process with cached references
Memory Management
- Components are lightweight - favor composition over inheritance
- Use
__slots__for frequently instantiated components - Clean up references in component destructors
class OptimizedComponent(Component):
__slots__ = ['x', 'y', 'timestamp'] # Reduces memory overhead
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.timestamp = time.time()
Event Bus Usage
- Subscribe to specific events, not broad categories
- Unsubscribe systems that are no longer needed
- Use event data efficiently to avoid serialization overhead
# Efficient event handling
def __init__(self, simulation_state, config, cognitive_scaffold):
super().__init__(simulation_state, config, cognitive_scaffold)
# Subscribe to specific, relevant events only
if self.event_bus:
self.event_bus.subscribe("agent_death", self.handle_death)
self.event_bus.subscribe("resource_depleted", self.handle_depletion)
# Avoid subscribing to "all_events" or overly broad categories