API reference

The surfaces you build against.

Three layers: an OpenAI-compatible HTTP endpoint on loopback (zero Lisa-specific dependencies), session D-Bus interfaces under dev.lisaos.*, and MCP tool manifests for exposing app actions to the Agent Bus. Everything below is read from the source on main.

OpenAI-compatible HTTP — 127.0.0.1:7777

Served by lisa-inferenced (source). Any OpenAI client works unmodified — point it at the base URL, any API key. The system instance owns port 7777; the per-user companion (which can route to cloud providers) owns 7778. Every generate/embed is gated by the Ledger: the entry precedes the action, and if the append fails the request is refused with 503.

GET /health

curl 127.0.0.1:7777/health
→ {"status":"ok","engine":"llama","version":"…"}   # engine: "stub" or "llama"

GET /v1/models

curl 127.0.0.1:7777/v1/models
→ {"object":"list","data":[{"id":"…","object":"model","created":…,"owned_by":"lisa"}]}

POST /v1/chat/completions

Request fields (from the wire types): model (optional — defaults to the resident system model), messages ([{role, content}]), stream (bool), response_format, lisa_priority ("interactive" | "background" — background requests are preempted by interactive ones), max_tokens.

curl 127.0.0.1:7777/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"write a haiku about entropy"}]}'
→ {"id":"chatcmpl-lisa-…","object":"chat.completion","created":…,"model":"…",
   "choices":[{"index":0,"message":{"role":"assistant","content":"…"},"finish_reason":"stop"}],
   "usage":{"prompt_tokens":0,"completion_tokens":…,"total_tokens":…}}

Streaming ("stream": true): Server-Sent Events, OpenAI chunk convention — a role preamble chunk, then delta.content token chunks, a finish_reason: "stop" chunk, then data: [DONE]. Errors mid-stream arrive as a {"error":{"message":…}} data event.

Guided generation — the flagship feature. Hand it a JSON Schema and the output is grammar-constrained (JSON Schema → GBNF, enforced by the sampler), so it always parses:

from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:7777/v1", api_key="local")

r = client.chat.completions.create(
    model="lisa",
    messages=[{"role": "user", "content": "Extract the recipe.\n\n" + text}],
    response_format={"type": "json_schema",
                     "json_schema": {"name": "recipe", "schema": SCHEMA}})
# always valid JSON for SCHEMA

An unsupported schema returns 400 with invalid_request_error. Non-streaming guided requests get one server-side re-sample if the output isn't valid JSON — structured output is the contract.

POST /v1/embeddings

curl 127.0.0.1:7777/v1/embeddings \
  -H 'Content-Type: application/json' \
  -d '{"input": ["first text", "second text"]}'
→ {"object":"list","model":"…",
   "data":[{"object":"embedding","index":0,"embedding":[…]}, …],
   "usage":{"prompt_tokens":0,"total_tokens":0}}

input is a string or an array of strings (anything else → 400).

D-Bus interfaces

Session-bus services under dev.lisaos.*. Rich results are JSON strings — one serialization, so busctl and scripts read them directly. All are tested over zbus peer-to-peer connections and registered on the session bus on real systems.

Naming note (ADR-0016): the source on main uses dev.lisaos.* / app.lisaos.*; release v20260724.25 still carries the older org.lisa.* names. The rename ships with the next release.

dev.lisaos.Inference1 — inference sessions

Object path /dev/lisaos/Inference1 (source). The fd-stream contract: OpenSession returns a session object path and the read end of a pipe; tokens stream over that fd as raw UTF-8, and the daemon closes its write end when generation completes — EOF is end-of-message.

Ping() → s                                # "lisa-inferenced <version>"
OpenSession(a{sv} options) → (o path, h fd)
    # options: "model_hint" (s) selects a resident model

# on the returned session object (dev.lisaos.Inference1.Session):
Generate(s prompt, a{sv} params)
    # params: "schema" (s, JSON Schema → grammar-constrained output),
    #         "max_tokens" (u), "priority" ("interactive"|"background")
    # tokens stream over the session fd; fd closes at end-of-message
Embed(as texts) → aad                     # array of array of double
Cancel()                                  # abort in-flight generation → early EOF
Close()                                   # release the session object path

dev.lisaos.Agent1 — the Agent Bus

Object path /dev/lisaos/Agent1, served by lisa-agentd (source). Read-tier calls with a fully trusted chain execute immediately; everything else parks and emits ConfirmationRequested. Every path is ledgered before anything happens.

Ping() → s
ListTools() → (s tools_json)              # [{app_id, name, tier, description, undoable}]
Discover(s query) → (s tools_json)        # rank tools against a natural-language query
RequestCall(s app_id, s tool, s args_json, a{sv} options)
    → (t call_id, s disposition, s detail_json)
    # options: "actor" (s), "provenance" (as — the trigger chain;
    #          omitted/empty = unknown = escalates one tier, rule 6)
    # disposition: "executed" | "failed" | "confirm-chip" |
    #              "confirm-modal" | "denied"
Confirm(t call_id, b approve) → (s status, s detail_json)
    # status: "executed" | "failed" | "denied"
Undo() → (s report_json)                  # revert via the journaled compensation
signal ConfirmationRequested(t call_id, s spec_json)
    # spec_json carries the typed-diff material (tool, args, tiers, chain)

dev.lisaos.Context1 — the context fabric

Object path /dev/lisaos/Context1, served by lisa-contextd (source). Every search appends a context.search[.hybrid|.scoped] ledger entry before the store is queried — if the append fails, the retrieval does not happen. Per-app memory is namespace-isolated: every method takes the app id and no call can cross it.

Ping() → s
Search(s query, a{sv} options) → (s hits_json)
    # options: "limit" (u, default 3), "hybrid" (b, BM25×cosine blend),
    #          "scopes" (as — present ⇒ ACL-scoped retrieval,
    #          deny-by-default on empty/unknown scopes)
    # hits_json: [{source, provenance, snippet, score}]
MemoryGet(s app, s key) → s               # missing key → error
MemorySet(s app, s key, s value)
MemoryList(s app) → s                     # JSON object, key → value
MemoryWipe(s app)                         # zero residual rows

dev.lisaos.Remote1 — the egress broker

Interface dev.lisaos.Remote1 at /dev/lisaos/Remote1; note the well-known bus name is dev.lisaos.Remoted (source). The Settings app's management plane: providers, credentials (write-only — no method ever returns key material), per-scope offload consent, and "Sign in with Claude / ChatGPT" OAuth.

Ping() → s
State() → s                               # providers + credential presence + consent, one JSON doc
AddProvider(s id, s display_name, s base_url)   # user-supplied OpenAI-compat endpoint
RemoveProvider(s id)
SetKey(s id, s key)                       # write-only credential store
ClearKey(s id)
SetConsent(s scope, b allowed)            # scopes: prompt|files|mail|calendar|screen|memory,
                                          # default: nothing leaves
BeginLogin(s provider_id) → s             # authorize URL ("anthropic" or "openai");
                                          # completion arrives via LoginCompleted
Logout(s provider_id)                     # forget a stored OAuth session (idempotent)
ListModels(s provider) → s                # the provider's live /models, JSON array of ids
signal LoginCompleted(s provider_id, b ok, s detail)
    # no token material is ever carried

dev.lisaos.Overlay1 — the assistant overlay backend

Bus name dev.lisaos.Overlay1 at /dev/lisaos/Overlay1 (source) — the headless backend shared by every thin frontend (the GNOME Shell extension, the Assistant chat window). Ask() returns a query id immediately; tokens arrive as Token signals and the turn ends with Finished.

Ask(s prompt, a{sv} options) → (t query_id)
Cancel(t query_id)                        # on a query awaiting consent, answers "deny"
Respond(t query_id, b approve)            # answer a ConfirmationNeeded
GetStatus() → a{sv}

signal Started(t query_id, s meta_json)
signal Token(t query_id, s text)
signal ConfirmationNeeded(t query_id, s spec_json)
signal Finished(t query_id, s status, s detail)

Options (a{sv}): the per-invocation context affordances as booleans — "my_stuff" (Context Fabric retrieval), "window" (screen capture → VLM; lands M6, currently reported unavailable), "selection" (app resource / AT-SPI; reported unavailable) — plus "model_hint" (s).

The chat lane (used by the persistent Assistant window) adds three options: "lane" = "chat" selects the multi-turn chat lane (no Agent pass; talks to the OpenAI-compat endpoint so the chat template applies and remote:<provider>:<model> routes through the broker), "history_json" (prior [{role, content}] turns), and "model_hint" (a local model id or remote:<provider>:<model>). Tokens and Finished are emitted exactly as for the inference lane.

A companion frontend-owned interface, dev.lisaos.Overlay1.UI at /dev/lisaos/Overlay1/UI, offers Summon(s prompt, a{sv} options), Hide(), and GetVisible() → b — the launcher's Spotlight-style "Ask Lisa" handoff.

MCP tools — the app manifest

Apps expose actions to the Agent Bus by declaring typed tools in a manifest (PLAN §5.4, Appendix B). The Notes app ships the worked example, apps/notes/app.lisaos.notes.json (abridged):

{
  "lisa_manifest": 1,
  "app_id": "app.lisaos.notes",
  "mcp": { "transport": "unix", "activatable": false },
  "tools": [
    {
      "name": "create_note",
      "tier": "write",
      "description": "Create a note with a title and optional body",
      "input_schema": {
        "type": "object", "required": ["title"],
        "additionalProperties": false,
        "properties": {
          "title": { "type": "string", "maxLength": 120 },
          "body":  { "type": "string", "maxLength": 4000 }
        }
      },
      "undo": { "tool": "delete_note", "map": { "id": "$result.id" } }
    },
    { "name": "list_notes",   "tier": "read",   },
    { "name": "search_notes", "tier": "read",   },
    { "name": "delete_note",  "tier": "write",
      "undo": { "tool": "restore_note", "map": { "id": "$input.id" } } },
    { "name": "restore_note", "tier": "write",  }
  ]
}
  • tier sets the confirmation policy, enforced at the bus: read → silent (ledgered), write → inline confirmation chip, destructive → explicit modal with a typed diff.
  • input_schema is a JSON Schema; the bus validates arguments before dispatch.
  • undo declares the compensating call, with $input.* / $result.* mappings journaled at execution — this is what powers lisa undo and Agent1.Undo().

Try it from the terminal:

lisa tools                                              # list registered tools
lisa call app.lisaos.notes create_note '{"title":"milk"}'
lisa undo                                               # reverts via the declared compensation