Manifest: app.json
Schema 1. Unknown keys are rejected when you install and again when the executor loads the app.
Fields
| Key | Type | Required | Notes |
|---|---|---|---|
schema | 1 | yes | Schema version. Unknown keys are rejected everywhere. |
id | [a-z0-9-]+ | yes | App id. Prefix of everything the app owns (services, actions, sockets). |
name | string | yes | Display name. |
version | semver | yes | An install of a strictly newer version upgrades; never a downgrade. |
requires.protocol | 1 | yes | Service protocol version. Checked at install. |
ui.kind | qml | web | native | none | yes | web runs in purpose-webhost; native must live under bin/; qml runs in Quickshell. |
ui.entry | path | Must exist in the bundle. | |
launcher | object | icon, categories, mime_types, show. Generates a .desktop file. | |
services[] | object[] | name, runtime (native), exec, start (session), timeout_ms, actions[]. | |
intents[] | object[] | intent, action, phrases, result (capture | none), sink, view, end, discard. | |
names[] | string[] | Spoken names, so "open my diary" finds the app. | |
permissions.call | string[] | Other services the app may call. An app may always call its own. | |
permissions.files | string[] | ro:~/path or rw:~/path, 1 to 8 plain components. Enforced by Landlock. | |
permissions.network | bool | true is rejected in v1 (signed apps later). | |
permissions.mic | bool | Declared; the microphone is owned by the voice service. | |
uses[] / implements[] | reserved | Parsed and ignored. Reserved for swappable services (action@app-id). |
Example: Journal
{
"schema": 1,
"id": "journal",
"name": "Journal",
"version": "0.1.0",
"requires": { "protocol": 1 },
"ui": { "kind": "web", "entry": "ui/index.html" },
"launcher": { "icon": "assets/journal.svg", "categories": ["Office"], "show": true },
"services": [{
"name": "store",
"runtime": "native",
"exec": "bin/journal-store",
"start": "session",
"timeout_ms": 30000,
"actions": ["journal.start", "journal.append", "journal.end", "journal.list", "journal.search"]
}],
"intents": [{
"intent": "journal",
"action": "journal.start",
"phrases": ["add to journal", "journal this", "dear diary"],
"result": "capture",
"sink": "journal.append",
"view": "action:ui/Capture.qml",
"end": "journal.end",
"discard": "journal.discard"
}],
"names": ["journal", "my diary"],
"permissions": { "call": ["journal-store"], "data": "own", "files": [], "network": false, "mic": false }
} Rules
- Service plugin name is <id>-<service>. Actions are <id>.*, except an intent's own name.
- A service binary must accept: serve --socket PATH
- Bundle: gzip tar of regular files only. No links, devices, absolute paths or .. segments. data/ is reserved.
- ELF files must match the host CPU. A bundle named *-any.papp may contain none.
- sink, end and discard must be actions the same app declares, and need result: capture.
- Checksums prove integrity, not authorship. Signing is not implemented yet.
Intents and capture
An intent maps spoken phrases to an action. Phrases match exactly, or by content words: “add this to my journal” matches “add to my journal”, but “add milk to my journal” does not. A spoken name before a comma is ignored: “buddy, add to journal”.
With result: "capture" the next dictation goes to your sink action until the user saves or discards. The end action finishes it and discard throws it away. A capture that times out counts as a save, so an abandoned dictation is kept.
| Field | Meaning |
|---|---|
result | capture or none |
sink | Action that receives each dictated segment. Required for capture. |
view | action:ui/Capture.qml, a QML file the shell shows as the dictation panel. |
end, discard | Actions for Save and Discard. Capture only. |
What the installer says
These are the real messages from the validator for three broken manifests.
app.json: services[0].actions[0]: "other.greet" is not owned by this app: actions must start with "hello." (or be a declared intent name)app.json: permissions.network: network access is not supported in protocol 1 and apps requesting it are refused (planned for signed apps)app.json is not valid: unknown field `bogus`, expected one of `call`, `data`, `files`, `network`, `mic`What install generates
For each service the installer writes an executor manifest, a session start hook and a ledger entry. You never write these.
~/.local/share/purpose/apps/<id>/ code (read-only) and data/
~/.local/share/purpose/plugins/<id>-<svc>/ executor manifest
~/.local/share/purpose/session.d/6N-<id>-<svc> start hook, restarts the service with backoff
~/.local/share/applications/purpose-<id>.desktop
~/.local/share/purpose/ledger/<id>.json exactly what to remove on uninstall {"abi":"service","abi_version":1,"actions":["hello.greet"],"entry":"hello-svc.sock","kind":"runtime","name":"hello-svc","timeout_ms":10000,"version":"0.1.0"}