OpenCode Server Protocol - Phase 0 Findings

Status: Initial Discovery Complete
OpenCode Version: 1.1.53
Discovery Date: 2026-02-15
Environment: codev pod (code-server namespace)


Executive Summary

OpenCode 1.1.53 provides two protocols for different use cases:

1. ACP (Agent Client Protocol) - JSON-RPC over stdio

Use case: Editor integrations (VSCode, Cursor, etc.)

Per https://opencode.ai/docs/acp/:

To use OpenCode via ACP, configure your editor to run the opencode acp command. The command starts OpenCode as an ACP-compatible subprocess that communicates with your editor over JSON-RPC via stdio.

  • Protocol: JSON-RPC over stdin/stdout
  • Command: opencode acp
  • Target: Editor extensions
  • Not suitable for: OpenClaw integration (we’re not an editor)

2. HTTP REST API + SSE - For programmatic access

Use case: Web UI, automation, external integrations (like OpenClaw!)

  • Protocol: HTTP REST + Server-Sent Events
  • Command: opencode web or opencode serve
  • Target: Browser UI, programmatic clients
  • ✅ Perfect for: OpenClaw Gateway integration

Key Findings:

  • ✅ HTTP REST API fully documented and tested
  • ✅ Server-Sent Events (SSE) for real-time updates
  • ✅ Session creation/messaging/listing all working
  • ✅ Session storage on filesystem
  • Phase 0 Complete

Running Server in codev

CRITICAL DISCOVERY: Server is workspace-specific!

The OpenCode web server serves sessions from the workspace where it was started. This means:

  • Starting from /home/coder/src → serves sessions from project 58c042e67dd312114ee098f01f12b2db0226abca
  • Starting from /home/coder → serves sessions from project global
  • Starting from /home/coder/src/repos/k8s-lab → serves sessions from project 74f4e11b32dded159775570fcf47b20dd2a9ff39

Current Deployment:

# Must start from the correct workspace directory!
cd /home/coder/src
opencode web --hostname 0.0.0.0 --port 5400 &

Accessible at:

  • Internal: http://localhost:5400 (from within pod)
  • Service: codev.code-server:5400 (from other pods)
  • Port-forward: kubectl port-forward -n code-server deployment/codev 5400:5400

Multi-Workspace Strategy: For OpenClaw integration with multiple builder workspaces, we have options:

  1. Single server per workspace - Run separate OpenCode servers on different ports
  2. Switch context - Restart server when switching workspaces (not ideal)
  3. Project-scoped API calls - Check if /project/:id/session endpoints exist (needs testing)

HTTP REST API - Complete Documentation

Base URL

http://localhost:5400  (codev pod)

Project & Workspace Management

List All Projects

GET /project

List all OpenCode projects (workspaces) registered in the system.

curl http://localhost:5400/project | jq '.'

Response (200 OK):

[
  {
    "id": "58c042e67dd312114ee098f01f12b2db0226abca",
    "worktree": "/home/coder/src",
    "vcs": "git",
    "sandboxes": [],
    "time": {
      "created": 1770323322243,
      "updated": 1771248366416
    },
    "icon": {
      "color": "pink"
    }
  },
  {
    "id": "74f4e11b32dded159775570fcf47b20dd2a9ff39",
    "worktree": "/home/coder/src/repos/k8s-lab",
    "vcs": "git",
    "sandboxes": [],
    "time": {
      "created": 1770375621736,
      "updated": 1770375673994
    }
  },
  {
    "id": "global",
    "worktree": "/",
    "sandboxes": [],
    "time": {
      "created": 1770323336398,
      "updated": 1771247827349
    }
  }
]

Key Fields:

  • id - Unique project hash (SHA-1 of worktree path, or “global”)
  • worktree - Absolute path to workspace directory
  • vcs - Version control system (“git” or omitted)
  • sandboxes - Array of sandbox environments (usually empty)
  • time - Creation/update timestamps
  • icon - UI display properties

Project ID Calculation:

// Project ID is SHA-1 hash of worktree path
// Example: SHA1("/home/coder/src") = "58c042e67dd312114ee098f01f12b2db0226abca"
// Special case: "global" project has id "global"

Workspace-Specific Behavior

CRITICAL: The OpenCode HTTP server is workspace-scoped:

  • GET /session returns sessions only from the workspace where the server started
  • POST /session creates sessions in the current workspace
  • POST /session/:id/message only works for sessions in the current workspace

To access sessions from different workspaces:

  1. Start OpenCode server from that workspace’s directory
  2. Or run multiple OpenCode servers (one per workspace) on different ports

Session Management

Create Session

POST /session

Create a new OpenCode session.

