Architecture: OpenClaw-OpenCode ACP Integration

Status: Draft
Date: 2026-02-12

System Overview

┌─────────────────────────────────────────────────────────────────┐
│                         User Layer                               │
│  WhatsApp, Telegram, Discord, Signal, Web Chat                  │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                    OpenClaw Gateway                              │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Agent Runtime (Claude, GPT-4, etc.)                      │  │
│  │    ├─ Tools: browser, message, nodes, exec...            │  │
│  │    └─ NEW: opencode (ACP client tool)                    │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────────────────────┬────────────────────────────────────┘
                             │ HTTP / WebSocket
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                  OpenCode ACP Server(s)                          │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐   │
│  │  Instance 1    │  │  Instance 2    │  │  Instance N    │   │
│  │  (local dev)   │  │  (VPS build)   │  │  (team shared) │   │
│  │  Port: 3333    │  │  Port: 3334    │  │  Port: 3335    │   │
│  └────────────────┘  └────────────────┘  └────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                    OpenCode Agent Sessions                       │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │ Session 1   │  │ Session 2   │  │ Session 3   │             │
│  │ api-server  │  │ dashboard   │  │ PR review   │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
│            (Each session = isolated agent context)               │
└─────────────────────────────────────────────────────────────────┘

Component Design

1. OpenCode ACP Server

What it is:

  • Headless daemon that manages OpenCode agent sessions
  • Exposes HTTP + WebSocket endpoints for programmatic control
  • Runs independently of OpenClaw (local or remote)

Startup:

# Basic
opencode acp --port 3333
 
# Remote-accessible
opencode acp --hostname 0.0.0.0 --port 3333
 
# With default working directory
opencode acp --port 3333 --cwd ~/projects
 
# Alternative: web mode (if ACP is included)
opencode web --hostname 0.0.0.0 --port 3333

Responsibilities:

  • Accept session create/attach requests
  • Route messages to appropriate agent sessions
  • Stream agent output back to clients
  • Persist session state (TBD: confirm OpenCode behavior)

State Management:

  • Sessions stored in-memory + disk (TBD: confirm persistence)
  • Session IDs are UUIDs or human-readable names
  • Clients reconnect via session ID

2. OpenClaw opencode Tool

What it is:

  • Native OpenClaw tool (like browser, message, exec)
  • Speaks ACP protocol (HTTP for commands, WebSocket for streaming)
  • Manages connection lifecycle to one or more ACP servers

Tool Interface:

interface OpenCodeTool {
  // Connection management
  connect(url: string, name?: string): Promise<ConnectionStatus>
  disconnect(name?: string): Promise<void>
  
  // Session operations
  createSession(config: SessionConfig): Promise<SessionInfo>
  listSessions(instanceName?: string): Promise<SessionInfo[]>
  attachSession(sessionId: string): Promise<SessionInfo>
  terminateSession(sessionId: string): Promise<void>
  
  // Messaging
  sendMessage(sessionId: string, message: string): Promise<MessageResponse>
  getMessages(sessionId: string, limit?: number): Promise<Message[]>
  
  // Status & monitoring
  getStatus(sessionId: string): Promise<SessionStatus>
  getStats(sessionId: string): Promise<TokenStats>
  exportSession(sessionId: string): Promise<SessionExport>
  
  // Stream handling (internal)
  subscribeToSession(sessionId: string, callback: (event: AgentEvent) => void): void
  unsubscribeFromSession(sessionId: string): void
}

Connection State:

interface ConnectionState {
  instances: Map<string, {
    url: string,
    connected: boolean,
    lastPing: Date,
    activeSessions: Set<string>
  }>
}

Tool Parameters (OpenClaw function call format):

{
  "tool": "opencode",
  "action": "create_session",
  "instance": "local",
<<<<<<< HEAD
  "project": "/workspace/projects/api-server",
=======
  "project": "repos/api-server",
>>>>>>> f146a548fbab991798ce91719b88cbf1f064588a
  "message": "Add rate limiting to /login endpoint",
  "model": "anthropic/claude-sonnet-4",
  "agent": "default"
}

3. ACP Protocol Client

Implementation:

  • TypeScript library (or Node.js module)
  • Handles HTTP requests + WebSocket streams
  • Manages reconnection, retries, backoff

Core Classes:

class ACPClient {
  constructor(baseUrl: string, options?: ClientOptions)
  
  // HTTP operations
  async createSession(config: SessionConfig): Promise<SessionInfo>
  async listSessions(): Promise<SessionInfo[]>
  async sendMessage(sessionId: string, message: string): Promise<void>
  async getStatus(sessionId: string): Promise<SessionStatus>
  async terminateSession(sessionId: string): Promise<void>
  
  // WebSocket streaming
  connectStream(sessionId: string): WebSocket
  onMessage(callback: (event: AgentEvent) => void): void
  onError(callback: (error: Error) => void): void
  disconnect(): void
}
 
class ACPConnectionManager {
  private clients: Map<string, ACPClient>
  
  addInstance(name: string, url: string): void
  removeInstance(name: string): void
  getClient(name: string): ACPClient | undefined
  healthCheck(): Promise<Map<string, boolean>>
}

Reconnection Strategy:

  • Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (max)
  • Keep session ID across reconnects
  • Resubscribe to WebSocket streams after reconnect

4. Enhanced coding-agent Skill

Current: CLI-only (codex exec, opencode run, etc.)

Enhanced: Smart mode detection

## Mode Selection
 
1. Check if ACP server configured and reachable
2. If yes: Use ACP mode (persistent sessions, better UX)
3. If no: Fall back to CLI mode (spawn process, one-shot)
 
## Usage Examples
 
### ACP Mode (preferred)
```bash
# User: "Start OpenCode on api-server and add rate limiting"
# OpenClaw detects ACP server at localhost:3333
 
opencode action:create_session project:~/projects/api-server message:"Add rate limiting"
# Returns: session_id = "abc123"
 
opencode action:get_status session:abc123
# Polls status until complete
 
# Later:
opencode action:attach session:abc123 message:"Now write tests"

CLI Mode (fallback)

# No ACP server found, use legacy mode
 
bash pty:true workdir:~/projects/api-server command:"opencode run 'Add rate limiting'"
# Blocks until complete

**Migration Path:**
- Skill detects ACP via config or auto-discovery (try localhost:3333)
- Logs which mode is used (for user transparency)
- Graceful fallback if ACP connection fails

---

## Data Flow

### Create Session + Send Message

User: “Start OpenCode on api-server, add rate limiting” │ ▼ OpenClaw Agent parses intent │ ▼ <<<<<<< HEAD Calls: opencode(action=“create_session”, project=“/workspace/projects/api-server”,

Calls: opencode(action=“create_session”, project=“repos/api-server”,

f146a548fbab991798ce91719b88cbf1f064588a message=“Add rate limiting to /login endpoint”) │ ▼ opencode tool → HTTP POST to ACP server POST /sessions Body: { <<<<<<< HEAD “project”: “/workspace/projects/api-server”, ======= “project”: “repos/api-server”, f146a548fbab991798ce91719b88cbf1f064588a “model”: “anthropic/claude-sonnet-4”, “agent”: “default” } │ ▼ ACP Server creates session Response: { “sessionId”: “abc123”, “status”: “created” } │ ▼ opencode tool → WebSocket connect to /sessions/abc123/stream │ ▼ opencode tool → HTTP POST message POST /sessions/abc123/messages Body: { “content”: “Add rate limiting to /login endpoint” } │ ▼ ACP Server routes to agent │ ▼ Agent works (reads files, makes changes, runs commands) │ ▼ Agent events streamed via WebSocket WS: { “type”: “file_opened”, “path”: “src/auth.ts” } WS: { “type”: “file_changed”, “path”: “src/auth.ts”, “additions”: 12 } WS: { “type”: “command_run”, “command”: “npm test” } WS: { “type”: “message”, “content”: “Tests passed!” } WS: { “type”: “complete”, “summary”: “Added express-rate-limit” } │ ▼ opencode tool buffers events, forwards to OpenClaw │ ▼ OpenClaw Agent formats for user │ ▼ User receives updates via WhatsApp: ”📂 Opened src/auth.ts” “✏️ Modified src/auth.ts (+12 lines)” ”🧪 Running tests…” ”✅ Tests passed!” “Done! Added express-rate-limit to /login. Ready to commit?”


---

## State Persistence

### OpenClaw Side
**Problem:** Gateway restarts shouldn't lose session references

**Solution:** Persist connection config + active sessions to disk
```typescript
// ~/.openclaw/config/opencode-state.json
{
  "instances": [
    {
      "name": "local",
      "url": "http://localhost:3333",
      "lastConnected": "2026-02-12T08:00:00Z"
    },
    {
      "name": "vps-heavy",
      "url": "http://vps.example.com:3333",
      "lastConnected": "2026-02-11T15:30:00Z"
    }
  ],
  "activeSessions": [
    {
      "sessionId": "abc123",
      "instance": "local",
<<<<<<< HEAD
      "project": "/workspace/projects/api-server",
=======
      "project": "repos/api-server",
>>>>>>> f146a548fbab991798ce91719b88cbf1f064588a
      "created": "2026-02-12T07:45:00Z",
      "lastActive": "2026-02-12T08:00:00Z"
    }
  ]
}

