CodeDocs Vault

02 — Core Business Logic & Data Flow

Agent System

Registry Pattern

All agents are registered via a class-level registry on the Agent ABC (openhands/controller/agent.py:33-169):

class Agent(ABC):
    _registry: dict[str, type['Agent']] = {}
 
    @classmethod
    def register(cls, name: str, agent_cls: type['Agent']) -> None:
        if name in cls._registry:
            raise AgentAlreadyRegisteredError(name)
        cls._registry[name] = agent_cls
 
    @classmethod
    def get_cls(cls, name: str) -> type['Agent']:
        if name not in cls._registry:
            raise AgentNotRegisteredError(name)
        return cls._registry[name]

Agents self-register when their modules are imported (via import openhands.agenthub in main.py:16).

Agent Base Class

The Agent ABC defines the minimal interface every agent must implement:

Method/Property Purpose
step(state) -> Action Core method: given current state, produce the next action
reset() Reset completion status between runs
name Returns the class name
get_system_message() Generates the system prompt as a SystemMessageAction
set_mcp_tools() Registers MCP (Model Context Protocol) tools

CodeActAgent — Primary Implementation

File: openhands/agenthub/codeact_agent/codeact_agent.py:57-229

CodeActAgent (v2.2) implements the CodeAct paradigm: consolidating agent actions into a unified code action space. At each turn, the agent can either converse in natural language or execute code (bash, Python, file edits, browser actions).

Key components initialized in __init__ (line 86):


Agent Controller

File: openhands/controller/agent_controller.py:109-1387

The AgentController is the orchestration engine. It:

  1. Subscribes to the EventStream to receive events
  2. Determines when the agent should step (via should_step(), line 410)
  3. Executes the agent step loop (via _step(), line 860)
  4. Manages delegation to sub-agents
  5. Detects stuck agents via StuckDetector (line 199)
  6. Tracks iteration and budget limits via StateTracker

Step Flow

AgentController._step()  [line 860]
        │
        ├── 1. Check state == RUNNING                    [line 862]
        ├── 2. Check no pending action                   [line 870]
        ├── 3. Sync budget flag with metrics             [line 887]
        ├── 4. Run stuck detection                       [line 888]
        ├── 5. Run control flags (iteration/budget)      [line 895]
        │
        ├── 6a. If replay mode → replay action           [line 903-906]
        │
        └── 6b. Call agent.step(state) → Action          [line 909]
                    │
                    ├── Handle recoverable errors        [line 913-926]
                    │   (malformed action, no action,
                    │    validation errors)
                    │
                    ├── Handle context window exceeded   [line 927-954]
                    │   → trigger condensation
                    │
                    └── Process action                   [line 975-1032]
                        ├── If runnable + confirmation mode
                        │   → security analysis
                        │   → maybe AWAITING_USER_CONFIRMATION
                        ├── Set as pending action
                        ├── Attach metrics for frontend
                        └── Add to EventStream

Event Handling

The controller's on_event() method (line 451) is the callback registered with the EventStream. It:

  1. Forwards events to the delegate if one is active
  2. Adds events to state history via StateTracker
  3. Handles actions (_handle_action, line 512) — state changes, delegation, finish
  4. Handles observations (_handle_observation, line 539) — clears pending actions
  5. Triggers the next step if appropriate (should_step, line 410)

Agent Step Flow (CodeActAgent)

CodeActAgent.step(state)  [line 169]
        │
        ├── 1. Pop pending actions (if any)              [line 192]
        │
        ├── 2. Check for /exit command                   [line 196]
        │
        ├── 3. Condense history                          [line 207]
        │      condenser.condensed_history(state)
        │      → View(events) or Condensation(action)
        │
        ├── 4. Build messages                            [line 220]
        │      conversation_memory processes events
        │      into LLM-ready Message objects
        │
        ├── 5. LLM completion                            [line 223-229]
        │      params = {messages, tools, extra_body}
        │      response = llm.completion(**params)
        │
        └── 6. Parse response → Action(s)
               function_calling.response_to_actions()
               → one or more Action objects