curl -X POST http://localhost:5400/session \
  -H 'Content-Type: application/json' \
  -d '{
    "directory": "/home/coder/src/.builders/my-builder",
    "title": "My Session Title",
    "agent": "default"
  }'

Request Body:

{
  "directory": "/home/coder/src/.builders/my-builder",  // Working directory
  "title": "My Session Title",                          // Human-readable title
  "agent": "default"                                    // Agent type
}

Response (200 OK):

{
  "id": "ses_39a240ec5ffe9TtVT3xbBgKfTl",
  "slug": "glowing-circuit",
  "version": "1.1.48",
  "projectID": "global",
  "directory": "/home/coder",
  "title": "Test API Session",
  "time": {
    "created": 1771235438906,
    "updated": 1771235438906
  }
}

List All Sessions

GET /session

List all sessions in the workspace where the server was started.

⚠️ CRITICAL: This endpoint is workspace-specific! The server only returns sessions from the project/workspace where opencode web was launched.

curl http://localhost:5400/session

Response (200 OK):

[
  {
    "id": "ses_39a240ec5ffe9TtVT3xbBgKfTl",
    "slug": "glowing-circuit",
    "version": "1.1.48",
    "projectID": "global",
    "directory": "/home/coder",
    "title": "Test API Session",
    "time": {
      "created": 1771235438906,
      "updated": 1771235463633
    },
    "summary": {
      "additions": 0,
      "deletions": 0,
      "files": 0
    }
  }
]

Get Session Details

GET /session/:id

Get details for a specific session.

curl http://localhost:5400/session/ses_39a240ec5ffe9TtVT3xbBgKfTl

Response (200 OK):

{
  "id": "ses_39a240ec5ffe9TtVT3xbBgKfTl",
  "slug": "glowing-circuit",
  "version": "1.1.48",
  "projectID": "global",
  "directory": "/home/coder",
  "title": "Test API Session",
  "time": {
    "created": 1771235438906,
    "updated": 1771235463633
  },
  "summary": {
    "additions": 0,
    "deletions": 0,
    "files": 0
  }
}

Send Message to Session

POST /session/:id/message

Send a message to an existing session.

curl -X POST http://localhost:5400/session/ses_39a240ec5ffe9TtVT3xbBgKfTl/message \
  -H 'Content-Type: application/json' \
  -d '{
    "parts": [
      {
        "type": "text",
        "text": "What files are in this directory?"
      }
    ]
  }'

Request Body:

{
  "parts": [
    {
      "type": "text",
      "text": "Your message here"
    }
  ],
  "model": {
    "providerID": "anthropic",
    "modelID": "claude-sonnet-4-5-20250929"
  }
}

Note: The model field is optional. If omitted, uses session’s default model.

Response Behavior:

⚠️ IMPORTANT: This endpoint streams the response and may take a long time (2+ minutes) to complete. The HTTP connection remains open while Claude processes the message.

The response includes the full assistant message once complete:

{
  "info": {
    "id": "msg_...",
    "sessionID": "ses_...",
    "role": "assistant",
    "time": {...},
    "modelID": "...",
    "providerID": "...",
    "mode": "build",
    "agent": "build",
    ...
  },
  "parts": [...]
}

For real-time updates: Use GET /global/event (SSE) to receive streaming updates instead of waiting for POST to complete.


Get Messages from Session

GET /session/:id/message

Retrieve all messages from a session.

curl http://localhost:5400/session/ses_3ae187259ffeMdIpeZbzA5R4ZU/message | jq '.'

Response (200 OK):

[
  {
    "info": {
      "id": "msg_...",
      "sessionID": "ses_3ae187259ffeMdIpeZbzA5R4ZU",
      "role": "user",
      "time": {
        "created": 1771248461234,
        "updated": 1771248461234
      }
    },
    "parts": [
      {
        "type": "text",
        "text": "Test from HTTP API - please respond with OK"
      }
    ]
  },
  {
    "info": {
      "id": "msg_...",
      "sessionID": "ses_3ae187259ffeMdIpeZbzA5R4ZU",
      "role": "assistant",
      "time": {...},
      "modelID": "claude-sonnet-4-5-20250929",
      "providerID": "anthropic"
    },
    "parts": [
      {
        "type": "text",
        "text": "OK"
      }
    ]
  }
]

Use cases:

  • Poll for new messages after sending via POST /session/:id/message
  • Retrieve conversation history
  • Check if assistant has responded

Error Response (400 Bad Request):

{
  "data": {...},
  "error": [
    {
      "expected": "array",
      "code": "invalid_type",
      "path": ["parts"],
      "message": "Invalid input: expected array, received undefined"
    }
  ],
  "success": false
}

Health & Status Endpoints

GET /global/health

Server health check

curl http://localhost:5400/global/health

Response:

