External Publication
Visit Post

Tangled: Knot-stored COB proposal

boltless May 21, 2026
Source

In this article, I will propose a new architecture to make Tangled fully decentralized, mainly about collaborative objects (COBs). I will explain what we are lacking today, how can we solve it, and what are the limitations of my own proposal.

DISCLAIMER: All future directions introduced in this article are just my personal proposal and not an official plan. We haven't fully settled down our plan yet and there is high possibility for us to choose completely different approach. We just thought it is interesting to share publicly to gather more opinions from users, so keep in mind that I'm not representing the Tangled in any sense from this article.

NOTE: This article is pretty long explaining all the backgrounds, what we've tried and how that fails, and all the big and small reasonings behind my choice. If you are ready to face the new architecture and don't have time to read all this, go straight to the summary.

How Tangled handles COBs today

Currently, author of the issue owns the issue record. The record content is stored in author's PDS. Maintainers cannot control anything about those issue records. To allow maintainers collaboratively modify the issue state, Tangled had to make issue/PR states as dedicated records like sh.tangled.repo.issue.state and sh.tangled.repo.pull.status where last ingested one overrides the old state.

For more complicated collaborative objects like issue/PR labels... we basically implemented a CRUD system on top of existing atproto CRUD system. We have sh.tangled.label.op record which represents add/update/delete operations. These records represent a modification instead of specific state and appview is expected to ingest everything in correct order, then amend everything to the final state. Here, we just assume label operation records to not be deleted or modified and always ingested in correct order and nothing will ever be deleted.

Even when initial label adding label:duplicate is deleted from the network, appview just remembers it to preserve current state.

This current architecture becomes way more complicated when we consider collaborators can also be removed.

Our current solution is to just... not allow removing a collaborator.

Even that doesn't fully work. What if Bob nukes his account? Suddenly all bob's work to that repo will disappear from entire network. This should not happen and obviously not an expected behavior in collaboration platform. For example, we don't expect old contributor to be able to nuke all their codes and commits from the project without permission just because they want to. Current Tangled appview is working fine because we use our own DB as a source of truth collecting all events in real-time since the very beginning, validating them just as we received them.

Because appview is collecting distributed issues for each repositories, the issue/PR ids are appview-local and doesn't work universally.

This situation is not good both for feature-wise and architecture-wise. We want full decentralization while acting as a working contribution platform. Maintainers should be able to restore the collaborated state from scratch without depending on centralized service holding all the cache.

Recap about did-for-repo situation

Before going further, let me do a recap of the did-for-repo situation for people who haven’t been following recently.

I heavily recommend reading the repo lifecycle post by Nelind because it can give you really good insights about "repository having a DID" concept.

Tangled recently switched the git repository identifier from sh.tangled.repo record AT-URI to plain DID. This is to allow repository transfer to different owner without loosing all the related data like stars, issues and PRs. We are still in progress of migrating existing records using the AT-URI as a reference, but eventually DID will be the preferred way to reference the git repository.

How repo renaming works today

While DID of the git repository represents the existence, owner still has sh.tangled.repo record to claim the ownership of that repo. Basically we do two-way verification for the repository ownership:

When there are more than one sh.tangled.repo records pointing to same git repository DID, we treat invalid ones as "old repository name" and redirect to current name.

For example, when alice renames repo-a to repo-b,

This way, we can easily rename repo and even have redirections for old names. Same architecture can be used for future repo owner transfer too.

Few things I'd like to change further

Atproto Repository vs Tangled Git Repository

Now "atproto repository" and "Tangled git repository" are pretty similar:

