PromptHub
Back to Blog
Developer Tools AI Infrastructure

Claude Agent Server: Stop Wrestling with Sandbox APIs

B

Bright Coding

Author

13 min read 40 views
Claude Agent Server: Stop Wrestling with Sandbox APIs

Claude Agent Server: Stop Wrestling with Sandbox APIs

What if running autonomous AI agents didn't require building infrastructure from scratch? Every developer who's tried deploying Claude Code in production knows the nightmare—sandbox escapes, API rate limits, and the endless dance of managing agent lifecycles. You write the orchestration layer. You handle the WebSocket reconnection logic. You pray your agents don't go rogue on shared infrastructure.

Here's the secret top AI engineers are already exploiting: dzhng/claude-agent-server transforms this chaos into a five-minute setup. This WebSocket server wraps the Claude Agent SDK in an E2B sandbox, giving you real-time bidirectional control without touching infrastructure code. No more managing container lifecycles. No more custom WebSocket implementations. Just install, connect, and deploy agents that actually stay contained.

The repository exploded on developer Twitter for one brutal reason—it solves the exact problem that's burned thousands of engineering hours. If you're building anything with autonomous AI agents, you need to understand what makes this architecture so ruthlessly effective.

What is dzhng/claude-agent-server?

dzhng/claude-agent-server is an open-source WebSocket relay server that bridges the gap between your applications and Anthropic's Claude Agent SDK. Created by David Zhang (@dzhng), this project addresses a critical infrastructure gap: how do you run Claude Code agents in isolated, scalable environments while maintaining real-time communication?

The project's core innovation is architectural simplicity. Instead of embedding agent logic directly into your application or managing complex container orchestration, you deploy the server as an E2B sandbox template. E2B (Execution to Binary) provides ephemeral, secure cloud sandboxes—each agent runs in complete isolation with its own filesystem, network policies, and resource limits. The WebSocket layer then becomes your control plane: send messages, receive responses, interrupt operations, all through a clean TypeScript client.

Why it's trending now: The timing is surgical. Anthropic released Claude Code with powerful agent capabilities, but production deployment remained an exercise in frustration. Developers needed sandboxing for security, WebSockets for real-time interaction, and SDK integration for full agent features. Building all three from scratch? That's weeks of infrastructure work. This repository collapses that to hours.

The monorepo structure separates concerns cleanly: packages/server/ handles the WebSocket relay and SDK integration, packages/client/ provides the consumer library, and packages/e2b-build/ manages deployment automation. This isn't a demo—it's production-grade infrastructure that Zhang himself uses for agent deployments.

Key Features That Eliminate Infrastructure Pain

Real-Time Bidirectional Communication The WebSocket implementation isn't bolted on—it's the primary interface. Messages flow both ways with typed schemas: WSInputMessage for commands, WSOutputMessage for responses. The server enforces single-connection exclusivity, preventing the race conditions that plague multi-tenant agent systems.

E2B Sandbox Native Every deployment spins a fresh, isolated environment. The build process (bun run build:e2b) creates a Bun 1.3-based template with automatic server startup on port 3000. Sandboxes terminate on disconnect—no zombie processes, no resource leaks. This is ephemeral compute done right.

Lazy Initialization Architecture The SDK query stream only initializes on first WebSocket connection. No wasted API calls, no idle Anthropic costs. Configuration happens pre-connection via the /config endpoint, then the server waits efficiently until needed.

Interrupt Capability Agents can runaway. The interrupt message type stops operations immediately—critical for production systems where you need kill switches. Most DIY implementations forget this until it's 3 AM and your agent is burning through rate limits.

Message Queue with Backpressure Incoming messages queue automatically; the SDK stream processes them sequentially. No message loss, no out-of-order execution. The relay pattern keeps the architecture stateless except for the active connection.

TypeScript-First Client Library The @dzhng/claude-agent package handles sandbox lifecycle, WebSocket management, and serialization. You get typed interfaces, automatic cleanup, and debug logging without writing infrastructure code.

Pre-Connection Configuration Set agents, allowed tools, system prompts, and models via REST before WebSocket establishment. This separation of concerns lets you version configurations independently from connection logic.

Use Cases Where This Architecture Dominates

Autonomous Coding Agents in CI/CD Imagine PR review bots that actually run code safely. Deploy a sandbox per pull request, let Claude analyze, test, and suggest changes in isolation. When the PR merges, the sandbox dies. No persistent access to your source code, no cross-contamination between reviews.

Multi-Tenant AI Applications Building a platform where users get their own AI assistant? Each user session spins a fresh E2B sandbox. Their data never touches shared infrastructure. The single-connection model maps perfectly to one-sandbox-per-user architectures without complex session management.

Long-Running Research Agents Claude Code excels at deep research tasks—hours of file reading, analysis, and synthesis. Running this locally ties up your machine; running it raw in cloud risks security issues. The sandbox model gives you persistent, isolated compute that you can monitor and interrupt remotely via WebSocket.

Safe Tool-Using Agent Demos Demo environments are attack surfaces. When prospects test your AI product, you want maximum capability with zero risk. E2B sandboxes provide read-only or fully ephemeral filesystems; even if someone prompts-injects destructive commands, the blast radius is one disposable container.

Embedded Agent Orchestration Your main application stays clean. The agent server handles all Anthropic SDK complexity, tool definitions, and execution environment. Your app just sends JSON messages and receives responses. Microservice boundaries without microservice overhead.

Step-by-Step Installation & Setup Guide

Prerequisites

You'll need Bun installed (the server targets Bun 1.3), plus API keys from Anthropic and E2B.

1. Clone and Configure

git clone https://github.com/dzhng/claude-agent-server.git
cd claude-agent-server

Copy the environment template:

cp .env.example .env

Edit .env with your credentials:

ANTHROPIC_API_KEY=sk-ant-your-api-key-here
E2B_API_KEY=e2b_your-api-key-here

Install dependencies across the monorepo:

bun install

2. Build Your E2B Sandbox Template

This is the deployment magic—one command builds your production environment:

bun run build:e2b

Behind the scenes, this executes packages/e2b-build/build.prod.ts, which:

  • Pulls the Bun 1.3 base image
  • Installs system dependencies (git)
  • Clones your repository into the sandbox
  • Runs bun install for dependencies
  • Configures automatic server startup on port 3000

The build takes several minutes. Once complete, your claude-agent-server template lives in E2B's infrastructure, ready to spawn sandboxes on demand.

3. Verify with Local Testing

Before burning E2B credits, test locally:

# Terminal 1: Start the server
bun run start:server

# Terminal 2: Run local test client
bun run test:local

The local server runs on http://localhost:3000 with WebSocket at ws://localhost:3000/ws. The test client connects directly—no E2B sandbox creation, faster iteration.

4. Deploy Client in Your Application

Install the client library:

npm install @dzhng/claude-agent
# or
bun add @dzhng/claude-agent

Configure with your template and connect:

import { ClaudeAgentClient } from '@dzhng/claude-agent'

const client = new ClaudeAgentClient({
  e2bApiKey: process.env.E2B_API_KEY,
  anthropicApiKey: process.env.ANTHROPIC_API_KEY,
  template: 'claude-agent-server', // Your built template name
  debug: true, // Essential during development
})

await client.start() // Creates sandbox, establishes WebSocket

REAL Code Examples from the Repository

These examples are extracted directly from the official README and implementation. Study them—they reveal the design patterns that make this system reliable.

Example 1: Complete Client Lifecycle (Production Pattern)

This is the canonical usage from the Quick Start section. Notice how cleanup is explicit—critical for preventing E2B sandbox leaks:

import { ClaudeAgentClient } from '@dzhng/claude-agent'

// Initialize with all required credentials
const client = new ClaudeAgentClient({
  e2bApiKey: process.env.E2B_API_KEY,
  anthropicApiKey: process.env.ANTHROPIC_API_KEY,
  template: 'claude-agent-server', // Must match your deployed template
  debug: true, // Logs sandbox creation, WebSocket events, errors
})

