From migrate-claude-sdk-to-acp
Migrate code that spawns Claude CLI (claude -p), uses @anthropic-ai/claude-agent-sdk, or calls Claude from Go/Python subprocesses to the Agent Client Protocol (ACP). Use when replacing claude -p calls, subprocess.run(["claude",...]), exec.Command("claude",...), or SDK query() calls with stateful ACP sessions. Covers TypeScript, Go, and Python.
How this skill is triggered — by the user, by Claude, or both
Slash command
/migrate-claude-sdk-to-acp:migrate-sdk-to-acpThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Migrate code from stateless Claude CLI/SDK calls to stateful ACP sessions with structured streaming.
Migrate code from stateless Claude CLI/SDK calls to stateful ACP sessions with structured streaming.
query() from @anthropic-ai/claude-agent-sdk (TypeScript/JavaScript)claude -p subprocess calls from Go or Pythonsubprocess.run(["claude", ...]) or exec.Command("claude", ...) patterns# Required for all languages -- ACP is a Node.js binary
npm install @agentclientprotocol/claude-agent-acp
# or: bun add @agentclientprotocol/claude-agent-acp
Binary location: node_modules/.bin/claude-agent-acp
Before (stateless):
Go/Python/JS --> exec("claude -p 'do X'") --> parse stdout text --> done (no memory)
JS only --> import { query } --> async gen blocks --> done (no memory)
After (ACP, stateful):
Any language --> spawn ACP process --> JSON-RPC initialize --> session/new --> session/prompt --> ndjson notifications
ACP communicates via JSON-RPC 2.0 over stdin/stdout (ndjson -- one JSON object per line).
1. Spawn: node node_modules/.bin/claude-agent-acp
2. initialize: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"textFiles":true}}}
3. session/new: {"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/path","permissionMode":"bypassPermissions","mcpServers":[]}}
4. prompt: {"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"abc","prompt":[{"type":"text","text":"Hello"}]}}
5. (receive many notifications, then response with id:3)
Notifications have method but no id. Event type is in params.update.sessionUpdate:
| Event Kind | Description | Key Fields |
|---|---|---|
agent_thought_chunk | Agent thinking | params.update.content.text |
agent_message_chunk | Agent text output | params.update.content.text (when content.type == "text") |
tool_call | Tool started | params.update.toolCallId, params.update._meta.claudeCode.toolName |
tool_call_update | Tool progress/done | params.update.status ("completed"), params.update.toolCallId |
usage_update | Context & cost | params.update.used, params.update.size, params.update.cost.amount |
MANDATORY: ACP uses Claude OAuth tokens, never ANTHROPIC_API_KEY.
ACP is designed to work with Claude Max subscriptions and Claude Pro/Team plans via OAuth. This means unlimited usage within your subscription -- no per-token API billing. The agent authenticates using cached OAuth credentials from ~/.claude/ (created by claude login).
When spawning ACP, always strip these env vars:
ANTHROPIC_API_KEY -- MUST be removed. Causes auth conflicts and would bypass OAuth, incurring API costs instead of using your subscription.CLAUDECODE -- makes agent think it's a sub-instanceCLAUDE_CODE_NEW_INIT -- sameCLAUDE_CODE_ENTRYPOINT -- sameNever set or pass ANTHROPIC_API_KEY in any ACP integration. If the user's code previously used an API key with claude -p, that pattern must be removed during migration. ACP enforces OAuth-only auth by design.
| Kind | Tools | UI Treatment |
|---|---|---|
think | Agent, Task, TodoWrite | Italic/gray, collapsible |
execute | Bash | Terminal-style, show command |
read | Read, Glob, Grep, WebFetch | File icon, show path |
edit | Write, Edit | Diff view, show path |
search | Glob, Grep, WebSearch | Search icon |
fetch | WebFetch, WebSearch | Globe icon |
Option A: kill the process
Option B: send {"jsonrpc":"2.0","method":"cancel","params":{"sessionId":"..."}}
Choose the reference for your language:
query() to ACP, event mapping, Electron/SSE patternsexec.Command("claude",...) to ACP client with goroutine streamingsubprocess.run(["claude",..]) to ACP client (sync + async)A self-contained SSE server + dark-theme UI template for streaming ACP events to a browser.
See references/visualizer.md for setup instructions.
Template files in assets/visualizer/.
initialize must be called first -- before any other method, with protocolVersion: 1mcpServers: [] -- pass empty array if unused, otherwise loads from project configid -- fire-and-forget from agent_meta.claudeCode.toolName -- real tool name is here, not in update.titleflush() stdin or use bufsize=1drain() after write -- asyncio.subprocess needs explicit drainnpx claudepluginhub liviogama/migrate-claude-sdk-to-acp --plugin migrate-claude-sdk-to-acpDocuments acpx CLI for ACP agent communication, including commands, persistent sessions, prompt queueing, background execution, and built-in agents like codex.
Implements Anthropic Claude Agent SDK for autonomous agents, subagents, tool orchestration, MCP servers, and multi-step workflows. Useful for session management, permissions, and errors like CLI not found or context exceeded.
Guides programmatic control of Claude Code sessions via Claude Agent SDK in TypeScript/JavaScript or Python. Supports custom agents, tools, streaming, and event handling for building AI agents.