21 KiB
Architecture Patterns
Domain: tmux-Claude CLI integration + OpenClaw skill bridge Researched: 2026-03-10
Recommended Architecture
The gsd-openclaw skill acts as a bridge between OpenClaw's multi-channel messaging system and GSD's Claude CLI-based project management. The architecture follows a session-orchestrator-bridge pattern.
┌─────────────────────────────────────────────────────────────────────────────┐
│ USER INTERFACES │
│ WhatsApp / Telegram / Slack / Discord / Feishu / WeChat / WebChat │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ OpenClaw Gateway │
│ (WebSocket Control Plane) │
│ ws://127.0.0.1:18789 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Channel │ │ Session │ │ Skill │ │
│ │ Handlers │ │ Manager │ │ Registry │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ Skill Invocation (SKILL.md trigger)
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ gsd-openclaw Skill Layer │
│ ~/.openclaw/workspace/skills/gsd-openclaw/ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ SKILL.md │ │
│ │ - Trigger patterns: "/gsd:*", "gsd*", project management keywords │ │
│ │ - Routes to orchestrator.py with parsed intent │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ orchestrator.py │ │
│ │ - Command routing and validation │ │
│ │ - User/project authentication │ │
│ │ - State coordination │ │
│ │ - Response formatting for messaging channels │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ tmux_session_manager.py │ │
│ │ - Create/destroy tmux sessions per user-project │ │
│ │ - Send commands to Claude CLI │ │
│ │ - Capture output (sync/async) │ │
│ │ - Session health monitoring │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ state_manager.py │ │
│ │ - Persistent state storage (JSON) │ │
│ │ - Project workspace tracking │ │
│ │ - User session mapping │ │
│ │ - Conversation history (for context recovery) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ libtmux / tmux commands
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ tmux Session Layer │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌────────────────────┐ │
│ │ tmux:gsd-user1-projA│ │ tmux:gsd-user1-projB│ │ tmux:gsd-user2-projC│ │
│ │ (Claude CLI) │ │ (Claude CLI) │ │ (Claude CLI) │ │
│ │ workspace: ~/projA │ │ workspace: ~/projB │ │ workspace: ~/projC│ │
│ └─────────────────────┘ └─────────────────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ Slash commands (/gsd:*)
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ GSD System │
│ ~/.claude/get-shit-done/ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /gsd:new- │ │ /gsd:plan │ │ /gsd:ship │ │ /gsd:status │ │
│ │ project │ │ │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ GSD Core │ │
│ │ - Project scaffolding (.planning/ structure) │ │
│ │ - Roadmap generation │ │
│ │ - Task management │ │
│ │ - Git integration │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Component Boundaries
| Component | Responsibility | Communicates With |
|---|---|---|
| SKILL.md | Trigger pattern matching, skill description | OpenClaw Gateway |
| orchestrator.py | Command routing, user auth, response formatting | state_manager, tmux_session_manager |
| tmux_session_manager.py | tmux session lifecycle, I/O with Claude CLI | tmux (via libtmux/subprocess), orchestrator |
| state_manager.py | Persistent state, workspace tracking, user mapping | orchestrator (reads/writes JSON) |
| prompts/ | LLM prompt templates for different scenarios | orchestrator (loads as needed) |
Data Flow
1. User sends message: "Create a new project for user authentication"
│
▼
2. OpenClaw Gateway receives via channel (WhatsApp/Telegram/etc.)
│
▼
3. Gateway matches trigger pattern → invokes gsd-openclaw skill
│
▼
4. orchestrator.py:
a. Extracts intent: "new-project" command
b. Validates user authentication
c. Checks state_manager for existing sessions
│
▼
5. tmux_session_manager.py:
a. Gets or creates tmux session for user+project
b. Sends "/gsd:new-project user authentication" to Claude CLI
│
▼
6. Claude CLI processes in tmux (persistent context):
a. GSD system scaffolds project
b. Generates initial planning files
c. Outputs progress/response
│
▼
7. tmux_session_manager captures output (async polling)
│
▼
8. orchestrator formats response for channel:
- Compact for mobile (WhatsApp/Telegram)
- Rich for Slack/Discord
│
▼
9. Response sent back through OpenClaw Gateway to user
│
▼
10. state_manager updates:
- Session last activity
- Project state changes
- Conversation checkpoint
Patterns to Follow
Pattern 1: Session-per-Project Isolation
What: Each user-project combination gets its own tmux session with isolated Claude context. When: Always, for any project-specific work. Why: Prevents context pollution between projects, enables parallel work.
# tmux_session_manager.py
class TmuxSessionManager:
def get_or_create_session(self, user_id: str, project_id: str) -> TmuxSession:
session_name = f"gsd-{user_id}-{project_id}"
# Check if session exists
session = self.find_session(session_name)
if session:
return session
# Create new session with project workspace
workspace = self.state_manager.get_project_workspace(user_id, project_id)
return self.create_session(
name=session_name,
working_dir=workspace,
command="claude" # Start Claude CLI
)
Pattern 2: Async Response Handling
What: Non-blocking output capture with callback-based response delivery. When: Any command that may take time (GSD operations can be slow). Why: Messaging channels expect timely responses; Claude operations may take minutes.
# orchestrator.py
async def handle_command(self, user_id: str, command: str, context: dict):
session = self.tmux_manager.get_or_create_session(
user_id,
context.get("project_id")
)
# Send command
session.send_keys(command)
# Start async output capture
output_future = self.tmux_manager.capture_output_async(
session,
timeout=300, # 5 min max
idle_timeout=30 # 30s of no output = done
)
# Return immediately with "processing" status
# Callback will send response when complete
return Response(status="processing", estimated_time="1-2 minutes")
Pattern 3: State Checkpointing
What: Periodic state snapshots to enable recovery from interruption. When: After each significant GSD operation, every N minutes during long operations. Why: Network interruptions, server restarts, user timeouts should not lose work.
# state_manager.py
def checkpoint_session(self, session_id: str, claude_output: str):
state = self.load_state()
state["sessions"][session_id]["last_checkpoint"] = {
"timestamp": datetime.now().isoformat(),
"claude_context_summary": self._extract_context_summary(claude_output),
"project_phase": self._detect_current_phase(claude_output),
"pending_actions": self._extract_pending_actions(claude_output)
}
self.save_state(state)
Pattern 4: Channel-Adaptive Formatting
What: Same content rendered differently based on target channel. When: Every response going back to user. Why: WhatsApp has 4096 char limit, Discord supports markdown, mobile needs compactness.
# response_formatter.py
def format_response(self, content: dict, channel: str) -> str:
formatters = {
"whatsapp": self._format_compact,
"telegram": self._format_markdown_lite,
"slack": self._format_rich_slack,
"discord": self._format_rich_discord,
"feishu": self._format_feishu_card
}
formatter = formatters.get(channel, self._format_generic)
return formatter(content)
Anti-Patterns to Avoid
Anti-Pattern 1: Shared Global tmux Session
What: One tmux session for all users/projects. Why bad: Context mixing, race conditions, security issues (users see each other's data). Instead: Session-per-project isolation (Pattern 1).
Anti-Pattern 2: Synchronous Blocking on Claude Output
What: Wait for Claude to complete before returning any response. Why bad: Timeouts on messaging channels (often 30-60s), poor UX, gateway hangs. Instead: Async with status updates (Pattern 2).
Anti-Pattern 3: State in Memory Only
What: Keep all state in Python process memory. Why bad: Lost on restart, cannot scale horizontally, no recovery from crash. Instead: Persistent JSON state with checkpointing (Pattern 3).
Anti-Pattern 4: Direct GSD Code Modification
What: Modify GSD source to add OpenClaw-specific features. Why bad: Breaks GSD updates, creates maintenance burden, diverges from upstream. Instead: Pure bridge pattern - send slash commands, capture output, no GSD modifications.
Anti-Pattern 5: Claude CLI in Non-Interactive Mode
What: Use Claude CLI with --no-interactive or pipe-only input.
Why bad: Loses conversational context, breaks GSD's interactive prompts, no approval flows.
Instead: Full tmux session with pseudo-terminal.
Scalability Considerations
| Concern | At 1 user | At 10 users | At 100 users |
|---|---|---|---|
| tmux sessions | 1-5 per user | 10-50 total | 100-500 total (consider session pooling/LRU eviction) |
| Memory | ~100MB (Claude) | ~1GB (10x Claude) | ~10GB (need session limits, swap to disk) |
| State storage | ~1MB JSON | ~10MB | ~100MB (consider SQLite migration) |
| Response latency | Immediate | Slight queue | Need job queue, async workers |
| Channel limits | N/A | Monitor rate limits | Implement rate limiting, batching |
Directory Structure
~/.openclaw/workspace/skills/gsd-openclaw/
├── SKILL.md # Skill definition and triggers
├── orchestrator.py # Main orchestration logic
├── tmux_session_manager.py # tmux session lifecycle
├── state_manager.py # Persistent state management
├── response_formatter.py # Channel-adaptive formatting
├── prompts/
│ ├── context_recovery.md # Prompt for recovering session context
│ ├── command_mapping.md # Natural language to GSD commands
│ └── error_recovery.md # Prompt for handling errors
└── state/
├── sessions.json # Active sessions mapping
├── users.json # User project mappings
└── projects/ # Per-project state snapshots
├── user1-projA.json
└── user1-projB.json
Build Order Implications
Based on component dependencies, recommended build order:
-
Phase 1: Core Infrastructure
state_manager.py(no dependencies, needed by all)tmux_session_manager.py(depends on state_manager for workspace paths)
-
Phase 2: Integration Layer
response_formatter.py(standalone, needed by orchestrator)prompts/(static files, needed by orchestrator)
-
Phase 3: Orchestration
orchestrator.py(depends on all above)SKILL.md(defines triggers, depends on orchestrator interface)
-
Phase 4: Testing & Polish
- Integration tests
- Channel-specific formatting refinement
- Error handling edge cases
Key Integration Points
OpenClaw Gateway Integration
- Skill is auto-loaded from
~/.openclaw/workspace/skills/gsd-openclaw/ - SKILL.md frontmatter defines trigger patterns
- Gateway calls orchestrator with: user_id, message, channel, context
GSD Integration
- No code changes to GSD - pure bridge pattern
- All interaction via tmux + slash commands
- Workspace paths from GSD conventions (
~/projects/{name})
tmux Integration Options
- libtmux (Python) - Recommended, cleaner API
- subprocess + tmux commands - Fallback, more control
# Option 1: libtmux
import libtmux
server = libtmux.Server()
session = server.new_session("gsd-user1-projA", attach=False)
pane = session.attached_window.attached_pane
pane.send_keys("/gsd:status")
# Option 2: subprocess
import subprocess
subprocess.run(["tmux", "new-session", "-d", "-s", "gsd-user1-projA"])
subprocess.run(["tmux", "send-keys", "-t", "gsd-user1-projA", "/gsd:status", "Enter"])
Sources
- OpenClaw GitHub Repository: https://github.com/openclaw/openclaw (HIGH confidence - official source)
- OpenClaw Website: https://openclaw.ai/ (HIGH confidence - official documentation)
- dev-workflow skill reference implementation (HIGH confidence - existing pattern in same ecosystem)
- GSD project context from PROJECT.md (HIGH confidence - project requirements)
Confidence Assessment
| Area | Confidence | Reason |
|---|---|---|
| OpenClaw skill structure | HIGH | Direct reference from dev-workflow, official docs |
| tmux integration patterns | MEDIUM | Standard Python/tmux patterns, needs validation with Claude CLI specifics |
| Async response handling | HIGH | Standard pattern for messaging bots, well-established |
| GSD bridge approach | HIGH | Project requirement explicitly states "bridge not modify" |
| Scalability estimates | LOW | Theoretical, needs real-world testing |