Summary

Version 1.6 of the Pico Engine ships the pico-to-pico identity layer I promised but hadn't built. Every pico now carries two DIDs: a portable did:webvh that says who it is, and a private did:peer for each relationship it forms. This is the piece that lets a pico move between engines and lets two meshes create a relationship without a federation agreement set up in advance.

Two kinds of identity for a pico

Last month I released version 1.5 of the Pico Engine and argued that identity inside the engine is really three problems, not one. A human needs to prove who they are to a pico mesh; an outside app or webhook needs a scoped way into the mesh; and one pico needs to know which pico is calling it and whether to trust it. I shipped the human and third-party layers then and left the harder one for later. Today I'm releasing version 1.6, and that third layer, pico-to-pico identity, is here.

The short version is that every pico now has its own cryptographic identity, and that identity can travel. This is the piece I've wanted for a long time, because it's the foundation for a goal I keep circling back to: picos and whole meshes that are portable between engines rather than pinned to the machine where they happened to be born. Let me describe the shape of it, because the design turns on a distinction that took me a while to get right.

The Layer I Was Waiting to Build

I shipped the first and third layers in 1.5 and held this one back on purpose. Passkeys and OAuth sit on machinery the engine already had: channels, ECIs, and channel policy. Pico-to-pico identity required something a bit more complex. It meant moving DID keys into the engine as a core primitive, pulling a lot of KRL into wrangler, and running pico-to-pico traffic over DIDComm instead of plain HTTP. That's a large change, and it touches much of the engine, so I wanted to handle it separately.

This layer is what makes a pico an actor with a real online presence. A channel identifier tells you how to reach a pico on one engine right now; it says nothing durable about the pico's identity. For introductions between strangers, for encrypted traffic, and eventually for verifiable credentials, a pico needs a stable answer to "which pico is this?" that is portable across engines. That answer is a DID, and in 1.6 every pico has one from the moment it's created.

Two DIDs, Two Jobs

The design I landed on gives each pico two types of DIDs, each with different jobs. The first is a did:webvh, the pico's portable identity. Every pico gets one when it's created, and the engine serves its DID document at a stable URL so anyone can resolve it. Think of it as the pico's passport: it's how a pico introduces itself, and it's what you hand a stranger who needs to know who you are before they'll talk to you.

The second is a did:peer, and there's one of them for every relationship, called a subscription, a pico forms. It isn't provisioned up front; the two engines mint a fresh pair during the introduction handshake. There is a separate pair of peer DIDs for each relationship. Think of it as an email you give to exactly one person. The passport says who you are to everyone; the email is the private line for a single connection, and it means nothing to anyone else.

Subscription developer UI showing webvh DID at the top and peer DIDs in the established subscription
Subscription developer UI showing webvh DID at the top and peer DIDs in the established subscription (click to enlarge)

The developer UI makes the split concrete. The Identity panel at the top of a pico's Subscriptions tab shows its one did:webvh, the passport it hands out, next to the switch that decides whether it accepts unsolicited introductions to that DID. Each established subscription below carries a different pair: the remote party's peer DID and this pico's own peer DID, minted for that one relationship. The subscription's Rx channel is where policy still lives; the DIDs name who the two parties are, and the ECI decides what this one is allowed to do here.

That split matters more than you might think. A single, universal identifier is a mistake I've watched newcomers to identity make for years. Correlatability is a factor, but the bigger payoff is practical: because every relationship has its own identifier, each one can be managed on its own. You can rotate the keys on a single connection, or tear it down and rebuild it, without disturbing any of the others; if a peer DID is ever compromised, the damage stops there and you recover that one relationship in isolation. It's the email analogy again, where changing the address you gave one contact doesn't impact the rest.

How Picos Actually Talk

