AgentFarm Perception System Overview
Executive Summary
The AgentFarm perception system implements a sophisticated multi-agent observation framework that provides agents with local, agent-centric views of their environment. The system operates through a single, unified perception component that balances memory efficiency, computational performance, and neural network compatibility while enabling scalable multi-agent reinforcement learning simulations.
The perception system operates through a single observation path via the PerceptionComponent, providing consistent multi-channel perception across all agents.
Table of Contents
- System Overview
- Key Principles
- Core Components
- System Architecture
- Configuration
- Performance Characteristics
- Integration & Usage
- References & Technical Details
System Overview
Mission & Requirements
The perception system provides:
- Real-time observation generation for hundreds to thousands of agents
- Multi-channel environmental awareness with temporal persistence
- Neural network compatibility with existing RL frameworks
- Scalable memory usage that grows linearly with agent count
- Efficient spatial queries for proximity-based perception
Core Innovation: Hybrid Architecture
The system combines:
- Sparse internal storage for memory efficiency
- Lazy dense construction for computational performance, with optional prebuilding of high-frequency channels (
high_frequency_channels) for fast O(1) copies during dense builds - Channel-specific optimization strategies (including prebuilt high-frequency channels and vectorized sparse population)
- Spatial indexing for efficient proximity queries
Key Principles
Local, Agent-Centric Perception
Local Perspective Fields: Each agent receives a small window (configurable) centered on their position, creating subjective, limited experiences rather than global knowledge.
Orientation Alignment: The agent’s facing direction is aligned to “up” in the perception grid based on each agent’s orientation (degrees, 0=north). World layers and entities are rotated accordingly for agent-centric perception. Default orientation=0 preserves legacy behavior.
Limited Field of View: Agents can only “see” within a certain radius, with areas beyond this masked as unknown, simulating partial observability.
Multi-Channel Observations
The system uses 13 default channels (indices 0-12) that can be extended:
Entity Channels (Sparse storage, INSTANT behavior):
SELF_HP(Index 0) - Agent’s current health at center positionALLIES_HP(Index 1) - Visible allies’ health at their positionsENEMIES_HP(Index 2) - Visible enemies’ health at their positions
Environmental Channels (Dense storage, INSTANT behavior):
RESOURCES(Index 3) - Resource distribution with bilinear interpolationOBSTACLES(Index 4) - Obstacle/passability mapTERRAIN_COST(Index 5) - Movement cost terrainVISIBILITY(Index 6) - Field-of-view visibility mask
Temporal Channels (Sparse with decay, DYNAMIC behavior):
KNOWN_EMPTY(Index 7) - Previously observed empty cells (decays over time)DAMAGE_HEAT(Index 8) - Recent damage events (decays over time)TRAILS(Index 9) - Agent movement trails (decays over time)ALLY_SIGNAL(Index 10) - Communication signals (decays over time)
Navigation Channels:
GOAL(Index 11) - Target waypoint positions (INSTANT behavior)LANDMARKS(Index 12) - Permanent reference points (PERSISTENT behavior)
Spatial Reasoning & Efficiency
Dual Spatial Indexing: The system uses KD-tree, Quadtree, and Spatial Hash indexing strategies for optimal performance across different query patterns:
- KD-Tree Indexing: scipy.spatial.cKDTree for O(log n) radial and nearest-neighbor queries
- Quadtree Indexing: Hierarchical partitioning for efficient rectangular range queries and dynamic updates
- Spatial Hash Grid: Uniform grid buckets for fast neighbor queries and dynamic updates
Smart Index Selection: The system automatically chooses the most efficient indexing strategy based on query type and entity movement patterns.
Key Features:
- Multi-level optimization: Dirty flag check (O(1)) → Count check (O(1)) → Hash verification (O(n))
- Named index system: Configurable indices for different entity types (agents, resources, etc.)
- Automatic filtering: Built-in support for filtering (e.g., only alive agents)
- Position validation: Relaxed bounds checking with 1% margin for edge cases
Query Types:
get_nearby(position, radius, index_names)- Radius-based entity queries across multiple indicesget_nearest(position, index_names)- Single nearest entity lookup per index- Coordinate transformation from world → local observation space
Channel Behaviors
INSTANT: Completely overwritten each simulation step with fresh data. Used for current state information that doesn’t need to persist between ticks.
DYNAMIC: Persist across ticks and decay over time using configurable gamma factors. Values gradually fade away, simulating the natural decay of transient information.
PERSISTENT: Remain unchanged until explicitly cleared. Used for long-term memory and permanent environment features that should accumulate over time.
Core Components
1. AgentObservation Class
Purpose: Central observation management with hybrid storage strategy
Key Features:
- Hybrid sparse/dense tensor management
- Lazy dense construction on-demand
- Channel-specific storage optimization
- Temporal decay with automatic cleanup
- Backward compatibility with existing RL code
Memory Optimization:
- Sparse storage: ~2-3 KB per agent average
- Lazy dense: ~6-24 KB built when needed
- ~60% memory reduction vs pure dense approach
2. Channel System
Purpose: Extensible multi-channel observation framework
Architecture:
- Dynamic channel registration system
- Handler-based processing with inheritance
- Three behavioral types: INSTANT, DYNAMIC, PERSISTENT
- Type-safe channel indexing and lookup
3. Spatial Index Integration
Purpose: Efficient proximity queries and spatial reasoning with smart change detection
Technical Implementation:
- KD-tree based spatial indexing using scipy.spatial.cKDTree
- Smart change detection with three-level optimization strategy:
- Dirty flag check (O(1)) for no-change scenarios
- Count-based check (O(1)) for structural changes
- Hash-based verification (O(n)) for position changes
- Named index system supporting configurable entity types with custom filters
- Automatic position validation with relaxed bounds checking
- Memory-efficient caching with position hash tracking
System Architecture
Single Observation Path
The perception system provides a single, unified path for observation generation:
AgentCore.step()
→ AgentCore._create_observation()
→ PerceptionComponent.get_observation_tensor()
→ AgentObservation.perceive_world() [with full multi-channel system]
→ DecisionModule.decide_action()
Architecture Components
1. Unified Perception Component
- All perception logic integrated into
PerceptionComponent - Direct integration with
AgentObservationsystem - Single source of truth for observation generation
- Consistent error handling and logging
2. Single Observation Path
- Single observation path through agent’s perception component
- Consistent multi-channel observation generation
- Improved maintainability and debugging
3. Enhanced Error Handling
- Specific error logging instead of silent failures
- Graceful fallback to simple perception grid
- Better debugging and monitoring capabilities
- Robust error recovery
4. Full Multi-Channel Integration
- Complete
AgentObservationsystem integration - All 13+ observation channels available
- Sparse/dense hybrid storage
- Bilinear interpolation support
- Spatial indexing integration
System Benefits
- Consistency: All agents use identical observation generation
- Maintainability: Single location for perception logic
- Performance: Efficient computation without redundancy
- Debugging: Clear error handling and logging
- Extensibility: Easy to add new observation channels
- Reliability: Robust error handling and fallbacks
Configuration
Basic Configuration
from farm.config import SimulationConfig
from farm.core.observations import ObservationConfig
# Create observation configuration
obs_config = ObservationConfig(
R=6, # Observation radius (cells visible in each direction)
fov_radius=6, # Field-of-view radius for visibility mask (default: 6)
gamma_trail=0.90, # Decay rate for movement trails (default: 0.90)
gamma_dmg=0.85, # Decay rate for damage heat (default: 0.85)
gamma_sig=0.92, # Decay rate for ally signals (default: 0.92)
gamma_known=0.98, # Decay rate for known empty cells (default: 0.98)
device="cpu", # Device for tensor operations (default: "cpu")
dtype="float32", # PyTorch dtype as string (default: "float32")
initialization="zeros", # Tensor initialization method (default: "zeros")
high_frequency_channels=["RESOURCES", "VISIBILITY"] # Optional optimization
)
# Attach to simulation configuration (sizes live on environment.*)
config = SimulationConfig.from_centralized_config(environment="development")
config.observation = obs_config
config.environment.width = 100
config.environment.height = 100
All 13 default channels are always included. Custom channels can be added through the dynamic channel registry system.
Advanced Configuration
# Advanced ObservationConfig with custom settings
obs_config = ObservationConfig(
R=8, # Larger observation radius
fov_radius=7, # Larger field-of-view
device="cuda", # Use GPU if available
dtype="float16", # Use half precision for memory efficiency
initialization="random", # Random initialization instead of zeros
random_min=-0.1, # Random initialization range
random_max=0.1,
# Custom gamma factors for different decay rates
gamma_trail=0.95, # Slower decay for movement trails
gamma_dmg=0.90, # Moderate decay for damage heat
gamma_sig=0.85, # Faster decay for communication signals
gamma_known=0.98 # Very slow decay for known empty areas
)
# Alternative: Create with defaults and modify
obs_config = ObservationConfig() # Use all defaults
obs_config.R = 10 # Larger observation radius
obs_config.device = "cuda" # Switch to GPU
obs_config.gamma_trail = 0.95 # Custom trail decay rate
YAML Configuration
# config/default.yaml - Observation settings
observation:
R: 6 # Observation radius
fov_radius: 6 # Field-of-view radius
gamma_trail: 0.90 # Trail decay rate
gamma_dmg: 0.85 # Damage heat decay rate
gamma_sig: 0.92 # Signal decay rate
gamma_known: 0.98 # Known empty decay rate
device: "cpu" # Device for tensor operations
dtype: "float32" # PyTorch dtype
initialization: "zeros" # Initialization method
random_min: 0.0 # Random init minimum
random_max: 1.0 # Random init maximum
# Load from centralized YAML (see docs/reference/config/)
from farm.config import SimulationConfig
config = SimulationConfig.from_centralized_config(environment="development")
Performance Characteristics
Memory Performance
| Component | Radius 5 | Radius 8 | Radius 10 |
|---|---|---|---|
| Observation Tensor | 6,292 bytes | 15,044 bytes | 23,508 bytes |
| Channel Overhead | 256 bytes | 512 bytes | 768 bytes |
| Spatial Index | 2,048 bytes | 2,048 bytes | 2,048 bytes |
| Total per Agent | 8,636 bytes | 17,604 bytes | 26,324 bytes |
Computational Performance
| Operation | Dense Complexity | Sparse Complexity | Notes |
|---|---|---|---|
| Storage | O(1) | O(1) | Dictionary lookup |
| Retrieval | O(1) | O(1) | Cached construction |
| Decay | O(grid_size) | O(active_elements) | Sparse much faster |
| NN Processing | O(grid_size) | O(grid_size) | Same dense tensor |
| Dense Build (hybrid) | — | — | Prebuilt channels copied O(S²), other sparse channels vectorized |
Scalability Analysis
| Agent Count | Dense Memory | Sparse Memory | Savings |
|---|---|---|---|
| 100 agents | 864 KB | 1.7 MB | 864 KB (50%) |
| 1,000 agents | 8.6 MB | 17.6 MB | 9 MB (51%) |
| 10,000 agents | 86.4 MB | 176 MB | 89.6 MB (51%) |
Benchmarking and Metrics (Comprehensive Analysis)
We conducted comprehensive benchmarks across varying agent counts, observation radii, storage modes, and interpolation methods to measure perception throughput, memory efficiency, and computational performance.
Test Configurations
Scale Testing:
- Agent counts: 100, 1,000, 10,000
- Observation radii: 5, 8, 10
- Storage modes: hybrid (sparse + lazy dense), dense
- Interpolation: bilinear, nearest-neighbor
- Steps per run: 5-20
- Device: CPU (GPU support available)
Performance Results
Memory Efficiency:
- Dense bytes per agent (R=5): 6,292 bytes
- Dense bytes per agent (R=8): 15,028 bytes
- Dense bytes per agent (R=10): 23,492 bytes
- Sparse bytes per agent: ~15% of dense (943-3,524 bytes)
- Memory reduction: 85.0% across all configurations
Throughput Analysis:
- 100 agents, R=5: 0.03-0.06ms step time
- 1,000 agents, R=5: 0.31-0.39ms step time
- 1,000 agents, R=8: 0.36-0.62ms step time
- Scaling: Linear with agent count, quadratic with radius
Interpolation Performance:
- Nearest-neighbor: Consistently 2x faster than bilinear
- Bilinear: Higher computational cost but preserves continuous positions
- GFLOPS range: 0.008-0.221 (scales with radius and agent count)
Storage Mode Comparison:
- Hybrid vs Dense: Minimal performance difference
- Cache behavior: Hybrid provides 85% memory reduction with negligible overhead
- Rebuild cost: Dense reconstruction adds <1ms per agent per step
Memory Estimation Formulas
Dense Memory:
dense_bytes_per_agent = channels × (2R+1)² × sizeof(dtype)
Sparse Memory:
sparse_bytes_per_agent ≈ num_active_points × (sizeof(value) + sizeof(y) + sizeof(x))
Memory Reduction:
reduction_percent = (1 - sparse_bytes / dense_bytes) × 100
Computational Analysis
GFLOPS Estimation:
gflops = (total_cells × operations_per_cell × steps) / 1e9 / total_time
Per-Agent Update Time:
update_time_ms = (total_time / (steps × agents)) × 1000
Cache Hit Rate:
hit_rate = hits / (hits + misses)
Key Performance Insights
- Memory Efficiency: Consistent 85% reduction with sparse storage across all scales
- Interpolation Choice: Nearest-neighbor provides 2x speedup for applications that can tolerate discrete positions
- Storage Mode: Hybrid storage provides memory benefits with minimal computational overhead
- Scaling: System scales linearly with agent count, making it suitable for large-scale simulations
- Radius Impact: Memory and computation scale quadratically with observation radius
Hardware Comparisons
CPU Performance (Intel/AMD):
- 100 agents: <1ms per step
- 1,000 agents: <1ms per step
- 10,000 agents: <10ms per step
GPU Acceleration (RTX 3090):
- Set
ObservationConfig.device="cuda"for GPU acceleration - Expected 5-10x speedup for large agent counts
- Memory bandwidth becomes limiting factor for very large simulations
Baseline Comparisons
vs Pure Dense Approaches:
- 85% memory reduction with hybrid storage
- Minimal computational overhead
- Better cache locality for sparse environments
vs Unity ML-Agents:
- Comparable throughput for similar agent counts
- Superior memory efficiency for sparse observations
- More flexible channel system
Integration & Usage
Basic Usage
from farm.config import SimulationConfig
from farm.core.environment import Environment
from farm.core.observations import ObservationConfig
# Create observation configuration
obs_config = ObservationConfig(
R=6, # Observation radius
fov_radius=6, # Field of view radius (default: 6)
gamma_trail=0.90, # Trail decay rate
gamma_dmg=0.85, # Damage heat decay rate
gamma_sig=0.92, # Signal decay rate
gamma_known=0.98 # Known empty decay rate
)
config = SimulationConfig.from_centralized_config(environment="development")
config.observation = obs_config
config.environment.width = 100
config.environment.height = 100
env = Environment(
width=config.environment.width,
height=config.environment.height,
resource_distribution="uniform",
config=config,
)
# Environment automatically includes spatial indexing and observation system
Direct AgentObservation Usage
from farm.core.observations import AgentObservation, ObservationConfig
# Create observation configuration
config = ObservationConfig(R=6, fov_radius=5)
# Create agent observation instance
agent_obs = AgentObservation(config)
# Update observation with world state
agent_obs.perceive_world(
world_layers={
"RESOURCES": resource_grid,
"OBSTACLES": obstacle_grid,
"TERRAIN_COST": terrain_grid
},
agent_world_pos=(50, 50),
self_hp01=0.8,
allies=[(48, 50, 0.9), (52, 50, 0.7)],
enemies=[(45, 45, 0.6)],
goal_world_pos=(60, 60),
spatial_index=env.spatial_index,
agent_object=agent
)
# Get observation tensor for neural network input
observation_tensor = agent_obs.tensor() # Shape: (13, 13, 13)
Channel Registration
from farm.core.channels import ChannelHandler, ChannelBehavior, register_channel
class CustomChannel(ChannelHandler):
def __init__(self):
super().__init__("CUSTOM_CHANNEL", ChannelBehavior.DYNAMIC, gamma=0.95)
def process(self, observation, channel_idx, config, agent_world_pos, **kwargs):
# Implement your channel processing logic
pass
# Register the channel
channel_idx = register_channel(CustomChannel())
Spatial Queries
# Get nearby entities using spatial index
nearby_entities = env.spatial_index.get_nearby(
position=agent.position,
radius=5.0,
index_names=["agents", "resources"]
)
# Extract specific entity types
nearby_agents = nearby_entities.get("agents", [])
nearby_resources = nearby_entities.get("resources", [])
# Get nearest entities across multiple indices
nearest_entities = env.spatial_index.get_nearest(
position=agent.position,
index_names=["agents", "resources"]
)
nearest_agent = nearest_entities.get("agents")
nearest_resource = nearest_entities.get("resources")
# Process nearby entities (agents are pre-filtered to alive only)
for agent_obj in nearby_agents:
# Calculate distance for observation processing
distance = ((agent.position[0] - agent_obj.position[0])**2 +
(agent.position[1] - agent_obj.position[1])**2)**0.5
# Update observation channels based on proximity
if distance <= observation_radius:
# Process agent for observation update
pass
# Register custom indices for specialized queries
env.spatial_index.register_index(
name="enemies",
data_reference=env.agents,
position_getter=lambda a: a.position,
filter_func=lambda a: a.team != agent.team and a.alive
)
# Query custom indices
nearby_enemies = env.spatial_index.get_nearby(
position=agent.position,
radius=10.0,
index_names=["enemies"]
)
References & Technical Details
📖 Detailed Technical Documentation
For in-depth technical information, refer to:
Design & Architecture:
- Perception System Design - Complete technical implementation details
- Architecture - System integration and architecture patterns
- Dynamic Channel System - Channel system implementation details
Performance & Optimization:
- Observation channels - Channel layout and sizing considerations
- Memmap optimization - Memory-mapped / footprint-related tuning
- Configuration Guide - Complete configuration reference
Integration & Usage:
- API Reference - Complete API documentation
- Usage Examples - Practical implementation examples
🔧 Implementation Files
Key implementation modules:
farm/core/observations.py- Core observation systemfarm/core/channels.py- Channel system implementationfarm/core/spatial_index.py- Spatial indexing implementationfarm/core/environment.py- Environment integration
⚙️ Configuration Examples
See Configuration Guide for:
- Complete configuration options
- Performance tuning recommendations
- Environment-specific settings
📊 Performance Analysis
For detailed performance characteristics:
- Memory usage patterns
- Computational bottlenecks
- Scaling recommendations
- Optimization strategies
Conclusion
The AgentFarm perception system provides a sophisticated foundation for realistic agent perception in multi-agent simulations. By combining efficient spatial indexing with a flexible channel system and hybrid storage strategies, it enables scalable, memory-efficient observation generation that supports both simple scripted agents and complex reinforcement learning systems.
The system’s design emphasizes local, subjective experiences over global knowledge, creating more realistic and adaptable agent behavior while maintaining the computational efficiency required for large-scale simulations.