NAVI // AGENTS.MD

how we build navi.

The project's shared brain — conventions, release process, and hard-won rules — written for agents and humans alike. Read it and hit the ground running.

Mirrored from AGENTS.md on the eiri rolling branch. The repo copy is the source of truth.

Read this first. This file is the project's shared brain: everything an agent or a human needs to hit the ground running on navi development. It's maintained by the people (and agents) who do the work — when you learn something the hard way, add it here so the next one doesn't have to.

What navi is

navi is xvoidsx's Linux distro: a complete, opinionated, agent-native desktop OS on a dependable Debian (trixie) base. Wayland-only (sway, our "wired" session) since 2026-10-08 — the X11 session (i3) was retired; even the 2008 ThinkPad x200 renders sway fine, so the compat argument was dead. XWayland covers X11 apps. The i3/picom/polybar configs stay in the repo as the port-back kit. Deep blacks, neon pink, phosphor green — the nightshadeNeon design language across the whole desktop.

Guiding ideas:

Who you're working with

Repo map

navi/
├── install.sh            # installer AND deploy script (--deploy-only)
├── wired/                # the desktop layer — see "The /wired boundary"
│   ├── VERSION             # <-- source of truth for the version string
│   ├── sway/ i3/           # window manager configs (the "wired" sessions)
│   ├── waybar/             # bar config, style.css (GTK CSS!), modules,
│   │                       # mod-open.sh (the floating launcher)
│   ├── identity/           # os-release, lsb-release, issue, issue.net
│   │                       # (re-stamped from VERSION at every deploy)
│   ├── naviApps/           # the app store: apps.json + build-catalog.sh
│   │                       # → catalog.js (inlined; never fetch() it)
│   ├── scripts/            # navi-notifs, helpers
│   ├── manual/             # built-in manual (Super+Shift+H); mirrored on site
│   ├── applications/       # .desktop launchers incl. rofi-visible entries
│   └── ...                 # per-app configs: rofi, alacritty, dunst, tmux…
├── scripts/                # system-level scripts (NOT the desktop layer)
│   ├── navi-update.sh        # self-updater: pulls release tags, redeploys
│   ├── navi-wired-restore.sh # drift diff / restore / backup vs stock
│   ├── navi-wired-adopt.sh   # lay stock wired configs over customized ones
│   └── installers/           # codex, antigravity, etc.
├── mods/                # Go mods (agent center, waybar modules, browser
│                           # picker, …) — prebuilt binaries committed
├── iso/
│   ├── stage.sh            # stages /opt/navi-iso for the ISO build
│   ├── installer/          # the CLI installer (preflight lives here)
│   ├── config/             # iso.yml inputs: RELEASE, ISO_BASENAME…
│   └── README.md
├── .github/workflows/
│   ├── iso.yml             # builds the ISO; RELEASE CHECKLIST lives here
│   └── publish-stable.yml  # publishes tag → GitHub release + assets
├── src-navi/ src-wiredWM/  # source trees (see RESTRUCTURE.md)
├── screenshots/
└── _sources/

The /wired boundary

/wired is the desktop layer only. Distro-level concerns (base system, users, doas/sudo, services, firewall) live elsewhere in the repo. Never move system-level pieces into /wired, and never reach out of it for them.

Source of truth: wired/VERSION

The version string (e.g. 2.0 "eiri" on the dev branch, 1.7 "mika" on stable) lives in wired/VERSION. Everything human-facing is derived from it at deploy time:

Rule: iso.yml carries a RELEASE CHECKLIST (including a POST-RELEASE section). Follow it every release. It exists because every item on it was once a shipped bug.