Event System

EventStream

File: openhands/events/stream.py:43-291

The EventStream is the central pub/sub hub connecting all components. It extends EventStore for persistence and provides:

Subscribers

The EventStreamSubscriber enum (line 23) defines the subscriber types:

Subscriber Purpose
AGENT_CONTROLLER Processes actions/observations, triggers agent steps
RUNTIME Executes actions in the sandbox, produces observations
MEMORY Handles RecallAction events for microagent retrieval
SERVER Forwards events to the frontend via WebSocket
MAIN CLI mode: handles user input prompts
RESOLVER OpenHands resolver (automated PR/issue handling)
TEST Test infrastructure

Event Dispatch

Events are dispatched in a dedicated queue thread (_run_queue_loop, line 246). Each subscriber gets its own ThreadPoolExecutor (1 worker) for isolation. Events are delivered to all subscribers in sorted key order.

Event Types

All events derive from the Event base dataclass (openhands/events/event.py:26). Events split into two hierarchies:

Event
├── Action              (agent/user intents)
│   ├── CmdRunAction
│   ├── IPythonRunCellAction
│   ├── FileEditAction
│   ├── FileReadAction
│   ├── FileWriteAction
│   ├── BrowseInteractiveAction
│   ├── BrowseURLAction
│   ├── MessageAction
│   ├── SystemMessageAction
│   ├── AgentFinishAction
│   ├── AgentRejectAction
│   ├── AgentDelegateAction
│   ├── AgentThinkAction
│   ├── ChangeAgentStateAction
│   ├── CondensationAction
│   ├── CondensationRequestAction
│   ├── RecallAction
│   ├── LoopRecoveryAction
│   ├── MCPAction
│   └── NullAction
│
└── Observation         (environment results)
    ├── CmdOutputObservation
    ├── FileReadObservation
    ├── FileWriteObservation
    ├── FileEditObservation
    ├── BrowserOutputObservation
    ├── ErrorObservation
    ├── AgentStateChangedObservation
    ├── AgentDelegateObservation
    ├── AgentThinkObservation
    ├── RecallObservation
    ├── LoopDetectionObservation
    ├── UserRejectObservation
    ├── NullObservation
    └── TaskTrackingObservation

Each Event carries:


Delegation

The controller supports hierarchical agent delegation for subtasks (openhands/controller/agent_controller.py:732-858).

Parent AgentController
    │
    ├── agent.step() returns AgentDelegateAction
    │
    ├── start_delegate() [line 732]
    │   ├── Look up delegate agent class from registry
    │   ├── Create new Agent instance
    │   ├── Create child AgentController (is_delegate=True)
    │   ├── Child subscribes to EventStream starting from current position
    │   └── Post task as MessageAction to delegate
    │
    ├── [delegate runs its own step loop]
    │
    └── end_delegate() [line 793]
        ├── Accumulate metrics
        ├── Close delegate controller
        ├── Create AgentDelegateObservation with results
        └── Resume parent processing

When a delegate is active, the parent's on_event() (line 451) forwards all events to the delegate instead of processing them itself. The parent only resumes when the delegate reaches a terminal state (FINISHED, ERROR, REJECTED).


Memory & Condensation

Memory Component

File: openhands/memory/memory.py:42-406

Memory is an EventStream subscriber that handles information retrieval. It responds to RecallAction events by producing RecallObservation events with:

Microagents

Microagents are loaded from three sources:

  1. Global: skills/ directory in the repo (line 34)
  2. User: ~/.openhands/microagents/ (line 39)
  3. Repository: .openhands/microagents/ in the workspace and optional org-level repository

Two types:

History Condensation

The Condenser system (openhands/memory/condenser/) manages conversation history truncation to stay within LLM context windows. Strategies include:

The condenser returns either a View (filtered events for the agent) or a Condensation (an action that modifies the event history, requiring a re-step).