Apps on Lisa.
One toolkit: GJS + GTK4/Adwaita (ADR-0047). The shell surfaces that ship in the image (Assistant, Ledger app, Settings, the overlay backend under shell/) and the apps (Mail, Surfer, Preview) are the same shape, so a fix reaches a device by copying files. Apps expose their actions to the system as MCP tools; the Agent Bus enforces confirmation tiers so apps don't have to.
The lisa-app launcher
Shell apps are interpreted (GJS), so updating them is copying files. /usr/bin/lisa-app <relpath> execs gjs -m on the app entry point, resolved out of the current apps tree. It does not know where that tree is: it asks lisa apps path shell, the same function lisa apps update installs through, so the writer and the reader cannot disagree (issue #239). .desktop files and D-Bus activation exec via lisa-app, so an updated tree takes effect on the next app launch, no reboot (ADR-0020).
The app update channel
App updates are decoupled from the OS image (ADR-0020): a versioned apps tree lives on the persistent /var, flipped atomically via symlink+rename — no partial states.
lisa apps update # fetch lisa-apps_<ver>.tar.zst, verify vs SHA256SUMS, unpack, flip
lisa apps status # installed versions AND the directory a launch would actually use
lisa apps rollback # flip back to the previous tree (or the baked image tree)
lisa apps path shell# the directories a launcher searches, best first- Same GitHub Releases channel and manifest the OS updates use — one release, two update planes.
- The tree carries the shell surfaces and the apps (Mail, Surfer, Preview) — the same tree the image bakes at
/usr/share/lisa/shell, staged by the same script. - A broken tree is one
lisa apps rollbackaway; with nothing older installed that restores the baked image tree. - No
sudo: the payload directories are group-writable for the desktop user (ADR-0034 §7b). - GNOME Shell extensions load at session start from the baked tree — they're out of scope for this channel and keep riding image releases.
- This channel is the interim: it's superseded by the Flatpak lane when M6 matures.
GJS shell surfaces
The first-party surfaces are TypeScript/GJS under shell/:
- Assistant (
shell/assistant, ADR-0015) — a persistent chat window, a frontend of the overlay backend: local + cloud models, streaming, ledgered. Super+C opens it. - Overlay (
shell/overlay-extension) — one headless backend owning state/streams (dev.lisaos.Overlay1) with thin frontends; Super+Shift+Space summons it. - Launcher (
shell/launcher) — the semantic search provider with the "Ask Lisa" handoff. - Ledger app (
shell/ledger-app, GTK4/GJS) — renders the audit DB. - Settings (
shell/settings) — providers, consent, models.
House style: fail-soft D-Bus calls everywhere — apps must degrade gracefully against older daemons, because the apps tree can be newer than the image.
Exposing tools (MCP manifests)
An app declares its actions in a manifest: typed tools with a JSON Schema per input, a confirmation tier (read / write / destructive), and optional undo mappings the bus journals for lisa undo. The Notes app (apps/notes) is the worked example — see the manifest walkthrough in the API reference. Tiers are enforced at the bus, not by app goodwill.
Checking an app: lisa dev check
One command decides whether a directory is a valid Lisa app (ADR-0050), and it is the same judgement the Forge uses as its verifier — so generated code and hand-written code are held to one standard. It gates on there being source at all, on no top-level await in an entry module (the failure that binds a socket, advertises it and answers nothing, with no error in any log), and on the manifest, parsed by the same code lisa-agentd runs.
lisa dev check apps/notes # exits non-zero, with findings
lisa forge "a notes app" --project ~/notesIt deliberately does not run the app's own tests and makes no JavaScript syntax claim — the verifier runs unconfined, and a checker that executes model-written code in order to verify it would hand the loop the escape the jail exists to prevent.
The Flutter lane is parked
libs/lisa_ui and libs/lisa_flutter are kept, not deleted, and neither is the way to build an app (ADR-0047). Nothing user-facing was ever written in Flutter; the reasons GJS won were iteration on real hardware, desktop integration (portals, D-Bus activation, AT-SPI, input methods) that GTK4 gets for free, and one toolkit meaning one design-token sheet, one test harness and one set of idioms. The image ships no lisa_ui payload and the CLI declares no Flutter dependencies.
No SDK required
You don't need anything Lisa-specific to build against the intelligence: the OpenAI-compatible endpoint on 127.0.0.1:7777 works with any existing OpenAI client — Electron, web, CLI, anything. Guided generation (response_format: json_schema) gives you typed output that always parses. See the API reference and docs/sdk/samples.