Skip to main content
Maru is a full-featured AI agent chat application that demonstrates how to use Moru sandboxes with the Claude Agent SDK.

Overview

Maru is an open-source example showing:
  • Running Claude Agent SDK in Moru sandboxes
  • Session persistence across sandbox restarts
  • Real-time streaming of agent output
  • Tool execution in isolated environments
  • Workspace file management

View on GitHub

Explore the complete source code.

Architecture

Components

ComponentDescription
Web AppReact-based chat interface
BackendNode.js server managing sandboxes
SandboxMoru sandbox running the agent
AgentClaude Agent SDK for AI reasoning
StorageSession and workspace persistence

Key Features

1. Sandbox Lifecycle Management

// Create a sandbox for a new session
const sandbox = await Sandbox.create("claude-agent-template", {
  metadata: { userId, sessionId },
  timeout: 3600  // 1 hour
})

// Start the agent process
await sandbox.commands.run("claude-code start", { background: true })

2. Session Persistence

Sessions are preserved across sandbox restarts:
// Save session before pausing
const sessionData = await sandbox.files.read("~/.claude/session.jsonl")
await storage.save(sessionId, sessionData)

// Restore session on resume
await sandbox.files.write("~/.claude/session.jsonl", sessionData)

3. Real-time Streaming

Stream agent output to the user:
await sandbox.commands.run("claude-code message", {
  onStdout: (data) => {
    ws.send(JSON.stringify({ type: "output", data }))
  }
})

4. Tool Execution

The agent executes tools in the sandbox:
// Agent runs code
const result = await sandbox.commands.run("python3 script.py")

// Agent modifies files
await sandbox.files.write("/workspace/app.py", code)

// Agent runs tests
const testResult = await sandbox.commands.run("pytest")

Quick Start

Prerequisites

  • Node.js 18+
  • Moru API key
  • Anthropic API key

Setup

# Clone the repository
git clone https://github.com/moru-ai/maru
cd maru

# Install dependencies
npm install

# Configure environment
cp .env.example .env
# Edit .env with your API keys

# Start the application
npm run dev

Environment Variables

# .env
MORU_API_KEY=moru_...
ANTHROPIC_API_KEY=sk-ant-...
DATABASE_URL=postgres://...

Project Structure

maru/
├── apps/
│   ├── web/              # React frontend
│   │   ├── src/
│   │   │   ├── components/
│   │   │   └── hooks/
│   │   └── package.json
│   └── server/           # Node.js backend
│       ├── src/
│       │   ├── routes/
│       │   ├── services/
│       │   └── sandbox/
│       └── package.json
├── packages/
│   └── shared/           # Shared types
└── docker/
    └── agent/            # Agent Dockerfile

Template Setup

The agent runs in a custom Moru template:
const template = Template()
  .fromPythonImage("3.11")
  .runCmd("pip install anthropic")
  .copy("./agent", "/app")
  .setStartCmd("python /app/main.py", waitForPort(8080))

await Template.build(template, { alias: "claude-agent-template" })

Sandbox Management

Creating Sessions

async function createSession(userId: string) {
  const sessionId = generateId()

  const sandbox = await Sandbox.create("claude-agent-template", {
    metadata: { userId, sessionId },
    envs: {
      ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY
    }
  })

  return { sessionId, sandboxId: sandbox.sandboxId }
}

Resuming Sessions

async function resumeSession(sessionId: string) {
  const session = await storage.get(sessionId)

  const sandbox = await Sandbox.connect(session.sandboxId)
  await sandbox.connect()  // Resume if paused

  return sandbox
}

Cleanup

async function cleanupSession(sessionId: string) {
  const session = await storage.get(sessionId)

  // Save state
  const workspace = await sandbox.files.read("/workspace", { format: "bytes" })
  await storage.saveWorkspace(sessionId, workspace)

  // Kill sandbox
  await sandbox.kill()
}

Learn More