Skip to main content

Agent Types

2.3 Agent Types​

The SDK provides five agent types that span a decision-making spectrum from deterministic rules to probabilistic LLM reasoning. The choice of agent type is a modelling decision: it determines how the agent makes decisions, what infrastructure it requires, and what its computational cost is.

Rule-Based Agent (Agent)​

The base Agent class. Decisions are implemented as pure Python methods. Deterministic given the same seed, fastest to execute (microseconds per agent per step), and fully transparent --- every decision is traceable through the code. Used for 90%+ of agents in most models.

LLM Agent (LLMAgent)​

Agents that delegate decisions to a large language model. Every LLMAgent subclass must implement rule_decide() as a mandatory fallback --- this ensures the model can still run if the LLM API is unavailable, over budget, or in testing mode.

class LLMAgent(Agent):
__behavior_type__ = BehaviorType.LLM_BASED

def __init__(self, agent_id: str, model: Optional[ABMModel] = None,
llm_config: Optional[LLMConfig] = None,
response_cache: Optional[ResponseCache] = None) -> None

def llm_decide(self, prompt: str, context: Dict[str, Any]) -> Dict[str, Any]
@abstractmethod
def rule_decide(self, context: Dict[str, Any]) -> Dict[str, Any]

@classmethod
def set_backend(cls, backend: LLMBackend) -> None
@classmethod
def clear_backend(cls) -> None

@property
def llm_call_count(self) -> int
@property
def llm_cost_usd(self) -> float
@property
def llm_fallback_count(self) -> int

The LLMConfig dataclass controls LLM behaviour:

FieldTypeDefaultDescription
modelstr"claude-sonnet-4-6"LLM model identifier
max_tokensint200Maximum response tokens
temperaturefloat0.0Sampling temperature (0 = deterministic)
cache_identical_promptsboolTrueEnable response caching
cost_budget_per_stepfloat0.01Max USD spend per step
cost_budget_totalfloat1.0Max USD spend total

Three backend implementations are provided: MockLLMBackend (deterministic, no API calls --- for testing), DebugLLMBackend (real API calls with logging --- for development), and LLMBackendPool (load-balanced multi-endpoint --- for production).

Hybrid Agent (HybridAgent)​

A subclass of LLMAgent that probabilistically routes decisions between the LLM and the rule-based fallback. The llm_probability attribute (default 1.0) controls the fraction of decisions routed to the LLM, using the agent's deterministic PRNG for the coin flip. This enables cost control: setting llm_probability=0.3 means 70% of decisions use cheap rules and 30% use the expensive LLM.

class HybridAgent(LLMAgent):
__behavior_type__ = BehaviorType.HYBRID

def __init__(self, agent_id: str, model: Optional[ABMModel] = None,
llm_config: Optional[LLMConfig] = None,
llm_probability: float = 1.0) -> None

RL Agent (RLAgent)​

Agents that learn optimal policies through reinforcement learning. The RLAgent base class defines three abstract methods that subclasses must implement:

class RLAgent(Agent):
def __init__(self, agent_id: str, model: Optional[Any] = None,
policy: Optional[Any] = None) -> None

@abstractmethod
def observe(self) -> np.ndarray # Current observation vector
@abstractmethod
def compute_reward(self) -> float # Reward for last action
@abstractmethod
def apply_action(self, action: np.ndarray) -> None # Execute action

def decide(self) -> np.ndarray # Policy inference

Two built-in policies are provided: RandomPolicy (uniform random actions --- for baselines) and TabularQPolicy (Q-learning with epsilon-greedy exploration --- for discrete action spaces).

External Agent Proxy (ExternalAgentProxy)​

Agents whose decisions come from an external system via HTTP or MCP. The proxy sends an ExternalDecideRequest (containing agent state, inbox, globals, and neighbour list) to the endpoint and receives an ExternalDecideResponse (containing the action and outgoing messages). If the endpoint is unreachable, the optional fallback callable is invoked instead.

class ExternalAgentProxy(Agent):
def __init__(self, agent_id: str, model: Optional[ABMModel] = None,
endpoint: Optional[str] = None, timeout: float = 30.0,
fallback: Optional[Callable] = None) -> None

def set_fallback(self, fallback: Callable[[], Dict[str, Any]]) -> None
def set_mock_server(self, mock_server: MockExternalServer) -> None
def decide(self) -> Dict[str, Any]