OpenClaw-OpenCode Bridge - Next Steps

Phase 0: ✅ COMPLETE (2026-02-16)
Phase 1: Ready to start


Phase 0 Summary

What We Accomplished

HTTP REST API fully documented:

  • /project - List all workspaces
  • /session - Create/list sessions (workspace-specific!)
  • /session/:id/message - Send/retrieve messages
  • /global/event - SSE event stream
  • End-to-end tested with real orchestrator session

Critical Discovery - Workspace Isolation:

  • OpenCode web server serves only the workspace where it was started
  • To access different workspaces, must run multiple servers or restart in new directory
  • For OpenClaw: Need multiple OpenCode instances (one per builder workspace)

Architecture Decision:

  • Use HTTP REST API (not ACP/JSON-RPC)
  • Server-Sent Events for real-time updates
  • Multiple OpenCode servers for multi-workspace support

Documentation Created

  1. opencode-protocol-discovered.md - Complete HTTP API reference
  2. phase-0-findings.md - Research journey and key discoveries
  3. phase-0-research.md - Original research plan (archived)

Phase 1: Core Pattern Implementation

Goal: Build TypeScript/Node.js client for OpenCode HTTP API + Gateway integration

Tasks

1. OpenCode HTTP Client Library

Location: packages/openclaw-gateway/src/opencode/ (or new package)

Core Components:

// src/opencode/client.ts
class OpenCodeClient {
  constructor(baseURL: string) // e.g., "http://localhost:5400"
  
  // Project management
  async listProjects(): Promise<Project[]>
  
  // Session management
  async createSession(opts: CreateSessionOpts): Promise<Session>
  async listSessions(): Promise<Session[]>
  async getSession(id: string): Promise<Session>
  
  // Messaging
  async sendMessage(sessionId: string, message: Message): Promise<void>
  async getMessages(sessionId: string): Promise<Message[]>
  
  // Event streaming
  streamEvents(callback: (event: OpenCodeEvent) => void): EventSource
  
  // Health
  async healthCheck(): Promise<HealthStatus>
}

Types:

interface Project {
  id: string
  worktree: string
  vcs?: 'git'
  time: { created: number; updated: number }
}
 
interface Session {
  id: string
  slug: string
  version: string
  projectID: string
  directory: string
  title: string
  time: { created: number; updated: number }
}
 
interface Message {
  info: {
    id: string
    sessionID: string
    role: 'user' | 'assistant'
    time: { created: number; updated: number }
  }
  parts: MessagePart[]
}
 
interface MessagePart {
  type: 'text' | 'tool_use' | 'tool_result'
  text?: string
  // ... other fields
}
 
interface OpenCodeEvent {
  directory?: string
  payload: {
    type: string // 'message.part.updated', 'file.edited', etc.
    properties: Record<string, any>
  }
}

Testing:

  • Unit tests with mock HTTP responses
  • Integration tests against real OpenCode server
  • Test workspace isolation behavior

2. Multi-Workspace Server Management

Problem: Each builder needs its own OpenCode server instance.

Solution: OpenCode Server Manager

// src/opencode/server-manager.ts
class OpenCodeServerManager {
  private servers: Map<string, OpenCodeServerInstance>
  
  async startServer(workspace: string): Promise<OpenCodeClient>
  async stopServer(workspace: string): Promise<void>
  getClient(workspace: string): OpenCodeClient | undefined
  async healthCheckAll(): Promise<Map<string, boolean>>
}
 
interface OpenCodeServerInstance {
  workspace: string
  port: number
  process: ChildProcess
  client: OpenCodeClient
  startTime: number
}

Port Allocation Strategy:

  • Base port: 5400
  • Per-builder offset: 5400 + builder_index
  • Example:
    • Builder 1 (/home/coder/src/.builders/builder-0001): port 5400
    • Builder 2 (/home/coder/src/.builders/builder-0002): port 5401
    • Builder 3 (/home/coder/src/.builders/builder-0003): port 5402

Startup Process:

cd /home/coder/src/.builders/builder-0001
opencode web --hostname 0.0.0.0 --port 5400 &

Lifecycle Management:

  • Start OpenCode when builder is created
  • Stop OpenCode when builder is destroyed
  • Health check every 30 seconds
  • Auto-restart on failure

3. Gateway Integration

Update Gateway to use OpenCode client:

// packages/openclaw-gateway/src/handlers/discord-dm.ts
import { OpenCodeServerManager } from '../opencode/server-manager'
 
class DiscordDMHandler {
  private serverManager: OpenCodeServerManager
  
  async handleMessage(message: DiscordMessage) {
    // 1. Determine which builder/workspace to use
    const workspace = await this.resolveWorkspace(message)
    
    // 2. Get or start OpenCode server for that workspace
    const client = await this.serverManager.startServer(workspace)
    
    // 3. Get or create session
    const session = await this.getOrCreateSession(client, message.author)
    
    // 4. Send message to OpenCode
    await client.sendMessage(session.id, {
      parts: [{ type: 'text', text: message.content }]
    })
    
    // 5. Stream events back to Discord (via event filter)
    // ... (see next section)
  }
}

4. Event Filtering (Hardcoded Rules)

