PAP doesn't plug into Okta or Active Directory — and that's intentional. The trust anchor is a device-bound Ed25519 keypair held by the individual principal. Every agent session runs under a time-limited Mandate signed by that key — no central hub decides access. When someone leaves your org, there's nothing to deprovision: mandates decay automatically (Active → Degraded → ReadOnly → Suspended) and cryptographically expire on their own.
Enterprise structure is expressed at the agent layer, not the identity layer. A company runs agents in Chrysalis and routes them to principals through normal PAP transport — org-level orchestration without encoding hierarchy into identity.
Key resilience: M-of-N social recovery (crates/pap-core/src/shamir.rs) lets a principal split their seed into N Shamir shards distributed to trustees they choose. Any M shards reconstruct the identity; fewer than M reveal nothing. Default is 2-of-3, fully configurable. Trustees are people the principal selects — not a platform.
The SAML bridge question assumes a hub-and-spoke model PAP is designed to make unnecessary. Instead of a central authority managing who has access, each agent protects itself — it declares what it requires, your orchestrator presents a cryptographically scoped mandate, and access is enforced at the edge with no IdP involved. When a mandate expires (default 8 hours), access stops without a deprovisioning call or a session token to revoke. For access tied to value exchange, the ecash layer issues blind-signed tokens using RFC 9474 RSABSSA-SHA384-PSS — the agent validates the token without learning who holds it, and receipts store only a commitment hash, never amounts or identities. What your security team actually needs maps to protocol mechanisms: access policy in mandate scope, audit trail in co-signed receipts (property type references, held locally by both parties), revocation through TTL decay.
The IdP question carries a centralized assumption PAP is designed to make unnecessary. Okta and Azure AD solve a hub-and-spoke problem: a central authority that decides who gets access to what. PAP inverts this. Agents protect themselves — they advertise what they require, principals present cryptographically scoped mandates, and the protocol enforces access at the edge without any central authority in the loop.
The PAP-native pattern for enterprise agent access control:
An enterprise runs agents on Chrysalis. Each agent declares its
requires_disclosure — the minimum SD-JWT claims it needs to execute
(e.g. schema:Organization.schema:memberOf to verify employment, or
a schema:Role claim scoped to a department). A principal's orchestrator
presents only those claims via selective disclosure
(crates/pap-credential/src/sd_jwt.rs); the agent verifies the
claim against its policy and either accepts the mandate or rejects it. No IdP
is in the path. No central session store. The agent is the access control point.
For value exchange between agents, ecash receipts replace
identity-gated permissions. Instead of "does this principal have the
Analyst role in Okta?", access is structured as "does this principal hold a valid
payment proof for this action?" The ecash system (crates/pap-ecash/)
issues blind-signed RFC 9474 RSABSSA-SHA384-PSS tokens — the agent validates
the token without learning who holds it, and the receipt proves the exchange
occurred without linking it to an identity. This is more privacy-preserving
than role-based access and eliminates the revocation problem entirely: a spent
token is simply gone.
What security teams actually need is answered by the protocol, not
by an IdP bridge: access policy is encoded in mandate scope
(Scope::deny_all() baseline, cryptographically enforced containment);
audit trail is the co-signed receipt chain (property references, never values,
held by both parties locally); revocation is
TTL decay
(Active → Degraded → ReadOnly → Suspended) with no deprovisioning
step required. The threat model of "who has a live session token?" doesn't apply
to a system where every mandate expires by design.
Most enterprise software achieves compliance by adding controls on top of a general-purpose system. PAP's compliance properties are protocol invariants — they hold because of how the cryptography works, not because someone configured them correctly. No central server. Receipts store property type references like "schema:Person.dateOfBirth", never the values. Every session runs under a mandate your device signed, specifying the exact agent, actions, data types, and duration. Access decays automatically without a deprovisioning step. For no-retention requirements, set no_retention: true on disclosure entries and the protocol requires the receiving agent to run in a TEE, making retention constraints auditable and binding. The 34 pre-built health and finance catalog agents (15 health, 19 finance) all require zero personal disclosure — public data sources only — giving you a no-PHI baseline to build from before you add anything sensitive.
Most enterprise software achieves compliance by adding controls on top of a general-purpose system. PAP's compliance properties are protocol invariants — they hold because of cryptographic enforcement, not configuration. Here is what that means per framework.
HIPAA — Minimum Necessary Rule: The mandate's DisclosureSet
(crates/pap-core/src/scope.rs) specifies exactly which
schema: properties an agent may receive. An agent requesting schema:SSN when the mandate permits
only schema:dateOfBirth is filtered out by the marketplace before any
session is established — the over-disclosure attempt never reaches the transport layer.
For no-retention requirements, set no_retention: true on disclosure
entries; the session can be backed by a Trusted Execution Environment
(crates/pap-tee/) — the TEE provides enclave measurement attestation
that the session ran in an isolated environment, making the retention commitment
auditable and binding rather than contractual-only. Audit trail: co-signed receipts in
pap-core/src/receipt.rs record property type references
("schema:Person.schema:dateOfBirth"), never values — satisfying
§164.312(b) while being safer than access logs containing real data.
GDPR — Purpose Limitation and Erasure: A PAP mandate is
structured, machine-enforceable consent: the principal signs which agent may act,
which actions it may take (Scope::deny_all() baseline), which properties
it may see, and for how long. Non-renewal of a mandate initiates the decay state
machine (Active → Degraded → ReadOnly → Suspended): access cryptographically
expires without any deprovisioning step. The episode store
(papillon-shared/src/episode_db.rs) is a local SQLite database under
the principal's sole control — no platform coordinates erasure. Portability: PAP uses
no central registry; the principal's did:key is self-sovereign and moves
to any compatible orchestrator.
PCI-DSS — Access Control and Payment Privacy:
Mandate scope containment is cryptographically enforced at every delegation step
(Scope::contains() in pap-core/src/scope.rs); a child mandate
cannot exceed its parent's scope or TTL regardless of configuration. For payment
agents (schema:PayAction), mandates carry payment proofs from the ecash
system (crates/pap-ecash/) using RFC 9474 RSABSSA-SHA384-PSS blind signatures —
the vendor cannot link the signing operation to the redemption, and receipts store
only a commitment hash, never amounts or card data.
SOC2 Type II in summary: CC3.1 (access control) — deny-by-default
scope, TTL bounds. CC6.1 (logical controls) — ephemeral session keys, automatic
credential expiry. CC7.1 (audit records) — immutable co-signed receipts. Privacy
use limitation — no_retention flag enforces purpose limitation at
protocol level, with optional TEE attestation for stronger assurance. These map
to protocol mechanisms, not to policy documents that can drift.
Pre-built catalog agents exist for both verticals: healthcare agents cover FDA FAERS,
ClinicalTrials, PubMed, and MedlinePlus
(crates/pap-agents/catalog/health/); finance agents cover Nasdaq, ECB,
Treasury, and World Bank data (crates/pap-agents/catalog/finance/).
All public-data agents require zero personal disclosure
(requires_disclosure: []), giving you a no-PHI-exposure baseline to
build from.
The hard parts are protocol invariants, not configuration. There's no central PAP server — nothing to breach at the platform level. Every session runs under a time-limited permission that decays on its own (Active → Degraded → ReadOnly → Suspended). Receipts store which types of data were accessed — "schema:Person.dateOfBirth" — never actual values. The episode store is local SQLite under your control. For stricter requirements — HIPAA no-retention policies, MiFID audit windows — those are configurable at deployment. Set no_retention: true on a disclosure entry and the protocol requires the receiving agent to run in a TEE, making the retention constraint cryptographically auditable rather than contractual.
Receipts contain property references only, never values
(pap-core/src/receipt.rs: Vec<String> of strings like
"schema:Person.name"). Each party holds their own copy locally —
receipts are never stored by a platform. There is no central data controller.
The compliance behaviors below are programmable at the deployment layer,
not gaps in the spec.
GDPR Article 17 (right to erasure): The episode store
(episode_db.rs) is a local SQLite database under the principal's
sole control. A GDPR-compliant deployment can implement a retention policy or
deletion API directly against the local DB — there is no platform or third party
to coordinate with. Immutability of the co-signed receipt is the default because
it provides the strongest accountability, but the deployment controls the store.
HIPAA Minimum Necessary: Property type references in receipts
can themselves constitute PHI. Knowing MedicalCondition was disclosed
reveals a medical relationship even without the value. A PAP-HIPAA deployment
profile can restrict which schema: types are loggable and configure
receipt retention periods — the mandate scope mechanism already enforces property-type
minimisation at handshake time.
MiFID II / Dodd-Frank: Ephemeral session DIDs are the default for privacy; a regulated deployment can configure session DID persistence and set mandate TTL to cover 7-year reconstruction windows. The protocol does not prevent long retention — it makes short retention the default. Financial agent deployments configure what they need.
Meaningful work, not a one-liner. PAP doesn't sit on top of your stack — it sits below it, as the trust layer your orchestration calls into before any agent executes. That means wrapping your existing agent dispatch paths with mandate issuance, connecting your session handling to the 6-phase handshake, and deciding how much of Chrysalis you self-host alongside your current infrastructure. None of that is inherently complicated, but it requires deliberate architecture, not a drop-in install.
The fastest path in is Python:
pip install pap-protocol
Pre-built wheels for Linux x86_64/aarch64, macOS universal2, and Windows x64 — no Rust toolchain needed. From there, docs/integration-guides/langchain-crewai-mcp.md walks through wiring PAP mandate issuance into a LangChain chain, a CrewAI crew, or an MCP server. The guide is 1,293 lines of annotated examples covering async, concurrent handshakes, and error propagation.
SDK coverage across languages: The core is Rust (crates/pap-core/), but production bindings exist for TypeScript (packages/pap-ts/, pure native async, zero Rust dependency), Python (PyO3, async-safe), C (crates/pap-c/, stable cdylib/staticlib), C# (via the C FFI layer), and Java (via the C FFI layer). If your stack is one of those, you're not writing FFI yourself.
What the integration actually involves:
Mandate issuance — wrap your existing "start agent task" calls to issue a PAP mandate first. The mandate specifies agent DID, action type, required disclosures, and TTL. Your orchestrator holds the principal's Ed25519 keypair and signs it. This is the core integration point.
Chrysalis registry — self-hostable alongside your current infrastructure. You can run a single-node Chrysalis deployment for your own agents before joining the federated mesh. The admission controls (vouching, probationary periods) are configurable per-deployment. Chrysalis docs →
Handshake transport — HTTP or WebSocket. Both are standard. The HTTP path is stateless per-phase (retry-safe); the WebSocket path shares one connection across all six phases (lower overhead for interactive sessions). Swap in your existing transport client.
For production deployments at scale, we work directly with teams on the integration — evaluating existing agent architecture, defining the mandate issuance points, and delivering working implementations. Work with us →
This is the right question to ask, because at first glance they do look similar. OAuth scopes define what an access token permits. Audit logs record what happened. PAP mandates define what this specific request permits — and co-signed receipts record what happened bilaterally, during the session, signed by both parties. The structural difference is when enforcement happens and who holds the proof.
OAuth is issuance-time policy. PAP is request-time cryptographic scope.
An OAuth access token is issued at login with a fixed scope (e.g. read:contacts write:calendar). Every request made with that token during its lifetime can use the full scope — regardless of which specific action is being taken, which agent is handling it, or how much data the current operation actually requires. Scope is set once at the authorization server, not per-request.
A PAP mandate is issued per request, signed by the principal's device-bound Ed25519 key. It specifies the exact agent DID, the exact schema:*Action type permitted, the exact set of data properties the agent may see (nothing more), and a TTL. An agent that receives a mandate for schema:SearchAction cannot use it to perform schema:CreateAction, even if both are conceptually within the user's overall permissions. Scope is cryptographically bound to the individual request.
OAuth tokens are bearer tokens. PAP mandates are principal-signed and hash-linked.
An OAuth bearer token can be used by any holder — if it's stolen or forwarded, the recipient has full access until expiry. The authorization server cannot distinguish the original caller from a downstream service holding the same token.
A PAP mandate is signed by the principal's did:key and hash-linked to its parent in the delegation chain (mandate.rs verify_chain()). A delegated mandate — one an orchestrator issues to a sub-agent — is cryptographically constrained to be a strict subset of its parent's scope and TTL (Scope::contains()). You cannot forward a mandate and silently expand what it permits. The chain is immutable and verifiable.
Audit logs are after-the-fact. Co-signed receipts are bilateral and produced during the session.
An audit log is written by the service — unilaterally, after execution. It records what the service says happened. The principal has no cryptographic proof of what was actually disclosed or executed; they have to trust the service's log.
A PAP co-signed receipt is produced at Phase 5 of the handshake, before the session closes. Both the orchestrator and the agent sign it. Both parties hold a local copy. It records which property types were disclosed (never values), which action was executed, and links to the mandate that authorized it. Neither party can later deny what occurred — and no platform holds the receipts. They're local to the participants.
OAuth doesn't bound multi-step agent delegation. PAP's chain containment does.
When an orchestrator delegates to a sub-agent, which delegates to another sub-agent, OAuth has no mechanism to enforce that each step stays within the original scope. Each layer can be issued its own token with potentially broader permissions, or tokens can be forwarded with the full original scope intact. The authorization model is flat.
PAP mandate chains are cryptographically monotone-decreasing: scope at step N+1 is always a strict subset of scope at step N. A child mandate cannot add permissions its parent doesn't have. This is enforced in pap-core/src/scope.rs at every verify_chain() call — it's not a policy setting. Multi-step agent pipelines cannot silently accumulate permissions as the chain grows.
The parts of PAP that enforce what an agent can do are cryptographic, not textual. Your device-signed key authorizes scope — no injected text can change that. The routing step has three layers: direct URL detection (deterministic), BM25 scoring (pure math — k1=1.5, b=0.75, no model), and on-device LLM fallback only when confidence drops below 0.35. Even at the LLM layer, the output must match one of a fixed list of permitted action strings from the agent catalog — a hallucinated action that isn't on the list is discarded. The model receives your query text and the list of allowed action types. It cannot see your mandate, your session keys, or any prior disclosed values.
The premise needs clarifying. Papillon's intent router is a three-level deterministic chain — no LLM is in the path until the optional third level:
- URL fast path (~0 µs) —
papillon-shared/src/intent.rsdetectspap://,pap+https://, and HTTPS URLs and dispatches them directly without touching the semantic layers. - BM25 semantic index (~50 µs) —
pap-agents/src/intent_index.rsscores the user query against every agent's catalog descriptor using Okapi BM25 (k1 = 1.5, b = 0.75, pure Rust, zero external deps). Scores are summed perschema:action group; the winning group drives routing, and the highest-scoring individual agent within that group becomes the preferred local agent. No network, no model weights. - On-Device AI fallback (~100 ms) — when BM25 confidence is below
threshold, the query goes to a local model (Candle, never a cloud API). The query
arrives in a delimited
[INST]…[/INST]conversation role; the preamble is the user's own memex history (trusted data), not external content.
The 6-phase PAP handshake enforces scope cryptographically after routing. Prompt injection has no effect on levels 1 or 2 — they are deterministic programs, not generation models. Level 3 uses an on-device, sandboxed model with zero disclosure requirements of its own.
An LLM appears in exactly two places, both after the handshake:
On-Device AI fallback — described above as level 3.
Pipeline Synthesizer — an optional post-execution node that merges multi-agent results using the local model. This runs after all disclosure and execution is complete; it cannot retroactively change what was disclosed.
Prompt injection risk is none in the main handshake path, and low in the local LLM path — the model is on-device, sandboxed, and operates with zero disclosure requirements of its own.
No — and the reason is that the model is structurally isolated from anything security-critical. It has one job: pick one of the action strings you handed it. It receives your query text and a list of permitted action types from the agent catalog. It cannot see your mandate, your session keys, your principal identity, or any previously disclosed values. Its output must match one of the strings it was given — a hallucinated action is discarded. What the model controls is routing: which catalog agent handles your query. What it cannot touch is the mandate that governs what that agent is allowed to do. Those are signed by your device-bound key, which the model never sees. If you prefer no model at all, LlmProvider::None gives you the full protocol running deterministically.
The LLM touches exactly one function in the PAP stack.
The LlmClient trait in crates/pap-agents/src/llm.rs exposes two methods:
classify_intent(text, candidate_actions) and complete(system, user).
That's the entire surface. The model receives the user's query string and a pre-computed list
of allowed schema:*Action strings. It does not receive — and cannot request —
mandate contents, scope, TTL, session DIDs, disclosure sets, or the principal's keypair.
Its output is validated before use. The return value of
classify_intent must match one of the strings in candidate_actions.
A hallucinated action that isn't on the list is discarded. The model controls which
catalog agent handles the query. It cannot alter the mandate that governs what that agent
is allowed to do — mandates are signed by the principal's device-bound Ed25519 key,
which the model never sees.
The four canonical LLM attack paths:
Prompt injection — succeeds only at routing the query to the wrong catalog
agent; it cannot forge the Ed25519 signature that authorizes scope. Context
exfiltration — the model only receives query text, never the mandate or session keys.
Session correlation — each session generates a fresh
ephemeral session DID discarded
at close; the model sees the query text, never the session DID. Scope escalation
— child mandates are cryptographically constrained to a subset of their parent's scope
(Scope::deny_all() baseline, contains() enforcement);
no model output can extend this.
The default is on-device. LlmProvider::BuiltIn runs inference
locally via Candle — no network call, no external endpoint, no logs leaving the device.
External providers (Mistral, Ollama, OpenAI-compatible) require the user to explicitly
configure an API key and endpoint URL. Even with an external provider, only query text
travels to the endpoint. For deployments requiring network-layer query privacy,
OHTTP (RFC 9458) with real HPKE decouples the IP address from the request content.
The LLM is also optional. The full 6-phase PAP handshake executes
deterministically via URL fast-path and BM25 semantic index without any model.
Setting LlmProvider::None gives a fully functional, cryptographically
sound protocol runtime. The model enhances intent resolution for ambiguous queries;
it gates no security decision.
The mandate chain enforces scope cryptographically at every delegation step. Each child's scope must be a strict subset of its parent's, its TTL can't exceed its parent's, and the whole chain is hash-linked so nothing can be forged or reordered. An agent cannot ask for more than it was granted, regardless of how the request is phrased. The narrower residual risk: a sequence of zero-disclosure micro-actions — each individually below the approval threshold — could collectively achieve something a single equivalent request would have surfaced for review. The cryptographic bounds still hold at each step. The gap is at the semantic layer, not the authorization layer.
Scope containment is cryptographically enforced at every delegation step.
pap-core/src/scope.rs contains() verifies that a child
mandate's
action set is a strict subset of its parent's. mandate.rs verify_chain()
binds each delegation to its parent by hash — you cannot forge a valid chain that expands scope.
TTL cannot exceed the parent mandate either.
Auto-approval has a further constraint: it triggers only when
requires_disclosure.is_empty() (canvas.rs). No action that
requires any disclosure from the principal can be auto-approved — the user always sees
the approval gate.
The narrower residual risk: a sequence of zero-disclosure sub-actions that individually pass auto-approval could collectively achieve an effect that a single equivalent request would have surfaced for explicit consent. The cryptographic scope bounds still apply to each step; the gap is at the semantic composition layer.
The loopback benchmarks — 2,000 runs each, 200 warmup — show full session lifecycle at 130 µs p50 / 500 µs p99, and a three-level mandate chain verification at 138.5 µs p50 / 480 µs p99. Those are on a single machine with no network hop, so they're best-case numbers. Real latency adds network RTT at each of the six protocol phases. One detail worth understanding: the HTTP path treats each phase as an independent POST and can be retried per-phase if a connection drops. The WebSocket path shares one persistent connection across all six phases — a drop requires starting a new session. Neither path is fail-open; errors propagate immediately to the caller.
Measured loopback numbers from CI (benches/baseline.json):
full session lifecycle 130 µs p50 / 500 µs p99 (target <20 ms),
depth-3 mandate chain verification 138.5 µs p50 / 480 µs p99.
The Criterion suite runs a 55% regression gate on p50 and a 50% gate on p99 —
thresholds calibrated for GitHub-hosted runner variance, not relaxed protocol limits.
p99 benchmarks are in benches/benches/p99.rs with CI regression guards.
Two edge cases worth understanding:
Network drop mid-handshake — behavior depends on which transport
is in use. HTTP (stateless): each phase is an independent POST;
a connection failure surfaces as a TransportError::ConnectionFailed
immediately. The request either reached the server and was processed, or it didn't —
the caller retries or aborts. WebSocket (stateful): all six phases
share one persistent connection; a connection drop terminates the session handler
and the session is abandoned. Neither transport is fail-open — the error propagates
to the caller. The distinction that matters is idempotency: the HTTP path can be
retried per-phase; the WebSocket path requires starting a new session. The residual
concern for high-value agents is a server-side session stuck in a non-Closed
state after a client drop — orphaned ephemeral keys and partial mandate state that
expire with the mandate TTL.
TTL expiry: mandate TTL is checked at every
verify_chain() call (mandate.rs), which occurs at
each phase boundary. A mandate close to its expiry when the session opens may
pass the Phase 1 check but fail at Phase 4 execution if the TTL ticks over
between phases. No built-in grace period or mid-flight renewal path exists today.
Full PyO3 bindings ship today. Async works correctly — the GIL is released during await, so concurrent handshakes don't block each other in asyncio pipelines. There's an integration guide covering LangChain, CrewAI, and MCP.
Install from PyPI (recommended):
pip install pap-protocol
Pre-built wheels are available for Linux x86_64/aarch64, macOS universal2, and Windows x64 — no Rust toolchain required.
Full PyO3 bindings ship today. crates/pap-python/src/lib.rs
(1,595 lines) exposes the complete PAP API with both blocking methods and
async variants for all transport steps. The async path uses PyO3 0.24
experimental-async, which releases the GIL during await — making
concurrent handshakes work correctly in asyncio agent pipelines without
to_thread() wrapping.
A comprehensive integration guide covers LangChain, CrewAI, and MCP with 1,293 lines
of annotated examples:
docs/integration-guides/langchain-crewai-mcp.md.
The TypeScript equivalent — packages/pap-ts — is a pure native async
implementation with zero Rust dependency, for reference.
Building from source (for contributors or unreleased branches):
cd crates/pap-python && pip install maturin && maturin develop --release
PAP gives you two controls for this. First, enable OHTTP in Settings — it wraps every request in RFC 9458 HPKE so even the relay hop sees nothing, not the query structure, not the property list. Second, set no_retention: true on any disclosure entry where you need the receiving agent to discard its copy of the receipt. When that flag is set, PAP requires the agent to run in a TEE — the retention constraint becomes cryptographically binding, not a promise. The risk isn't gone without these settings, but with them it's fully addressed.
The underlying concern — and why both controls matter.
Each agent in
pap-agents declares exactly what it needs via
AgentMeta.requires_disclosure
(crates/pap-agents/src/executor.rs) — those property names are embedded
in the Mandate's DisclosureSet during Phase 2 of the handshake, bounding
the scope of what the agent is permitted to receive. The Phase 3 disclosure payload
carries the query inside that scope boundary.
SD-JWT (pap-credential/src/sd_jwt.rs)
hides claim values via per-claim salted hashes and is available as an optional
enhancement for credential-bearing payloads, but is not used in the current query
handshake path — claim structure is not hidden. The
recipient agent always learns which property slots were disclosed;
that is the intent of the handshake. Network-level visibility is configurable:
OHTTP (RFC 9458, HPKE DHKEM-X25519 + AES-128-GCM) is a setting in Papillon and
Chrysalis — when enabled, relay operators and passive observers see nothing.
Without it, transport encryption is present but query structure is visible to
the relay hop.
Receipts store property references, never values: pap-core/src/receipt.rs
holds disclosed_by_initiator: Vec<String> — strings like
"schema:Person.name". The receiving agent holds a co-signed copy after
every session. Across multiple sessions the pattern of which properties a principal
discloses to that agent can become a quasi-identifier even without any value leaving.
The no_retention flag on DisclosureEntry
(pap-core/src/scope.rs) addresses this: when set, the handshake requires
TEE attestation from the receiving agent before proceeding, making receipt discard
auditable rather than contractual.
In healthcare and financial services, which properties were accessed is frequently PHI or material non-public information in its own right — both controls are worth enabling by default in those contexts.
What we're watching: The IRMA/Yivi ZKP-based approach provides stronger structural privacy guarantees using W3C VCs. A future spec section will evaluate ZKP-based disclosure as an optional enhancement for cases where even TEE-attested no-retention is insufficient.
The relay runs wherever you deploy it — colocated with your own node, not on Baur Software infrastructure. If you don't configure one, the protocol falls back to a direct connection: relay_url is optional, and when it's absent, the handshake continues without it. Privacy degrades — query structure becomes visible at the relay hop — but the session never breaks, and scope enforcement, expiring mandates, and co-signed receipts all work exactly the same.
OHTTP relay capability is built into Papillon and Chrysalis — it runs colocated with each deployment, not on Baur Software infrastructure. When OHTTP is configured, there is no external relay operator to capture traffic. The decentralization is topological: the relay lives alongside your own node.
relay_url in pap-transport/src/ohttp.rs is typed
Option<String>. When absent, the protocol falls back to a direct
connection — privacy degrades but the handshake never breaks. The core guarantees
(SD-JWT selective disclosure, ephemeral session DIDs, deny-by-default mandates)
do not depend on OHTTP being enabled.
When the relay is active, it uses RFC 9458 HPKE: DHKEM(X25519, HKDF-SHA256) + AES-128-GCM (RFC 9180). Relay operators and passive observers cannot link IP addresses to query content or SD-JWT disclosure structure.
Getting a fake agent into the Chrysalis mesh takes real calendar time, not just compute. A new peer needs three vouches from existing members, each of whom can only vouch for three peers per year — and must themselves have been active for at least 90 days before they can vouch for anyone. New peers spend 60 days in a probationary state. The trust graph also checks path diversity: if your three vouchers all trace back through the same cluster of ancestors, the registration is rejected. You can't manufacture trust quickly; you have to earn it slowly.
Vouch-based peer admission. Joining the Chrysalis mesh is not free —
it requires existing trusted peers to stake their reputation on you.
pap-federation/src/registry.rs enforces:
3 vouches required to register a new peer. Each existing peer may issue at most 3 vouches per year — a rate budget that makes flooding expensive. A peer must be registered for at least 90 days before it can vouch for anyone. New peers enter a 60-day probationary period with limited permissions.
Diverse trust paths (require_diverse_paths) are specified in the policy struct
and are being enforced now — ensuring N vouchers don't all route through the same
small set of ancestors, preventing a captured clique from bootstrapping unlimited new peers.
This is a social-graph approach rather than proof-of-stake or proof-of-work. The rate constraints make a Sybil flood expensive in terms of calendar time and peer relationships, not compute or tokens.
The protocol doesn't depend on Schema.org being controlled by anyone in particular. The action field in a mandate is a plain string — any namespace-prefixed identifier is valid. Schema.org is the default because it's widely understood and makes agents discoverable to everyone, but a financial compliance action, a FHIR operation, or a lab protocol step can be expressed as operator:FhirReadAction without touching Schema.org at all. The governance capture risk is real for the interoperability layer — agents that want to be universally discoverable benefit from Schema.org alignment. Agents targeting a specific vertical can use the operator: namespace freely.
Schema.org is a collaborative, open-community project founded by Google, Microsoft, Yahoo, and Yandex to create a shared structured-data vocabulary. The schema is community-governed; contributions come from across the web industry. Google drives significant adoption through rich-result support, but it does not have unilateral control over the vocabulary.
The protocol is not locked to Schema.org.
ScopeAction.action in crates/pap-core/src/scope.rs is a
plain string — any namespace-prefixed URI is valid. Spec §17.6 reserves three
prefixes: schema: (Schema.org, standard interoperability baseline),
operator: (implementation-defined, for agent operator custom vocabulary),
and pap: (reserved for PAP specification extensions). A financial
compliance action, a FHIR operation, or a laboratory protocol step can be expressed
as operator:FhirReadAction or pap:ClinicalTrialEnrollAction
without touching Schema.org at all.
crates/pap-core/src/extensions.rs implements spec sections 9.1–9.4:
Continuity Tokens (cross-session encrypted state, §9.3) and Auto-Approval Policies
(§9.4) are the first shipped protocol-level extensions. The conditions
field in ScopeAction is an opaque JSON map for arbitrary
protocol-level constraints not representable in Schema.org. Dynamic agent definitions
(crates/pap-agents/src/dynamic.rs) accept any action and return-type
strings, so custom-vocabulary agents ship without a spec change.
The governance capture risk is real for the interoperability layer —
agents that want to be universally discoverable benefit from Schema.org alignment.
Agents targeting a specific vertical or operator deployment can use the
operator: namespace freely. Schema.org is the default, not the ceiling.
Glossary
Key terms used throughout this FAQ.
- Agent
- A hosted program with defined inputs and outputs — the place where execution and value happen in a PAP session. Agents declare the
schema:*Actiontypes they perform, the data properties they require, and the schema.org types they return. They act only within the scope and TTL of a Mandate issued by the principal. - Principal
- The human whose data and intent drives a PAP session. The principal holds the root Ed25519 keypair and is the only entity that can issue or authorize mandates. Agents act on behalf of the principal, never independently.
- Mandate
- A cryptographically signed authorization granted by the principal to an agent. Specifies which actions the agent may take (
schema:*Action), which data properties it may see, and how long it is valid (TTL). Child mandates cannot exceed their parent's scope. - SD-JWT (Selective Disclosure JWT)
- A credential format that hides individual claim values via per-claim salted hashes. Available as an optional enhancement for credential-bearing payloads; not used in the current query handshake path — scope is instead bounded by the Mandate's
DisclosureSetduring Phase 2. Defined incrates/pap-credential/src/sd_jwt.rs. - did:key
- A W3C Decentralized Identifier method that derives the DID directly from a public key — no registry, no DNS, no issuer. PAP uses
did:keywith Ed25519 keypairs. Each principal's identity is self-sovereign and portable. - Papillon
- The PAP reference client. A desktop browser for the
pap://protocol — analogous to what Mosaic was for HTTP. Handles the 6-phase handshake, renders schema.org results as typed UI blocks, and holds the principal's keypair locally. - Chrysalis
- The PAP peer-to-peer agent registry and federation mesh. Organisations run agent workloads on Chrysalis; principals discover and interact with them through normal PAP transport. Entry requires vouch-based peer admission — not open registration.
- OHTTP (Oblivious HTTP)
- RFC 9458. Wraps an HTTP request in HPKE encryption so the relay cannot link the client's IP address to the request content. A configurable setting in Papillon and Chrysalis — when enabled, the relay sees neither query content nor SD-JWT structure.
- Decay states
- The lifecycle of a PAP mandate:
Active → Degraded → ReadOnly → Suspended. Expiry is automatic at TTL without any deprovisioning step. Provides cryptographic revocation without a central revocation authority. - Co-signed receipt
- An immutable record of a completed agent session, signed by both the orchestrator and the agent. Stores property type references only (e.g.
schema:Person.schema:email) — never values. Both parties hold a local copy; no platform stores receipts. - ecash / blind signatures
- RFC 9474 RSABSSA-SHA384-PSS blind-signed tokens used in
crates/pap-ecash/. The issuer cannot link the signing operation to the redemption — enabling private value exchange between agents without identity-gated permissions. - Scope::deny_all()
- The baseline mandate scope in
pap-core/src/scope.rs. Every mandate starts with all actions denied; permissions are added explicitly. Child mandates are verified withcontains()— a child can never exceed its parent's scope. - Ephemeral session DID
- A temporary
did:keygenerated fresh for each PAP session and discarded at close. The session DID is unlinkable to the principal's long-term identity, preventing session correlation across interactions.