### Setup Script Example Source: https://docs.openhands.dev/openhands/usage/customization/repository An example of a setup script that can be placed in .openhands/setup.sh. This script runs when OpenHands starts working with the repository, useful for setting environment variables and installing dependencies. ```bash #!/bin/bash export MY_ENV_VAR="my value" sudo apt-get update sudo apt-get install -y lsof cd frontend && npm install ; cd .. ``` -------------------------------- ### Run SDK Examples: Hello World Source: https://docs.openhands.dev/sdk/getting-started This command executes the simple 'hello world' example for the OpenHands SDK. It's a good starting point to verify your setup. ```bash # Simple hello world uv run python examples/01_standalone_sdk/01_hello_world.py ``` -------------------------------- ### Full Web Interface Configuration Example Source: https://docs.openhands.dev/openhands/usage/cli/web-interface Starts the web interface with a custom host address (0.0.0.0 to bind to all interfaces) and a custom port (3000). ```bash openhands web --host 0.0.0.0 --port 3000 ``` -------------------------------- ### Ready-to-run Example: Image Input for Vision Tasks Source: https://docs.openhands.dev/sdk/guides/llm-image-input A complete example demonstrating how to set up an agent with a vision-capable LLM and send images for analysis. This script includes basic agent setup, tool configuration, and conversation flow for image-based tasks. ```python """OpenHands Agent SDK — Image Input Example. This script mirrors the basic setup from ``examples/01_hello_world.py`` but adds vision support by sending an image to the agent alongside text instructions. """ import os from pydantic import SecretStr from openhands.sdk import ( LLM, Agent, Conversation, Event, ImageContent, LLMConvertibleEvent, Message, TextContent, get_logger, ) from openhands.sdk.tool.spec import Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.task_tracker import TaskTrackerTool from openhands.tools.terminal import TerminalTool logger = get_logger(__name__) # Configure LLM (vision-capable model) api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") base_url = os.getenv("LLM_BASE_URL") llm = LLM( usage_id="vision-llm", model=model, base_url=base_url, api_key=SecretStr(api_key), ) assert llm.vision_is_active(), "The selected LLM model does not support vision input." cwd = os.getcwd() agent = Agent( llm=llm, tools=[ Tool( name=TerminalTool.name, ), Tool(name=FileEditorTool.name), Tool(name=TaskTrackerTool.name), ], ) llm_messages = [] # collect raw LLM messages for inspection def conversation_callback(event: Event) -> None: if isinstance(event, LLMConvertibleEvent): llm_messages.append(event.to_llm_message()) conversation = Conversation( agent=agent, callbacks=[conversation_callback], workspace=cwd ) IMAGE_URL = "https://github.com/OpenHands/docs/raw/main/openhands/static/img/logo.png" conversation.send_message( Message( role="user", content=[ TextContent( text=( "Study this image and describe the key elements you see. " "Summarize them in a short paragraph and suggest a catchy caption." ) ), ImageContent(image_urls=[IMAGE_URL]), ], ) ) conversation.run() conversation.send_message( "Great! Please save your description and caption into image_report.md." ) conversation.run() print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") ``` -------------------------------- ### Agent Initialization Example Source: https://docs.openhands.dev/sdk/guides/agent-server/api-reference/conversations/start-conversation Demonstrates how to initialize an Agent with an LLM and a list of tools. This is a fundamental setup for agent-based applications. ```python from openhands.sdk import LLM, Agent, Tool llm = LLM(model="claude-sonnet-4-2-20250514", api_key=SecretStr("key")) tools = [Tool(name="TerminalTool"), Tool(name="FileEditorTool")] agent = Agent(llm=llm, tools=tools) ``` -------------------------------- ### Runnable Conversation Forking Example Setup Source: https://docs.openhands.dev/sdk/guides/convo-fork This code sets up the necessary components for running the conversation forking example. It initializes the LLM and Agent, and defines the current working directory. ```python import os from openhands.sdk import LLM, Agent, Conversation, Tool from openhands.tools.terminal import TerminalTool # ----------------------------------------------------------------- # Setup # ----------------------------------------------------------------- llm = LLM( model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"), api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL", None), ) agent = Agent(llm=llm, tools=[Tool(name=TerminalTool.name)]) cwd = os.getcwd() ``` -------------------------------- ### Python Example: Loading and Managing Plugins Source: https://docs.openhands.dev/sdk/guides/plugins Demonstrates loading plugins from GitHub via Conversation, installing plugins to persistent storage (local and GitHub), listing tracked plugins, and managing their enabled/disabled states. This example requires the `openhands` SDK and its dependencies. ```python """Example: Loading and Managing Plugins This example demonstrates plugin loading and lifecycle management in the SDK: 1. Loading a plugin from GitHub via Conversation (PluginSource) 2. Installing plugins to persistent storage (local and GitHub) 3. Listing tracked plugins and loading only the enabled ones 4. Inspecting the `.installed.json` metadata file and `enabled` flag 5. Disabling and re-enabling a plugin without reinstalling it 6. Uninstalling plugins from persistent storage Plugins bundle skills, hooks, and MCP config together. Supported plugin sources: - Local path: /path/to/plugin - GitHub shorthand: github:owner/repo - Git URL: https://github.com/owner/repo.git - With ref: branch, tag, or commit SHA - With repo_path: subdirectory for monorepos For full documentation, see: https://docs.all-hands.dev/sdk/guides/plugins """ import json import os import tempfile from pathlib import Path from pydantic import SecretStr from openhands.sdk import LLM, Agent, Conversation from openhands.sdk.plugin import ( PluginFetchError, PluginSource, disable_plugin, enable_plugin, install_plugin, list_installed_plugins, load_installed_plugins, uninstall_plugin, ) from openhands.sdk.tool import Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.terminal import TerminalTool script_dir = Path(__file__).parent local_plugin_path = script_dir / "example_plugins" / "code-quality" def print_state(label: str, installed_dir: Path) -> None: """Print tracked, loaded, and persisted plugin state.""" print(f"\n{label}") print("-" * len(label)) installed = list_installed_plugins(installed_dir=installed_dir) print("Tracked plugins:") for info in installed: print(f" - {info.name} (enabled={info.enabled}, source={info.source})") loaded = load_installed_plugins(installed_dir=installed_dir) print(f"Loaded plugins: {[plugin.name for plugin in loaded]}") metadata = json.loads((installed_dir / ".installed.json").read_text()) print("Metadata file:") print(json.dumps(metadata, indent=2)) def demo_conversation_with_github_plugin(llm: LLM) -> None: """Demo 1: Load plugin from GitHub via Conversation.""" print("\n" + "=" * 60) print("DEMO 1: Loading plugin from GitHub via Conversation") print("=" * 60) plugins = [ PluginSource( source="github:anthropics/skills", ref="main", ), ] agent = Agent( llm=llm, tools=[Tool(name=TerminalTool.name), Tool(name=FileEditorTool.name)], ) with tempfile.TemporaryDirectory() as tmpdir: try: conversation = Conversation( agent=agent, workspace=tmpdir, plugins=plugins, ) conversation.send_message( "What's the best way to create a PowerPoint presentation " "programmatically? Check the skill before you answer." ) skills = ( conversation.agent.agent_context.skills if conversation.agent.agent_context else [] ) print(f"✓ Loaded {len(skills)} skill(s) from GitHub plugin") for skill in skills[:5]: print(f" - {skill.name}") if len(skills) > 5: print(f" ... and {len(skills) - 5} more skills") if conversation.resolved_plugins: print("Resolved plugin refs:") for resolved in conversation.resolved_plugins: print(f" - {resolved.source} @ {resolved.resolved_ref}") conversation.run() except PluginFetchError as e: print(f"⚠ Could not fetch from GitHub: {e}") print(" Skipping this demo (network or rate limiting issue)") def demo_install_local_plugin(installed_dir: Path) -> str: """Demo 2: Install a plugin from a local path.""" print("\n" + "=" * 60) print("DEMO 2: Installing plugin from local path") print("=" * 60) info = install_plugin(source=str(local_plugin_path), installed_dir=installed_dir) print(f"✓ Installed: {info.name} v{info.version}") print(f" Source: {info.source}") print(f" Path: {info.install_path}") return info.name def demo_install_github_plugin(installed_dir: Path) -> None: """Demo 3: Install a plugin from GitHub to persistent storage.""" ``` -------------------------------- ### Get LLM Metrics from Example Script Source: https://docs.openhands.dev/sdk/guides/metrics This ready-to-run example demonstrates how to track token usage, costs, and performance metrics from LLM interactions. It configures an LLM, sets up tools, and runs a conversation, then prints the final LLM metrics. ```python import os from pydantic import SecretStr from openhands.sdk import ( LLM, Agent, Conversation, Event, LLMConvertibleEvent, get_logger, ) from openhands.sdk.tool import Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.terminal import TerminalTool logger = get_logger(__name__) # Configure LLM api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") base_url = os.getenv("LLM_BASE_URL") llm = LLM( usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key), ) cwd = os.getcwd() tools = [ Tool(name=TerminalTool.name), Tool(name=FileEditorTool.name), ] # Add MCP Tools mcp_config = {"mcpServers": {"fetch": {"command": "uvx", "args": ["mcp-server-fetch"]}}} # Agent agent = Agent(llm=llm, tools=tools, mcp_config=mcp_config) llm_messages = [] # collect raw LLM messages def conversation_callback(event: Event): if isinstance(event, LLMConvertibleEvent): llm_messages.append(event.to_llm_message()) # Conversation conversation = Conversation( agent=agent, callbacks=[conversation_callback], workspace=cwd, ) logger.info("Starting conversation with MCP integration...") conversation.send_message( "Read https://github.com/OpenHands/OpenHands and write 3 facts " "about the project into FACTS.txt." ) conversation.run() conversation.send_message("Great! Now delete that file.") conversation.run() print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") assert llm.metrics is not None print( f"Conversation finished. Final LLM metrics with details: {llm.metrics.model_dump()}" ) ``` -------------------------------- ### Starting MLflow Server Source: https://docs.openhands.dev/sdk/guides/observability Start the MLflow tracking server using the 'uvx mlflow server' command. Ensure MLflow is installed. ```bash uvx mlflow server ``` -------------------------------- ### Manually Install Project-Level Skill Source: https://docs.openhands.dev/overview/skills/adding Copy or clone a skill directory into the `.agents/skills/` directory for project-level installation. This example shows using git to clone and then copy the skill. ```bash # Using git git clone https://github.com/OpenHands/extensions temp-clone cp -r temp-clone/skills/codereview .agents/skills/ rm -rf temp-clone # Or download and extract manually ``` -------------------------------- ### Ready-to-run Example: LLM Routing with MultimodalRouter Source: https://docs.openhands.dev/sdk/guides/llm-routing This example demonstrates how to automatically route requests to different LLMs based on task characteristics using the MultimodalRouter. It includes setup for LLMs, the router, tools, and an agent, followed by sending messages with text and image content to test the routing logic. ```python import os from pydantic import SecretStr from openhands.sdk import ( LLM, Agent, Conversation, Event, ImageContent, LLMConvertibleEvent, Message, TextContent, get_logger, ) from openhands.sdk.llm.router import MultimodalRouter from openhands.tools.preset.default import get_default_tools logger = get_logger(__name__) # Configure LLM api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." model = os.getenv("LLM_MODEL", "openhands/claude-sonnet-45-20250929") base_url = os.getenv("LLM_BASE_URL") primary_llm = LLM( usage_id="agent-primary", model=model, base_url=base_url, api_key=SecretStr(api_key), ) secondary_llm = LLM( usage_id="agent-secondary", model="openhands/devstral-small-2507", base_url=base_url, api_key=SecretStr(api_key), ) multimodal_router = MultimodalRouter( usage_id="multimodal-router", llms_for_routing={"primary": primary_llm, "secondary": secondary_llm}, ) # Tools tools = get_default_tools() # Use our default openhands experience # Agent agent = Agent(llm=multimodal_router, tools=tools) llm_messages = [] # collect raw LLM messages def conversation_callback(event: Event): if isinstance(event, LLMConvertibleEvent): llm_messages.append(event.to_llm_message()) conversation = Conversation( agent=agent, callbacks=[conversation_callback], workspace=os.getcwd() ) conversation.send_message( message=Message( role="user", content=[TextContent(text=("Hi there, who trained you?"))], ) ) conversation.run() conversation.send_message( message=Message( role="user", content=[ ImageContent( image_urls=["http://images.cocodataset.org/val2017/000000039769.jpg"] ), TextContent(text=("What do you see in the image above?")), ], ) ) conversation.run() conversation.send_message( message=Message( role="user", content=[TextContent(text=("Who trained you as an LLM?"))], ) ) conversation.run() print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") ``` -------------------------------- ### Install OpenHands SDK and Tools for Local Development Source: https://docs.openhands.dev/sdk/arch/overview Install the core SDK and common tools packages for local development. This setup is suitable for prototyping and simple use cases. ```bash pip install openhands-sdk openhands-tools ``` -------------------------------- ### Python Example: OpenAI Compatible Gateway Source: https://docs.openhands.dev/sdk/guides/agent-server/openai-gateway This example demonstrates starting a local agent-server, storing an LLM profile, listing it, and then calling the Chat Completions API. It shows how to pass the conversation ID header to continue a conversation. ```python """Use the agent-server through an OpenAI-compatible Chat Completions client. This example starts a local agent-server, stores an LLM profile, lists it through ``GET /v1/models``, then calls ``POST /v1/chat/completions`` with the OpenAI Python SDK. The returned ``X-OpenHands-ServerConversation-ID`` header is passed back on a second call to continue the same OpenHands conversation. """ import os from uuid import UUID import httpx from openai import OpenAI from scripts.utils import ManagedAPIServer # The gateway runs a full OpenHands agent, but OpenAI clients still need a # normal model-like name. We create an LLM profile below and expose it as # `openhands_` through `/v1/models`. api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY") assert api_key is not None, "Set LLM_API_KEY or OPENAI_API_KEY." llm_model = os.getenv("LLM_MODEL", "gpt-5-nano") llm_base_url = os.getenv("LLM_BASE_URL") profile_name = "gateway_demo" gateway_model = f"openhands_{profile_name}" # Start a local agent-server for the demo. `use_session_api_key=True` turns on # authentication; the same key works as both `X-Session-API-Key` for native ``` -------------------------------- ### Task Tool Set: Initialization and Setup Source: https://docs.openhands.dev/sdk/guides/task-tool-set Sets up the LLM and imports necessary components for using the TaskToolSet. This code is part of a larger example demonstrating an animal quiz flow. ```python """ Animal Quiz with Task Tool Set Demonstrates the TaskToolSet with a main agent delegating to an animal-expert sub-agent. The flow is: 1. User names an animal. 2. Main agent delegates to the "animal_expert" sub-agent to generate a multiple-choice question about that animal. 3. Main agent shows the question to the user. 4. User picks an answer. 5. Main agent resumes the same sub-agent to check whether the answer is correct and explain why. """ import os from pydantic import SecretStr from openhands.sdk import LLM, Agent, AgentContext, Conversation, Tool from openhands.sdk.context import Skill from openhands.sdk.subagent import register_agent from openhands.tools.delegate import DelegationVisualizer from openhands.tools.task import TaskToolSet # ── LLM setup ──────────────────────────────────────────────────────── api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." llm = LLM( model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"), api_key=SecretStr(api_key), base_url=os.getenv("LLM_BASE_URL", None), ) ``` -------------------------------- ### Demonstrate Skill Installation and Uninstallation Source: https://docs.openhands.dev/sdk/guides/skill This Python code demonstrates the full lifecycle of installing, enabling/disabling, and uninstalling a skill. It verifies the state of installed skills after each operation. ```python print_state("After uninstall", installed_dir) assert not (installed_dir / skill_name).exists() metadata = json.loads((installed_dir / ".installed.json").read_text()) assert skill_name not in metadata["skills"] assert remaining_skill_name in metadata["skills"] if __name__ == "__main__": with tempfile.TemporaryDirectory() as tmpdir: installed_dir = Path(tmpdir) / "installed-skills" installed_dir.mkdir(parents=True) installed_names = demo_install_skills(installed_dir) demo_list_and_load_skills(installed_dir) demo_enable_disable_skill(installed_dir, skill_name="rot13-encryption") demo_uninstall_skill( installed_dir, skill_name="rot13-encryption", remaining_skill_name="code-style-guide", ) remaining_names = [ info.name for info in list_installed_skills(installed_dir=installed_dir) ] assert remaining_names == ["code-style-guide"] assert sorted(installed_names) == ["code-style-guide", "rot13-encryption"] print("\nEXAMPLE_COST: 0") ``` -------------------------------- ### Install and Manage Skills from a Marketplace Source: https://docs.openhands.dev/sdk/guides/skill This Python script demonstrates how to create a marketplace with local and remote skills, install them, and list installed skills. It supports installing all skills, forcing a reinstall, and listing installed skills. ```python """Example: Mixed Marketplace with Local and Remote Skills This example demonstrates how to create a marketplace that includes both: 1. Local skills hosted in your project directory 2. Remote skills from GitHub (OpenHands/extensions repository) The marketplace.json schema supports source paths in these formats: - Local paths: ./path, ../path, /absolute/path, ~/path, file:///path - GitHub URLs: https://github.com/{owner}/{repo}/blob/{branch}/{path} This pattern is useful for teams that want to: - Maintain their own custom skills locally - Reference specific skills from remote repositories - Create a curated skill set for their specific workflows Directory Structure: 43_mixed_marketplace_skills/ ├── .plugin/ │ └── marketplace.json # Marketplace with local and remote skills ├── skills/ │ └── greeting-helper/ │ └── SKILL.md # Local skill content ├── main.py # This file └── README.md # Documentation Usage: # Install all skills from marketplace to ~/.openhands/skills/installed/ python main.py --install # Force reinstall (overwrite existing) python main.py --install --force # Show installed skills python main.py --list """ import sys from pathlib import Path from openhands.sdk.plugin import Marketplace from openhands.sdk.skills import ( install_skills_from_marketplace, list_installed_skills, ) def main(): script_dir = Path(__file__).parent if "--list" in sys.argv: # List installed skills print("=" * 80) print("Installed Skills") print("=" * 80) installed = list_installed_skills() if not installed: print("\nNo skills installed.") print("Run with --install to install skills from the marketplace.") else: for info in installed: desc = (info.description or "No description")[:60] print(f"\n {info.name}") print(f" Description: {desc}...") print(f" Source: {info.source}") return if "--install" in sys.argv: # Install skills from marketplace print("=" * 80) print("Installing Skills from Marketplace") print("=" * 80) print(f"\nMarketplace directory: {script_dir}") force = "--force" in sys.argv installed = install_skills_from_marketplace(script_dir, force=force) print(f"\n\nInstalled {len(installed)} skills:") for info in installed: print(f" - {info.name}") # Show all installed skills print("\n" + "=" * 80) print("All Installed Skills") print("=" * 80) all_installed = list_installed_skills() for info in all_installed: desc = (info.description or "No description")[:50] print(f" - {info.name}: {desc}...") return # Default: show marketplace info print("=" * 80) print("Marketplace Information") print("=" * 80) print(f"\nMarketplace directory: {script_dir}") marketplace = Marketplace.load(script_dir) print(f"Name: {marketplace.name}") print(f"Description: {marketplace.description}") print(f"Skills defined: {len(marketplace.skills)}") print("\nSkills:") for entry in marketplace.skills: source_type = "remote" if entry.source.startswith("http") else "local" print(f" - {entry.name} ({source_type})") print(f" Source: {entry.source}") if entry.description: print(f" Description: {entry.description}") print("\n" + "-" * 80) print("Usage:") print(" python main.py --install # Install all skills") print(" python main.py --install --force # Force reinstall") print(" python main.py --list # List installed skills") if __name__ == "__main__": main() ``` -------------------------------- ### Full Confirmation Example Source: https://docs.openhands.dev/sdk/guides/security A complete example demonstrating how to require user approval before executing agent actions. This example includes setting up the conversation and handling confirmations. ```python """OpenHands Agent SDK — Confirmation Mode Example""" import os import signal from collections.abc import Callable from pydantic import SecretStr from openhands.sdk import LLM, BaseConversation, Conversation from openhands.sdk.conversation.state import ( ConversationExecutionStatus, ConversationState, ) from openhands.sdk.security.confirmation_policy import AlwaysConfirm, NeverConfirm from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer from openhands.tools.preset.default import get_default_agent ``` -------------------------------- ### Full Plugin Management Example Execution Source: https://docs.openhands.dev/sdk/guides/plugins Orchestrates the execution of various plugin management demos, including installation, listing, lifecycle management, and uninstallation. Requires an LLM API key to run certain parts. ```python if __name__ == "__main__": api_key = os.getenv("LLM_API_KEY") if not api_key: print("Set LLM_API_KEY to run the full example") print("Running install and lifecycle demos only...") llm = None else: model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") llm = LLM( usage_id="plugin-demo", model=model, api_key=SecretStr(api_key), base_url=os.getenv("LLM_BASE_URL"), ) with tempfile.TemporaryDirectory() as tmpdir: installed_dir = Path(tmpdir) / "installed-plugins" installed_dir.mkdir() if llm: demo_conversation_with_github_plugin(llm) local_plugin_name = demo_install_local_plugin(installed_dir) demo_install_github_plugin(installed_dir) demo_list_and_load_plugins(installed_dir) demo_enable_disable_plugin(installed_dir, local_plugin_name) demo_uninstall_plugins(installed_dir) print("\n" + "=" * 60) print("EXAMPLE COMPLETED SUCCESSFULLY") print("=" * 60) if llm: print(f"EXAMPLE_COST: {llm.metrics.accumulated_cost:.4f}") else: print("EXAMPLE_COST: 0") ``` -------------------------------- ### Python Custom Visualizer Example Source: https://docs.openhands.dev/sdk/guides/convo-custom-visualizer This Python script demonstrates creating a `MinimalVisualizer` by subclassing `ConversationVisualizerBase`. It implements the `on_event` method to print raw event data. The example also shows how to configure an LLM, get a default agent, and start a conversation with the custom visualizer. ```python """Custom Visualizer Example This example demonstrates how to create and use a custom visualizer by subclassing ConversationVisualizer. This approach provides: - Clean, testable code with class-based state management - Direct configuration (just pass the visualizer instance to visualizer parameter) - Reusable visualizer that can be shared across conversations This demonstrates how you can pass a ConversationVisualizer instance directly to the visualizer parameter for clean, reusable visualization logic. """ import logging import os from pydantic import SecretStr from openhands.sdk import LLM, Conversation from openhands.sdk.conversation.visualizer import ConversationVisualizerBase from openhands.sdk.event import \ Event, ) from openhands.tools.preset.default import get_default_agent class MinimalVisualizer(ConversationVisualizerBase): """A minimal visualizer that print the raw events as they occur.""" def on_event(self, event: Event) -> None: """Handle events for minimal progress visualization.""" print(f"\n\n[EVENT] {type(event).__name__}: {event.model_dump_json()[:200]}...") api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") base_url = os.getenv("LLM_BASE_URL") llm = LLM( model=model, api_key=SecretStr(api_key), base_url=base_url, usage_id="agent", ) agent = get_default_agent(llm=llm, cli_mode=True) # ============================================================================= # Configure Visualization # ============================================================================ # Set logging level to reduce verbosity logging.getLogger().setLevel(logging.WARNING) # Start a conversation with custom visualizer cwd = os.getcwd() conversation = Conversation( agent=agent, workspace=cwd, visualizer=MinimalVisualizer(), ) # Send a message and let the agent run print("Sending task to agent...") conversation.send_message("Write 3 facts about the current project into FACTS.txt.") conversation.run() print("Task completed!") # Report cost cost = llm.metrics.accumulated_cost print(f"EXAMPLE_COST: {cost:.4f}") ``` -------------------------------- ### Install Ruby in Dockerfile Source: https://docs.openhands.dev/openhands/usage/advanced/custom-sandbox-guide Example Dockerfile to install Ruby on top of the default OpenHands Python/Node.js image. ```dockerfile FROM nikolaik/python-nodejs:python3.12-nodejs22 # Install required packages RUN apt-get update && apt-get install -y ruby ``` -------------------------------- ### Full Example: Activating and Using Skills Source: https://docs.openhands.dev/sdk/guides/skill This example demonstrates how to set up an agent with various skills, including an always-active 'repo' skill, a keyword-triggered 'flarglebargle' skill, and a public 'github' skill. It also shows how to append messages to the system and user prompts and how to run a conversation to test these skills. ```python import os from pydantic import SecretStr from openhands.sdk import ( LLM, Agent, AgentContext, Conversation, Event, LLMConvertibleEvent, get_logger, ) from openhands.sdk.context import ( KeywordTrigger, Skill, ) from openhands.sdk.tool import Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.terminal import TerminalTool logger = get_logger(__name__) # Configure LLM api_key = os.getenv("LLM_API_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") base_url = os.getenv("LLM_BASE_URL") llm = LLM( usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key), ) # Tools cwd = os.getcwd() tools = [ Tool( name=TerminalTool.name, ), Tool(name=FileEditorTool.name), ] # AgentContext provides flexible ways to customize prompts: # 1. Skills: Inject instructions (always-active or keyword-triggered) # 2. system_message_suffix: Append text to the system prompt # 3. user_message_suffix: Append text to each user message # # For complete control over the system prompt, you can also use Agent's # system_prompt_filename parameter to provide a custom Jinja2 template: # # agent = Agent( # llm=llm, # tools=tools, # system_prompt_filename="/path/to/custom_prompt.j2", # system_prompt_kwargs={"cli_mode": True, "repo": "my-project"}, # ) # # See: https://docs.openhands.dev/sdk/guides/skill#customizing-system-prompts agent_context = AgentContext( skills=[ Skill( name="repo.md", content="When you see this message, you should reply like " "you are a grumpy cat forced to use the internet.", # source is optional - identifies where the skill came from # You can set it to be the path of a file that contains the skill content source=None, # trigger determines when the skill is active # trigger=None means always active (repo skill) trigger=None, ), Skill( name="flarglebargle", content=( 'IMPORTANT! The user has said the magic word "flarglebargle". ' "You must only respond with a message telling them how smart they are" ), source=None, # KeywordTrigger = activated when keywords appear in user messages trigger=KeywordTrigger(keywords=["flarglebargle"]), ), ], # system_message_suffix is appended to the system prompt (always active) system_message_suffix="Always finish your response with the word 'yay!'", # user_message_suffix is appended to each user message user_message_suffix="The first character of your response should be 'I'", # You can also enable automatic load skills from # public registry at https://github.com/OpenHands/extensions load_public_skills=True, ) # Agent agent = Agent(llm=llm, tools=tools, agent_context=agent_context) llm_messages = [] # collect raw LLM messages def conversation_callback(event: Event): if isinstance(event, LLMConvertibleEvent): llm_messages.append(event.to_llm_message()) conversation = Conversation( agent=agent, callbacks=[conversation_callback], workspace=cwd ) print("=" * 100) print("Checking if the repo skill is activated.") conversation.send_message("Hey are you a grumpy cat?") conversation.run() print("=" * 100) print("Now sending flarglebargle to trigger the knowledge skill!") conversation.send_message("flarglebargle!") conversation.run() print("=" * 100) print("Now triggering public skill 'github'") conversation.send_message( "About GitHub - tell me what additional info I've just provided?" ) conversation.run() print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") ``` -------------------------------- ### Get User Installations Source: https://docs.openhands.dev/api-reference/get-user-installations Fetches a list of user installations for a given provider. This is useful for managing and viewing connected services. ```APIDOC ## GET /api/user/installations ### Description Retrieves a list of user installations for a specified provider. ### Method GET ### Endpoint /api/user/installations ### Parameters #### Query Parameters - **provider** (ProviderType) - Required - Specifies the type of provider (e.g., github, gitlab, bitbucket, enterprise_sso). ### Response #### Success Response (200) - **array of strings** - A list of installation identifiers. #### Response Example ```json [ "installation-id-1", "installation-id-2" ] ``` #### Error Response (422) - **HTTPValidationError** - Returned when the request parameters are invalid. ``` -------------------------------- ### Ready-to-run Example: LLMProfileStore Usage Source: https://docs.openhands.dev/sdk/guides/llm-profile-store Demonstrates using LLMProfileStore with a pre-generated profile and creating a new profile at runtime. This example shows how secrets are masked and other fields are preserved. ```python """Example: Using LLMProfileStore to save and reuse LLM configurations. This example ships with one pre-generated profile JSON file and creates another profile at runtime. The checked-in profile comes from a normal save, so secrets are masked instead of exposed and non-secret fields like `base_url` are kept when present. """ import os import shutil import tempfile from pathlib import Path from pydantic import SecretStr from openhands.sdk import LLM, LLMProfileStore SCRIPT_DIR = Path(__file__).parent EXAMPLE_PROFILES_DIR = SCRIPT_DIR / "profiles" DEFAULT_MODEL = "anthropic/claude-sonnet-4-5-20250929" profile_store_dir = Path(tempfile.mkdtemp()) / "profiles" shutil.copytree(EXAMPLE_PROFILES_DIR, profile_store_dir) store = LLMProfileStore(base_dir=profile_store_dir) print(f"Seeded profiles: {store.list()}") api_key = os.getenv("LLM_API_KEY") creative_llm = LLM( usage_id="creative", model=os.getenv("LLM_MODEL", DEFAULT_MODEL), api_key=SecretStr(api_key) if api_key else None, base_url=os.getenv("LLM_BASE_URL"), temperature=0.9, ) # The checked-in fast.json was generated with a normal save, so its api_key is # masked and any configured base_url would be preserved. This runtime profile ``` -------------------------------- ### List All SDK Examples Source: https://docs.openhands.dev/sdk/getting-started This command lists all available example Python scripts in the `examples/01_standalone_sdk/` directory. It's useful for discovering the full range of SDK capabilities. ```bash # See all examples ls examples/01_standalone_sdk/ ``` -------------------------------- ### OpenAPI Specification for Get User Installations Source: https://docs.openhands.dev/api-reference/get-user-installations This OpenAPI 3.1.0 specification defines the GET /api/user/installations endpoint. It includes details on parameters, responses, and security schemes. ```yaml openapi: 3.1.0 info: title: OpenHands description: 'OpenHands: Code Less, Make More' version: 0.53.0 servers: - url: https://app.all-hands.dev description: Production server - url: http://localhost:3000 description: Local development server security: [] paths: /api/user/installations: get: summary: Get User Installations operationId: get_user_installations_api_user_installations_get parameters: - name: provider in: query required: true schema: $ref: '#/components/schemas/ProviderType' responses: '200': description: Successful Response content: application/json: schema: type: array items: type: string title: Response Get User Installations Api User Installations Get '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' security: - APIKeyHeader: [] components: schemas: ProviderType: type: string enum: - github - gitlab - bitbucket - enterprise_sso title: ProviderType HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type type: object required: - loc - msg - type title: ValidationError securitySchemes: APIKeyHeader: type: apiKey in: header name: X-Session-API-Key ``` -------------------------------- ### Example AGENTS.md File Content Source: https://docs.openhands.dev/overview/skills/repo This is an example of the content an AGENTS.md file should contain, including repository purpose, setup, structure, CI/CD workflows, and development guidelines. ```markdown # Repository Purpose This project is a TODO application that allows users to track TODO items. # Setup Instructions To set it up, you can run `npm run build`. # Repository Structure - `/src`: Core application code - `/tests`: Test suite - `/docs`: Documentation - `/.github`: CI/CD workflows # CI/CD Workflows - `lint.yml`: Runs ESLint on all JavaScript files - `test.yml`: Runs the test suite on pull requests # Development Guidelines Always make sure the tests are passing before committing changes. You can run the tests by running `npm run test`. ``` -------------------------------- ### Example Docker and Kubernetes Deployment Source: https://docs.openhands.dev/overview/skills/creating Show concrete examples for deploying a web application using Docker and updating a Kubernetes deployment. ```bash # Build the image docker build -t myapp:v1.0 . # Push to registry docker push registry.example.com/myapp:v1.0 # Update Kubernetes deployment kubectl set image deployment/web web=registry.example.com/myapp:v1.0 ``` -------------------------------- ### Run First Agent: Hello World Example Source: https://docs.openhands.dev/sdk/getting-started This Python script demonstrates creating an agent, configuring an LLM, adding tools, and initiating a conversation to perform a simple task. Ensure your LLM API key and model are set in the environment variables. ```python import os from openhands.sdk import LLM, Agent, Conversation, Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.task_tracker import TaskTrackerTool from openhands.tools.terminal import TerminalTool llm = LLM( model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"), api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL", None), ) agent = Agent( llm=llm, tools=[ Tool(name=TerminalTool.name), Tool(name=FileEditorTool.name), Tool(name=TaskTrackerTool.name), ], ) cwd = os.getcwd() conversation = Conversation(agent=agent, workspace=cwd) conversation.send_message("Write 3 facts about the current project into FACTS.txt.") conversation.run() print("All done!") ``` -------------------------------- ### API Response for Starting a Conversation Source: https://docs.openhands.dev/openhands/usage/cloud/cloud-api The response to a conversation start request includes an ID, status, and other relevant details. The status field indicates the progress of the conversation setup. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "WORKING", "app_conversation_id": "660e8400-e29b-41d4-a716-446655440001", "sandbox_id": "sandbox-abc123", "created_at": "2025-01-15T10:30:00Z" } ``` -------------------------------- ### Start SuperGateway Proxy Servers Source: https://docs.openhands.dev/openhands/usage/settings/mcp-settings Use these commands to start separate SuperGateway proxy instances for filesystem and fetch MCP servers. Ensure SuperGateway is installed and accessible in your PATH. ```bash supergateway --stdio "npx @modelcontextprotocol/server-filesystem /" --port 8080 supergateway --stdio "uvx mcp-server-fetch" --port 8081 ``` -------------------------------- ### Full Agent Settings Example Source: https://docs.openhands.dev/sdk/guides/agent-settings Demonstrates building, serializing, deserializing, and creating an agent from settings, including running a conversation and modifying agent capabilities by changing tools and condenser settings. ```python """Create, serialize, and deserialize OpenHandsAgentSettings, then build an agent. Demonstrates: 1. Configuring an agent entirely through OpenHandsAgentSettings (LLM, tools, condenser). 2. Serializing settings to JSON and restoring them. 3. Building an Agent from settings via ``create_agent()``. 4. Running a short conversation to prove the settings take effect. 5. Changing the tool list and showing the agent's capabilities change. """ import json import os from pydantic import SecretStr from openhands.sdk import LLM, Conversation, OpenHandsAgentSettings, Tool from openhands.sdk.settings import CondenserSettings from openhands.tools.file_editor import FileEditorTool from openhands.tools.terminal import TerminalTool # ── 1. Build settings ──────────────────────────────────────────────────── api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." settings = OpenHandsAgentSettings( llm=LLM( model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"), api_key=SecretStr(api_key), base_url=os.getenv("LLM_BASE_URL"), ), tools=[ Tool(name=TerminalTool.name), Tool(name=FileEditorTool.name), ], condenser=CondenserSettings(enabled=True, max_size=50), ) # ── 2. Serialize → JSON → deserialize ──────────────────────────────────── payload = settings.model_dump(mode="json") print("Serialized settings (JSON):") print(json.dumps(payload, indent=2, default=str)[:800], " …") print() restored = OpenHandsAgentSettings.model_validate(payload) assert restored.condenser.enabled is True assert restored.condenser.max_size == 50 assert len(restored.tools) == 2 print("✓ Roundtrip deserialization successful — all fields preserved") print() ``` -------------------------------- ### Basic Setup with OpenAI Source: https://docs.openhands.dev/openhands/usage/environment-variables Configure the language model and API key for OpenAI integration. Set DEBUG to true for verbose logging. ```bash export LLM_MODEL="gpt-4o" export LLM_API_KEY="your-openai-api-key" export DEBUG=true ``` -------------------------------- ### Running the Example Source: https://docs.openhands.dev/sdk/guides/agent-server/custom-tools This indicates the command or process used to execute the example. It is typically a shell command. ```bash Running the Example theme={null} ``` -------------------------------- ### Repository-Specific QA Guide Configuration Source: https://docs.openhands.dev/openhands/usage/use-cases/qa-changes Defines the name, description, and trigger for a custom QA guide within a repository. This file allows for project-specific testing instructions and environment setup. ```markdown --- name: qa-guide description: Project-specific QA guidelines triggers: - /qa-changes --- # QA Guidelines for [Your Project] ## Environment Setup - Run `make setup` to initialize the development environment - The dev server runs on port 8080 ## Key Test Scenarios - Always verify the admin dashboard at /admin after backend changes - For API changes, test with both authenticated and unauthenticated requests ## Known Limitations - The payment module requires a Stripe test key — skip payment flow testing ``` -------------------------------- ### Bash: Running the SaaS Credentials Example Source: https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace This command demonstrates how to set the necessary environment variable to run the SaaS credentials example. Ensure you replace 'your-cloud-api-key' with your actual OpenHands Cloud API key. ```bash export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key" # Optional: override LLM model from your SaaS settings ```