A research assistant built with Moru sandbox and Claude Agent SDK.
demo-hq.mp4
Run multiple Claude in parallel. Each agent gets its own dedicated Linux VM.
Renders Claude Code's native message format. Not restricted message subset of Claude Agent SDK.
See tool executions, thinking, and results in real-time
Agents maintain session history. Resume sessions anytime
Workspaces are saved to storage and restored on session resume. Files and Claude session persist across sessions.
Browse and view files in the agent's workspace. Download files that the agent writes or edits.
- Claude Code stores each chat session locally as newline-delimited JSON.
- I reverse-engineered those JSONL message formats and turned them into JSON Schemas for reuse (see agent-schemas)
- When a user sends a message, we boot a fresh Moru VM with a clean workspace; if it’s a resume, we restore the previous workspace and session JSONL into the VM
- Inside the VM, the agent reads the message from stdin and calls the Claude Agent SDK
query()function. - The backend tails the session JSONL file for new records and streams them to the frontend in near real time.
- When the run completes, we sync the workspace to Google Cloud Storage (GCS) so it can be restored next time.
Cloud: maru.moru.io - Maru is BYOK (Bring Your Own Key), but your API key is not saved on our server.
Self-hosted: Follow the setup instructions below.
- Node.js 22+
- PostgreSQL
- Moru API Key (for sandbox execution)
- GitHub OAuth App (for authentication)
- Clone and install dependencies:
git clone https://github.com/moru-ai/maru.git
cd maru
npm install- Set up environment files:
cp apps/server/.env.example apps/server/.env
cp apps/frontend/.env.example apps/frontend/.env
cp packages/db/.env.template packages/db/.env- Configure environment variables:
packages/db/.env
DATABASE_URL="postgresql://postgres:@127.0.0.1:5432/maru_dev"
DIRECT_URL="postgresql://postgres:@127.0.0.1:5432/maru_dev"apps/server/.env
# Database
DATABASE_URL="postgresql://postgres:@127.0.0.1:5432/maru_dev"
# GitHub OAuth
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
# Moru Sandbox (required)
MORU_API_KEY=your_moru_api_key
MORU_TEMPLATE_ID=maru-agent
MORU_SANDBOX_TIMEOUT_MS=3600000
# GCS Storage (optional, for workspace persistence)
GCS_BUCKET_NAME=your-bucket-name
GCS_KEY_FILE=./gcs-key.jsonapps/frontend/.env
NEXT_PUBLIC_SERVER_URL="http://localhost:4000"
BETTER_AUTH_SECRET=your_secret_here
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
DATABASE_URL="postgresql://postgres:@127.0.0.1:5432/maru_dev"- Set up the database:
# Create database
psql -U postgres -c "CREATE DATABASE maru_dev;"
# Generate Prisma client and push schema
npm run generate
npm run db:push- Build the agent template:
cd apps/agent
cp .env.example .env
# Add your MORU_API_KEY to .env
# Build and register the template
.venv/bin/python template.py- Start development servers:
npm run dev- Frontend: http://localhost:3000
- Backend: http://localhost:4000
# Start dev servers (frontend + backend)
npm run dev
# Type checking
npm run check-types
# Linting
npm run lint
# Database operations
npm run db:push # Push schema changes
npm run generate # Generate Prisma client
npm run db:studio # Open Prisma StudioAfter modifying any files in apps/agent/ (including Dockerfile, src/, or INSTRUCTIONS.md), you must rebuild the template:
cd apps/agent
.venv/bin/python template.pyThis builds a new Docker image and registers it with Moru. The next sandbox will use the updated template.
MIT License