Skip to content

Run Claude Code agents in ephemeral Firecracker VMs on Fly.io

pattern

Running multiple Claude Code agents safely requires sandboxed environments with streaming output and shared library access

claude-codeagentsorchestrationfly-iofirecrackerdevops
23 views

Problem

You want to run Claude Code as a headless agent across multiple tasks in parallel, but each agent needs an isolated filesystem, its own OAuth credentials, and a way to stream structured logs back to a central API. Running agents directly on a shared host risks file conflicts, credential leakage, and no easy way to tear down state between runs.

Solution

Use a three-tier architecture: a Go WebSocket tunnel client on the user's machine, a Node.js orchestrator on Fly.io, and ephemeral Node.js worker agents on Fly.io Firecracker VMs.

Orchestrator spawns workers (up to 5 parallel):

// orchestrator.ts
import { spawn } from "child_process";

interface AgentTask {
  id: string;
  prompt: string;
  repo: string;
  agentConfig: string;
}

function spawnWorker(task: AgentTask): void {
  const proc = spawn("claude", [task.prompt], {
    cwd: `/workspace/${task.repo}`,
    env: {
      ...process.env,
      CLAUDE_CODE_OAUTH_TOKEN: getOAuthKey(task.id),
    },
  });

  proc.stdout.on("data", (chunk) => {
    tunnel.send(JSON.stringify({ taskId: task.id, log: chunk.toString() }));
  });
}

Agent definitions as JSON in a library system:

{
  "name": "code-reviewer",
  "model": "claude-sonnet-4-20250514",
  "system_prompt": "You are a senior code reviewer...",
  "tools": ["Read", "Glob", "Grep"],
  "max_tokens": 4096
}

Library directory structure for shared resources:

/data/library/
  skill/{namespace}/{name}/
  agent/{namespace}/{name}/
  mcp/{namespace}/{name}/
  hooks/{namespace}/{name}/
  rules/{namespace}/{name}/
  settings/{namespace}/{name}/

Per-repo MCP servers seeded at boot:

{
  "mcpServers": {
    "filesystem": { "command": "npx", "args": ["@anthropic/mcp-filesystem"] },
    "github": { "command": "npx", "args": ["@anthropic/mcp-github"] },
    "postgres": { "command": "npx", "args": ["@anthropic/mcp-postgres"] },
    "puppeteer": { "command": "npx", "args": ["@anthropic/mcp-puppeteer"] }
  }
}

Per-repo agent definitions with scoped tool access:

<!-- .claude/agents/code-reviewer.md -->
You are a code reviewer. Focus on correctness, security, and performance.

allowedTools: Read, Glob, Grep, WebFetch
deniedTools: Write, Edit, Bash

Why It Works

Firecracker VMs on Fly.io provide sub-second boot times with full isolation, so each Claude Code agent runs in a clean environment that is destroyed after the task completes. The WebSocket tunnel pipes SDK messages from ephemeral workers back to the Go client without exposing ports or managing long-lived connections. The library system with namespace scoping (_global for shared, project-specific otherwise) lets agents share skills and MCP configs without filesystem coupling. By calling the claude CLI directly rather than wrapping it in LangChain or CrewAI, you avoid framework overhead and get the full Claude Code tool suite -- including hooks, rules, and the agent system -- with zero abstraction tax.

Context

  • The two key innovations are injecting the Claude Code OAuth key into ephemeral servers at boot and piping Claude Code SDK messages back through WebSocket tunnels to the central Go server
  • No framework dependencies (no LangChain, no CrewAI) -- just the Claude Code CLI spawned as a child process
  • Fly.io Firecracker VMs provide hardware-level isolation at container-like speeds
  • The orchestrator caps at 5 parallel workers to stay within API rate limits
  • Agent tool access is controlled via allowedTools and deniedTools arrays in per-repo agent definitions under .claude/agents/
  • The _global namespace makes skills and configs available to all repos without duplication
About this share
Contributormblode
Repositorymblode/shares
CreatedFeb 10, 2026
View on GitHub