diff --git a/AGENTS.md b/AGENTS.md
index de1edc63b..018039270 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -15,6 +15,9 @@ The site is built with **Mintlify** and deployed automatically by Mintlify on pu
- `docs.json` — Mintlify site configuration (nav tabs, redirects, OpenAPI integration)
- `overview/` — high-level docs (intro, quickstart, community, skills overview)
- `openhands/usage/` — product docs for Web/Cloud/CLI/etc.
+- `openhands/usage/agent-canvas/` — Agent Canvas Beta docs (setup, backends, development, profiles, customize/settings, automations, self-hosting, troubleshooting)
+- `openhands/usage/run-openhands/local-setup.mdx` and related Local GUI pages remain on their existing URLs as **Local GUI (Legacy)** docs; prefer Agent Canvas pages for the current local/self-hosted UI
+
- `sdk/` — Agent SDK docs (guides, architecture, API reference pages)
- `openapi/` — OpenAPI specs consumed by Mintlify
- `openapi/openapi.json` — OpenHands REST API schema
diff --git a/docs.json b/docs.json
index 548487e14..377a6bdc1 100644
--- a/docs.json
+++ b/docs.json
@@ -117,7 +117,21 @@
]
},
{
- "group": "Local GUI",
+ "group": "Agent Canvas (Beta)",
+ "pages": [
+ "openhands/usage/agent-canvas/overview",
+ "openhands/usage/agent-canvas/setup",
+ "openhands/usage/agent-canvas/backends",
+ "openhands/usage/agent-canvas/development",
+ "openhands/usage/agent-canvas/llm-profiles",
+ "openhands/usage/agent-canvas/customize-and-settings",
+ "openhands/usage/agent-canvas/automations",
+ "openhands/usage/agent-canvas/self-hosting",
+ "openhands/usage/agent-canvas/troubleshooting"
+ ]
+ },
+ {
+ "group": "Local GUI (Legacy)",
"pages": [
"openhands/usage/run-openhands/local-setup",
"openhands/usage/run-openhands/gui-mode",
diff --git a/enterprise/enterprise-vs-oss.mdx b/enterprise/enterprise-vs-oss.mdx
index 369c10d7f..3bb0f7a6a 100644
--- a/enterprise/enterprise-vs-oss.mdx
+++ b/enterprise/enterprise-vs-oss.mdx
@@ -52,9 +52,9 @@ OpenHands Enterprise is the right choice when you need:
- Get started with OpenHands on your local machine using Docker or the CLI launcher.
+ Install Agent Canvas from npm and run OpenHands from your terminal.
`
```
**
@@ -3257,7 +3257,7 @@ Complex tool with initialization parameters:
: class TerminalTool(ToolDefinition[TerminalAction,
: TerminalObservation]):
@classmethod
- def create(cls, conv_state,
+ def create(cls, conv_state,
`
`
```
**
@@ -3918,18 +3918,18 @@ flowchart TB
Events["Event History"]
Context["Agent Context
Skills + Prompts"]
end
-
+
subgraph Core["Agent Core"]
Condense["Condenser
History compression"]
Reason["LLM Query
Generate actions"]
Security["Security Analyzer
Risk assessment"]
end
-
+
subgraph Execution[" "]
Tools["Tool Executor
Action → Observation"]
Results["Observation Events"]
end
-
+
Events --> Condense
Context -.->|Skills| Reason
Condense --> Reason
@@ -3937,11 +3937,11 @@ flowchart TB
Security --> Tools
Tools --> Results
Results -.->|Feedback| Events
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Reason primary
class Condense,Security secondary
class Tools tertiary
@@ -3967,55 +3967,55 @@ flowchart TB
Start["step() called"]
Pending{"Pending
actions?"}
ExecutePending["Execute pending actions"]
-
+
HasCondenser{"Has
condenser?"}
Condense["Call condenser.condense()"]
CondenseResult{"Result
type?"}
EmitCondensation["Emit Condensation event"]
UseView["Use View events"]
UseRaw["Use raw events"]
-
+
Query["Query LLM with messages"]
ContextExceeded{"Context
window
exceeded?"}
EmitRequest["Emit CondensationRequest"]
-
+
Parse{"Response
type?"}
CreateActions["Create ActionEvents"]
CreateMessage["Create MessageEvent"]
-
+
Confirmation{"Need
confirmation?"}
SetWaiting["Set WAITING_FOR_CONFIRMATION"]
-
+
Execute["Execute actions"]
Observe["Create ObservationEvents"]
-
+
Return["Return"]
-
+
Start --> Pending
Pending -->|Yes| ExecutePending --> Return
Pending -->|No| HasCondenser
-
+
HasCondenser -->|Yes| Condense
HasCondenser -->|No| UseRaw
Condense --> CondenseResult
CondenseResult -->|Condensation| EmitCondensation --> Return
CondenseResult -->|View| UseView --> Query
UseRaw --> Query
-
+
Query --> ContextExceeded
ContextExceeded -->|Yes| EmitRequest --> Return
ContextExceeded -->|No| Parse
-
+
Parse -->|Tool calls| CreateActions
Parse -->|Message| CreateMessage --> Return
-
+
CreateActions --> Confirmation
Confirmation -->|Yes| SetWaiting --> Return
Confirmation -->|No| Execute
-
+
Execute --> Observe
Observe --> Return
-
+
style Query fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Condense fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Confirmation fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4050,26 +4050,26 @@ The agent applies `AgentContext` which includes **skills** and **prompts** to sh
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%%
flowchart LR
Context["AgentContext"]
-
+
subgraph Skills["Skills"]
Repo["repo
Always active"]
Knowledge["knowledge
Trigger-based"]
end
SystemAug["System prompt prefix/suffix
Per-conversation"]
System["Prompt template
Per-conversation"]
-
+
subgraph Application["Applied to LLM"]
SysPrompt["System Prompt"]
UserMsg["User Messages"]
end
-
+
Context --> Skills
Context --> SystemAug
Repo --> SysPrompt
Knowledge -.->|When triggered| UserMsg
System --> SysPrompt
SystemAug --> SysPrompt
-
+
style Context fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Repo fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Knowledge fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4092,26 +4092,26 @@ 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
@@ -4143,14 +4143,14 @@ flowchart LR
LLM["LLM"]
Tools["Tools"]
Context["AgentContext"]
-
+
Conv -->|.step calls| Agent
Agent -->|Reads events| Conv
Agent -->|Query| LLM
Agent -->|Execute| Tools
Context -.->|Skills and Context| Agent
Agent -.->|New events| Conv
-
+
style Agent fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Conv fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style LLM fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4192,23 +4192,23 @@ The Agent Server enables:
```mermaid
graph TB
Client[Web/Mobile Client] -->|HTTPS| API[FastAPI Server]
-
+
API --> Auth[Authentication]
API --> Router[API Router]
-
+
Router --> WS[Workspace Manager]
Router --> Conv[Conversation Handler]
-
+
WS --> Docker[Docker Manager]
Docker --> C1[Container 1
User A]
Docker --> C2[Container 2
User B]
Docker --> C3[Container 3
User C]
-
+
Conv --> Agent[Software Agent SDK]
Agent --> C1
Agent --> C2
Agent --> C3
-
+
style Client fill:#e1f5fe
style API fill:#fff3e0
style WS fill:#e8f5e8
@@ -4329,7 +4329,7 @@ curl -H "Authorization: Bearer YOUR_API_KEY" \
async with websocket_connect(url) as ws:
# Send message
await ws.send_json({"message": "Hello"})
-
+
# Receive events
async for event in ws:
if event["type"] == "message":
@@ -4539,39 +4539,47 @@ curl https://agent-server.example.com/health
- Resource exhaustion
- Container failures
-## Client SDK
+## Client Integration Architecture
-Python SDK for interacting with Agent Server:
+The SDK implements a **workspace-based dispatch pattern** for connecting to agent servers. The `Conversation` factory inspects the workspace type and returns the appropriate conversation implementation.
-```python
-from openhands.client import AgentServerClient
+```mermaid
+flowchart LR
+ Conv["Conversation()"] --> Check{"Workspace Type?"}
+ Check -->|LocalWorkspace| Local["LocalConversation"]
+ Check -->|RemoteWorkspace| Remote["RemoteConversation"]
+ Remote -->|HTTP/WebSocket| Server["Agent Server"]
-client = AgentServerClient(
- url="https://agent-server.example.com",
- api_key="your-api-key"
-)
+ style Conv fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
+ style Remote fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
+ style Server fill:#fff4df,stroke:#b7791f,stroke-width:2px
+```
-# Create conversation
-conversation = client.create_conversation()
+### Workspace Types
-# Send message
-response = client.send_message(
- conversation_id=conversation.id,
- message="Hello, agent!"
-)
+| Workspace | Conversation Type | Communication |
+|-----------|------------------|---------------|
+| [`LocalWorkspace`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/workspace/local.py) | [`LocalConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py) | Direct execution |
+| [`OpenHandsCloudWorkspace`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-workspace/openhands/workspace/cloud/workspace.py) | [`RemoteConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py) | HTTPS + WebSocket to OpenHands Cloud |
+| [`APIRemoteWorkspace`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-workspace/openhands/workspace/remote_api/workspace.py) | [`RemoteConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py) | HTTPS + WebSocket to Runtime API |
+| [`DockerWorkspace`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-workspace/openhands/workspace/docker/workspace.py) | [`RemoteConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py) | HTTP + WebSocket to local container |
-# Stream responses
-for event in client.stream_conversation(conversation.id):
- if event.type == "message":
- print(event.content)
-```
+### [`RemoteConversation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py) Responsibilities
+
+The `RemoteConversation` implementation handles all client-server concerns:
+
+- **Session management**: Authenticates and maintains connection to agent server
+- **Event streaming**: WebSocket connection for real-time agent events
+- **Request routing**: HTTP calls for conversation lifecycle operations
+- **Reconnection**: Automatic retry logic for transient failures
-**Client handles**:
-- Authentication
-- Request/response serialization
-- Error handling
-- Streaming
-- Retries
+### Usage Examples
+
+For complete working examples with all required setup:
+
+- **[OpenHands Cloud Workspace](/sdk/guides/agent-server/cloud-workspace)** - Managed cloud infrastructure
+- **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Custom runtime environments
+- **[Docker Sandbox](/sdk/guides/agent-server/docker-sandbox)** - Local containerized execution
## Cost Considerations
@@ -4617,11 +4625,11 @@ WORKSPACE_CONFIG = {
### Use Agent Server When:
-✅ **Multi-user system**: Web app with many users
-✅ **Remote clients**: Mobile app, web frontend
-✅ **Centralized management**: Need to monitor all agents
-✅ **Workspace isolation**: Users shouldn't interfere
-✅ **SaaS product**: Building agent-as-a-service
+✅ **Multi-user system**: Web app with many users
+✅ **Remote clients**: Mobile app, web frontend
+✅ **Centralized management**: Need to monitor all agents
+✅ **Workspace isolation**: Users shouldn't interfere
+✅ **SaaS product**: Building agent-as-a-service
✅ **Scaling**: Need to handle concurrent users
**Examples**:
@@ -4632,10 +4640,10 @@ WORKSPACE_CONFIG = {
### Use Standalone SDK When:
-✅ **Single-user**: Personal tool or script
-✅ **Local execution**: Running on your machine
-✅ **Full control**: Need programmatic access
-✅ **Simpler deployment**: No server management
+✅ **Single-user**: Personal tool or script
+✅ **Local execution**: Running on your machine
+✅ **Full control**: Need programmatic access
+✅ **Simpler deployment**: No server management
✅ **Lower latency**: No network overhead
**Examples**:
@@ -4728,40 +4736,40 @@ flowchart TB
subgraph Interface["Abstract Interface"]
Base["CondenserBase
Abstract base"]
end
-
+
subgraph Implementations["Concrete Implementations"]
NoOp["NoOpCondenser
No compression"]
LLM["LLMSummarizingCondenser
LLM-based"]
Pipeline["PipelineCondenser
Multi-stage"]
end
-
+
subgraph Process["Condensation Process"]
View["View
Event history"]
Check["should_condense()?"]
Condense["get_condensation()"]
Result["View | Condensation"]
end
-
+
subgraph Output["Condensation Output"]
CondEvent["Condensation Event
Summary metadata"]
NewView["Condensed View
Reduced tokens"]
end
-
+
Base --> NoOp
Base --> LLM
Base --> Pipeline
-
+
View --> Check
Check -->|Yes| Condense
Check -->|No| Result
Condense --> CondEvent
CondEvent --> NewView
NewView --> Result
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Base primary
class LLM,Pipeline secondary
class Check,Condense tertiary
@@ -4791,9 +4799,9 @@ flowchart LR
View["View"]
NoOp["NoOpCondenser"]
Same["Same View"]
-
+
View --> NoOp --> Same
-
+
style NoOp fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
```
@@ -4812,7 +4820,7 @@ flowchart LR
AddToHistory["Add to History"]
NextStep["Next Step: View.from_events()"]
NewView["Condensed View"]
-
+
View --> Check
Check -->|Yes| Summarize
Summarize --> Summary
@@ -4820,7 +4828,7 @@ flowchart LR
Metadata --> AddToHistory
AddToHistory --> NextStep
NextStep --> NewView
-
+
style Check fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Summarize fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style NewView fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4851,9 +4859,9 @@ flowchart LR
C2["Condenser 2"]
C3["Condenser 3"]
Final["Final View"]
-
+
View --> C1 --> C2 --> C3 --> Final
-
+
style C1 fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style C2 fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style C3 fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4876,9 +4884,9 @@ flowchart TB
Check1["condenser.condense(view)"]
Trigger1["should_condense()?"]
end
-
+
Agent1 --> Build1 --> Check1 --> Trigger1
-
+
style Check1 fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
```
@@ -4897,9 +4905,9 @@ flowchart TB
NextStep["Next Agent Step"]
Trigger2["condense() detects request"]
end
-
+
Error --> Request --> NextStep --> Trigger2
-
+
style Request fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
**Manual Trigger:**
@@ -4913,11 +4921,11 @@ flowchart TB
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%%
flowchart TB
Start["Agent calls condense(view)"]
-
+
Decision{"should_condense?"}
-
+
ReturnView["Return View
Agent proceeds"]
-
+
Extract["Select Events to Keep/Forget"]
Generate["LLM Generates Summary"]
Create["Create Condensation Event"]
@@ -4927,7 +4935,7 @@ flowchart TB
FilterEvents["Filter forgotten events"]
InsertSummary["Insert summary at offset"]
NewView["New condensed view"]
-
+
Start --> Decision
Decision -->|No| ReturnView
Decision -->|Yes| Extract
@@ -4939,7 +4947,7 @@ flowchart TB
NextStep --> FilterEvents
FilterEvents --> InsertSummary
InsertSummary --> NewView
-
+
style Decision fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Generate fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Create fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -4971,14 +4979,14 @@ flowchart LR
View["View
LLMConvertibleEvents"]
Convert["events_to_messages()"]
LLM["LLM Input"]
-
+
Events --> FromEvents
FromEvents --> Filter
Filter --> Insert
Insert --> View
View --> Convert
Convert --> LLM
-
+
style View fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style FromEvents fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -5001,12 +5009,12 @@ flowchart LR
Event["Condensation Event
forgotten_event_ids"]
Applied["View.from_events()"]
New["New View
~60 events + summary"]
-
+
Old -.->|Summarized| Summary
Summary --> Event
Event --> Applied
Applied --> New
-
+
style Event fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Summary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -5026,33 +5034,33 @@ flowchart LR
flowchart TB
View["Current View
120+ events"]
Check["Count Events"]
-
+
Compare{"Count >
max_size?"}
-
+
Keep["Keep All Events"]
-
+
Split["Split Events"]
Head["Head
First 4 events"]
Middle["Middle
~56 events"]
Tail["Tail
~56 events"]
Summarize["LLM Summarizes Middle"]
Result["Head + Summary + Tail
~60 events total"]
-
+
View --> Check
Check --> Compare
-
+
Compare -->|Under| Keep
Compare -->|Over| Split
-
+
Split --> Head
Split --> Middle
Split --> Tail
-
+
Middle --> Summarize
Head --> Result
Summarize --> Result
Tail --> Result
-
+
style Compare fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Split fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Summarize fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -5075,13 +5083,13 @@ flowchart LR
Condenser["Condenser"]
State["Conversation State"]
Events["Event Log"]
-
+
Agent -->|"View.from_events()"| State
State -->|View| Agent
Agent -->|"condense(view)"| Condenser
Condenser -->|"View | Condensation"| Agent
Agent -->|Adds Condensation| Events
-
+
style Condenser fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Events fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -5122,7 +5130,7 @@ The Conversation system has four primary responsibilities:
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 25, "rankSpacing": 35}} }%%
flowchart LR
User["User Code"]
-
+
subgraph Factory[" "]
Entry["Conversation()"]
end
@@ -5131,26 +5139,26 @@ flowchart LR
Local["LocalConversation
Direct execution"]
Remote["RemoteConversation
Via agent-server API"]
end
-
+
subgraph Core[" "]
State["ConversationState
• agent
workspace • stats • ..."]
EventLog["ConversationState.events
Event storage"]
end
-
+
User --> Entry
Entry -.->|LocalWorkspace| Local
Entry -.->|RemoteWorkspace| Remote
-
+
Local --> State
Remote --> State
-
+
State --> EventLog
-
+
classDef factory fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef impl fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef core fill:#fff4df,stroke:#b7791f,stroke-width:2px
classDef service fill:#e9f9ef,stroke:#2f855a,stroke-width:1.5px
-
+
class Entry factory
class Local,Remote impl
class State,EventLog core
@@ -5178,11 +5186,11 @@ flowchart LR
Check{Workspace Type?}
Local["LocalConversation
Agent runs in-process"]
Remote["RemoteConversation
Agent runs via API"]
-
+
Input --> Check
Check -->|str or LocalWorkspace| Local
Check -->|RemoteWorkspace| Remote
-
+
style Input fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Local fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Remote fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
@@ -5204,13 +5212,13 @@ flowchart TB
Start["State Update Request"]
Lock["Acquire FIFO Lock"]
Decision{New Event?}
-
+
StateOnly["Update State Fields
stats, status, metadata"]
EventPath["Append to Event Log
messages, actions, observations"]
-
+
Callback["Trigger Callbacks"]
Release["Release Lock"]
-
+
Start --> Lock
Lock --> Decision
Decision -->|No| StateOnly
@@ -5218,7 +5226,7 @@ flowchart TB
StateOnly --> Callback
EventPath --> Callback
Callback --> Release
-
+
style Decision fill:#fff4df,stroke:#b7791f,stroke-width:2px
style EventPath fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style StateOnly fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
@@ -5305,13 +5313,13 @@ flowchart LR
WS["Workspace"]
Tools["Tools"]
LLM["LLM"]
-
+
Conv -->|Delegates to| Agent
Conv -->|Configures| WS
Agent -.->|Updates| Conv
Agent -->|Uses| Tools
Agent -->|Queries| LLM
-
+
style Conv fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style WS fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -5339,52 +5347,52 @@ The **OpenHands Software Agent SDK** is part of the [OpenHands V1](https://openh
## Optional Isolation over Mandatory Sandboxing
-**V0 Challenge:**
-Every tool call in V0 executed in a sandboxed Docker container by default. While this guaranteed reproducibility and security, it also created friction — the agent and sandbox ran as separate processes, states diverged easily, and multi-tenant workloads could crash each other.
+**V0 Challenge:**
+Every tool call in V0 executed in a sandboxed Docker container by default. While this guaranteed reproducibility and security, it also created friction — the agent and sandbox ran as separate processes, states diverged easily, and multi-tenant workloads could crash each other.
Moreover, with the rise of the Model Context Protocol (MCP), which assumes local execution and direct access to user environments, V0's rigid isolation model became incompatible.
-**V1 Principle:**
-**Sandboxing should be opt-in, not universal.**
-V1 unifies agent and tool execution within a single process by default, aligning with MCP's local-execution model.
+**V1 Principle:**
+**Sandboxing should be opt-in, not universal.**
+V1 unifies agent and tool execution within a single process by default, aligning with MCP's local-execution model.
When isolation is needed, the same stack can be transparently containerized, maintaining flexibility without complexity.
## Stateless by Default, One Source of Truth for State
-**V0 Challenge:**
+**V0 Challenge:**
V0 relied on mutable Python objects and dynamic typing, which led to silent inconsistencies — failed session restores, version drift, and non-deterministic behavior. Each subsystem tracked its own transient state, making debugging and recovery painful.
-**V1 Principle:**
-**Keep everything stateless, with exactly one mutable state.**
-All components (agents, tools, LLMs, and configurations) are immutable Pydantic models validated at construction.
+**V1 Principle:**
+**Keep everything stateless, with exactly one mutable state.**
+All components (agents, tools, LLMs, and configurations) are immutable Pydantic models validated at construction.
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
-**V0 Challenge:**
-The same codebase powered the CLI, web interface, and integrations (e.g., Github, Gitlab, etc). Over time, application-specific conditionals and prompts polluted the agent core, making it brittle.
+**V0 Challenge:**
+The same codebase powered the CLI, web interface, and integrations (e.g., Github, Gitlab, etc). Over time, application-specific conditionals and prompts polluted the agent core, making it brittle.
Heavy research dependencies and benchmark integrations further bloated production builds.
-**V1 Principle:**
-**Maintain strict separation of concerns.**
-V1 divides the system into stable, isolated layers: the [SDK (agent core)](/sdk/arch/overview#1-sdk-%E2%80%93-openhands-sdk), [tools (set of tools)](/sdk/arch/overview#2-tools-%E2%80%93-openhands-tools), [workspace (sandbox)](/sdk/arch/overview#3-workspace-%E2%80%93-openhands-workspace), and [agent server (server that runs inside sandbox)](/sdk/arch/overview#4-agent-server-%E2%80%93-openhands-agent-server).
+**V1 Principle:**
+**Maintain strict separation of concerns.**
+V1 divides the system into stable, isolated layers: the [SDK (agent core)](/sdk/arch/overview#1-sdk-%E2%80%93-openhands-sdk), [tools (set of tools)](/sdk/arch/overview#2-tools-%E2%80%93-openhands-tools), [workspace (sandbox)](/sdk/arch/overview#3-workspace-%E2%80%93-openhands-workspace), and [agent server (server that runs inside sandbox)](/sdk/arch/overview#4-agent-server-%E2%80%93-openhands-agent-server).
Applications communicate with the agent via APIs rather than embedding it directly, ensuring research and production can evolve independently.
## Composable Components for Extensibility
-**V0 Challenge:**
+**V0 Challenge:**
Because agent logic was hard-coded into the core application, extending behavior (e.g., adding new tools or entry points) required branching logic for different entrypoints. This rigidity limited experimentation and discouraged contributions.
-**V1 Principle:**
-**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.
+**V1 Principle:**
+**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.
### Events
@@ -5410,37 +5418,37 @@ The Event System has four primary responsibilities:
flowchart TB
Base["Event
Base class"]
LLMBase["LLMConvertibleEvent
Abstract base"]
-
+
subgraph LLMTypes["LLM-Convertible Events
Visible to the LLM"]
Message["MessageEvent
User/assistant text"]
Action["ActionEvent
Tool calls"]
System["SystemPromptEvent
Initial system prompt"]
CondSummary["CondensationSummaryEvent
Condenser summary"]
-
+
ObsBase["ObservationBaseEvent
Base for tool responses"]
Observation["ObservationEvent
Tool results"]
UserReject["UserRejectObservation
User rejected action"]
AgentError["AgentErrorEvent
Agent error"]
end
-
+
subgraph Internals["Internal Events
NOT visible to the LLM"]
ConvState["ConversationStateUpdateEvent
State updates"]
CondReq["CondensationRequest
Request compression"]
Cond["Condensation
Compression result"]
Pause["PauseEvent
User pause"]
end
-
+
Base --> LLMBase
Base --> Internals
LLMBase --> LLMTypes
ObsBase --> Observation
ObsBase --> UserReject
ObsBase --> AgentError
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Base,LLMBase,Message,Action,SystemPromptEvent primary
class ObsBase,Observation,UserReject,AgentError secondary
class ConvState,CondReq,Cond,Pause tertiary
@@ -5493,12 +5501,12 @@ flowchart LR
Group["Group ActionEvents
by llm_response_id"]
Convert["Convert to Messages"]
LLM["LLM Input"]
-
+
Events --> Filter
Filter --> Group
Group --> Convert
Convert --> LLM
-
+
style Filter fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Group fill:#fff4df,stroke:#b7791f,stroke-width:2px
style Convert fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
@@ -5565,13 +5573,13 @@ flowchart LR
Conversation["Conversation"]
Tools["Tools"]
Services["Auxiliary Services"]
-
+
Agent -->|Reads| Events
Agent -->|Writes| Events
Conversation -->|Manages| Events
Tools -->|Creates| Events
Events -.->|Stream| Services
-
+
style Events fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Conversation fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -5637,37 +5645,37 @@ flowchart TB
JSON["JSON Files
config/llm.json"]
Code["Programmatic
LLM(...)"]
end
-
+
subgraph Core["Core LLM"]
Model["LLM Model
Pydantic configuration"]
Pipeline["Request Pipeline
Retry, timeout, telemetry"]
end
-
+
subgraph Backend["LiteLLM Backend"]
Providers["100+ Providers
OpenAI, Anthropic, etc."]
end
-
+
subgraph Output["Telemetry"]
Usage["Token Usage"]
Cost["Cost Tracking"]
Latency["Latency Metrics"]
end
-
+
Env --> Model
JSON --> Model
Code --> Model
-
+
Model --> Pipeline
Pipeline --> Providers
-
+
Pipeline --> Usage
Pipeline --> Cost
Pipeline --> Latency
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Model primary
class Pipeline secondary
class LiteLLM tertiary
@@ -5698,10 +5706,10 @@ flowchart LR
Code["Python Code"]
LLM["LLM(model=...)"]
Agent["Agent"]
-
+
Code --> LLM
LLM --> Agent
-
+
style LLM fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
```
@@ -5762,30 +5770,30 @@ If you need to include secrets in JSON, use `llm.model_dump_json(exclude_none=Tr
flowchart TB
Request["completion() or responses() call"]
Validate["Validate Config"]
-
+
Attempt["LiteLLM Request"]
Success{"Success?"}
-
+
Retry{"Retries
remaining?"}
Wait["Exponential Backoff"]
-
+
Telemetry["Record Telemetry"]
Response["Return Response"]
Error["Raise Error"]
-
+
Request --> Validate
Validate --> Attempt
Attempt --> Success
-
+
Success -->|Yes| Telemetry
Success -->|No| Retry
-
+
Retry -->|Yes| Wait
Retry -->|No| Error
-
+
Wait --> Attempt
Telemetry --> Response
-
+
style Attempt fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Retry fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Telemetry fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -5809,37 +5817,37 @@ In addition to the standard chat completion API, the LLM system supports [OpenAI
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%%
flowchart TB
Check{"Model supports
Responses API?"}
-
+
subgraph Standard["Standard Path"]
ChatFormat["Format as
Chat Messages"]
ChatCall["litellm.completion()"]
end
-
+
subgraph ResponsesPath["Responses Path"]
RespFormat["Format as
instructions + input[]"]
RespCall["litellm.responses()"]
end
-
+
ChatResponse["ModelResponse"]
RespResponse["ResponsesAPIResponse"]
-
+
Parse["Parse to Message"]
Return["LLMResponse"]
-
+
Check -->|No| ChatFormat
Check -->|Yes| RespFormat
-
+
ChatFormat --> ChatCall
RespFormat --> RespCall
-
+
ChatCall --> ChatResponse
RespCall --> RespResponse
-
+
ChatResponse --> Parse
RespResponse --> Parse
-
+
Parse --> Return
-
+
style RespFormat fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style RespCall fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -5866,7 +5874,7 @@ Software Agent SDK uses LiteLLM for provider abstraction:
flowchart TB
SDK["Software Agent SDK"]
LiteLLM["LiteLLM"]
-
+
subgraph Providers["100+ Providers"]
OpenAI["OpenAI"]
Anthropic["Anthropic"]
@@ -5874,14 +5882,14 @@ flowchart TB
Azure["Azure"]
Others["..."]
end
-
+
SDK --> LiteLLM
LiteLLM --> OpenAI
LiteLLM --> Anthropic
LiteLLM --> Google
LiteLLM --> Azure
LiteLLM --> Others
-
+
style LiteLLM fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style SDK fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -5928,23 +5936,23 @@ LLM requests automatically collect metrics:
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%%
flowchart LR
Request["LLM Request"]
-
+
subgraph Metrics
Tokens["Token Counts
Input/Output"]
Cost["Cost
USD"]
Latency["Latency
ms"]
end
-
+
Events["Event Log"]
-
+
Request --> Tokens
Request --> Cost
Request --> Latency
-
+
Tokens --> Events
Cost --> Events
Latency --> Events
-
+
style Metrics fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Events fill:#fff4df,stroke:#b7791f,stroke-width:2px
```
@@ -5987,13 +5995,13 @@ flowchart LR
Events["Events"]
Security["Security Analyzer"]
Condenser["Context Condenser"]
-
+
Agent -->|Uses| LLM
LLM -->|Records| Events
Security -.->|Optional| LLM
Condenser -.->|Optional| LLM
Conversation -->|Provides context| Agent
-
+
style LLM fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Events fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6039,38 +6047,38 @@ flowchart TB
Sync["MCPClient
Sync/Async bridge"]
Async["AsyncMCPClient
FastMCP base"]
end
-
+
subgraph Bridge["Tool Bridge"]
Def["MCPToolDefinition
Schema conversion"]
Exec["MCPToolExecutor
Execution handler"]
end
-
+
subgraph Integration["Agent Integration"]
Action["MCPToolAction
Dynamic model"]
Obs["MCPToolObservation
Result wrapper"]
end
-
+
subgraph External["External"]
Server["MCP Server
stdio/HTTP"]
Tools["External Tools"]
end
-
+
Sync --> Async
Async --> Server
-
+
Server --> Def
Def --> Exec
-
+
Exec --> Action
Action --> Server
Server --> Obs
-
+
Server -.->|Spawns| Tools
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Sync,Async primary
class Def,Exec secondary
class Action,Obs tertiary
@@ -6101,14 +6109,14 @@ flowchart TB
Async["Async MCP Call"]
Server["MCP Server"]
Result["Result"]
-
+
Sync --> Bridge
Bridge --> Executor
Executor --> Async
Async --> Server
Server --> Result
Result --> Sync
-
+
style Bridge fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Executor fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Async fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6159,23 +6167,23 @@ flowchart TB
Config["MCP Config"]
Spawn["Spawn Server"]
List["List Tools"]
-
+
subgraph Convert["Convert Each Tool"]
Schema["MCP Schema"]
Action["Generate Action Model"]
Def["Create ToolDefinition"]
end
-
+
Register["Register in ToolRegistry"]
-
+
Config --> Spawn
Spawn --> List
List --> Schema
-
+
Schema --> Action
Action --> Def
Def --> Register
-
+
style Spawn fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Action fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Register fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6201,11 +6209,11 @@ flowchart LR
Parse["Parse Parameters"]
Model["Dynamic Pydantic Model
MCPToolAction"]
Def["ToolDefinition
SDK format"]
-
+
MCP --> Parse
Parse --> Model
Model --> Def
-
+
style Parse fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Model fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -6254,15 +6262,15 @@ flowchart TB
Agent["Agent generates action"]
Action["MCPToolAction"]
Executor["MCPToolExecutor"]
-
+
Convert["Convert to MCP format"]
Call["MCP call_tool"]
Server["MCP Server"]
-
+
Result["MCP Result"]
Obs["MCPToolObservation"]
Return["Return to Agent"]
-
+
Agent --> Action
Action --> Executor
Executor --> Convert
@@ -6271,7 +6279,7 @@ flowchart TB
Server --> Result
Result --> Obs
Obs --> Return
-
+
style Executor fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Call fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Obs fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6297,10 +6305,10 @@ flowchart LR
Executor["MCPToolExecutor"]
Client["MCP Client"]
Name["tool_name"]
-
+
Executor -->|Uses| Client
Executor -->|Knows| Name
-
+
style Executor fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Client fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -6324,32 +6332,32 @@ flowchart TB
Spawn["Spawn MCP Servers"]
Discover["Discover Tools"]
Register["Register Tools"]
-
+
Ready["Agent Ready"]
-
+
Step["Agent Step"]
LLM["LLM Tool Call"]
Execute["Execute MCP Tool"]
Result["Return Observation"]
-
+
End["End Conversation"]
Cleanup["Close MCP Clients"]
-
+
Load --> Start
Start --> Spawn
Spawn --> Discover
Discover --> Register
Register --> Ready
-
+
Ready --> Step
Step --> LLM
LLM --> Execute
Execute --> Result
Result --> Step
-
+
Step --> End
End --> Cleanup
-
+
style Spawn fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Execute fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Cleanup fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6372,22 +6380,22 @@ MCP tools can include metadata hints for agents:
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%%
flowchart LR
Tool["MCP Tool"]
-
+
subgraph Annotations
ReadOnly["readOnlyHint"]
Destructive["destructiveHint"]
Progress["progressEnabled"]
end
-
+
Security["Security Analysis"]
-
+
Tool --> ReadOnly
Tool --> Destructive
Tool --> Progress
-
+
ReadOnly --> Security
Destructive --> Security
-
+
style Destructive fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Security fill:#fff4df,stroke:#b7791f,stroke-width:2px
```
@@ -6414,12 +6422,12 @@ flowchart LR
Tools["Tool Registry"]
Agent["Agent"]
Security["Security"]
-
+
Skills -->|Configures| MCP
MCP -->|Registers| Tools
Agent -->|Uses| Tools
MCP -->|Provides hints| Security
-
+
style MCP fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Skills fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Agent fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -6478,7 +6486,7 @@ graph TB
end
SDK[Software Agent SDK
openhands.sdk + tools + workspace]
-
+
subgraph External["External Services"]
LLM[LLM Providers
OpenAI, Anthropic, etc.]
Runtime[Runtime Services
Docker, Remote API, etc.]
@@ -6487,14 +6495,14 @@ graph TB
UI --> SDK
CLI --> SDK
Custom --> SDK
-
+
SDK --> LLM
SDK --> Runtime
-
+
classDef interface fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef sdk fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef external fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class UI,CLI,Custom interface
class SDK sdk
class LLM,Runtime external
@@ -6531,9 +6539,9 @@ pip install openhands-sdk openhands-tools
flowchart LR
SDK["openhands.sdk
Agent · LLM · Conversation
+ LocalWorkspace"]:::sdk
Tools["openhands.tools
BashTool · FileEditor · GrepTool · …"]:::tools
-
+
SDK -->|uses| Tools
-
+
classDef sdk fill:#e8f3ff,stroke:#2b6cb0,color:#0f2a45,stroke-width:2px,rx:8,ry:8
classDef tools fill:#e9f9ef,stroke:#2f855a,color:#14532d,stroke-width:2px,rx:8,ry:8
```
@@ -6556,31 +6564,31 @@ pip install openhands-sdk openhands-tools openhands-workspace openhands-agent-se
```mermaid
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 20, "rankSpacing": 30}} }%%
flowchart LR
-
+
WSBase["openhands.sdk
Base Classes:
Workspace · Local · Remote"]:::sdk
-
+
subgraph WS[" "]
direction LR
Docker["openhands.workspace DockerWorkspace
extends RemoteWorkspace"]:::ws
Remote["openhands.workspace RemoteAPIWorkspace
extends RemoteWorkspace"]:::ws
end
-
+
Server["openhands.agent_server
FastAPI + WebSocket"]:::server
Agent["openhands.sdk
Agent · LLM · Conversation"]:::sdk
Tools["openhands.tools
BashTool · FileEditor · …"]:::tools
-
+
WSBase -.->|extended by| Docker
WSBase -.->|extended by| Remote
Docker -->|spawns container with| Server
Remote -->|connects via HTTP to| Server
Server -->|runs| Agent
Agent -->|uses| Tools
-
+
classDef sdk fill:#e8f3ff,stroke:#2b6cb0,color:#0f2a45,stroke-width:1.1px,rx:8,ry:8
classDef ws fill:#fff4df,stroke:#b7791f,color:#5b3410,stroke-width:1.1px,rx:8,ry:8
classDef server fill:#f3e8ff,stroke:#7c3aed,color:#3b2370,stroke-width:1.1px,rx:8,ry:8
classDef tools fill:#e9f9ef,stroke:#2f855a,color:#14532d,stroke-width:1.1px,rx:8,ry:8
-
+
style WS stroke:#b7791f,stroke-width:1.5px,stroke-dasharray: 4 3,rx:8,ry:8,fill:none
```
@@ -6674,7 +6682,7 @@ sequenceDiagram
participant Agent
participant LLM
participant Tool
-
+
You->>Conversation: "Create hello.txt"
Conversation->>Agent: Process message
Agent->>LLM: What should I do?
@@ -6699,17 +6707,17 @@ graph TB
subgraph "Your Code (Unchanged)"
Code["Agent + Tools + LLM"]
end
-
+
subgraph "Deployment Options"
Local["Local
Direct execution"]
Docker["Docker
Containerized"]
Remote["Remote
Multi-user server"]
end
-
+
Code -->|LocalWorkspace| Local
Code -->|DockerWorkspace| Docker
Code -->|RemoteAPIWorkspace| Remote
-
+
style Code fill:#e1f5fe
style Local fill:#e8f5e8
style Docker fill:#e8f5e8
@@ -6776,18 +6784,18 @@ The SDK package handles:
```mermaid
graph TB
Conv[Conversation
Lifecycle Manager] --> Agent[Agent
Reasoning Loop]
-
+
Agent --> LLM[LLM
Language Model]
Agent --> Tools[Tool System
Capabilities]
Agent --> Micro[Skills
Behavior Modules]
Agent --> Cond[Condenser
Memory Manager]
-
+
Tools --> Workspace[Workspace
Execution]
-
+
Conv --> Events[Events
Communication]
Tools --> MCP[MCP
External Tools]
Workspace --> Security[Security
Validation]
-
+
style Conv fill:#e1f5fe
style Agent fill:#f3e5f5
style LLM fill:#e8f5e8
@@ -6924,7 +6932,7 @@ graph TB
2. **Observation**: Output schema (what the tool returns)
3. **ToolExecutor**: Logic that transforms Action → Observation
-**Why this pattern?**
+**Why this pattern?**
- Type safety catches errors early
- LLMs get accurate schemas for tool calling
- Tools are testable in isolation
@@ -7242,46 +7250,46 @@ flowchart TB
subgraph Interface["Abstract Interface"]
Base["SecurityAnalyzerBase
Abstract analyzer"]
end
-
+
subgraph Implementations["Concrete Analyzers"]
LLM["LLMSecurityAnalyzer
Inline risk prediction"]
NoOp["NoOpSecurityAnalyzer
No analysis"]
end
-
+
subgraph Risk["Risk Levels"]
Low["LOW
Safe operations"]
Medium["MEDIUM
Moderate risk"]
High["HIGH
Dangerous ops"]
Unknown["UNKNOWN
Unanalyzed"]
end
-
+
subgraph Policy["Confirmation Policy"]
Check["should_require_confirmation()"]
Mode["Confirmation Mode"]
Decision["Require / Allow"]
end
-
+
Base --> LLM
Base --> NoOp
-
+
Implementations --> Low
Implementations --> Medium
Implementations --> High
Implementations --> Unknown
-
+
Low --> Check
Medium --> Check
High --> Check
Unknown --> Check
-
+
Check --> Mode
Mode --> Decision
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
classDef danger fill:#ffe8e8,stroke:#dc2626,stroke-width:2px
-
+
class Base primary
class LLM secondary
class High danger
@@ -7307,20 +7315,20 @@ Security analyzers return one of four risk levels:
flowchart TB
Action["ActionEvent"]
Analyze["Security Analyzer"]
-
+
subgraph Levels["Risk Levels"]
Low["LOW
Read-only, safe"]
Medium["MEDIUM
Modify files"]
High["HIGH
Delete, execute"]
Unknown["UNKNOWN
Not analyzed"]
end
-
+
Action --> Analyze
Analyze --> Low
Analyze --> Medium
Analyze --> High
Analyze --> Unknown
-
+
style Low fill:#d1fae5,stroke:#10b981,stroke-width:2px
style Medium fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style High fill:#ffe8e8,stroke:#dc2626,stroke-width:2px
@@ -7351,13 +7359,13 @@ flowchart TB
Extract["Extract security_risk
from arguments"]
ActionEvent["ActionEvent
with security_risk set"]
Analyzer["LLMSecurityAnalyzer
returns security_risk"]
-
+
Schema --> LLM
LLM --> ToolCall
ToolCall --> Extract
Extract --> ActionEvent
ActionEvent --> Analyzer
-
+
style Schema fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Extract fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Analyzer fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -7400,9 +7408,9 @@ flowchart LR
Action["ActionEvent"]
NoOp["NoOpSecurityAnalyzer"]
Unknown["SecurityRisk.UNKNOWN"]
-
+
Action --> NoOp --> Unknown
-
+
style NoOp fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
```
@@ -7433,20 +7441,20 @@ flowchart TB
CheckUnknown{"Risk ==
UNKNOWN?"}
UseConfirmUnknown{"confirm_unknown
setting?"}
CheckThreshold{"risk.is_riskier
(threshold)?"}
-
+
Confirm["Require Confirmation"]
Allow["Allow Execution"]
-
+
Risk --> CheckUnknown
CheckUnknown -->|Yes| UseConfirmUnknown
CheckUnknown -->|No| CheckThreshold
-
+
UseConfirmUnknown -->|True| Confirm
UseConfirmUnknown -->|False| Allow
-
+
CheckThreshold -->|Yes| Confirm
CheckThreshold -->|No| Allow
-
+
style CheckUnknown fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Confirm fill:#ffe8e8,stroke:#dc2626,stroke-width:2px
style Allow fill:#d1fae5,stroke:#10b981,stroke-width:2px
@@ -7503,12 +7511,12 @@ flowchart LR
Conversation["Conversation"]
Tools["Tools"]
MCP["MCP Tools"]
-
+
Agent -->|Validates actions| Security
Security -->|Checks| Tools
Security -->|Uses hints| MCP
Conversation -->|Pauses for confirmation| Agent
-
+
style Security fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Conversation fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -7554,47 +7562,47 @@ flowchart TB
Knowledge["Knowledge Skill
trigger: KeywordTrigger"]
Task["Task Skill
trigger: TaskTrigger"]
end
-
+
subgraph Triggers["Trigger Evaluation"]
Always["Always Active
Repository guidelines"]
Keyword["Keyword Match
String matching on user messages"]
TaskMatch["Keyword Match + Inputs
Same as KeywordTrigger + user inputs"]
end
-
+
subgraph Content["Skill Content"]
Markdown["Markdown with Frontmatter"]
Dynamic["Dynamic Commands
!`command` execution"]
MCPTools["MCP Tools Config
Repo skills only"]
Inputs["Input Metadata
Task skills only"]
end
-
+
subgraph Integration["Agent Integration"]
Context["Agent Context"]
Prompt["System Prompt"]
end
-
+
Repo --> Always
Knowledge --> Keyword
Task --> TaskMatch
-
+
Always --> Markdown
Keyword --> Markdown
TaskMatch --> Markdown
-
+
Markdown -.->|Optional| Dynamic
Repo -.->|Optional| MCPTools
Task -.->|Requires| Inputs
-
+
Markdown --> Context
Dynamic --> Context
MCPTools --> Context
Context --> Prompt
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
classDef dynamic fill:#e9f9ef,stroke:#2f855a,stroke-width:2px
-
+
class Repo,Knowledge,Task primary
class Always,Keyword,TaskMatch secondary
class Context tertiary
@@ -7627,11 +7635,11 @@ flowchart LR
Parse["Parse Frontmatter"]
Skill["Skill(trigger=None)"]
Context["Always in Context"]
-
+
File --> Parse
Parse --> Skill
Skill --> Context
-
+
style Skill fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Context fill:#fff4df,stroke:#b7791f,stroke-width:2px
```
@@ -7664,13 +7672,13 @@ flowchart TB
Activate["Activate Skill"]
Skip["Skip Skill"]
Context["Add to Context"]
-
+
User --> Check
Check --> Match
Match -->|Yes| Activate
Match -->|No| Skip
Activate --> Context
-
+
style Check fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Activate fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -7704,13 +7712,13 @@ flowchart TB
Template["Apply Template"]
Context["Add to Context"]
Skip["Skip Skill"]
-
+
User --> Match
Match -->|Yes| Inputs
Match -->|No| Skip
Inputs --> Template
Template --> Context
-
+
style Match fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Template fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -7744,35 +7752,35 @@ Skills are evaluated at different points in the agent lifecycle:
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%%
flowchart TB
Start["Agent Step Start"]
-
+
Repo["Check Repository Skills
trigger: None"]
AddRepo["Always Add to Context"]
-
+
Message["Check User Message"]
Keyword["Match Keyword Triggers"]
AddKeyword["Add Matched Skills"]
-
+
TaskType["Check Task Type"]
TaskMatch["Match Task Triggers"]
AddTask["Add Task Skill"]
-
+
Build["Build Agent Context"]
-
+
Start --> Repo
Repo --> AddRepo
-
+
Start --> Message
Message --> Keyword
Keyword --> AddKeyword
-
+
Start --> TaskType
TaskType --> TaskMatch
TaskMatch --> AddTask
-
+
AddRepo --> Build
AddKeyword --> Build
AddTask --> Build
-
+
style Repo fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Keyword fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style TaskMatch fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -7799,11 +7807,11 @@ flowchart LR
MCPConfig["mcp_tools Config"]
Client["MCP Client"]
Tools["Tool Registry"]
-
+
Skill -->|Contains| MCPConfig
MCPConfig -->|Spawns| Client
Client -->|Registers| Tools
-
+
style Skill fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style MCPConfig fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Tools fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -7892,12 +7900,12 @@ flowchart LR
Context["Agent Context"]
Agent["Agent"]
MCP["MCP Client"]
-
+
Skills -->|Injects content| Context
Skills -.->|Spawns tools| MCP
Context -->|System prompt| Agent
MCP -->|Tool| Agent
-
+
style Skills fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Context fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Agent fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -7943,7 +7951,7 @@ flowchart TB
Observation["Observation
Output schema"]
Executor["Executor
Business logic"]
end
-
+
subgraph Framework["Tool Framework"]
Base["ToolBase
Abstract base"]
Impl["Tool Implementation
Concrete tool"]
@@ -7955,7 +7963,7 @@ flowchart TB
ToolSpec["Tool Spec
name + params"]
Base -.->|Extends| Impl
-
+
ToolSpec -->|resolve_tool| Registry
Registry -->|Create instances| Impl
Impl -->|Available in| Agent
@@ -7964,11 +7972,11 @@ flowchart TB
Agent -->|Parse & validate| Action
Agent -->|Execute via Tool.\_\_call\_\_| Executor
Executor -->|Return| Observation
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Base primary
class Action,Observation,Executor secondary
class Registry tertiary
@@ -8002,14 +8010,14 @@ flowchart TB
WrapObs["ObservationEvent
wraps Observation"]
Error["AgentErrorEvent"]
end
-
+
subgraph ToolSystem["Tool System"]
ActionType["Action
Pydantic model"]
ToolCall2["tool.\_\_call\_\_(action)
type-safe execution"]
Execute["ToolExecutor
business logic"]
ObsType["Observation
Pydantic model"]
end
-
+
ToolCall --> ParseJSON
ParseJSON -->|Valid JSON| CreateAction
ParseJSON -->|Invalid JSON| Error
@@ -8020,7 +8028,7 @@ flowchart TB
ToolCall2 --> Execute
Execute --> ObsType
ObsType --> WrapObs
-
+
style ToolSystem fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style ActionType fill:#ddd6fe,stroke:#7c3aed,stroke-width:2px
@@ -8047,11 +8055,11 @@ flowchart LR
Obs["Define Observation
with to_llm_content"]
Exec["Define Executor
stateless logic"]
Tool["ToolDefinition(...,
executor=Executor())"]
-
+
Action --> Tool
Obs --> Tool
Exec --> Tool
-
+
style Tool fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
```
@@ -8073,12 +8081,12 @@ flowchart LR
Exec["Define Executor
with \_\_init\_\_ and state"]
Subclass["class MyTool(ToolDefinition)
with create() method"]
Instance["Return [MyTool(...,
executor=instance)]"]
-
+
Action --> Subclass
Obs --> Subclass
Exec --> Subclass
Subclass --> Instance
-
+
style Instance fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -8096,21 +8104,21 @@ flowchart TB
P1E["Define ToolExecutor
with \_\_call\_\_()"]
P1T["ToolDefinition(...,
executor=Executor())"]
end
-
+
subgraph Pattern2["Pattern 2: Subclass with Factory"]
P2A["Define Action/Observation
with visualize/to_llm_content"]
P2E["Define Stateful ToolExecutor
with \_\_init\_\_() and \_\_call\_\_()"]
P2C["class MyTool(ToolDefinition)
@classmethod create()"]
P2I["Return [MyTool(...,
executor=instance)]"]
end
-
+
P1A --> P1E
P1E --> P1T
-
+
P2A --> P2E
P2E --> P2C
P2C --> P2I
-
+
style P1T fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style P2I fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -8154,20 +8162,20 @@ The registry enables **dynamic tool discovery** and instantiation from tool spec
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%%
flowchart LR
ToolSpec["Tool Spec
name + params"]
-
+
subgraph Registry["Tool Registry"]
Resolver["Resolver
name → factory"]
Factory["Factory
create(params)"]
end
-
+
Instance["Tool Instance
with executor"]
Agent["Agent"]
-
+
ToolSpec -->|"resolve_tool(spec)"| Resolver
Resolver -->|Lookup factory| Factory
Factory -->|"create(**params)"| Instance
Instance -->|Used by| Agent
-
+
style Registry fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Factory fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -8231,18 +8239,18 @@ flowchart TB
Server["MCP Server
stdio/HTTP"]
ExtTools["External Tools"]
end
-
+
subgraph Bridge["MCP Integration Layer"]
MCPClient["MCPClient
Sync/Async bridge"]
Convert["Schema Conversion
MCP → MCPToolDefinition"]
MCPExec["MCPToolExecutor
Bridges to MCP calls"]
end
-
+
subgraph Agent["Agent System"]
ToolsMap["tools_map
str -> ToolDefinition"]
AgentLogic["Agent Execution"]
end
-
+
Server -.->|Spawns| ExtTools
MCPClient --> Server
Server --> Convert
@@ -8251,11 +8259,11 @@ flowchart TB
ToolsMap --> AgentLogic
AgentLogic -->|Tool call| MCPExec
MCPExec --> MCPClient
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef external fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class MCPClient primary
class Convert,MCPExec secondary
class Server,ExtTools external
@@ -8284,13 +8292,13 @@ flowchart LR
Loop["Background Event Loop"]
Async["Async MCP Call"]
Result["Return Result"]
-
+
Sync --> Bridge
Bridge --> Loop
Loop --> Async
Async --> Result
Result --> Sync
-
+
style Bridge fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Loop fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -8311,16 +8319,16 @@ flowchart TB
Config["MCP Server Config
command + args"]
Spawn["Spawn Server Process
MCPClient"]
List["List Available Tools
client.list_tools()"]
-
+
subgraph Convert["For Each MCP Tool"]
Store["Store MCP metadata
name, description, inputSchema"]
CreateExec["Create MCPToolExecutor
bound to tool + client"]
Def["Create MCPToolDefinition
generic MCPToolAction type"]
end
-
+
Register["Add to Agent's tools_map
bypasses ToolRegistry"]
Ready["Tools Available
Dynamic models created on-demand"]
-
+
Config --> Spawn
Spawn --> List
List --> Store
@@ -8328,7 +8336,7 @@ flowchart TB
CreateExec --> Def
Def --> Register
Register --> Ready
-
+
style Spawn fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Def fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Register fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -8389,18 +8397,18 @@ flowchart TB
Native["Native Tools"]
MCP["MCP Tools"]
end
-
+
Registry["Tool Registry
resolve_tool"]
ToolsMap["Agent.tools_map
Merged tool dict"]
-
+
subgraph AgentSystem["Agent System"]
Agent["Agent Logic"]
LLM["LLM"]
end
-
+
Security["Security Analyzer"]
Conversation["Conversation State"]
-
+
Native -->|register_tool| Registry
Registry --> ToolsMap
MCP -->|create_mcp_tools| ToolsMap
@@ -8408,7 +8416,7 @@ flowchart TB
Agent -->|Execute tools| ToolsMap
ToolsMap -.->|Action risk| Security
ToolsMap -.->|Read state| Conversation
-
+
style ToolsMap fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Agent fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style Security fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -8455,38 +8463,38 @@ flowchart TB
subgraph Interface["Abstract Interface"]
Base["BaseWorkspace
Abstract base class"]
end
-
+
subgraph Implementations["Concrete Implementations"]
Local["LocalWorkspace
Direct subprocess"]
Remote["RemoteWorkspace
HTTP API calls"]
end
-
+
subgraph Operations["Core Operations"]
Command["execute_command()"]
Upload["file_upload()"]
Download["file_download()"]
Context["__enter__ / __exit__"]
end
-
+
subgraph Targets["Execution Targets"]
Process["Local Process"]
Container["Docker Container"]
Server["Remote Server"]
end
-
+
Base --> Local
Base --> Remote
-
+
Base -.->|Defines| Operations
-
+
Local --> Process
Remote --> Container
Remote --> Server
-
+
classDef primary fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
classDef secondary fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px
-
+
class Base primary
class Local,Remote secondary
class Command,Upload tertiary
@@ -8523,21 +8531,21 @@ flowchart TB
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%%
flowchart LR
Tool["Tool invokes
execute_command()"]
-
+
Decision{"Workspace
type?"}
-
+
LocalExec["subprocess.run()
Direct execution"]
RemoteExec["POST /command
HTTP API"]
-
+
Result["CommandResult
stdout, stderr, exit_code"]
-
+
Tool --> Decision
Decision -->|Local| LocalExec
Decision -->|Remote| RemoteExec
-
+
LocalExec --> Result
RemoteExec --> Result
-
+
style Decision fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style LocalExec fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style RemoteExec fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -8581,16 +8589,16 @@ The SDK provides remote workspace implementations in `openhands-workspace` packa
%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 50}} }%%
flowchart TB
Base["RemoteWorkspace
SDK base class"]
-
+
Docker["DockerWorkspace
Auto-spawn containers"]
API["RemoteAPIWorkspace
Connect to existing server"]
-
+
Base -.->|Extended by| Docker
Base -.->|Extended by| API
-
+
Docker -->|Creates| Container["Docker Container
with agent-server"]
API -->|Connects| Server["Remote Agent Server"]
-
+
style Base fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Docker fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
style API fill:#fff4df,stroke:#b7791f,stroke-width:2px
@@ -8604,7 +8612,7 @@ flowchart TB
| **DockerWorkspace** | Spawn container | Container | Multi-user, untrusted code |
| **RemoteAPIWorkspace** | Connect to URL | Remote server | Distributed systems, cloud |
-**Source:**
+**Source:**
- **DockerWorkspace**: [`openhands-workspace/openhands/workspace/docker`](https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-workspace/openhands/workspace/docker)
- **RemoteAPIWorkspace**: [`openhands-workspace/openhands/workspace/remote_api`](https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-workspace/openhands/workspace/remote_api)
@@ -8618,10 +8626,10 @@ flowchart LR
Workspace["Workspace"]
Conversation["Conversation"]
AgentServer["Agent Server"]
-
+
Conversation -->|Configures| Workspace
Workspace -.->|Remote type| AgentServer
-
+
style Workspace fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style Conversation fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px
```
@@ -9805,7 +9813,7 @@ def get_planning_tools() -> list[Tool]:
The planning agent uses:
- **GlobTool**: For discovering files and directories matching patterns
-- **GrepTool**: For searching specific content across files
+- **GrepTool**: For searching specific content across files
- **PlanningFileEditorTool**: For writing structured plans to `PLAN.md` only
This read-only approach (except for `PLAN.md`) keeps the agent focused on analysis without implementation distractions.
@@ -11767,6 +11775,17 @@ cd agent-sdk
uv run python examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py
```
+
+## Settings and Secrets API Examples
+
+The remote agent-server examples also include end-to-end scripts for settings-backed secrets and authenticated LLM configuration:
+
+- [examples/02_remote_agent_server/12_settings_and_secrets_api.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/12_settings_and_secrets_api.py) demonstrates storing secrets through the Settings and Secrets API, referencing them with `LookupSecret`, and cleaning them up after use.
+- [examples/02_remote_agent_server/13_workspace_get_llm.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py) demonstrates configuring LLM settings on an authenticated agent-server and retrieving them through `RemoteWorkspace.get_llm()`.
+
+
+
+
## Next Steps
- **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Connect to Runtime API service
@@ -12013,7 +12032,7 @@ ENV PYTHONPATH="/app:${PYTHONPATH}"
This example is available on GitHub: [examples/02_remote_agent_server/06_custom_tool/](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/06_custom_tool)
-```python icon="python" expandable examples/02_remote_agent_server/06_custom_tool/custom_tool_example.py
+```python icon="python" expandable examples/02_remote_agent_server/06_custom_tool/main.py
"""Example: Using custom tools with remote agent server.
This example demonstrates how to use custom tools with a remote agent server
@@ -12292,7 +12311,7 @@ The docker sandboxed agent server demonstrates how to run agents in isolated Doc
This provides complete isolation from the host system, making it ideal for production deployments, testing, and executing untrusted code safely.
-Use `DockerWorkspace` with a pre-built agent server image for the fastest startup. When you need to build your own image from a base image, switch to `DockerDevWorkspace`.
+Use `DockerWorkspace` with a pre-built agent server image for the fastest startup. When you need to build your own image from a base image, switch to `DockerDevWorkspace`.
the Docker sandbox image ships with features configured in the [Dockerfile](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-agent-server/openhands/agent_server/docker/Dockerfile) (e.g., secure defaults and services like VSCode and VNC exposed behind well-defined ports), which are not available in the local (non-Docker) agent server.
@@ -12779,7 +12798,7 @@ agent = get_default_agent(
When `cli_mode=False`, the agent gains access to browser automation tools for web interaction.
-When VNC is available and `extra_ports=True`, the browser will be opened in the VNC desktop to visualize agent's work. You can watch the browser in real-time via VNC. Demo video:
+When VNC is available and `extra_ports=True`, the browser will be opened in the VNC desktop to visualize agent's work. You can watch the browser in real-time via VNC. Demo video: