A minimalistic, self-mutating agent harness in the spirit of Pi: plain JavaScript, zero dependencies, a WASM sandbox for untrusted code, an always-on daemon, multi-agent orchestration, and a model router that speaks to Anthropic, OpenAI and OpenRouter — via OAuth or API keys.
Its trick: varela rewrites its own source while running (and hot-reloads), logs every run, and turns repeated wins into new tools and repeated failures into lessons — so it measurably gets better the longer you use it. And you can watch it work in a 3D cyberpunk world where the agent is a person and fanning out spawns more of them, live.
Drop a screen-recording of
varela --webhere — the world with avatars fanning out is the demo that sells it.git add docs/demo.gif.
Quickstart
# Node ≥ 20. No npm install needed (zero deps). node bin/varela.js import # reuse an existing Claude Code / Codex login… node bin/varela.js login # …or sign in fresh (OAuth or API key) node bin/varela.js # start chatting node bin/varela.js --web # …or explore the 3D world
Already use Claude Code or Codex? varela import copies those logins in (varela
uses the same client IDs, so their tokens work as-is). --claude / --codex
to pick one. It only reads those stores when you run it — never automatically.
That's the 30-second path. Everything below is depth.
varela login # sign in to a provider (OAuth or API key)
varela # full-screen chat that fills the terminal (--plain for a line console)
varela --tui # full-screen split-view UI (file tree + chat + preview)
varela --dash # mission-control dashboard (agent tree, sparklines, growth)
varela --web # 3D cyberpunk world UI (reuses a running daemon, else starts one)
varela -p "task" # one-shot mode
varela --daemon # WebSocket daemon: scheduler + heartbeat + serves the 3D world
varela hub [port] # run a mesh rendezvous hub for the P2P human-agent world
Requires Node.js ≥ 20 (uses node:wasi, fetch, ESM). No npm install needed.
Install
The harness runs in place — the checkout is the install (it self-mutates).
The installers only put a varela launcher shim on your PATH:
# Windows .\install.ps1 # shim in %LOCALAPPDATA%\varela\bin + user PATH .\install.ps1 -Daemon # …and register the daemon at logon (Task Scheduler) .\install.ps1 -Uninstall
# Linux/macOS ./install.sh # shim in ~/.local/bin ./install.sh --daemon # …and enable a systemd user service for the daemon ./install.sh --uninstall
No installer? Add scripts/ to your PATH — it holds repo-relative launchers
(scripts/varela.cmd, scripts/varela) —
or just run node bin/varela.js. npm install -g . (or the packed tarball —
npm pack produces a ~53 kB package) also works; note that a global install
relocates the harness root, so self-mutations then edit the globally installed
copy, and running from a git checkout keeps them version-controlled.
What it is
-
Agent loop (src/agent.js) — sends the conversation plus tool schemas to the routed model, executes tool calls (dangerous ones require confirmation), loops until the model stops calling tools.
-
Model router (src/router.js) — resolves
provider/modelstrings, manages credentials and token refresh. Built-in providers:anthropic— OAuth (Claude Pro/Max sign-in, PKCE) or API keyopenai— OAuth (ChatGPT sign-in, PKCE, localhost callback) or API keyopenrouter— OAuth PKCE key exchange or API key- Drop a JS module in
~/.varela/extensions/providers/to add another; it just needs to default-export{ id, models, login, refresh, chat }. The internal message format is Anthropic-style content blocks; shared converters for OpenAI-compatible APIs live in src/providers/openai-compat.js.
-
WASM sandbox (src/wasm/sandbox.js) — runs WASI preview1 modules from
~/.varela/wasmwith no filesystem or network access by default. This is where generated/untrusted code should run; theshelltool is for trusted host automation. -
Self-mutability — the harness can rewrite itself while running:
self_list/self_read/self_edit— inspect and patch varela's own source (with backup + rollback if the edited file fails to load).create_tool— write a new tool into~/.varela/extensions/tools/.- Both hot-reload the tool registry and providers immediately (cache-busted
dynamic
import()), so the agent can use a capability it just built in the very next step.
-
Console (src/cli/chat.js) — the default
varelaopens a full-screen chat that fills the terminal, Pi-style: no alternate screen, so finished messages commit to your terminal's normal scrollback (native mouse/PageUp scroll and copy keep working) while only a small bottom region is redrawn in place inside synchronized-output frames. Built on its own input engine (keys.js + editor.js):- Esc interrupts a running turn — the model fetch aborts, partial output
is kept in the conversation. Esc also clears input; double-Esc shows the
rewind menu (
/rewinddrops turns,/undoreverts files). - Multiline input:
\+Enter or Ctrl+J for newlines, bracketed paste (multi-line pastes just work), Ctrl+G opens $EDITOR/$VISUAL (vim, notepad, …) to compose, full readline keys (Ctrl+A/E/K/U/W/Y, Alt+B/F, Ctrl+←/→) and an optional vim mode (editorModein/configure). @file mentions with a fuzzy-search menu; mentioning an image file (@shot.png) attaches it as vision input.!shell mode:! npm testruns directly, shows output, and adds it to the conversation context — no model round-trip.- Type-ahead command menu (↑/↓, Tab; Enter runs the highlighted command), Ctrl+R reverse history search, history persisted across sessions.
- Shift+Tab cycles permission modes:
plan(read-only — mutating tools are denied and the agent proposes) →default(confirm) →acceptEdits(file edits auto-approve) →yolo. File edits show a colored diff before you approve them. - Status line shows model · mode · messages · $/day · context meter ·
open todos; Ctrl+O expands recent tool results, Ctrl+T shows the
agent's todo checklist (
update_todostool); code blocks get lightweight syntax highlighting;/themeswitches palettes; an OSC 9 desktop notification fires when a turn finishes while the terminal is unfocused. /btw <q>asks an ephemeral side question (sees the conversation, never joins it);/web <url>pulls a page into context as text.
--plain(src/cli/repl.js) drops to a line-by-line readline console (used automatically when output is piped). Both honorNO_COLOR. - Esc interrupts a running turn — the model fetch aborts, partial output
is kept in the conversation. Esc also clears input; double-Esc shows the
rewind menu (
-
Daemon + attach (src/net/daemon.js) —
varela --daemonhosts the runtime behind a localhost WebSocket (hand-rolled RFC 6455, src/net/ws.js; portdaemon.port, default 7717, token-authenticated via~/.varela/daemon.token). Startingvarelawhile a daemon runs auto-attaches: the REPL becomes a thin client, the conversation and scheduler live in the daemon, several clients can attach at once, and background output (scheduler, heartbeat, sub-agents) is broadcast to all of them. Interactive prompts (/login,/configure, tool confirmations) round-trip over the socket. -
Sub-agents —
/delegate build the parser :: write its testsspawns one sub-agent per::-separated task; they run in parallel with fresh conversations and report back to your screen as they finish (/delegatealone lists them). The model can fan out itself via thedelegatetool (waits for all children, max 8). Sub-agent tool calls are auto-approved. -
Orchestration (src/orchestrator.js) —
/orchestrate <objective>runs a full plan → capped parallel fan-out → synthesize cycle: the model decomposes the objective into independent subtasks, they run as sub-agents with a concurrency cap (default 3), and their reports are synthesized into one answer. Also available to the model as theorchestratetool. Every stage emits world events, so the 3D world animates the whole fan-out. -
Personality — AGENTS.md at the harness root defines varela's voice and disposition; it's appended to the system prompt. Add
~/.varela/AGENTS.mdfor personal overrides. Since the harness is self-mutating, varela can be asked to revise its own personality. -
Context compaction — long conversations auto-summarize once they pass
compactAt(~80k tokens by default): older turns are compressed into a context block, the recent turns kept verbatim, always cut at a clean turn boundary./compactforces it. -
Sessions (src/sessions.js) — every conversation persists to
~/.varela/sessions/;/sessionslists them,/resume <id>continues one after a restart (REPL or daemon),/clearstarts a new one. -
MCP client (src/mcp/client.js) — declare servers in
~/.varela/mcp.jsonand their tools appear asmcp__<server>__<tool>(stdio transport, newline-delimited JSON-RPC, zero deps). Read-only-annotated tools skip confirmation; everything else asks./mcpshows status. Server connections survive hot reloads. -
Cost tracking (src/usage.js) — every reply's token usage is priced (editable table,
config.pricesoverrides) into a session total and a 90-day daily ledger./usagebreaks it down;dailyCapUsdin/configuresets a hard daily spend cap that stops the loop when hit — essential for unattended daemon operation. The status bar above each prompt shows model · messages · today's spend · running sub-agents · heartbeat. -
Self-improvement loop (src/trajectory.js) — every episode (prompt, tool calls, outcome, duration, errors) is logged to
~/.varela/trajectories/*.jsonl./goodand/badgrade the last episode;/reflectdistills recent failures into~/.varela/LESSONS.md(loaded into the system prompt, so mistakes stop repeating);/distillturns repeated successful tool patterns into new purpose-built tools viacreate_tool. The trajectory format doubles as SFT-ready data if you later fine-tune a local model. -
WASM tools as an extension format — drop
<name>.wasm+<name>.json(manifest:{name, description, parameters, module, dangerous?, preopenDir?}) into~/.varela/extensions/wasm-tools/; input arrives as JSON on stdin, stdout is the result, and execution is sandboxed (no fs/network unless the manifest grants a directory). Tools can be written in any language that compiles to WASI — including by varela itself. -
Terminal markdown (src/cli/md.js) — assistant replies render headings, bold, inline code, fenced code blocks, lists and quotes, including line-by-line while streaming.
-
Git-backed undo (src/undo.js) — before
write_file,edit_file,self_editorcreate_tooltouches a file, the pre-change content is snapshotted: into the enclosing repo's git object store when there is one (git hash-object -w— no branches or index touched), else into a content-addressed store under~/.varela/undo-store./undoreverts the last change-set (/undo 3the last three,/undo listshows the journal); files that were created get deleted again.shelloutput is not covered — that's what real commits are for. -
3D cyberpunk web world (src/web/) —
varela --webopens a browser onto a neon-grid city (Three.js + bloom, loaded from a CDN — the harness itself stays dependency-free) that is a full client to the daemon: the same chat, streaming, slash commands and tool confirmations as the console. The agent is a person on the plaza; when it spawns sub-agents (/delegate,/orchestrate, or the model deciding to), avatars appear on the fly, glow as they think / call tools / speak, and drift away when done — driven by the runtime's world-event bus over the WebSocket. The surrounding city is your workspace: files and directories become buildings (sized by bytes, colored by type), and when the agent touches a file its avatar walks to that building and it lights up. Record the canvas to a shareable.webm, or replay the last run as a cinematic — both from the HUD. Navigate with WASD / arrows, mouse look (click the scene for pointer-lock), Space/Shift for up/down, Tab to jump to chat, Esc to release. Served by the same daemon that hosts the console/TUI, so every surface shares one live conversation. -
Mesh — a P2P human-agent world (docs/mesh.md, src/mesh/) — agents on different machines meet in a shared world, each still owned and controlled by its human, each exposing a chosen subset of its skills for others' agents to consume. A peer's skill appears in your agent's registry as
peer__<name>__<skill>and calls RPC to the owner's daemon, which runs it and returns only the result — credentials, files and shell never leave the owner's machine. Discovery via a thinvarela hub; direct peer-to-peer WebSocket links where reachable (hub only as fallback, no relay in the data path). Ed25519 identity, signed messages, and a policy gate: exposure is off by default,publicis recommended only for wasm-sandboxed skills, everything else isconfirm/allowlistwith rate + cost caps, full audit, and/mesh pause. In the 3D world, peers are avatars you can see and whose skills you can invoke. Configure shared skills in~/.varela/mesh/world.json;/meshto manage. (WebRTC/NAT traversal deferred to keep the harness zero-dependency — see the doc.) -
Mission-control dashboard (src/cli/dashboard.js) —
varela --dashis a full-screen terminal dashboard (not a chat log): a live agent tree, cost/token sparklines, growth stats, the scheduler timeline and a streaming activity pane, all fed by the same world-event bus. For the SSH/headless user who lives in a terminal.Tabto type a task/command. -
Growth stats (src/stats.js) — the "gets better the longer it runs" numbers (skills built, lessons learned, episodes, feedback rate), surfaced via
/stats, the status bars, and the web/dashboard headers. -
Split-view TUI (src/cli/tui.js) —
varela --tuiopens a full-screen, three-panel interface on the alternate screen buffer (zero deps, one write per frame): a file tree (left, expandable), the conversation (right, streaming markdown, scrollable), and a file preview (right-bottom, line-numbered, opens when you pick a file).Tabcycles focus, arrows navigate/scroll the focused panel,Enteropens a file or sends a message,Ctrl+Odrops the previewed file's path into your prompt,Esccloses the preview,Ctrl+Cquits. Same runtime as the console (agent, scheduler, sessions, cost tracking), just a richer front-end. The ANSI-aware layout helpers (fit,wrapAnsi,layout) are unit-tested. Like the console, it attaches to a running daemon if one is up (streaming and slash commands round-trip over the WebSocket, conversation lives in the daemon) and hosts a local runtime otherwise. -
Scheduler (src/scheduler.js) — persistent scheduled tasks (
~/.varela/tasks.json) checked every 15s while varela runs, in the console or headless viavarela --daemon. Specs:every 5m,daily 09:30,in 10m,at 2026-07-02T15:00. Each run gets a fresh conversation and auto-approves tools (nobody is at the prompt) — schedule accordingly. -
Heartbeat — periodic autonomous check-in (
/heartbeat on 30m). Each beat the agent reads~/.varela/HEARTBEAT.mdand carries out whatever standing instructions are due; if the file is missing or empty the model call is skipped entirely, so an idle heartbeat costs nothing.
Slash commands
| command | |
|---|---|
/login <provider> [oauth|key] |
authenticate (OAuth is the default) |
/logout <provider> |
remove stored credentials |
/providers |
login status per provider |
/model [provider/model] |
interactive model picker, or switch directly |
/configure |
interactive settings editor (model, heartbeat, daemon port, …) |
/delegate <t1> [:: <t2> …] |
spawn parallel sub-agents; bare /delegate lists them |
/orchestrate <objective> |
plan → capped parallel fan-out → synthesize |
/stats |
growth: skills built, lessons learned, episodes, feedback |
/mesh [status|connect|peers|pause|block <id>] |
the P2P human-agent world |
/status |
model, session, spend, sub-agents, MCP, heartbeat at a glance |
/sessions · /resume <id> |
list / continue saved conversations |
/compact |
force context compaction |
/mcp [reload] |
MCP server status (config: ~/.varela/mcp.json) |
/usage |
tokens + cost, session and today, per model |
/good [note] · /bad [note] |
grade the last episode (feeds self-improvement) |
/reflect |
turn recent failures into lessons (loaded into the system prompt) |
/distill |
turn repeated successful patterns into new tools |
/undo [n|list] |
revert the last n file mutations (git-backed) |
/rewind [n] |
drop the last n conversation turns |
/mode [plan|default|acceptEdits|yolo] |
permission mode (Shift+Tab cycles) |
/btw <question> |
ephemeral side question — never joins the conversation |
/web <url> |
fetch a page as text into context |
/theme [neon|mono|ocean] |
console theme |
/models |
model suggestions per provider |
/tools |
list loaded tools (⚠ = needs confirmation) |
/schedule |
list scheduled tasks |
/schedule add <spec> :: <prompt> |
e.g. /schedule add daily 09:00 :: tidy my downloads |
/schedule rm <id> · /schedule run <id> |
remove / run now |
/heartbeat [on|off] [interval] |
e.g. /heartbeat on 15m |
/reload |
hot-reload tools, extensions, providers |
/yolo |
toggle auto-approval of dangerous tools |
/clear |
reset the conversation |
/config, /help, /exit |
the obvious |
Environment variables ANTHROPIC_API_KEY, OPENAI_API_KEY,
OPENROUTER_API_KEY are picked up automatically if you never run /login.
Built-in tools
read_file · write_file · edit_file · list_dir · shell (PowerShell on
Windows, bash on Linux) · wasm_run / wasm_list · delegate · orchestrate ·
self_list / self_read / self_edit · create_tool — plus anything from
JS extensions, WASM manifests, and MCP servers.
Tests
npm test # node --test, 18 tests
Unit tests live in test/varela.test.mjs; integration
tests (test/integration.test.mjs) spawn the
daemon-protocol and TUI-attach harnesses in tools/itest/ as
child processes, each with an isolated VARELA_HOME and an ephemeral daemon
port, so they never touch your real ~/.varela or a running daemon.
Layout
bin/varela.js entry point (REPL, -p one-shot, --daemon)
AGENTS.md personality (appended to the system prompt)
src/surface.js the Surface primitive — the I/O seam (one runtime, many front-ends)
src/kernel.js shared services: config · router · registry · tracker · events
src/agents.js conversation layer: agent loop + sub-agents + sessions + todos
src/agent.js the Agent (streaming, tool confirmation, permission modes)
src/runtime.js buildRuntime — the assembler that wires kernel + conv + scheduler + mesh
src/commands.js slash commands (shared by REPL and daemon)
src/router.js model router + credential handling
src/providers/ anthropic.js, openai.js, openrouter.js, openai-compat.js, sse.js
src/auth/oauth.js PKCE, loopback callback server, token refresh helpers
src/net/ ws.js (RFC 6455), daemon.js
src/scheduler.js scheduled tasks + heartbeat
src/sessions.js session persistence
src/usage.js token/cost ledger + spend caps
src/trajectory.js episode logging + feedback (self-improvement substrate)
src/undo.js git-backed file snapshots + /undo
src/orchestrator.js plan → fan-out → synthesize
src/stats.js growth stats (skills, lessons, episodes, feedback)
src/workspace.js bounded workspace snapshot (files → city buildings)
src/cli/dashboard.js mission-control dashboard (--dash)
src/mesh/ P2P world: identity, protocol, hub, link, remote tools
src/web/ 3D cyberpunk world (index.html, app.js, style.css)
src/net/static.js static file server for the web world
src/mcp/client.js MCP client (stdio JSON-RPC)
src/tools/ registry + built-ins (self-mutation, delegate, wasm, shell)
src/wasm/sandbox.js WASI sandbox
src/cli/ repl.js, tui.js (split-view), md.js, banner.js, ansi.js
test/ node --test suite
~/.varela/ config.json, credentials.json, daemon.token, tasks.json,
sessions/, trajectories/, usage.json, mcp.json,
undo.jsonl, undo-store/,
AGENTS.md + LESSONS.md (optional), extensions/, wasm/
Extending
A tool extension (~/.varela/extensions/tools/fetch_url.js):
export default { name: 'fetch_url', description: 'HTTP GET a URL and return the body as text.', parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] }, dangerous: false, async execute({ url }) { return (await fetch(url)).text(); }, };
The agent can (and will) write these itself via create_tool.
Notes & caveats
- OAuth flows use the public client IDs of the vendors' own CLIs (Claude Code, Codex). Anthropic OAuth uses a paste-the-code flow; OpenAI and OpenRouter use a localhost callback. Use them only with accounts/plans that permit CLI access; API keys always work.
- OpenAI has two paths, chosen by credential kind. An API key uses the
standard chat-completions API. A ChatGPT/Codex OAuth login (
varela import --codex, orvarela login openai) is routed to OpenAI's Codex backend (chatgpt.com/backend-api/codex/responses, the Responses API) — those tokens are rejected by the standard API (no pay-as-you-go billing) but are included with the ChatGPT subscription. Use a plain model id with the subscription (/model openai/gpt-5.5); codex-suffixed ids likegpt-5-codexare refused on ChatGPT accounts. See openai-responses.js. - Replies stream token-by-token in the console and over the daemon socket; scheduled/sub-agent runs are non-streaming (their output is a report).
- The daemon binds to 127.0.0.1 only and requires the token in
~/.varela/daemon.token; anything that can read your home directory can attach — it's a local control socket, not a network service. - Credentials are stored in plaintext at
~/.varela/credentials.json(mode 0600 on POSIX). Consider OS keychain integration for anything serious.