AgentMap Agent Contract
This document defines the interface and behavior that all agents in the AgentMap ecosystem must follow. This contract ensures extensibility and configurability through a clean architecture pattern with protocol-based dependency injection.
Related Documentation:
- Service Injection - Complete guide to the dependency injection system
- Advanced Agent Types - Context configuration reference and advanced patterns
- AgentMap Agent Types - Built-in agent types and basic usage
Architecture Overview
AgentMap uses a modern clean architecture with clear separation between infrastructure services (injected via constructor) and business services (configured via protocols).
Modern BaseAgent Architecture
Class Definition
All agents must inherit from BaseAgent
which uses protocol-based dependency injection:
from typing import Any, Dict, Optional
import logging
from agentmap.agents.base_agent import BaseAgent
from agentmap.services.execution_tracking_service import ExecutionTrackingService
from agentmap.services.state_adapter_service import StateAdapterService
from agentmap.services.protocols import LLMCapableAgent, StorageCapableAgent, PromptCapableAgent
class BaseAgent:
"""
Modernized base class for all agents in AgentMap.
Uses protocol-based dependency injection for clean service management.
Infrastructure services are injected via constructor, business services
are configured post-construction via configure_*_service() methods.
"""
def __init__(
self,
name: str,
prompt: str,
context: Optional[Dict[str, Any]] = None,
# Infrastructure services - core services that ALL agents need
logger: Optional[logging.Logger] = None,
execution_tracker_service: Optional[ExecutionTrackingService] = None,
state_adapter_service: Optional[StateAdapterService] = None
):
"""
Initialize the agent with infrastructure dependency injection.
Business services (LLM, storage) are configured post-construction
via configure_*_service() methods using protocol-based injection.
Args:
name: Name of the agent node
prompt: Prompt or instruction for the agent
context: Additional context including input/output configuration
logger: Logger instance (required for proper operation)
execution_tracker_service: ExecutionTrackingService instance (required)
state_adapter_service: StateAdapterService instance for state operations
"""
# Core agent configuration
self.name = name
self.prompt = prompt
self.context = context or {}
self.prompt_template = prompt
# Extract input_fields and output_field from context
self.input_fields = self.context.get("input_fields", [])
self.output_field = self.context.get("output_field", None)
self.description = self.context.get("description", "")
# Infrastructure services (required)
self._logger = logger
self._execution_tracker_service = execution_tracker_service
self._state_adapter_service = state_adapter_service
self._log_prefix = f"[{self.__class__.__name__}:{self.name}]"
# Business services (configured post-construction via protocols)
self._llm_service: Optional[LLMServiceProtocol] = None
self._storage_service: Optional[StorageServiceProtocol] = None
Required Implementation
All agents must implement:
def process(self, inputs: Dict[str, Any]) -> Any:
"""
Process the inputs and return an output value.
Subclasses must implement this method.
Args:
inputs: Dictionary of input values extracted from state
Returns:
Output value for the output_field
"""
raise NotImplementedError("Subclasses must implement process()")
Service Injection Architecture
Infrastructure Services (Constructor Injection)
These core services are injected via the constructor and are required for all agents:
# Service Access Properties (with error handling)
@property
def logger(self) -> logging.Logger:
"""Get logger instance, raising if not available."""
if self._logger is None:
raise ValueError(f"Logger not provided to agent '{self.name}'")
return self._logger
@property
def execution_tracker_service(self) -> ExecutionTrackingService:
"""Get execution tracker instance, raising if not available."""
if self._execution_tracker_service is None:
raise ValueError(f"ExecutionTracker not provided to agent '{self.name}'")
return self._execution_tracker_service
@property
def state_adapter_service(self) -> StateAdapterService:
"""Get state adapter service."""
return self._state_adapter_service
Business Services (Protocol-Based Configuration)
Business services are configured post-construction using protocols:
LLM Service Configuration
from agentmap.services.protocols import LLMCapableAgent, LLMServiceProtocol
class MyLLMAgent(BaseAgent, LLMCapableAgent):
"""Agent that uses LLM services."""
def configure_llm_service(self, llm_service: LLMServiceProtocol) -> None:
"""Configure LLM service for this agent."""
self._llm_service = llm_service
self.log_debug("LLM service configured")
@property
def llm_service(self) -> LLMServiceProtocol:
"""Get LLM service, raising clear error if not configured."""
if self._llm_service is None:
raise ValueError(f"LLM service not configured for agent '{self.name}'")
return self._llm_service
def process(self, inputs: Dict[str, Any]) -> Any:
# Use LLM service
messages = [{"role": "user", "content": inputs.get("query", "")}]
response = self.llm_service.call_llm(
provider="anthropic",
messages=messages,
model="claude-3-5-sonnet-20241022"
)
return response
Storage Service Configuration
from agentmap.services.protocols import StorageCapableAgent, StorageServiceProtocol
class MyStorageAgent(BaseAgent, StorageCapableAgent):
"""Agent that uses storage services."""
def configure_storage_service(self, storage_service: StorageServiceProtocol) -> None:
"""Configure storage service for this agent."""
self._storage_service = storage_service
self.log_debug("Storage service configured")
@property
def storage_service(self) -> StorageServiceProtocol:
"""Get storage service, raising clear error if not configured."""
if self._storage_service is None:
raise ValueError(f"Storage service not configured for agent '{self.name}'")
return self._storage_service
def process(self, inputs: Dict[str, Any]) -> Any:
# Use storage service
collection = inputs.get("collection", "default")
data = self.storage_service.read(collection)
return data
Prompt Service Configuration
from agentmap.services.protocols import PromptCapableAgent, PromptManagerServiceProtocol
class MyPromptAgent(BaseAgent, PromptCapableAgent):
"""Agent that uses prompt manager services."""
def configure_prompt_service(self, prompt_service: PromptManagerServiceProtocol) -> None:
"""Configure prompt manager service for this agent."""
self._prompt_manager_service = prompt_service
# Re-resolve prompt with the new service
self.resolved_prompt = self._resolve_prompt(self.prompt)
self.log_debug("Prompt service configured")