Docs / Building apps

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 rollback away; 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 ~/notes

It 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.