{
  "healthy": true,
  "version": "1.1.48"
}

GET /project/current

Current project information

curl http://localhost:5400/project/current

Response:

{
  "id": "global",
  "worktree": "/",
  "sandboxes": [],
  "time": {
    "created": 1770323336398,
    "updated": 1770492969629
  }
}

GET /session/status

Current session status (context-specific)

curl http://localhost:5400/session/status

Response:

{}

(Empty when no active session in request context)


Event Streaming (Server-Sent Events)

GET /global/event

Real-time event stream

curl http://localhost:5400/global/event

Protocol: Server-Sent Events (SSE)

Event Format:

data: {"payload":{"type":"server.connected","properties":{}}}

data: {"directory":"/home/coder/src","payload":{"type":"message.part.updated","properties":{...}}}

data: {"directory":"/home/coder/src","payload":{"type":"file.edited","properties":{...}}}

Event Types Observed:

  • server.connected - Initial connection established
  • message.part.updated - Streaming message updates
  • file.edited - File modification events
  • command.executed - Command execution events
  • session.updated - Session state changes
  • session.diff - Session diffs

Additional Endpoints (From JavaScript Analysis)

Discovered from bundled JavaScript (/assets/index-CtVOE8w2.js):

Configuration:

  • /config/providers - Provider configuration
  • /global/config - Global configuration

File Operations:

  • /file/content - File content access
  • /file/status - File status
  • /find/file - File search
  • /find/symbol - Symbol search

Session & Lifecycle:

  • /session/status - Session status
  • /global/dispose - Global cleanup
  • /instance/dispose - Instance cleanup

Experimental:

  • /experimental/resource - Resource access
  • /experimental/tool - Tool access
  • /experimental/worktree - Worktree management

Auth:

  • /provider/auth - Provider authentication

TUI:

  • /tui/publish - Terminal UI publishing

(These endpoints need further testing to document request/response formats)


Session Storage

Filesystem Location

Sessions are persisted to filesystem:

~/.local/share/opencode/storage/session/<project-id>/

Session File Format

Each session is stored as JSON:

File: ses_<session-id>.json

Example:

{
  "id": "ses_3a7955e53ffe71fy4GJWD2yQyy",
  "slug": "playful-star",
  "version": "1.1.48",
  "projectID": "58c042e67dd312114ee098f01f12b2db0226abca",
  "directory": "/home/coder/src",
  "parentID": "ses_3ae187259ffeMdIpeZbzA5R4ZU",
  "title": "review changes [commit|branch|pr], defaults to uncommitted (@build subagent)",
  "permission": [
    {
      "permission": "todowrite",
      "action": "deny",
      "pattern": "*"
    },
    {
      "permission": "todoread",
      "action": "deny",
      "pattern": "*"
    },
    {
      "permission": "task",
      "action": "deny",
      "pattern": "*"
    }
  ],
  "time": {
    "created": 1771009909164,
    "updated": 1771009996086
  },
  "summary": {
    "additions": 0,
    "deletions": 0,
    "files": 0
  }
}

Key Fields:

  • id - Unique session ID (format: ses_<hash>)
  • slug - Human-readable slug
  • projectID - Project hash ID
  • directory - Working directory
  • parentID - Parent session (for sub-agents)
  • permission - Permission configuration array
  • time - Creation/update timestamps
  • summary - File change summary

Client Attachment

Interactive TUI

opencode attach http://localhost:5400 [options]

Options:

  • --session <id> - Continue specific session
  • --dir <path> - Working directory
  • --password <pw> - Basic auth password

Note: This opens an interactive Terminal UI, not programmatic access.


Authentication

Default (Unsecured)

Server runs without authentication if OPENCODE_SERVER_PASSWORD is not set.

Warning shown on startup:

Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.

Secured Mode

Set environment variable before starting:

export OPENCODE_SERVER_PASSWORD="your-secure-password"
opencode web --port 5400

Then authenticate via:

  • opencode attach --password <pw> (TUI)
  • HTTP Basic Auth header (programmatic)

Commands Comparison

opencode web

  • Starts server with web UI
  • Serves HTML/JS application on root /
  • Provides REST API endpoints
  • SSE event streaming
  • Use for: Browser access + programmatic API

opencode serve

  • Headless server (no UI focus)
  • Same API endpoints as web
  • Use for: Programmatic access only

opencode acp

  • Unclear - Did not bind to HTTP port as expected
  • May be deprecated or different protocol
  • Needs further investigation
  • Recommendation: Use opencode web/serve instead

opencode attach

  • Client that connects to running server
  • Opens interactive TUI
  • Not for programmatic access

Architecture Implications

For OpenClaw Integration