On Gateway restart:

  1. Load state file
  2. Reconnect to instances (with health check)
  3. Reattach to active sessions (if still alive)

OpenCode Side

Question: Does opencode acp persist sessions across restarts?

If yes: Gateway just needs to track session IDs If no: Need to handle “session lost” gracefully (notify user)


Error Handling

Connection Failures

try {
  await opencode.connect('http://localhost:3333')
} catch (err) {
  if (err.code === 'ECONNREFUSED') {
    return "OpenCode ACP server not running. Start with: opencode acp --port 3333"
  } else if (err.code === 'TIMEOUT') {
    return "OpenCode server not responding. Check if it's healthy."
  } else {
    throw err
  }
}

Session Lost

// WebSocket disconnected, can't reconnect to session
if (sessionGone) {
  notify user: "⚠️ Session abc123 was lost (server restarted?). You can:
    1. Create new session
    2. List other active sessions
    3. Check if work was saved"
}

Permission Timeout

// User didn't respond to permission request in 5 minutes
if (permissionTimedOut) {
  send to agent: "Permission denied (timeout)"
  notify user: "⏰ Permission request expired. Agent paused at: npm install express-rate-limit"
}

Security Considerations

Authentication

Question: Does ACP require auth tokens?

If yes:

  • Store tokens in OpenClaw config (encrypted?)
  • Include in HTTP headers: Authorization: Bearer <token>

If no:

  • Rely on network isolation (localhost-only or VPN)
  • Consider adding basic auth for remote instances

Command Execution

Risk: Agent runs arbitrary commands on OpenCode host

Mitigation:

  • Use OpenCode’s built-in sandboxing
  • Rely on permission approval flow (commands > danger threshold require approval)
  • OpenClaw forwards approvals from user, doesn’t auto-approve

Data Exfiltration

Risk: Agent could read sensitive files, send to LLM provider

Mitigation:

  • OpenCode’s .gitignore + .opencodeignore patterns
  • User controls which directories are accessible (via --cwd)
  • Session scope limited to project directory

Deployment Topologies

Topology 1: Local Dev (Simplest)

┌──────────────────────────────────┐
│  Developer's Laptop               │
│  ┌─────────────────────────────┐ │
│  │  OpenClaw Gateway           │ │
│  └──────────┬──────────────────┘ │
│             │ localhost:3333      │
│  ┌──────────▼──────────────────┐ │
│  │  opencode acp               │ │
│  └─────────────────────────────┘ │
└──────────────────────────────────┘

Use case: Personal dev, quick tasks


Topology 2: Remote Build Server

┌──────────────────┐         ┌────────────────────────┐
│  OpenClaw        │         │  VPS (beefy)           │
│  (Gateway)       │◄───────►│  ┌──────────────────┐  │
│  Laptop/K8s      │  HTTP   │  │ opencode acp     │  │
│                  │         │  │ --hostname 0.0.0.0│ │
└──────────────────┘         │  └──────────────────┘  │
                             │  Port: 3333             │
                             └────────────────────────┘

Use case: Heavy builds, keep laptop cool


Topology 3: Team Collaboration

┌────────────────┐       ┌──────────────────────────┐
│  Alice         │       │  Shared OpenCode Server  │
│  (OpenClaw)    │◄─────►│  (K8s / VM)              │
└────────────────┘       │  ┌────────────────────┐  │
                         │  │ opencode acp       │  │
