virtualme
v0.3.1
Published
Virtual Me: private personal background agent. CLI for running the Docker container.
Maintainers
Readme
Virtual Me
Private Personal Background Agent
Your own AI assistant that browses the web for you, on your machine, on your schedule. Give it recurring tasks in plain English and watch it work in a real browser you can take over any time. Everything it sees, types, and remembers stays on your hardware.
Runs on a workstation, Raspberry Pi, or remote host. No AI bills. No GPU required. Your data never leaves your machine.
Your hardware. No cloud. No GPU required.
Chat that drives a real browser.
Watch and use the private browser live.
- Open source — free forever.
- Fully local models — no accounts, no API keys, no prices, no limits; free forever.
- Full local browser use — the agent drives a real Chromium on your machine, not a cloud session.
- Total data locality — no cloud service; chat, browser profiles, and projects never leave your computer.
- All in one container, one port — the whole stack ships as a single Docker image on port 8080.
Not frontier intelligence, but free forever.
Quick start
npx virtualme doctor # check node >= 22 + docker
npx virtualme start # pull + run the container
# open http://localhost:8080Prefer the virtualme CLI. Equivalent Docker with a data directory:
docker run -d --name virtualme -p 8080:8080 \
-v ~/.virtualme:/home/virtualme/.virtualme \
mayanklahiri/virtualme:latestFirst start loads a ~4 GB model; give /healthz a few minutes to go green.
No authentication or TLS yet. Run only on a trusted computer on a private network.
User's Guide
Where everything lives
| Artifact | Location |
|---|---|
| npm package | virtualme |
| Docker image | mayanklahiri/virtualme |
| Source | GitHub |
| Documentation | mayanklahiri.github.io/virtualme |
| Published releases | GitHub Releases |
| CI | Workflow runs · source |
| Release automation | Workflow runs · source |
| Design contracts | specs/ |
CLI commands
| Command | Effect |
|---|---|
| virtualme help | Show usage and every command |
| virtualme version | Print the package version |
| virtualme doctor | Check Node, Docker, daemon access, hooks, CPU, and RAM |
| virtualme start [--data <dir>] [--no-browser-sandbox] [--gpus <spec>] [--no-gpu] [--rebuild] | Run unprivileged with port 8080 and the data dir mounted rw; a detected host NVIDIA stack is passed through automatically (--no-gpu opts out, explicit --gpus <spec> overrides); optionally force Chromium's sandbox fallback; --rebuild builds the image, stops any running container, then starts |
| virtualme stop | Stop and remove the container; the data directory survives |
| virtualme status | Show container state and service health |
| virtualme logs [-f\|--follow] | Show or follow container logs |
| virtualme build | Build :dev and the configured start tag from a checkout |
| virtualme keygen | Generate a 256-bit base64url token |
| virtualme update | Pull the configured image tag |
| virtualme soak [--no-build] | Rebuild once, run the full e2e suite, then run live soak flows on a fresh data dir (source checkout only) |
| ./cli.sh docs dev [--host <host>] [--port <port>] | Serve the documentation site from a source checkout |
| ./cli.sh docs build | Build and verify the static documentation site from a source checkout |
Set VIRTUALME_IMAGE or VIRTUALME_TAG to override the default image reference,
VIRTUALME_DATA to override the default data directory. Runtime settings live
in virtualme.config.yaml; explicitly set legacy TZ and VM_* settings are
still forwarded and override YAML during the two-minor-release migration
window, with a value-free deprecation warning.
Data directory
The host directory ~/.virtualme (override with --data <dir> or
VIRTUALME_DATA) is created on first start and mounted read-write at the
container's ~/.virtualme. It contains valkey/ (chat and speech history,
stats, the reliable job queue, activity ledger, and project records),
chromium/ (browser profile), xdg/{config,cache,data}/, metrics/ (tiered
history), agent/ (agent artifacts and manual dom-dumps/ development
captures), projects/ (per-project scratch space),
tts-cache/ (recomputable synthesized audio), and mail/ (dma config/spool,
bounded flush log, flush marker, and the DKIM private key), plus the mode-0600
virtualme.config.yaml master configuration and mode-0600
controller-lifecycle.json crash/clean marker. Notification history and its
global read state live in the Valkey AOF. Chromium settings,
projects, and queued mail survive container and image replacement. The
container runs as the invoking host uid/gid, so
every data file is host-owned. Everything else is intentionally ephemeral or
baked into the image; the canonical persistence map is
specs/007-persistence-locality.md §1,
as superseded for mail by spec 010 §7.
Ports
| Address | Purpose |
|---|---|
| 8080 (exposed) | Controller SPA, health API, websocket state, and desktop proxy |
| 5900 (internal) | x11vnc |
| 6080 (internal) | noVNC/websockify |
| 6379 (internal) | Valkey |
| 8081 (internal) | llama-server |
| 8082 (internal, loopback only) | Local sherpa-onnx/Piper TTS daemon |
| 9222 (internal, loopback only) | Chromium DevTools observation endpoint |
| Xvfb display :99 | Virtual desktop |
Controller endpoints
| Route | Purpose |
|---|---|
| / | Console home: host and reachable/container addresses, live health/capacity, build version, model, and links |
| /projects | Recurring natural-language tasks, schedules, manual runs, and recent results |
| /projects/<id> | Project task, schedule, status, run history, and scratch-directory details |
| /jobs | Queue, filterable machine activity with durations, and type-specific details in three panes |
| /notifications | Durable assistant/service messages with global unread state, filters, paged history, and structured details |
| /tools | Authoritative agent-tool list plus manual-only development tools (dump_dom), schema-generated forms, queue-backed manual invocation, and structured result rendering |
| /data | Read-only icon/list explorer of $VM_DATA_DIR with sortable size summaries, deep links, a resizable preview pane, and typed viewers |
| /config | Schema-driven master configuration read/edit view with optimistic saves and deliberate service restart |
| /status | Service health, system/GPU meters, LLM token/throughput and browser-action charts, active time selectors, Quick Options (jiggler and scheduler pause), and persistent per-process/GPU metrics |
| /chat | Markdown chat, generation controls, LLM progress, and conversation totals |
| /telegram | Optional Telegram Bot API long-poll status, authorized destinations, test sends, and bounded event diagnostics |
| /speech | Single-voice streaming local speech with seeds, persistent history/replay, and disk cache |
| /mail | Outbound-mail composer, durable outbox with per-message status, queue contents/errors/next-flush timing, queue clearing, and DKIM DNS record |
| /desktop-view | Embedded private noVNC desktop |
| /healthz | Aggregate JSON health for all eight services |
| /ws | Websocket: live state, metrics, notifications, Telegram, queued jobs, activity, tool manifests/invocation, chat, agent, TTS, and mail frames |
| POST /v1/audio/speech | OpenAI-compatible local speech API (wav or raw pcm) |
| GET /api/data/list | Read-only directory listing under $VM_DATA_DIR (path-contained; trust model) |
| GET /api/data/file | Read-only file stream under $VM_DATA_DIR (text capped at 256 KiB unless download=1) |
| GET /api/data/du | Recursive sizes of immediate child directories, cached in Valkey for five minutes |
| GET /api/config/schema | Ordered, documented configuration schema projection with no values |
| GET /api/config | Unresolved raw values, redacted effective state, sources, hashes, and restart plan |
| PUT /api/config | Validate and atomically replace the complete raw configuration using baseHash |
| POST /api/config/restart | Preflight and apply a confirmed pending configuration revision |
| POST /api/config/secrets/refresh | Refresh one configured secret reference and return redacted status |
| /desktop/ | Redirect to the noVNC client; child paths reverse-proxy noVNC and websockify |
History-API routes without file extensions fall back to the embedded SPA; missing asset paths still return 404. Status charts offer synchronized 15m through 30d lookbacks, boundary-aligned locale-aware time ticks, responsive titles and controls, and theme-consistent series colors; every chart downsamples client-side to at most 36 bars, CPU load and memory plus GPU utilization and memory (in GB) draw side by side, and dedicated charts track LLM tokens in/out, effective token throughput, and browser actions by category. The Status top banner carries overall health, the server-local scheduler clock with active selector tokens, and uptime. The branded console has eight themes, each with light and dark variants plus automatic system-scheme selection; the collapsed theme button is in the sidebar footer. Its home page shows hostname, the browser-reachable address, up to two container-interface addresses, uptime, CPU/load, memory, disk capacity, build version, and a detected GPU beside a theme-tinted logo hero image.
The sidebar connection watch shows the controller hostname and port beside a status pip that is green when live, red while reconnecting, and muted while connecting. The text below reports server uptime and the current browser connection duration. Reduced-motion mode disables pulsing without removing the color-coded state.
The Status-page Quick Options panel is a row of fixed-size cockpit-style illuminated buttons with uppercase labels beneath and tooltips on hover/focus (tap a label on touch). The default-on JIGGLER button moves the virtual desktop's OS cursor in short humanlike bursts every 8 to 27 seconds, yields only while the agent holds the input-actuation lock, and records each burst in Jobs activity. The SCHED button is lit while scheduled-job promotion runs; pressing it pauses promotion without touching interactive work. Both states persist in Valkey across reloads and container restarts.
GPU detection is best-effort and multi-vendor. The Status card reports the
first visible NVIDIA, AMD, or Intel GPU and available model/VRAM (in GB)/driver
parameters. NVIDIA and supported AMD sysfs devices also expose utilization and
memory history; presence-only devices do not show an empty chart. GPU absence
is normal and never affects health. NVIDIA passthrough is automatic when
start detects nvidia-smi or Docker's nvidia runtime; use --no-gpu to
opt out. AMD/Intel /dev/dri passthrough is host-specific and outside the
CLI's --gpus option.
Closing Chromium in /desktop-view automatically brings back one blank tab. Chromium uses its namespace sandbox when the host permits unprivileged user namespaces; otherwise it falls back to --no-sandbox with the warning infobar suppressed. Use --no-browser-sandbox to force that fallback when diagnosing host compatibility.
Browser agent
Messages in /chat can ask Virtual Me to operate Chromium. The local model can
observe a screenshot (gridded for its vision only), compact rendered DOM, page
URL/title/text, and system information. Precise CSS extraction, multi-assertion validation,
side-effect-guarded page evaluation, and layout/occlusion debugging provide
additional read-only CDP observations; it acts through xdotool OS
mouse/keyboard input or a bounded bash tool. CDP is method-allowlisted and
never performs input or navigation.
The console shows each tool step and its screenshot; Stop cancels the active
model call or tool process.
The agent estimates prompt size before every model call, supersedes stale
observations, compacts older tool results, and adapts the reply allowance to
the remaining default 32768-token context (up to one quarter). If generation
reaches that limit, the reply ends with
…[response truncated at token limit].
Use /tools to inspect all model-callable tools plus manual-only development
tools such as dump_dom, and invoke them manually. Forms are generated from
the server schemas, calls join the same sequential queue as chat/project work,
and results up to 64 KiB enter the Jobs activity ledger. Results render by
shape: page-shaped JSON becomes a linked title plus plain text, KEY=value
runs become sorted tables, manual screenshots omit the agent's coordinate
grid, and read_page YAML renders as a collapsible tree. At the default model
context, model-facing read_page YAML is capped at 24576 bytes (the cap scales
with context), treats layout tables as containers so links survive. Real data tables retain
structured links. This can invoke bash and browser actuation;
under the current trust model it has no additional authentication, so expose the
console only on a trusted private network.
Use /data to browse the persistent volume without docker exec. Its
full-height explorer provides icon and list views, sortable columns, recursive
size bars, clickable breadcrumbs, ?path= deep links, a remembered resizable
preview pane, and a full-screen mobile preview. Typed viewers handle image
lightboxes, JSON/YAML trees, JSONL rows, WAV playback, and capped text; every
file has a raw download. The root UI omits internal chromium, mail,
metrics, valkey, and xdg directories, but direct deep links and the
strictly path-contained read-only API expose the whole volume by design under
the current trust model.
All LLM work runs through a Valkey-backed sequential queue with interactive
priority, retries, visibility recovery, and a dead-letter list. Chat messages
wait instead of returning a busy error. Closing the initiating browser tab
cancels its running generation immediately and drops its queued jobs. The CLI
passes the host TZ into the container so time-bucket scheduling uses the
host's local wall clock.
Projects
Use /projects to create recurring agent tasks in plain language. Each project
has a coarse day/time selector, an Enabled switch, a manual Run now action, and
five recent run summaries. Scheduled runs persist through Valkey and execute
sequentially with chat work. A manual run is tied to its initiating browser tab
and is cancelled if that tab closes; scheduled runs are not.
Project scratch files live under ~/.virtualme/projects/<id>/. Deleting a
project removes its record and run summaries but deliberately preserves that
directory and its operator data.
Jobs
Use /jobs to see what the machine is actually doing. The queue card groups
rows into Running now (at most one; only that row pulses), Up next, and
Recently finished, each row led by a short type-derived name, kind pill, and an
explicit status icon (check, error, or spinner). The newest-first activity
ledger records each LLM generation, agent tool call, speech synthesis, and
mail submission with its run time; selecting a row opens its payload, result,
timing, and type-specific details in a third pane at desktop widths. Tool
calls and jiggler bursts are hidden by default behind persisted filter
toggles. The bounded ledger persists in Valkey across container restarts.
Notifications
The sidebar bell and /notifications page show the newest 500 durable
assistant and local-service messages. Unread state is global: reading in one
connected tab updates every tab, and state survives reconnects, controller
restarts, container stops, and image replacement. The page provides server
type/read filters, 50-row websocket paging, deep-linked structured details,
and a mobile detail slide-over. The agent's notify tool creates a durable
message without adding a second activity record.
Graceful shutdown records a clean lifecycle notification. A forced controller
kill is detected from controller-lifecycle.json and produces one idempotent
unclean-startup message; deliberate configuration restarts produce explicit
shutdown/startup messages instead. Notification protocol is websocket-only;
there is no notification HTTP API.
Telegram
Telegram is optional and disabled by default. Configure
integrations.telegram on /config with a BotFather token held only through
an ${env:...} or ${file:/absolute/path} secret reference, plus at least one
canonical decimal allowedChatIds entry. Authorization is the chat allowlist
AND the optional user allowlist; leaving allowedUserIds empty permits every
human sender in an allowed chat.
The controller uses outbound HTTPS long polling against the Telegram Bot API:
there is no webhook, inbound port, SDK, or sidecar. Authorized text joins the
same persistent conversation as /chat; final answers route only to the
originating Telegram chat. /telegram shows bot/poller state, authorized test
destinations, and a bounded event log. Telegram is an external cloud boundary:
authorized text, replies, chat actions, and Bot API metadata leave the host.
Group privacy mode is managed with
BotFather.
The next update offset and staged ingress record survive restart, so a
partially accepted update repairs to one history message, query statistic, and
queued job. Refreshing the configured secret cancels the active long poll,
authenticates a new client generation, and preserves offsets and diagnostics.
Local speech
The /speech tab streams sentence-level audio from the fully local
sherpa-onnx engine with the baked
Piper Lessac (en-US) voice. Seed texts (including a Sci-fi AI medley of famous
fictional computer lines) fill the editor, and the global newest-first history
persists in Valkey with cached replay. The browser starts playing after the
first sentence while later sentences synthesize. Agent chat can use the
speak tool when asked for an audible response; its audio bubble supports
replay.
OpenAI-compatible clients can call POST /v1/audio/speech; wav is the
default response format and pcm returns raw 16-bit mono PCM. Any voice
value other than en_US-lessac-medium falls back to Lessac. Exact
sentence/voice/speed renders are cached under ~/.virtualme/tts-cache/;
VM_TTS_CACHE_MAX_MB sets the LRU cap (default 256 MiB), and deleting the
cache is safe.
Master configuration
First start creates ~/.virtualme/virtualme.config.yaml from the embedded
schema, with stable schema-derived comments and mode 0600. Use /config for
schema-driven editing, or edit the YAML while the container is stopped.
Configuration is strict: unknown keys, unsupported YAML syntax, invalid types,
malformed references, and semantic conflicts stop startup before long-running
services. An unavailable enabled Telegram token is retained as redacted
secret_unavailable state so the controller and Config UI remain recoverable;
Telegram stays disconnected until refresh succeeds. Saving rewrites the
complete canonical file; Save and Restart are separate operations.
Precedence is explicit legacy environment override, then YAML, then schema
default. Secret fields accept only empty values or whole ${env:NAME} and
${file:/absolute/path} references. Secret files must be regular, at most
64 KiB, and grant no group/other permissions. Raw API state retains references;
effective API state replaces secret values with null. The deterministic
reference at docs/src/generated/config-reference.json is generated from the
same schema with configctl docs.
Outbound mail
The /mail tab submits standards-compliant multipart mail through the
unprivileged dma queue. Messages can include
a generated inline CID image. Delivery is direct to recipient MX hosts by
default, or through a configured STARTTLS smarthost. Optional controller-side
DKIM signing exposes the DNS TXT name and value to publish in the status panel.
Expandable queue rows show the envelope recipient, subject, age, plain-text
preview, attachment types/sizes, newest recorded delivery error, and a live
countdown to the next queue flush; a confirmed Clear queue control removes all
spooled mail. The countdown is not a delivery guarantee: dma may apply its own
retry backoff. A durable Valkey outbox tracks each submission as queued, left
queue (dma cannot distinguish delivered from bounced), error, or cleared.
| Deprecated environment override | Master configuration path |
|---|---|
| VM_MAIL_MAILNAME | mail.mailname |
| VM_MAIL_FROM | mail.from |
| VM_MAIL_SMARTHOST | mail.smarthost.host |
| VM_MAIL_SMARTHOST_PORT | mail.smarthost.port |
| VM_MAIL_SMARTHOST_USER | mail.smarthost.username |
| VM_MAIL_SMARTHOST_PASS | mail.smarthost.password (literal legacy adapter; migrate to a reference) |
| VM_MAIL_DKIM_DOMAIN | mail.dkimDomain |
| VM_MAIL_DKIM_SELECTOR | mail.dkimSelector |
| VM_MAIL_FLUSH_SEC | mail.flushSeconds |
Prefer /config or YAML; the CLI temporarily forwards explicitly set legacy
variables. Direct delivery needs SPF and/or DKIM alignment plus acceptable IP
and PTR reputation. Residential/dynamic IP mail is usually rejected, including
by Gmail, so use a reputable smarthost for reliable delivery.
Task screenshots and JSONL step logs are retained under
~/.virtualme/agent/<taskId>/ for the most recent 20 tasks. CPU-only vision can
take tens of seconds per step. With NVIDIA passthrough (automatic detection,
explicit --gpus <spec>, or VM_LLAMA_GPU=1), the container's pinned Vulkan
llama.cpp build runs the model fully offloaded to the GPU; without it, the
CPU build is used. On startup the llama service logs its enumerated devices
(svc-llama: ... Vulkan0: ...); an empty list means the Vulkan driver failed
to initialize and inference is running on the CPU.
AI skills
| Skill | Path | Use |
|---|---|---|
| operate | .cursor/skills/operate/SKILL.md | Install, run, and troubleshoot |
| develop | .cursor/skills/develop/SKILL.md | Follow contribution rules and patterns |
| do-release | .cursor/skills/do-release/SKILL.md | Execute the full npm and Docker release |
| master-update | .cursor/skills/master-update/SKILL.md | Audit docs against the tree; refresh docs/src/screenshots/ from live :8080 and rewire README image markers |
Claude Code discovers these through the .claude/skills symlink; Codex reads AGENTS.md.
After changing anything structural, run the /master-update skill — it re-syncs this README, AGENTS.md, and all skills against the repo.
Specifications
| Spec | Purpose |
|---|---|
| 001 | Constitution, CLI, gates, CI/CD, and docs |
| 002 | Docker image and controller stub |
| 003 | Controller, UI, assets, and end-to-end tests |
| 004 | Immutable release versions, registry pre-checks, and GitHub Releases |
| 005 | Multi-page console, tiered metrics, themes, markdown chat, and LLM status |
| 006 | Reliable Chromium supervision, sandbox fallback, and profile persistence |
| 007 | Persistence grounding and deterministic LLM-locality gate |
| 008 | OS-level browser-control agent |
| 009 | Local sherpa-onnx/Piper speech synthesis |
| 010 | Outbound dma queue, MIME/DKIM, and Mail console |
| 011 | Console brand, collapsed theme picker, eight themes, and live-capacity home page |
| 012 | Dense DOM observations, settled navigation, desktop coverage, Playwright-layer removal, and the live soak suite |
| 013 | Valkey job queue, time-bucket scheduler, and initiator-bound cancellation |
| 014 | Projects: periodic natural-language tasks with schedules and scratch dirs |
| 015 | Jobs page: activity ledger and queue peek with details pane |
| 016 | Chromium determinism flags and single full-screen window |
| 017 | Jiggler: humanlike OS-level mouse motion with a Status switch |
| 018 | Multi-vendor GPU detection, status widget, and usage series |
| 019 | Chart ticks, titles, lookback control, and uniform series color |
| 020 | Speech seeds/history, TTS disk cache, and audio hygiene |
| 021 | CDP observation tools and the Tools console page |
| 022 | On-disk embedded system prompts and SLM-optimized rewrite |
| 023 | Mail queue transparency: contents, errors, and retry timing |
| 024 | Brand wordmark, wristwatch live indicator, and console polish |
| 025 | Marvin release notes, registry metadata, and the /do-release skill |
| 026 | Console bugfix sweep: chat, speech, charts, jobs, mail, tools, screenshots |
| 027 | Structured YAML read_page digest, tree UI, and tool-testing soak |
| 028 | Read-only Data explorer tab and /api/data/* volume API |
| 029 | read_page DOM goldens, layout-table fidelity, 32K context, and proportionate caps |
| 030 | Static Astro documentation/marketing site, shared themes, local docs CLI, and orphan-branch Pages publication |
| 031 | Master configuration schema, strict YAML loader, preflight, console, restart flow, and generated reference |
| 032 | Durable assistant notifications, global read state, lifecycle markers, agent tool, and console UI |
| 033 | Authorized Telegram Bot API long-poll ingress, correlated shared-chat delivery, status console, and diagnostics |
| 034 | Preflight agent context budgeting, scaled observations, adaptive completions, and graduated recovery |
CI/CD
| Workflow | Trigger | Purpose and secrets |
|---|---|---|
| CI (source) | Push to main; pull request | Node 22/24 gates, container smoke test, and the CLI-driven E2E test (including the restart cycle and chat probe); no secrets |
| Release (source) | Tag v* | Registry immutability and committed-notes pre-checks; native amd64/arm64 Docker publishing; Docker Hub overview refresh; npm publishing; curated GitHub Release notes plus generated commits; DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, NPM_TOKEN |
| Docs (source) | Docs-related pull requests and pushes to main | Builds and browser-tests with read permission; main pushes rebuild and publish branch-root output to the orphan docs branch with write permission |
Development setup
git clone https://github.com/mayanklahiri/virtualme.git
cd virtualme
npm install
npm ci --prefix docs
git config core.hooksPath .githooks
bash controller/tools/fetch-assets.sh
npm run check
./cli.sh build
./cli.sh startnpm run check builds the minified SPA (controller/web/dist/, gitignored),
checks generated shared-theme outputs, and builds the isolated docs package
offline before the Go gates. Run npm run build:web to rebuild the SPA alone,
or ./cli.sh docs dev / ./cli.sh docs build for the site. Astro and
Playwright remain exact-pinned only in docs/package.json; browser setup is
explicit: npm --prefix docs exec playwright install chromium.
The eight controller/docs themes are authored once in
common/themes/themes.json and checked through
scripts/generate-themes.mjs --check. Site analytics is disabled by default;
its sole edit point is docs/src/config/site.ts.
After the docs workflow first publishes, configure GitHub Pages once to deploy
from branch docs, folder / (root). The production URL is
https://mayanklahiri.github.io/virtualme/.
Offline read_page golden tests discover test/fixtures/*.dom.json, execute
the production extractor, evaluate optional *.props.mjs assertions, and
byte-compare *.digest.golden.yaml snapshots. Set REGEN_GOLDENS=1 to
refresh snapshots. To capture a live fixture, navigate the running browser and
run node test/capture-dom.mjs <fixture-name> against a healthy :8080.
Release runbook
Requires the spec 002+ container scaffold, including docker/Dockerfile. Run
the /do-release skill for the full preflight, version, notes, commit, tag,
push, monitoring, and verification procedure.
Every tag requires a committed, non-empty release-notes/vX.Y.Z.md. The
release workflow verifies that the version is unused and the notes exist,
builds amd64 and arm64 images on native runners, merges the multi-arch
manifest, refreshes the Docker Hub overview from docker/DOCKERHUB.md,
publishes npm, and creates a GitHub Release with the curated notes followed by
the generated commit list.
DOCKERHUB_TOKEN must be a Docker Hub password or PAT with repository write
scope. A read-only token makes the overview PATCH fail with HTTP 403 and fails
the release job.
Version tags are write-once on Docker Hub (X.Y.Z, X.Y.Z-amd64, and X.Y.Z-arm64) and npm (X.Y.Z); only Docker's latest tag moves. If an attempt fails after publishing any versioned artifact, re-run the failed jobs in that same Actions run or fix the problem, bump the patch version, and create a new tag. Never delete and re-push a release tag: even an orphaned per-architecture Docker tag permanently retires that version number.
Hardware
Use a Raspberry Pi 5 or Raspberry Pi 4 with 8 GB RAM at minimum. The RAM floor is 8 GB; Gemma 4 E2B Q4_0 is approximately 3 GB on disk and 4 GB resident.
Architecture
The container has s6-supervised Xvfb, openbox, x11vnc, noVNC, Chromium, Valkey, vision-enabled llama.cpp with Gemma 4 E2B and a default 32768-token context (baked CPU and Vulkan GPU runtimes; the service picks one at startup), local sherpa-onnx/Piper TTS, a dma outbound-mail queue, and a Go controller on :8080, running unprivileged (host uid/gid) with one rw data mount. The controller concurrently probes service health, detects and samples visible NVIDIA/AMD/Intel GPUs, samples and persists metrics, records machine activity, composes and DKIM-signs mail, schedules and sequentially executes reliable Valkey jobs and recurring projects, streams shared chat and speech, produces default-on ambient OS-level mouse motion, and runs a bounded browser-agent loop combining screenshots, compact DOM/read-only CDP observations, OS-level xdotool actions, bash, and audible responses. It proxies noVNC and embeds the same-origin minified multi-page SPA. See specs/ for the authoritative architecture and implementation contracts.
The model's system prompts are plain text in controller/prompts/ (spec 022).