|                | atproto repository | tangled git repository |
|----------------|--------------------|------------------------|
| identity       | did:plc / did:web  | did:plc / did:web      |
| host service   | PDS (#atproto_pds) | Knot (#tngl_knot)      |
| data structure | MST                | DAG (git)              |
| data format    | CBOR               | raw git blobs          |
| event stream   | firehose           | knotstream             |
| fetch/sync     | xrpc               | git                    |

Git-repository-owned data

Due to the limitations of our current model and the context where repos can now exist as standalone identity without belonging under immutable owner, it's obvious that we should move some of the data under the git repository itself to achieve full decentralization. Since repo owner can also change, any data owned by the git repository must be editable by any arbitrary user with correct permissions. The owner is the first collaborator of the repository. Therefore, we refer to all repository-owned data as collaborative objects (COBs).

Here is a list of data that should be owned by the git repository.

Decision to store issues and PRs under the git-repository's identity will bring up lots of questions which I will answer later in this article. For now, let's focus on a fact that "Git repo needs to own some amount of structured data" because identity and metadata parts are pretty obvious ones.

Things like stars and comments won't be included because we don't expect git repository to verify those. If maintainers value comments and want to own those, they can just import those to issues.

Attempt 1: Can we merge Knot and PDS?

My first thought was to assign a PDS to the git repository. But then users need to host two separate services just to self-host their own git repos. The syncing can also be a problem. We currently use git to sync the git objects and it's unlikely for us to change that just to use more atproto infra. Which means if we assign both PDS and Knot to a git repository, we will basically have two separate data synced by two separate protocols.

Even if we decide to make Knot become PDS-compatible while serving git in parallel, we should blindly guess that #atproto_pds service in specific DID to be Knot-compatible. Which, can technically work by fetching Knot-specific xrpc methods but requiring a fetch just to ensure the service type is not ideal. Especially when we can define clear service type in DID document.

The Knot needs its own spec and that indicates having Tangled-specific service namespace. Having application-specific service isn't crazy, Bluesky also has its own dedicated services like #bsky_chat and #bsky_feedgen.

Attempt 2: How about T-log service?

One proposed solution is to have a general t-log service that cryptographically guarantees the timing/ordering of events. But it has several problems:

Example scenario - missing records

T-log service is about the verification and not about data syncing. "When this event happened" is a thing that is missing from current ATproto spec and doubtlessly a good-to-have feature. But it doesn't solve the real problem we are suffering with. For T-log service to work, it should store all event history alongside with their contents and that's essentially what I'm going to suggest.

We are already storing structured data in git

If you think about it...

All these informations can be stored outside of git but included in git for convenience and simplicity. It's not that crazy to store structured data in git. Imagine a world where you can fetch all public data related to the repository via single command: git clone --mirror.

How Radicle handles same problem

Radicle exactly does this. It uses git to store structured data. They store every records under the git repository in special refs like refs/rad/id and refs/cobs/<nsid>/* and sign entire repository state using refs/rad/sigrefs. source

Proposed Architecture

Just store COBs in git!

Make Knot more PDS-shaped with ability to store records (but not actual #atproto_pds in any sense.)

We already have complete data structure in Knot and already syncing them pretty similar to syncing PDS data, so why not just use that? We can follow the same architecture from Radicle. We can either use refs/tngl/sigrefs or just reuse refs/rad/sigrefs.

$ git cat-file -t refs/tngl/sigrefs
blob

$ git cat-file -p refs/tngl/sigrefs
9767b485c2aad1e23097d2b5165287ba84cfa452 refs/heads/master
088bdecc8d3b64f1d69638e75daeadcaca387b60 refs/cobs/org.tangled.repo.identity/self
f3eaa7454e3a4714885905ae99f616fc7895b5fa refs/cobs/org.tangled.repo.collaborator/3mlof4wfodj22
0590b78ee42b39087983e4de04164065e5aa11bc refs/cobs/org.tangled.repo.collaborator/3mhgbkz4ruw22
...


$ git cat-file -p refs/cobs/org.tangled.repo.collaborator/3mhgbkz4ruw22 | jq
{
  "subject": "did:plc:3fwecdnvtcscjnrx2p4n7alz",
  "createdAt": "2026-03-19T17:04:18+02:00",
  "permissions": [...]
}

I'm not sure if cobs should be JSON or CBOR. JSON makes sense considering ease of debugging, while CBOR can be slightly more efficient.

Extra benefits of doing this

Few extra benefits that I didn't covered from above sections:

Dual-signing COBs

We should still allow users to sign their own issues and PRs to verify they actually made that operations. But in same time, it is git-repository's bug tracker so they should also be able to sign and own those issues. Both repo & author should agree with the existence of the collaborative objects. To achieve this, we can create a detached signature by creating a record in their PDS including the hard reference (I mean the strongRef) of the original record. Users can have similar effect of signing by creating sh.tangled.cob.signature records.

I said strongRef but we will be using our custom schema because it isn't pointing to the atproto repository.

This way, we can allow both users and git-repository to verify the issue while issue content preserved under the git-repo's identity.

Example scenarios

Case 1: Opening an issue

Bob opens an issue in Alice's repo.

Case 2: Editing an issue

Charlie who is collaborator of Alice's repo found Bob's issue is duplicate of old one so close it as duplicate.

Mind-shift of issue ownership & lifecycle

With this proposal, I'm changing the issue ownership model from atproto-style to self-hosted-git-forge-style, where maintainers have full control over submitted issues. They can rephrase the title, change states or even delete the issues from their own repository.

Because issues and PRs are now stored in Knots, writing something in your PDS doesn't guarantee you the record creation. All your operations, creating, updating, and deleting issue should pass the Knot and be verified by the git repository. If maintainer doesn't allow authors to delete their own issue, you cannot delete it. You can un-verify it by removing the signature stored in your PDS, but you don't have full control over the lifecycle.

Someone used to atproto-style "only you own your stuffs" might be frustrated in this decision.

Why issue should be owned by the git repository? Can't we only put things like issue state or labels in Knot while leaving issue title and body stored in users PDS? It feels like issue lifecycle should be controlled by users, in their own PDSs. If user deleted the issue, the repository should not be able to backfill it.

The reason why I think this model is ok and should work like this is because I see issues as contributions. Just like commits, your issues are contributions since their initial creation. The repository's state is now built on top of your contributions. If you want to remove it, just like git commits, you usually just amend it passing the verification step from the git repository again. You can completely delete it by rebasing the history, but that also needs the verification from repository maintainers. Once you contribute, the issue is not just yours. You've already contributed to the repository and you cannot undo that operation.

For example, something like this should not happen:

Or in case of deletion, this should be avoided:

To be clear, you still have ability to verify and un-verify your operation without the confirmation from the repository. If you un-verify your work, your work will be still there but you can still claim that you didn't do it.

FAQ

How about spindle?

Simple, Spindle is basically a Bluesky feedgen in Tangled. Both services serve automated, dynamic data for subscribed identities. The data isn't synced but fetched on-demand.

|       | Bluesky Feedgen                 | Tangled Spindle                                        |
|-------|---------------------------------|--------------------------------------------------------|
| data  | dynamic, unsigned               | dynamic, unsigned                                      |
| sync  | on-demand fetch                 | on-demand fetch                                        |
| fetch | `app.bsky.feed.getFeedSkeleton` | `/logs` (can be `sh.tangled.ci.subscribeLogs` instead) |
NOTE: Of course we can sign and sync the completed pipeline logs, but fundamental model is same as feedgen.

So basically it is a solved problem. We can follow the exactly same model of Bluesky Feeds. Git repos won't sign or own the pipelines because they don't explicitly trigger those. Instead, git repos can have full control over which spindle to use just like you have a control on which feed to use.

This leaves a fact that we might allow assigning multiple spindles to a git repository as several users requested, but that needs more discussion which I won't cover in this article.

Isn't that public write access to the Knot? DDoS?

Yes, and that's the whole point of being open-source. You accept contributions from open network. It doesn't matter cause you have full control on your own issue list. You can close them, delete them or even disable ANY submissions and just treat the entire repository as source-available. DDoS is a problem of being open-source. It's up to you to deal with them.

How will appview treat unverified COBs?

Just like unverified git commits. The appview will show them, but with "unverified" mark. Or we just don't render the author's profile or and don't link to their account at all when the issue is not verified by that user. We will be super clear when the record isn't verified, but we will still show them without connecting to your identity.

Summary

What this proposal is changing in high level:

For users:

For maintainers:

For Knot hosters:

Misc:

Basically I am giving more power to the maintainers in this proposal.

Again, this is yet unconfirmed proposal and I'm not representing the entire Tangled team's opinion. We haven't fully decided our direction yet, so we want to hear what you think. Would these changes be ok? Do you have better ideas? Please share your thoughts if you have any :)

Edit 1: correction in git data structure

Discussion in the ATmosphere

Loading comments...