Skip to content

Grounded Specification Patterns: Extracted from Benchmark Repos

Edit page

Generated: 2026-03-20 | Researcher Agent Purpose: Extract concrete, evidence-based patterns for 5 missing ReCursor specs from benchmark repos


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:

  1. Bridge HTTP API - REST endpoint patterns
  2. Error Handling/Recovery - Session recovery, reconnection patterns
  3. TLS/Certificate Trust - Self-signed certs, pinning, trust models
  4. Hook Event Schema/Validation - Claude Code hook patterns
  5. 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.


PatternPrimary SourceSecondary SourcesAdoption Decision
Persistent PTY sessionsremote-claude (MadsLangkilde)obekt/iCodeAdopt
QR code pairingremote-claude, BitFunclaude-remoteAdopt
FIFO queuingBAREclaw-Adopt
Channel abstractionBAREclawACP BridgeAdopt
Hook file-based IPCCCGram-Adapt - HTTP preferred
Session persistence JSONBAREclaw, CCGramremote-claudeAdopt
Self-signed cert generationremote-claudecode-serverAdopt
Activity-based suppressionCCGram-Adapt - for notifications
Type-safe message protocolcontinue.devcodex-rsAdopt pattern
Claude Code bridge ingressly0/cc-remote-control-serveryakiv/conwainAdopt

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.ts
router.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/:sessionId
Remote Control Server
↕ WebSocket /v2/session_ingress/ws/:sessionId
↕ HTTP POST /v2/session_ingress/session/:sessionId/events
Claude 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.ts
class 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

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

Pattern: Thin translation layers for different transports

// Abstract: PushRegistry routes outbound to correct adapter
interface 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.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.js
const PTY_GRACE_MS = 30 * 60 * 1000; // 30 min grace period
const REPLAY_BUFFER_SIZE = 100000; // 100KB replay buffer
const ptySessions = new Map(); // path -> { pty, replayBuffer, listeners }
// On new connection, replay missed output
if (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 reconnections
proc.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.js
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && currentProject) {
if (!ws || ws.readyState === WebSocket.CLOSED) {
reconnectWebSocket();
}
}
});

Dart Implementation:

// ReCursor equivalent
AppLifecycleListener(
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 BAREclaw
const 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 flag
claudeProcess = 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.swift
func 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

From multiple repos: cc-remote-control-server implements reconnection with exponential backoff

// Generic pattern
const 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.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

Terminal window
# From CLAUDE.md and implementation
openssl 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

docs/iphone.md
# Requires CA:true certificate with SAN matching hostname
openssl 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 .local mDNS)
  • 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 pinning
SecurityContext 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 required
  • private_network — RFC1918 — Self-signed sufficient
  • secure_remote — Tailscale/WireGuard — mTLS via mesh
  • direct_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:

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.js
const IPC_DIR = '/tmp/claude-prompts';
const USER_RESPONSE_TIMEOUT = 300000; // 5 min
// Write request to file-based queue
const 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 watcher
const 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 patterns
interface 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 usage
const lastActivityFile = path.join(IPC_DIR, '.last-activity');
fs.writeFileSync(lastActivityFile, Date.now().toString());
// In notification hooks, check if user is active
const lastActivity = parseInt(fs.readFileSync(lastActivityFile, 'utf8'));
const isActive = Date.now() - lastActivity < 60000; // 1 min threshold
if (!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:

  1. Claude Code Hooks are one-way notification only (official docs)
  2. CCGram only achieves bidirectional via tmux/PTY injection, not hooks
  3. 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

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):

apps/mobile/lib/core/protocol/types.dart
@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

From multiple repos: All use id field for request-response correlation

// From cc-remote-control-server
client.send(JSON.stringify({
type: 'request',
id: generateUUID(), // Client generates ID
payload: { ... }
}));
// Server responds with same ID
server.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

Standard format across benchmark repos:

interface BridgeError {
code: string; // Machine-readable error code
message: string; // Human-readable description
details?: unknown; // Additional context
}
// In message wrapper
interface ErrorMessage {
type: 'error';
id: string;
payload: BridgeError;
}

