OpenCode Session Pattern
Version: 1.0
Date: 2026-02-12
Purpose: Shared reference for chat-accessible OpenCode sessions
Overview
This document describes the canonical pattern for integrating OpenCode with chat platforms (Slack, WhatsApp, Discord, etc.) using builder workspaces and bidirectional communication.
What This Covers
- Multi-tier architecture (DM → Personal Assistant → Orchestrator → Builders)
- Orchestrator session (workspace-level operations and project management)
- Builder workspace creation (sibling worktree pattern)
- OpenCode session lifecycle
- Message routing (chat ↔ OpenCode)
- Session management (create, pause, resume, terminate)
- Integration with
.ai/projectlist.md(projects as sessions)
What This Doesn’t Cover
- Chat platform specifics (Slack webhooks, WhatsApp message format, etc.)
- Gateway implementation details (these are adapters)
- Deployment automation (namespace creation, ConfigMaps, etc.)
Architecture Tiers
Tier 1: DM Channel (Chat Interface)
Platform binding (WhatsApp DM, Slack DM, etc.) that routes user messages to the Personal Assistant.
Tier 2: Personal Assistant (OpenClaw Agent)
General-purpose conversational AI with access to OpenClaw tools (browser, message, etc.).
Responsibilities:
- General conversation, questions, web searches
- Update
.ai/projectlist.mdstatus (with human approval) - Query and display session status
- Delegate workspace operations to Orchestrator
- Non-workspace operations
Tier 3: Orchestrator (OpenCode @ workspace-root)
Special OpenCode session running at workspace root that manages workspace-level operations.
Location: Runs directly at <workspace-root>/ (no builder workspace needed)
Access: Full workspace access (.ai/, repos/, codev/, .builders/, Taskfile.yaml)
Deployment: Always-running daemon (existing OpenCode web pod in code-server namespace)
Responsibilities:
- Create builder sessions (via Taskfile
builder:init) - Terminate builder sessions (via Taskfile
builder:cleanup) - Sync builder workspace state back to repos
- Read
.ai/projectlist.mdto discover projects - Reconcile builders based on projectlist changes
Does NOT:
- Update
.ai/projectlist.md(Personal Assistant handles this) - Route messages to builders (Gateway handles this via chat bindings)
Tier 4: Builder Sessions (OpenCode @ .builders/)
Project-specific coding sessions in isolated worktree workspaces.
Created by: Orchestrator (delegated from Personal Assistant)
Bound to: Chat channels/groups/threads via Gateway
Naming convention: .builders/<project-id>-<name>/ (from .ai/projectlist.md)
Core Concepts
1. Builder Workspace
A builder workspace is an isolated environment where OpenCode runs with access to:
- workspace-root files (specs, docs, tasks)
- One or more project repos (the code being worked on)
Structure:
<workspace-root>/.builders/<builder-name>/
├── workspace-root/ (worktree → <workspace-root>)
├── repo-a/ (worktree → <workspace-root>/repos/repo-a)
└── repo-b/ (worktree → <workspace-root>/repos/repo-b)
Why sibling worktrees:
- ✅ No gitignore conflicts (siblings, not nested under repos/)
- ✅ Each worktree has its own .git and tracking
- ✅ OpenCode sees all repos + workspace-root files
- ✅ Clean isolation between parallel sessions
2. OpenCode Session
An OpenCode session is a running instance of the OpenCode agent (via opencode acp or opencode web).
Types:
Orchestrator Session
- Location: Runs at workspace root (
<workspace-root>/) - Access: Full workspace (all repos,
.ai/,.builders/, etc.) - Lifecycle: Always-running (started with deployment, not per-project)
- Purpose: Workspace-level operations and project management
Builder Session
- Location: Runs in
.builders/<project-id>-<name>/ - Access: Scoped to builder workspace (worktrees only)
- Lifecycle: Created per-project, terminated when done
- Purpose: Project-specific coding tasks
Builder Session Properties:
- session_id: Project ID from
.ai/projectlist.md(e.g.,"0014") - builder_name:
<project-id>-<name>(e.g.,"0014-ironman-training") - builder_path:
.builders/<builder-name>/ - repos: List of repos included as siblings (from projectlist.md)
- model: LLM model (e.g.,
anthropic/claude-sonnet-4) - status: Mirrors project status in
.ai/projectlist.md
3. Chat Binding
A chat binding links a builder session to a chat surface (thread, channel, group, DM).
Stored in:
.ai/projectlist.md- Project metadata and binding info~/.openclaw/config/- Gateway configuration (must be correct)
Properties:
- session_id: Project ID (e.g.,
"0014") - platform:
slack,whatsapp,discord, etc. - channel_id: Platform-specific identifier (thread_ts, group JID, channel ID) - optional in projectlist
- trigger: How messages are routed:
all_messages: Every message in channel → agentmention: Only when agent is @mentionedprefix: Only messages starting with command prefix
- created_by: User who created the binding
Multi-binding: YAML supports multiple bindings per project (future-proofed, not currently in scope)
4. Project-Session Mapping
Key principle: Projects in .ai/projectlist.md ARE the sessions.
Mapping:
# In .ai/projectlist.md
- id: "0014"
title: "Ironman Training"
repos: [ai-dev] # Which repos this project needs
files:
plan: .ai/projects/tri-training/2026-02-12IMBuild/plan.md
# Project metadata path inferred from files.spec or files.plan directoryResults in:
- Builder workspace:
.builders/0014-ironman-training/ - Session ID:
"0014" - Project metadata:
.ai/projects/tri-training/2026-02-12IMBuild/ - Repos: Defined in
projectlist.md, can be extended when creating builder
Lifecycle
Phase 0: Project Planning (Human)
Before any builders are created:
-
Human updates
.ai/projectlist.md:- id: "0015" title: "New Feature" status: planned # Human sets this repos: [ai-dev, domain-apis] files: spec: .ai/projects/ai-dev/new-feature/prd.md -
Human tells Personal Assistant:
"I want to start working on project 0015" -
Personal Assistant updates projectlist.md:
status: implementing # PA changes with human approval -
Personal Assistant delegates to Orchestrator:
"Create builder for project 0015"
Phase 1: Creation (Orchestrator)
Orchestrator reads .ai/projectlist.md:
- Project ID:
0015 - Repos needed:
[ai-dev, domain-apis] - Builder name:
0015-new-feature(derived from project title)
Orchestrator executes:
# Via Taskfile (orchestrator runs this)
task builder:init BUILDER_NAME=0015-new-feature REPOS=ai-dev,domain-apis
# Taskfile creates:
# 1. .builders/0015-new-feature/
# 2. Worktree for workspace-root
# 3. Worktree for ai-dev
# 4. Worktree for domain-apis
# Start OpenCode session in builder
cd .builders/0015-new-feature
opencode acp --cwd . --session 0015Chat binding:
- Stored in
.ai/projectlist.md+~/.openclaw/config/ - Gateway routes messages from chat channel → builder session
Result:
- ✅ Builder workspace at
.builders/0015-new-feature/ - ✅ OpenCode session running, session_id =
0015 - ✅ Gateway knows to route chat channel → this session
- ✅ Personal Assistant can send messages to builder via chat binding
Phase 2: Usage
Message Flow (Chat → Builder):
User sends message in WhatsApp group
↓
Gateway receives message
↓
Gateway looks up binding (channel → session)
↓
Gateway routes message to Builder OpenCode session (via ACP)
POST /sessions/0015/messages
Body: { "content": "<message>", "author": "<user>" }
↓
Builder OpenCode agent processes message
(reads files, makes changes, runs commands)
↓
Builder streams events via WebSocket
(file_opened, file_changed, command_run, etc.)
Event Flow (Builder → Chat):
Builder OpenCode emits event via WebSocket
↓
Gateway receives event
↓
Gateway filters events (hardcoded rules, no AI processing)
- Keep: agent.complete, agent.question, agent.error
- Drop: agent.file_opened, agent.thinking, etc.
↓
Gateway formats for chat platform
↓
Gateway sends to bound channel
(WhatsApp group, Slack thread, etc.)
Event Filtering (Token Efficiency):
To avoid expensive AI processing and noisy chat, Gateway filters events using hardcoded rules:
Always forward:
agent.complete- ”✅ Done! Added rate limiting.”agent.question- ”❓ Should I write tests?”agent.error- ”❌ Build failed: missing dependency”agent.waiting_approval- ”🔒 Need permission to run npm install”agent.started- ”🔨 Working on it…” (immediate feedback)
Never forward:
agent.file_opened,agent.file_read,agent.thinking(too noisy)
Conditional:
agent.command_run- Only if exit_code !== 0 (failed commands)agent.test_run- Only if tests failed
Session history storage: Full event stream stored for on-demand queries (implementation TBD: file-based in builder, ACP session history API, or other)
Example:
User (in chat): "Add rate limiting to /login endpoint"
OpenCode (via gateway → chat):
📂 Opened ai-dev/services/gateway/src/auth.ts
✏️ Added express-rate-limit middleware
🧪 Running tests...
✅ Tests passed!
Done! Rate limiter added (5 req/min). Ready to commit?
User: "Yes, commit"
OpenCode:
✅ Committed: feat: add rate limiting to login
🚀 Pushed to ai-dev/main
Phase 3: Management
Operations Split:
| Operation | Handler | Details |
|---|---|---|
| List sessions | Personal Assistant | Reads .ai/projectlist.md, shows projects with status: implementing |
| Show session status | Personal Assistant | Queries stored metadata, displays to user |
| Update project status | Personal Assistant | Updates .ai/projectlist.md with human approval |
| Create builder | Orchestrator | Reads projectlist, runs task builder:init |
| Terminate builder | Orchestrator | Runs task builder:cleanup, checks for uncommitted changes |
| Sync workspace | Orchestrator | Pushes changes from builder back to repos |
| Route messages | Gateway | Via chat bindings (not Personal Assistant) |
Example (WhatsApp DM):
User (DM): "Show me all OpenCode sessions"
Personal Assistant reads .ai/projectlist.md:
📊 Active Sessions:
1. Project 0014: Ironman Training
Status: implementing
Repos: ai-dev
Builder: .builders/0014-ironman-training
Platform: WhatsApp group "Coach"
2. Project 0015: New Feature
Status: implementing
Repos: ai-dev, domain-apis
Builder: .builders/0015-new-feature
Platform: WhatsApp group "Dev Team"
User: "I want to wrap up project 0014"
Personal Assistant: "Ready to update status to 'implemented'?"
User: "Yes"
Personal Assistant updates .ai/projectlist.md:
status: implemented
Personal Assistant: "Updated! Ready to terminate the builder?"
User: "Yes"
Personal Assistant → Orchestrator: "Terminate builder 0014-ironman-training"
Orchestrator: Checks for uncommitted changes, runs task builder:cleanup
Personal Assistant: "✅ Builder cleaned up. Project 0014 is now 'implemented'."
Phase 4: Cleanup
Trigger:
- Manual only (for now, designed to support automation later)
- User explicitly requests termination via Personal Assistant
- Project status changed to
implemented,committed,integrated, orabandoned
Process:
-
Personal Assistant asks Orchestrator to terminate builder:
Personal Assistant → Orchestrator: "Terminate builder 0015-new-feature" -
Orchestrator validation:
- Check for uncommitted changes in worktrees
- If found: Report back to Personal Assistant → User must decide
- If clean: Proceed with cleanup
-
Orchestrator executes cleanup:
task builder:cleanup BUILDER_NAME=0015-new-feature -
Taskfile cleanup steps:
builder:cleanup: desc: Cleanup builder workspace and worktrees vars: BUILDER_NAME: '{{.BUILDER_NAME}}' cmds: - | cd .builders/{{.BUILDER_NAME}} # Check for uncommitted changes first if ! git diff --quiet || ! git diff --cached --quiet; then echo "ERROR: Uncommitted changes found" exit 1 fi # Remove each worktree for dir in */; do (cd "$dir" && git worktree remove .) done cd ../.. rm -rf .builders/{{.BUILDER_NAME}} git worktree prune -
Orchestrator reports back:
Orchestrator → Personal Assistant: "Builder 0015-new-feature cleaned up successfully" -
Personal Assistant notifies user:
"✅ Builder cleaned up. Project 0015 workspace removed."
What’s preserved:
- Session history/logs (implementation TBD - may move to
.ai/projects/.../session-history.json) - Git commits (assumed pushed to remote already)
- Project metadata in
.ai/projects/(untouched)
What’s deleted:
- Builder directory
.builders/0015-new-feature/ - All worktrees
- Local uncommitted changes (cleanup blocked if present)
Shared Infrastructure
Workspace Root Volume
All components share a common workspace root volume (mounted at various locations depending on environment):
<workspace-root>/ # Shared volume
├── repos/ # Git repositories
│ ├── ai-dev/
│ ├── domain-apis/
│ └── k8s-lab/
│
├── .builders/ # Builder workspaces (ephemeral)
│ ├── api-team-abc123/
│ ├── frontend-team-def456/
│ └── ...
│
├── .ai/ # Specs, docs, notes
├── codev/ # Codev artifacts
└── Taskfile.yaml # Workspace tasks
Why shared:
- No file syncing needed
- All components see the same files
- Builder workspaces are just subdirectories
- Worktrees reference repos in-place
Message Routing Strategies
Strategy 1: All Messages (Dedicated Channels)
Use case: Channel exists solely for one OpenCode session
Config:
{
"trigger": "all_messages",
"session_id": "abc123"
}Behavior:
- Every message in channel → OpenCode
- Agent has full conversation context
- Natural team collaboration
Example:
Alice: We need rate limiting
Bob: 5 requests per minute?
Alice: Yes
OpenCode: Got it, adding 5 req/min rate limit to /login...
Pros:
- ✅ Natural conversation flow
- ✅ Agent has full context
- ✅ No @mention friction
Cons:
- ❌ Burns tokens on every message
- ❌ Not suitable for shared channels
Strategy 2: Mention-Based (Shared Channels)
Use case: Multi-purpose channel with multiple conversations
Config:
{
"trigger": "mention",
"mention_patterns": ["@opencode", "hey opencode"],
"session_id": "abc123"
}Behavior:
- Only messages mentioning agent → OpenCode
- Agent doesn’t see other conversations
- Explicit invocation required
Example:
Alice: We need rate limiting
Bob: 5 requests per minute?
Alice: @opencode add rate limiting, 5 req/min
OpenCode: Adding 5 req/min rate limit to /login...
Pros:
- ✅ Token-efficient
- ✅ Works in busy channels
- ✅ Clear intent
Cons:
- ❌ Agent misses context from prior messages
- ❌ Extra friction (@mention required)
Strategy 3: Prefix-Based
Use case: Command-style interaction
Config:
{
"trigger": "prefix",
"prefixes": ["/code", "!opencode"],
"session_id": "abc123"
}Behavior:
- Only messages with prefix → OpenCode
- Clear command syntax
- Agent doesn’t see other messages
Example:
/code add rate limiting to /login
!opencode run tests
Pros:
- ✅ Very explicit
- ✅ Token-efficient
- ✅ Familiar command pattern
Cons:
- ❌ Less conversational
- ❌ No context from prior messages
Platform Adapters
Each chat platform needs an adapter that:
- Receives messages from platform (webhooks, polling, etc.)
- Looks up binding (channel → session)
- Routes to OpenCode (via ACP)
- Receives events from OpenCode (WebSocket)
- Formats and sends to platform (API calls)
Example Adapters:
Slack Adapter
- Receives: Slack Event API webhooks
- Binding key:
thread_ts(thread identifier) - Sends via: Slack Web API (
chat.postMessage)
WhatsApp Adapter
- Receives: WhatsApp Business API webhooks
- Binding key:
group_jid(group identifier) - Sends via: WhatsApp Business API
Discord Adapter
- Receives: Discord Gateway WebSocket
- Binding key:
channel_id(channel identifier) - Sends via: Discord REST API
Common Interface:
interface ChatAdapter {
// Receive message from platform
onMessage(message: PlatformMessage): Promise<void>
// Send message to platform
sendMessage(channel: string, content: string): Promise<void>
// Format OpenCode event for platform
formatEvent(event: OpenCodeEvent): PlatformMessage
}Configuration
Project Configuration (.ai/projectlist.md)
Projects are the source of truth for sessions:
projects:
- id: "0014"
title: "Ironman Training"
status: implementing # Personal Assistant updates this
repos: [ai-dev] # Which repos this project needs (can be extended)
files:
plan: .ai/projects/tri-training/2026-02-12IMBuild/plan.md
chat_binding: # Optional: chat binding metadata
platform: whatsapp
group_name: "Coach"
trigger: all_messages
tags: [ai-dev, openclaw, coaching, training, whatsapp]
notes: "Builder: .builders/0014-ironman-training"Gateway Configuration (~/.openclaw/config/)
Gateway maintains chat bindings (must be correct):
opencode:
workspace_root: ${WORKSPACE_ROOT} # Set via env var or mount point
builders_dir: ${WORKSPACE_ROOT}/.builders
# Orchestrator connection
orchestrator:
acp_url: http://opencode-web.code-server:8080 # Existing OpenCode web pod
session_id: orchestrator # Special orchestrator session
# Default session settings
defaults:
model: anthropic/claude-sonnet-4
trigger: all_messages
# Active bindings (synced from projectlist.md)
bindings:
- session_id: "0014" # Project ID
builder_name: 0014-ironman-training
builder_path: .builders/0014-ironman-training
repos: [ai-dev]
platform: whatsapp
channel_id: "120363xxx@g.us"
channel_name: "Coach"
trigger: all_messages
created_by: "+447403331966"
created_at: "2026-02-12T08:00:00Z"
- session_id: "0015"
builder_name: 0015-new-feature
builder_path: .builders/0015-new-feature
repos: [ai-dev, domain-apis]
platform: whatsapp
channel_id: "120363yyy@g.us"
channel_name: "Dev Team"
trigger: all_messages
created_by: "+447403331966"
created_at: "2026-02-13T09:00:00Z"Event Filtering Configuration
Hardcoded in Gateway (no AI processing):
// Gateway event filter
const FORWARD_EVENTS = {
always: [
'agent.complete',
'agent.question',
'agent.error',
'agent.waiting_approval',
'agent.started'
],
never: [
'agent.file_opened',
'agent.file_read',
'agent.thinking'
],
conditional: {
'agent.command_run': (event) => event.exit_code !== 0,
'agent.test_run': (event) => event.failed > 0
}
};Special Cases
Multiple Builders Per Project
Projects can have multiple simultaneous builders (e.g., separate microservices):
# Project 0012 in .ai/projectlist.md
- id: "0012"
title: "OpenCode Slack Integration"
repos: [ai-dev]
notes: "Two builders: 0012-slack-gateway, 0012-bridge-plugin"Builders:
.builders/
├── 0012-slack-gateway/ # First builder (Python/FastAPI)
└── 0012-bridge-plugin/ # Second builder (TypeScript)
Chat bindings:
- If >1 simultaneous builder in a category, create new WhatsApp group for subsequent builder
- Each builder has its own group/thread
- Builders run independently (integration contract agreed beforehand)
Status: All builders share same project status in projectlist.md
Implementation Checklist
Phase 0: Research & Discovery
- OpenCode ACP protocol discovery (endpoints, WebSocket events, session management)
- Test orchestrator session at workspace root (does it work as expected?)
- Research OpenClaw event filtering/aggregation capabilities
- Determine how Personal Assistant invokes Orchestrator (API vs tool wrapper)
- Session history storage options (file-based, ACP API, or other)
Phase 1: Core Pattern
- Taskfile:
builder:init(create sibling worktrees) - Taskfile:
builder:cleanup(remove worktrees, check uncommitted changes) - Test manual builder creation via orchestrator
- Verify OpenCode sees all siblings correctly
- Document repos field in
.ai/projectlist.mdschema
Phase 2: Orchestrator Integration
- Deploy orchestrator session at workspace root (existing OpenCode web pod)
- Orchestrator can read
.ai/projectlist.md - Orchestrator can create builders (calls
task builder:init) - Orchestrator can terminate builders (calls
task builder:cleanup) - Personal Assistant → Orchestrator communication pattern
Phase 3: Personal Assistant
- Personal Assistant reads/updates
.ai/projectlist.md - Personal Assistant delegates builder operations to orchestrator
- Personal Assistant shows session status (query projectlist)
- Personal Assistant handles status transitions with human approval
Phase 4: Gateway & Bindings
- Chat binding storage (projectlist.md + ~/.openclaw/config)
- Message routing (chat → builder via ACP)
- Event filtering (hardcoded rules, no AI processing)
- Event streaming (builder → chat via WebSocket)
- Sync bindings from projectlist.md to Gateway config
Phase 5: Platform Adapters
- WhatsApp adapter (webhooks, message sending)
- Slack adapter (if needed)
- Event formatting per platform
- Platform-specific features (inline buttons, etc.)
Key Design Decisions
From design-questions.md review (2026-02-12):
- Orchestrator Location: Runs directly at workspace root (no builder workspace needed)
- Orchestrator Lifecycle: Always-running daemon (existing OpenCode web pod)
- Session-Project Mapping: Project ID = Session ID, inferred from projectlist.md
- Repos Discovery: Maintained in projectlist.md (defaults from project metadata, can extend)
- Status Updates: Personal Assistant updates projectlist.md (with human approval), then orchestrator reconciles
- Event Filtering: Hardcoded rules in Gateway (no AI processing to avoid token cost)
- Multi-Builder: Allowed per project, each gets own chat group
- Builder Cleanup: Manual only (designed for future automation)
- Chat Bindings: Stored in projectlist.md + ~/.openclaw/config
- Backward Compatibility: Existing manual builders supported, no migration needed
Open Questions / Research Needed
Phase 0 Prerequisites:
- OpenCode ACP protocol details (endpoints, WebSocket event types, session history API)
- OpenClaw event filtering/aggregation capabilities (can we do hardcoded filters in code?)
- Personal Assistant → Orchestrator invocation pattern (recommend API over CLI, but needs research)
- Session history storage strategy (file-based vs ACP API vs other)
To be determined during implementation:
- Exact event types emitted by OpenCode (for filtering rules)
- WebSocket reconnection handling
- Error recovery patterns
- Session state persistence across restarts
References
- Sibling Worktree Pattern: Original design from opencode-slack-integration
- Builder Workspace: Used in codev and opencode-slack
- ACP Protocol: OpenCode’s Agent Client Protocol
- Project List:
.ai/projectlist.md- Source of truth for projects/sessions - Design Decisions:
.ai/projects/ai-dev/openclaw-opencode-bridge/design-questions.md
Version History
- 1.0 (2026-02-12): Initial version extracted from openclaw-opencode-bridge and opencode-slack-integration specs
- 1.1 (2026-02-13): Added multi-tier architecture (Orchestrator pattern), integration with projectlist.md, event filtering, design decisions
Usage in Projects
This document is referenced by:
openclaw-opencode-bridge(WhatsApp integration) -.ai/projects/ai-dev/openclaw-opencode-bridge/opencode-slack-integration(Slack integration) -.ai/projects/ai-dev/opencode-slack-integration/- Future: Discord, Telegram, etc. integrations
Each project adds:
- Platform-specific adapter implementation
- UI/UX conventions for that platform
- Platform-specific features (e.g., Slack workflow forms, WhatsApp inline buttons)
Each project references this doc for:
- Multi-tier architecture (DM → Personal Assistant → Orchestrator → Builders)
- Builder workspace creation
- OpenCode session lifecycle
- Message routing patterns
- Event filtering rules
- Integration with
.ai/projectlist.md - Shared infrastructure layout