# Opencode Acp

> Control OpenCode directly via the Agent Client Protocol (ACP). Start sessions, send prompts, resume conversations, and manage OpenCode updates. Use when driving OpenCode headless or programmatically over ACP - starting a session, sending prompts, resuming a prior session, or checking and triggering OpenCode updates.

- Skill: `mahdtech/opencode-acp` (Agent Skill, multi-file: 125 files)
- Install (CLI): `npx skillmds@latest add mahdtech/opencode-acp`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mahdtech/opencode-acp/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: MAHDTech (https://skillmd.com/u/mahdtech)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mahdtech/opencode-acp

---


# OpenCode ACP Skill

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

**See also:** `/opencode` - for driving the OpenCode CLI interactively (commands, agents, tools, MCP, config) rather than over ACP.

## Metadata

- For ACP Protocol Docs (for Agents/LLMs): [Introduction](resources/auto/get-started-introduction.md) and [Protocol Overview](resources/auto/protocol-v1-overview.md)
- 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 --cwd /path/to/project", 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 --cwd /path/to/your/project",
  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 --cwd <project-dir>", background: true, workdir: "<project-dir>")
   -> 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":"<project-dir>","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 --cwd /path/to/project", 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 --cwd " + workdir, 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/sst/opencode/releases/latest", format: "text")
```

The redirect URL contains the latest version tag:

- Redirects to: `https://github.com/sst/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 --cwd /path/to/project", 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.ai/install | bash`

### Update Workflow Summary

```
function updateOpenCode():
    current = bash("opencode --version")  # e.g., "1.1.13"

    latestPage = webfetch("https://github.com/sst/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