Decision: ✅ Adopt - Consistent error structure across languages


PatternSource RepoDecisionNotes
Bridge HTTP API
Dual transport (WS + HTTP)ly0/cc-remote-control-server✅ AdoptWebSocket for chat, HTTP for hooks
/hooks/event endpointly0 + CCGram✅ AdoptStandard POST endpoint
Session path param :sessionIdly0✅ AdoptRESTful pattern
Channel abstractionBAREclaw✅ AdoptProcess isolation
Adapter patternBAREclaw✅ AdoptTransport agnostic
FIFO queuingBAREclaw⚠️ AdaptClient-side in Dart
Error Handling
PTY replay bufferremote-claude✅ AdoptCritical for mobile
Visibility reconnectremote-claude✅ AdoptFlutter lifecycle
Session JSON persistenceBAREclaw✅ Adopt.recursor-sessions.json
Exponential backoffcc-remote-control-server✅ AdoptStandard pattern
Tailscale auto-detectremote-claude✅ AdoptBridge capability
TLS/Certs
Self-signed generationremote-claude✅ AdoptFor private networks
CA:true constraintcode-server✅ AdoptSafari requirement
SAN with IP/DNScode-server✅ AdoptWebSocket requirement
Certificate pinning-⚠️ ProvisionalNo benchmark evidence
Connection mode securityReCursor spec✅ AdoptNo conflicts
Hook Schema
Event type enumCCGram + official✅ AdoptConfirmed 9 events
Risk classificationCCGram✅ Adoptlow/medium/high
ISO 8601 timestampsAll repos✅ AdoptStandard
Timeout: 5 minCCGram✅ AdoptPermission timeout
Activity suppressionCCGram✅ AdaptNotification logic
File-based IPCCCGram❌ RejectUse HTTP preferred
Hook bidirectionalCCGram❌ RejectNot truly bidirectional
Cross-Language
Protocol abstractioncontinue.dev✅ AdoptSeparate core/gui
TypeScript source of truthcontinue.dev✅ AdoptGenerate Dart
Message correlation IDAll repos✅ AdoptUUIDv4
Semver version checkReCursor spec✅ AdoptCompatibility
Structured errorsAll repos✅ AdoptStandard format

  1. MadsLangkilde/remote-claude - https://github.com/MadsLangkilde/remote-claude

    • Persistent PTY, QR pairing, Tailscale auto-detect, self-signed certs
  2. ly0/cc-remote-control-server - https://github.com/ly0/cc-remote-control-server

    • Claude Code bridge protocol, session ingress patterns
  3. jsayubi/ccgram - https://github.com/jsayubi/ccgram

    • Hook events, bidirectional injection (via PTY, not hooks), session management
  4. elliotbonneville/bareclaw - https://github.com/elliotbonneville/bareclaw

    • Channel abstraction, FIFO queuing, session persistence
  5. continuedev/continue - https://github.com/continuedev/continue

    • Cross-platform protocol, GUI/core separation
  1. coder/code-server - https://github.com/coder/code-server

    • iOS certificate requirements, PWA patterns
  2. yazinsai/claude-code-remote - https://github.com/yazinsai/claude-code-remote

    • Cloudflare Tunnel deployment, PWA patterns
  3. kyujin-cho/claude-code-remote - https://github.com/kyujin-cho/claude-code-remote

    • Go-based hook notifications, multi-messenger
RepoStarsLanguageLast Activity
remote-claude0TypeScript/SwiftActive (2024)
ccgram2JavaScriptActive
bareclaw19TypeScriptActive
continue.dev24k+TypeScriptVery Active
code-server69k+TypeScriptVery Active
cc-remote-control-serverN/ATypeScriptActive

  1. Certificate Pinning: No benchmark repo demonstrates pinning for self-signed certs in private networks. Need to verify Flutter HttpClient approach.

  2. Hook Event Schema Validation: Need to verify official Claude Code hook schema in anthropics/claude-code repo at plugins/hookify/hooks/hooks.json.

  3. Agent SDK Integration: Limited benchmark evidence for Agent SDK in bridge pattern — most repos wrap CLI (claude -p) instead.

  4. 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