Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions sdk/guides/agent-acp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

> A ready-to-run example is available [here](#ready-to-run-example)!

`ACPAgent` lets you use any [Agent Client Protocol](https://agentclientprotocol.com/protocol/overview) server as the backend for an OpenHands conversation. Instead of calling an LLM directly, the agent spawns an ACP server subprocess and communicates with it over JSON-RPC. The server manages its own LLM, tools, and execution — your code just sends messages and collects responses.

Check warning on line 8 in sdk/guides/agent-acp.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/agent-acp.mdx#L8

Did you really mean 'subprocess'?

## Basic Usage

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

## Cleanup

Always call `agent.close()` when you are done to terminate the ACP server subprocess. A `try/finally` block is recommended:

Check warning on line 87 in sdk/guides/agent-acp.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/agent-acp.mdx#L87

Did you really mean 'subprocess'?

```python icon="python"
agent = ACPAgent(acp_command=["npx", "-y", "claude-code-acp"])
Expand Down Expand Up @@ -159,6 +159,113 @@
uv run python examples/01_standalone_sdk/40_acp_agent_example.py
```

## Remote Runtime Example

<Note>
This example is available on GitHub: [examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py)
</Note>

This example shows how to run an ACPAgent in a remote sandboxed environment via the Runtime API, using `APIRemoteWorkspace`:

Check warning on line 168 in sdk/guides/agent-acp.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/agent-acp.mdx#L168

Did you really mean 'ACPAgent'?

Check warning on line 168 in sdk/guides/agent-acp.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/agent-acp.mdx#L168

Did you really mean 'sandboxed'?

```python icon="python" expandable examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py
"""Example: ACPAgent with Remote Runtime via API.

This example demonstrates running an ACPAgent (Claude Code via ACP protocol)
in a remote sandboxed environment via Runtime API. It follows the same pattern
as 04_convo_with_api_sandboxed_server.py but uses ACPAgent instead of the
default LLM-based Agent.

Usage:
uv run examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py

Requirements:
- LLM_BASE_URL: LiteLLM proxy URL (routes Claude Code requests)
- LLM_API_KEY: LiteLLM virtual API key
- RUNTIME_API_KEY: API key for runtime API access
"""

import os
import time

from openhands.sdk import (
Conversation,
RemoteConversation,
get_logger,
)
from openhands.sdk.agent import ACPAgent
from openhands.workspace import APIRemoteWorkspace


logger = get_logger(__name__)


# ACP agents (Claude Code) route through LiteLLM proxy
llm_base_url = os.getenv("LLM_BASE_URL")
llm_api_key = os.getenv("LLM_API_KEY")
assert llm_base_url and llm_api_key, "LLM_BASE_URL and LLM_API_KEY required"

# Set ANTHROPIC_* vars so Claude Code routes through LiteLLM
os.environ["ANTHROPIC_BASE_URL"] = llm_base_url
os.environ["ANTHROPIC_API_KEY"] = llm_api_key

runtime_api_key = os.getenv("RUNTIME_API_KEY")
assert runtime_api_key, "RUNTIME_API_KEY required"

# If GITHUB_SHA is set (e.g. running in CI of a PR), use that to ensure consistency
# Otherwise, use the latest image from main
server_image_sha = os.getenv("GITHUB_SHA") or "main"
server_image = f"ghcr.io/openhands/agent-server:{server_image_sha[:7]}-python-amd64"
logger.info(f"Using server image: {server_image}")

with APIRemoteWorkspace(
runtime_api_url=os.getenv("RUNTIME_API_URL", "https://runtime.eval.all-hands.dev"),
runtime_api_key=runtime_api_key,
server_image=server_image,
image_pull_policy="Always",
target_type="binary", # CI builds binary target images
forward_env=["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY"],
) as workspace:
agent = ACPAgent(
acp_command=["claude-agent-acp"], # Pre-installed in Docker image
)

received_events: list = []
last_event_time = {"ts": time.time()}

def event_callback(event) -> None:
received_events.append(event)
last_event_time["ts"] = time.time()

conversation = Conversation(
agent=agent, workspace=workspace, callbacks=[event_callback]
)
assert isinstance(conversation, RemoteConversation)

try:
conversation.send_message(
"List the files in /workspace and describe what you see."
)
conversation.run()

while time.time() - last_event_time["ts"] < 2.0:
time.sleep(0.1)

# Report cost
cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost
print(f"EXAMPLE_COST: {cost:.4f}")
finally:
conversation.close()
```

```bash Running the Example
export LLM_BASE_URL="https://your-litellm-proxy.example.com"
export LLM_API_KEY="your-litellm-api-key"
export RUNTIME_API_KEY="your-runtime-api-key"
export RUNTIME_API_URL="https://runtime.eval.all-hands.dev"
cd software-agent-sdk
uv run python examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py
```

## Next Steps

- **[Creating Custom Agents](/sdk/guides/agent-custom)** — Build specialized agents with custom tool sets and system prompts
Expand Down
Loading