From 875dd02d22f59c532228209f730aff5559e91495 Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 24 Jan 2026 10:21:30 +0000 Subject: [PATCH 1/5] docs(sdk): add normative invariants to architecture docs\n\nCloses #1815\n\nCo-authored-by: openhands --- sdk/arch/agent.mdx | 70 +++++++++++++++++++++++++++++++++++---- sdk/arch/conversation.mdx | 47 +++++++++++++++++++++++--- sdk/arch/design.mdx | 67 +++++++++++++++++++++++++++++++++++++ sdk/arch/events.mdx | 43 +++++++++++++++++++++++- sdk/arch/tool-system.mdx | 46 +++++++++++++++++++++++++ sdk/arch/workspace.mdx | 32 ++++++++++++++++++ 6 files changed, 292 insertions(+), 13 deletions(-) diff --git a/sdk/arch/agent.mdx b/sdk/arch/agent.mdx index 22b0e134..888a9d02 100644 --- a/sdk/arch/agent.mdx +++ b/sdk/arch/agent.mdx @@ -199,31 +199,87 @@ Tools follow a **strict action-observation pattern**: flowchart TB LLM["LLM generates tool_call"] Convert["Convert to ActionEvent"] - + Decision{"Confirmation
mode?"} Defer["Store as pending"] - + Execute["Execute tool"] Success{"Success?"} - + Obs["ObservationEvent
with result"] Error["ObservationEvent
with error"] - + LLM --> Convert Convert --> Decision - + Decision -->|Yes| Defer Decision -->|No| Execute - + Execute --> Success Success -->|Yes| Obs Success -->|No| Error - + style Convert fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px style Execute fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px style Decision fill:#fff4df,stroke:#b7791f,stroke-width:2px ``` +## Invariants (Normative) + +### AgentBase: Configuration is Stateless and Immutable + +Natural language invariant: + +- An `AgentBase` instance is a **pure configuration object**. It may cache materialized `ToolDefinition` instances internally, but it must remain valid to re-create those tools from its declarative spec. + +OCL-like: + +- `context AgentBase inv Frozen: self.model_config.frozen = true` + +### Initialization: System Prompt Precedes Any User Message + +`Agent.init_state(state, on_event=...)` is responsible for creating the initial system prompt event. + +Natural language invariant: + +- A `ConversationState` must not contain a user `MessageEvent` before it contains a `SystemPromptEvent`. + +OCL-like (conceptual): + +- `context ConversationState inv SystemBeforeUser: self.events->select(e|e.oclIsKindOf(SystemPromptEvent))->size() >= 1 implies self.events->forAll(e| e.oclIsKindOf(MessageEvent) and e.source='user' implies e.index > systemPromptIndex )` + +### Tool Materialization: Names Resolve to Registered ToolDefinitions + +An `Agent` is configured with a list of tool *specs* (`openhands.sdk.tool.spec.Tool`) that reference registered `ToolDefinition` factories. + +Natural language invariant: + +- `resolve_tool(Tool(name=X))` must succeed (tool name present in registry) for all tools the agent intends to use. +- Tool factories must return a **sequence** of `ToolDefinition` instances; tool sets (e.g., browser tool sets) are represented as multi-element sequences. + +### Multi-Tool Calls: Shared Thought Only on First ActionEvent + +When an LLM returns parallel tool calls, the SDK represents this as multiple `ActionEvent`s that share the same `llm_response_id`. + +Natural language invariant: + +- For a batch of `ActionEvent`s with the same `llm_response_id`, only the first action carries `thought` / `reasoning_content` / `thinking_blocks`; subsequent actions must have empty `thought`. + +OCL-like (as modeled in `event.base._combine_action_events`): + +- `context ActionEvent inv BatchedThoughtOnlyFirst: (self.llm_response_id = other.llm_response_id and self <> first) implies self.thought->isEmpty()` + +### Confirmation Mode: Requires Both Analyzer and Policy + +`conversation.is_confirmation_mode_active` is true iff: + +- A `SecurityAnalyzer` is configured, and +- The confirmation policy is not `NeverConfirm`. + +OCL-like (conceptual): + +- `context BaseConversation inv ConfirmationModeIff: self.is_confirmation_mode_active = (self.state.security_analyzer <> null and not self.state.confirmation_policy.oclIsKindOf(NeverConfirm))` + **Execution Modes:** | Mode | Behavior | Use Case | diff --git a/sdk/arch/conversation.mdx b/sdk/arch/conversation.mdx index e0391121..0f60299f 100644 --- a/sdk/arch/conversation.mdx +++ b/sdk/arch/conversation.mdx @@ -185,13 +185,50 @@ The conversation system provides pluggable services that operate independently o | **[Event Log](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/event_store.py)** | Append-only immutable storage | Event sourcing with indexing | | **[Persistence](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/state.py)** | Auto-save & resume | Debounced writes, incremental events | | **[Stuck Detection](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/stuck_detector.py)** | Loop prevention | Sliding window pattern matching | -| **[Visualization](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/visualizer.py)** | Execution diagrams | Event stream → visual representation | +| **[Visualization](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/visualizer/default.py)** | Execution diagrams | Event stream → visual representation | | **[Secret Registry](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/secret_registry.py)** | Secure value storage | Memory-only with masked logging | -**Design Principle:** Services read from the event log but never mutate state directly. This enables: -- Services can be enabled/disabled independently -- Easy to add new services without changing core orchestration -- Event stream acts as the integration point +**Design Principle:** Services read from the event log but never mutate state directly. + +## Invariants (Normative) + +### Conversation Factory: Workspace Chooses Implementation + +Natural language invariant: + +- `Conversation(...)` is a factory that returns `LocalConversation` unless the provided `workspace` is a `RemoteWorkspace`. +- When `workspace` is remote, `persistence_dir` must be unset (`None`). + +OCL-like (conceptual): + +- `context Conversation::__new__ pre RemoteNoPersistence: workspace.oclIsKindOf(RemoteWorkspace) implies persistence_dir = null` + +### ConversationState: Validated Snapshot + Event Log + +Natural language invariants: + +- `ConversationState` is the **only** component intended to hold mutable execution status (`IDLE`, `RUNNING`, `WAITING_FOR_CONFIRMATION`, etc.). +- `ConversationState` owns persistence (`FileStore`) and the event store; all other components treat persistence as an implementation detail. + +### Confirmation Mode Predicate + +The SDK exposes a single predicate for confirmation mode: + +- Confirmation mode is active iff `state.security_analyzer != None` **and** the confirmation policy is not `NeverConfirm`. + +### ask_agent() Must Be Stateless + +Natural language invariant (from the public contract): + +- `BaseConversation.ask_agent(question)` **must not** append events, mutate execution status, or persist anything. It is safe to call concurrently with `run()`. + +### Secrets Persistence Requires a Cipher + +Natural language invariant: + +- If `ConversationState` is persisted without a cipher, secret values are redacted and **cannot be recovered on restore**. + +(Implication: use `Cipher` when persistence is enabled and you expect to resume with secrets intact.) ## Component Relationships diff --git a/sdk/arch/design.mdx b/sdk/arch/design.mdx index e946d9f9..a6aa71dc 100644 --- a/sdk/arch/design.mdx +++ b/sdk/arch/design.mdx @@ -58,3 +58,70 @@ Because agent logic was hard-coded into the core application, extending behavior **Everything should be composable and safe to extend.** Agents are defined as graphs of interchangeable components—tools, prompts, LLMs, and contexts—each described declaratively with strong typing. Developers can reconfigure capabilities (e.g., swap toolsets, override prompts, add delegation logic) without modifying core code, preserving stability while fostering rapid innovation. + +--- + +## Design Invariants (Normative) + +This page describes the **architectural invariants** the SDK relies on. These are treated as *contracts* between components. + +Where appropriate, we express invariants in a lightweight OCL-like notation: + +- `context X inv Name: ` +- `pre:` / `post:` for pre/post-conditions + +If an invariant cannot be expressed precisely in OCL without significant auxiliary modeling, we state it in precise natural language. + +### Single Source of Truth for Runtime State + +The SDK is designed so that **all runtime state that affects agent execution is representable as an event log plus a small, validated state snapshot**. + +- **Configuration objects are immutable** (Pydantic `frozen=True` where applicable). +- **The only intentionally mutable entity is `ConversationState`**, which owns the event log, execution status, secrets registry, and persistence handles. + +OCL-like: + +- `context AgentBase inv StatelessConfiguration: self.model_config.frozen = true` +- `context Event inv Immutable: self.model_config.frozen = true` + +Natural language invariant: + +- `ConversationState` is the single coordination point for execution. Other objects may maintain private runtime caches, but **must not** be required to restore or replay a conversation. + +### Workspace Boundary is the I/O Boundary + +All side effects against the environment (filesystem, processes, git operations) must occur **through a Workspace** (local or remote), which becomes the **I/O boundary**. + +- Tools may execute in different runtimes (local process vs inside agent-server), but *conceptually* they always operate against a workspace rooted at `workspace.working_dir`. + +OCL-like: + +- `context BaseWorkspace inv WorkingDirIsString: self.working_dir.oclIsTypeOf(String)` + +### Event Log is the Execution Trace + +The event stream is the single authoritative trace of what the agent *saw* and *did*. + +Natural language invariant: + +- Any agent decision that should be reproducible on replay must be representable as an `LLMConvertibleEvent` (for LLM context) plus associated non-LLM events (e.g., state updates, errors). + +### Tool Calls are Explicit, Typed, and Linkable + +The SDK assumes an explicit `Action -> Observation` pairing. + +OCL-like (conceptual): + +- `context ActionEvent inv HasToolCallId: self.tool_call_id <> null` +- `context ObservationEvent inv RefersToAction: self.action_id <> null` + +Natural language invariant: + +- Observations must be attributable to a specific action/tool call so that conversations can be audited, visualized, and resumed. + +### Remote vs Local is an Execution Detail + +The SDK makes *deployment mode* (local vs remote) a **factory decision**, not a type-system fork. + +- `Conversation(...)` returns either `LocalConversation` or `RemoteConversation` based solely on the provided workspace. +- User-facing code should not need to change when switching workspaces. diff --git a/sdk/arch/events.mdx b/sdk/arch/events.mdx index 2d37f966..b47297dd 100644 --- a/sdk/arch/events.mdx +++ b/sdk/arch/events.mdx @@ -142,7 +142,48 @@ Events for metadata, control flow, and user actions (not sent to LLM): | **ConversationStateUpdateEvent** | environment | State synchronization | `key` (field name), `value` (serialized data) | | **CondensationRequest** | environment | Trigger history compression | Signal to condenser when context window exceeded | | **Condensation** | environment | Compression result | `forgotten_event_ids`, `summary`, `summary_offset` | -| **PauseEvent** | user | User pause action | Indicates agent execution was paused by user | + +## 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 `ActionEvent`s 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` **Source Types:** - **user**: Event originated from user input diff --git a/sdk/arch/tool-system.mdx b/sdk/arch/tool-system.mdx index 1762af9b..181678df 100644 --- a/sdk/arch/tool-system.mdx +++ b/sdk/arch/tool-system.mdx @@ -259,6 +259,52 @@ flowchart LR **Resolution Workflow:** +1. **[Tool (Spec)](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/spec.py)** - Configuration object with `name` (e.g., "TerminalTool") and `params` (e.g., `{"working_dir": "/workspace"}`) +2. **Resolver Lookup** - Registry finds the registered resolver for the tool name +3. **Factory Invocation** - Resolver calls the tool's `.create()` method with params and conversation state + +## Invariants (Normative) + +### ToolDefinition Naming + +By default, tool names are derived from the class name: + +- `TerminalTool` → `terminal` +- `FileEditorTool` → `file_editor` + +Natural language invariant: + +- Unless explicitly overridden, `ToolDefinition.name` is deterministic and stable across runs. + +### Tool Registry + +`register_tool(name, factory)` maintains a global name→resolver mapping. + +Invariants: + +- Tool names must be non-empty strings. +- A `ToolDefinition` instance can only be registered if it has a non-None `executor`. +- A `ToolDefinition` subclass can only be registered if it implements a concrete `create(...)` classmethod that returns `Sequence[ToolDefinition]`. +- Resolving an unregistered tool name must raise `KeyError`. + +OCL-like (conceptual): + +- `context ToolRegistry inv NonEmptyNames: name.trim().size() > 0` + +### Executor Presence and Call Semantics + +Natural language invariant: + +- A `ToolDefinition` without an `executor` is not executable; attempts to call it must fail fast. +- All tool execution is performed in a `LocalConversation` context (even when invoked remotely) because the agent-server hosts the actual conversation that runs tools. + +### Action/Observation Schemas are Validated + +Natural language invariant: + +- `Action` and `Observation` are Pydantic models; tool inputs are validated before execution, and outputs are coerced to the declared observation model (if present). + + 1. **[Tool (Spec)](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/spec.py)** - Configuration object with `name` (e.g., "BashTool") and `params` (e.g., `{"working_dir": "/workspace"}`) 2. **Resolver Lookup** - Registry finds the registered resolver for the tool name 3. **Factory Invocation** - Resolver calls the tool's `.create()` method with params and conversation state diff --git a/sdk/arch/workspace.mdx b/sdk/arch/workspace.mdx index dbff538b..6148b326 100644 --- a/sdk/arch/workspace.mdx +++ b/sdk/arch/workspace.mdx @@ -122,6 +122,38 @@ flowchart LR | **timeout** | bool | Whether command timed out | | **duration** | float | Execution time in seconds | +## Invariants (Normative) + +### Workspace Factory: Host Chooses Remote + +The `Workspace(...)` constructor is a factory: + +- If `host` is provided, it returns a `RemoteWorkspace`. +- Otherwise it returns a `LocalWorkspace`. + +OCL-like (conceptual): + +- `context Workspace::__new__ post RemoteIffHost: (host <> null) implies result.oclIsKindOf(RemoteWorkspace)` + +### BaseWorkspace Contract + +All workspace implementations must satisfy: + +- `execute_command(command, cwd, timeout)` returns a `CommandResult` where `exit_code=-1` indicates timeout. +- `file_upload` / `file_download` return a `FileOperationResult` with `success=false` and a populated `error` field on failure. +- Git helpers (`git_changes`, `git_diff`) must raise if the path is not a git repository. + +### working_dir Normalization + +Natural language invariant: + +- `working_dir` is normalized to a `str` even if passed as a `Path`. + +### Pause/Resume Semantics + +- `LocalWorkspace.pause()` / `.resume()` are no-ops. +- Remote/container workspaces may implement pause/resume; if unsupported they must raise `NotImplementedError`. + ### File Operations | Operation | Local Implementation | Remote Implementation | From f5fe3d4b06afce69c6bf89d830f80ee80475cddd Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 24 Jan 2026 13:49:35 +0000 Subject: [PATCH 2/5] docs(sdk): clarify wording on workspace parity and schema parsing\n\nCo-authored-by: openhands --- sdk/arch/design.mdx | 10 +++++++--- sdk/arch/tool-system.mdx | 2 +- sdk/arch/workspace.mdx | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/sdk/arch/design.mdx b/sdk/arch/design.mdx index a6aa71dc..72aefd0a 100644 --- a/sdk/arch/design.mdx +++ b/sdk/arch/design.mdx @@ -121,7 +121,11 @@ Natural language invariant: ### Remote vs Local is an Execution Detail -The SDK makes *deployment mode* (local vs remote) a **factory decision**, not a type-system fork. +The SDK makes *deployment mode* (local vs remote) a **runtime selection behind a common interface**, not two separate programming models. -- `Conversation(...)` returns either `LocalConversation` or `RemoteConversation` based solely on the provided workspace. -- User-facing code should not need to change when switching workspaces. +- `Conversation(...)` returns either `LocalConversation` or `RemoteConversation` based on the provided workspace. +- User-facing code typically should not need to change when switching workspaces; you mostly swap configuration. + + +This does **not** mean every optional method behaves identically across workspace types (e.g., `pause()` / `resume()` may be a no-op locally and meaningful remotely). The core conversation API (`send_message`, `run`, events) stays consistent. + diff --git a/sdk/arch/tool-system.mdx b/sdk/arch/tool-system.mdx index 181678df..faa88262 100644 --- a/sdk/arch/tool-system.mdx +++ b/sdk/arch/tool-system.mdx @@ -302,7 +302,7 @@ Natural language invariant: Natural language invariant: -- `Action` and `Observation` are Pydantic models; tool inputs are validated before execution, and outputs are coerced to the declared observation model (if present). +- `Action` and `Observation` are Pydantic models; tool inputs are validated before execution, and tool results are **parsed/validated** into the declared observation model (if present). If the executor already returns the correct observation type, this is a no-op. 1. **[Tool (Spec)](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/spec.py)** - Configuration object with `name` (e.g., "BashTool") and `params` (e.g., `{"working_dir": "/workspace"}`) diff --git a/sdk/arch/workspace.mdx b/sdk/arch/workspace.mdx index 6148b326..728e3c8e 100644 --- a/sdk/arch/workspace.mdx +++ b/sdk/arch/workspace.mdx @@ -149,10 +149,15 @@ Natural language invariant: - `working_dir` is normalized to a `str` even if passed as a `Path`. -### Pause/Resume Semantics +### Pause/Resume Semantics (Optional Capability) + +`pause()` / `resume()` are intentionally **optional capabilities**: - `LocalWorkspace.pause()` / `.resume()` are no-ops. -- Remote/container workspaces may implement pause/resume; if unsupported they must raise `NotImplementedError`. +- Remote/container workspaces may implement pause/resume to conserve resources. +- If a workspace type does not support pausing, it must raise `NotImplementedError`. + +This is compatible with the “swap workspaces without rewriting code” principle because most client code should only rely on the *core* workspace and conversation operations. Optional capabilities should be feature-detected or used conditionally. ### File Operations From 353a8b073f77ae9b2d87fa938aee8a03882ff0f7 Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 24 Jan 2026 14:02:45 +0000 Subject: [PATCH 3/5] docs(sdk): add discussion on pause/resume semantics\n\nCo-authored-by: openhands --- sdk/arch/workspace.mdx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sdk/arch/workspace.mdx b/sdk/arch/workspace.mdx index 728e3c8e..23149e5a 100644 --- a/sdk/arch/workspace.mdx +++ b/sdk/arch/workspace.mdx @@ -159,6 +159,27 @@ Natural language invariant: This is compatible with the “swap workspaces without rewriting code” principle because most client code should only rely on the *core* workspace and conversation operations. Optional capabilities should be feature-detected or used conditionally. +#### Discussion: `pause()` / `resume()` semantics (design tradeoff) + +There is a mild design smell here: the method names `pause()` / `resume()` suggest a strong guarantee (that work is actually suspended), but the SDK currently treats them as a **best-effort resource management hook**. + +- Locally, there is often nothing meaningful the workspace can suspend at the boundary (it is operating on the host OS), so `LocalWorkspace.pause()` is a no-op. +- Some remote/container workspaces may be able to pause a container or VM, but others may not. + +This tension matters because it creates two different reasonable expectations: + +1. *Ergonomic expectation*: orchestration code can call `pause()` unconditionally and it will be safe. +2. *Guarantee expectation*: calling `pause()` actually pauses resource usage. + +**Maybe it would make sense to** model this explicitly as an optional capability: + +- Add `supports_pause` (or a richer `pause_capability`) to `BaseWorkspace`, and +- Make `pause()` / `resume()` no-ops everywhere by default (including remote) while letting pausable implementations override, +- Keep a strict helper (e.g., `pause_or_raise()`) for callers who require a guarantee. + +This would make the default behavior unsurprising (safe to call), while still letting clients opt into fail-fast behavior when pausing is required. + + ### File Operations | Operation | Local Implementation | Remote Implementation | From 0e831601efeef8a6a53ce708a6fbd8b31a6d3ef4 Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 24 Jan 2026 14:06:11 +0000 Subject: [PATCH 4/5] docs(sdk): reframe workspace pause/resume compatibility as discussion\n\nCo-authored-by: openhands --- sdk/arch/workspace.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/arch/workspace.mdx b/sdk/arch/workspace.mdx index 23149e5a..4b3b3227 100644 --- a/sdk/arch/workspace.mdx +++ b/sdk/arch/workspace.mdx @@ -157,10 +157,10 @@ Natural language invariant: - Remote/container workspaces may implement pause/resume to conserve resources. - If a workspace type does not support pausing, it must raise `NotImplementedError`. -This is compatible with the “swap workspaces without rewriting code” principle because most client code should only rely on the *core* workspace and conversation operations. Optional capabilities should be feature-detected or used conditionally. - #### Discussion: `pause()` / `resume()` semantics (design tradeoff) +There is an argument that this is compatible with the “swap workspaces without rewriting code” principle, because most client code should only rely on the *core* workspace and conversation operations, while optional capabilities are feature-detected or used conditionally. + There is a mild design smell here: the method names `pause()` / `resume()` suggest a strong guarantee (that work is actually suspended), but the SDK currently treats them as a **best-effort resource management hook**. - Locally, there is often nothing meaningful the workspace can suspend at the boundary (it is operating on the host OS), so `LocalWorkspace.pause()` is a no-op. From 67b9cc10a05f3adc987149988e4b5324bdf43d8b Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 24 Jan 2026 14:19:27 +0000 Subject: [PATCH 5/5] docs(sdk): cross-link condenser and events architecture\n\nCo-authored-by: openhands --- sdk/arch/condenser.mdx | 6 +++++- sdk/arch/events.mdx | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/arch/condenser.mdx b/sdk/arch/condenser.mdx index f5702ce0..310c3133 100644 --- a/sdk/arch/condenser.mdx +++ b/sdk/arch/condenser.mdx @@ -3,7 +3,11 @@ title: Condenser description: High-level architecture of the conversation history compression system --- -The **Condenser** system manages conversation history compression to keep agent context within LLM token limits. It reduces long event histories into condensed summaries while preserving critical information for reasoning. For more details, read the [blog here](https://openhands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents). +The **Condenser** system manages conversation history compression to keep agent context within LLM token limits. It reduces long event histories into condensed summaries while preserving critical information for reasoning. + +For how condensation is represented in the event system (`Condensation`, `CondensationRequest`, and how they transform the LLM view), see **[Events Architecture](/sdk/arch/events)**. + +For more details, read the [blog here](https://openhands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents). **Source:** [`openhands-sdk/openhands/sdk/context/condenser/`](https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-sdk/openhands/sdk/context/condenser) diff --git a/sdk/arch/events.mdx b/sdk/arch/events.mdx index b47297dd..6d1defe7 100644 --- a/sdk/arch/events.mdx +++ b/sdk/arch/events.mdx @@ -185,6 +185,8 @@ 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](/sdk/arch/condenser)**. + **Source Types:** - **user**: Event originated from user input - **agent**: Event generated by agent logic