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/):
CodeActAgent— Primary implementation, function-calling basedBrowsingAgent— Specialized for web browsing tasksDelegatorAgent— Orchestrates multi-agent workflowsVerifierAgent— Verifies task completion
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:
- Environment setup:
setup_initial_env()(line 216) — env vars, git config - Repository cloning:
clone_or_init_repo()(line 414) — workspace setup - Microagent loading:
get_microagents_from_selected_repo()(line 902) - Security analysis: Initializes
SecurityAnalyzerif configured (line 202) - Git operations: Via
GitHandlercomposition (line 145) - Plugin management: VSCode, Jupyter, AgentSkills plugins (line 155)
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):
AGENT— Actions from the AI agentUSER— Actions from the user (messages, confirmations)ENVIRONMENT— Observations from the runtime/system
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:
- Retry logic (via
RetryMixin) with configurable backoff - Token counting (
get_token_count, line 694) - Cost tracking (
_completion_cost, line 759) - Vision detection (
vision_is_active, line 556) - Prompt caching (
is_caching_prompt_active, line 591) - Function calling detection (
is_function_calling_active, line 602) - Non-native function calling conversion (via
fn_call_converter)
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 |