NAVI // AGENTS.MD
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.
navi is xvoidsx's Linux distro: a complete, opinionated, agent-native desktop OS on a dependable Debian (trixie) base. Wayland-first (sway, our "wired" session), X11 fallback (i3). Deep blacks, neon pink, phosphor green — the nightshadeNeon design language across the whole desktop.
Guiding ideas:
navi-wired-adopt is for (explicit, backed up, loud warning).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.
├── 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/
/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.
wired/VERSIONThe version string (e.g. 1.4.6 "mika") lives in wired/VERSION. Everything
human-facing is derived from it at deploy time:
install.sh's install_identity re-stamps wired/identity/ (os-release,
lsb-release, issue, issue.net) from VERSION on every deploy — including
--deploy-only, so navi-update refreshes them. These files go stale
silently otherwise (they read 1.3 all through the 1.4 cycle once).iso/config RELEASE/ISO_BASENAME/
volume label, and iso/README.md must all be checked per release.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.
install.sh does double duty: fresh installs and redeploys onto a live
system via --deploy-only (this is what navi-update runs after pulling).
/var/lib/navi/deploy-manifest.tsv
(sha256/version/relpath/dest/mode) so drift can be detected later.install -m 0755 on /usr/bin copies and chmod 0755 on every
*.sh under /usr/share/navi — scripts run no matter how the repo was
fetched (clone, zip, tarball).SIGUSR2 config-reload. If you deploy a config for a daemon,
check whether the daemon picks it up on its own; if not, signal it./etc/sudoers.d/10-navi-user (<user> ALL=(ALL:ALL)
ALL, password-required), validated with visudo -c, self-removing on
check failure. Effective on the next sudo call — no re-login (group
membership needs a re-login; a sudoers rule doesn't).scripts/installers/: nightly browsers, ani-cli,
charm, cliamp) are not run by the installer — one failure there used
to abort the whole install under set -e. They're deployed to
/usr/share/navi/installers/ and offered through the navi-extras
post-install menu instead (/usr/bin/navi-extras, rofi-visible).setup_heylain deploys mods/hey-lain/ to
/usr/share/navi/hey-lain and builds its STT/TTS venv in place
(faster-whisper + piper + whisper model). The venv survives redeploys —
the .venv-ready marker stores a hash of requirements.txt, so
unchanged requirements mean "refresh scripts, keep venv"; a PyPI outage
warns and continues instead of wedging the install. Logs go to
$XDG_STATE_HOME/hey-lain/log (HEY_LAIN_LOG_DIR) because the deploy
tree is root-owned. Alt+V is bound in the shipped sway config;
warmup.sh runs at login.Three commands, three jobs. Don't confuse them:
navi-update — pulls the release channel and re-runs deploy.
Two channels, Debian-style: stable follows release tags (never
arbitrary main commits — a fix on main is invisible to the updater until
it's in a tag); eiri is rolling and tracks the eiri branch head
(deployed commit remembered in /var/lib/navi/navi-update.state).
--check for dry status; --channel for channel selection (switching
asks first, then pins /etc/navi/channel). Two-mode trust: signing key
provisioned → verify fail-closed; no key → loud warning + unsigned
pull. Preserves customized configs by design.navi-wired-restore — shows how the live setup drifted from stock and
restores what you choose, with backups of everything it touches.navi-wired-adopt — the explicit "give me the new stock desktop"
path: lays current stock wired configs over customized ones, backup first,
loud warning, --dry-run to preview, offers WM/bar reload when done.
Has a rofi launcher.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…>:
swaymsg -t get_version
round-trip (then i3-msg -t get_version); never trust $SWAYSOCK alone.for_window rule BEFORE the window maps — no race.1234 can't
match 12345.~/.local/share/navi/mod-open.log — future
misbehavior must leave footprints.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 JSON
with an error key and exit code 0, so parse the body, don't trust the exit.
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 — 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 opencode.
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.
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).main (the working branch; the mika line merged into main).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.)iso.yml builds the ISO artifact (navi-<version>-mika.iso).publish-stable.yml (parameterized by tag) publishes the GitHub release
with the ISO + SHA256SUMS.txt. /releases/latest then resolves to it.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.~/workspace/your_files/.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.
dunstctl history is busctl JSON: every D-Bus variant arrives wrapped as
{"type": "<sig>", "data": <value>} — mocks with plain strings passed
while real data crashed.python3 -c has two traps: no backslashes inside f-string expressions
(f"{x(\"y\")}" is a SyntaxError on our Python) and no bare return at
top level (module scope — use sys.exit()). Extract the -c body and run
it standalone against sample input.style.css is GTK CSS, not browser CSS. The parser rejects
comma-separated keyframe selectors — 0%, 100% { } fails with "Expected
closing bracket after keyframes block". One selector per keyframe rule.iso/stage.sh must copy EVERYTHING install.sh reads from $REPO_DIR.
If install.sh deploys from $REPO_DIR/<dir>, stage.sh must stage <dir>
into /opt/navi-iso and the installer's preflight must require it.
(Missing scripts/ once meant fresh ISOs silently skipped navi-update.)gh-push-mika overwrites remote files with LOCAL content (and stores
*.sh as mode 100755). A stale local tree once shipped an ISO package
list with the network stack silently dropped. Fetch + diff first, every
time. (Transient HTTP 422 "Tree SHA does not exist" at commit creation:
retry once before investigating.)mod-open.log); diagnose the live
output, not the mocks.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.
${badgeFor()}
threw on a.source and stuck the whole store on "loading curated apps"
— one uncaught TypeError kills the first render.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.)
xvoidsx/enter-the-wired. Pages: index, roadmap, news, manual
(mirrored from wired/manual/ — update both copies together),
sponsors, labs, community./releases/latest, never a pinned tag.<details> collapsibles. When adding
a post, demote the 4th-newest.AGENTS.md is mirrored to the website as agents.html. After
changing this file, re-run build-agents-page.sh in the site repo and push
— the repo copy stays the source of truth.navivim.html (hand-written product page), navivim-handbook.html
(mirror of its HANDBOOK.md), navivim-agents.html (mirror of its AGENTS.md)
— all built by build-navivim-pages.sh in the site repo. The NaviVim repo
copies stay the source of truth.markdown package. Make sure it's
installed somewhere that survives your environment rebuilds — a plain system
pip install may not be.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.