Skip to main content

View source on GitHub

Load a custom tool from a published Python package in OpenHands Cloud. The package is installed via pip install --target /workspace, making it importable by the frozen agent-server, which then loads and registers the tool via tool_module_qualnames. This is the natural evolution of custom-agent-with-tool: same loading mechanism (declare the tool in agent.tools, map it in tool_module_qualnames), but the deploy step becomes “pip-install-into-working-dir” instead of “upload source files.” The example uses oh-markdown-tool (oh-markdown-tool[openhands]==0.2.1), a real published package that provides structural markdown editing (renumber sections, manage TOC, etc.). The agent fixes a markdown doc with messy numbering and adds a table of contents.

How It Works

  1. Create a sandbox (POST /api/v1/sandboxes) and wait for RUNNING.
  2. Install the package into /workspace via pip install --target:
    The --target flag puts the package into the conversation’s working directory, which is on the agent-server’s sys.path. We use --no-deps to skip dependencies that are already bundled in the frozen agent-server (openhands-sdk, pydantic, rich), then install only the non-bundled deps (mdformat, pymarkdownlnt) explicitly.
  3. Create a sample markdown file with messy numbering (sections 5, 10, 3).
  4. Create a conversation that maps the tool via tool_module_qualnames:
    The agent-server imports oh_markdown_tool.tool, which registers the tool (the package’s tool.py calls register_tool("markdown_document", ...) on import).
  5. Run and verify:
    • The tool appears in SystemPromptEvent.tools (registered).
    • The tool appears in ActionEvent records (used).
    • The file is actually modified (TOC added, sections renumbered 1, 2, 3).

Prerequisites

Optional overrides (sensible defaults are used otherwise):

Run It

⚠️ Timing note: If the script runs and deletes the sandbox too quickly, conversation events may not have synced from the agent-server to the main API yet, making the conversation appear empty or incomplete in the Cloud UI. To inspect conversation events in real time, run with --keep to leave the sandbox alive, or add a delay before cleanup. The script prints the conversation URL — you can view it while the sandbox is still running.
Expected output:

Why pip install --target (and Not Plain pip install)?

On OpenHands Cloud the agent-server is a frozen, self-contained binary (built with PyInstaller). A plain pip install targets the sandbox’s system Python interpreter, which the frozen server cannot see — so the module would never be importable. The conversation’s working directory is on the agent-server’s import path, so pip install --target /workspace places the package where the server can actually import it. The bundled dependencies (openhands-sdk, pydantic, rich) are already available inside the frozen binary, so we skip them (--no-deps) and only install the package’s own code plus its non-bundled dependencies. This approach works for any published package, not just oh-markdown-tool — the key is installing into the working directory and being aware of what’s already bundled.

Verification (Same Technique as the Other Examples)

Both this example and custom-agent-with-tool read the SystemPromptEvent.tools array to confirm registration, and the ActionEvent records to confirm usage. This example adds a third check: did the tool actually do something? We read the markdown file after the run and confirm it was modified (TOC present, sections renumbered).

The Tool: oh-markdown-tool

oh-markdown-tool is a real, published package (PyPI | GitHub) that provides structural markdown editing. It’s built with the OpenHands SDK and demonstrates the full packaging story: core library (no SDK dependency) + optional [openhands] extra for the agent tool integration. The tool offers commands like:
  • overview — show document structure
  • renumber — fix section numbering
  • toc_update — add/update table of contents
  • move, insert, delete, promote, demote — section operations
  • rewrap, lint, fix, cleanup — formatting
For this example we use renumber + toc_update to fix a messy doc.

Files

Cleanup

Without --keep the sandbox is deleted automatically. With --keep, delete it when done:
Note: DELETE /api/v1/sandboxes/{id} requires sandbox_id as both the path segment and a query parameter; omitting the query parameter returns HTTP 422 and leaks the sandbox.