Browser strategy (Raven's call, 2026-09-29)

Brave is navi's default browser — full Brave, not Origin. Stock Chromium stays installed out of the box as the vanilla alternative. Firefox ESR ships too. The OS suggests, the user decides.

Out-of-the-box apps: navi-imprint (replaces balenaEtcher, 2026-10-14)

navi-imprint is xvoidsx's native nightshadeNeon ISO-flashing tool (Go/bubbletea) — flash OS images to SD cards/USB drives from the terminal. It replaced balenaEtcher, which was dropped 2026-10-01 when its balena-etcher-electron package hard-depended on gconf2 (removed from Debian trixie entirely — apt refuses the install). The old setup_etcher code paths in install.sh are retired, not just commented out.

Matrix clients (Raven's call, 2026-11-03)

install.sh — installer and deployer

install.sh does double duty: fresh installs and redeploys onto a live system via --deploy-only (this is what navi-update runs after pulling).

The self-maintenance layer

Three commands, three jobs. Don't confuse them:

navi mods always float (standing rule)

Panel modules (notification bell, update indicator, …) open floating Alacritty TUIs. Launchers enforce floating themselves — never rely on the shipped sway config's for_window rules (customized configs are preserved, so the rule may not exist) and never sleep-then-float (a race slow machines lose).

The convention is wired/waybar/mod-open.sh <title> <cmd…>:

  1. Probe, don't guess, the compositor. Real swaymsg -t get_version round-trip (then i3-msg -t get_version); never trust $SWAYSOCK alone.
  2. Install a runtime for_window rule BEFORE the window maps — no race.
  3. Poll-and-float by PID as the backstop. Titles are mutable (TUIs rename themselves); PIDs aren't. Anchor the match so PID 1234 can't match 12345.
  4. Log one line per launch to ~/.local/share/navi/mod-open.log — future misbehavior must leave footprints.
  5. If there's no compositor at all, just run the command (plain-open path).

The agent mod (custom/agent)

The little robot face on the panel — navi's agent-status integration. wired/waybar/agent-mod.sh polls every 5s and emits waybar JSON with one of five classes; wired/waybar/agent-menu.sh is the right-click rofi dispatcher.

State priority (first match wins): agent-blocked (a herdr agent waits on the user) → agent-listening (Hey Lain mic hot via $XDG_RUNTIME_DIR/hey-lain/rec.pid, or thinking via processing.lock) → agent-working → agent-ready (agents idle, or ollama reachable) → agent-asleep. Herdr states come from herdr agent list (idle/working/blocked/done/unknown); the server-down shape is usually JSON with an error key and exit code 0, so parse the body — but some versions fail the call outright when the server is down, so the center checks the install (LookPath) separately from the server and reports "isn't running" instead of "isn't installed".

Click contract: left → the agent center (navi-agents-config, floating), middle → herdr attach (floating), right → the rofi menu, which leads with blocked agents ("⚠ <agent> needs you — jump to herdr") when any are blocked.

The center's tabs: status (live herdr agents with waybar-style faces, enter jumps to one via herdr agent attach addressed at the pane id — herdr doesn't resolve bare agent labels, so the label is display-only; r reads recent output the same way), models (ollama service on/off via doas, pull/delete models), harnesses (installed harnesses, default agent — Enter on an installed harness writes the file; also what navi-Q opens), autonomy (opencode permission profiles - ask me / balanced / yolo - written to ~/.config/opencode/opencode.json, preserving other keys; other harnesses keep their own settings for now), evals (model compare: an 8-prompt built-in set run against selected pulled ollama models, each response scored 1-5 by a local judge model, scoreboard with averages and a winner), providers (API keys). The default agent is stored in ~/.config/navi/default-agent (whitelisted to known harness binaries); navi-Q/navi-Qx read it, falling back to hermes (Lain) via navi-lain, then opencode — and the center's harnesses tab shows the same default (pink ♥), so the marker and Alt+A never disagree.

Scope is honest by design: herdr only sees its own panes, so bare-terminal agents are invisible — tooltips say "in herdr" and never claim global awareness. CSS lives in wired/waybar/style.css (#custom-agent + state classes; GTK CSS rules apply). Customized waybar configs are preserved by navi-update, so users on customized configs need navi-wired-adopt (or a manual module add) to see new modules.

Mods ship as prebuilt binaries

navi mods (mods/<name>/) are committed as compiled binaries (mods/<name>/<binary>, mode 100755) — install.sh copies them to /usr/bin, it never runs go build. Changing a mod's source does nothing on installed machines until every affected binary is rebuilt (go build -o <name> . in the mod dir; mods replace => ../theme, so a theme change means rebuilding every mod) and recommitted. Verify the new behaviour made it into the binary before pushing (strings on it, or the mod's own smoke check) — HEAD-current is not binary-current.

Release process, end to end

  1. Bump wired/VERSION; run the iso.yml RELEASE CHECKLIST (every human-facing string: VERSION, installer banner + success box, iso config RELEASE/ISO_BASENAME/volume label, iso/README.md, identity files).
  2. Commit to eiri (the working branch); stable releases are cut from main.
  3. Tag v<version>-mika (e.g. v1.4.6-mika). All 1.x releases are intentionally unsigned — signing is deferred to the eiri era. (The release-signing GPG key exists; it's just not wired into 1.x.)
  4. iso.yml builds the ISO artifact (navi-<version>-mika.iso).
  5. publish-stable.yml (parameterized by tag) publishes the GitHub release with the ISO + SHA256SUMS.txt. /releases/latest then resolves to it.
  6. POST-RELEASE: bump the website (xvoidsx/enter-the-wired: index.html, roadmap.html, news.html — keep download buttons on /releases/latest, never a pinned tag; follow the news-archive convention below) and the navi README version strings.
  7. Write release notes; keep a copy under ~/workspace/your_files/.

eiri rolling releases (the dev channel): eiri ISOs are date-stamped snapshots. Bump ISO_BASENAME (navi-2.0-eiri-YYYY-MM-DD) + EIRI_BUILD in .github/workflows/iso-eiri.yml and push — the push trigger fires the build (workflow_dispatch does NOT work while the file lives on eiri only). publish-eiri.yml publishes the build under the rolling eiri tag (moved via API). A mika image from eiri needs an eiri→main merge that explicitly sets wired/VERSION for main — never inherit eiri's 2.0 "eiri" stamp.

Release naming: 1.x = "mika", 2.x = "eiri". Tags look like v1.4.6-mika. The updater discovers "newer" via sort -V over tags matching ^v1\.[0-9]+, so patch versions sort correctly.

Never claim publication, updater success, hardware validation, or public deployment without verifying it. The ping/announcement is a contract — keep it, and only for what's actually done.

Hard-won rules (each one cost us a release)

naviApps catalog rule

The store page must NEVER fetch() its catalog — file:// CORS plus Chromium ignoring CLI flags when already running breaks it. The catalog ships inlined as wired/naviApps/catalog.js, generated from apps.json by wired/naviApps/build-catalog.sh. Re-run the script and push BOTH files whenever apps.json changes.

Seamless GIF loops

Modulo-wrapping a position does NOT make a loop seamless. Every translating element must move an integer number of spatial periods per loop (v * loop_seconds ≡ 0 mod period). Verify frame N against frame 0 numerically, not by vibes. (Keep flagship gifpapers under GitHub's 25 MB file limit so they can ship in the repo.)

Website conventions

House conventions

Voice: Voxtype + Moonshine Base is the dictation default (2026-10-22)

navi's dictation stack is Voxtype with the Moonshine Base engine — chosen for the lowest memory footprint and memory-bandwidth cost of Voxtype's engines; Parakeet streaming was considered and deferred. Scroll_Lock push-to-talk lives in wired/sway/config (compositor owns the key; the daemon runs with [hotkey] enabled = false and state_file = "auto"; Moonshine needs the ONNX variant, voxtype setup onnx --enable). New installations should ship Voxtype installed and enabled with Moonshine Base preconfigured so dictation works out of the box. Open thread: Hey Lain's STT is faster-whisper today — Moonshine is a candidate replacement, but bench it on real hardware before switching her over.

The one-sentence version

Probe, don't guess; test the real shape on real hardware; preserve the user's customizations; log everything; and never claim what you haven't verified.