// Start creates E2B sandbox AND establishes WebSocket connection
await client.start()

// Register handler BEFORE sending messages to catch all responses
client.onMessage(message => {
  if (message.type === 'sdk_message') {
    console.log('Claude:', message.data)
  }
})

// Send structured message matching SDKUserMessage format
client.send({
  type: 'user_message',
  data: {
    type: 'user',
    session_id: 'my-session', // Your application session tracking
    message: {
      role: 'user',
      content: 'Hello, Claude!',
    },
  },
})

// CRITICAL: Always stop to terminate sandbox and free resources
await client.stop()

Why this pattern matters: The start()/stop() symmetry ensures sandbox lifecycle management. Without stop(), E2B sandboxes run until timeout (default 5 minutes), burning credits. The session_id field lets you correlate agent responses with your application's session model.

Example 2: Direct Server Configuration via REST API

For advanced use cases, configure the server before WebSocket connection. This example from the Server API Reference shows how to constrain agent capabilities:

# POST configuration before WebSocket connection
curl -X POST http://localhost:3000/config \
  -H "Content-Type: application/json" \
  -d '{
    "systemPrompt": "You are a helpful assistant.",
    "allowedTools": ["read_file", "write_file"], // Restrict tool access
    "anthropicApiKey": "sk-ant-...", // Override env var per-request
    "model": "claude-3-5-sonnet-20241022",
    "agents": {
      "myAgent": {
        "name": "My Custom Agent",
        "description": "A custom agent"
      }
    }
  }'

The security insight: Passing anthropicApiKey via /config overrides the environment variable. This enables multi-tenant deployments where each client brings their own API credentials—your infrastructure never holds customer keys permanently.

Example 3: E2B Template Customization (Infrastructure as Code)

The build configuration reveals how sandboxes are constructed. This from packages/e2b-build/build.prod.ts:

const template = Template()
  .fromBunImage('1.3')                    // Pin to specific Bun version
  .runCmd('sudo apt install -y git')      // System dependency installation
  .gitClone('https://github.com/...', ...) // Your fork or private repo
  .setWorkdir('/home/user/app')           // Predictable filesystem layout
  .runCmd('bun install')                  // Dependency resolution in sandbox
  .setStartCmd('bun packages/server/index.ts', waitForPort(3000)) // Health check

Customization vectors: Add .runCmd() steps for additional system packages, modify setWorkdir() for monorepo structures, or change waitForPort() timeout for slower startup. The waitForPort(3000) ensures E2B marks the sandbox healthy only when the server actually listens.

Example 4: Raw WebSocket Message Protocol

Understanding the wire format helps debug and build custom clients. From the WebSocket API documentation:

// Client → Server: User message
const userMessage = {
  type: 'user_message',
  data: {
    type: 'user',
    session_id: 'your-session-id',
    parent_tool_use_id: null, // Set when responding to tool_use
    message: {
      role: 'user',
      content: 'Hello, Claude!'
    }
  }
}

// Client → Server: Emergency stop
const interrupt = { type: 'interrupt' }

// Server → Client: Connection confirmed
const connected = { type: 'connected' }

// Server → Client: Agent response
const sdkMessage = {
  type: 'sdk_message',
  data: {
    type: 'assistant',
    session_id: '...',
    message: { /* Claude's response structure */ }
  }
}

Protocol design note: The parent_tool_use_id field enables multi-turn tool interactions. When Claude requests a tool execution, your application responds with this field set, maintaining conversation continuity. The interrupt type is your circuit breaker—send it anytime to halt execution.

Advanced Usage & Best Practices

Template Versioning for Safe Deployments Never overwrite your production template during development. Modify build.prod.ts to append version suffixes: claude-agent-server-v2. The client library's template option makes switching trivial. This prevents breaking production agents during experimentation.

Connection URL Fallback Strategy The client supports connectionUrl for local development and E2B for production. Structure your code to switch based on environment:

