View source on GitHub
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
- Create a sandbox (
POST /api/v1/sandboxes) and wait forRUNNING. - Install the package into
/workspaceviapip install --target:The--targetflag puts the package into the conversation’s working directory, which is on the agent-server’ssys.path. We use--no-depsto 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. - Create a sample markdown file with messy numbering (sections 5, 10, 3).
- Create a conversation that maps the tool via
tool_module_qualnames:The agent-server importsoh_markdown_tool.tool, which registers the tool (the package’stool.pycallsregister_tool("markdown_document", ...)on import). - Run and verify:
- The tool appears in
SystemPromptEvent.tools(registered). - The tool appears in
ActionEventrecords (used). - The file is actually modified (TOC added, sections renumbered 1, 2, 3).
- The tool appears in
Prerequisites
Run It
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 andcustom-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 structurerenumber— fix section numberingtoc_update— add/update table of contentsmove,insert,delete,promote,demote— section operationsrewrap,lint,fix,cleanup— formatting
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.