Discovery Impact:

  1. HTTP REST API exists - Can make programmatic API calls
  2. SSE for events - Real-time streaming without WebSocket
  3. ⚠️ Session creation unclear - Need to test POST endpoints
  4. ⚠️ Message sending unclear - Need to test messaging API
  5. Session storage known - Can query filesystem if needed

Recommended Approach:

  1. Gateway → OpenCode REST API (HTTP POST/GET)
  2. Gateway ← OpenCode SSE stream (Server-Sent Events)
  3. Event filtering in Gateway (hardcoded rules, no AI)

Questions Answered ✅

Critical (Formerly Blocking) - ALL RESOLVED

  • How to create session programmatically?

    • POST /session with JSON body
    • ✅ Request format: {directory, title, agent}
  • How to send message to session?

    • POST /session/<id>/message
    • ✅ Request format: {parts: [{type: "text", text: "..."}]}
  • How to list sessions?

    • GET /session returns array of all sessions

Remaining Questions (Non-blocking)

Important

  • How to terminate session programmatically?
    • Try: DELETE /session/:id?
  • Can multiple clients attach to same session?
    • Test: Multiple SSE connections to /global/event
  • Session persistence across server restarts?
    • Test: Restart OpenCode, check if sessions survive
  • Rate limiting / throttling?
    • Check response headers
  • CORS configuration?
    • Test cross-origin requests

Nice-to-have

  • Bulk operations?
  • Session export/import?
  • WebHook support?
  • Pagination for session lists?

Phase 0 Status: COMPLETE ✅

Critical Path Completed

HTTP REST API fully documented and tested:

  • ✅ Project listing: GET /project
  • ✅ Session creation: POST /session
  • ✅ Session listing: GET /session (workspace-specific!)
  • ✅ Session details: GET /session/:id
  • ✅ Message sending: POST /session/:id/message (tested end-to-end)
  • ✅ Message retrieval: GET /session/:id/message
  • ✅ Event streaming: GET /global/event (SSE)
  • ✅ Health check: GET /global/health

Architecture decision made:

  • Use HTTP REST API (not ACP/JSON-RPC)
  • OpenClaw Gateway → OpenCode HTTP API
  • Server-Sent Events for real-time updates

Critical discoveries:

  • Workspace isolation: Server is workspace-specific (must start from correct directory)
  • Project management: /project endpoint lists all workspaces
  • End-to-end tested: Successfully sent message via API and received response
  • Message polling works: Can retrieve messages via GET /session/:id/message

Blocking questions answered:

  • ✅ How to create sessions → POST /session
  • ✅ How to send messages → POST /session/:id/message with {parts, model}
  • ✅ How to retrieve messages → GET /session/:id/message
  • ✅ How to list sessions → GET /session (workspace-scoped)
  • ✅ How projects/workspaces work → GET /project + workspace isolation
  • ✅ Session storage format → Documented in filesystem section

Ready for Phase 1 Implementation

Can now proceed with:

  1. ✅ Build TypeScript/Node.js client library for OpenCode HTTP API
  2. ✅ Implement Gateway integration
  3. ✅ Handle workspace isolation (one server per builder workspace)
  4. ✅ Test event filtering (hardcoded rules, no AI)
  5. ✅ Validate with real builder workspaces

Multi-Workspace Strategy for OpenClaw

Decision needed: How to handle multiple builder workspaces?

Option 1: Multiple servers (Recommended)

  • Run one opencode web per builder workspace
  • Each on a different port: 5400, 5401, 5402, etc.
  • Gateway maintains mapping: workspace → port
  • Pros: Clean isolation, no context switching
  • Cons: More processes, more ports

Option 2: Dynamic server restart

  • Stop server, cd to new workspace, restart
  • Only works for single active builder at a time
  • Pros: Single process
  • Cons: Destroys active sessions, slow switching

Recommendation: Option 1 (multiple servers)

Optional (Non-blocking)

  • Test session termination: DELETE /session/:id
  • Test multi-client SSE connections
  • Document remaining endpoints (/file/*, /find/*, etc.)
  • Session persistence testing
  • Test project-scoped endpoints (e.g., /project/:id/session if they exist)

References

  • OpenCode CLI: opencode --help
  • Running Instance: http://localhost:5400 (codev pod)
  • Session Storage: ~/.local/share/opencode/storage/session/
  • Phase 0 Research Plan: phase-0-research.md
  • Initial Findings: phase-0-findings.md

Version History

  • 2026-02-16: Phase 0 Complete!
    • Discovered workspace isolation (server is workspace-specific)
    • Documented /project endpoint for workspace management
    • End-to-end tested message sending via HTTP API
    • Documented message retrieval via GET /session/:id/message
    • All critical questions answered
    • Ready for Phase 1 implementation
  • 2026-02-15: Initial discovery (HTTP REST + SSE documented)