The agent runtime that doesn't pick a side.

LLM-agnostic, plugin-first, sandboxed by default. A small core you can read in a weekend, with the wiring you actually need: providers, tools, policy, REPL, and an SSE task stream.

License: MIT CI status TypeScript Node 20+ npm workspaces Docker ready 281 total tests Runtime smoke passing Issues welcome

git clonenpm inpx tsx examples/hello-world.ts — that's the whole onboarding.

What's in the box

Six concerns, six small packages. Replace any of them.

Provider-neutral LLM

OpenAI, Anthropic Messages, Ollama in-box behind one LLMProvider interface. No vendor SDK; just fetch.

Sandbox by default

Shell metachars, NUL bytes, interpreter eval flags, path escapes — all rejected at parse time, before spawn.

Plugin-first

Drop a folder under plugins/, declare a manifest in package.json, get a hot-reloaded tool.

LLMClient decorator

Exponential backoff, AbortController timeouts, FIFO token bucket, in-memory cost tracker — opt-in.

SSE task stream

Per-task ring buffer with Last-Event-ID resume. Tail an agent run from any browser.

CLI & Web shipped

Native REPL with /help /model /reset /save /exit. React + Tailwind SPA in apps/web. Zero hidden services.

How it compares

Honest, opinionated, three rows that actually matter.

OpenHandAutoGPTCrewAILangChain Agents
Provider lock-in none OpenAI-first OpenAI-first many, heavy
Sandbox by default yes no no opt-in
Hot-reload plugins yes (fs.watch) no no restart
Core LOC you can read small large medium very large
Typing TS strict Python Python Python / JS
Interfaces shipped CLI + Web + HTTPCLI SDK SDK

What can you actually build?

Four concrete things, each in <100 lines.

Weather bot

The shipped plugins/weather exposes a get_weather tool. Wire it to a chat REPL and you have a working assistant.

openhand chat
> what's the weather in Tokyo?

Code reviewer

Allow git + cat in the sandbox; ask the agent to read a diff and post inline notes.

openhand ask "review the diff in HEAD~1..HEAD"

RSS digest agent

15-line plugin (see Cookbook 02) + a 5-minute cron pulls Hacker News and pushes a Markdown digest to Slack.

POLL_MS=300000 npx tsx examples/rss-digest-agent.ts

Shell automation helper

Sandbox-restricted shell + agent loop = "deploy assistant" that can git pull, run tests, but cannot rm -rf.

openhand exec "git status && npm test"

How it's organized

Apps depend on packages. Plugins are discovered, not imported. Providers are a swap.

flowchart LR
  user([User])
  subgraph Apps["apps/"]
    CLI["cli
(REPL)"] WEB["web
(React + Tailwind)"] SRV["server
(HTTP + SSE)"] end subgraph Packages["packages/"] CORE["core
agent · planner · policy"] TOOLS["tools
file · shell · http · email"] SBX["sandbox
spawn · policy · checks"] LLM["llm
providers + LLMClient"] end PLUG["plugins/*
(weather, calculator, code-reviewer,
rss-digest, file-organizer, git-summary, web-scraper)"] PROV[("OpenAI · Anthropic · Ollama · custom")] user --> CLI user --> WEB WEB --> SRV CLI --> CORE SRV --> CORE CORE --> TOOLS CORE --> LLM TOOLS --> SBX LLM --> PROV PLUG -.discovers + registers tools.-> CORE

Try the demo (in your browser)

Below is a tiny chat UI driven by the same MockProvider that examples/hello-world.ts defaults to — running on a Web Worker so it streams chunk-by-chunk without blocking the page. Nothing leaves your browser; there's no backend.

OpenHand · MockProvider

// Type a message and press send. Chunks arrive at ~6 chars/tick.

5-minute quickstart

Local dev

git clone https://github.com/ricardo-foundry/openhand.git
cd openhand
cp .env.example .env
npm install && npm run build
npm run dev          # CLI + server + web

Docker

git clone https://github.com/ricardo-foundry/openhand.git
cd openhand
cp .env.example .env
docker compose up --build
# web    -> http://localhost:3000
# server -> http://localhost:3001

Hello world (no API key)

brew install ollama && ollama serve &
ollama pull qwen2.5:0.5b

LLM_PROVIDER=ollama LLM_MODEL=qwen2.5:0.5b \
  npx tsx examples/hello-world.ts

SSE task stream

npm run dev:server &
curl -N http://localhost:3001/api/tasks/demo-1/stream &
curl -X POST http://localhost:3001/api/tasks/demo-1/_demo

Cookbook

Seven short recipes. Each one is <500 words, code-first.

01 · Hello World

Drive a real agent end-to-end with zero API keys via Ollama.

02 · Writing a plugin

Ship a plugin the agent can call. Use npm run plugin:new to scaffold.

03 · Custom LLM provider

Point the LLM layer at any OpenAI-compatible local server.

04 · Sandboxed shell

Watch the sandbox reject rm -rf $HOME at parse time.

05 · Streaming UI

Tail the SSE task stream from a 30-line React component.

06 · Multi-agent orchestration

Router + worker agents in ~80 lines of core + llm.

07 · Streaming + tool use

Drain stream() while the model calls tools mid-flight.