Goal: Filter OpenCode SSE events → send relevant updates to Discord DM

No AI processing - just hardcoded rules based on event types.

// src/opencode/event-filter.ts
class OpenCodeEventFilter {
  shouldForward(event: OpenCodeEvent): boolean {
    const { type } = event.payload
    
    // Forward these events to user
    const forwardTypes = [
      'message.part.updated',  // Claude's response chunks
      'file.edited',           // File changes
      'command.executed',      // Commands run
      'session.diff',          // Session diffs
    ]
    
    return forwardTypes.includes(type)
  }
  
  formatForDiscord(event: OpenCodeEvent): string {
    // Convert OpenCode event to Discord message format
    // ...
  }
}

Event Flow:

OpenCode SSE Event
  ↓
Event Filter (hardcoded rules)
  ↓ (if shouldForward)
Format for Discord
  ↓
Send to Discord DM

5. Testing Plan

Unit Tests:

  • OpenCodeClient methods
  • ServerManager lifecycle
  • Event filter rules

Integration Tests:

  1. Start OpenCode server programmatically
  2. Create session via API
  3. Send message via API
  4. Receive response
  5. Stream events via SSE
  6. Stop server cleanly

End-to-End Test:

  1. User sends Discord DM → “Create a new file hello.txt”
  2. Gateway receives message
  3. Gateway starts/uses OpenCode for appropriate workspace
  4. Gateway sends message to OpenCode session
  5. OpenCode processes with Claude
  6. Gateway receives SSE events (file created, command executed)
  7. Gateway filters events
  8. Gateway sends updates back to Discord DM
  9. User sees “Created hello.txt” in Discord

6. Integration with Orchestrator Pattern

Current Architecture:

Discord DM → Personal Assistant → Orchestrator → Builder Sessions

With OpenCode Integration:

Discord DM 
  ↓
Personal Assistant (OpenCode session in codev)
  ↓
Orchestrator (OpenCode session in codev)
  ↓
Builder Sessions (OpenCode sessions in .builders/builder-NNNN)

Key Insight: All tiers use OpenCode, but in different workspaces!

Workspace Mapping:

  • Personal Assistant: /home/coder (global project)
  • Orchestrator: /home/coder/src (main workspace)
  • Builder N: /home/coder/src/.builders/builder-000N (builder workspace)

Implementation:

interface OpenClawTier {
  name: 'personal-assistant' | 'orchestrator' | 'builder'
  workspace: string
  opencode: OpenCodeClient
  session: Session
}
 
class OpenClawArchitecture {
  private tiers: Map<string, OpenClawTier>
  private serverManager: OpenCodeServerManager
  
  async initializeTier(tier: 'personal-assistant' | 'orchestrator' | 'builder', workspace: string) {
    const client = await this.serverManager.startServer(workspace)
    const session = await client.createSession({
      directory: workspace,
      title: `OpenClaw ${tier}`,
      agent: 'default'
    })
    
    this.tiers.set(tier, { name: tier, workspace, opencode: client, session })
  }
  
  async sendToTier(tier: string, message: string) {
    const tierData = this.tiers.get(tier)
    await tierData.opencode.sendMessage(tierData.session.id, {
      parts: [{ type: 'text', text: message }]
    })
  }
}

Phase 1 Deliverables

  1. OpenCodeClient class (TypeScript)
  2. OpenCodeServerManager class (TypeScript)
  3. OpenCodeEventFilter class (TypeScript)
  4. ✅ Unit tests for all components
  5. ✅ Integration tests with real OpenCode server
  6. ✅ Gateway integration (Discord DM → OpenCode)
  7. ✅ End-to-end test with builder workspace
  8. ✅ Documentation updates

Phase 1 Timeline

Estimated Duration: 2-3 days

Breakdown:

  • Day 1: OpenCodeClient + ServerManager implementation
  • Day 2: Event filtering + Gateway integration
  • Day 3: Testing + documentation

Success Criteria

Phase 1 is complete when:

  1. ✅ Can start OpenCode server programmatically for any workspace
  2. ✅ Can create sessions via HTTP API
  3. ✅ Can send messages and receive responses
  4. ✅ SSE event stream works and filters correctly
  5. ✅ Discord DM → OpenCode → Discord DM works end-to-end
  6. ✅ Multi-workspace support validated (personal assistant + orchestrator + builder)
  7. ✅ All tests passing

Next Phase Preview

Phase 2: Advanced Features

  • Session persistence across restarts
  • Builder lifecycle integration with Taskfile
  • Project list integration (.ai/projectlist.md)
  • Advanced event filtering (tool use detection)
  • Error handling & retry logic
  • Metrics & monitoring

Phase 3: Production Readiness

  • Security (OPENCODE_SERVER_PASSWORD)
  • Resource limits (max concurrent sessions)
  • Graceful shutdown
  • Logging & debugging
  • Performance optimization

References

  • Phase 0 Docs: opencode-protocol-discovered.md, phase-0-findings.md
  • Architecture: .ai/projects/ai-dev/opencode-session-pattern.md
  • OpenCode HTTP API: http://localhost:5400 (when running)
  • Orchestrator Session: ses_3ae187259ffeMdIpeZbzA5R4ZU