Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions sdk/arch/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
**Key Characteristics:**
- **Stateless:** Agent holds no mutable state between steps
- **Event-Driven:** Reads from event history, writes new events
- **Interruptible:** Each step is atomic and can be paused/resumed

Check warning on line 150 in sdk/arch/agent.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/agent.mdx#L150

Did you really mean 'Interruptible'?

## Agent Context

Expand Down Expand Up @@ -199,31 +199,87 @@
flowchart TB
LLM["LLM generates tool_call"]
Convert["Convert to ActionEvent"]

Decision{"Confirmation<br>mode?"}
Defer["Store as pending"]

Execute["Execute tool"]
Success{"Success?"}

Obs["ObservationEvent<br><i>with result</i>"]
Error["ObservationEvent<br><i>with error</i>"]

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:

Check warning on line 274 in sdk/arch/agent.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/agent.mdx#L274

Did you really mean '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 |
Expand Down
6 changes: 5 additions & 1 deletion sdk/arch/condenser.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
47 changes: 42 additions & 5 deletions sdk/arch/conversation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
| **[`Conversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/conversation.py)** | Unified entrypoint | Returns correct implementation based on workspace type |
| **[`LocalConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py)** | Local execution | Runs agent directly in process |
| **[`RemoteConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py)** | Remote execution | Delegates to agent-server via HTTP/WebSocket |
| **[`ConversationState`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/state.py)** | State container | Pydantic model with validation and serialization |

Check warning on line 67 in sdk/arch/conversation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/conversation.mdx#L67

Did you really mean 'Pydantic'?
| **[`EventLog`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/event_store.py)** | Event storage | Immutable append-only store with efficient queries |

## Factory Pattern
Expand Down Expand Up @@ -178,20 +178,57 @@

## Auxiliary Services

The conversation system provides pluggable services that operate independently on the event stream:

Check warning on line 181 in sdk/arch/conversation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/conversation.mdx#L181

Did you really mean 'pluggable'?

| Service | Purpose | Architecture Pattern |
|---------|---------|---------------------|
| **[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 |

Check warning on line 186 in sdk/arch/conversation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/conversation.mdx#L186

Did you really mean 'Debounced'?
| **[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`.

Check warning on line 217 in sdk/arch/conversation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/conversation.mdx#L217

Did you really mean 'iff'?

### 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

Expand Down
71 changes: 71 additions & 0 deletions sdk/arch/design.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

The **OpenHands Software Agent SDK** is part of the [OpenHands V1](https://openhands.dev/blog/the-path-to-openhands-v1) effort — a complete architectural rework based on lessons from **OpenHands V0**, one of the most widely adopted open-source coding agents.

[Over the last eighteen months](https://openhands.dev/blog/one-year-of-openhands-a-journey-of-open-source-ai-development), OpenHands V0 evolved from a scrappy prototype into a widely used open-source coding agent. The project grew to tens of thousands of GitHub stars, hundreds of contributors, and multiple production deployments. That growth exposed architectural tensions — tight coupling between research and production, mandatory sandboxing, mutable state, and configuration sprawl — which informed the design principles of agent-sdk in V1.

Check warning on line 9 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L9

Did you really mean 'sandboxing'?

## Optional Isolation over Mandatory Sandboxing

Check warning on line 11 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L11

Did you really mean 'Sandboxing'?

<Info>
**V0 Challenge:**
Expand All @@ -17,8 +17,8 @@
</Info>

**V1 Principle:**
**Sandboxing should be opt-in, not universal.**

Check warning on line 20 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L20

Did you really mean 'Sandboxing'?
V1 unifies agent and tool execution within a single process by default, aligning with MCP's local-execution model.

Check warning on line 21 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L21

Did you really mean 'MCP's'?
When isolation is needed, the same stack can be transparently containerized, maintaining flexibility without complexity.

## Stateless by Default, One Source of Truth for State
Expand All @@ -30,7 +30,7 @@

**V1 Principle:**
**Keep everything stateless, with exactly one mutable state.**
All components (agents, tools, LLMs, and configurations) are immutable Pydantic models validated at construction.

Check warning on line 33 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L33

Did you really mean 'LLMs'?

Check warning on line 33 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L33

Did you really mean 'Pydantic'?
The only mutable entity is the [conversation state](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/conversation_state.py), a single source of truth that enables deterministic replay and robust persistence across sessions or distributed systems.

## Clear Boundaries between Agent and Applications
Expand All @@ -47,7 +47,7 @@
Applications communicate with the agent via APIs rather than embedding it directly, ensuring research and production can evolve independently.


## Composable Components for Extensibility

Check warning on line 50 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L50

Did you really mean 'Composable'?

<Info>
**V0 Challenge:**
Expand All @@ -55,6 +55,77 @@
</Info>

**V1 Principle:**
**Everything should be composable and safe to extend.**

Check warning on line 58 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L58

Did you really mean 'composable'?
Agents are defined as graphs of interchangeable components—tools, prompts, LLMs, and contexts—each described declaratively with strong typing.

Check warning on line 59 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L59

Did you really mean 'LLMs'?

Check warning on line 59 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L59

Did you really mean 'declaratively'?
Developers can reconfigure capabilities (e.g., swap toolsets, override prompts, add delegation logic) without modifying core code, preserving stability while fostering rapid innovation.

Check warning on line 60 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L60

Did you really mean 'toolsets'?

---

## 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: <predicate>`
- `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).

Check warning on line 79 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L79

Did you really mean 'Pydantic'?
- **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`.

Check warning on line 95 in sdk/arch/design.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/design.mdx#L95

Did you really mean 'runtimes'?

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 **runtime selection behind a common interface**, not two separate programming models.

- `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.

<Note>
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.
</Note>
45 changes: 44 additions & 1 deletion sdk/arch/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

The Event System has four primary responsibilities:

1. **Type Safety** - Enforce event schemas through Pydantic models

Check warning on line 14 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L14

Did you really mean 'Pydantic'?
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
Expand Down Expand Up @@ -63,7 +63,7 @@

| Component | Purpose | Design |
|-----------|---------|--------|
| **[`Event`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/base.py)** | Base event class | Immutable Pydantic model with ID, timestamp, source |

Check warning on line 66 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L66

Did you really mean 'Pydantic'?
| **[`LLMConvertibleEvent`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/base.py)** | LLM-compatible events | Abstract class with `to_llm_message()` method |
| **[`MessageEvent`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/llm_convertible/message.py)** | Text messages | User or assistant conversational messages with skills |
| **[`ActionEvent`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/llm_convertible/action.py)** | Tool calls | Agent tool invocations with thought, reasoning, security risk |
Expand Down Expand Up @@ -142,7 +142,50 @@
| **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"`.

Check warning on line 150 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L150

Did you really mean 'Pydantic'?

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

Check warning on line 166 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L166

Did you really mean '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`

For the condenser algorithms, thresholds, and configuration, see **[Condenser Architecture](/sdk/arch/condenser)**.

**Source Types:**
- **user**: Event originated from user input
Expand Down Expand Up @@ -185,7 +228,7 @@

- AgentErrorEvent
- Type: ObservationBaseEvent (LLM-convertible)
- Scope: Error for a specific tool call (has tool_name and tool_call_id)

Check warning on line 231 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L231

Did you really mean 'tool_name'?

Check warning on line 231 in sdk/arch/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/events.mdx#L231

Did you really mean 'tool_call_id'?
- Source: "agent"
- LLM visibility: Sent as a tool message so the model can react/recover
- Effect: Conversation continues; not a terminal state
Expand Down
46 changes: 46 additions & 0 deletions sdk/arch/tool-system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

The Tool System has four primary responsibilities:

1. **Type Safety** - Enforce action/observation schemas via Pydantic models

Check warning on line 14 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L14

Did you really mean 'Pydantic'?
2. **Schema Generation** - Auto-generate LLM-compatible tool descriptions from Pydantic schemas

Check warning on line 15 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L15

Did you really mean 'Pydantic'?
3. **Execution Lifecycle** - Validate inputs, execute logic, wrap outputs
4. **Tool Registry** - Discover and resolve tools by name or pattern

Expand Down Expand Up @@ -64,11 +64,11 @@
| Component | Purpose | Design |
|-----------|---------|--------|
| **[`ToolBase`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/tool.py)** | Abstract base class | Generic over Action and Observation types, defines abstract `create()` |
| **[`ToolDefinition`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/tool.py)** | Concrete tool class | Can be instantiated directly or subclassed for factory pattern |

Check warning on line 67 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L67

Did you really mean 'subclassed'?
| **[`Action`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/schema.py)** | Input model | Pydantic model with `visualize` property |

Check warning on line 68 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L68

Did you really mean 'Pydantic'?
| **[`Observation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/schema.py)** | Output model | Pydantic model with `to_llm_content` property |

Check warning on line 69 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L69

Did you really mean 'Pydantic'?
| **[`ToolExecutor`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/tool.py)** | Execution interface | ABC with `__call__()` method, optional `close()` |
| **[`ToolAnnotations`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/tool.py)** | Behavioral hints | MCP-spec hints (readOnly, destructive, idempotent, openWorld) |

Check warning on line 71 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L71

Did you really mean 'readOnly'?

Check warning on line 71 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L71

Did you really mean 'openWorld'?
| **[`Tool` (spec)](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/spec.py)** | Tool specification | Configuration object with name and params |
| **[`ToolRegistry`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/tool/registry.py)** | Tool discovery | Resolves Tool specs to ToolDefinition instances |

Expand Down Expand Up @@ -141,8 +141,8 @@
```

**Components:**
1. **Action** - Pydantic model with `visualize` property for display

Check warning on line 144 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L144

Did you really mean 'Pydantic'?
2. **Observation** - Pydantic model with `to_llm_content` property for LLM

Check warning on line 145 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L145

Did you really mean 'Pydantic'?
3. **ToolExecutor** - Stateless executor with `__call__(action) → observation`
4. **ToolDefinition** - Direct instantiation with executor instance

Expand Down Expand Up @@ -218,7 +218,7 @@

### Tool Annotations

Tools include optional `ToolAnnotations` based on the [Model Context Protocol (MCP) spec](https://github.com/modelcontextprotocol/modelcontextprotocol) that provide behavioral hints to LLMs:

Check warning on line 221 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L221

Did you really mean 'LLMs'?

| Field | Meaning | Examples |
|-------|---------|----------|
Expand All @@ -229,7 +229,7 @@

**Key Behaviors:**
- [LLM-based Security risk prediction](/sdk/guides/security) automatically added for tools with `readOnlyHint=False`
- Annotations help LLMs reason about tool safety and side effects

Check warning on line 232 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L232

Did you really mean 'LLMs'?

### Tool Registry

Expand Down Expand Up @@ -259,11 +259,57 @@

**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]`.

Check warning on line 287 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L287

Did you really mean 'classmethod'?
- 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 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.

Check warning on line 305 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L305

Did you really mean 'Pydantic'?


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
4. **Instance Creation** - Tool instance(s) are created with configured executors
5. **Agent Usage** - Instances are added to the agent's tools_map for execution

Check warning on line 312 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L312

Did you really mean 'tools_map'?

**Registration Types:**

Expand Down Expand Up @@ -296,7 +342,7 @@
**Benefits:**
- **Separation of Concerns** - Public API separate from implementation
- **Avoid Circular Imports** - Import `impl` only inside `create()` method
- **Consistency** - All tools follow same structure for discoverability

Check warning on line 345 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L345

Did you really mean 'discoverability'?

**Example Reference:** See [`execute_bash/`](https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-tools/openhands/tools/execute_bash) for complete implementation

Expand Down Expand Up @@ -352,10 +398,10 @@
|-----------|---------|--------|
| **[`MCPClient`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/client.py)** | MCP server connection | Extends FastMCP with sync/async bridge |
| **[`MCPToolDefinition`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py)** | Tool wrapper | Wraps MCP tools as SDK `ToolDefinition` with dynamic validation |
| **[`MCPToolExecutor`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py)** | Execution handler | Bridges agent actions to MCP tool calls via MCPClient |

Check warning on line 401 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L401

Did you really mean 'MCPClient'?
| **[`MCPToolAction`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Generic action wrapper | Simple `dict[str, Any]` wrapper for MCP tool arguments |
| **[`MCPToolObservation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/definition.py)** | Result wrapper | Wraps MCP tool results as observations with content blocks |
| **[`_create_mcp_action_type()`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/mcp/tool.py)** | Dynamic schema | Runtime Pydantic model generated from MCP `inputSchema` for validation |

Check warning on line 404 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L404

Did you really mean 'Pydantic'?

### Sync/Async Bridge

Expand Down Expand Up @@ -428,7 +474,7 @@
- Wraps the MCP tool metadata in `MCPToolDefinition`
- Uses generic `MCPToolAction` as the action type (NOT dynamic models yet)
5. **Add to Agent** - All `MCPToolDefinition` instances are added to agent's `tools_map` during `initialize()` (bypasses ToolRegistry)
6. **Lazy Validation** - Dynamic Pydantic models are generated lazily when:

Check warning on line 477 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L477

Did you really mean 'Pydantic'?
- `action_from_arguments()` is called (argument validation)
- `to_openai_tool()` is called (schema export to LLM)

Expand All @@ -439,7 +485,7 @@
| `name` | Tool name (stored in `MCPToolDefinition`) | Discovery, execution |
| `description` | Tool description for LLM | Discovery, LLM prompt |
| `inputSchema` | Stored in `mcp_tool.inputSchema` | Lazy model generation |
| `inputSchema` fields | Converted to Pydantic fields via `Schema.from_mcp_schema()` | Validation, schema export |

Check warning on line 488 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L488

Did you really mean 'Pydantic'?
| `annotations` | Mapped to `ToolAnnotations` | Security analysis, LLM hints |

### MCP Server Configuration
Expand Down Expand Up @@ -500,18 +546,18 @@
```

**Relationship Characteristics:**
- **Native → Registry → tools_map**: Native tools resolved via `ToolRegistry`

Check warning on line 549 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L549

Did you really mean 'tools_map'?
- **MCP → tools_map**: MCP tools bypass registry, added directly during `initialize()`

Check warning on line 550 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L550

Did you really mean 'tools_map'?
- **tools_map → LLM**: Generate schemas describing all available capabilities

Check warning on line 551 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L551

Did you really mean 'tools_map'?
- **Agent → tools_map**: Execute actions, receive observations

Check warning on line 552 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L552

Did you really mean 'tools_map'?
- **tools_map → Conversation**: Read state for context-aware execution

Check warning on line 553 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L553

Did you really mean 'tools_map'?
- **tools_map → Security**: Tool annotations inform risk assessment

Check warning on line 554 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L554

Did you really mean 'tools_map'?

## See Also

- **[Agent Architecture](/sdk/arch/agent)** - How agents select and execute tools
- **[Events](/sdk/arch/events)** - ActionEvent and ObservationEvent structures
- **[Security Analyzer](/sdk/arch/security)** - Action risk assessment
- **[Skill Architecture](/sdk/arch/skill)** - Embedding MCP configs in repository skills

Check warning on line 561 in sdk/arch/tool-system.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/arch/tool-system.mdx#L561

Did you really mean 'configs'?
- **[Custom Tools Guide](/sdk/guides/custom-tools)** - Building your own tools
- **[FastMCP Documentation](https://gofastmcp.com/)** - Underlying MCP client library
Loading