home / skills / openclaw / skills / opencode-acp-control

opencode-acp-control skill

/skills/bjesuiter/opencode-acp-control

This skill enables controlling OpenCode via ACP to start sessions, send and read prompts, resume conversations, and manage updates.

npx playbooks add skill openclaw/skills --skill opencode-acp-control

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
9.6 KB
---
name: opencode-acp-control
description: Control OpenCode directly via the Agent Client Protocol (ACP). Start sessions, send prompts, resume conversations, and manage OpenCode updates.
metadata: {"version": "1.0.2", "author": "Benjamin Jesuiter <[email protected]>", "license": "MIT", "github_url": "https://github.com/bjesuiter/opencode-acp-skill"}
---

# OpenCode ACP Skill

Control OpenCode directly via the Agent Client Protocol (ACP).

## Metadata

- For ACP Protocol Docs (for Agents/LLMs): https://agentclientprotocol.com/llms.txt
- GitHub Repo: https://github.com/bjesuiter/opencode-acp-skill
- If you have issues with this skill, please open an issue ticket here: https://github.com/bjesuiter/opencode-acp-skill/issues

## Quick Reference

| Action | How |
|--------|-----|
| Start OpenCode | `bash(command: "opencode acp", background: true)` |
| Send message | `process.write(sessionId, data: "<json-rpc>\n")` |
| Read response | `process.poll(sessionId)` - repeat every 2 seconds |
| Stop OpenCode | `process.kill(sessionId)` |
| List sessions | `bash(command: "opencode session list", workdir: "...")` |
| Resume session | List sessions → ask user → `session/load` |
| Check version | `bash(command: "opencode --version")` |

## Starting OpenCode

```
bash(
  command: "opencode acp",
  background: true,
  workdir: "/path/to/your/project"
)
```

Save the returned `sessionId` - you'll need it for all subsequent commands.

## Protocol Basics

- All messages are **JSON-RPC 2.0** format
- Messages are **newline-delimited** (end each with `\n`)
- Maintain a **message ID counter** starting at 0

## Step-by-Step Workflow

### Step 1: Initialize Connection

Send immediately after starting OpenCode:

```json
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"clawdbot","title":"Clawdbot","version":"1.0.0"}}}
```

Poll for response. Expect `result.protocolVersion: 1`.

### Step 2: Create Session

```json
{"jsonrpc":"2.0","id":1,"method":"session/new","params":{"cwd":"/path/to/project","mcpServers":[]}}
```

Poll for response. Save `result.sessionId` (e.g., `"sess_abc123"`).

### Step 3: Send Prompts

```json
{"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{"sessionId":"sess_abc123","prompt":[{"type":"text","text":"Your question here"}]}}
```

Poll every 2 seconds. You'll receive:
- `session/update` notifications (streaming content)
- Final response with `result.stopReason`

### Step 4: Read Responses

Each poll may return multiple lines. Parse each line as JSON:

- **Notifications**: `method: "session/update"` - collect these for the response
- **Response**: Has `id` matching your request - stop polling when `stopReason` appears

### Step 5: Cancel (if needed)

```json
{"jsonrpc":"2.0","method":"session/cancel","params":{"sessionId":"sess_abc123"}}
```

No response expected - this is a notification.

## State to Track