Two picos that aren't parent and child talk to each other through a subscription, which is just a pairwise relationship with a record on each side. In 1.6 you form one by handing the initiator the recipient's did:webvh as the target; the two picos run the introduction, agree, and store each other's peer DIDs. After that, queries and events flow over the peer relationship. On the same mesh the engine keeps that traffic local; across meshes or across engines it runs encrypted over DIDComm to the peer's address.

DIDs didn't need a new authorization mechanism; they slot into the channel model the engine already had. A subscription's peer DID is its channel, and channel policy governs it exactly as it governs any other channel, so authorization is enforced right where it always was. The DID names who is on the other end of the relationship, and the policy on that channel decides what they are allowed to do. Legacy ECIs haven't gone anywhere either; parent and child picos still talk over family channels named by ECIs, where a full DID would be overkill.

The consequence I care about is what this does for trust between strangers. A pico can choose to accept unsolicited introductions to its public identity, which lets a community pico or a registry take subscriptions from picos it has never met. Two meshes owned by two different people, on two different engines, can form a relationship because one resolved the other's DID and both agreed; no one had to stand up a federation agreement or register with a common broker first. The relationship is the unit of trust, and these changes make it cryptographic and portable.

Why These Methods, Not KERI

Anyone who has spent time at IIW will ask why I reached for did:webvh and did:peer rather than KERI, which answers the same "who is this actor" question with self-certifying identifiers and a key event log that needs no web host at all. KERI has a great design, and on the narrow point of surviving a move it's arguably stronger than did:webvh; a KERI identifier carries no URL that has to stay reachable, which is exactly the loose end I admitted above. So this wasn't a judgment that one approach is right and the other wrong. It came down to what picos already need to do the moment two of them are connected.

That need is messaging, and picos exchange events and queries over DIDComm. Peer dids were built for precisely that job: a pairwise, private relationship identity that DIDComm tooling already knows how to carry encrypted traffic over. Pairing it with did:webvh for the public introduction let me use a messaging philosophy the pico world has used for years since it parallels the DIDComm model. KERI's strengths live mostly in the identifier and its key history; picos needed the identifier and the encrypted conversation that follows it, and the DIDComm path gave me both with the least new machinery. If the day comes when the portability tradeoff bites hard enough, I'd happily revisit that choice.

A Step Toward Portable Meshes

I won't pretend a pico can pick up and move to a new engine today with no loose ends; the engine base URL is still baked into a pico's did:webvh, and making that survive a move is work still ahead. But the hard part, giving each pico a real cryptographic identity and a way to carry its relationships, is now in the engine rather than wrapped around it. That's the foundation portability was waiting on, and everything above it, from moving a mesh between engines to carrying credentials between them, builds on this.

Full portability is the largest gap, but it isn't the only one. The identifiers are in place; the verifiable credentials that ride on them are not, so a pico can prove who it is but can't yet hand another pico a signed claim about what it is or may do. Key management is thin across the board: the private keys behind a pico's DIDs sit in the engine's own store today, when they belong in the operating system's key vault or hardware-backed storage, and rotation and recovery need more work now that a pico's identity is the thing others rely on. Losing the keys shouldn't mean losing the pico. Authorization also still keys off the channel a caller holds rather than the identity behind it, so the natural next step is to let a receiving pico decide based on who is actually calling using a proper authorization engine. Each of these builds on the identity that landed in 1.6 rather than replacing it.

I've spent a long time arguing that people deserve software that acts for them and that they own outright, not a rented seat on someone else's platform. A mesh you can pick up and move provides infrastructure to realize that idea. If your picos can only live on one engine, you don't really own them unless everyone is running their own engines (not likely). Version 1.6 doesn't finish that story, but it lays down the identity it depends on. The next thing I want to do is exercise it in Manifold and find the rough edges by using it. If you want the details, the DID and Subscriptions pages walk through each piece.


Photo Credit: Two kinds of identity for a pico from ChatGPT (public domain)


Please leave comments using the Hypothes.is sidebar.

Last modified: Wed Aug 12 10:19:18 2026.