packages/os/linux/variants/milady-tails/PLAN.md
The phased work order to take elizaOS Live from "empty scaffold" to "boots into a working elizaOS live desktop on real USB hardware, with the AI app ready, optional Tor privacy mode, and optional encrypted persistent storage."
This is a multi-week project. Each phase has a clear success criterion;
don't jump phases. With the containerized build (see Phase 1) a full ISO
is ~1–1.5 h cold, and incremental rebuilds (just binary) are ~10 min —
several phases still need iteration.
Detailed, file-level implementation specs for each phase live in
docs/specs/. This PLAN is the map; the specs are the
turn-by-turn directions.
| Phase 0 — Scaffold | ✅ Done |
| Phase 1 — Base ISO builds + boots | ✅ Done — base image builds and boots through QEMU via -cdrom |
| Phase 2 — elizaOS system branding | ✅ Source implemented; latest validated artifact QEMU visual path passed |
| Phase 3 — Privacy mode | 🔨 Source implemented; needs exact-release network/Tor validation |
| Phase 4 — Bake elizaOS app | ✅ App payload/install path QEMU-passed on latest validated artifact; clean checkout still must run just milady-app before a full build |
| Phase 5 — Autolaunch | ✅ Desktop/systemd wrapper QEMU-passed on latest validated artifact |
| Phase 6 — Agent/broker | 🔨 OS broker/env path implemented; approval-gated privileged actions still need hardening |
| Phase 7 — Persistence | 🔨 Tails Persistent Storage row/hooks implemented; real USB persistence validation still pending |
| Phases 8–9 | 📋 Spec/backlog (docs/specs/), not release-complete |
| Phases 10–11 | ⏳ Not started |
What exists right now:
Dockerfile, build.sh, build-iso.sh,
acng.conf, Justfile) that builds the ISO on any host with Docker — no
Vagrant, no libvirt, no host-specific setup. See
docs/build-infrastructure.md.gdisk/mtools for the
partitioning initramfs hook). All upstream-worthy./opt/milady, fixes
permissions, and removes the staging copy. /opt/milady is an internal
runtime path until the app package itself is renamed./usr/local/lib/elizaos/capability-runner. For the first rebuild it is
intentionally conservative: status/privacy/persistence helpers plus exact
sudo only for root-status; package/network mutation is deferred until an
approval-gated policy layer exists.~/.eliza Persistent Storage overlays are
implemented locally. QEMU has proven the normal greeter/desktop/app path
on the latest validated local ISO artifact, and USB flash/readback passed
on a prior artifact. The current gate is rebuilding/validating the exact
release commit if the branch moves, then repeat USB flash/readback, real
USB boot, persistence, and privacy behavior.See ROADMAP.md for the honest road from here to a real,
fully-working demo.
USB-only distribution with two storage modes and a privacy toggle. No install-to-internal-disk yet — see § Deferred for the rationale.
tps) tool
unchanged. Selected dirs bind-mount from the LUKS partition.Both axes combine freely: 4 valid configurations.
| Amnesia | Persistent | |
|---|---|---|
| Normal | "Burner laptop with AI" | "Portable AI computer" |
| Privacy | "Burner with full anonymity" | "Encrypted portable + anonymity" |
The product target is that the same features work in all four configurations. The only intended differences are:
See docs/mode-parity.md for the exhaustive feature matrix. Anything that
doesn't work in one mode gets a documented known-gap entry; no silent
feature loss. Phase 8 builds the harness that proves this. Until the
rebuilt ISO passes QEMU and real-USB validation, the matrix is an
acceptance target, not production evidence.
Known v1.0 privacy gap: embedded browser/OAuth surfaces are not
production-claimable in Privacy Mode until explicit proxy behavior is
proven. The live OS routing exists, but the app/browser layer still needs
validation and possibly runtime proxy injection. Documented in
docs/privacy-mode-v1-gap.md. Closing this is v1.1 work.
tails/ at this directory's root (~6000
tracked files, copied from a Tails stable clone).packages/os/android/vendor/eliza/ precedent in this
monorepo (brand vendor tree inside system structure).Tails' upstream build drives a Vagrant + libvirt VM. We replaced that
with a plain container — the container is the build environment.
Any dev on Linux/macOS/Windows/CI runs just build and gets the same
ISO. The earlier Vagrant attempt is documented (and buried) in
docs/build-infrastructure.md; don't
resurrect it.
The production release shape is not "rebuild the ISO for every app change." The intended architecture is:
The checked verifier foundation now exists for signed app/runtime manifests, root-owned materialization, and baked-runtime fallback. Production keys, downloader UX, revocation, mirrors, rollback health promotion, model downloads, and provenance gates are still release work. Until those exist, builds are demos or test artifacts even if they boot.
Tails uses a GTK greeter (tails-greeter) at first boot. We keep
this UX — it's battle-tested for live-USB scenarios — and rebrand it.
Boot sequence:
tails-greeter):
System-level choices go through the GTK greeter. Personal/AI choices go through the elizaOS app.
/usr/share/doc/elizaos-tails/CREDITSKernel loads GPU drivers (amdgpu, i915, nvidia, nouveau) regardless of where root filesystem lives. Vulkan / CUDA / ROCm all functional from USB boot. Local LLM gets full GPU acceleration on user's hardware.
| Feature | Normal+Amnesia | Normal+Persist | Privacy+Amnesia | Privacy+Persist |
|---|---|---|---|---|
| Local LLM chat | ✓ | ✓ | ✓ | ✓ |
| BUILD_APP via local stub | ✓ | ✓ | ✓ | ✓ |
| BUILD_APP via Claude CLI | ✓ | ✓ | ✓ slow | ✓ slow |
| Voice (Whisper / Kokoro) | ✓ | ✓ | ✓ | ✓ |
| Wallpaper / SET_WM / SHELL | ✓ | ✓ | ✓ | ✓ |
| GPU acceleration | ✓ | ✓ | ✓ | ✓ |
| Cloud APIs | ✓ fast | ✓ fast | ✓ slow | ✓ slow |
| OAuth | ✓ | ✓ | ⚠ may be blocked | ⚠ may be blocked |
| Chromium browser windows | ✓ | ✓ | ⚠ v1.0 gap | ⚠ v1.0 gap |
| Onboarding survives reboot | ✗ redo | ✓ once | ✗ redo | ✓ once |
| Built apps survive reboot | ✗ | ✓ | ✗ | ✓ |
| Downloaded models survive reboot | ✗ | ✓ | ✗ | ✓ |
| Wifi passwords | ✗ | ✓ | ✗ | ✓ |
| API keys | ✗ | ✓ in LUKS keyring | ✗ | ✓ in LUKS keyring |
(✓ = works. ⚠ = works with caveat. ✗ = wipes on reboot by design.)
packages/os/linux/variants/milady-tails/tails/Goal: the build pipeline runs against our Tails tree and produces a bootable ISO indistinguishable from upstream Tails.
Spec: docs/build-infrastructure.md
Dockerfile, build.sh,
build-iso.sh, acng.conf, Justfile (recipes build / build-fast /
config / binary / nspawn / boot / clean / cache-clean)apt-cacher-ng wired in — required (Tails' chroot has Tor-only DNS
that's dead at build time; apt reaches packages via the proxy by IP) and
it caches downloads so rebuilds are fastifupdown,
isc-dhcp-client, qemu-guest-agent, vagrant agent channel, and
gdisk/mtools for the partitioning initramfs hook)lb config go/no-go passes in the containerlb build produced a finished .iso in out/-cdrom; confirm Tails greeter appearsGoal: Tails greeter still does its job, but visually it's elizaOS.
Spec: docs/specs/phase-2-rebrand.md
— enumerates every file (greeter title/logo/CSS, boot menu, Plymouth,
GNOME theme, wallpaper, os-release, issue), the real elizaOS asset
sources, and the hard "do not rename" list (apt sources, /usr/share/doc/tails,
TAILS_* keys, session-wired filenames).
/etc/os-release → elizaos-tails identity (keep all TAILS_* keys)/etc/issue MOTD → elizaOS/usr/share/doc/elizaos-tails/CREDITSBrand assets are pre-rendered (greeter logo, about logo, Plymouth wordmark, wallpaper, screensaver bg) from real elizaOS sources.
Goal: Two boot menu entries flip Tor routing on/off. Both produce the same elizaOS product surface, with speed/provider caveats documented.
Spec: docs/specs/phase-3-privacy-mode.md
lib/live/config/0001-elizaos-privacy-mode — reads the privacy
kernel cmdline flag (elizaos_privacy=1, with compatibility for
elizaos.privacy=on) → writes /etc/elizaos/privacy-mode; malformed
values fail closed to privacy/Tor modeetc/ferm/ferm-direct.conf — permissive firewall (Tor NAT-redirects
dropped), the privacy=off counterpart to Tails' Tor-only ferm.confdispatcher.d/00-firewall.sh + 10-tor.sh branch on the flaggrub.cfg edit) + syslinux (10-syslinux_customize)Goal: /opt/milady/ exists in the chroot, contains a runnable binary.
Spec: docs/specs/phase-4-bake-milady-app.md
— the real build sequence, the 9100-install-milady hook design, and the
runtime-package validation direction.
just milady-app recipe — builds the current desktop app package on the host
(the build needs the eliza-first install + setup-upstreams.mjs +
MILADY_ELIZA_SOURCE=local dance — a naive bun run build:desktop fails)tails/config/chroot_local-includes/usr/share/elizaos/milady-app/
(.gitignore'd — it is ~2.5–2.9 GB uncompressed, far too large to commit)tails/config/chroot_local-hooks/9100-install-milady — installs to
/opt/milady/, guards version.json, fixes perms incl. chrome-sandbox
setuid, then rm -rf's the staging copy (critical for ISO size)tails-common.list plus the staged
app bundle. There is no committed milady-runtime.list in the current
tree; production should replace this with a generated, audited package
manifest instead of stale docs.usr/share/applications/milady.desktop⚠ Top risk: the app tree is ~2.9 GB uncompressed (eliza-dist/ alone is
2.2 GB) — much larger than first estimated. The resulting ISO could be
3–4 GB. And chrome-sandbox under Tails' AppArmor + read-only squashfs is
the most likely "boots but won't render" failure (--no-sandbox fallback
documented). See the spec + ROADMAP risk section.
Goal: after the greeter exits, GNOME comes up with the elizaOS app as the first window and keeps it available as the home agent without hiding the normal desktop.
Spec: docs/specs/phase-5-6-autolaunch-and-agent.md
— mostly config, not code: the current implementation uses root-owned systemd
supervision plus live-user services.
etc/systemd/system/milady.path + milady.service — root-owned
system service starts when the live user session bus appears, runs
the app as amnesia, and restarts it if it exits/usr/local/bin/milady and elizaOS launch helpers/usr/local/bin/milady — pins ELIZA_STATE_DIR=/home/amnesia/.eliza
plus XDG dirs in the launch env and uses a lock to avoid duplicate app
instancesetc/dconf/db/local.d/00_Tails_defaults — light elizaOS theme, wallpaper,
disable GNOME welcome dialog (don't clobber Tails' enabled-extensions)dconf updateGoal: the same elizaOS app stack that runs on desktop runs on this live USB.
Spec: docs/specs/phase-5-6-autolaunch-and-agent.md.
This is not "one code delta" — it is real product integration work.
The live image has to align the Electrobun/CEF runtime, embedded Bun
agent, plugin package graph, ~/.eliza state, model/provider defaults,
and Tails' live-user session. The demo branch includes explicit runtime
guards/fallbacks; production needs cleaner package boundaries and a
security review of every privileged capability.
~/.eliza) + env prefix for the
OS-side launch path: /usr/local/bin/milady exports ELIZA_STATE_DIR,
MILADY_STATE_DIR, ELIZAOS_*, and ELIZAOS_CAPABILITY_RUNNER~/.eliza works in amnesia (tmpfs) and persistent (LUKS bind-mount)Goal: user opts into LUKS persistence via the greeter; elizaOS app data survives reboots; no Tails persistence code is modified, only added configuration.
Spec: docs/specs/phase-7-persistence.md
— note: this Tails release uses the modern Persistent Storage (tps)
stack, not the legacy tails-persistence-setup. Footprint is tiny.
MiladyData Feature subclass in tps/configuration/features.py
(bindings for ~/.eliza, ~/.milady, ~/.config/milady,
enabled_by_default=True)features_view.ui.in (required or the frontend crashes)Goal: all 4 combos work the same. Anything that doesn't = documented gap.
Spec: docs/specs/phase-8-mode-parity-harness.md
— a mode-parity.sh orchestrator for this variant's QEMU and USB-image
paths.
scripts/mode-parity.sh + scripts/mode-parity-checklist.sh{amnesia,persistent}×{normal,privacy} combos through
one shared checklist, diffs them, emits parity-report.mdjust mode-parity recipedocs/mode-parity.mdGoal: "Install i3", "switch tiling", "swipe-down-for-notis" — all through chat with elizaOS orchestrating Linux underneath.
Spec: docs/specs/phase-9-customization-actions.md
— most substrate already exists (INSTALL_PACKAGE + its confirmation
flow, OPEN_TERMINAL, SET_WALLPAPER).
SHELL action — a thin gating layer over existing install intent plus
the elizaOS capability broker. Passwordless apt sudoers/polkit overlays are
not accepted in the current security model.SET_DESKTOP, THEME, NOTIFICATIONS actions (compose the existing
install flow)customization.ts persistence-awareness helperdocs/customization-vocabulary.md"Make this my main computer. Wipe my drive, install elizaOS on it." — would let users use elizaOS as a daily-driver Linux, trading the live-USB constraints for full hardware speed + storage.
Why deferred and being considered with respect for Tails' design:
Tails refuses to install itself to disk by design. Their reasoning:
We respect that reasoning. An elizaOS ISO that defaults to amnesia inherits the same forensic protection. Tails users picked Tails specifically because there's no disk install option — adding one without thought betrays that choice.
That said: elizaOS Live's target audience is broader than Tails'. Many users want "AI Linux as my daily driver" without needing amnesia-on-laptop. For them, install-to-disk would be a real product.
Before we add it, we need a real design RFC covering: the threat model when installed, default full-disk encryption, the dual-boot story, the install UX (Calamares vs. an elizaOS app flow), and the Tails community pulse on the derivative. Planned target: v2.0, after v1.0 ships and real users tell us what they want. For now: don't add it.
Closes the Privacy Mode embedded-web gap. Patch the active app shell/runtime
to inject an explicit Tor proxy into any external web/OAuth surface when
elizaos.privacy=on. If CEF/Electrobun is active, that likely means
--proxy-server=socks5://127.0.0.1:9050; if WebKit is active, it needs the
equivalent WebKit/network-context proof.
Switch privacy modes mid-session without reboot. iptables atomic swap + tor.service start/stop + Chromium re-proxy.
.deb, .AppImage, Flatpak packaging. Lower priority — the live-USB IS
the product.
apt-cacher-ng cache
makes each iteration fast.9100 hook must rm -rf the staging copy; consider a slimmer build
profile; re-measure and budget. See Phase 4 spec.chrome-sandbox under AppArmor + squashfs — the likely "boots but
the elizaOS app won't render" failure. --no-sandbox is the documented
fallback.eliza-first + setup-upstreams.mjs + MILADY_ELIZA_SOURCE=local
sequence; a naive bun run build:desktop fails. Encoded in just milady-app.tails/ tree is ~6000 files.
PR maintainers may push back; submodule pattern is the fallback.memlockd zeros RAM on shutdown; we keep it.stable clone.
Pin in tails/debian/changelog; document upgrade cadence.tails/ git strategy — the vendored copy ships without
.git; build-iso.sh git inits a throwaway repo at build time so
the build works either way. Long-term: keep as committed files, or
convert to a submodule of an elizaOS Tails fork. Decide before v1.0.~/.eliza for product state while supporting existing app/runtime
paths (MILADY_*, ~/.milady) until the app package is renamed.The build needs only Docker. From this directory:
just config # ~1 min go/no-go — does the Tails config tree process?
just build # full clean ISO → out/ (~1–1.5 h cold, faster cached)
just binary # ~10 min incremental rebuild after editing overlay files
just nspawn # seconds — boot the built chroot for non-GUI sanity
just boot # boot the latest ISO in QEMU
Pick a phase, read its spec in docs/specs/, implement against the
vendored tails/ tree, validate with just binary + just boot.
Exploratory work until Phase 10 ships a real v1.0 ISO that boots on bare
metal.