Roj is a TypeScript SDK for building multi-agent LLM applications. You describe agents (system prompt, model, tools, sub-agents) as a preset, run it on a server, and consume it from a browser with ready-made React hooks and chat components.
The runtime is event-sourced and plugin-driven: every state change is a domain event, and almost all behavior — filesystem access, shell, services, todos, mailbox, compaction — lives in plugins you compose per preset and per agent.
Status: early. Packages are published as
0.1.xand the API still moves between releases.
| Package | Description |
|---|---|
@roj-ai/sdk |
Agent runtime — LLM providers, sessions, agents, event store, plugin system, built-in tools |
@roj-ai/transport |
WebSocket transport + RPC protocol (browser and Bun adapters) |
@roj-ai/shared |
Shared types, projections, RPC schemas |
@roj-ai/client |
Vanilla RPC client + platform REST client |
@roj-ai/client-react |
React hooks (useChat, usePreviewUrl, session stores) and chat components |
@roj-ai/debug |
Debug UI components (event timeline, agent tree, LLM calls) |
@roj-ai/standalone-server |
Single-instance local runtime with a platform-compatible REST + WS surface |
@roj-ai/sandbox-runtime |
Bun agent host for E2B sandboxes |
@roj-ai/cli |
REPL + CLI for inspecting sessions, agents, events and LLM calls |
@roj-ai/platform-cli |
roj build / upload / deploy for agent bundles |
@roj-ai/demo |
App Builder demo + e2e test (not published) |
Roj is also the OSS core of a hosted platform. The same client packages talk to either the standalone server or the Cloudflare-hosted platform — only the URL and auth differ.
- Bun (the monorepo, the server runtime and the test runner)
- An LLM API key —
ANTHROPIC_API_KEYorOPENROUTER_API_KEY
The demo runs the whole stack locally: an App Builder agent that writes plain HTML/CSS/JS, serves it from a dev service, and streams the result into a React SPA with a live preview iframe.
bun install
export ANTHROPIC_API_KEY=sk-ant-...
# terminal 1 — API server on :2486
bun run --filter @roj-ai/demo dev:server
# terminal 2 — SPA on :2487
bun run --filter @roj-ai/demo dev:spaOpen http://localhost:2487. See packages/demo/README.md
for the layout and the snapshot-based e2e test.
1. Define a preset. An agent is a system prompt, a model, plugins and optional sub-agents. Services (dev servers, watchers) start alongside it.
// roj.config.ts
import { ModelId, createOrchestrator, createPreset, defineAgent, defineConfig } from '@roj-ai/sdk'
import { filesystemPlugin } from '@roj-ai/sdk/tools/filesystem'
import { shellPlugin } from '@roj-ai/sdk/tools/shell'
const builder = defineAgent({
name: 'builder',
system: 'You build small web apps from a description.',
model: ModelId('anthropic/claude-haiku-4.5'),
services: [{
type: 'dev',
description: 'Preview server',
command: ({ port }) => `bunx serve -l ${port} .`,
autoStart: true,
readyPattern: 'Accepting connections',
}],
plugins: [filesystemPlugin.configureAgent({ directoryListing: { maxDepth: 3 } })],
tools: [],
agents: [],
})
export default defineConfig({
presets: [createPreset({
id: 'app-builder',
name: 'App Builder',
workspaceDir: '/tmp/roj/sessions/{sessionId}',
plugins: [shellPlugin.configure({ cwd: '/tmp/roj/sessions' })],
orchestrator: createOrchestrator({ ...builder, agents: [] }),
})],
})2. Run it. The standalone server hosts the preset and exposes the platform
REST + WebSocket shape on :2486.
bunx roj-standalone roj.config.ts3. Consume it. useChat handles the WebSocket connection, message stream,
agent questions, attachments and service readiness.
import { MessageInput, MessageList, useChat } from '@roj-ai/client-react'
function Chat({ instanceId, sessionId }: { instanceId: string; sessionId: string }) {
const chat = useChat({
platformUrl: 'http://localhost:2486',
instanceId,
sessionId,
token: '', // standalone has no auth — bind to localhost
services: ['dev'],
})
return (
<>
<MessageList messages={chat.messages} isAgentTyping={chat.isAgentTyping} />
<MessageInput disabled={!chat.isConnected} />
</>
)
}Instances and sessions are created with createRojClient from
@roj-ai/client/platform — see packages/demo/spa/App.tsx.
bun run packages/cli/src/main.ts # REPL against http://localhost:2486
bun run packages/cli/src/main.ts agents <sessionId> # agent tree
bun run packages/cli/src/main.ts events <sessionId> # domain events
bun run packages/cli/src/main.ts llm-calls <sessionId> # LLM call logThe server reads these environment variables (see
packages/sdk/src/config.ts):
| Variable | Default | Meaning |
|---|---|---|
PORT |
2486 |
Listen port |
HOST |
0.0.0.0 |
Bind address |
DATA_PATH |
./data |
Event store, workspaces, local registry |
PERSISTENCE |
file |
file or memory |
ANTHROPIC_API_KEY |
— | Anthropic provider |
OPENROUTER_API_KEY |
— | OpenRouter provider (fallback) |
DEFAULT_MODEL |
anthropic/claude-haiku-4.5 |
Model when a preset does not set one |
LOG_LEVEL / LOG_FORMAT |
info / console |
Logging |
Per-preset and per-agent settings (plugins, tools, workspace, services) live in
roj.config.ts, not in the environment.
bun install
bun run ts:build # type-check + build all packages
bun run ts:watch # incremental
bun run lint # Biome
bun run lint:fix
bun test # package testsConventions: ESM only, export type for type-only exports, Biome for linting.
packages/sdk is excluded from Biome and keeps its own conventions.
Tag a commit on clean main — publish.yml
publishes every public package to npm.
git tag vX.Y.Z && git push origin vX.Y.ZCLAUDE.md— repo map and conventionspackages/sdk/CLAUDE.md— plugin system, event sourcing, transportpackages/standalone-server/CLAUDE.md— REST surface, git layout, local registrypackages/demo/CLAUDE.md— demo and snapshot e2e test