What is an atproto repository?

Quick reaction to @ipv.sx 's All data should be permissioned data and the emerging permissioned data atproto spec , in particular Richard's proposal to drop deniable crypto from the spec.

We have established that data in atproto is stored in repositories. What is an atproto repository?

At the lowest level, as far as I understand it, an atproto repo is a key-value store. The keys are record paths and the values are DRISL-encoded, lexicon-defined records with $type fields. The only thing that's different at this level with regard to public and permissioned data is the key format:

Public data

Key:

at://did:web:iame.li/place.stream.chat.message/3mpmmppz7le24

Value:

{
"$type": "place.stream.chat.message",
"text": "brb",
"streamer": "did:web:iame.li",
"createdAt": "2026-07-01T23:01:18.093Z"
}

Permissioned data

Key:

at://did:web:example.com/space/com.atmoboards.forum/streamplace-secrets/did:web:iame.li/com.atmoboards.thread/3mpr7ae3oef22

Value:

{
"$type": "com.atmoboards.thread",
"text": "i won't actually brb, i signed off for the day ;)",
"createdAt": "2026-07-01T23:01:19.173Z"
}

How do I sync an atproto repository?

The atproto-savvy among you might be objecting now. No, you say, atproto repositories are Merkle trees. And you'd be right until permissioned data lands. The Merkle tree format is, until recently, how we presented atproto repositories to syncing applications. It's the format that you get over the firehose or when exporting a CAR file.

But the MST structure isn't the canonical data. Take an atproto CAR export, delete all of the Merkle tree structure, and retain all of the records themselves and the SignedCommit at the root. The MST structure you just deleted can be deterministically re-derived from what you have left and the SignedCommit will be verifiable.

I contend that in the permissioned data world, an "atproto repository" is the key-value store, and we have two ways of syncing atproto repositories:

"Public sync" is what we have now: organize the records as a Merkle tree, sign the root of the tree, publish the MST diff to the firehose.

"Deniable sync" is the new piece in the permissioned data proposal, which utilizes an LtHash set-hash digest and an HMAC to facilitate data sync in a way that doesn't allow the syncer to forward the authenticated data to anybody else.

Both sync methods drop you at the same place: you have a cryptographic proof of the contents of the repository tied to the verification key in the user's DID document.

But if you set things up this way conceptually, we now separated out the concerns of "permissioning" and "sync". Technically, there's nothing stopping you from syncing a public repository using deniable sync or a permissioned repository using public sync. Would you ever want to?

Private to Public

Streamplace is working on our "VOD Drafts" feature, allowing users to upload a video and tweak the title, description, and thumbnail before it goes live. Here's a common use case: I'd like to share this "draft video" with a few people before publishing it publicly. We can accomplish this by putting each VOD in its own space and shifting the access control over time. This yields an AT-URI like this:

at://did:web:iame.li/space/place.stream.videoSpace/3mprd6d7acd22/did:web:iame.li/place.stream.video/3mprd6d7acd22

We then grant access to an ever-expanding group of people:

All of this is easily accommodated by slowly granting ever-larger groups of people access to read the at://did:web:iame.li/space/place.stream.videoSpace/3mprd6d7acd22 space. Easy!

After this, I want to make the video public. Now things get tricky. With the current proposal, I have two bad options:

at://did:web:iame.li/place.stream.video/3mprd6d7acd22

But now all of the likes and comments that my paid subscribers left on the original video have strongRef s to the incorrect key; no good. Not necessarily impossible to reconcile by implementing applications, but difficult and confusing; you need to hold on to two primary keys.

There's a scaling problem; spaces are only designed to scale to around ~1,000,000 users. I'm extremely popular, so that's not gonna work.

There's also a discoverability problem: how do the thousands of applications that index Streamplace VODs find out that I dropped a new video? It's not on the firehose; you won't hear about it unless you were already subscribed to the space.

Or we just make the space public

Here's an alternative approach: I make the space public, meaning I just start publishing the space over the public firehose. Instead of doing deniable sync with an HMAC, we just start doing public sync with a SignedCommit. Something like:

Sync your data your way

This is just one of a variety of use cases that open up if we treat atproto repositories as the user-sovereign base containers of atproto data and allow multiple syncing methods. Maybe there's even use cases where you could go public --> private --> public --> private. But let me know what you think!