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.
git clone → npm i → npx tsx examples/hello-world.ts — that's the whole onboarding.
Six concerns, six small packages. Replace any of them.
OpenAI, Anthropic Messages, Ollama in-box behind one LLMProvider interface. No vendor SDK; just fetch.
Shell metachars, NUL bytes, interpreter eval flags, path escapes — all rejected at parse time, before spawn.
Drop a folder under plugins/, declare a manifest in package.json, get a hot-reloaded tool.
Exponential backoff, AbortController timeouts, FIFO token bucket, in-memory cost tracker — opt-in.
Per-task ring buffer with Last-Event-ID resume. Tail an agent run from any browser.
Native REPL with /help /model /reset /save /exit. React + Tailwind SPA in apps/web. Zero hidden services.
Honest, opinionated, three rows that actually matter.
| OpenHand | AutoGPT | CrewAI | LangChain 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 + HTTP | CLI | SDK | SDK |
Four concrete things, each in <100 lines.
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?
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"
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
Sandbox-restricted shell + agent loop = "deploy assistant" that can git pull, run tests, but cannot rm -rf.
openhand exec "git status && npm test"
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
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.
npx tsx examples/hello-world.ts — see
examples/.
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
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
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
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
Seven short recipes. Each one is <500 words, code-first.
Drive a real agent end-to-end with zero API keys via Ollama.
Ship a plugin the agent can call. Use npm run plugin:new to scaffold.
Point the LLM layer at any OpenAI-compatible local server.
Watch the sandbox reject rm -rf $HOME at parse time.
Tail the SSE task stream from a 30-line React component.
Router + worker agents in ~80 lines of core + llm.
Drain stream() while the model calls tools mid-flight.