┌────────────────┐       │  │ Multi-session      │  │
│  Bob           │◄─────►│  └────────────────────┘  │
│  (OpenClaw)    │       │  Port: 3333              │
└────────────────┘       │  Auth: Required          │
                         └──────────────────────────┘

Use case: Pair programming, shared review sessions


Topology 4: Multi-Instance (Advanced)

┌─────────────────────────────────────────────────┐
│  OpenClaw Gateway                               │
│  ┌──────────────────────────────────────────┐  │
│  │  opencode tool                           │  │
│  │  ┌──────────┬──────────┬──────────────┐ │  │
│  │  │Instance 1│Instance 2│  Instance 3  │ │  │
│  │  │ (local)  │ (VPS)    │  (team)      │ │  │
│  │  │ :3333    │ :3334    │  :3335       │ │  │
│  │  └──────────┴──────────┴──────────────┘ │  │
│  └──────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘

Use case: Different projects, different hardware, different teams

Config:

opencode:
  instances:
    - name: local
      url: http://localhost:3333
      default: true
    - name: vps-heavy
      url: http://build-server.example.com:3333
      auth_token: "xxxxx"
    - name: team-shared
      url: http://team-opencode.internal:3333
      auth_token: "yyyyy"

Implementation Phases

Phase 0: Discovery (1-2 days)

  • Run opencode acp and document endpoints
  • Test with curl / wscat
  • Understand WebSocket event format
  • Check if opencode web includes ACP
  • Document authentication requirements

Phase 1: Minimal Client (3-5 days)

  • Build TypeScript ACP client library
  • Implement: create session, send message, get status
  • Test against opencode acp instance
  • Handle basic errors (connection refused, timeout)

Phase 2: OpenClaw Tool (5-7 days)

  • Create opencode tool for OpenClaw
  • Wire into tool registry
  • Implement tool actions: create, list, attach, status
  • Add connection state management
  • Test via OpenClaw agent (WhatsApp → OpenClaw → ACP)

Phase 3: Streaming & UX (3-5 days)

  • WebSocket subscription to agent events
  • Format events for messaging surfaces
  • Progress notifications (file changes, commands, etc.)
  • Completion notifications with summary

Phase 4: Persistence & Resilience (3-5 days)

  • State file for instances + sessions
  • Auto-reconnect on connection loss
  • Session recovery after Gateway restart
  • Graceful degradation (ACP → CLI fallback)

Phase 5: Multi-Instance & Permissions (5-7 days)

  • Support multiple ACP servers
  • Instance selection (default, by name, by project)
  • Permission approval flow (bidirectional)
  • Advanced features: export, stats, etc.

Total estimate: 3-4 weeks for production-ready integration


Testing Strategy

Unit Tests

  • ACP client library (HTTP calls, WebSocket handling)
  • Connection manager (instance registry, health checks)
  • Error handling (retries, backoff, fallback)

Integration Tests

  • Spin up mock ACP server
  • OpenClaw tool calls → mock server responses
  • Verify state persistence (restart Gateway, sessions survive)

End-to-End Tests

  • Real opencode acp instance
  • OpenClaw agent receives WhatsApp message
  • Session created, message sent, status polled
  • Verify user receives formatted updates

Load Tests

  • 10 concurrent sessions
  • Measure: latency, memory usage, error rate
  • Confirm: no session crosstalk, clean teardown

Open Technical Questions

  1. ACP Endpoints: What are the exact HTTP routes? (Needs documentation or code inspection)
  2. WebSocket Protocol: What events does OpenCode stream? Format?
  3. Session Lifecycle: How does OpenCode handle pause/resume? Persist across restarts?
  4. Multi-Client: Can multiple clients attach to same session? (For team features)
  5. Model Selection: Can you specify LLM model per-session via ACP?
  6. Rate Limiting: Does ACP enforce rate limits? How does it handle overload?
  7. opencode web: Does it expose ACP endpoints, or is it a separate web-only mode?

Next step: Run discovery phase to answer these questions.


References

  • OpenCode CLI help: opencode --help, opencode acp --help <<<<<<< HEAD
  • Existing bridge: /workspace/projects/ai-dev/plugins/opencode-bridge/ =======
  • Existing bridge: projects/ai-dev/plugins/opencode-bridge/ (if it exists)

f146a548fbab991798ce91719b88cbf1f064588a

  • OpenClaw tool patterns: browser tool (persistent connections), exec tool (process management)