Per OpenCode instance, track:
- `processSessionId` - from bash tool (clawdbot's process ID)
- `opencodeSessionId` - from session/new response (OpenCode's session ID)  
- `messageId` - increment for each request you send

## Polling Strategy

- Poll every **2 seconds**
- Continue until you receive a response with `stopReason`
- Max wait: **5 minutes** (150 polls)
- If no response, consider the operation timed out

## Common Stop Reasons

| stopReason | Meaning |
|------------|---------|
| `end_turn` | Agent finished responding |
| `cancelled` | You cancelled the prompt |
| `max_tokens` | Token limit reached |

## Error Handling

| Issue | Solution |
|-------|----------|
| Empty poll response | Keep polling - agent is thinking |
| Parse error | Skip malformed line, continue |
| Process exited | Restart OpenCode |
| No response after 5min | Kill process, start fresh |

## Example: Complete Interaction

```
1. bash(command: "opencode acp", background: true, workdir: "/home/user/myproject")
   -> processSessionId: "bg_42"

2. process.write(sessionId: "bg_42", data: '{"jsonrpc":"2.0","id":0,"method":"initialize",...}\n')
   process.poll(sessionId: "bg_42") -> initialize response

3. process.write(sessionId: "bg_42", data: '{"jsonrpc":"2.0","id":1,"method":"session/new","params":{"cwd":"/home/user/myproject","mcpServers":[]}}\n')
   process.poll(sessionId: "bg_42") -> opencodeSessionId: "sess_xyz789"

4. process.write(sessionId: "bg_42", data: '{"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{"sessionId":"sess_xyz789","prompt":[{"type":"text","text":"List all TypeScript files"}]}}\n')
   
5. process.poll(sessionId: "bg_42") every 2 sec until stopReason
   -> Collect all session/update content
   -> Final response: stopReason: "end_turn"

6. When done: process.kill(sessionId: "bg_42")
```

---

## Resume Session

Resume a previous OpenCode session by letting the user choose from available sessions.

### Step 1: List Available Sessions

```
bash(command: "opencode session list", workdir: "/path/to/project")
```

Example output:
```
ID                                  Updated              Messages
ses_451cd8ae0ffegNQsh59nuM3VVy      2026-01-11 15:30     12
ses_451a89e63ffea2TQIpnDGtJBkS      2026-01-10 09:15     5
ses_4518e90d0ffeJIpOFI3t3Jd23Q      2026-01-09 14:22     8
```

### Step 2: Ask User to Choose

Present the list to the user and ask which session to resume:

```
"Which session would you like to resume?
 
1. ses_451cd8ae... (12 messages, updated 2026-01-11)
2. ses_451a89e6... (5 messages, updated 2026-01-10)
3. ses_4518e90d... (8 messages, updated 2026-01-09)

Enter session number or ID:"
```

### Step 3: Load Selected Session

Once user responds (e.g., "1", "the first one", or "ses_451cd8ae..."):

1. **Start OpenCode ACP**:
   ```
   bash(command: "opencode acp", background: true, workdir: "/path/to/project")
   ```

2. **Initialize**:
   ```json
   {"jsonrpc":"2.0","id":0,"method":"initialize","params":{...}}
   ```

3. **Load the session**:
   ```json
   {"jsonrpc":"2.0","id":1,"method":"session/load","params":{"sessionId":"ses_451cd8ae0ffegNQsh59nuM3VVy","cwd":"/path/to/project","mcpServers":[]}}
   ```

**Note**: `session/load` requires `cwd` and `mcpServers` parameters.

On load, OpenCode streams the full conversation history back to you.

### Resume Workflow Summary

```
function resumeSession(workdir):
    # List available sessions
    output = bash("opencode session list", workdir: workdir)
    sessions = parseSessionList(output)
    
    if sessions.empty:
        notify("No previous sessions found. Starting fresh.")
        return createNewSession(workdir)
    
    # Ask user to choose
    choice = askUser("Which session to resume?", sessions)
    selectedId = matchUserChoice(choice, sessions)
    
    # Start OpenCode and load session
    process = bash("opencode acp", background: true, workdir: workdir)
    initialize(process)
    
    session_load(process, selectedId, workdir, mcpServers: [])
    
    notify("Session resumed. Conversation history loaded.")
    return process
```

### Important Notes

- **History replay**: On load, all previous messages stream back
- **Memory preserved**: Agent remembers the full conversation
- **Process independent**: Sessions survive OpenCode restarts

---

## Updating OpenCode

OpenCode auto-updates when restarted. Use this workflow to check and trigger updates.

### Step 1: Check Current Version

```
bash(command: "opencode --version")
```

Returns something like: `opencode version 1.1.13`

Extract the version number (e.g., `1.1.13`).

### Step 2: Check Latest Version

```
webfetch(url: "https://github.com/anomalyco/opencode/releases/latest", format: "text")
```

The redirect URL contains the latest version tag:
- Redirects to: `https://github.com/anomalyco/opencode/releases/tag/v1.2.0`
- Extract version from the URL path (e.g., `1.2.0`)

### Step 3: Compare and Update

If latest version > current version:

1. **Stop all running OpenCode processes**:
   ```
   process.list()  # Find all "opencode acp" processes
   process.kill(sessionId) # For each running instance
   ```

2. **Restart instances** (OpenCode auto-downloads new binary on start):
   ```
   bash(command: "opencode acp", background: true, workdir: "/path/to/project")
   ```

3. **Re-initialize** each instance (initialize + session/load for existing sessions)

### Step 4: Verify Update

```
bash(command: "opencode --version")
```

If version still doesn't match latest:
- Inform user: "OpenCode auto-update may have failed. Current: X.X.X, Latest: Y.Y.Y"
- Suggest manual update: `curl -fsSL https://opencode.dev/install | bash`

### Update Workflow Summary

```
function updateOpenCode():
    current = bash("opencode --version")  # e.g., "1.1.13"
    
    latestPage = webfetch("https://github.com/anomalyco/opencode/releases/latest")
    latest = extractVersionFromRedirectUrl(latestPage)  # e.g., "1.2.0"
    
    if semverCompare(latest, current) > 0:
        # Stop all instances
        for process in process.list():
            if process.command.includes("opencode"):
                process.kill(process.sessionId)
        
        # Wait briefly for processes to terminate
        sleep(2 seconds)
        
        # Restart triggers auto-update
        bash("opencode acp", background: true)
        
        # Verify
        newVersion = bash("opencode --version")
        if newVersion != latest:
            notify("Auto-update may have failed. Manual update recommended.")
    else:
        notify("OpenCode is up to date: " + current)
```

### Important Notes

- **Sessions persist**: `opencodeSessionId` survives restarts — use `session/load` to recover
- **Auto-update**: OpenCode downloads new binary automatically on restart
- **No data loss**: Conversation history is preserved server-side

Overview

This skill lets you control OpenCode via the Agent Client Protocol (ACP). It provides commands and workflows to start OpenCode, initialize ACP sessions, send prompts, resume conversations, and manage automatic updates. Use it to programmatically run and maintain OpenCode instances tied to project workdirs.

How this skill works

The skill starts OpenCode in ACP mode as a background process and tracks the returned process session ID. It speaks JSON-RPC 2.0 over newline-delimited messages, maintaining a message ID counter and polling the process every 2 seconds for session updates and final responses. It supports creating new sessions, loading existing sessions, canceling prompts, and orchestrating version checks and restart-based updates.

When to use it

  • Automate conversational workflows against OpenCode from an agent or bot
  • Resume a previous OpenCode conversation and replay history into a new process
  • Run repeated prompts and stream partial agent output for interactive UIs
  • Manage and verify OpenCode versions across development environments
  • Trigger and validate OpenCode auto-updates for multiple instances

Best practices

  • Always save both the processSessionId (background tool) and opencodeSessionId (session/new) for each instance
  • Initialize immediately after starting OpenCode using the prescribed initialize JSON-RPC message
  • Poll every 2 seconds and stop after a stopReason or a 5-minute timeout (150 polls)
  • Parse each newline as a separate JSON object: collect session/update notifications and stop when the request id returns a stopReason
  • When updating, stop all running opencode acp processes, restart to trigger auto-download, then re-initialize and reload sessions

Example use cases

  • Start OpenCode in a project directory, initialize ACP, create a new session, and ask for a code search or refactor plan
  • List stored sessions, present them to a user, load the chosen session and replay full conversation history into a fresh process
  • Send a prompt that produces streaming partial results via session/update notifications and aggregate them until stopReason=end_turn
  • Automate periodic version checks: compare local opencode --version with GitHub latest release and restart instances to trigger auto-update

FAQ

What format must messages use?

All requests and notifications must be JSON-RPC 2.0 and newline-delimited; increment your message id counter for each request.

How long should I poll for responses?

Poll every 2 seconds and stop when you receive a stopReason or after 5 minutes (150 polls) to avoid hanging.

How do I recover conversations after a restart?

List available sessions, start OpenCode ACP, run initialize, then call session/load with the saved opencodeSessionId and cwd; OpenCode will stream the full history.