From c0a393c626831bae05f87ac53736dd62b27d0de1 Mon Sep 17 00:00:00 2001 From: Simon Rosenberg Date: Fri, 20 Feb 2026 08:33:56 -0300 Subject: [PATCH 1/2] docs(sdk): add ACP Agent guide Documents ACPAgent usage, configuration, metrics, and cleanup with a ready-to-run example referencing examples/01_standalone_sdk/40_acp_agent_example.py. Co-Authored-By: Claude Opus 4.6 --- docs.json | 1 + sdk/guides/agent-acp.mdx | 150 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 sdk/guides/agent-acp.mdx diff --git a/docs.json b/docs.json index 6cd73304..2976cb53 100644 --- a/docs.json +++ b/docs.json @@ -298,6 +298,7 @@ { "group": "Agent Features", "pages": [ + "sdk/guides/agent-acp", "sdk/guides/agent-interactive-terminal", "sdk/guides/agent-browser-use", "sdk/guides/agent-custom", diff --git a/sdk/guides/agent-acp.mdx b/sdk/guides/agent-acp.mdx new file mode 100644 index 00000000..e4163fcf --- /dev/null +++ b/sdk/guides/agent-acp.mdx @@ -0,0 +1,150 @@ +--- +title: ACP Agent +description: Delegate to an ACP-compatible server (Claude Code, Gemini CLI, etc.) instead of calling an LLM directly. +--- + +import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx"; + +> 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. + +## Basic Usage + +```python icon="python" wrap focus={5-7} +from openhands.sdk.agent import ACPAgent +from openhands.sdk.conversation import Conversation + +# Point at any ACP-compatible server +agent = ACPAgent(acp_command=["npx", "-y", "claude-code-acp"]) + +conversation = Conversation(agent=agent, workspace="./my-project") +conversation.send_message("Explain the architecture of this project.") +conversation.run() + +agent.close() +``` + +The `acp_command` is the shell command used to spawn the server process. The SDK communicates with it over stdin/stdout JSON-RPC. + +## How It Works + +1. `ACPAgent` spawns the ACP server as a subprocess +2. The SDK initializes the ACP protocol and creates a session +3. When you call `conversation.send_message(...)`, the message is forwarded to the ACP server via `conn.prompt()` +4. The server processes the request using its own LLM and tools, streaming session updates (text chunks, thought chunks, tool calls) back to the SDK +5. The SDK accumulates the response and emits it as a `MessageEvent` +6. Permission requests from the server are auto-approved +7. Token usage and cost metrics from the ACP server are captured into the agent's `LLM.metrics` + +## Configuration + +### Server Command and Arguments + +```python icon="python" wrap +agent = ACPAgent( + acp_command=["npx", "-y", "claude-code-acp"], + acp_args=["--profile", "my-profile"], # extra CLI args + acp_env={"CLAUDE_API_KEY": "sk-..."}, # extra env vars +) +``` + +| Parameter | Description | +|-----------|-------------| +| `acp_command` | Command to start the ACP server (required) | +| `acp_args` | Additional arguments appended to the command | +| `acp_env` | Additional environment variables for the server process | + +### What ACPAgent Does Not Support + +Because the ACP server manages its own tools and context, these `AgentBase` features are not available on `ACPAgent`: + +- `tools` / `include_default_tools` — the server has its own tools +- `mcp_config` — configure MCP on the server side +- `condenser` — the server manages its own context window +- `critic` — the server manages its own evaluation +- `agent_context` — configure the server directly + +Passing any of these raises `NotImplementedError` at initialization. + +## Metrics + +Token usage and cost data are automatically captured from the ACP server's responses. You can inspect them through the standard `LLM.metrics` interface: + +```python icon="python" wrap +metrics = agent.llm.metrics +print(f"Total cost: ${metrics.accumulated_cost:.6f}") + +for usage in metrics.token_usages: + print(f" prompt={usage.prompt_tokens} completion={usage.completion_tokens}") +``` + +Usage data comes from two ACP protocol sources: +- **`PromptResponse.usage`** — per-turn token counts (input, output, cached, reasoning tokens) +- **`UsageUpdate` notifications** — cumulative session cost and context window size + +## Cleanup + +Always call `agent.close()` when you are done to terminate the ACP server subprocess. A `try/finally` block is recommended: + +```python icon="python" wrap +agent = ACPAgent(acp_command=["npx", "-y", "claude-code-acp"]) +try: + conversation = Conversation(agent=agent, workspace=".") + conversation.send_message("Hello!") + conversation.run() +finally: + agent.close() +``` + +## Ready-to-run Example + + +This example is available on GitHub: [examples/01_standalone_sdk/40_acp_agent_example.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/40_acp_agent_example.py) + + +```python icon="python" expandable examples/01_standalone_sdk/40_acp_agent_example.py +"""Example: Using ACPAgent with Claude Code ACP server. + +This example shows how to use an ACP-compatible server (claude-code-acp) +as the agent backend instead of direct LLM calls. + +Prerequisites: + - Node.js / npx available + - Claude Code CLI authenticated (or CLAUDE_API_KEY set) + +Usage: + uv run python examples/01_standalone_sdk/40_acp_agent_example.py +""" + +import os + +from openhands.sdk.agent import ACPAgent +from openhands.sdk.conversation import Conversation + + +agent = ACPAgent(acp_command=["npx", "-y", "claude-code-acp"]) + +try: + cwd = os.getcwd() + conversation = Conversation(agent=agent, workspace=cwd) + + conversation.send_message( + "List the Python source files under openhands-sdk/openhands/sdk/agent/, " + "then read the __init__.py and summarize what agent classes are exported." + ) + conversation.run() +finally: + # Clean up the ACP server subprocess + agent.close() + +print("Done!") +``` + +This example does not use an LLM API key directly — the ACP server (Claude Code) handles authentication on its own. + +## Next Steps + +- **[Creating Custom Agents](/sdk/guides/agent-custom)** — Build specialized agents with custom tool sets and system prompts +- **[Agent Delegation](/sdk/guides/agent-delegation)** — Compose multiple agents for complex workflows +- **[LLM Metrics](/sdk/guides/metrics)** — Track token usage and costs across models From 718ed5721991e305fff6b17e2db1bb706ef77612 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Feb 2026 13:10:22 +0000 Subject: [PATCH 2/2] docs(sdk): apply review suggestions for ACP Agent guide - Replace 'wrap focus={5-7}' with 'highlight={5-7}' per Mintlify standards - Remove 'wrap' attribute from other code blocks - Add Note callout explaining key differentiator (no LLM_API_KEY needed) - Move 'What ACPAgent Does Not Support' section after Basic Usage - Expand permission auto-approval explanation with security context Co-authored-by: openhands --- sdk/guides/agent-acp.mdx | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/sdk/guides/agent-acp.mdx b/sdk/guides/agent-acp.mdx index e4163fcf..00e3b718 100644 --- a/sdk/guides/agent-acp.mdx +++ b/sdk/guides/agent-acp.mdx @@ -11,7 +11,7 @@ import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx"; ## Basic Usage -```python icon="python" wrap focus={5-7} +```python icon="python" highlight={5-7} from openhands.sdk.agent import ACPAgent from openhands.sdk.conversation import Conversation @@ -27,6 +27,22 @@ agent.close() The `acp_command` is the shell command used to spawn the server process. The SDK communicates with it over stdin/stdout JSON-RPC. + +**Key difference from standard agents:** With `ACPAgent`, you don't need an `LLM_API_KEY` in your code. The ACP server handles its own LLM authentication and API calls. This is *delegation* — your code sends messages to the ACP server, which manages all LLM interactions internally. + + +### What ACPAgent Does Not Support + +Because the ACP server manages its own tools and context, these `AgentBase` features are not available on `ACPAgent`: + +- `tools` / `include_default_tools` — the server has its own tools +- `mcp_config` — configure MCP on the server side +- `condenser` — the server manages its own context window +- `critic` — the server manages its own evaluation +- `agent_context` — configure the server directly + +Passing any of these raises `NotImplementedError` at initialization. + ## How It Works 1. `ACPAgent` spawns the ACP server as a subprocess @@ -34,14 +50,14 @@ The `acp_command` is the shell command used to spawn the server process. The SDK 3. When you call `conversation.send_message(...)`, the message is forwarded to the ACP server via `conn.prompt()` 4. The server processes the request using its own LLM and tools, streaming session updates (text chunks, thought chunks, tool calls) back to the SDK 5. The SDK accumulates the response and emits it as a `MessageEvent` -6. Permission requests from the server are auto-approved +6. Permission requests from the server are auto-approved — this means the SDK automatically grants any tool execution or file access the server requests, so ensure you trust the ACP server you're running 7. Token usage and cost metrics from the ACP server are captured into the agent's `LLM.metrics` ## Configuration ### Server Command and Arguments -```python icon="python" wrap +```python icon="python" agent = ACPAgent( acp_command=["npx", "-y", "claude-code-acp"], acp_args=["--profile", "my-profile"], # extra CLI args @@ -55,23 +71,11 @@ agent = ACPAgent( | `acp_args` | Additional arguments appended to the command | | `acp_env` | Additional environment variables for the server process | -### What ACPAgent Does Not Support - -Because the ACP server manages its own tools and context, these `AgentBase` features are not available on `ACPAgent`: - -- `tools` / `include_default_tools` — the server has its own tools -- `mcp_config` — configure MCP on the server side -- `condenser` — the server manages its own context window -- `critic` — the server manages its own evaluation -- `agent_context` — configure the server directly - -Passing any of these raises `NotImplementedError` at initialization. - ## Metrics Token usage and cost data are automatically captured from the ACP server's responses. You can inspect them through the standard `LLM.metrics` interface: -```python icon="python" wrap +```python icon="python" metrics = agent.llm.metrics print(f"Total cost: ${metrics.accumulated_cost:.6f}") @@ -87,7 +91,7 @@ Usage data comes from two ACP protocol sources: Always call `agent.close()` when you are done to terminate the ACP server subprocess. A `try/finally` block is recommended: -```python icon="python" wrap +```python icon="python" agent = ACPAgent(acp_command=["npx", "-y", "claude-code-acp"]) try: conversation = Conversation(agent=agent, workspace=".")