CodeDocs Vault

03 — Key Abstractions & Interfaces

Agent Interface

File: openhands/controller/agent.py:33-191

class Agent(ABC):
    _registry: dict[str, type['Agent']] = {}
    sandbox_plugins: list[PluginRequirement] = []
 
    def __init__(self, config: AgentConfig, llm_registry: LLMRegistry):
        self.llm = llm_registry.get_llm_from_agent_config('agent', config)
        self.config = config
        self.tools: list = []
        self.mcp_tools: dict[str, ChatCompletionToolParam] = {}
 
    @abstractmethod
    def step(self, state: 'State') -> 'Action':
        """Given current state, produce the next action."""
        pass
 
    def reset(self) -> None: ...
    def get_system_message(self) -> 'SystemMessageAction | None': ...
 
    @classmethod
    def register(cls, name: str, agent_cls: type['Agent']) -> None: ...
 
    @classmethod
    def get_cls(cls, name: str) -> type['Agent']: ...
 
    @classmethod
    def list_agents(cls) -> list[str]: ...

Registry pattern: Agents register themselves at import time. The controller retrieves agent classes by name via Agent.get_cls() (line 144). This enables delegation — a parent agent can spawn any registered agent for a subtask.

Current registered agents (via openhands/agenthub/):


Runtime Interface

File: openhands/runtime/base.py:93-1282

class Runtime(FileEditRuntimeMixin):
    """Abstract base class for agent runtime environments."""
 
    @abstractmethod
    async def connect(self) -> None: ...
 
    @abstractmethod
    def run(self, action: CmdRunAction) -> Observation: ...
 
    @abstractmethod
    def run_ipython(self, action: IPythonRunCellAction) -> Observation: ...
 
    @abstractmethod
    def read(self, action: FileReadAction) -> Observation: ...
 
    @abstractmethod
    def write(self, action: FileWriteAction) -> Observation: ...
 
    @abstractmethod
    def edit(self, action: FileEditAction) -> Observation: ...
 
    @abstractmethod
    def browse(self, action: BrowseURLAction) -> Observation: ...
 
    @abstractmethod
    def browse_interactive(self, action: BrowseInteractiveAction) -> Observation: ...
 
    @abstractmethod
    async def call_tool_mcp(self, action: MCPAction) -> Observation: ...
 
    @abstractmethod
    def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): ...
 
    @abstractmethod
    def list_files(self, path: str | None = None) -> list[str]: ...
 
    @abstractmethod
    def copy_from(self, path: str) -> Path: ...

The Runtime ABC defines the sandbox interface. It subscribes to the EventStream (line 152) and dispatches incoming actions to the appropriate method via run_action() (line 969), which uses ACTION_TYPE_TO_CLASS for dynamic dispatch.

Built-in Implementations

Runtime Module Description
DockerRuntime openhands/runtime/docker/ Runs commands in Docker containers (default)
RemoteRuntime openhands/runtime/impl/remote/ Remote execution via API
LocalRuntime openhands/runtime/impl/local/ Local execution (development)
KubernetesRuntime openhands/runtime/impl/k8s/ Kubernetes pod execution
CLIRuntime openhands/runtime/impl/cli/ Direct CLI execution

Key Responsibilities

The base Runtime also handles:


Event Hierarchy

File: openhands/events/event.py:26-122

@dataclass
class Event:
    INVALID_ID = -1
 
    @property
    def id(self) -> int: ...           # Auto-assigned by EventStream
 
    @property
    def timestamp(self) -> str | None: ...  # ISO datetime
 
    @property
    def source(self) -> EventSource | None: ...  # AGENT, USER, ENVIRONMENT
 
    @property
    def cause(self) -> int | None: ...  # ID of causing action
 
    @property
    def llm_metrics(self) -> Metrics | None: ...  # Cost/token data
 
    @property
    def tool_call_metadata(self) -> ToolCallMetadata | None: ...

EventSource (openhands/events/event.py:9-12):


FileStore Interface

File: openhands/storage/files.py

class FileStore(ABC):
    @abstractmethod
    def write(self, path: str, contents: str | bytes) -> None: ...
 
    @abstractmethod
    def read(self, path: str) -> str: ...
 
    @abstractmethod
    def list(self, path: str) -> list[str]: ...
 
    @abstractmethod
    def delete(self, path: str) -> None: ...

Implementations

Implementation Description
LocalFileStore Filesystem-based storage
InMemoryFileStore In-memory storage (testing)
S3FileStore AWS S3 storage
GoogleCloudFileStore Google Cloud Storage

The FileStore is used for persisting event streams, session state, and trajectories. It is configured via OpenHandsConfig.file_store (values: local, s3, google_cloud, memory).


Storage Abstractions

ConversationStore

Manages conversation metadata (creation time, last update, status, selected repository). Implementations: FileConversationStore, RedisConversationStore. Configured via ServerConfig.conversation_store.

SettingsStore

Stores user settings (LLM provider, model preferences). Implementations: FileSettingsStore, RedisSettingsStore. Configured via ServerConfig.settings_store.

SecretsStore

Stores user secrets (API keys, tokens). Implementations: FileSecretsStore, RedisSecretsStore. Configured via ServerConfig.secrets_store.

All three follow the same pluggable pattern — the ServerConfig class determines which backend to use, and the server instantiates the appropriate implementation at startup.


LLM Abstraction

LLM Class

File: openhands/llm/llm.py:64-868

class LLM(RetryMixin, DebugMixin):
    def __init__(self, config: LLMConfig, service_id: str,
                 metrics: Metrics | None = None,
                 retry_listener: Callable | None = None):
        self.config: LLMConfig = copy.deepcopy(config)
        self.metrics: Metrics = metrics or Metrics(model_name=config.model)
        self._completion = partial(litellm_completion, model=config.model, ...)

The LLM class wraps LiteLLM's completion function with:

LLMRegistry

File: openhands/llm/llm_registry.py:30-60

class LLMRegistry:
    def __init__(self, config: OpenHandsConfig, ...):
        self.service_to_llm: dict[str, LLM] = {}
        self.active_agent_llm: LLM = self.get_llm('agent', llm_config)

A factory that creates and caches LLM instances per service ID. Ensures that the same LLM configuration produces the same LLM instance (with shared metrics). Supports routing to different models via get_router().


Design Patterns

Pattern Where Used Description
Registry Agent._registry, LLMRegistry Agents and LLMs register by name for dynamic lookup
Factory LLMRegistry.get_llm(), get_runtime_cls() Create objects by configuration
Observer / Pub-Sub EventStream + subscribers Decoupled event-driven communication
Strategy Condenser subclasses, Runtime subclasses Interchangeable algorithms/implementations
Abstract Factory FileStore, ConversationStore, SettingsStore Pluggable storage backends
Template Method Runtime.run_action() dispatching to abstract methods Base class defines flow, subclasses implement specifics
Composition Runtime + GitHandler + FileEditRuntimeMixin Building complex behavior from focused components
Dependency Injection AgentController(agent, event_stream, ...) Components receive dependencies at construction