A local-first desktop coding companion that works in your repo and remembers how you work.
Roro turns a typed task into a real coding-agent run against the project you choose: it recalls your local preferences, asks a local Ollama brain to plan the turn, streams Codex or Claude through one action timeline, and keeps useful lessons for next time. The pixel cat makes the loop visible, but the job is coding help with continuity.
The v0 promise is intentionally narrow: local-first coding help that gets better because it remembers how you work.
ask -> recall memory (local) -> think on local Ollama -> run Codex or Claude -> remember the result (local)
Privacy promise: Roro's default brain, recall, embeddings, and memory run on
your machine with local Ollama and local storage. Memory contents are encrypted
at rest by default with the OS keychain, and Roro fails loud instead of silently
saving plaintext if that keychain is unavailable. Roro itself does not require
accounts, app-owned telemetry, or a cloud-model key. Coding turns still run
through your own local executor CLI: install and sign in to Codex or Claude
outside Roro first, or point Roro at the binary with RORO_CODEX_BIN /
RORO_CLAUDE_BIN.
See FOUNDING.md for the identity, locked invariants, and strategy of
record (read it first), PUBLIC.md for the path to public readiness, and
HANDOFF.md for current engineering truth.
Most coding agents still feel like command-line tools with better autocomplete. Roro makes the agent loop visible and social:
- the cat changes posture when the system is listening, thinking, working, done, or stuck
- the brain's decide phase appears as the "thinking" layer
- executor output becomes a normalized action timeline
- local memory recall is surfaced as a visible memory beat
- optional screen reads show a visible "Taking one screen snapshot." beat before capture
- the same character layer can run inside the full app or as a transparent floating desktop agent
The intended demo is simple: ask the character to fix a bug, watch it plan, watch it run the coding agent, then ask what it remembers from the earlier turn.
The default character is a procedural 16-bit tuxedo cat drawn with PixiJS in
src/renderer/character/avatar.ts. It does
not need image assets or model files.
The base cat uses a tight four-color palette: black body, white tuxedo and paws, yellow eyes, and gray inner ears. State effects deliberately expand that palette: cyan for listening and talking, gold for thinking, blue for work motion, green and gold for success, and red/orange for errors.
| State | Behavior |
|---|---|
idle |
breathes, blinks, twitches ears; floating mode slowly cycles stand/sit/walk |
listening |
perks up and shows signal pixels |
thinking |
sits, looks upward, shows thought pixels |
working |
walks with a leg cycle and turns before reversing direction |
done |
shows success sparkles |
error |
flattens ears and shows alert pixels |
| talking layer | opens the mouth and adds signal pixels while speech is active |
The renderer talks to a character-agnostic CharacterDriver
(setState, setMouthOpen, setTalking, speak), so a different character
implementation can be mounted later behind the same interface.
flowchart LR
user["User task<br/>text by default"] --> renderer["Renderer<br/>cat, captions, controls"]
renderer --> main["Electron main<br/>typed IPC"]
main --> memoryRecall["memory2 recall<br/>local index over encrypted files"]
memoryRecall --> brain["Local Ollama brain<br/>qwen2.5 decision"]
brain --> executor["Executor adapter<br/>Codex or Claude"]
executor --> events["Canonical action events"]
events --> renderer
events --> memoryWrite["memory2 remember (local)"]
brain --> vision["Local vision<br/>optional screen read"]
| Subsystem | Role | Source |
|---|---|---|
| Electron shell | windowing, IPC, macOS permission checks, floating mode | src/main/ |
| Character | pixel cat, state machine, lip sync facade | src/renderer/character/ |
| Brain | local Ollama reasoning/vision/embeddings | src/core/brain/ |
| Memory | encrypted files-as-truth + in-memory vector index (local, owner-scoped) | src/core/memory2/ |
| Executor | Codex and Claude stream adapters | src/core/executor/ |
| Voice | internal on-device VAD + STT + TTS seam (Silero / whisper / Kokoro), hidden behind dev flags and cut from v0 | src/renderer/voice/ |
| Shared contracts | typed IPC, action events, avatar states | src/shared/ |
The important boundary is the canonical action-event vocabulary in
src/shared/events.ts. The renderer does not parse raw
Codex or Claude output; it only reacts to normalized events like command,
file_change, message, run.completed, and run.failed.
# 1. Start the local brain (Ollama) and pull the core models:
ollama serve
ollama pull qwen2.5:3b # reasoning
ollama pull nomic-embed-text # embeddings (768-dim)
# Optional later, for screen understanding:
ollama pull qwen2.5vl:7b # vision
# 2. Run the app:
npm install
npm startIn a packaged build, Roro asks you to choose the project folder it should work
on, stores that choice in userData/config.json, and reuses it after relaunch.
The working repo resolves from the persisted in-app choice first, then the
RORO_WORKDIR env var, then the explicit RORO_ALLOW_CWD=1 cwd fallback (dev).
If Roro says no working repo is set, choose a project in Settings or relaunch
with RORO_WORKDIR=/absolute/path/to/repo npm start; blank or stale paths are
treated as unset so the executor never runs against a guessed directory.
On boot Roro runs a non-blocking brain preflight; if Ollama is down or a model is
missing, the window still opens and a clear diagnostic appears (it never silently
falls back to the cloud). See
docs/WS1-OLLAMA-INTEGRATION-TEST.md.
Roro launches as the floating desktop agent by default:
npm startThe floating mode opens a transparent, frameless 360x420 click-through window with
the cat resting bottom-left — the empty area passes clicks straight through to
whatever is underneath. At rest it is just the cat: the input, the working chip,
and error cards are transient reactions tethered beside the cat, summoned by
⌘⇧Space or a hover-revealed handle. Setup banners appear only when action is needed.
Tap or hold the cat to pet it; drag to move it. (The on-device voice stack —
including the mute/M/right-click path — now lives in packages/voice; there is no
mute control in the current app, and a future voice reintegration restores it.) The cat's body
carries only affection + move — talk and tasking live off the body (see the
current interaction contract at docs/INTERACTION.md).
The floating window stays above normal windows and across macOS Spaces,
including full-screen apps. The summoned input bubble is the compact task surface;
set RORO_FLOATING_WINDOW=0 npm start for the legacy full dev window with the
full prompt, controls, captions, memory panel, and action timeline visible.
Roro's default brain and memory paths need no app-owned cloud/model keys:
Ollama runs locally, memory is a local encrypted files-as-truth store, and packaged builds
store the chosen working repo in userData/config.json. A local .env (see
.env.example) is for development overrides, model tuning, and
optional cloud/executor paths:
# Brain provider: 'ollama' is the only supported value (local-first; anything else fails loud).
BRAIN_PROVIDER=ollama
OLLAMA_HOST=http://127.0.0.1:11434
OLLAMA_MODEL=qwen2.5:3b
OLLAMA_VISION_MODEL=qwen2.5vl:7b
OLLAMA_EMBED_MODEL=nomic-embed-text
OLLAMA_EMBED_DIM=768 # set this if OLLAMA_EMBED_MODEL is not 768-dim
# Optional dev fallback (a project saved in-app wins over this). Packaged
# first-run uses the native Choose Project flow.
RORO_WORKDIR=/absolute/path/to/scratch-git-repo
ANTHROPIC_API_KEY=... # optional, only for the Claude executorMemory is a local encrypted files-as-truth store under the app's userData dir (RORO_DB_DIR to
override) — no external database. See RUN.md for the on-device voice
dev flags, macOS permissions, and the full live-run checklist.
Migrating from an older checkout: the internal env prefix was renamed
COMPANION_*→RORO_*(andVITE_COMPANION_FLOATING_WINDOW→VITE_RORO_FLOATING_WINDOW). The old names still work for now (a deprecation warning is logged); update your.env/scripts at your convenience.
npx tsc --noEmit -p tsconfig.json
npx vitest run --no-file-parallelism
npm run verify:floating # on-screen smoke for the floating Ask (needs a display)
npm run verify:floating-geometry # default transparent 360x420 click-through shell, cat bottom-left
npm run release:doctor # CI-safe release/signing doctor for the unsigned/ad-hoc path
npm run package
npm run verify:release-artifact # after npm run package, verifies the packaged .app shape
npm run package:release
npm run verify:release-channel # release-channel build refuses deferred-v0 flags
npm run verify:packaged-memory # packaged memory write -> quit -> relaunch -> recall smoke
npm run verify:packaged-memory-health # packaged Keychain/memory-paused UX stays non-blocking
npm run verify:packaged-live-memory-turn # packaged relaunch -> live Ollama turn uses recalled memory
npm run verify:packaged-natural-memory-turn # packaged natural-language teach -> relaunch -> recall turn
npm run verify:packaged-onboarding
npm run verify:packaged-model-setup # packaged Ollama-down/Recheck -> one-click local model pull
npm run verify:packaged-first-task # packaged persisted project -> first typed coding task writes a file
npm run verify:packaged-real-codex # opt-in: same packaged path with your real Codex CLI
npm run verify:memory-steered # opt-in proof that recalled memory reaches DECIDE and args.task
# Developer-ID release path, after exporting APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID:
npm run verify:signing-readiness # strict Developer-ID env/cert/tool doctor before npm run make
npm run verify:signing-auth # checks Apple credential auth with notarytool history
npm run make
npm run verify:release-artifact:dmg # after npm run make, verifies the mounted DMG contains Roro.app
npm run verify:release-artifact:signed # after Developer-ID npm run makeWhat is working:
- Electron app builds and packages
- typed IPC between renderer and main
- procedural pixel cat, transparent floating mode, and state effects
- packaged workdir onboarding: native project picker, persisted
userData/config.json, and typed/floating Ask gates - packaged same-build memory persistence smoke: a real packaged app writes and recalls an observation across relaunch
- packaged live-memory turn smoke: with local Ollama ready, a real packaged turn narrates a recalled value after relaunch
- packaged natural-memory turn smoke: with local Ollama ready, a real packaged turn learns a stated preference, relaunches, and uses it
- packaged model-setup smoke: a real packaged app guides Ollama-down Recheck into missing local models, then through the public Download button and flips ready
- packaged first-task smoke: a real packaged app with persisted project config verifies the public executor-readiness bridge, then runs a typed coding task through public
turnRunand Codex event handling - packaged real-Codex smoke: the packaged first-task path can discover and run the user's authenticated Codex CLI without the fake executor override
- release/signing doctor: CI checks the unsigned path, and a strict local doctor fails loud before Developer-ID
make - signing auth doctor: with Apple env set, verifies
notarytoolcredentials before uploading a build - DMG release artifact: macOS CI builds a versioned
.dmgand verifies it mounts withRoro.app - local Ollama brain (decide/vision/embeddings) — verified end-to-end against a live daemon
- local encrypted files-as-truth memory (owner-scoped, survives restarts)
- Codex and Claude executor adapters behind one event stream
- typed text path + the on-device voice control core/seam hidden behind dev flags
What needs extra setup or a real device:
- on-device voice (Silero VAD + whisper STT + Kokoro TTS) behind internal
RORO_*_VOICEdev flags + microphone permission — fully local, no cloud/model keys - screen capture permission for vision (the 7B vision model needs substantial RAM)
src/main/ Electron main process and orchestration
src/renderer/ UI, character, voice, captions, event wiring
src/core/brain/ local Ollama decision, vision, embeddings
src/core/memory2/ local encrypted files-as-truth + in-memory vector index
src/core/executor/ Codex and Claude adapters
src/shared/ IPC, event, memory, avatar, env contracts
RUN.md live setup and integration guide
