npm version Pure JavaScript ESM zero dependencies MIT license

Pure JavaScript container
orchestration for Claude AI agents

Run Claude agents in isolated Docker containers with streaming output, filesystem IPC, and built-in MCP tools. Plain .js files. No build step. Zero dependencies.

$ npm install jsclaw Copied!

What's inside

The core primitives you need to orchestrate Claude agents in containers.

📦

Container Runner

Spawn Docker, Podman, or Apple containers. Stream agent output in real-time via sentinel-delimited JSON over stdout.

📁

Filesystem IPC

Atomic JSON file-based communication between host and container. No sockets, no ports—just files. Simple and reliable.

Group Queue

Per-group concurrency with configurable global limits. Exponential backoff retry. One container per group, up to N in parallel.

🔧

MCP Tools

Built-in MCP server exposes send_message, schedule_task, and task lifecycle tools to the agent inside the container.

🔒

Mount Security

Allowlist-based volume mount validation. Blocks sensitive paths (.ssh, .env, credentials) by default. Symlink-aware.

Zero Dependencies

The host side uses only Node.js built-ins. No native modules, no build step. Pure JavaScript ESM that just works.

Quick start

Install, configure, run. Plain JavaScript — no compilation, no transpilation.

run-agent.js
import { runContainerAgent, createConfig } from 'jsclaw';

const result = await runContainerAgent(
  { name: 'my-agent', folder: 'my-agent' },
  { prompt: 'Analyze the codebase and suggest improvements.',
    groupFolder: 'my-agent', chatJid: 'user-1', isMain: true },
  (proc, name) => console.log(`Started ${name}`),
  async (output) => console.log(output.result),
);

Architecture

Host process communicates with isolated containers via stdin/stdout and filesystem IPC.

Host Process (your app) container-runner.js Spawns containers, streams output ipc.js Polls IPC directories group-queue.js Concurrency control Docker Container agent-runner.js Drives Claude SDK mcp-server.js send_message, schedule_task /workspace/group/ Isolated filesystem stdin stdout files files data/ipc/{group}/ messages/ tasks/ input/ /workspace/ipc/ messages/ tasks/ input/

API Reference

Granular ESM exports — import only what you need.

Export Description
createConfig(overrides?) Create configuration with defaults, env vars, and overrides
runContainerAgent(group, input, ...) Spawn a container, run a Claude agent, stream results
GroupQueue Per-group concurrency queue with global container limit
startIpcWatcher(deps, config?) Poll IPC directories for messages and task operations
writeIpcFile(dir, data) Atomic JSON file write (temp + rename)
drainIpcDir(dir, filter?) Read all JSON files from a directory, parse, and delete
validateAdditionalMounts(...) Validate volume mounts against a security allowlist
buildVolumeMounts(group, config) Build Docker volume mount arguments for a group
parseContainerOutput(buffer) Parse sentinel-delimited JSON from container stdout
createLogger(options?) Minimal JSON logger (no dependencies)

MCP Tools

Tools available to the Claude agent inside the container via the built-in MCP server.

Tool Description
send_message Send a message to the user or group immediately
schedule_task Schedule a cron, interval, or one-shot task
list_tasks List all scheduled tasks for the current group
pause_task Pause a scheduled task by ID
resume_task Resume a paused task by ID
cancel_task Cancel and delete a scheduled task

Configuration

Configure via createConfig() overrides or environment variables.

Env Variable Default Description
JSCLAW_CONTAINER_IMAGE jsclaw-agent:latest Docker image for agent containers
JSCLAW_CONTAINER_RUNTIME docker docker, podman, or container
JSCLAW_CONTAINER_TIMEOUT 1800000 Idle timeout in milliseconds
JSCLAW_MAX_CONCURRENT 5 Maximum concurrent containers
JSCLAW_DATA_DIR ./data IPC data directory
JSCLAW_GROUPS_DIR ./groups Group workspace directory
ANTHROPIC_API_KEY Required for Claude API access

Telegram Bot in 30 Lines

Bring your own I/O — here's a full Telegram bot wired to jsclaw. Per-chat containers, session continuity, queue concurrency.

examples/telegram.js
import { Bot } from 'grammy';
import { runContainerAgent, GroupQueue, createConfig } from 'jsclaw';

const bot   = new Bot(process.env.TELEGRAM_BOT_TOKEN);
const queue = new GroupQueue(createConfig());
const sessions = new Map();

bot.on('message:text', async (ctx) => {
  const id = String(ctx.chat.id);
  await ctx.replyWithChatAction('typing');

  await queue.enqueueTask(id, `msg-${Date.now()}`, async () => {
    await runContainerAgent(
      { name: id, folder: id },
      { prompt: ctx.message.text, groupFolder: id,
        chatJid: id, isMain: true, sessionId: sessions.get(id) },
      null,
      async (out) => {
        if (out.result) await ctx.reply(out.result);
        if (out.newSessionId) sessions.set(id, out.newSessionId);
      },
    );
  });
});

bot.start();

Full example with error handling, message splitting, and graceful shutdown: examples/telegram.js