Skip to content

Instantly share code, notes, and snippets.

@ewindisch
Last active May 29, 2026 03:38
Show Gist options
  • Select an option

  • Save ewindisch/f11408fe72c12c79b7358087854452ca to your computer and use it in GitHub Desktop.

Select an option

Save ewindisch/f11408fe72c12c79b7358087854452ca to your computer and use it in GitHub Desktop.
Federated Agentic Namespaces Draft

Federated Agentic Namespaces Draft


tags:

  • architecture
  • federation
  • atproto
  • spiffe
  • plan9
  • wanix
  • mcp
  • 9p
  • draft
  • collaboration status: draft version: '0.2' date: '2026-05-27' author: Erica Windisch organization: Cyberdione Labs Corporation

Federated Agentic Namespaces

A composition pattern for federated agentic infrastructure.

Erica Windisch — Cyberdione Labs Corporation. Draft v0.2.

Abstract

Agentic systems — software stacks that serve large language models, vector databases, policy engines, identity providers, tool runtimes, and the connective tissue between them — currently compose at the level of HTTP and gRPC endpoints, with identity layered ad-hoc on top. This document describes a pattern that composes them at the level of a Plan 9-style mountable namespace, with all identity protocols rooted at a single DNS name (the instance's FQDN), federation expressed as policy-gated admission of foreign principals into the namespace, and the Model Context Protocol (MCP) endpoint serving simultaneously as the external face of the system and as one mount within the local tree.

The pattern is designed to be implemented by multiple vendors using a shared set of identity primitives (OAuth 2.1, did:web, CIMD, SPIFFE), a shared namespace wire protocol (9P2000.L), and a shared policy primitive for federation admission. It composes naturally with existing standards rather than replacing them.

Note some choices such as ZMQ / ZMTP framing has been made due to preferences for flexibility beyond the network, extending to Unix socket IPC and in-process messaging. This is a transport layer that is not required to implement. We are currently evaluating MoQ — Media over QUIC to replace ZeroMQ libraries and framing as referenced in this document, and as implemented in the reference architecture.

1. Background and motivation

1.1 What an agentic system has to compose

A working agentic platform today comprises, at minimum:

  • One or more inference services (LLMs, embedding models, multimodal models)
  • A policy / authorization service or library
  • An identity provider (OAuth 2.1 server, often OpenID Connect-compatible)
  • A tool catalog exposed via MCP or equivalent
  • A memory / context store (vector DB, KV store, document store)
  • A registry of models, datasets, agents, or workflows
  • A discovery layer that lets workloads locate each other

These typically run as separate processes, often as separate containers, often on separate hosts. Composition between them is per-service HTTP/gRPC wiring with bespoke authentication. Cross-instance federation — having an agent on host A use tools served by host B on behalf of a user who originally authenticated at host C — is mostly not solved.

1.2 Why the existing surfaces don't cover composition

MCP solves a particular tool-discovery and tool-invocation surface very well. It does not address:

  • How services internal to one MCP server compose with each other
  • How an MCP server discovers identity, policy, and infrastructure services local to its host
  • How two MCP servers run by different organizations federate
  • How workload identity differs from user identity differs from third-party-app identity

OpenID Connect federation, OAuth, did:web, SPIFFE, and atproto each address parts of the identity story but live in separate specifications with separate trust roots. An operator who wants all four either runs four separate trust domains or accepts informal alignment.

1.3 What this pattern proposes

A composition primitive (the namespace), an identity story unified at the DNS name (FQDN), and a federation primitive (one policy resource that admits or denies foreign principals at the trust boundary). MCP becomes a mount within the namespace — preserving its existing protocol shape while gaining composability, internal addressability, and a clear federation story.

2. Design principles

The following invariants define the pattern. An implementation that breaks one of these is no longer the same pattern — it might still be a good architecture, but it stops being interoperable in the ways this document describes.

2.1 The identifier root is the trust boundary

An instance is identified by a single root — typically an FQDN, but possibly a long-lived cryptographic key (such as a Tor v3 onion service's Ed25519 public key, addressed via did:onion). One identifier root corresponds to one entity, one trust domain.

When the root is a DNS name, every identity protocol the instance speaks (OAuth issuer URL, did:web identifier prefix, SPIFFE trust domain, CIMD-acceptable origin) MUST be rooted at the same name; the TLS chain that authenticates the FQDN for one protocol authenticates it for all of them.

When the root is an Ed25519 onion key (did:onion), the Tor circuit handshake to the onion service authenticates the root in place of the TLS-and-PKI chain. The trust-boundary principle is the same: control of the secret tied to the root proves the identity.

Implementations may operate under one root type or both; cross-root federation is gated by the same policy primitive (§4).

2.2 The path is the API

Any service whose state can be expressed as a tree of nodes (files, directories, control points) is composable into the namespace. The path schema becomes the operator-facing and script-facing API; the on-wire RPC remains the transport for control operations.

2.3 Auth gates the boundary, not the leaf

Authentication and authorization fire at the trust boundary — typically the namespace's externally-reachable endpoints. Once a principal has entered the namespace, traversal and per-mount checks are policy decisions, not authentication.

2.4 Federation admits principals, not code

A federated instance admits principals (users, clients, workloads, peer instances) from a foreign trust domain; it does not execute foreign code paths. Admission is gated by a single explicit policy primitive at the boundary.

2.5 Multiple implementations, shared primitives

Two implementations of this pattern using different transports, programming languages, and storage backends MUST be able to (a) verify identities issued by each other, (b) traverse each other's namespaces under federation policy, and (c) be configured by operators using analogous policy primitives.

3. The identity stack

Several identifier kinds compose at the identifier root. The OAuth issuer URL and the protected-resource URL are two roles of the same DNS identifier; the remaining four kinds (did:web, did:onion, CIMD, SPIFFE) each address a distinct principal type. Each kind, its form, what it identifies, and where the verifying material lives:

Identifier kind URI form Who/what it identifies Verification endpoint
OAuth/OIDC issuer https://hs.example.com the instance, as authorization server /.well-known/oauth-authorization-server (RFC 8414) and /.well-known/openid-configuration (OIDC Discovery 1.0)
Protected resource same the instance, as resource server /.well-known/oauth-protected-resource (RFC 9728)
did:web subject did:web:hs.example.com:users:alice a human user (handle-rooted at the FQDN) /users/alice/did.json (W3C DID Core)
did:onion subject did:onion:{onion-address} a user or instance rooted at a Tor v3 onion service's Ed25519 key DID document served by the onion service itself (e.g. http://{onion}.onion/.well-known/did.json)
CIMD client_id https://app.partner.com/client.json (third-party FQDN) a third-party OAuth client app hosted by the client on its own FQDN (see client.dev and draft-ietf-oauth-client-id-metadata-document)
SPIFFE ID spiffe://hs.example.com/service/{name} a workload running inside the instance /.well-known/spiffe/bundle (SPIFFE Federation API)

In the placeholder notation above, {onion-address} is a v3 onion service identifier (the 56-character base32 form), and {name} is the local service name.

3.1 The shared trust root

All four protocols MUST consume the same TLS material at the FQDN. An operator rotating the certificate for the OAuth issuer URL implicitly rotates it for did:web document fetches, CIMD client metadata fetches, and SPIFFE bundle fetches. There is no protocol-specific cert pinning.

3.2 Atproto compatibility

User identity is explicitly atproto-compatible (see the atproto Identity spec and atproto OAuth profile). The handle's domain component is the instance's FQDN: alice.hs.example.com resolves (via /.well-known/atproto-did on the handle's host) to a DID identifier — did:web:hs.example.com:users:alice when using did:web. The handle and the DID point at the same identity material on the same FQDN.

The reference implementation currently uses did:web as the DID method and serves DID documents at /users/<id>/did.json. Handle resolution via /.well-known/atproto-did and atproto-OAuth-style JIT user creation from foreign DIDs are part of the in-flight atproto compatibility work (see §10.5) and are not yet served by the reference implementation. did:plc is a planned alternative (§11.6).

CAR files are out of scope for this pattern in its current form. Atproto's data-interchange model (CAR + lexicons) is orthogonal to the namespace/MCP composition described here; an implementation MAY adopt CAR for repository-style synchronization without changing anything in this document, but the reference implementation does not use CAR today.

3.3 The workload/user/client distinction

Within one trust domain, three principal kinds coexist:

  • Users carry did:web or did:onion subjects. Their identity is durable across token issuance.
  • Workloads (services inside the instance) carry SPIFFE IDs. Their identity is attested at runtime (Unix UID, systemd unit, container metadata, K8s ServiceAccount).
  • Clients (third-party apps, typically MCP clients) carry CIMD URLs, or — when they have no FQDN of their own — opaque registration credentials.

The same aud claim format in JWTs (RFC 8707 resource indicators) addresses all three.

3.4 DNS-rooted vs key-rooted identifier roots

The five identifier kinds in the §3 table fall into two trust-anchoring families:

  • DNS-rooted — OAuth issuer URL, protected-resource URL, did:web, CIMD client_id, SPIFFE trust domain. The DNS name is the root; the TLS chain (and the operator's control of the certificate) authenticates it. This is the dominant case for public-internet deployments.
  • Key-rooted — did:onion, and any future DID method whose identifier IS the public key it is anchored at. The cryptographic key is the root; possession of the corresponding private key authenticates it. Verification typically happens via a protocol that performs the cryptographic handshake (the Tor circuit's introduction-point handshake, in the did:onion case), not via a CA chain.

Both families are first-class. The pattern does not require an instance to operate under only one root family — an instance may publish both a did:web identity at https://hs.example.com AND a did:onion identity at its onion address, and accept federation traffic via either.

3.5 Hardware-bound bootstrap (WebAuthn-PRF)

A client's user identity SHOULD be bound to a hardware-resident key when possible. The recommended pattern is a WebAuthn credential with the PRF extension: the credential remains on the hardware authenticator, and a deterministic per-credential Ed25519 keypair is derived in the browser at signing time and discarded.

The same hardware-derived Ed25519 key can serve as:

  • The signing key for OAuth private_key_jwt and DPoP
  • The user's did:web verificationMethod key
  • The Ed25519 key that controls a Tor v3 onion service (and thus a did:onion identifier)

This collapses the "many keys, one user" problem into "one hardware-rooted key, many identifier representations". The user's data never leaves the hardware authenticator; the client process holds the derived key only for the duration of one signature operation. The pattern is implemented today by the the-onion-shell reference deployment for its SSH signer (§10.5).

4. The federation gate

A single policy primitive governs admission of foreign principals across all four planes (peer instance, foreign user, foreign client app, foreign workload).

4.1 Resource name

federation:register (or an equivalent operator-recognizable string). The subject is the principal's origin as defined by RFC 6454 §4: scheme + lowercase host + non-default port. The action is check. The effect is allow or deny.

4.2 Rule shape

A federation policy rule is a tuple

(subject_pattern, resource, action, effect)

where:

  • subject_pattern matches the principal's origin (§4.1) by exact string, wildcard, or other operator-chosen mechanism
  • resource is federation:register (or the implementation's equivalent string)
  • action is check
  • effect is allow or deny; deny SHOULD win over allow when both match

Operators express federation policy as rules of this shape. A single wildcard rule covers a peer instance's CIMD clients, user identities, MCP clients, and workloads when they share an origin pattern (e.g. https://*.partner.org covers app.partner.org, hs.partner.org, tools.partner.org at once).

The wire form of these rules is an implementation choice. As one illustration (non-normative), Casbin's p line syntax expresses the tuple as:

p, https://*.partner.org,         *, federation:register, check, allow
p, https://hs.partner.org,        *, federation:register, check, allow
p, https://app.bad.partner.org,   *, federation:register, check, deny

Other policy engines (OPA/Rego, Cedar, custom DSLs) MAY use different surface syntax for the same tuple.

4.3 Default posture

Implementations MUST default to deny. An operator with no federation policy in place admits no foreign principals — the instance runs in isolated mode.

4.4 Fail-closed on policy outage

When the policy service is unreachable, admission MUST fail closed. Returning the OAuth error code without an internal description (per OAuth 2.1 §5.3) is RECOMMENDED — the failure is visible to operators via logs but is not surfaced as information disclosure to unauthenticated callers.

4.5 Where the gate fires

Surface When the gate fires
CIMD client metadata fetch Before the HTTPS fetch — denial avoids the network round trip and prevents SSRF amplification
OAuth private_key_jwt at the token endpoint Defense-in-depth re-check using the client's JWKS, in case policy was revoked between authorization and token exchange
Peer entity-statement / JWKS fetch (federation) Before the HTTPS fetch — denial avoids cross-org traffic for non-permitted peers
MCP request from an external client At the boundary — the client's identity origin is checked before request dispatch
(Future) SPIFFE federated SVID verification When verifying an SVID whose trust domain is foreign

5. Request lifecycles

Two diagrams ground the rest of the document: a single-instance MCP request showing how identity admission flows into namespace dispatch (§5.1), and a PDS-style multi-client view showing how a web client, a CLI/agent, and a federated foreign service all converge on the same home instance with different transports (§5.2).

The mechanics of the namespace itself are described in §6 onward; the diagrams below assume only the identity stack (§3) and the federation gate (§4).

5.1 Single-instance MCP request

sequenceDiagram
    autonumber
    participant Ext as External MCP client
    participant Endp as MCP endpoint (https://hs.example.com/mcp)
    participant Auth as Identity / federation gate
    participant NS as Local namespace
    participant Mod as Model service mount

    Ext->>Endp: tools/call (Bearer token + DPoP proof)
    Endp->>Auth: verify token + DPoP + federation:register on iss origin
    Auth-->>Endp: principal admitted (did:web:.../users/bob)
    Endp->>NS: resolve /mcp/tools/<tool> → bound to model service
    NS->>Mod: write(/services/model/infer, args)
    Mod-->>NS: stream output
    NS-->>Endp: response stream
    Endp-->>Ext: SSE stream
Loading

The boundary check (steps 2-3) is the only place identity is authenticated. Internal hops (steps 4-6) are authorized per-mount but use already-admitted principals.

5.2 Atproto-compatible PDS — federated client paths

This pattern is explicitly atproto-compatible: a user's data lives at their home PDS (the instance whose FQDN roots their identity, in the atproto sense). Any client — browser, CLI, agent, or service in another trust domain — that wants to interact with that user's data tree converges on that PDS. The client's transport differs by environment; the PDS surface they hit is the same.

The reference implementation does not currently use CAR files (atproto's repository data-interchange format); the PDS shape described here is the OAuth / identity / namespace surface, which is the part that matters for cross-vendor agentic-infrastructure interoperability. CAR interop is a separable extension if and when the use case demands it.

This diagram shows three concurrent client kinds operating against alice's home PDS at hs.alpha.org:

  • A web client running in alice's browser, using WebTransport (HTTP/3 over QUIC)
  • A CLI / agent runner on alice's workstation, speaking 9P2000.L directly over QUIC
  • A foreign service running in another trust domain (hs.beta.org), federated in

A fourth pattern, exercised by the the-onion-shell reference deployment (§10.5), is an in-browser WASM agent reaching an onion-rooted PDS: the agent runs inside a Wanix-hosted browser tab, allocates a Snowflake capability (a WebRTC DataChannel to a Snowflake bridge), tunnels Tor over it via Arti, and dials an onion service whose Ed25519 root is the user's did:onion identifier. Diagrammatically this lane converges on the PDS layer the same way as the three shown below; the transport stack underneath is WebRTC → Snowflake → Tor circuit → onion service, and the PDS is reached at <onion>.onion rather than hs.alpha.org. This is the censorship-resistant and identity-portable variant of the web-client lane.

%%{init: {'theme':'base', 'themeVariables': {'noteTextColor':'#111', 'noteBkgColor':'#fafafa', 'noteBorderColor':'#666', 'actorTextColor':'#111', 'sequenceNumberColor':'#fff'}}}%%
sequenceDiagram
    autonumber
    participant Web as Web client (browser tab)
    participant CLI as CLI / agent runner
    participant Svc as Foreign service (hs.beta.org)
    participant PDS as alice's home PDS (hs.alpha.org)

    Note over Web,PDS: All three principals act on behalf of did:web:hs.alpha.org:users:alice against her home PDS - the FQDN that holds her data tree.

    rect rgb(225, 235, 250)
        Note over Web,PDS: Web client - WebTransport (HTTP/3 over QUIC)
        Web->>PDS: OAuth code flow (PAR + DPoP), receive access token
        Web->>PDS: WebTransport CONNECT /mcp and open 9P session
        PDS-->>Web: stream of /mcp/tools and /users/alice subtree per policy
    end

    rect rgb(255, 235, 220)
        Note over CLI,PDS: CLI / agent - 9P2000.L over QUIC (mTLS via did:web client cert)
        CLI->>PDS: device flow or local CLI flow, receive access token and DPoP key
        CLI->>PDS: QUIC dial with ALPN 9p, mTLS, open 9P session
        PDS-->>CLI: synthetic tree with long-lived streams per long-running call
    end

    rect rgb(225, 250, 230)
        Note over Svc,PDS: Foreign service - federated, SPIFFE-attested mTLS
        Svc->>Svc: holds alice's token (iss=hs.beta.org, aud=hs.alpha.org)
        Svc->>PDS: QUIC dial, mTLS uses Svc's SPIFFE X.509-SVID (trust domain hs.beta.org)
        PDS->>PDS: federation register check on https://hs.beta.org returns allow
        PDS->>PDS: verify alice's token signature against beta's JWKS
        PDS-->>Svc: scoped subtree per alice's per-path policy
    end
Loading

Several invariants hold across all three paths:

  • The same FQDN is contacted. alice's home PDS is hs.alpha.org. There is no separate API host or auth host: the FQDN is the identity, the OAuth issuer, the did:web root, and the SPIFFE trust domain (§3).
  • The same namespace is exposed. Each client lands inside /mcp and /users/alice in the local namespace; what they see beyond that point is gated per-path. A browser doing /mcp/tools/foo and a CLI doing cat /mcp/tools/foo invoke the same backend.
  • Different identity proofs, same admission check. Web client identity is sender-constrained by DPoP-bound bearer tokens; CLI mTLS is a did:web service cert; foreign service mTLS is a SPIFFE X.509-SVID with a federation-origin admission check. All three result in an admitted principal whose sub is did:web:hs.alpha.org:users:alice, after which traversal is uniform.
  • Wire framing per environment. The browser does WebTransport because that's what browsers can do; the CLI does direct 9P/QUIC because it can; the foreign service does the same as the CLI plus the federation handshake. The PDS terminates each connection at the appropriate adapter and dispatches into one shared namespace.

This is what "PDS" means for this pattern: the home FQDN holds the data tree, and all client kinds converge on that tree through the appropriate transport.

6. The namespace primitive

6.1 Plan 9 namespaces, briefly

A namespace is a per-process tree of paths. Each path either resolves to a leaf (a file-like resource) or is a mount point binding a sub-tree to a backing service. Mount and unmount operate on the local namespace; they don't affect other processes. A namespace is constructed by composing several backing services into one tree.

6.2 The namespace interface

The pattern's normative surface is the semantic interface — a set of operations modelled on 9P2000.L (walk, open, read, write, clunk, stat, etc.), and the Mount trait in §6.3 that abstracts them. Two implementations are interoperable at the namespace layer when their operations map to the same semantics, regardless of wire framing.

9P2000.L is the recommended interop wire for clients that speak 9P natively (browser clients via Wanix, Plan 9 tools, the p9p toolkit). Implementations SHOULD provide a 9P2000.L bridge or adapter at boundaries where such clients connect.

Implementations MAY use alternative wire framings for their own internal mesh — Cap'n Proto schemas, gRPC service definitions, or others — as long as the operation set maps cleanly onto the 9P-aligned semantics. The reference implementation in §10 uses a Cap'n Proto schema (nine.capnp) that embeds the 9P request/response shapes as envelopes inside each service's RPC union (§7.4.2); see §10.5 for current status, including the fact that a general-purpose 9P2000.L-over-network listener is not yet exposed by the reference implementation — its only 9P wire today is the consumer-side DMA bridge to Wanix.

6.3 The Mount trait (abstract)

An implementation's Mount trait MUST surface at least:

  • walk(path) → node — resolve a path relative to the mount's root
  • open(node, mode) → handle — open a node for read/write
  • read(handle, offset, count) → bytes and write(handle, offset, bytes) → count
  • readdir(node) → entries
  • stat(node) → metadata
  • A capability to negotiate features at mount time (read-only? streaming? extended attributes?)

The trait SHOULD be async. Synchronous Mount implementations force every consumer into the same blocking model; async lets I/O-bound and CPU-bound mounts coexist.

6.4 Browser bridge

The same 9P namespace SHOULD be reachable from in-browser code so that WebAssembly clients (typing-fast UIs, in-tab agents, browser-based shells) consume the same composition primitive as native processes. Wanix is the reference Plan 9-flavored browser OS this pattern targets for in-browser composition.

The reference implementation uses a custom DMA-ring transport over SharedArrayBuffer to carry 9P frames between the WASM agent and the in-browser 9P server with zero-copy semantics. This DMA-ring extension is not part of upstream Wanix — it is a fork-side feature that the reference implementation maintains. Wanix's mainline browser bridge uses a different transport; vendors implementing this pattern can either adopt the DMA-ring extension or contribute back into Wanix mainline. Other browser-bridge transports (postMessage-framed, WebTransport-to-localhost, etc.) are valid as long as they preserve the 9P semantic interface (§6.2).

6.5 Per-process namespaces

Implementations SHOULD provide per-process namespace isolation in the Plan 9 tradition: a process can mutate its own namespace (mount, unmount, bind) without affecting sibling processes. This gives a natural sandboxing primitive for running untrusted code (a federated workload, a script in an embedded interpreter) without leaking access to the parent's full tree.

Per-process namespace isolation is aspirational for the reference implementation today; see §10.5. The reference implementation currently maintains a process-level mount table that routes by longest-prefix match — sufficient for the composition patterns this document describes, but not yet providing the per-process sandbox property.

6.6 Wire framings and transports

Two orthogonal choices stack underneath the namespace interface:

  1. Wire framing — how the namespace operations (§6.2) are encoded on the wire
  2. Transport substrate — how those frames are carried

6.6.1 Wire framings

Framing Role Notes
9P2000.L Canonical interop wire Required at the Wanix bridge and any Plan 9 / p9p client edge; SHOULD be supported at instance boundaries for cross-implementation interop
IDL-defined RPC (Cap'n Proto, gRPC, etc.) Internal mesh and/or federation, when both peers speak the same IDL Permits richer schema validation and tighter typing than 9P, at the cost of compatibility with general-purpose 9P tooling
Other 9P variants (9P2000, 9P2000.u) Legacy interop Only when targeting older clients; new implementations SHOULD prefer 9P2000.L

An implementation MAY use one framing internally (e.g., Cap'n Proto over the intra-instance mesh) and another at its boundaries (e.g., a 9P2000.L bridge for Wanix and Plan 9 tools). The semantic interface in §6.2 is what defines interop; the wire framing is a packaging detail.

6.6.2 Transport substrates

Transport Role Notes
Unix domain socket In-host, intra-process-group Peer credentials available for attestation; default for service-to-service inside one instance
TCP Traditional Plan 9 native Works everywhere; mandate TLS for any non-loopback use
QUIC (RFC 9000) Cross-instance federation; mobile or long-lived clients First-class multiplexed streams (one stream per session avoids head-of-line blocking), built-in TLS 1.3, connection migration across IP changes, 0-RTT possible for trusted peers
WebTransport (HTTP/3 over QUIC) Browser clients without SharedArrayBuffer (no COOP/COEP) Same QUIC streams, exposed to JavaScript via the WebTransport API; complements the in-tab DMA ring rather than replacing it
SharedArrayBuffer DMA ring In-tab browser clients with COOP/COEP isolation Zero-copy between worker and main thread. This specific DMA-ring transport is a fork-side extension to Wanix maintained by the reference implementation; not upstream Wanix.
WebRTC DataChannel Browser-to-anywhere reachability under censorship; in-browser substrate when WebTransport/COOP/COEP are unavailable DTLS-SRTP encryption; reliable-ordered mode for 9P framing. Pairs with Snowflake (Tor pluggable transport) to reach Tor circuits from browsers without a native Tor client. Used by the the-onion-shell reference deployment.
Tor circuit (clearnet or onion) Anonymous transport; reaches onion-service identifiers (did:onion) natively Stream-oriented, one Tor stream per session. Server authentication for .onion targets is the onion circuit handshake (Ed25519 key match), so application-layer TLS is not required for that purpose. The reference Tor stack uses Arti (Rust).

QUIC is RECOMMENDED for cross-instance federation traffic regardless of which wire framing rides on top. The combination of mTLS (mutual workload authentication via SPIFFE X.509-SVIDs or TLS client certs derived from did:web), stream multiplexing, and connection migration maps directly onto the federation lifecycle.

For external client traffic to the MCP endpoint, HTTP/3 (HTTP semantics over QUIC) is the target transport. Implementations MAY serve over HTTP/1.1 over TLS as a starting point and upgrade to HTTP/3 as their stacks mature. The reference implementation currently serves the MCP endpoint over HTTP/1.1 with TLS (see §10.1); HTTP/3 / WebTransport for browser MCP clients are planned (§10.5).

6.6.3 Common stacks

Combinations that implementations are expected to support or interoperate with at the pattern level (implementation status for the reference implementation is in §10.5):

  • 9P2000.L over SharedArrayBuffer DMA — fork-side extension to Wanix maintained by the reference implementation; the reference implementation speaks this as a 9P client against the in-browser 9P server. Not in upstream Wanix; alternative browser bridges work as long as they carry 9P semantically.
  • 9P2000.L over Tor circuit to a .onion service — anonymous, identifier-rooted access (the onion address IS the server's Ed25519 public key). The address authentication is the onion circuit handshake.
  • WebRTC DataChannel → Snowflake bridge → Tor circuit → onion-service-hosted 9P listener — the censorship-resistant browser path. Demonstrated by the the-onion-shell reference deployment for SSH; the same layered stack carries 9P or MCP Streamable HTTP without changes to the framings on top.
  • MCP Streamable HTTP over Tor circuit — corresponding HTTP-shaped path for clients that prefer the MCP wire over a 9P-shaped namespace traversal.
  • 9P2000.L over QUIC / WebTransport — interop edge for browser-out-of-tab clients and cross-implementation peer connections (aspirational; not exposed by the reference implementation today)
  • 9P2000.L over TLS over TCP — fallback / legacy interop with traditional 9P tooling
  • 9P2000.L over Unix domain socket — local services to local consumer (e.g. a CLI shell into the instance)
  • Cap'n Proto payloads inside ZMTP 3.1 frames over QUIC (ALPN zmtp3) — the reference implementation's intra-cluster transport. Layer cake: QUIC (TLS 1.3, ALPN-negotiated) carries a single bidirectional stream per ZMTP "connection"; that stream carries ZMTP 3.1 message frames (multipart, with MORE/LONG/COMMAND flags) per RFC-ZMTP §3; the message body of each ZMTP frame is a Cap'n Proto-encoded message. The ZMTP handshake uses the NULL mechanism because QUIC's TLS already provides encryption — no CurveZMQ.
  • Cap'n Proto IDL with embedded 9P envelopes (§7.4.2) — a single connection carries both service-domain calls and 9P operations as alternates within the same request union; sidesteps the need for a separate 9P listener inside the cluster

The semantic interface stays the same. A 9P2000.L client connecting to a Cap'n Proto-internal instance hits a 9P2000.L adapter at the instance boundary; the adapter translates between framings without changing semantics.

7. Services as mounts

7.1 The composition pattern

Every internal service projects a synthetic tree. The tree's shape is the service's API; the underlying RPC is the transport. A consumer reading /services/policy/policy.csv triggers the implementation to dispatch a policy RPC and return the result; a consumer writing to /services/model/load triggers a model load.

7.2 Example service trees

/services/
  identity/                       # OAuth + did:web + CIMD + SPIFFE
    issuer                        # FQDN as text
    jwks                          # current JWKS
    federation/
      register                    # write: policy command
      policy.csv                  # read: current rules
      anchors/                    # SPIFFE trust anchors (future)
  policy/
    ctl                           # write: policy commands
    policy.csv                    # current ruleset
    history/                      # git-style commit log
    templates/                    # built-in templates as files
  discovery/
    endpoints/<service>/<id>      # live announcements
    entity-statements/<issuer>    # cached federation entity statements
    envelope-keysets/<issuer>     # cached signing key sets
  model/
    catalog/<model>/              # available models
    loaded/<handle>/              # running model handles
    load                          # write to load
    infer                         # write to infer; reads stream output
  registry/
    repos/<name>/                 # version-controlled artifacts
    refs/<ref>/                   # branch / tag pointers

The exact paths are an implementation choice; the shape (each service exposes ctl + state + history + per-entity subtree) is normative for this pattern.

7.3 The control file convention

A service's namespace SHOULD distinguish:

  • Control files (write-mostly) — commands that mutate state
  • State files (read-mostly) — current snapshot
  • History directories — append-only event log or git-style commit history
  • Per-entity directories — one subtree per live instance of an entity (model, session, user)

This matches the Plan 9 convention and gives observability and control via the same shell tools (cat, echo, ls).

7.4 Worked example: model and inference services

Two services in the reference implementation illustrate the composition patterns this document recommends:

  • Model service — lifecycle of inference instances (load, unload, status), plus scoped sub-interfaces for per-model operations
  • Inference service — token generation, image embedding, chat-template application; one InferenceService per loaded model

Other implementations will differ in API shapes; the patterns illustrated here (scoped sub-namespaces, 9P envelope embedding, currying scope into request subtypes, out-of-band streaming) are what's spec-relevant.

7.4.1 The service surface and currying scope

A service's request type carries both service-level methods and scoped sub-interfaces. Each scoped variant takes the scope field (modelRef below) plus its own inner union:

struct ModelRequest {
  union {
    # Service-level
    load        @1 :LoadModelRequest;
    unload      @2 :UnloadModelRequest;
    status      @3 :StatusRequest;
    healthCheck @4 :Void;

    # Model-scoped sub-interfaces (modelRef inside each)
    ttt     @5 :TttRequest;       # test-time training
    adapter @6 :AdapterRequest;   # LoRA adapter management
    infer   @7 :InferRequest;     # inference
    fs      @8 :ModelFsRequest;   # 9P-style sub-namespace (see §7.4.2)
  }
}

struct InferRequest {
  modelRef @0 :Text;
  union {
    generateStream    @1 :GenerationRequest;
    applyChatTemplate @2 :ChatTemplateRequest;
    status            @3 :Void;
    embed             @4 :EmbedImagesRequest;
  }
}

The IDL codegen produces a scoped sub-client for each such pattern: a consumer holds an InferClient already curried to modelRef=qwen3:main and calls generate_stream(request) without restating the model on every call. This is the IDL equivalent of cd-ing into a directory.

7.4.2 Nested namespaces: the 9P envelope pattern

The fs variant above is the key composition primitive. It embeds 9P operations as alternatives in the same union as the service's domain methods:

struct ModelFsRequest {
  modelRef @0 :Text;
  union {
    walk   @1 :Nine.NpWalk;
    open   @2 :Nine.NpOpen;
    read   @3 :Nine.NpRead;
    write  @4 :Nine.NpWrite;
    clunk  @5 :Nine.NpClunk;
    stat   @6 :Nine.NpStatReq;
    create @7 :Nine.NpCreate;
    remove @8 :Nine.NpRemove;
  }
}

The shared Nine.* types (defined once in a single nine.capnp schema, imported by every service that exposes a namespace) provide the canonical 9P2000-aligned request/response shapes (Qid, NpStat, fid management, mode flags). Every service that embeds them gets 9P semantics with no per-service redefinition.

Consequences at the spec layer:

  • A service is both an API and a filesystem. The consumer can invoke model.infer.generate_stream(modelRef, …) or walk model.fs.walk(modelRef, ["status"]) + open + read against the same connection. There is no separate 9P listener.
  • The scope field is the mount point. modelRef curries to "this model's subtree." Multiple scopes coexist (different modelRef values address different model namespaces) without needing separate connections.
  • Nested mounts compose by adoption. A vendor implementing this pattern in their own IDL adopts the 9P envelope and immediately inherits Plan 9-style nested-mount ergonomics, regardless of whether they expose a literal 9P wire.

7.4.3 Synthetic tree projection

A walk + readdir over a model scope yields a tree shaped like:

/services/model/<modelRef>/
  ctl                # write: control commands (e.g. "reload")
  status             # read: JSON status snapshot
  defaults           # read/write: generation defaults
  chat/              # per-session chat subtrees
  adapters/          # on-disk LoRA adapters
  ttt/
    status           # delta accumulator metrics
    pending          # pending adaptation, if any

The exact paths are implementation choices; the shape (per-entity subdirectory, ctl + state + history + sub-entity dirs) is the pattern of §7.3 instantiated.

7.4.4 How the model service routes to the inference service

When a consumer invokes model.infer.generate_stream(modelRef=qwen3:main, prompt):

  1. Model service validates modelRef, looks up the loaded model handle, and routes to the InferenceService instance bound to that model (one InferenceService per loaded model, owning the GPU memory).
  2. The InferenceService starts token generation in the background.
  3. The synchronous RPC reply is stream metadata (StreamInfo) — not the tokens. The tokens flow on the out-of-band channel described in §7.5.

This is "nested mount" at the dispatch layer: ModelService is the entry point, InferenceService is the downstream worker, and the consumer sees one logical service via the model namespace.

7.5 The stream mechanism

Long-running outputs (token streams, training progress, log tails) MUST NOT be returned inline in the synchronous RPC reply. The pattern requires an out-of-band stream channel, separately authenticated, with the RPC reply carrying only subscription metadata.

7.5.1 The contract

struct StreamInfo {
  streamId  :Text;          # opaque stream identifier
  endpoint  :Text;          # publisher/broker address to subscribe to
  dhPublic  :Data;          # publisher's ephemeral DH public key
}

Spec-layer invariants:

  • Out-of-band. Stream content travels on a separate channel from the originating RPC. This frees the request/reply connection for unrelated calls during a long-running stream.
  • Self-describing subscription. The StreamInfo payload MUST contain everything the subscriber needs to find and authenticate the stream — no second RPC round-trip for keys or topics.
  • Per-stream key. The publisher's dhPublic is ephemeral per stream; reusing it across streams MUST NOT be permitted.
  • E2E authenticated chunks. Each chunk MUST be cryptographically bound to the originating subscriber's key, even when the transport broker is untrusted.
  • Chained authenticator. Each chunk's authenticator MUST cover its predecessor (e.g., HMAC chain mac_n = HMAC(key, mac_{n-1} || chunk_n)) so a malicious broker cannot reorder, drop, or splice.

7.5.2 The reference instantiation

The reference implementation uses ZMQ XPUB/SUB as the out-of-band transport (broker forwards blindly; never reads or signs content):

  1. Consumer calls infer.generateStream(modelRef, request).
  2. InferenceService generates an ephemeral Ristretto255 DH keypair; replies with StreamInfo { streamId, endpoint, dhPublic }.
  3. Consumer generates its own DH keypair, derives a shared secret with dhPublic, runs HKDF → (topic, mac_key). The topic is unguessable to the broker.
  4. Consumer subscribes to that topic on the XPUB endpoint.
  5. Publisher chunks the output. Each StreamBlock carries the chained HMAC over (prev_mac || segments), with prev_mac seeded from the topic for the first chunk.
  6. Consumer verifies each chunk's HMAC; failure terminates the stream.
sequenceDiagram
    autonumber
    participant C as Consumer
    participant M as Model service
    participant I as Inference service (publisher)
    participant B as Stream broker (XPUB/SUB)

    C->>M: infer.generateStream(modelRef, prompt)
    M->>I: dispatch (loaded model handle)
    I-->>M: StreamInfo (streamId, endpoint, dhPublic)
    M-->>C: StreamInfo

    C->>C: derive (topic, mac_key) = HKDF(DH(c_priv, dhPublic))
    C->>B: SUB on topic
    I->>B: PUSH StreamBlock chunks (chained HMAC via mac_key)
    B->>C: forward bytes (broker never sees keys)
    C->>C: verify HMAC chain and reconstruct tokens
    Note over I,C: completion or error sent as final StreamBlock payload
Loading

7.5.3 Why this is spec-relevant

Without out-of-band streaming, a long-running tool call (streaming token generation, a multi-minute training run, a log tail) either monopolizes the RPC channel or requires the IDL to grow first-class server-streaming semantics (gRPC server-streaming, HTTP/2 server-push) into the request/reply protocol. Both approaches couple the streaming behaviour to a specific transport (Cap'n Proto over QUIC, 9P-over-QUIC, HTTP/3, etc.), which becomes a portability concern as transports evolve.

Returning subscription metadata in the synchronous reply and authenticating the stream end-to-end keeps the protocol layer cleanly factored:

  • The request/reply channel only carries requests and stream-metadata.
  • The streaming channel can use any pub/sub transport with the right multiplexing properties (ZMQ, NATS, MQTT-over-QUIC, custom).
  • The trust model doesn't depend on the broker — the publisher and subscriber share a fresh DH-derived key per stream.

Vendors implementing this pattern should plan for the out-of-band streaming contract from day one; retrofitting it onto a protocol that started with inline streaming is painful.

8. MCP as a namespace

8.1 The catalog projection

MCP's catalog (tools, resources, prompts) projects as a tree:

/mcp/
  tools/<tool-name>               # read = JSON schema + description
                                  # write = invocation (body = arguments)
  resources/<uri-encoded-id>      # read = fetch; metadata via .meta
  prompts/<prompt-name>           # read = templated content
  sessions/<session-id>/          # live session state
    requests/<id>/                # request log
    state                         # session state

A consumer who can readdir /mcp/tools/ enumerates available tools without speaking the MCP wire protocol. A consumer who writes JSON to /mcp/tools/foo invokes foo and reads the response body from the same file handle (or from a streaming sub-handle for long-running tools).

8.2 The MCP server endpoint

The MCP server endpoint at https://<fqdn>/mcp uses the Streamable HTTP transport from the MCP authorization specification (single endpoint, streaming-in-response-body for tool calls that emit progressively). The earlier SSE-based MCP transport is not used.

The endpoint is both:

  • The publicly-discoverable external face of the instance, advertised via RFC 9728 protected-resource-metadata and consumed by MCP clients per the MCP authorization specification.
  • A mount at /mcp in the local namespace (pattern-level — see §10.5 for implementation status), addressable by internal callers.

The intent is that these two are views of the same backing implementation: a change to a tool's schema appears simultaneously in the external MCP catalog response and as a write to /mcp/tools/<name> in the local namespace.

8.3 Authentication at the boundary

Inbound MCP requests authenticate per the MCP authorization spec (OAuth 2.1 bearer + DPoP recommended). The sub claim identifies a principal of one of the kinds in §3:

  • A did:web user (did:web:hs.example.com:users:alice)
  • A SPIFFE workload (spiffe://hs.example.com/service/agent-runner) — when an internal agent acts on its own behalf
  • A CIMD-registered third-party client (the client URL is the sub for client-credentials-style flows)

The aud claim MUST identify the MCP endpoint (RFC 8707) so tokens cannot be replayed against other endpoints under the same issuer.

8.4 Authorization beyond the boundary

Once admitted, an MCP request that traverses to other mounts (e.g., a tool call that internally writes to /services/model/infer) is gated by per-path policy checks, not by re-authentication. This separation lets operators reason about external admission policy (the federation gate) separately from internal access policy (per-resource Casbin or equivalent).

9. Federation across peer instances

9.1 The setup

Two instances, hs.alpha.org and hs.beta.org, each running this pattern. An operator at alpha wants users at beta to be able to invoke tools on alpha (or vice versa).

9.2 The handshake

  1. Alpha's operator adds: p, https://*.beta.org, *, federation:register, check, allow. Beta's operator does the same for alpha.

  2. A user bob.hs.beta.org (handle resolving to did:web:hs.beta.org:users:bob) wants to use a tool at alpha.

  3. Bob's client (browser, CLI, agent) initiates an OAuth flow against beta's authorization server (his home IDP). The token's aud includes alpha's MCP endpoint, per RFC 8707.

  4. Bob's client presents the token to alpha's MCP endpoint. Alpha verifies:

    • Token signature against beta's JWKS (fetched via beta's OIDC discovery or via beta's published OIDF entity statement)
    • The token's issuer origin (https://hs.beta.org) passes alpha's federation:register policy
    • The token's aud matches alpha's MCP endpoint
    • DPoP proof matches the presenter
  5. Alpha admits the request, mints an internal admission token (or carries the original token forward, with claim transformations as policy allows), and dispatches into the namespace.

9.3 What does and does not cross

What crosses:

  • Bob's identity (his DID)
  • His access token (whose audience explicitly authorizes use at alpha)
  • The MCP request body

What does not cross:

  • Alpha's private trust material (signing keys, internal service tokens)
  • Alpha's internal namespace structure beyond what alpha's per-path policy permits Bob to see
  • Beta's private trust material

9.4 Speculative: peer mounts

A future extension might allow alpha to mount beta's namespace at /peers/beta/ in the local tree, gated by the same federation policy. A traversal of /peers/beta/mcp/tools/ would proxy through alpha's federation client to beta's MCP endpoint, authenticated by alpha's own SPIFFE workload identity bound by alpha-issued tokens that beta has admitted. This is not yet a normative part of the pattern; it's an obvious composition once admission and mount are both standard.

The transport for peer-mount traffic is naturally 9P-over-QUIC (§6.6). Each cross-instance 9P session takes one QUIC connection between alpha and beta, with each walk/open/read/write exchange running on its own stream — a slow read from a streaming model output mount does not block an unrelated stat on the same connection. The mTLS material is derived from each instance's SPIFFE workload identity (X.509-SVIDs in the SPIFFE federation case, or did:web service-key certs in instances that don't run SPIRE), and the policy gate fires both at TLS handshake (peer cert subject vs federation:register) and at admission for each principal whose identity flows through the connection.

10. Implementation notes (non-normative)

10.1 Reference implementation framings and transports

The reference implementation currently uses the following framings and transports per surface. Items marked (aspirational) describe targets, not what is exposed today; see the §10.5 status matrix.

Surface Status Application payload Message framing Transport substrate Notes
Intra-instance service mesh implemented Cap'n Proto-encoded messages ZMTP 3.1 (multipart message frames; MORE/LONG/COMMAND flag bits) QUIC with ALPN zmtp3, TLS 1.3 One ZMTP "connection" maps to a single QUIC bidirectional stream; the ZMTP handshake uses the NULL mechanism because QUIC's TLS handles encryption. Cap'n Proto messages ride as bytes inside ZMTP message bodies.
Wanix bridge (client direction) implemented (on a fork) 9P2000.L request/response messages 9P2000.L wire framing SharedArrayBuffer DMA ring (fork-side extension to Wanix) The reference implementation acts as a 9P client against the in-browser 9P server. Zero-copy between worker and main thread. The DMA-ring transport is maintained on a fork of Wanix by the reference implementation — not in upstream Wanix.
MCP endpoint to external clients implemented (HTTP/1.1 only); HTTP/3 aspirational MCP Streamable HTTP (rmcp StreamableHttpService) HTTP/1.1 message framing TLS 1.3 over TCP via axum-server + rustls HTTP/2 and HTTP/3 are not enabled today; WebTransport-to-browser is planned.
Cross-instance / peer namespace via 9P aspirational 9P2000.L requests embedded in service envelopes (Nine.* types, §7.4.2) 9P2000.L over the same ZMTP-over-QUIC stack above QUIC A general-purpose 9P2000.L network listener is not exposed today; the 9P envelope-in-Cap'n Proto pattern (§7.4.2) is how 9P operations cross the network for now.

Why ZMTP-over-QUIC for the intra-instance mesh

ZMTP 3.1 gives multipart-message framing with first-class MORE/COMMAND flags — a good match for envelope-wrapped Cap'n Proto payloads where the outer envelope and inner payload travel as adjacent frames. QUIC supplies TLS 1.3 (so ZMTP's CurveZMQ layer can be dropped — the NULL mechanism is sufficient), reliable ordered delivery per stream, and connection migration. The combination keeps the operator-visible socket primitive (XPUB/SUB, REQ/REP, PUSH/PULL) familiar from ZeroMQ while adopting modern transport security.

Other implementations are free to make different choices internally (gRPC over HTTP/2, JSON-RPC over WebSockets, plain Cap'n Proto over TCP, etc.) as long as the §6.2 semantic interface is preserved at the boundaries.

10.2 Policy engine

Casbin is one reasonable choice for the federation:register policy primitive, given its first-class wildcard matching and effect model. Other engines (OPA, Cedar, custom) work as long as the operator-facing rule shape supports the §4.2 form.

10.3 Cryptography

10.3.1 Classical algorithm choices

The pattern is algorithm-agnostic but recommends:

  • Ed25519 for service signing keys (compact, fast, broadly supported)
  • ES256 (P-256 ECDSA) for atproto OAuth compatibility (mandated by atproto)

For envelope signing of inter-service messages, COSE_Sign1 (RFC 9052) with detached payload is recommended over JWS to keep on-wire size compact and to integrate with the broader COSE/CBOR ecosystem.

10.3.2 Post-quantum cryptography

The reference implementation explicitly supports post-quantum lattice cryptography for federation-root identity, with the following primitives in active use:

  • ML-DSA-65 (FIPS 204; formerly CRYSTALS-Dilithium) — module-lattice digital signatures used for federation root signing.
  • ML-KEM-768 (FIPS 203; formerly CRYSTALS-Kyber) — module-lattice key encapsulation used for forward-secret session establishment when applicable.

These are deployed in a hybrid (composite) mode rather than as pure-PQ replacements:

  • Composite Ed25519 + ML-DSA-65 signatures (draft-ietf-lamps-pq-composite-sigs) are used for federation root signing at the X.509 / trust-anchor layer. Each signature is the concatenation of a classical Ed25519 signature and an ML-DSA-65 signature over the same message; a verifier accepts the signature only if both components verify. This means a successful forgery requires breaking both algorithms, hedging against three risk surfaces:
    • Cryptanalysis of either algorithm in isolation — classical attacks on Ed25519, or future cryptanalytic advances against ML-DSA-65.
    • Implementation defects in either signature library.
    • Harvest-now-decrypt-later threats against long-lived federation-root signing material that may have to remain verifiable across a quantum transition.

Why hybrid rather than pure-PQ: the lattice schemes are standardized and well-studied but newer in production deployment than the classical curves. A composite signature commits to a signer who could break neither — which is the right posture for material that signs trust anchors and federation roots whose validity window extends into uncertain cryptanalytic territory.

Why this matters for the pattern at large: federation root keys are the longest-lived secrets in the system. They sign entity statements, trust bundles, and the keys that sign everything else. They are also exactly the kind of secret an adversary would harvest today and attempt to forge under future quantum capability. Composite signing at this layer is RECOMMENDED for any vendor implementing the federation primitives in §4.

Legacy-client interoperability is a primary reason to prefer composite over pure-PQ. JWT verifier libraries in the OIDC / OAuth ecosystem today do not generally support pure post-quantum signing schemes — jsonwebtoken, browser SubtleCrypto, server-side OIDC libraries, and the long tail of language-specific verifiers all target the classical signature suite (Ed25519, ES256, RS256). Issuing pure-PQ signatures would lock out that entire installed base.

For per-token JWS issuance, the mechanism is dual JWK publication in JWKS rather than composite signatures on the token itself. The issuer publishes both an Ed25519 JWK (the classical signing key) and an ML-DSA AKP JWK (RFC 9964 — "ML-DSA for JOSE and COSE") in the same RFC 7517 JWKS, each with its own kid. Two verifier postures both work against the same JWKS:

  • Legacy verifiers iterate the JWKS to find a JWK matching the JWT's kid and alg. Entries whose kty/alg they don't recognize (the ML-DSA AKP entries) are skipped at key-selection time per RFC 7517 §4.6 (unknown parameters are ignored); they find and use the Ed25519 entry. Tokens signed with EdDSA verify normally. This has been validated against real-world JWT libraries (including a fix landed in ruby-jwt).
  • PQ-aware verifiers consume the same JWKS, recognize the RFC 9964 AKP entries, and can verify tokens signed with ML-DSA. The issuer selects per-audience: an audience advertising PQ support in its OAuth metadata receives an ML-DSA-signed token; everyone else receives an EdDSA-signed token.

This gives a smooth migration path: an issuer adopting RFC 9964 today is immediately verifiable by every existing OAuth/OIDC verifier (via the Ed25519 JWK) AND by PQ-aware verifiers (via the ML-DSA AKP JWK). Pure-PQ issuance would lock out the entire installed verifier base.

The composite construction described above operates at a different layer: federation trust-anchor signatures (entity statements, trust bundles, federation root certs) are emitted as composite Ed25519+ML-DSA-65 per draft-ietf-lamps-pq-composite-sigs. Per-token JWS uses the simpler dual-publication pattern because JWS Compact Serialization carries exactly one alg and verifiers MUST reject unknown algs per RFC 7515 §4.1.1 — composite would not be JWS-verifiable today without ecosystem-wide upgrades.

The reference implementation pins specific library versions (ml-dsa = "0.1.0-rc.11", ml-kem = "0.3") and tracks the FIPS 203/204 final-form crypto closely; vendors should expect to track NIST PQC errata and library API breakage during the standardization tail.

10.4 Workload attestation

A reference implementation issuing JWT-SVIDs MAY attest local workloads using:

  • Unix peer credentials over the Workload API socket (UID/GID, executable path)
  • systemd unit name for systemd-managed services
  • Kubernetes ServiceAccount + Pod selector for K8s deployments

These are SPIRE's standard attestor types; an implementation can either run SPIRE or implement equivalent attestors in-process.

10.5 Reference implementation status

This section records the implementation status of the reference implementation against the patterns described in this document. Items marked implemented are running today; partial means the schema/primitive is present but full runtime exposure is not; designed means specified but not built; fork-side means present on a private branch the reference team maintains, not on upstream.

The reference implementation has two deployment shapes:

  • hyprstream (github.com/hyprstream/hyprstream) — the long-running server instance described in §10.1
  • the-onion-shell (wanix-tor) (gitlab.torproject.org/ewindisch/the-onion-shell) — a browser WASM Go shell running inside Wanix that exercises the censorship-resistant identity and transport surfaces (did:onion, WebRTC/Snowflake, WebAuthn-PRF). It is the existing deployment for the §3.4 key-rooted identifier family and the §6.6 WebRTC / Tor transports.
Pattern surface Status Notes
OAuth 2.1 issuer + RFC 9728 protected-resource-metadata implemented
OAuth private_key_jwt + CIMD + PAR + DPoP + PKCE implemented
Federation gate (federation:register policy primitive) implemented Deny-by-default; templated allow path; fail-closed on policy outage
did:web users with DID documents at /users/<id>/did.json implemented
atproto handle resolution via /.well-known/atproto-did designed Roadmapped; not served today
Atproto-OAuth-style JIT user creation from a foreign DID designed
did:plc support designed
CAR-file repository interchange out of scope (v1)
9P-aligned namespace interface (Mount trait, Nine.* schema) implemented In-process; nine.capnp embedded in service unions
Scoped-fs envelope pattern (e.g., ModelFsRequest) implemented (schema); runtime walk/open/read of synthetic trees partial
Streaming via StreamInfo + DH-derived topic + chained HMAC implemented ZMQ XPUB/SUB substrate
ZMTP 3.1 (Cap'n Proto payloads) over QUIC with ALPN zmtp3 implemented Intra-instance mesh
Wanix 9P bridge over SharedArrayBuffer DMA ring fork-side The reference implementation maintains the DMA-ring extension on a fork of Wanix; not upstream. Direction is client-only (reference → Wanix-as-server).
General-purpose 9P2000.L network listener not built We have a 9P client + Wanix-DMA bridge; no Tlisten 9P server bound to TCP or QUIC for cross-implementation peer access
MCP-as-mount (/mcp/tools/... browseable tree) designed The pattern in §8 is design; the MCP endpoint serves over Streamable HTTP (rmcp StreamableHttpService) today
MCP endpoint serving HTTP/3 / WebTransport for browsers designed HTTP/1.1 over TLS today
Services-as-mount synthetic trees (/services/policy/...) partial Service-side schemas are 9P-shaped; full operator-facing live mounts are not yet served
Cross-instance peer namespace mount (/peers/<peer>/) designed §9.4; speculative
SPIFFE JWT-SVID issuance + trust bundle endpoint designed Phase-planned; nothing served today
SPIFFE Workload API socket designed
Per-process namespace isolation (Plan 9 style) designed Today: process-level mount table with longest-prefix routing
did:onion identifier root + Tor v3 onion service hosting implemented (the-onion-shell); not in hyprstream the-onion-shell allocates Tor circuits and onion connectivity as Wanix capabilities; hyprstream itself does not publish a did:onion identity today
WebRTC DataChannel transport (Snowflake) implemented (the-onion-shell); not in hyprstream Snowflake capability with patched pion v4; published as #cap/<id>/{ctl,status,data} per the §7.3 control-file convention
Tor circuit transport via Arti implemented (the-onion-shell); not in hyprstream Rust Arti bridged to Go through window.__artiConns
WebAuthn-PRF-derived Ed25519 client signing implemented for SSH (the-onion-shell); not yet wired to OAuth/DID in hyprstream Hardware-resident key; derived in JS per signature operation; never persisted in Go memory
Cross-deployment unification (single key → did:web + did:onion + OAuth signer) designed The §3.5 hardware-bootstrap pattern; pieces exist on both sides but are not yet bound together

This matrix exists so collaborators can distinguish the pattern's normative shape from what any single implementation has shipped. Other implementations are encouraged to publish their own status matrix in the same shape.

11. Open questions for collaboration

The following are areas where vendors and the Wanix team would benefit from agreement before independently implementing.

11.1 MCP-as-mount specification

Should the MCP catalog being projectable as a 9P-mountable tree be a spec extension (so MCP clients can speak 9P and bypass the HTTP/SSE catalog when they prefer)? Or an implementation pattern (each MCP server vendor exposes the mount independently)? The path schema in §8.1 is a starting point, not a spec.

11.2 Federation policy resource name

Standardizing on federation:register (or whatever the agreed string is) lets operators write portable policy across implementations. This is a small thing with disproportionate operator-experience value.

11.3 Mount trait shape

Async vs sync, exact method signatures, error types. The Wanix team's Mount surface and other implementations' surfaces would benefit from textual alignment even if exact types vary by language.

11.4 In-tree scripting

Plan 9 used rc as its shell. The reference implementation uses Tcl, implemented via molt. Wanix does not constrain the scripting layer; the choice is the reference implementation's, not part of the pattern.

The Tcl-via-molt choice has a specific design rationale at the spec layer worth recording:

  • Tcl is string-typed. Every value is a string with optional internal representations. This maps cleanly onto LLM token generation: the output of tools/call or infer.generateStream is naturally a stream of strings; a shell language whose every value is already a string composes those streams without a typing impedance mismatch. Strongly-typed shell front-ends (PowerShell-style typed pipelines, typed APIs) impose coercions at every boundary that the agentic-namespace stream traffic doesn't need.
  • molt provides Rust-side runtime type and memory safety. Embedding a Tcl interpreter in a long-running daemon hosting untrusted scripts is a non-trivial security boundary. molt's Rust implementation gives memory safety for the host process and runtime checks that would otherwise need to live in a sandbox layer around a C-implemented Tcl.

The string-pipeline alignment with LLM output is the design property; molt is the implementation property that makes it safe to embed.

A future direction is to support rc alongside Tcl for operators coming from the Plan 9 tradition. The 9P tree itself is the language-neutral interface; any shell language (Tcl, rc, POSIX) can drive it. A standardized way to expose the same namespace to multiple shell front-ends would be useful. Strongly-typed shell front-ends (PowerShell, etc.) may layer typed views over the same string-pipeline backbone where the use case demands it.

11.5 Trust-anchor pinning vs operator allowlist

Open OpenID Federation 1.0 envisions trust-anchor pinning with chain walking through intermediates. The pattern in §4 uses operator allowlists at the leaf instead — simpler, no transitive-trust surprises, requires explicit operator action per new peer. Vendors building open federation networks may want the chain-walking variant; those building curated federation may prefer allowlists. A standard policy primitive that accommodates both views (chain-walking implementations express anchor pinning as a wildcard allow) would be useful.

11.6 Atproto compatibility — did:plc and CAR

This pattern is explicitly atproto-compatible (§3.2, §5.2). Two open areas where the alignment could be tightened:

  • did:plc support alongside did:web. atproto's primary DID method is did:plc; the reference implementation currently uses only did:web. A pattern that gracefully supports both — without forcing each instance to operate a PLC directory — would broaden adoption. Two concrete questions: (a) how does the federation gate's origin-pattern matching apply to did:plc identifiers, which aren't rooted at a DNS name? and (b) is there a canonical mapping from did:plc:<id> to the operator-facing rule shape in §4.2?
  • CAR-file repository interchange. atproto's repository data-interchange model uses CAR files. The reference implementation does not adopt CAR today and treats it as out of scope (§3.2, §5.2). Implementations that do want CAR interop should be able to add it without changing the namespace/federation surfaces this document specifies.

11.7 Cross-instance namespace mounting (§9.4)

Whether peer mounts (one instance mounting another's namespace) should be a spec feature or left to ad-hoc implementations. The 9P protocol supports it natively; the gating question is per-path policy semantics when the path crosses a federation boundary.

11.8 Post-quantum signature interop

The reference implementation issues federation-root signatures in composite Ed25519 + ML-DSA-65 form (§10.3.2, RFC 9964). For this to provide value across implementations, peers must be able to verify composite signatures. Two coordination questions for the wider ecosystem:

  • Verifier baseline. Should implementations of this pattern be required to verify composite signatures by some agreed-upon date, even if they do not yet issue them? A staggered baseline — "MUST verify composite by year N, MUST issue composite by year N + 1" — would let the verifier population catch up before issuers cut over.
  • Algorithm agility. ML-DSA is one PQ signature scheme; other lattice and non-lattice candidates exist. The pattern's federation-gate and trust-bundle surfaces should be able to negotiate an agreed algorithm without requiring a wire-format change every time NIST updates a recommendation. A capability-discovery field in the trust bundle (analogous to OAuth code_challenge_methods_supported) would let peers detect each other's supported signature suites without out-of-band coordination.

12. Glossary

Term Definition
9P / 9P2000.L The wire protocol of Plan 9's distributed filesystem; .L is the Linux-semantics variant with extended attributes and Unix permissions
agentic infrastructure Software stacks serving LLMs and the surrounding tools, identity, and policy plumbing needed for agent execution
atproto The protocol behind Bluesky; defines federated identity via handles and DIDs (atproto.com/specs, Identity, OAuth profile)
CIMD Client ID Metadata Document; OAuth extension where the client_id is an HTTPS URL pointing at a JSON metadata document (client.dev, draft-ietf-oauth-client-id-metadata-document)
COSE_Sign1 CBOR Object Signing and Encryption single-signer structure (RFC 9052); compact alternative to JWS
DCR Dynamic Client Registration (RFC 7591); legacy OAuth client onboarding
DID Decentralized Identifier (W3C DID Core); a URI scheme for self-sovereign identifiers
did:web DID method that resolves identifiers via HTTPS at a well-known path
did:plc DID method that uses a public ledger (atproto's primary method)
DPoP Demonstration of Proof-of-Possession (RFC 9449); sender-constrains OAuth tokens via a per-request signature
FQDN Fully Qualified Domain Name; the unambiguous DNS name of an instance
JWKS JSON Web Key Set (RFC 7517); a set of public keys for verifying JWTs
JWT-SVID JWT-encoded SPIFFE Verifiable Identity Document
MCP Model Context Protocol; standardizes tool/resource/prompt exchange between agent clients and tool servers
PAR Pushed Authorization Requests (RFC 9126); OAuth extension that submits authorization parameters server-side before the browser redirect
PDS Personal Data Server; atproto's term for the per-user home instance
PKCE Proof Key for Code Exchange (RFC 7636); binds an OAuth authorization code to the requester via a challenge/verifier pair
QUIC UDP-based transport (RFC 9000) with mandatory TLS 1.3, multiplexed streams without head-of-line blocking, and connection migration; substrate for HTTP/3 and WebTransport
RFC 8707 Resource Indicators for OAuth 2.0; lets tokens be audience-scoped to a specific resource
SPIFFE Secure Production Identity Framework for Everyone; CNCF standard for workload identity
SPIRE The SPIFFE reference implementation (server + agent)
SVID SPIFFE Verifiable Identity Document; an X.509 cert or JWT carrying a SPIFFE ID
Wanix Browser-based Plan 9-flavored OS providing 9P over a SharedArrayBuffer DMA ring
WebTransport Bidirectional, multiplexed stream protocol exposed to JavaScript, running over HTTP/3 (QUIC); the standards-track way to give browser code QUIC's stream semantics
@ThisIsMissEm
Copy link
Copy Markdown

A small note on compatibility: In AT Protocol, path segments in did:web's are not supported, it expects the DID document to be did:web:actor.example -> https://actor.example/.well-known/did.json, so reusing AT Protocol's handle resolution may actually cause compatibility issues because it'll allow a handle string to resolve to an invalid (according to AT Protocol) did:web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment