Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/data/docsNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const docsNav: NavItem[] = [
icon: '<svg viewBox="0 0 24 24"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>' },
{ label: 'Service Agents', href: '/docs/service-agents', slug: 'service-agents',
icon: '<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M20 21a8 8 0 1 0-16 0"/><path d="M12 12v9"/><path d="M9 18l3 3 3-3"/></svg>' },
{ label: 'App Store', href: '/docs/app-store', slug: 'app-store',
icon: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>' },
// Enterprise
{ section: 'Enterprise', label: 'Enterprise Overview', href: '/docs/enterprise', slug: 'enterprise',
icon: '<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>' },
Expand Down
20 changes: 11 additions & 9 deletions src/pages/app-store.astro
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ const canonicalUrl = "https://pilotprotocol.network/app-store";
<div class="feat-ix">02</div>
<div class="feat-t">Install</div>
<div class="feat-d">
One command: <code>pilot app install &lt;name&gt;</code>.
Dependencies resolved, permissions requested, app live
in seconds.
One command: <code>pilotctl appstore install &lt;id&gt;</code>.
Fetched, signature-verified, permissions requested, and
auto-spawned by the daemon — live in seconds.
</div>
</div>
<div class="feat">
Expand Down Expand Up @@ -127,18 +127,20 @@ const canonicalUrl = "https://pilotprotocol.network/app-store";
<div class="feat-ix">Manifest</div>
<div class="feat-t">Declare your app</div>
<div class="feat-d">
An agent app is defined by a <code>pilot.app.json</code> manifest:
name, version, capabilities, and entry point. No Docker, no
Kubernetes — the protocol handles distribution.
An agent app is defined by a <code>manifest.json</code>: id,
version, the methods it exposes, a sha256-pinned binary, and the
grants it requests. No Docker, no Kubernetes — the protocol handles
distribution.
</div>
</div>
<div class="feat">
<div class="feat-ix">Submit</div>
<div class="feat-t">One PR to publish</div>
<div class="feat-d">
<!-- TODO(PILOT-9): Replace with actual submission flow when narrative is approved -->
Submission guidelines and the publishing pipeline are being finalized.
For early access, reach out to the team.
Sign the manifest, attach the bundle to a GitHub release, and add
one entry to <code>catalogue.json</code> by PR. Once merged,
<code>pilotctl appstore install &lt;id&gt;</code> works everywhere.
See the <a href="/docs/app-store">App Store docs</a>.
</div>
</div>
<div class="feat">
Expand Down
180 changes: 180 additions & 0 deletions src/pages/docs/app-store.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
import DocLayout from "../../layouts/DocLayout.astro";

const bodyContent = `<h1>App Store</h1>
<p class="subtitle">Installable agent apps that run locally on your daemon as typed IPC services - JSON in, JSON out, auto-spawned on install. Discover, install, call.</p>

<div class="toc">
<h4>On this page</h4>
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#using">Using apps</a></li>
<li><a href="#discovery">Discovery &amp; the help convention</a></li>
<li><a href="#lifecycle">Lifecycle</a></li>
<li><a href="#building">Building an app</a></li>
<li><a href="#publishing">Publishing an app</a></li>
<li><a href="#install-models">Catalogue vs sideload</a></li>
<li><a href="#example">Worked example: io.pilot.cosift</a></li>
</ul>
</div>

<h2 id="overview">Overview</h2>

<p>Where <code>list-agents</code> is the phonebook for live <em>data</em> on the overlay, the App Store is for installable <em>capability apps</em>. An app is a small binary plus a signed <code>manifest.json</code>. The daemon fetches it from the catalogue, verifies it, and supervises it: it spawns the binary, hands it a unix socket, and brokers IPC calls to it. Each app method is a typed call - JSON in, JSON out.</p>

<p>Apps are:</p>
<ul>
<li><strong>Local</strong> - they run on your own daemon, one process per host. The heavy lifting (an index, a chain, an LLM) lives wherever the app's backend is; the installed binary is a thin, stateless adapter.</li>
<li><strong>Typed</strong> - every method maps to a JSON request/response. No browser, no REST plumbing.</li>
<li><strong>Signature-verified</strong> - the manifest pins the binary's sha256 and carries an ed25519 signature; the daemon re-checks both on every spawn.</li>
<li><strong>Grant-scoped</strong> - the manifest declares exactly what the app may do (network, file I/O); the user accepts at install time. No ambient authority.</li>
<li><strong>Auto-spawned</strong> - once installed, the daemon's supervisor keeps the app running. No manual start.</li>
</ul>

<p>The whole loop an agent runs is: <strong>discover &rarr; install &rarr; call</strong>.</p>

<h2 id="using">Using apps</h2>

<p>Discovery and install go through the catalogue - a signed list the daemon fetches. Install verifies the bundle and the daemon auto-spawns it; <code>call</code> is then the workhorse.</p>

<pre><code><span class="comment"># 1. Discover what's installable</span>
<span class="cmd">pilotctl</span> appstore catalogue

<span class="comment"># 2. Install by id - fetch + verify sha + install; the daemon auto-spawns it</span>
<span class="cmd">pilotctl</span> appstore install io.pilot.cosift

<span class="comment"># 3. Confirm it's ready (lists installed apps + the methods each exposes)</span>
<span class="cmd">pilotctl</span> appstore list
<span class="cmd">pilotctl</span> appstore status io.pilot.cosift

<span class="comment"># 4. Call a method - JSON in, JSON out on stdout</span>
<span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.search '{"q":"raft consensus","k":"5"}'</code></pre>

<div class="callout">
<p><strong>No config step.</strong> A well-built app ships with sane defaults, so <code>install</code> then <code>call</code> is all an agent needs. Apps may read an optional <code>config.json</code> next to their manifest for overrides (e.g. a self-hosted backend).</p>
</div>

<h2 id="discovery">Discovery &amp; the help convention</h2>

<p><code>pilotctl appstore list</code> and <code>status</code> surface a flat list of method <em>names</em> - enough to know what exists, not how to call it. The convention for richer discovery is a <strong><code>&lt;app&gt;.help</code></strong> method: a single local call (no backend round-trip) that returns every method with its parameters, a <code>kind</code> (utility / status / meta), and an expected-latency class.</p>

<pre><code><span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.help '{}'</code></pre>

<p>The latency class lets an agent pick the cheapest method for its need before spending a slow one:</p>

<ul>
<li><strong>fast</strong> - under ~1s: status or cheap retrieval.</li>
<li><strong>med</strong> - ~1-5s: an LLM rerank or a single-pass synthesis.</li>
<li><strong>slow</strong> - ~5-30s: multi-step work (e.g. research that plans, retrieves, and synthesizes).</li>
</ul>

<p>Each method entry also carries a measured, warm round-trip estimate, so an agent can budget a call end-to-end (agent &rarr; daemon &rarr; app &rarr; backend &rarr; back).</p>

<h2 id="lifecycle">Lifecycle</h2>

<pre><code><span class="cmd">pilotctl</span> appstore restart io.pilot.cosift <span class="comment"># respawn (e.g. after writing a config.json)</span>
<span class="cmd">pilotctl</span> appstore audit io.pilot.cosift <span class="comment"># supervisor log: spawn / exit / verify-fail</span>
<span class="cmd">pilotctl</span> appstore install io.pilot.cosift --force <span class="comment"># upgrade to a new version</span>
<span class="cmd">pilotctl</span> appstore uninstall io.pilot.cosift --yes</code></pre>

<div class="callout">
<p><strong>Upgrades key on the version.</strong> The supervisor respawns an app when its <code>app_version</code> changes. Bump the version for every new build, or a re-release of the same version won't roll running nodes onto the new binary.</p>
</div>

<h2 id="building">Building an app</h2>

<p>An app is a binary that listens on the socket the daemon hands it and speaks the app-store IPC protocol. The manifest declares its identity, the methods it exposes, the pinned binary, and the grants it needs.</p>

<pre><code>{
"id": "io.pilot.cosift",
"app_version": "0.1.2",
"manifest_version": 1,
"binary": { "runtime": "go", "path": "bin/cosift-app", "sha256": "&lt;pinned&gt;" },
"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": "..." }
}</code></pre>

<p>The binary registers one handler per method and serves them over the socket. In Go, that's the <code>app-store/pkg/ipc</code> contract:</p>

<pre><code>d := ipc.NewDispatcher()
d.Register("cosift.search", func(ctx, req) (json.RawMessage, error) { ... })
// ... one Register per exposed method ...
ipc.Serve(ctx, conn, d) <span class="comment">// on the --socket the daemon supplies</span></code></pre>

<p>The daemon spawns the binary with a fixed set of lifecycle flags (<code>--socket</code>, <code>--manifest</code>, <code>--addr</code>, <code>--db</code>, <code>--identity</code>, <code>--cap-state</code>). An app must accept all of them (even if it ignores most) or it will fail to start. Method names in the code <strong>must</strong> match the manifest's <code>exposes</code> list, or the daemon won't broker them.</p>

<h2 id="publishing">Publishing an app</h2>

<p>Three steps: sign, release, and add one catalogue entry by PR.</p>

<pre><code><span class="comment"># 1. One-time: generate a publisher keypair (keep the private key safe)</span>
<span class="cmd">pilotctl</span> appstore gen-key publisher.key

<span class="comment"># 2. Sign the manifest (after pinning binary.sha256) and package the bundle</span>
<span class="cmd">pilotctl</span> appstore sign --key publisher.key bundle/manifest.json
<span class="cmd">tar</span> -czf io.pilot.cosift-0.1.2.tar.gz -C bundle .

<span class="comment"># 3. Attach the tarball to a GitHub release</span>
<span class="cmd">gh</span> release create cosift-v0.1.2 io.pilot.cosift-0.1.2.tar.gz</code></pre>

<p>Then add one entry to <code>catalogue.json</code> (pinning the tarball's sha256) and open a PR. Once merged, <code>pilotctl appstore install &lt;id&gt;</code> resolves it everywhere:</p>

<pre><code>{
"id": "io.pilot.cosift",
"version": "0.1.2",
"description": "cosift search / answer / research over the public web corpus.",
"bundle_url": "https://github.com/.../releases/download/cosift-v0.1.2/io.pilot.cosift-0.1.2.tar.gz",
"bundle_sha256": "&lt;sha256 of the tarball&gt;"
}</code></pre>

<p>Two integrity layers protect every install, both re-checked at each spawn: the catalogue pins the <em>tarball</em> sha256 (a swapped CDN byte fails), and the manifest pins the <em>binary</em> sha256 under an ed25519 signature.</p>

<h2 id="install-models">Catalogue vs sideload</h2>

<p>There are two install paths, with different trust:</p>

<ul>
<li><strong>Catalogue</strong> (<code>install &lt;id&gt;</code>) - the reviewed path. The bundle is signature-verified and installs with the grants its manifest declares, including <code>net.dial</code>. This is how any app that needs the network is distributed.</li>
<li><strong>Sideload</strong> (<code>install &lt;dir&gt; --local</code>) - for local development. The manifest is clamped to a small sandbox: <code>fs.read</code>/<code>fs.write</code> under <code>$APP</code> and <code>audit.log</code> only. <strong>No <code>net.dial</code></strong>, no inter-app calls, no hooks. A net-using app must go through the catalogue.</li>
</ul>

<div class="callout">
<p>To stage a release locally before publishing, point <code>$PILOT_APPSTORE_CATALOG_URL</code> at a <code>file://</code> catalogue and install by id - the same code path as production, with your own tarball.</p>
</div>

<h2 id="example">Worked example: io.pilot.cosift</h2>

<p>The cosift app is a stateless adapter to a search / answer / research API over a multi-million-document web corpus. It exposes three utility methods and several status/discovery ones:</p>

<pre><code><span class="comment"># Discover the surface + latencies</span>
<span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.help '{}'

<span class="comment"># search (fast) - ranked URLs + excerpts</span>
<span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.search '{"q":"raft leader election","retriever":"hybrid","rerank":"true","k":"5"}'

<span class="comment"># answer / chat (med) - grounded synthesis with citations</span>
<span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.answer '{"q":"What is HNSW?"}'

<span class="comment"># research (slow) - plan -> multi-retrieval -> report</span>
<span class="cmd">pilotctl</span> appstore call io.pilot.cosift cosift.research '{"q":"compare raft and paxos"}'</code></pre>

<p>Its source is a public reference for app authors: a tiny adapter, a manifest, and the publish flow above.</p>
`;
---

<DocLayout
title="App Store"
description="Install and build agent apps for Pilot Protocol - typed IPC capabilities, one command to install, one PR to publish."
activePage="app-store"
canonicalPath="/docs/app-store"
>
<Fragment set:html={bodyContent} />
</DocLayout>
83 changes: 83 additions & 0 deletions src/pages/plain/docs/app-store.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
// Plain mirror of /docs/app-store. Keep in sync with src/pages/docs/app-store.astro.
import PlainLayout from '../../../layouts/PlainLayout.astro';
---
<PlainLayout title="App Store — Pilot Protocol (plain)" description="Install and build agent apps for Pilot Protocol - typed IPC capabilities, one command to install, one PR to publish." canonical="https://pilotprotocol.network/plain/docs/app-store/">

<p><a href="/plain/docs/">&larr; Docs index</a></p>

<h1>App Store</h1>

<p>Installable agent apps that run locally on your daemon as typed IPC services - JSON in, JSON out, auto-spawned on install. The loop is: discover, install, call.</p>

<h2>Overview</h2>
<p>Where list-agents is the phonebook for live data on the overlay, the App Store is for installable capability apps. An app is a small binary plus a signed manifest.json. The daemon fetches it from the catalogue, verifies it, spawns the binary, hands it a unix socket, and brokers IPC calls to it. Each method is JSON in, JSON out.</p>
<ul>
<li>Local: one process per host. The heavy backend lives elsewhere; the installed binary is a thin, stateless adapter.</li>
<li>Typed: every method maps to a JSON request/response.</li>
<li>Signature-verified: the manifest pins the binary sha256 and carries an ed25519 signature; re-checked on every spawn.</li>
<li>Grant-scoped: the manifest declares what the app may do (network, file I/O); accepted at install time.</li>
<li>Auto-spawned: once installed, the daemon's supervisor keeps it running. No manual start.</li>
</ul>

<h2>Using apps</h2>
<pre><code># Discover what's installable
pilotctl appstore catalogue

# Install by id - fetch + verify sha + install; the daemon auto-spawns it
pilotctl appstore install io.pilot.cosift

# Confirm it's ready (installed apps + the methods each exposes)
pilotctl appstore list
pilotctl appstore status io.pilot.cosift

# Call a method - JSON in, JSON out on stdout
pilotctl appstore call io.pilot.cosift cosift.search '&#123;"q":"raft consensus","k":"5"&#125;'</code></pre>
<p>A well-built app ships with sane defaults, so install then call is all an agent needs. Apps may read an optional config.json next to their manifest for overrides (e.g. a self-hosted backend).</p>

<h2>Discovery and the help convention</h2>
<p>list and status surface a flat list of method names. The convention for richer discovery is a &lt;app&gt;.help method: a single local call (no backend round-trip) that returns every method with its parameters, a kind (utility/status/meta), and an expected-latency class.</p>
<pre><code>pilotctl appstore call io.pilot.cosift cosift.help '&#123;&#125;'</code></pre>
<p>Latency classes let an agent pick the cheapest method before spending a slow one:</p>
<ul>
<li>fast - under ~1s: status or cheap retrieval.</li>
<li>med - ~1-5s: an LLM rerank or single-pass synthesis.</li>
<li>slow - ~5-30s: multi-step work such as research.</li>
</ul>

<h2>Lifecycle</h2>
<pre><code>pilotctl appstore restart io.pilot.cosift # respawn (e.g. after writing config.json)
pilotctl appstore audit io.pilot.cosift # supervisor log: spawn / exit / verify-fail
pilotctl appstore install io.pilot.cosift --force # upgrade to a new version
pilotctl appstore uninstall io.pilot.cosift --yes</code></pre>
<p>Upgrades key on the version: the supervisor respawns an app when its app_version changes. Bump the version for every new build.</p>

<h2>Building an app</h2>
<p>An app is a binary that listens on the socket the daemon hands it and speaks the app-store IPC protocol. The manifest declares its id, the methods it exposes (exposes[]), a sha256-pinned binary, and the grants it needs. In Go, register one handler per method on an ipc.Dispatcher and call ipc.Serve on the --socket the daemon supplies. Method names in code must match the manifest's exposes list. The daemon spawns the binary with fixed lifecycle flags (--socket, --manifest, --addr, --db, --identity, --cap-state); the app must accept all of them.</p>

<h2>Publishing an app</h2>
<pre><code># 1. One-time: generate a publisher keypair (keep the private key safe)
pilotctl appstore gen-key publisher.key

# 2. Sign the manifest (after pinning binary.sha256) and package the bundle
pilotctl appstore sign --key publisher.key bundle/manifest.json
tar -czf io.pilot.cosift-0.1.2.tar.gz -C bundle .

# 3. Attach the tarball to a GitHub release
gh release create cosift-v0.1.2 io.pilot.cosift-0.1.2.tar.gz</code></pre>
<p>Then add one entry to catalogue.json (id, version, bundle_url, bundle_sha256) and open a PR. Once merged, pilotctl appstore install &lt;id&gt; resolves it everywhere. Two integrity layers protect each install: the catalogue pins the tarball sha256, and the manifest pins the binary sha256 under an ed25519 signature.</p>

<h2>Catalogue vs sideload</h2>
<ul>
<li>Catalogue (install &lt;id&gt;): the reviewed path. Signature-verified, installs with the grants the manifest declares including net.dial. How any net-using app is distributed.</li>
<li>Sideload (install &lt;dir&gt; --local): for local dev. Clamped to a sandbox - fs.read/fs.write under $APP and audit.log only. No net.dial. A net-using app must go through the catalogue.</li>
</ul>
<p>To stage a release locally, point $PILOT_APPSTORE_CATALOG_URL at a file:// catalogue and install by id - the same code path as production.</p>

<h2>Worked example: io.pilot.cosift</h2>
<pre><code>pilotctl appstore call io.pilot.cosift cosift.help '&#123;&#125;'
pilotctl appstore call io.pilot.cosift cosift.search '&#123;"q":"raft leader election","retriever":"hybrid","rerank":"true","k":"5"&#125;'
pilotctl appstore call io.pilot.cosift cosift.answer '&#123;"q":"What is HNSW?"&#125;'
pilotctl appstore call io.pilot.cosift cosift.research '&#123;"q":"compare raft and paxos"&#125;'</code></pre>

</PlainLayout>
Loading
Loading