Skip to content

Agent SDK Integration

Edit page

Using the Claude Agent SDK for parallel agent sessions in ReCursor. This is a supported integration path — ReCursor does not claim to mirror or control existing Claude Code sessions via unsupported Remote Control protocols.


The Claude Agent SDK (@anthropic-ai/claude-agent-sdk) is the officially supported way to build agentic applications that interact with Claude. ReCursor uses the Agent SDK to create parallel agent sessions that can receive user input from the mobile app and execute tools independently.

Key Concept: Agent SDK sessions are parallel, not mirrored. They exist alongside Claude Code sessions rather than controlling them.


flowchart TB
subgraph Mobile["📱 ReCursor App"]
UI["OpenCode-style UI"]
WSClient["WebSocket Client"]
end
subgraph Desktop["💻 Development Machine"]
Bridge["Bridge Server"]
subgraph Sessions["Agent Sessions"]
CC["Claude Code CLI\n(User's main session)"]
SDK["Agent SDK Session\n(Parallel, ReCursor-managed)"]
end
end
subgraph Anthropic["☁️ Anthropic API"]
API["Claude API"]
end
UI <--> WSClient
WSClient <-->|WebSocket| Bridge
Bridge <-->|Agent SDK| SDK
SDK <-->|API Calls| API
CC <-->|Internal| API

ScenarioSolution
User wants to chat with agent from mobile✅ Agent SDK session
User wants to approve tool calls from mobile⚠️ Hooks + Agent SDK (see below)
User wants to see what Claude Code is doing✅ Hooks
User wants to control existing Claude Code session❌ Not supported (Remote Control is first-party only)

For full functionality, ReCursor uses both:

  1. Hooks — Observe Claude Code events (tool use, session state)
  2. Agent SDK — Receive user input and execute independent actions

When a user “approves” a tool call in the mobile app:

  • The approval is sent to the Agent SDK session
  • The Agent SDK session executes a similar tool
  • Claude Code continues independently (Hooks show what it did)

Terminal window
npm install @anthropic-ai/claude-agent-sdk
import { Agent } from '@anthropic-ai/claude-agent-sdk';
import { ReadTool, EditTool, BashTool } from '@anthropic-ai/claude-agent-sdk/tools';
const agent = new Agent({
model: 'claude-3-5-sonnet-20241022',
tools: [new ReadTool(), new EditTool(), new BashTool()],
workingDirectory: '/home/user/project',
});
// Start a conversation
const response = await agent.run({
messages: [{ role: 'user', content: 'Tighten the bridge startup validation in bridge_setup_screen.dart' }],
});
// Bridge server manages Agent SDK sessions
import { Agent } from '@anthropic-ai/claude-agent-sdk';
import { EventEmitter } from 'events';
class AgentSessionManager {
private sessions: Map<string, Agent> = new Map();
private eventEmitter: EventEmitter = new EventEmitter();
async createSession(sessionId: string, config: SessionConfig): Promise<void> {
const agent = new Agent({
model: config.model || 'claude-3-5-sonnet-20241022',
tools: this.loadTools(config.toolAllowlist),
workingDirectory: config.workingDirectory,
});
this.sessions.set(sessionId, agent);
// Forward events to mobile
agent.on('tool_use', (event) => {
this.eventEmitter.emit('tool-use', { sessionId, event });
});
agent.on('message', (event) => {
this.eventEmitter.emit('message', { sessionId, event });
});
}
async sendMessage(sessionId: string, message: string): Promise<void> {
const agent = this.sessions.get(sessionId);
if (!agent) throw new Error('Session not found');
// Stream response back to mobile
const stream = agent.run({
messages: [{ role: 'user', content: message }],
});
for await (const chunk of stream) {
this.eventEmitter.emit('stream_chunk', { sessionId, chunk });
}
}
async executeTool(sessionId: string, toolCall: ToolCall): Promise<ToolResult> {
const agent = this.sessions.get(sessionId);
if (!agent) throw new Error('Session not found');
return agent.executeTool(toolCall);
}
}

