Skip to main content
The Event System provides an immutable, type-safe event framework that drives agent execution and state management. Events form an append-only log that serves as both the agent’s memory and the integration point for auxiliary services. Source: openhands-sdk/openhands/sdk/event/

Core Responsibilities

The Event System has four primary responsibilities:
  1. Type Safety - Enforce event schemas through Pydantic models
  2. LLM Integration - Convert events to/from LLM message formats
  3. Append-Only Log - Maintain immutable event history
  4. Service Integration - Enable observers to react to event streams

Architecture

Key Components

ComponentPurposeDesign
EventBase event classImmutable Pydantic model with ID, timestamp, source
LLMConvertibleEventLLM-compatible eventsAbstract class with to_llm_message() method
MessageEventText messagesUser or assistant conversational messages with skills
ActionEventTool callsAgent tool invocations with thought, reasoning, security risk
ObservationBaseEventTool response baseBase for all tool call responses
ObservationEventTool resultsSuccessful tool execution outcomes
UserRejectObservationUser rejectionUser rejected action in confirmation mode
AgentErrorEventAgent errorsErrors from agent/scaffold (not model output)
SystemPromptEventSystem contextSystem prompt with tool schemas
CondensationSummaryEventCondenser summaryLLM-convertible summary of forgotten events
ConversationStateUpdateEventState updatesKey-value conversation state changes
CondensationCondensation resultEvents being forgotten with optional summary
CondensationRequestRequest compressionTrigger for conversation history compression
PauseEventUser pauseUser requested pause of agent execution

Event Types

LLM-Convertible Events

Events that participate in agent reasoning and can be converted to LLM messages:
Event TypeSourceContentLLM Role
MessageEvent (user)userText, imagesuser
MessageEvent (agent)agentText reasoning, skillsassistant
ActionEventagentTool call with thought, reasoning, security riskassistant with tool_calls
ObservationEventenvironmentTool execution resulttool
UserRejectObservationenvironmentRejection reasontool
AgentErrorEventagentError detailstool
SystemPromptEventagentSystem prompt with tool schemassystem
CondensationSummaryEventenvironmentSummary of forgotten eventsuser
The event system bridges agent events to LLM messages: Special Handling - Parallel Function Calling: When multiple ActionEvents share the same llm_response_id (parallel function calling):
  1. Group all ActionEvents by llm_response_id
  2. Combine into single Message with multiple tool_calls
  3. Only first event’s thought, reasoning_content, and thinking_blocks are included
  4. All subsequent events in the batch have empty thought fields
Example:
ActionEvent(llm_response_id="abc123", thought="Let me check...", tool_call=tool1)
ActionEvent(llm_response_id="abc123", thought=[], tool_call=tool2)
→ Combined into single Message(role="assistant", content="Let me check...", tool_calls=[tool1, tool2])

Internal Events

Events for metadata, control flow, and user actions (not sent to LLM):
Event TypeSourcePurposeKey Fields
ConversationStateUpdateEventenvironmentState synchronizationkey (field name), value (serialized data)
CondensationRequestenvironmentTrigger history compressionSignal to condenser when context window exceeded
CondensationenvironmentCompression resultforgotten_event_ids, summary, summary_offset

Invariants (Normative)

Event Immutability

All events inherit from Event / LLMConvertibleEvent with Pydantic config frozen=True and extra="forbid". Natural language invariant:
  • Once appended to the event log, an event must be treated as immutable. Mutations are represented as new events, not edits.
OCL-like:
  • context Event inv Frozen: self.model_config.frozen = true

LLM-Convertible Stream Can Be Reconstructed Deterministically

Natural language invariant:
  • LLMConvertibleEvent.events_to_messages(events) must produce the exact LLM message stream used for decision making, including batching of parallel tool calls.

Parallel Tool Calls are Batched by llm_response_id

When multiple ActionEvents share the same llm_response_id, they represent a single assistant turn with multiple tool calls. Natural language invariant:
  • In a batch, only the first ActionEvent may contain thought/reasoning; subsequent actions must have empty thought. This is asserted when combining events.

Condensation is a Pure View Transformation

Condensation.apply(events) removes forgotten events and optionally inserts a synthetic CondensationSummaryEvent at summary_offset. Natural language invariants:
  • Condensation never mutates existing events; it returns a new list.
  • forgotten_event_ids must refer to events that exist in the input list (otherwise the operation is a no-op for those IDs).
  • If summary is present, summary_offset must also be present to insert the summary into the view; otherwise the summary is metadata only.
OCL-like (conceptual):
  • context Condensation inv SummaryOffsetPair: (self.summary <> null) implies (self.summary_offset <> null) or true -- insertion requires both; metadata-only summary allowed
For the condenser algorithms, thresholds, and configuration, see Condenser Architecture. Source Types:
  • user: Event originated from user input
  • agent: Event generated by agent logic
  • environment: Event from system/framework/tools

Component Relationships

How Events Integrate

Relationship Characteristics:
  • Agent → Events: Reads history for context, writes actions/messages
  • Conversation → Events: Owns and persists event log
  • Tools → Events: Create ObservationEvents after execution
  • Services → Events: Read-only observers for monitoring, visualization

Error Events: Agent vs Conversation

Two distinct error events exist in the SDK, with different purpose and visibility:

See Also