GitHub - ajensenwaud/varela: Minimalistic self-mutating agent harness in zero-dependency Node.js — model router (Claude/OpenAI/OpenRouter), WASM sandbox, hot-reloadable tools, WebSocket daemon, P2P mesh, and a 3D cyberpunk world UI.

16 min read Original article ↗

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 --web here — 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/model strings, manages credentials and token refresh. Built-in providers:

    • anthropic — OAuth (Claude Pro/Max sign-in, PKCE) or API key
    • openai — OAuth (ChatGPT sign-in, PKCE, localhost callback) or API key
    • openrouter — 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/wasm with no filesystem or network access by default. This is where generated/untrusted code should run; the shell tool 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 varela opens 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 (/rewind drops turns, /undo reverts 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 (editorMode in /configure).
    • @ file mentions with a fuzzy-search menu; mentioning an image file (@shot.png) attaches it as vision input.
    • ! shell mode: ! npm test runs 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_todos tool); code blocks get lightweight syntax highlighting; /theme switches 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 honor NO_COLOR.

  • Daemon + attach (src/net/daemon.js) — varela --daemon hosts the runtime behind a localhost WebSocket (hand-rolled RFC 6455, src/net/ws.js; port daemon.port, default 7717, token-authenticated via ~/.varela/daemon.token). Starting varela while 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 tests spawns one sub-agent per ::-separated task; they run in parallel with fresh conversations and report back to your screen as they finish (/delegate alone lists them). The model can fan out itself via the delegate tool (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 the orchestrate tool. Every stage emits world events, so the 3D world animates the whole fan-out.

  • PersonalityAGENTS.md at the harness root defines varela's voice and disposition; it's appended to the system prompt. Add ~/.varela/AGENTS.md for 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. /compact forces it.

  • Sessions (src/sessions.js) — every conversation persists to ~/.varela/sessions/; /sessions lists them, /resume <id> continues one after a restart (REPL or daemon), /clear starts a new one.

  • MCP client (src/mcp/client.js) — declare servers in ~/.varela/mcp.json and their tools appear as mcp__<server>__<tool> (stdio transport, newline-delimited JSON-RPC, zero deps). Read-only-annotated tools skip confirmation; everything else asks. /mcp shows status. Server connections survive hot reloads.

  • Cost tracking (src/usage.js) — every reply's token usage is priced (editable table, config.prices overrides) into a session total and a 90-day daily ledger. /usage breaks it down; dailyCapUsd in /configure sets 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. /good and /bad grade the last episode; /reflect distills recent failures into ~/.varela/LESSONS.md (loaded into the system prompt, so mistakes stop repeating); /distill turns repeated successful tool patterns into new purpose-built tools via create_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_edit or create_tool touches 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. /undo reverts the last change-set (/undo 3 the last three, /undo list shows the journal); files that were created get deleted again. shell output is not covered — that's what real commits are for.

  • 3D cyberpunk web world (src/web/) — varela --web opens 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 thin varela 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, public is recommended only for wasm-sandboxed skills, everything else is confirm/allowlist with 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; /mesh to manage. (WebRTC/NAT traversal deferred to keep the harness zero-dependency — see the doc.)

  • Mission-control dashboard (src/cli/dashboard.js) — varela --dash is 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. Tab to 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 --tui opens 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). Tab cycles focus, arrows navigate/scroll the focused panel, Enter opens a file or sends a message, Ctrl+O drops the previewed file's path into your prompt, Esc closes the preview, Ctrl+C quits. 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 via varela --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.md and 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, or varela 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 like gpt-5-codex are 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.