sequenceDiagram
participant Mobile as ReCursor App
participant Bridge as Bridge Server
participant SDK as Agent SDK
participant API as Claude API
Mobile->>Bridge: message { text, session_id }
Bridge->>SDK: agent.run({ messages })
SDK->>API: Claude API request
API-->>SDK: Stream response
loop Streaming
SDK->>Bridge: Stream chunk
Bridge->>Mobile: stream_chunk
Mobile->>Mobile: Update UI
end
SDK-->>Bridge: Complete
Bridge-->>Mobile: stream_end
sequenceDiagram
participant Mobile as ReCursor App
participant Bridge as Bridge Server
participant SDK as Agent SDK
participant Tools as Tool Implementations
SDK->>SDK: Tool use requested
SDK->>Bridge: tool_use event
Bridge->>Mobile: approval_required
alt Approved
Mobile->>Bridge: approval_response { approved }
Bridge->>SDK: Continue execution
SDK->>Tools: Execute tool
Tools-->>SDK: Result
SDK->>Bridge: tool_result
Bridge->>Mobile: Render Tool Card
else Rejected
Mobile->>Bridge: approval_response { rejected }
Bridge->>SDK: Halt execution
SDK-->>Bridge: Error
end

import {
ReadTool,
EditTool,
BashTool,
GlobTool,
GrepTool,
LSTool,
} from '@anthropic-ai/claude-agent-sdk/tools';
const tools = [
new ReadTool(), // Read file contents
new EditTool(), // Edit files (find/replace)
new BashTool({ // Execute shell commands
allowedCommands: ['git', 'flutter', 'npm'], // Optional allowlist
}),
new GlobTool(), // File globbing
new GrepTool(), // Text search
new LSTool(), // List directory contents
];
import { Tool, ToolInput, ToolResult } from '@anthropic-ai/claude-agent-sdk';
class DeployTool implements Tool {
name = 'deploy_app';
description = 'Deploy the application to staging/production';
async execute(input: ToolInput): Promise<ToolResult> {
const { environment, version } = input.parameters;
// Custom deployment logic
const result = await this.deploy(environment, version);
return {
success: result.success,
content: result.message,
};
}
}

interface SessionLifecycle {
// Create new session
async createSession(config: SessionConfig): Promise<string>;
// Resume existing session (if supported)
async resumeSession(sessionId: string): Promise<void>;
// Pause (keep context, stop processing)
async pauseSession(sessionId: string): Promise<void>;
// Close (cleanup resources)
async closeSession(sessionId: string): Promise<void>;
}
interface SessionContext {
sessionId: string;
workingDirectory: string;
gitBranch?: string;
toolAllowlist: string[];
model: string;
temperature: number;
// Conversation history (for resuming)
messageHistory: Message[];
}

Terminal window
# Bridge server .env
ANTHROPIC_API_KEY=sk-ant-...
AGENT_MODEL=claude-3-5-sonnet-20241022
AGENT_MAX_ITERATIONS=25
AGENT_TEMPERATURE=0.7
interface SessionConfig {
model?: string;
temperature?: number;
maxIterations?: number;
toolAllowlist?: string[];
workingDirectory: string;
initialInstructions?: string;
}

ErrorCauseSolution
APIErrorInvalid API key or rate limitCheck API key, implement backoff
ToolErrorTool execution failedShow error in tool card
TimeoutErrorTool took too longSet appropriate timeouts
SessionErrorSession ID not foundValidate session on mobile
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(Math.pow(2, i) * 1000); // Exponential backoff
}
}
throw new Error('Unreachable');
}

  • Store ANTHROPIC_API_KEY in bridge server environment only
  • Never expose to mobile app
  • Rotate keys regularly
// Restrict dangerous tools
const safeTools = [
new ReadTool(),
new EditTool(),
new BashTool({
allowedCommands: ['git', 'flutter', 'npm'],
blockedCommands: ['rm -rf', 'sudo', 'chmod'],
}),
];
// Verify working directory is within allowed paths
function validateWorkingDirectory(dir: string): void {
const allowedRoot = process.env.ALLOWED_PROJECT_ROOT;
if (!dir.startsWith(allowedRoot)) {
throw new Error('Working directory outside allowed root');
}
}


Last updated: 2026-03-17