const client = new ClaudeAgentClient(
  process.env.NODE_ENV === 'development'
    ? { connectionUrl: 'http://localhost:3000', anthropicApiKey }
    : { e2bApiKey, anthropicApiKey, template }
)

System Prompt Presets with Extension The systemPrompt option accepts either raw strings or structured presets. Use the claude_code preset with append to extend without losing tested defaults:

systemPrompt: {
  type: 'preset',
  preset: 'claude_code',
  append: 'Always include performance considerations in your analysis.'
}

Resource Cleanup in Error Paths Wrap client usage in try/finally blocks. E2B sandboxes cost money; uncaught exceptions that skip stop() are expensive:

const client = new ClaudeAgentClient(config)
try {
  await client.start()
  // ... operations
} finally {
  await client.stop() // Guaranteed execution
}

Debug Logging in Production The debug: true option is verbose but essential for initial production validation. Log WebSocket connection events, message timing, and sandbox creation latency. Disable once stable.

Comparison with Alternatives

Feature dzhng/claude-agent-server Raw Claude SDK LangChain Agents Custom Docker↗ Bright Coding Blog Setup
Sandbox Isolation Native E2B integration None Optional Manual configuration
WebSocket Interface Built-in None None Build yourself
Deployment Complexity Single command (build:e2b) Local only Cloud orchestration Full DevOps↗ Bright Coding Blog pipeline
Agent SDK Features Full native support Direct access Abstraction layer Manual integration
Connection Model Single exclusive WebSocket N/A Variable Variable
Interrupt Capability Native message type SDK method Framework-dependent Custom implementation
Client Library Official TypeScript package None LangChain JS None
Ephemeral Compute Automatic sandbox lifecycle N/A Manual Manual container mgmt

The decisive factor: Alternatives force you to build infrastructure. This repository gives you infrastructure as a configured default. You trade flexibility for velocity—and for most agent deployments, that's the correct trade.

FAQ

Is dzhng/claude-agent-server free to use? The server is MIT-licensed and free. You pay for Anthropic API usage and E2B sandbox compute. E2B offers generous free tiers for development.

Can I run this without E2B? Absolutely. Use connectionUrl: 'http://localhost:3000' for local servers, or deploy the server to any infrastructure that exposes WebSocket endpoints. E2B is the optimized path, not a hard dependency.

How do I handle multiple concurrent users? The server accepts only one WebSocket connection at a time. For multi-user applications, spawn one sandbox per user via the client library—each gets isolated compute and a dedicated connection.

What Bun version is required? The E2B template uses Bun 1.3. Local development should match this version. The server relies on Bun's native WebSocket and HTTP server APIs.

Can I customize the sandbox environment? Yes. Modify packages/e2b-build/build.prod.ts to add system packages, environment variables, or resource configurations. Rebuild with bun run build:e2b.

How do I debug WebSocket connection issues? Enable debug: true in the client constructor. Check browser dev tools for the test client at http://localhost:3000/. Verify E2B sandbox health in the E2B dashboard.

Is this production-ready? The architecture is designed for production: isolated sandboxes, automatic cleanup, typed protocols, and interrupt safety. As with any infrastructure, load test before high-traffic deployment.

Conclusion

The gap between Claude Code's capabilities and production deployment just collapsed. dzhng/claude-agent-server isn't another wrapper—it's infrastructure opinionation done right. The E2B sandbox model eliminates security anxiety. The WebSocket relay eliminates integration complexity. The TypeScript client eliminates boilerplate.

What strikes me most is the architectural restraint. Zhang could have built a complex orchestration system; instead, he built a simple relay with strict constraints (single connection, lazy init, message queue). These constraints make the system predictable, debuggable, and robust.

If you're building with AI agents in 2024, you have two paths: spend weeks on infrastructure, or deploy in minutes with battle-tested patterns. The repository is ready, the documentation is complete, and the community is growing.

Stop wrestling with sandbox APIs. Clone dzhng/claude-agent-server and ship your first isolated agent today.

Comments (0)

Comments are moderated before appearing.

No comments yet. Be the first to share your thoughts!

Recommended Prompts

View All
All tools