Outposts & the BYO worker model

The quickstart walks the commands; this page explains why they're shaped that way. Four ideas carry most of the design.

The Outpost is the sole tenant boundary

An Outpost is the one unit Pnyon authorizes, isolates, and scopes against. Every stored vector, every mission, and every token carries an Outpost id, and the store filters on it — a caller scoped to one Outpost can never read or write another's rows. There is no tenant granularity above or below it.

A repository is not a tenant. One Outpost holds many repositories; a repository is just a metadata label on the rows within an Outpost, not a separate isolation boundary. This is what lets context earned in one repo become queryable memory for an agent working in another — they share an Outpost, so they share a memory space, while everything outside the Outpost stays sealed off.

Bring your own worker, and attest to it

Rather than run your code on shared compute, you run the pnyon agent on a machine you control. It has to prove itself before Pnyon will route work to it, and today it does so by self-attestation:

  • On first pnyon worker connect, the CLI generates an ed25519 keypair under ~/.pnyon/. The private key stays on your machine (0600, never printed); only the public key leaves, once, inside your registration request.
  • Your steward vouches for you out-of-band, then trusts your public key and mints a runner token scoped to your Outpost. An untrusted key is rejected outright.

This is honest about what it proves: it proves you control the key your steward trusted — not that you're running an unmodified official build. That's an interim model. When the signed- release pipeline lands, the trust root moves from per-adopter keys to the release signer's key, and per-adopter key generation is retired.

Outposts are addressed in the URL path

Your worker connects with a plain WebSocket, and a standard WebSocket client can't set request headers on the opening handshake. So the Outpost isn't carried in a header — it lives in the URL path the client fully controls:

wss://dispatch.pnyon.com/outpost/<your-outpost-id>

The same rule holds for the HTTP submit path (/outpost/<id>/embed). One routing rule for both transports, with the Outpost as a first-class path segment. The Outpost id is a non-secret routing identifier; the submit token stays the secret and is carried separately.

Fail-closed by default

Every gate in the path denies rather than fakes. A missing or malformed Outpost segment is rejected. An unknown, expired, or wrong-scope token resolves to nothing and is denied. An untrusted attestation key is refused. A missing or wrong submit token answers 401 before any embed work runs. And if any invariant fails while producing an embedding, the system errors and persists nothing — it never returns a fabricated vector.

The practical consequence: when something isn't wired correctly, you get a clear rejection, not silently wrong data. That's the posture the whole BYO model is built to preserve.