Modeling communities on permissioned data

This one isn’t a data diary! I’m working on the next one and it’ll be coming soon.

But folks are getting into the permissioned data protocol and starting to work through how to model communities on top of it. This is something that Bluesky is interested in figuring out as well .

I wanted to start the conversation on a data modeling question that I’ve been noodling on. The first half of the post explains the argument (universal communities with particular spaces), and the second half raises a couple questions that I’d love to get feedback on.

I’ve also started a thread in the community forum where we can discuss.

Against universal spaces

An idea I've seen come up a bit is establishing a generic/universal Lexicon for a "community space". One space for a community that holds every modality: forum posts, microblogs, events, chat, photos, etc.

I get the instinct on it. Atproto provides a universal data model & it’s tempting to model "universal communities" directly. But Lexicon is at its best modeling the particular and letting the universal emerge from that. I discussed this a bit in my last data diary .

There are two concrete problems with universal communities.

It's illegible at the consent screen

The space type is what we use to explain, in human words, what an app is asking for. "Give this app access to your AtmoBoards forums" works because a forum is a thing people understand. "Give this app access to your Open Communities" is abstract and doesn't ground the consent in some recognizable form. Try to layer it ("let this app create AtmobBoards posts in your Open Communities") and you're stitching together text from two different Lexicon authors and overwhelming users with variables. That overwhelm isn’t just a UX issue; it’s also a security issue. As more variables and scopes get added to consent screens, they get less secure as users are more likely to skim over them.

The access boundary is wrong

A space defines an access and sync boundary. It’s all or nothing by design. Read access to a space means access to everything. If a community is one big universal container and you want to log into an event management app to create a new community event, that app gets your chat, photos, and forum posts too. There’s no protocol-level way to scope it down.

Communities as a bunch of spaces

Spaces are all under a DID (the space authority). DIDs actually are universal! So instead of capturing a community in a single space, I think communities should be modeled as many spaces under a single DID.

When you want to extend a community with a new kind of content, you don’t widen the container. You create a new space of the right type, under the same community DID, with an overlapping (often identical) member list.

So “Protocol Nerds” becomes a forum space + an events space + a chat space + a video feed, all hanging off one community DID. Each keeps its particular type, its legible consent string, its access perimeter.

Capturing this in OAuth scopes does have implications on the app shapes that fall out:

Modality-specific: AtmoBoards does forums, across every forum of that type on the network. The OAuth resource is granted by type: “Access to your AtmoBoards forums”

Community-specific: A “Protocol Nerds” client does many modalities (forum, chat, events, photos), but only for one community. The OAuth resource is granted by authority: “everything under the Protocol Nerds community”.

The “universal community” still exists! It just gets assembled in the app layer by grouping spaces that share a community DID.

Community handles

The modality grant is easy to render. The space type & Lexicon permission sets give you human readable text. However, the community grant needs something legible too, and a bare DID isn’t really fit for a consent screen.

Good thing we have a nice handle <> DID linking system! If community DIDs have handles, same as users, then the consent screen can have something like “read and write all @protocol-nerds.network spaces”.

I also just like that communities can be known by their handle. We can think of the Atmosphere as a network of interconnected social spaces and users, not just apps.

Generic community management

Just because spaces aren’t universal doesn’t mean that there’s no room for ecosystem standards to emerge around communities. I think the most natural place for this to emerge is around community management.

I want to highlight the work that Zicklag & the folks at Roomy are doing on The Arbiter . For anyone not following that project, the arbiter is a general-purpose interoperable group-management service that sits on top of permissioned spaces. The arbiter hosts community DIDs and their spaces and exposes a standard API for creating spaces and managing those spaces, membership, and member roles.

The argument I’m making isn’t “don’t do generic/universal things”. It’s “put the genericness in the governance layer and keep the spaces themselves particular.”

Two things I want thoughts on

Okay most of that was background for the real reason I’m writing this post which is that if we dig this “particular spaces, universal communities” idea, I think it opens up a couple follow-up questions. I have some thoughts/vibes/takes, but I’m not really making a formal proposal here. I’m looking for input.

A conditional, cross modality scope?

The by-type (modality-specific) and by-authority (community-specific) axes for requesting scopes are pretty clean & straightforward, but there’s a use case that kinda sits across both.

Basically any app that is high-cardinality on both dimensions (but not totally universal), is hard to describe in a consent screen. To ground this in reality: say the Bluesky app implements a “communities” feature. In Bluesky communities, you make microblogging posts scoped to the community. But communities also exist as “atmospheric communities” with their own website that a bunch of different modalities hang off of.

The Bluesky app is fine focusing on only really surfacing the Bluesky modality. But it also wants to serve as the “lobby” or “router” for your communities. Basically it wants to deliver notifications across all modalities, but only for communities that actually show up in Bluesky. This requires read access to those spaces.

The logic is roughly “If I’m a member of an app.bsky.group space, grant me access to all the spaces that that space’s owner DID is the authority for, that I’m also a member of.

That’s not really by-type or by-authority. It’s by-authority, but triggered by-type. (It’s actually very similar to how the signal collection works in atproto sync if you’re familiar with that).

I think capturing this logic in a scope is too complex/dynamic. I have two other ideas for addressing this.

Solve this in consent screen UX. Picture an “authoirty-picker”, kinda like the iOS photo-picker, where the user explicitly picks which communities an app sees across modalities. Let it be default-filled by the modality-trigger logic (“here are the communities with a Bluesky groups space, grant all?”). This still surfaces the logic in the consent screen & keeps the user in the loop without inventing an increasingly convoluted scope language.

Just own up to the limitation. And say that if you want notifications to surface in the Bluesky app, then they need to be published in the Bluesky space. This doesn’t necessarily mean publishing the thing itself in the Bluesky space. Rather you could publish a pointer/envelope to the thing in the Bluesky space. So for instance, an event still gets published in the event space, but an “event notification” gets published into the Bluesky space.

How’re folks feeling about this? Do you have the same sense that I have of this use case? Any thoughts on other types of applications that are going to be hard to model on these type/authority axes?

Who runs an arbiter & who hosts a community?

Again using the Bluesky app as a practical example. If you create a community in the app, we can’t create the community on any service other than your PDS or the Bluesky app. Those are the only services that a user has any sort of relationship with.

(Of course, regardless of where a user creates the community, they should always be able to migrate it.)

The PDS is I think the most obvious service for communities to be hosted. But the PDS is not going to implement an arbiter interface, it’s only going to implement the imperative APIs for creating & managing spaces and member lists. So if we want to give richer community management tools for communities created on the Bluesky PDS, then the arbiter needs to be able to run “statelessly.” Rather than hosting the space/community, it holds an OAuth credential for a PDS, and uses that credential to imperatively steer the spaces under the community.

That’s getting a little convoluted (and also a bit inefficient). Also it sounds a little nice to not have to ship a whole “controlled account/DID” system in the PDS (as I mentioned in data diary 4 )! So maybe instead we consider the arbiter part of “the Bluesky app”. The downside with that is that every app then ends up deploying their own arbiter that hosts the communities. Users that create communities on different apps end up having the communities they “own” scattered across all sorts of different platforms. This is all discoverable on-protocol, so maybe this is fine? But it seems like a lot of cognitive overhead to bounce around between different arbiters to manage all your different communities.

Any way we cut this, we probably need to prioritize migration between arbiters. This will be supported at the protocol level. But I expect arbiters will also be storing off-protocol state for all the roles/memberships that they manage.

What’re thoughts on this one? Do we think we need a controlled account system in the PDS or do arbiters suffice? We want it to be easy for apps to “do the right thing”. Is deploying an arbiter if you’re doing communities “easy enough”? How do users manage the complexity of the communities they own getting scattered across a bunch of different apps?

Don’t stress, it’ll be messy

Just to remind myself & you all: this whole layer is much more forgiving than the protocol underneath it. That’s actually why we’re intentionally trying to keep the protocol layer so simple (just a member list). The protocol gets standardized & is hard to change. The layer we’re talking about here (how we model stuff on top of the protocol) is mostly recommendations. At the end of the day this is an open network. Folks can do what they want. If the shape is wrong, we’ll try new ones. It’ll be messy, but that’s the fun of it!

Let me know your thoughts! I created a sister thread in the community forum for discussion. If you have takes, please share them. If I’ve missed previous discussions on exactly these topics, please provide links to those as well.