[ Switch to styled version → ]
The App Store provides apps an agent installs with one command and calls over local IPC, consuming and producing JSON. The daemon starts them on install.
The App Store is for installable capability apps. An app consists of a binary and a signed manifest.json. pilotctl fetches the bundle, verifies its SHA and signature, and extracts it. The daemon supervises the app, spawning the binary, providing a unix socket, and brokering IPC calls. Each app method is a typed call with JSON input and output.
Apps are made by their publishers (Cosift, Plainweb, Smol, AgentPhone, Orthogonal, the Wallet app and others) and reviewed before they are listed; Pilot runs the catalogue and the install path. The catalogue lists 30 apps today. Run `pilotctl appstore catalogue` for the current list.
The agent loop is: discover, install, call.
Discovery and installation use the catalogue, a signed list fetched by the daemon. Installation verifies the bundle, and the daemon auto-spawns it. The `call` command is used for execution.
# 1. Discover what's installable
pilotctl appstore catalogue
# 2. Inspect before committing - description, vendor, changelog, size, source, methods, permissions
pilotctl appstore view io.pilot.cosift [--all-changelog]
# 3. Install by id - fetch + verify sha + install; the daemon auto-spawns it
pilotctl appstore install io.pilot.cosift
# 4. Confirm it's ready (lists installed apps + the methods each exposes)
pilotctl appstore list
pilotctl appstore status io.pilot.cosift
# 5. Call a method - JSON in, JSON out on stdout
pilotctl appstore call io.pilot.cosift cosift.search '{"q":"raft consensus","k":"5"}'The `view <id>` command shows an app's details, including description, vendor, changelog, size, source URL, license, methods, and requested permissions. This works for both installed and uninstalled apps.
Apps ship with default configurations. An optional `config.json` can be used for overrides.
`pilotctl appstore list` and `status` show method names. A convention for richer discovery is a `<app>.help` method. This is a local call that returns every method with its parameters, a `kind`, and an expected-latency class.
pilotctl appstore call io.pilot.cosift cosift.help '{}'The latency class lets an agent pick the cheapest method for its need.
Each method entry also carries a warm round-trip estimate the app publishes.
Most apps run locally and are free. A few call a paid partner API through Pilot's broker, which holds the partner key and meters each user's calls, for example Orthogonal, AgentPhone and Smol's cloud machines. Each Pilot user gets a budget of USD 5 for each metered app; only successful paid calls draw on it, and reads are free.
Two surfaces provide context to agents: a product demo at install and next-steps after every call. These are authored by the publisher.
When `pilotctl appstore install` finishes, it displays a "Full usage demo" with examples. This demo also renders on the app's store page.
After every `pilotctl appstore call`, a next-steps block is printed to stderr, suggesting commands for success or failure states. stdout remains pure JSON.
# stdout is the pure JSON result (here an error envelope) — the hints go to stderr:
pilotctl appstore call io.pilot.sqlite sqlite.query '{"sql":"SELECT 42"}'
error: ipc: server error: backend: missing required param(s): database
next: sqlite needs an explicit database — there is no default
1. pilotctl appstore call io.pilot.sqlite sqlite.query '{"database":":memory:","sql":"SELECT 42 AS a"}'
why: pass database (:memory: for a scratch db) — fixes the error aboveThis feature can be disabled with `PILOT_NEXT_STEPS=off`. With the `--json` flag, the hint is a structured `next_steps` object. A malformed hint does not cause a working call to fail.
Hints are driven by a graph shipped in the catalogue. `pilotctl` lazily refreshes this graph from the catalogue approximately every 12 hours per app. No re-installation is needed to receive updated hints.
pilotctl appstore restart io.pilot.cosift # respawn (e.g. after writing a config.json)
pilotctl appstore caps io.pilot.cosift # spend caps + current rolling-window usage
pilotctl appstore audit io.pilot.cosift # supervisor log: spawn / exit / verify-fail
pilotctl appstore actions # pilotctl-side install/uninstall log (survives removal)
pilotctl appstore upgrade io.pilot.cosift # move to the catalogue's current version (state kept)
pilotctl appstore install io.pilot.cosift --force # reinstall the same version in place (state kept)
pilotctl appstore uninstall io.pilot.cosift --yes`caps` reports spend caps and usage. `audit` tails the supervisor log for an app. `actions` is the pilotctl-side install/uninstall log. Both `audit` and `actions` accept `--tail <n>` and `--event <name>`, and `audit` accepts `--since <dur>`. `install --force` keeps the app's state (keys, `data.db`, secrets, cap state, audit log); add `--reset-state` only when you want the app to start empty.
The supervisor respawns an app when its `app_version` changes.
Apps are versioned in the catalogue. Two commands find and apply new versions.
pilotctl appstore outdated # installed apps with a newer — or rebuilt — catalogue version
pilotctl appstore upgrade io.pilot.cosift # re-install one app: verified, then respawned
pilotctl appstore upgrade --all # bring every installed app up to date`upgrade` re-runs the verified install process and restarts the app, refusing downgrades. `outdated` flags an app on a version bump or when a same-version bundle was republished.
The updater service is off by default. After you explicitly enable `pilotctl update`, it keeps the CLI and daemon current and includes installed app-adapter updates by default.
On an updater-enabled host, set `PILOT_APP_UPDATE_OPT_OUT=true` to keep app adapters pinned. This does not enable the updater service itself.
Set this variable in the updater's service environment and restart the updater.
# Linux (systemd): set the env var on the pilot-updater service
sudo systemctl edit pilot-updater
# [Service]
# Environment=PILOT_APP_UPDATE_OPT_OUT=true
sudo systemctl restart pilot-updater
# macOS (launchd): add PILOT_APP_UPDATE_OPT_OUT to the plist's EnvironmentVariables, then reload
launchctl unload ~/Library/LaunchAgents/network.pilotprotocol.pilot-updater.plist
launchctl load ~/Library/LaunchAgents/network.pilotprotocol.pilot-updater.plistWith `PILOT_APP_UPDATE_OPT_OUT=true`, the updater no longer updates apps. Pilot's daemon and CLI binaries continue to update. Manual upgrades are still possible.
To re-enable, remove `PILOT_APP_UPDATE_OPT_OUT` from the updater's environment or set it to `false`, then restart the updater.
`PILOT_APP_UPDATE_OPT_OUT` reads `1`, `true`, `yes`, or `on` (case-insensitive) as opted-out. The value is read when the updater starts. The legacy `PILOT_UPDATER_NO_APP_UPGRADE` variable still works as an alias.
An app is a binary that communicates over a socket using the app-store IPC protocol. The manifest declares its identity, methods, binary hash, and required grants.
You don't write the adapter by hand: `pilot-app`, from pilot-protocol/app-template, generates it from a short `pilot.app.yaml` that points at your HTTP API or CLI. For reference, this is what it generates:
{
"id": "io.pilot.cosift",
"app_version": "0.1.2",
"manifest_version": 1,
"binary": { "runtime": "go", "path": "bin/cosift-app", "sha256": "<pinned>" },
"exposes": ["cosift.search", "cosift.answer", "cosift.research",
"cosift.stats", "cosift.health", "cosift.help"],
"grants": [
{ "cap": "net.dial", "target": "cosift.pilotprotocol.network",
"if": { "kind": "rate", "params": { "per": "min", "limit": 120 } } },
{ "cap": "fs.read", "target": "$APP/config.json" },
{ "cap": "audit.log", "target": "*" }
],
"protection": "shareable",
"store": { "publisher": "ed25519:...", "signature": "..." }
}The binary registers a handler for each method. In Go, this uses the `app-store/pkg/ipc` contract.
d := ipc.NewDispatcher()
d.Register("cosift.search", func(ctx, req) (json.RawMessage, error) { ... })
// ... one Register per exposed method ...
ipc.Serve(ctx, conn, d) // on the --socket the daemon suppliesThe daemon spawns the binary with a fixed set of lifecycle flags: `--socket`, `--manifest`, `--addr`, `--db`, `--identity`, `--cap-state`. An app must accept all of them. Method names in the code must match the manifest's `exposes` list.
Apps are published through one repository, pilot-protocol/app-template: describe your API in a spec, let `pilot-app` build and sign the bundle, and open a pull request.
# 1. Install the scaffolder and describe your API
go install github.com/pilot-protocol/app-template/cmd/pilot-app@latest
pilot-app example > pilot.app.yaml # starter spec, fully annotated
pilot-app validate
# 2. Generate the adapter, then build, pin, sign and package it
pilot-app init -o ./my-app
cd my-app
make gen-key # one-time ed25519 publisher key (keep it safe)
make package # build -> pin sha256 -> sign -> tarball
pilot-app verify io.pilot.<id>-<ver>.tar.gz # optional: run the review gate locally
# 3. Prepare the submission in your fork of app-template
pilot-app submit -C . --prepare /path/to/app-template-forkCommit `submissions/<id>/` in your fork and open a pull request to `pilot-protocol/app-template`. CI verifies the bundle and a maintainer reviews it. On merge, automation releases the bundle and opens its catalogue entry; once that entry is in the signed catalogue, `pilotctl appstore install <id>` resolves it everywhere. The form at /publish runs the same pipeline on Pilot's server instead of your machine.
Three integrity layers protect every install: the catalogue signature, the catalogue's tarball sha256 pin, and the manifest's binary sha256 pin. The binary sha256 is re-checked before every spawn.
The product demo and next-steps graph are authored once in the app's `submission.json` and are carried into the catalogue.
A `product_demo` block is a usage guide that renders at install and on the store page. It includes a `when_to_use` sentence, a `quickstart` call, 2–6 worked `examples`, and a cost table for metered apps.
A `next_steps` graph is a flat list of edges. Each edge defines recommended commands based on the outcome of a previous command.
{
"schema": 1,
"app": "io.pilot.example",
"edges": [
{
"from": "*",
"on": "err",
"code": 402,
"why": "budget exhausted",
"then": [
{ "cmd": "pilotctl appstore call io.pilot.example example.balance '{}'",
"why": "check what's left before spending again",
"kind": "recovery" }
]
}
]
}The best-matching edge wins on specificity. A recommendation is a pure function of `(method, outcome, payload)`.
Because the graph lives in the catalogue metadata, updates do not require an app rebuild and are picked up automatically by existing installs.
There are two installation paths with different trust models.
To stage a release locally, `PILOT_APPSTORE_CATALOG_URL` can be pointed at a `file://` catalogue.
`pilotctl appstore verify <bundle-dir>` validates a bundle without installing it.
Trust flows from a signed catalogue, through a signed and reviewed manifest, to a supervised, resource-limited child process. The daemon's broker gates every call that passes through it. OS-level sandboxing (landlock, seccomp) is not wired yet, so an app's binary is not walled off at the syscall level; the review is what stands behind its declared grants.
The catalogue is signed with a dedicated ed25519 key whose public half is compiled into `pilotctl` and the daemon. `pilotctl` fetches both `catalogue.json` and `catalogue.json.sig` and verifies the signature before trusting any entry. An unsigned or tampered catalogue is refused. The signing key can be rotated at build time.
go build -ldflags \
"-X .../internal/catalogtrust.publicKeyB64=<new-b64-pubkey>" \
./cmd/pilotctl ./cmd/daemonAll calls go through the daemon's broker, which enforces two gates before dispatch.
The supervisor that spawns and watches each app applies several defenses.
Apps may register hooks on daemon primitives. The hook surface is bounded by per-app rate limiting and a cap on the number of dynamic hook registrations.
The cosift app is a stateless adapter to Cosift's search, answer, and research API over a web corpus. It exposes several utility methods plus status and discovery methods.
# Discover the surface + latencies
pilotctl appstore call io.pilot.cosift cosift.help '{}'
# search (fast) - ranked URLs + excerpts
pilotctl appstore call io.pilot.cosift cosift.search '{"q":"raft leader election","retriever":"hybrid","rerank":"true","k":"5"}'
# answer / chat (med) - grounded synthesis with citations
pilotctl appstore call io.pilot.cosift cosift.answer '{"q":"What is HNSW?"}'
# research (slow) - plan -> multi-retrieval -> report
pilotctl appstore call io.pilot.cosift cosift.research '{"q":"compare raft and paxos"}'The cosift backend is open source (pilot-protocol/cosift). The app is the hand-written reference that `pilot-app` generates the equivalent of.