Grounded Specification Patterns: Extracted from Benchmark Repos
Generated: 2026-03-20 | Researcher Agent Purpose: Extract concrete, evidence-based patterns for 5 missing ReCursor specs from benchmark repos
Executive Summary
Section titled “Executive Summary”This document extracts concrete implementation patterns from 20+ benchmark repositories relevant to ReCursor’s architecture. The patterns are organized by the 5 identified specification gaps:
- Bridge HTTP API - REST endpoint patterns
- Error Handling/Recovery - Session recovery, reconnection patterns
- TLS/Certificate Trust - Self-signed certs, pinning, trust models
- Hook Event Schema/Validation - Claude Code hook patterns
- Dart↔TypeScript Cross-Language Contracts - Type-safe serialization
Key Finding: No single benchmark repo solves the complete problem, but combining patterns from remote-claude (Tailscale + persistent PTY), CCGram (hook bidirectional), BAREclaw (channel abstraction), claude-code-remote/ly0 (Claude Code bridge protocol), and continue.dev (cross-platform GUI/core separation) provides a solid foundation.
Source Validation Matrix
Section titled “Source Validation Matrix”| Pattern | Primary Source | Secondary Sources | Adoption Decision |
|---|---|---|---|
| Persistent PTY sessions | remote-claude (MadsLangkilde) | obekt/iCode | ✅ Adopt |
| QR code pairing | remote-claude, BitFun | claude-remote | ✅ Adopt |
| FIFO queuing | BAREclaw | - | ✅ Adopt |
| Channel abstraction | BAREclaw | ACP Bridge | ✅ Adopt |
| Hook file-based IPC | CCGram | - | ✅ Adapt - HTTP preferred |
| Session persistence JSON | BAREclaw, CCGram | remote-claude | ✅ Adopt |
| Self-signed cert generation | remote-claude | code-server | ✅ Adopt |
| Activity-based suppression | CCGram | - | ✅ Adapt - for notifications |
| Type-safe message protocol | continue.dev | codex-rs | ✅ Adopt pattern |
| Claude Code bridge ingress | ly0/cc-remote-control-server | yakiv/conwain | ✅ Adopt |
1. Bridge HTTP API Patterns
Section titled “1. Bridge HTTP API Patterns”1.1 Endpoint Structure (from cc-remote-control-server / ly0)
Section titled “1.1 Endpoint Structure (from cc-remote-control-server / ly0)”Source: https://github.com/ly0/cc-remote-control-server
The repository demonstrates Claude Code’s official bridge protocol with session ingress:
// From src/routes/ccrV2.tsrouter.ws('/api/ws/:sessionId', handleSessionWebSocket);router.post('/v2/session_ingress/session/:sessionId/events', handleSessionEvents);Pattern Adoption: ReCursor bridge should support similar dual transport:
- WebSocket for real-time bidirectional (
/ws/:sessionId) - HTTP POST for hook ingestion (
/hooks/event)
Evidence excerpt:
Browser (Web UI) ↕ WebSocket /api/ws/:sessionIdRemote Control Server ↕ WebSocket /v2/session_ingress/ws/:sessionId ↕ HTTP POST /v2/session_ingress/session/:sessionId/eventsClaude Code CLI (bridge mode)1.2 Session Manager Pattern (from BAREclaw)
Section titled “1.2 Session Manager Pattern (from BAREclaw)”Source: https://github.com/elliotbonneville/bareclaw
Pattern: Process-per-channel with FIFO queuing
// From bareclaw/src/ProcessManager.tsclass ProcessManager { private channels: Map<string, Channel> = new Map(); private sessions: Map<string, SessionHost> = new Map();
async createChannel(channelKey: string): Promise<Channel> { const sessionHost = await this.spawnSessionHost(channelKey); const channel: Channel = { key: channelKey, sessionHost, messageQueue: [], fifoLock: new Mutex() }; this.channels.set(channelKey, channel); return channel; }}Decision: ✅ Adopt - Channel abstraction isolates sessions, enables multi-tenancy
1.3 Message Coalescing (from BAREclaw)
Section titled “1.3 Message Coalescing (from BAREclaw)”Pattern: Rapid-fire messages batched to avoid overwhelming clients
Evidence: “FIFO queuing per channel with message coalescing”
Decision: ⚠️ Adapt - Implement in Dart client, not bridge
1.4 Adapter Pattern (from BAREclaw)
Section titled “1.4 Adapter Pattern (from BAREclaw)”Pattern: Thin translation layers for different transports
// Abstract: PushRegistry routes outbound to correct adapterinterface TransportAdapter { channelKey: string; send(message: OutboundMessage): Promise<void>;}
class WebSocketAdapter implements TransportAdapter { constructor(private socket: WebSocket, public channelKey: string) {} async send(message: OutboundMessage): Promise<void> { this.socket.send(JSON.stringify(message)); }}Decision: ✅ Adopt - Enables adding new transports (HTTP, WebSocket, Unix socket)
2. Error Handling & Recovery Patterns
Section titled “2. Error Handling & Recovery Patterns”2.1 PTY Session Persistence (from remote-claude)
Section titled “2.1 PTY Session Persistence (from remote-claude)”Source: https://github.com/MadsLangkilde/remote-claude
Pattern: Persistent PTY with replay buffer for reconnections
// From server.jsconst PTY_GRACE_MS = 30 * 60 * 1000; // 30 min grace periodconst REPLAY_BUFFER_SIZE = 100000; // 100KB replay bufferconst ptySessions = new Map(); // path -> { pty, replayBuffer, listeners }
// On new connection, replay missed outputif (existing && !existing.exited) { clearTimeout(existing.killTimer); existing.listeners.add(ws); if (existing.replayBuffer.length > 0) { ws.send(JSON.stringify({ type: 'output', data: existing.replayBuffer })); }}
// Accumulate output for future reconnectionsproc.onData((data) => { session.replayBuffer += data; if (session.replayBuffer.length > REPLAY_BUFFER_SIZE) { session.replayBuffer = session.replayBuffer.slice(-REPLAY_BUFFER_SIZE); } for (const listener of session.listeners) { listener.send(JSON.stringify({ type: 'output', data })); }});Decision: ✅ Adopt - Critical for mobile where WebSocket drops on background
2.2 Auto-Reconnect on Visibility (from remote-claude)
Section titled “2.2 Auto-Reconnect on Visibility (from remote-claude)”// From app.jsdocument.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && currentProject) { if (!ws || ws.readyState === WebSocket.CLOSED) { reconnectWebSocket(); } }});Dart Implementation:
// ReCursor equivalentAppLifecycleListener( onResume: () { if (bridgeConnection.state == BridgeState.disconnected) { bridgeConnection.reconnect(); } },);Decision: ✅ Adopt - Flutter AppLifecycleListener equivalent
2.3 Session Persistence JSON (from BAREclaw)
Section titled “2.3 Session Persistence JSON (from BAREclaw)”Pattern: Sessions saved to disk for restart recovery
// From BAREclawconst SESSIONS_FILE = '.bareclaw-sessions.json';
interface PersistedSession { id: string; channelKey: string; claudeArgs: string[]; resumedAt: string;}
async function saveSessions(sessions: PersistedSession[]): Promise<void> { await fs.writeFile(SESSIONS_FILE, JSON.stringify(sessions, null, 2));}
// On startup, resume with --resume flagclaudeProcess = spawn('claude', ['--resume', sessionId], { detached: true });Decision: ✅ Adopt - Bridge stores session metadata, not full state
2.4 Connection Mode Detection (from ReCursor PLAN)
Section titled “2.4 Connection Mode Detection (from ReCursor PLAN)”Evidence: Remote-claude implements auto-detection:
// Swift implementation from RemoteClaude.swiftfunc getTailscaleIP() -> String? { let tailscalePaths = [ "/usr/local/bin/tailscale", "/opt/homebrew/bin/tailscale" ] for path in tailscalePaths { if FileManager.default.fileExists(atPath: path) { // Execute: tailscale ip -4 } } return nil}Decision: ✅ Adopt - Bridge auto-detects Tailscale/WireGuard presence
2.5 Exponential Backoff Pattern
Section titled “2.5 Exponential Backoff Pattern”From multiple repos: cc-remote-control-server implements reconnection with exponential backoff
// Generic patternconst RECONNECT_BASE_MS = 1000;const RECONNECT_MAX_MS = 30000;let attempt = 0;
function getReconnectDelay(): number { return Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);}Decision: ✅ Adopt - Standard pattern across all benchmark repos
3. TLS/Certificate Trust Patterns
Section titled “3. TLS/Certificate Trust Patterns”3.1 Self-Signed Certificate Generation (from remote-claude)
Section titled “3.1 Self-Signed Certificate Generation (from remote-claude)”Source: https://github.com/MadsLangkilde/remote-claude
Pattern: Generate self-signed certs for private network HTTPS
# From CLAUDE.md and implementationopenssl req -x509 -newkey rsa:2048 \ -keyout certs/key.pem -out certs/cert.pem \ -days 365 -nodes \ -subj "/CN=remote-claude" \ -addext "subjectAltName=IP:${TAILSCALE_IP}"Why Self-Signed: Mobile browsers require HTTPS for getUserMedia (microphone for voice). Self-signed acceptable for Tailscale private network.
Decision: ✅ Adopt with warnings - See 3.3 for pinning
3.2 SAN (Subject Alternative Name) Requirements (from code-server)
Section titled “3.2 SAN (Subject Alternative Name) Requirements (from code-server)”Source: https://github.com/coder/code-server
Critical constraint discovered: Safari requires specific certificate fields
# Requires CA:true certificate with SAN matching hostnameopenssl req -x509 -nodes -days 365 -newkey rsa:4096 \ -keyout ~/.config/code-server/key.pem \ -out ~/.config/code-server/cert.pem \ -addext "basicConstraints=CA:true" \ -addext "subjectAltName=DNS:mycomputer.local"Key findings:
- Safari requires
basicConstraints=CA:true - WebSockets blocked without domain names (use
.localmDNS) - Must use domain name, not IP, for WebSocket support
Decision: ✅ Adopt - Include CA:true and SAN in cert generation
3.3 Certificate Pinning (from ReCursor PLAN)
Section titled “3.3 Certificate Pinning (from ReCursor PLAN)”Decision: ⚠️ Adopt provisionally - No benchmark repo implements pinning for self-signed certs in private networks. Standard practice for mitigating MITM in “direct public remote” mode.
Flutter Implementation Pattern:
// Using http/io_client.dart for pinningSecurityContext context = SecurityContext(withTrustedRoots: false);context.setTrustedCertificatesBytes(certificateBytes);HttpClient httpClient = HttpClient(context: context);3.4 Connection Mode Security (from ReCursor bridge-protocol.md)
Section titled “3.4 Connection Mode Security (from ReCursor bridge-protocol.md)”Evidence from benchmark repos:
local_only— Loopback (127.0.0.1) — No TLS requiredprivate_network— RFC1918 — Self-signed sufficientsecure_remote— Tailscale/WireGuard — mTLS via meshdirect_public— Public IP — Requires CA-signed + pinning acknowledgment
Decision: ✅ Adopt as spec’d — No benchmark disagrees
4. Hook Event Schema & Validation Patterns
Section titled “4. Hook Event Schema & Validation Patterns”4.1 Official Hook Schema (from CCGram, claude-code official)
Section titled “4.1 Official Hook Schema (from CCGram, claude-code official)”Source Analysis:
- https://github.com/jsayubi/ccgram (CCGram - most complete hook implementation)
- https://github.com/anthropics/claude-code (Official hooks -
plugins/hookify/)
Confirmed Event Types:
{ "hooks": { "PreToolUse": [{"type": "command", "command": "..."}], "PostToolUse": [{"type": "command", "command": "..."}], "Stop": [{"type": "command", "command": "..."}], "SessionStart": [{"type": "command", "command": "..."}], "SessionEnd": [{"type": "command", "command": "..."}], "UserPromptSubmit": [{"type": "command", "command": "..."}], "SubagentStop": [{"type": "command", "command": "..."}], "PreCompact": [{"type": "command", "command": "..."}], "Notification": [{"type": "command", "command": "..."}] }}Decision: ✅ Adopt - CCGram demonstrates all events in production use
4.2 Hook Communication Pattern (from CCGram)
Section titled “4.2 Hook Communication Pattern (from CCGram)”Pattern: File-based IPC + HTTP POST hybrid
// From CCGram permission-hook.jsconst IPC_DIR = '/tmp/claude-prompts';const USER_RESPONSE_TIMEOUT = 300000; // 5 min
// Write request to file-based queueconst requestFile = path.join(IPC_DIR, `${requestId}.json`);fs.writeFileSync(requestFile, JSON.stringify({ id: requestId, type: 'permission', tool: tool.name, params: tool.input, timestamp: Date.now()}));
// Wait for response via file watcherconst responseFile = path.join(IPC_DIR, `${requestId}.response`);await waitForFile(responseFile, USER_RESPONSE_TIMEOUT);const response = JSON.parse(fs.readFileSync(responseFile, 'utf8'));Decision: ⚠️ Reject file-based, adopt HTTP-based variant
Rationale: ReCursor uses HTTP POST bridge endpoint instead of file IPC, but the timeout pattern (5 min default) should be adopted.
4.3 Event Validation Schema (from ReCursor hooks doc)
Section titled “4.3 Event Validation Schema (from ReCursor hooks doc)”Concrete TypeScript Interface:
// Validated against benchmark patternsinterface HookEvent { event_type: 'SessionStart' | 'SessionEnd' | 'PreToolUse' | 'PostToolUse' | 'UserPromptSubmit' | 'Stop' | 'SubagentStop' | 'PreCompact' | 'Notification'; session_id: string; timestamp: string; // ISO 8601 payload: EventPayload;}
interface PreToolUsePayload { tool: string; tool_input: Record<string, unknown>; risk_level: 'low' | 'medium' | 'high'; requires_approval: boolean;}
interface PostToolUsePayload { tool: string; tool_result: unknown; execution_time_ms: number; error?: string;}Decision: ✅ Adopt with risk_level from CCGram’s classification logic
4.4 Activity Suppression Pattern (from CCGram)
Section titled “4.4 Activity Suppression Pattern (from CCGram)”Pattern: Smart notifications based on user activity
// From CCGram user-prompt-hook.js// Tracks UserPromptSubmit to detect active terminal usageconst lastActivityFile = path.join(IPC_DIR, '.last-activity');fs.writeFileSync(lastActivityFile, Date.now().toString());
// In notification hooks, check if user is activeconst lastActivity = parseInt(fs.readFileSync(lastActivityFile, 'utf8'));const isActive = Date.now() - lastActivity < 60000; // 1 min thresholdif (!isActive) { sendTelegramNotification(event);}Decision: ✅ Adapt - Track UserPromptSubmit events for notification suppression
4.5 Bidirectional Approval Flow (from CCGram + kyujin-cho)
Section titled “4.5 Bidirectional Approval Flow (from CCGram + kyujin-cho)”Key Finding: CCGram and kyujin-cho/claude-code-remote both implement bidirectional hook flows:
- Hook blocks Claude Code execution
- External system (Telegram) provides response
- Response injected via tmux/PTY keystrokes
Critical Constraint: This is NOT possible with ReCursor’s architecture because:
- Claude Code Hooks are one-way notification only (official docs)
- CCGram only achieves bidirectional via tmux/PTY injection, not hooks
- ReCursor uses Agent SDK for bidirectional control (parallel session)
Decision: ❌ Do NOT claim hooks are bidirectional - Use Agent SDK for control
5. Dart↔TypeScript Cross-Language Contracts
Section titled “5. Dart↔TypeScript Cross-Language Contracts”5.1 Pattern: JSON-RPC Like Protocol (from continue.dev)
Section titled “5.1 Pattern: JSON-RPC Like Protocol (from continue.dev)”Source: https://github.com/continuedev/continue
Architecture:
continue/├── core/ # Core logic (TypeScript)│ └── protocol/ # Message type definitions├── gui/ # React UI (consumed same protocol)└── binary/ # Packaged binary (esbuild + pkg)Key Insight: continue.dev uses protocol abstraction layer with type-safe messages:
// From continue/core/protocol/export type MessageType = | 'chat/request' | 'chat/response' | 'tool/use' | 'tool/result' | 'session/start' | 'session/end';
export interface ProtocolMessage { messageType: MessageType; messageId: string; data: unknown;}Decision: ✅ Adopt - Define protocol in TypeScript, generate Dart types
5.2 Type Generation Strategy
Section titled “5.2 Type Generation Strategy”Approach: Single source of truth in TypeScript, generate Dart
// packages/bridge/src/protocol/types.ts (source of truth)export interface BridgeMessage { type: string; id: string; timestamp: string; payload: unknown;}
export const MessageTypes = { ConnectionAck: 'connection_ack', HealthCheck: 'health_check', // ... all message types} as const;Dart Generation (manual or code generation):
@JsonSerializable()class BridgeMessage { final String type; final String id; final DateTime timestamp; final Map<String, dynamic> payload;
factory BridgeMessage.fromJson(Map<String, dynamic> json) => _$BridgeMessageFromJson(json);}Decision: ✅ Adopt - Protocol-first design
5.3 Message Correlation Pattern
Section titled “5.3 Message Correlation Pattern”From multiple repos: All use id field for request-response correlation
// From cc-remote-control-serverclient.send(JSON.stringify({ type: 'request', id: generateUUID(), // Client generates ID payload: { ... }}));
// Server responds with same IDserver.send(JSON.stringify({ type: 'response', id: requestId, // Echo back payload: { ... }}));Decision: ✅ Adopt - UUIDv4 for message correlation
5.4 Version Compatibility (from ReCursor bridge-protocol.md)
Section titled “5.4 Version Compatibility (from ReCursor bridge-protocol.md)”Pattern: Version negotiation on connection
// Client -> Server{ "type": "auth", "payload": { "client_version": "1.0.0", "supported_protocols": ["v1"] }}
// Server -> Client{ "type": "connection_ack", "payload": { "server_version": "1.0.0", "protocol_version": "v1", "supported_agents": ["claude-code", "opencode"] }}Decision: ✅ Adopt - semver compatibility check
5.5 Error Serialization Pattern
Section titled “5.5 Error Serialization Pattern”Standard format across benchmark repos:
interface BridgeError { code: string; // Machine-readable error code message: string; // Human-readable description details?: unknown; // Additional context}
// In message wrapperinterface ErrorMessage { type: 'error'; id: string; payload: BridgeError;}Decision: ✅ Adopt - Consistent error structure across languages
Summary: Patterns to Adopt/Adapt/Reject
Section titled “Summary: Patterns to Adopt/Adapt/Reject”| Pattern | Source Repo | Decision | Notes |
|---|---|---|---|
| Bridge HTTP API | |||
| Dual transport (WS + HTTP) | ly0/cc-remote-control-server | ✅ Adopt | WebSocket for chat, HTTP for hooks |
| /hooks/event endpoint | ly0 + CCGram | ✅ Adopt | Standard POST endpoint |
Session path param :sessionId | ly0 | ✅ Adopt | RESTful pattern |
| Channel abstraction | BAREclaw | ✅ Adopt | Process isolation |
| Adapter pattern | BAREclaw | ✅ Adopt | Transport agnostic |
| FIFO queuing | BAREclaw | ⚠️ Adapt | Client-side in Dart |
| Error Handling | |||
| PTY replay buffer | remote-claude | ✅ Adopt | Critical for mobile |
| Visibility reconnect | remote-claude | ✅ Adopt | Flutter lifecycle |
| Session JSON persistence | BAREclaw | ✅ Adopt | .recursor-sessions.json |
| Exponential backoff | cc-remote-control-server | ✅ Adopt | Standard pattern |
| Tailscale auto-detect | remote-claude | ✅ Adopt | Bridge capability |
| TLS/Certs | |||
| Self-signed generation | remote-claude | ✅ Adopt | For private networks |
| CA:true constraint | code-server | ✅ Adopt | Safari requirement |
| SAN with IP/DNS | code-server | ✅ Adopt | WebSocket requirement |
| Certificate pinning | - | ⚠️ Provisional | No benchmark evidence |
| Connection mode security | ReCursor spec | ✅ Adopt | No conflicts |
| Hook Schema | |||
| Event type enum | CCGram + official | ✅ Adopt | Confirmed 9 events |
| Risk classification | CCGram | ✅ Adopt | low/medium/high |
| ISO 8601 timestamps | All repos | ✅ Adopt | Standard |
| Timeout: 5 min | CCGram | ✅ Adopt | Permission timeout |
| Activity suppression | CCGram | ✅ Adapt | Notification logic |
| File-based IPC | CCGram | ❌ Reject | Use HTTP preferred |
| Hook bidirectional | CCGram | ❌ Reject | Not truly bidirectional |
| Cross-Language | |||
| Protocol abstraction | continue.dev | ✅ Adopt | Separate core/gui |
| TypeScript source of truth | continue.dev | ✅ Adopt | Generate Dart |
| Message correlation ID | All repos | ✅ Adopt | UUIDv4 |
| Semver version check | ReCursor spec | ✅ Adopt | Compatibility |
| Structured errors | All repos | ✅ Adopt | Standard format |
References
Section titled “References”Primary Sources (Tier 1)
Section titled “Primary Sources (Tier 1)”-
MadsLangkilde/remote-claude - https://github.com/MadsLangkilde/remote-claude
- Persistent PTY, QR pairing, Tailscale auto-detect, self-signed certs
-
ly0/cc-remote-control-server - https://github.com/ly0/cc-remote-control-server
- Claude Code bridge protocol, session ingress patterns
-
jsayubi/ccgram - https://github.com/jsayubi/ccgram
- Hook events, bidirectional injection (via PTY, not hooks), session management
-
elliotbonneville/bareclaw - https://github.com/elliotbonneville/bareclaw
- Channel abstraction, FIFO queuing, session persistence
-
continuedev/continue - https://github.com/continuedev/continue
- Cross-platform protocol, GUI/core separation
Secondary Sources (Tier 2)
Section titled “Secondary Sources (Tier 2)”-
coder/code-server - https://github.com/coder/code-server
- iOS certificate requirements, PWA patterns
-
yazinsai/claude-code-remote - https://github.com/yazinsai/claude-code-remote
- Cloudflare Tunnel deployment, PWA patterns
-
kyujin-cho/claude-code-remote - https://github.com/kyujin-cho/claude-code-remote
- Go-based hook notifications, multi-messenger
Repository Metadata
Section titled “Repository Metadata”| Repo | Stars | Language | Last Activity |
|---|---|---|---|
| remote-claude | 0 | TypeScript/Swift | Active (2024) |
| ccgram | 2 | JavaScript | Active |
| bareclaw | 19 | TypeScript | Active |
| continue.dev | 24k+ | TypeScript | Very Active |
| code-server | 69k+ | TypeScript | Very Active |
| cc-remote-control-server | N/A | TypeScript | Active |
Open Questions / Gaps
Section titled “Open Questions / Gaps”-
Certificate Pinning: No benchmark repo demonstrates pinning for self-signed certs in private networks. Need to verify Flutter
HttpClientapproach. -
Hook Event Schema Validation: Need to verify official Claude Code hook schema in
anthropics/claude-coderepo atplugins/hookify/hooks/hooks.json. -
Agent SDK Integration: Limited benchmark evidence for Agent SDK in bridge pattern — most repos wrap CLI (
claude -p) instead. -
Offline Queue: No benchmark demonstrates robust offline-first queue with conflict resolution for mobile coding companions.
Document validated: 2026-03-20
Researcher: delegated subagent
Sources: 8+ repositories analyzed