Skip to content

Personal-data agent: ship it as an App (app-store demo) + the app→orchestrator consent gate #99

Description

@NotASithLord

@jonybur — a direction call on where the local-first personal-data agent lands (your spec: docs/specs/LOCAL-FIRST-PERSONAL-DATA-AGENT.md). Short version: build it as an App, not just a chat skill-recipe. That's deliberate, and it has a dependency. Details below.

Why an App

The chat-recipe version (harvest → js_notebook query → answer) is fine for the solo case, but it's a prompt, not a place. As an App it becomes something you open and come back to — a dashboard over your own data you can re-query without re-prompting the model.

The bigger reason is distribution: a personal-data App is something we can preload over the network into the app store as a flagship demo. A visitor installs it and it fills itself in from their own accounts, entirely on-device. That's the "try it and get it" moment we want to seed the store with.

And it makes the privacy pitch stronger, not weaker: an opaque-origin App with no network egress physically can't exfiltrate, so handing it your freshly-harvested data is safe by construction. "Nothing leaves your machine" stops being a promise and becomes a property of the container.

The seam: the harvest can't live in the App

An App is an opaque-origin iframe with no access to your real tabs or logged-in sessions — by design, that's why it's safe to run untrusted code. So it can't do the harvest itself; that's the do/get/check runner, which is extension-privileged. The harvest stays agent-side. Which means a preloaded App needs a way to ask the agent to do the privileged part for it.

The mechanism (app → orchestrator, user-gated)

  1. The App calls an internal peerd.* capability the resident has — something like peerd.requestOrchestration(intent). The App can only trigger it.
  2. peerd renders a permission prompt in its own trusted UI — not inside the iframe. The App must never be able to draw this. It shows the user exactly what's being requested.
  3. User approves → it's passed as a message to the orchestrator, which does the work (delegates the harvest to residents).
  4. Results come back to the app-resident, which finishes building the on-device store / dashboard.

One-shot to start — every harvest re-asks. Keep it simple. We can add a scoped, revocable per-app grant later (DECISIONS #21 territory) if the re-ask friction gets annoying, but for "read my bank tab," that friction is arguably the feature.

Controls that have to be in v1

This is reading your authenticated accounts, so the gate has to be real:

  • Un-spoofable consent. The prompt is peerd chrome outside the App's iframe — the browser-permission model (the App can ask, only peerd can grant). If the App can render its own "approve" button, we've lost.
  • Show the capability scope, not just the prompt text. What the user is really approving is an untrusted App injecting an instruction into their trusted agent. The prose can read benign while the tools it invokes aren't. So the consent screen should say what the delegated turn can reach ("will read logged-in tabs + write on-device storage; will NOT send anything off-device"), derived from the allowed toolset — not the words. For the preloaded-demo case especially, I'd lean toward structured intents (the App picks harvest:orders from a constrained vocabulary) over freeform prompts — far easier to render an honest consent screen for.
  • Restricted posture for the delegated turn. Run it under the runner's untrusted fence, a tight gate set, no privilege escalation, and it can't itself re-delegate.
  • Provenance in the prompt. Bundles are already signed and content-addressed, so the consent screen can show "signed by peerd" vs "unsigned / anonymous." An anonymous App asking to read your bank tab should feel different from a first-party one.

Dependency: this rides on the DESIGN-17 resident refactor (#61)

This isn't a separate system — it's the inbound clamp that #61's spec explicitly deferred. The sender gate there already blocks inbound/untrusted requests by default (inbound = synthetic && !trusted); this consent flow is the user-mediated way an inbound App request gets promoted to trusted. So it lands after #61, and the requestOrchestration capability should be designed as part of that surface, not bolted on. Flagging it as a hard dependency.

Net

Personal-data agent → an App. Harvest stays agent-side. The App bootstraps itself via a one-shot, user-approved orchestration request. Ships on top of #61. This is the path that gets us a real preloadable app-store demo instead of a prompt people have to know to type.

React on any of it — especially the structured-intents-vs-freeform call and whether one-shot holds up once you start building the demo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions