Un système de fichiers social
Cet article est une traduction française de « A Social Filesystem » écrit par Dan Abramov et publié le 18 janvier 2026 sur son blog overreacted.io. Traduit et republié avec son autorisation.
Vous vous souvenez des fichiers ?
alt
Un dessin d'une icône de fichier.
Vous écrivez un document, vous appuyez sur « Enregistrer », et le fichier est sur votre ordinateur. Il est à vous. Vous pouvez l’inspecter, l’envoyer à un ami, l’ouvrir avec d’autres applications.
Les fichiers viennent du paradigme de l’informatique personnelle.
Cet article, pourtant, ne parle pas d’informatique personnelle. Ce dont je veux parler, c’est de l’informatique sociale — les applications comme Instagram, Reddit, Tumblr, GitHub et TikTok.
Quel rapport entre les fichiers et l’informatique sociale ?
Historiquement, pas grand-chose — jusqu’à récemment.
alt
Les répertoires de plusieurs utilisateurs forment ensemble un système de fichiers social distribué.
Mais d’abord, un hommage aux fichiers.
Pourquoi les fichiers sont géniaux
Les fichiers, tels qu’ils ont été inventés, n’étaient pas censés vivre à l’intérieur des applications.
Puisque les fichiers représentent vos créations, ils doivent vivre quelque part sous votre contrôle. Les applications créent et lisent vos fichiers pour votre compte, mais les fichiers n’appartiennent pas aux applications.
alt
Les fichiers vivent en dehors des applications, dans le système de fichiers de l'utilisateur.
Les fichiers vous appartiennent — à vous, la personne qui utilise ces applications.
Les applications (et leurs développeurs) ne possèdent peut-être pas vos fichiers, mais elles ont besoin de pouvoir les lire et les écrire. Pour le faire de façon fiable, les applications ont besoin que vos fichiers soient structurés. C’est pourquoi les développeurs d’applications, en créant leurs applications, inventent et font évoluer des formats de fichiers.
Un format de fichier est comme une langue. Une application peut « parler » plusieurs formats. Un même format peut être compris par de nombreuses applications. Applications et formats sont en relation plusieurs-à-plusieurs. Les formats de fichiers permettent à différentes applications de travailler ensemble sans se connaître.
Prenez ce .svg :
alt
Un dessin fait dans Excalidraw, enregistré au format SVG.
SVG est une spécification ouverte. Cela signifie que différents développeurs s’accordent sur la façon de lire et d’écrire du SVG. J’ai créé ce fichier SVG dans Excalidraw, mais j’aurais pu utiliser Adobe Illustrator ou Inkscape à la place. Votre navigateur savait déjà afficher ce SVG. Il n’a pas eu besoin d’appeler la moindre API d’Excalidraw ni de demander la permission à Excalidraw pour l’afficher. Peu importe quelle application a créé ce SVG.
Le format de fichier est l’API.
Bien sûr, tous les formats de fichiers ne sont pas ouverts ou documentés.
Certains formats sont spécifiques à une application, voire propriétaires, comme .doc. Et pourtant, bien que .doc ne fût pas documenté, cela n’a pas empêché des développeurs motivés de le rétro-concevoir et de créer davantage de logiciels qui lisent et écrivent du .doc :
alt
Plusieurs applications différentes lisent et écrivent le même format .doc.
Une victoire de plus pour le paradigme des fichiers.
Le paradigme des fichiers capture une intuition du monde réel à propos des outils : ce que nous fabriquons avec un outil n’appartient pas à l’outil. Un manuscrit ne reste pas dans la machine à écrire, une photo ne reste pas dans l’appareil photo, et une chanson ne reste pas dans le microphone.
Nos souvenirs, nos pensées, nos créations devraient survivre aux logiciels que nous avons utilisés pour les créer. Un stockage agnostique des applications (le système de fichiers) impose cette séparation.
Un fichier a plusieurs vies.
Vous pouvez créer un fichier dans une application, mais quelqu’un d’autre peut le lire avec une autre. Vous pouvez changer d’applications, ou les utiliser ensemble. Vous pouvez convertir un fichier d’un format à un autre. Tant que deux applications « parlent » correctement le même format de fichier, elles peuvent travailler en tandem, même si leurs développeurs se détestent cordialement.
Et si l’application est nulle ?
Quelqu’un peut toujours créer « la prochaine application » pour les fichiers que vous avez déjà :
alt
Une nouvelle application vient remplacer l'ancienne, en lisant les mêmes fichiers.
Les applications vont et viennent, mais les fichiers restent — du moins, tant que nos applications pensent en fichiers.
Le dossier fourre-tout
Quand vous pensez aux applications sociales — Instagram, Reddit, Tumblr, GitHub, TikTok — vous ne pensez probablement pas aux fichiers. Les fichiers, c’est réservé à l’informatique personnelle, non ?
Une publication Tumblr n’est pas un fichier.
Un follow Instagram n’est pas un fichier.
Un vote Hacker News n’est pas un fichier.
Mais et s’ils se comportaient comme des fichiers — du moins, sur tous les points importants ? Imaginez que vous ayez un dossier contenant tout ce que votre personnage en ligne a publié :
alt
Un dossier contenant toutes les créations d'un utilisateur à travers différentes applications sociales.
Il contiendrait tout ce que vous avez créé à travers les différentes applications sociales — vos publications, vos likes, vos scrobbles, vos recettes, etc. On pourrait l’appeler votre « dossier fourre-tout ».
Bien sûr, les applications fermées comme Instagram ne sont pas construites ainsi. Mais imaginez qu’elles le soient. Dans ce monde-là, une « publication Tumblr » ou un « follow Instagram » sont des formats de fichiers sociaux :
- Publier sur Tumblr créerait un fichier « publication Tumblr » dans votre dossier.
- Suivre quelqu’un sur Instagram mettrait un fichier « follow Instagram » dans votre dossier.
- Voter sur Hacker News ajouterait un fichier « vote HN » dans votre dossier.
Notez que ce dossier n’est pas une sorte d’archive. C’est là que vos données vivent réellement :
alt
Le dossier de l'utilisateur est la source de vérité ; les applications en reflètent le contenu.
Les fichiers sont la source de vérité — les applications refléteraient ce qui se trouve dans votre dossier.
Toute écriture dans votre dossier serait synchronisée vers les applications concernées. Par exemple, supprimer un fichier « follow Instagram » fonctionnerait aussi bien que se désabonner via l’application. Publier dans trois communautés Tumblr à la fois se ferait en créant trois fichiers « publication Tumblr ». Sous le capot, chaque application gère des fichiers dans votre dossier.
Dans ce paradigme, les applications sont réactives aux fichiers. La base de données de chaque application devient essentiellement de la donnée dérivée — une vue matérialisée, mise en cache et spécifique à l’application, des dossiers de tout le monde.
Un système de fichiers social
Cela peut sembler très hypothétique, mais ça ne l’est pas. Ce que j’ai décrit jusqu’ici est le principe du protocole AT. Il fonctionne en production, à grande échelle. Bluesky, Leaflet, Tangled, Semble et Wisp font partie des nouvelles applications open social construites de cette façon.
Utiliser ces applications ne semble pas différent. Mais en sortant les données utilisateur des applications, nous imposons la même séparation que nous avions dans l’informatique personnelle : les applications n’emprisonnent pas ce que vous fabriquez avec elles. Quelqu’un peut toujours créer une nouvelle application pour d’anciennes données :
alt
Une nouvelle application sociale peut être créée pour les données existantes des utilisateurs.
Comme avant, les développeurs d’applications font évoluer leurs formats de fichiers. En revanche, ils ne peuvent pas contrôler qui lit et écrit des fichiers dans ces formats. Quelles applications utiliser, c’est à vous d’en décider.
Ensemble, les dossiers de tout le monde forment quelque chose comme un système de fichiers social distribué :
alt
Les répertoires de plusieurs utilisateurs forment ensemble un système de fichiers social distribué.
J’ai déjà écrit sur le protocole AT dans Open Social, en examinant son modèle sous un angle centré sur le web. Mais je trouve que le regarder sous l’angle du système de fichiers est tout aussi fascinant, alors je vous invite à faire un tour de son fonctionnement.
Un système de fichiers personnel commence par un fichier.
Par quoi commence un système de fichiers social ?
Un enregistrement
Voici une publication typique sur un réseau social :
alt
Une publication de dril disant « no », avec 819 réponses, 56 137 repartages et 125 381 likes.
Comment la représenteriez-vous sous forme de fichier ?
Il est naturel de penser au JSON comme format. Après tout, c’est ce que vous renverriez si vous construisiez une API. Décrivons donc entièrement cette publication en JSON :
{
author: {
avatar: 'https://example.com/dril.jpg',
displayName: 'wint',
handle: 'dril'
},
text: 'no',
createdAt: '2008-09-15T17:25:00.000Z',
replyCount: 819,
repostCount: 56137,
likeCount: 125381
}
Cependant, si nous voulons stocker cette publication en tant que fichier, cela n’a pas de sens d’y inclure les informations sur l’auteur. Après tout, si l’auteur change plus tard son nom d’affichage ou son avatar, nous ne voudrions pas parcourir chacune de ses publications pour les y modifier.
Supposons donc que son avatar et son nom vivent ailleurs — peut-être dans un autre fichier. Nous pourrions garder author: 'dril' dans le JSON, mais c’est inutile aussi. Puisque ce fichier vit dans le dossier de son créateur — c’est sa publication, après tout — nous pouvons toujours déduire l’auteur en fonction du dossier que nous sommes en train de regarder.
Supprimons complètement le champ author :
{
text: 'no',
createdAt: '2008-09-15T17:25:00.000Z',
replyCount: 819,
repostCount: 56137,
likeCount: 125381
}
Cela semble une bonne façon de décrire cette publication :
alt
La même publication de dril.
Mais attendez, non, c’est encore faux.
Voyez-vous, replyCount, repostCount et likeCount ne sont pas vraiment quelque chose que l’auteur de la publication a créé. Ces valeurs sont dérivées des données créées par d’autres personnes — leurs réponses, leurs repartages, leurs likes. L’application qui affiche cette publication devra les compter d’une manière ou d’une autre, mais ce ne sont pas les données de cet utilisateur.
En réalité, il ne nous reste donc que ceci :
{
text: 'no',
createdAt: '2008-09-15T17:25:00.000Z'
}
Voilà notre publication en tant que fichier !
alt
La publication réduite à ses données essentielles : le texte et la date de création.
Remarquez qu’il a fallu élaguer pour identifier quelles parties des données appartiennent réellement à ce fichier. C’est quelque chose auquel il faut être attentif lorsqu’on crée des applications avec le protocole AT. Mon modèle mental pour cela est de penser à la requête POST. Quand l’utilisateur a créé cette chose, quelles données a-t-il envoyées ? C’est probablement proche de ce que nous voudrons stocker. C’est ce que l’utilisateur vient de créer.
Notre système de fichiers social sera structuré plus rigoureusement qu’un système de fichiers traditionnel. Par exemple, il ne sera constitué que de fichiers JSON. Pour rendre cela plus explicite, nous allons commencer à introduire notre nouvelle terminologie. Nous appellerons ce type de fichier un enregistrement (record).
Les clés d’enregistrement
Il nous faut maintenant donner un nom à notre enregistrement. Il n’existe pas de nom naturel pour les publications. Pourrait-on utiliser des numéros séquentiels ? Nos noms n’ont besoin d’être uniques qu’au sein d’un dossier :
posts/
├── 1.json
├── 2.json
└── 3.json
Un inconvénient est qu’il faudrait garder trace du dernier numéro, avec un risque de collision lorsqu’on crée de nombreux fichiers depuis plusieurs appareils en même temps.
À la place, utilisons des horodatages avec un peu d’aléa par horloge :
posts/
├── 1221499500000000-c5.json
├── 1221499500000000-k3.json # l'id d'horloge évite les collisions globales
└── 1221499500000001-k3.json # le +1 artificiel évite les collisions locales
C’est mieux, car ces noms peuvent être générés localement et n’entreront presque jamais en collision.
Nous utiliserons ces noms dans des URL, alors encodons-les de façon plus compacte. Nous allons choisir notre encodage avec soin pour que le tri alphabétique suive l’ordre chronologique :
posts/
├── 34qye3wows2c5.json
├── 34qye3wows2k3.json
└── 34qye3wows3k3.json
Maintenant, ls -r nous donne une chronologie antéchronologique des publications ! C’est chouette. Et puisque nous nous en tenons au JSON comme lingua franca, nous n’avons pas besoin d’extensions de fichiers.
posts/
├── 34qye3wows2c5
├── 34qye3wows2k3
└── 34qye3wows3k3
Tous les enregistrements ne s’accumulent pas au fil du temps. Par exemple, vous pouvez écrire de nombreuses publications, mais vous n’avez qu’un seul exemplaire de vos informations de profil — votre avatar et votre nom d’affichage. Pour les enregistrements « singletons », il est logique d’utiliser un nom prédéfini, comme me ou self :
posts/
├── 34qye3wows2c5
├── 34qye3wows2k3
└── 34qye3wows3k3
profiles/
└── self
Au passage, enregistrons ce profil dans profiles/self :
{
avatar: 'https://example.com/dril.jpg',
displayName: 'wint'
}
Remarquez que, pris ensemble, posts/34qye3wows2c5 et profiles/self nous permettent de reconstituer une plus grande partie de l’interface de départ, même si certains morceaux manquent encore :
alt
La publication reconstituée à partir des enregistrements de post et de profil ; les compteurs manquent encore.
Mais avant de les combler, il faut rendre notre système plus robuste.
Les lexiques
Voici la forme de notre enregistrement de publication :
{
text: 'no',
createdAt: '2008-09-15T17:25:00.000Z'
}
Et voici la forme de notre enregistrement de profil :
{
avatar: 'https://example.com/dril.jpg',
displayName: 'wint'
}
Comme ils sont stockés en tant que fichiers, il est important que le format ne dérive pas.
Écrivons quelques définitions de types :
type Post = {
text: string,
createdAt: string
};
type Profile = {
avatar?: string,
displayName?: string
};
TypeScript semble pratique pour cela, mais il ne suffit pas. Par exemple, nous ne pouvons pas exprimer des contraintes comme « la chaîne text doit contenir au plus 300 graphèmes Unicode », ou « la chaîne createdAt doit être formatée comme une date-heure ».
Il nous faut un moyen plus riche de définir des formats de fichiers sociaux.
Nous pourrions faire le tour des options existantes (RDF ? JSON Schema ?), mais si rien ne convient vraiment, autant concevoir notre propre langage de schéma, explicitement pensé pour les besoins de notre système de fichiers social. Voici à quoi ressemble notre Post :
{
// ...
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["text", "createdAt"],
"properties": {
"text": { "type": "string", "maxGraphemes": 300 },
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
}
}
Nous appellerons cela le lexique (lexicon) Post, parce que c’est comme une langue que notre application veut parler.
Ma première réaction a aussi été « aïe », mais ça m’a aidé de penser que conceptuellement, c’est ceci :
type Post = {
@maxGraphemes(300) text: string,
createdAt: datetime
};
Je rêvais autrefois d’une meilleure syntaxe, mais j’en suis venu, avec quelques hésitations, à apprécier le JSON. Le fait qu’il soit trivial à analyser rend très facile la construction d’outillage autour (nous y reviendrons à la fin). Et bien sûr, nous pouvons créer des bindings qui transforment ces définitions en types et en code de validation pour n’importe quel langage de programmation.
Les collections
Notre système de fichiers social ressemble pour l’instant à ceci :
posts/
├── 34qye3wows2c5
├── 34qye3wows2k3
└── 34qye3wows3k3
profiles/
└── self
Le dossier posts/ contient des enregistrements qui satisfont le lexique Post, et le dossier profiles/ contient des enregistrements (un seul, en fait) qui satisfont le lexique Profile.
On peut faire fonctionner cela pour une seule application. Mais voici un problème. Que se passe-t-il si une autre application a sa propre notion de « publications » et de « profils » ?
Rappelez-vous, chaque utilisateur a un « dossier fourre-tout» avec les données de toutes les applications :
alt
Le dossier de l'utilisateur contient les données de toutes ses applications.
Différentes applications seront probablement en désaccord sur ce qu’est le format d’une « publication » ! Par exemple, une publication de microblog peut avoir une limite de 300 caractères, alors qu’un vrai article de blog n’en aura peut-être pas.
Peut-on amener les applications à s’entendre ?
On pourrait essayer d’enfermer tous les développeurs d’applications dans la même pièce jusqu’à ce qu’ils s’accordent sur un lexique parfait pour une publication. Ce serait un usage intéressant du temps de chacun.
Pour certains cas d’usage, comme la syndication entre sites, un lexique quasi standard, gouverné conjointement, a du sens. Pour d’autres cas, on veut vraiment que l’application soit aux commandes. C’est en fait une bonne chose que différents produits puissent être en désaccord sur ce qu’est une publication ! Produits différents, ambiances différentes. Nous voulons soutenir cela, pas le combattre.
En réalité, nous nous posions la mauvaise question. Nous n’avons pas besoin que tous les développeurs s’accordent sur ce qu’est un post ; nous avons juste besoin de laisser chacun « définir » son propre post.
Nous pourrions essayer de préfixer les types d’enregistrements par le nom de l’application :
twitter/
├── posts/
│ ├── 34qye3wows2c5
│ ├── 34qye3wows2k3
│ └── 34qye3wows3k3
└── profiles/
└── self
tumblr/
├── posts/
│ ├── 34qye3wows4c5
│ └── 34qye3wows5k3
└── profiles/
└── self
Mais les noms d’applications peuvent aussi entrer en collision. Heureusement, nous avons déjà un moyen d’éviter les conflits : les noms de domaine. Un nom de domaine est unique et implique une propriété.
Pourquoi ne pas s’inspirer un peu de Java ?
com.twitter.post/
├── 34qye3wows2c5
├── 34qye3wows2k3
└── 34qye3wows3k3
com.twitter.profile/
└── self
com.tumblr.post/
├── 34qye3wows4c5
└── 34qye3wows5k3
com.tumblr.profile/
└── self
Cela nous donne les collections.
Une collection est un dossier contenant des enregistrements d’un certain type de lexique. Le lexique de Twitter pour les publications peut différer de celui de Tumblr, et c’est très bien — ils sont dans des collections séparées. La collection est toujours nommée <qui.conçoit.le.lexique>.<nom>.
Par exemple, vous pourriez imaginer ces noms de collections :
com.instagram.followpour les follows Instagramfm.last.scrobblepour les scrobbles Last.fmio.letterboxd.reviewpour les critiques Letterboxd
Vous pourriez aussi imaginer ces noms de collections un peu plus farfelus :
com.ycombinator.news.vote(les sous-domaines sont acceptés)co.wint.shitpost(les domaines personnels fonctionnent aussi)org.schema.recipe(un standard partagé, un jour ?)fm.last.scrobble_v2(changement cassant = nouveau lexique, comme pour les formats de fichiers)
C’est comme avoir un dossier dédié pour chaque extension de fichier.
Pour voir de vrais noms de lexiques, allez voir UFOs et Lexicon Garden.
Il n’y a pas de police des lexiques
Si vous êtes développeur d’applications, vous vous dites peut-être :
Qui garantit que les enregistrements respectent leurs lexiques ? Si n’importe quelle application peut (avec le consentement explicite de l’utilisateur) écrire dans la collection de n’importe quelle autre application, comment ne pas se retrouver avec des tonnes de données invalides ? Et si une autre application met n’importe quoi dans « ma » collection ?
La réponse est que les enregistrements peuvent effectivement être du n’importe quoi, et que ça fonctionne quand même.
Le parallèle avec les extensions de fichiers aide. Rien n’empêche quelqu’un de renommer cat.jpg en cat.pdf. Un lecteur PDF refusera simplement de l’ouvrir.
La validation des lexiques fonctionne de la même manière. Le com.tumblr dans com.tumblr.post indique qui a conçu le lexique, mais les enregistrements eux-mêmes peuvent avoir été créés par n’importe quelle application. C’est pourquoi les applications traitent toujours les enregistrements comme des entrées non fiables, à l’image des corps de requêtes POST. Quand vous générez des définitions de types depuis un lexique, vous obtenez aussi une fonction qui fait la validation pour vous. Si un enregistrement passe la vérification, parfait — vous obtenez un objet typé. Sinon, tant pis, ignorez cet enregistrement.
Donc : valider à la lecture, exactement comme les fichiers.
Il faut un peu de soin pour faire évoluer les lexiques. À partir du moment où un lexique est utilisé dans la nature, vous ne devez jamais changer quels enregistrements il considère valides. Par exemple, vous pouvez ajouter de nouveaux champs optionnels, mais vous ne pouvez pas changer si un champ est optionnel. Cela garantit que le nouveau code peut toujours lire les anciens enregistrements et que l’ancien code pourra lire les nouveaux. Il existe un linter pour vérifier cela. (Pour les changements cassants, créez un nouveau lexique, comme vous le feriez avec un format de fichier.)
Bien que ce ne soit pas obligatoire, vous pouvez publier vos lexiques pour la documentation et la distribution. C’est comme publier des définitions de types. Il n’y a pas de registre séparé pour cela ; vous les placez simplement dans la collection com.atproto.lexicon.schema d’un compte, puis vous prouvez que le domaine du lexique vous appartient. Par exemple, si je voulais publier un lexique io.overreacted.comment, je pourrais le placer ici :
app.bsky.feed.post/
├── 3mclfkzg4uc2k
├── 3mcleqsh7cc2k
└── 3mclejvlp5c2k
com.atproto.lexicon.schema
└── io.overreacted.comment
Ensuite, je devrais faire une petite configuration DNS pour prouver que overreacted.io est à moi. Cela ferait apparaître mon lexique dans pdsls, Lexicon Garden et d’autres outils.
Les liens
Revenons à notre publication.
alt
La publication de dril.
Nous avons déjà décidé que le profil doit vivre dans la collection com.twitter.profile, et la publication elle-même dans la collection com.twitter.post :
alt
Le profil et la publication placés dans leurs collections respectives.
Mais qu’en est-il des likes ?
D’ailleurs, qu’est-ce qu’un like ?
Un like est quelque chose que l’utilisateur crée, il est donc logique que chaque like soit un enregistrement. Un enregistrement de like ne transporte aucune donnée, si ce n’est quelle publication est likée :
type Post = {
text: string,
createdAt: string
};
// ...
type Like = {
subject: Post
};
Un Like est donc un enregistrement qui fait référence à son Post.
Mais comment exprimer cela en JSON ?
{
subject: "???"
}
Comment faire référence, depuis un fichier JSON, à un autre fichier JSON ?
Nous pourrions essayer de référencer l’enregistrement Post par son chemin dans notre « dossier fourre-tout » :
{
subject: "com.twitter.post/34qye3wows2c5"
}
Mais cela ne l’identifie de façon unique qu’au sein du « dossier fourre-tout » d’un seul utilisateur. Rappelez-vous que chaque utilisateur a ses propres dossiers, complètement isolés, avec toutes ses affaires :
alt
Chaque utilisateur a son propre dossier isolé.
Il nous faut trouver un moyen de faire référence aux utilisateurs eux-mêmes :
{
subject: "???????????????????????????????/com.twitter.post/34qye3wows2c5"
}
Comment faire ?
L’identité
C’est un problème difficile.
Jusqu’ici, nous avons construit une sorte de système de fichiers pour applications sociales. Mais la partie « sociale » exige des liens entre utilisateurs. Il nous faut un moyen fiable de faire référence à un utilisateur. Le défi est que nous construisons un système de fichiers distribué, où les « dossiers fourre-tout » de différents utilisateurs peuvent être hébergés sur des ordinateurs différents, par des entreprises, des communautés ou des organisations différentes, ou être auto-hébergés.
De plus, nous ne voulons pas que quiconque soit prisonnier de son hébergement actuel. L’utilisateur doit pouvoir changer d’hébergeur de son « dossier fourre-tout » à tout moment, sans casser les liens existants vers ses fichiers. La tension principale est que nous voulons préserver la capacité des utilisateurs à changer d’hébergement, sans que cela ne casse aucun lien. En outre, nous voulons être sûrs que, bien que le système soit distribué, chaque donnée n’a pas été altérée.
Pour l’instant, oubliez tout ce qui concerne les enregistrements, les collections et les dossiers. Nous allons nous concentrer sur un seul problème : les liens. Concrètement, il nous faut concevoir des liens permanents qui autorisent un hébergement interchangeable. Si nous n’y arrivons pas, tout le reste s’effondre.
Tentative 1 : l’hébergeur comme identité
Supposons que le contenu de dril soit hébergé par some-cool-free-hosting.com. La façon la plus intuitive de créer un lien vers son contenu est d’utiliser un lien HTTP normal vers son hébergement :
{
subject: "https://some-cool-free-hosting.com/com.twitter.post/34qye3wows2c5"
}
Cela fonctionne, mais si dril veut changer d’hébergeur, il cassera tous les liens. Ce n’est donc pas une solution — c’est exactement le problème que nous essayons de résoudre. Nous voulons que les liens pointent vers « là où seront les affaires de dril », pas « là où sont les affaires de dril en ce moment ».
Il nous faut une forme d’indirection.
Tentative 2 : le pseudo comme identité
Nous pourrions donner à dril un identifiant persistant comme @dril et l’utiliser dans les liens :
{
subject: "@dril/com.twitter.post/34qye3wows2c5"
}
Nous pourrions ensuite faire tourner un registre qui stocke un document JSON de ce genre pour chaque utilisateur :
{
// ...
"service": [{
// ...
"serviceEndpoint": "https://some-cool-free-hosting.com"
}]
}
L’idée est que ce document nous indique comment trouver l’hébergement réel de @dril.
Il nous faudrait aussi fournir un moyen pour dril de mettre à jour ce document.
Une version de cela pourrait fonctionner, mais il semble dommage d’inventer notre propre espace de noms mondial alors qu’il en existe déjà un sur internet. Essayons une variante de cette idée.
Tentative 3 : le domaine comme identité
Il existe déjà un espace de noms mondial auquel n’importe qui peut participer : le DNS. Si dril possède wint.co, nous pourrions peut-être le laisser utiliser ce domaine comme identité persistante :
{
subject: "@wint.co/com.twitter.post/34qye3wows2c5"
}
Cela ne veut pas dire que le contenu réel est hébergé sur wint.co ; cela veut juste dire que wint.co héberge le document JSON qui indique où se trouve actuellement le contenu. Par exemple, la convention pourrait être de servir ce document sur /document.json. Là encore, le document nous pointe vers l’hébergement. Évidemment, dril peut mettre à jour son document.
C’est assez élégant, mais en pratique, le compromis n’est pas terrible. Perdre un domaine est assez courant, et la plupart des gens ne voudraient pas que cela détruise leur compte.
Tentative 4 : le hash comme identité
Les deux dernières tentatives partagent un défaut : elles vous lient au même pseudo pour toujours.
Que ce soit un pseudo comme @dril ou un pseudo-domaine comme @wint.co, nous voulons que les gens puissent changer de pseudo à tout moment sans casser de liens.
Ça vous rappelle quelque chose ? Nous voulons la même chose pour l’hébergement. Gardons donc l’idée des « pseudos-domaines », mais stockons le pseudo actuel dans le JSON, à côté de l’hébergement actuel :
{
// ...
"alsoKnownAs": ["@wint.co"],
// ...
"service": [{
// ...
"serviceEndpoint": "https://some-cool-free-hosting.com"
}]
}
Ce JSON devient une sorte de carte de visite de votre identité. « Appelez-moi @wint.co, mes affaires sont sur https://some-cool-free-hosting.com. »
Il nous faut maintenant un endroit où héberger ce document, et un moyen pour vous de le modifier.
Revenons au « registre centralisé » de l’approche n° 2. Un de ses problèmes était d’utiliser les pseudos comme identifiants permanents. Aussi, centralisé, c’est mal — mais pourquoi est-ce mal ? C’est mal pour bien des raisons, mais généralement c’est le risque d’abus de pouvoir ou le point de défaillance unique. Peut-être pouvons-nous, sinon supprimer, du moins réduire certains de ces risques. Par exemple, ce serait bien de rendre les réponses du registre auto-vérifiables.
Voyons si les mathématiques peuvent nous aider.
Quand vous créez un compte, nous générons une clé privée et une clé publique. Nous créons ensuite un morceau de JSON avec votre pseudo initial, votre hébergement et votre clé publique. Nous signons cette opération « créer le compte » avec votre clé privée. Puis nous hachons l’opération signée. Cela nous donne une chaîne de charabia comme 6wpkkitfdkgthatfvspcfmjo.
Le registre stockera votre opération sous ce hash. Ce hash devient l’identifiant permanent de votre compte. Nous l’utiliserons dans les liens pour faire référence à vous :
{
subject: "6wpkkitfdkgthatfvspcfmjo/com.twitter.post/34qye3wows2c5"
}
Pour résoudre un tel lien, nous demandons au registre le document appartenant à 6wpkkitfdkgthatfvspcfmjo. Il renvoie votre hébergement, votre pseudo et votre clé publique actuels. Ensuite, nous récupérons com.twitter.post/34qye3wows2c5 depuis votre hébergement.
D’accord, mais comment mettre à jour votre pseudo ou votre hébergement dans ce registre ?
Pour une mise à jour, vous créez une nouvelle opération avec un champ prev contenant le hash de votre opération précédente. Vous la signez et l’envoyez au registre. Le registre valide la signature, ajoute l’opération à votre journal et met à jour le document.
Pour prouver qu’il ne falsifie pas les documents servis, le registre expose un point d’accès qui liste les opérations passées d’un identifiant. Pour vérifier une opération, vous vérifiez que sa signature est valide et que son champ prev correspond au hash de l’opération précédente. Cela vous permet de vérifier toute la chaîne de mises à jour jusqu’à la première opération. Le hash de la première opération est l’identifiant, donc vous pouvez le vérifier aussi. À ce stade, vous savez que chaque changement a été signé avec la clé de l’utilisateur.
(Plus de détails sur le modèle de confiance dans la spécification PLC.)
Avec cette approche, le registre reste centralisé, mais il ne peut pas falsifier les documents de quiconque sans risquer d’être détecté. Pour réduire encore le besoin de faire confiance au registre, tout son journal d’opérations est auditable. Le registre ne détiendrait aucune donnée privée et serait entièrement open source. Idéalement, il serait à terme transféré à une entité juridique indépendante, pour devenir à long terme quelque chose comme l’ICANN.
Comme la plupart des gens ne veulent pas gérer de clés, on suppose que l’hébergeur détient les clés pour le compte de l’utilisateur. Le registre inclut un moyen d’enregistrer une clé de rotation prioritaire, ce qui est utile au cas où l’hébergeur lui-même deviendrait malveillant. (J’aimerais un moyen de configurer cela avec une bonne UX ; la plupart des gens ne l’ont pas activé.)
Enfin, puisque le pseudo est désormais déterminé par le document détenu dans le registre, il nous faut ajouter un moyen pour un domaine de signaler qu’il accepte d’être le pseudo d’un identifiant. Cela peut se faire via DNS, HTTPS, ou un mélange des deux.
Ouf ! Ce n’est pas parfait, mais ça nous mène étonnamment loin.
Tentative 5 : le DID comme identité
Du point de vue de l’utilisateur final, la tentative n° 4 (le hash comme identité) est la plus conviviale. Elle n’utilise pas les domaines pour l’identité (seulement comme pseudos), donc perdre un domaine n’est pas grave.
Cependant, certains trouvent intenable de dépendre d’un registre tiers, aussi transparent soit-il. Il serait donc bien de prendre aussi en charge l’approche n° 3 (le domaine comme identité).
Nous utiliserons un standard d’identifiant flexible appelé DID (identifiant décentralisé), qui est essentiellement une façon de regrouper sous un même espace de noms plusieurs méthodes d’identification sans lien entre elles :
did:web:wint.coet compagnie — basé sur le domaine (tentative n° 3)did:plc:6wpkkitfdkgthatfvspcfmjoet compagnie — basé sur le registre (tentative n° 4)- Cela nous laisse aussi la possibilité d’ajouter d’autres méthodes à l’avenir, comme
did:bla:...
Notre enregistrement Like ressemble donc à ceci :
{
subject: "at://did:plc:6wpkkitfdkgthatfvspcfmjo/com.twitter.post/34qye3wows2c5"
}
Ce sera sa forme définitive. Nous écrivons at:// ici pour nous rappeler que ce n’est pas un lien HTTP, et qu’il faut suivre la procédure de résolution (obtenir le document, obtenir l’hébergement, puis obtenir l’enregistrement) pour réellement obtenir le résultat.
Vous pouvez maintenant oublier tout ce que nous venons de voir et retenir quatre choses :
- Un DID est un identifiant sous forme de chaîne qui représente un compte.
- Le DID d’un compte ne change jamais.
- Chaque DID pointe vers un document contenant l’hébergement, le pseudo et la clé publique actuels.
- Un pseudo doit être vérifié dans l’autre sens (le domaine doit être d’accord).
Le modèle mental est qu’il existe une fonction comme celle-ci :
async function resolveDID(did) {
// ...
return { hosting, handle, publicKey };
}
Vous lui donnez un DID, et elle vous renvoie où trouver les affaires du compte, son pseudo actuel vérifié dans les deux sens, et sa clé publique. Vous voudrez y mettre un 'use cache'.
Terminons maintenant notre système de fichiers social.
L’URI at://
Avec un DID, nous pouvons enfin construire un chemin qui identifie chaque enregistrement particulier :
at://did:plc:6wpkkitfdkgthatfvspcfmjo/com.twitter.post/34qye3wows2c5
└──────────── qui ────────────┘ └─ collection ─┘ └── enreg. ─┘
Une URI at:// est un lien vers un enregistrement qui survit aux changements d’hébergement et de pseudo.
Le modèle mental ici est que vous pouvez toujours la résoudre en un enregistrement :
async function fetchRecord(atURI) {
const { did, collection, rkey } = parseATUri(atURI);
const { hosting } = await resolveDID(did);
const params = `repo=${did}&collection=${collection}&rkey=${rkey}`;
return fetch(`${hosting}/xrpc/com.atproto.repo.getRecord?${params}`);
}
Si l’hébergement est en panne, elle ne se résoudra temporairement pas, mais si l’utilisateur le remet en ligne n’importe où et y fait pointer son DID, elle recommencera à se résoudre. L’utilisateur peut aussi supprimer l’enregistrement, ce qui le retire de son « dossier fourre-tout ».
Une autre façon de voir l’URI at:// est comme l’identifiant unique de chaque enregistrement de notre système de fichiers : elle peut donc servir de clé dans une base de données ou un cache.
Des hyperliens pour le JSON
Avec les liens, nous pouvons enfin représenter les relations entre enregistrements.
Regardons à nouveau la publication de dril :
alt
La publication de dril.
D’où viennent les 125 000 likes ?
Ce sont simplement 125 000 enregistrements com.twitter.like dans les « dossiers fourre-tout » de différentes personnes, qui pointent chacun vers l’enregistrement com.twitter.post de dril :
alt
Des enregistrements de like dans les dossiers de nombreux utilisateurs pointent vers la publication de dril.
D’où viennent les 56 000 repartages ? De la même façon, cela signifie qu’il y a 56 000 enregistrements com.twitter.repost à travers notre système de fichiers social pointant vers cette publication :
alt
Des enregistrements de repost dans les dossiers de nombreux utilisateurs pointent vers la publication de dril.
Et les réponses ?
Une réponse est simplement une publication qui a une publication parente. En TypeScript, on écrirait ceci :
type Post = {
text: string,
createdAt: string,
parent?: Post
};
En lexique, on écrirait ceci :
// ...
"text": { "type": "string", "maxGraphemes": 300 },
"createdAt": { "type": "string", "format": "datetime" },
"parent": { "type": "string", "format": "at-uri" }
// ...
Cela dit : le champ parent est un lien at:// vers un autre enregistrement.
Chaque réponse à la publication de dril aura la publication de dril comme parent :
{
"text": "yes",
"createdAt": "2008-09-15T18:02:00.000Z",
"parent": "at://did:plc:6wpkkitfdkgthatfvspcfmjo/com.twitter.post/34qye3wows2c5"
}
Donc, pour obtenir le nombre de réponses, il suffit de compter toutes ces publications :
alt
Les réponses sont des publications dont le champ parent pointe vers la publication de dril.
Nous avons maintenant expliqué comment chaque morceau de l’interface d’origine peut être dérivé de fichiers :
- Le nom d’affichage et l’avatar viennent du
com.twitter.profile/selfde dril. - Le texte et la date du tweet viennent du
com.twitter.post/34qye3wows2c5de dril. - Le nombre de likes est agrégé depuis les
com.twitter.likede tout le monde. - Le nombre de repartages est agrégé depuis les
com.twitter.repostde tout le monde. - Le nombre de réponses est agrégé depuis les
com.twitter.postde tout le monde.
La dernière touche, c’est le pseudo. Malheureusement, @dril ne peut plus fonctionner comme pseudo puisque nous avons choisi d’utiliser des domaines comme pseudos. En guise de consolation, dril pourra utiliser @wint.co dans toutes les futures applications sociales, s’il le souhaite.
alt
La publication de dril entièrement reconstituée, avec le pseudo @wint.co.
Un répertoire
Il est temps de donner un vrai nom à notre « dossier fourre-tout ». Nous l’appellerons un répertoire (repository). Un répertoire est identifié par un DID. Il contient des collections, qui contiennent des enregistrements :
did:plc:fpruhuo22xkm5o7ttr2ktxdo/
├── com.twitter.like/
│ └── ...
├── com.twitter.post/
│ └── ...
├── fm.last.scrobble/
│ ├── 3ld5nsp8q2w9j
│ ├── 3ld5ntq9r3x0k
│ └── ...
└── com.ycombinator.news.vote/
├── 3ld6our0s4y1l
└── ...
Chaque répertoire est une petite parcelle du système de fichiers social d’un utilisateur. Un répertoire peut être hébergé n’importe où — un fournisseur gratuit, un service payant, ou votre propre serveur. Vous pouvez déplacer votre répertoire autant de fois que vous voulez sans casser de liens.
Un des défis pour construire un système de fichiers social en pratique est que les applications doivent pouvoir calculer des données dérivées (par exemple les compteurs de likes) sans surcoût. Bien sûr, il serait complètement impraticable de chercher chaque enregistrement com.twitter.like dans chaque répertoire faisant référence à une publication donnée au moment de servir l’interface de cette publication.
C’est pourquoi, en plus de traiter un répertoire comme un système de fichiers — vous pouvez lister et lire des choses — vous pouvez le traiter comme un flux, en vous y abonnant par WebSocket. Cela permet à quiconque de construire un cache local, propre à son application, contenant uniquement les données dérivées dont elle a besoin. Sur le flux, vous recevez chaque commit comme un événement, accompagné du delta de l’arbre.
Par exemple, un backend Hacker News pourrait écouter les créations/mises à jour/suppressions d’enregistrements com.ycombinator.news.* dans chaque répertoire connu et sauvegarder ces enregistrements localement pour des requêtes rapides. Il pourrait aussi suivre des données dérivées comme vote_count.
S’abonner à chaque répertoire connu depuis chaque application n’est pas pratique. Il est plus agréable d’utiliser des services dédiés appelés relais, qui retransmettent tous les événements. Mais cela soulève la question de la confiance : comment savoir si le relais de quelqu’un d’autre ne ment pas ?
Pour résoudre cela, rendons les données du répertoire auto-certifiantes. Nous pouvons structurer le répertoire comme un arbre de hachage. Chaque écriture est un commit signé contenant le nouveau hash racine. Cela permet de vérifier les enregistrements au fur et à mesure de leur arrivée, contre les clés publiques de leurs auteurs d’origine. Tant que vous êtes abonné à un relais qui retransmet ses preuves, vous pouvez vérifier chaque preuve pour savoir que les enregistrements sont authentiques.
Vérifier l’authenticité des enregistrements ne nécessite pas de stocker leur contenu, ce qui veut dire que les relais peuvent agir comme de simples retransmetteurs et sont abordables à faire tourner.
Là-haut dans l’Atmosphère
Ouvrez pdsls.
Si vous voulez explorer l’Atmosphère (at://-mosphère, vous l’avez ?), pdsls est le meilleur point de départ. À partir d’un DID ou d’un pseudo, il affiche la liste des collections et de leurs enregistrements. C’est vraiment comme un gestionnaire de fichiers à l’ancienne, mais pour les choses sociales.
Allez sur at://danabra.mov si vous voulez un point de départ au hasard. Remarquez que vous comprenez 80 % de ce qui s’y passe — Collections, Identité, Enregistrements, etc.
N’hésitez pas à vous balader. Les enregistrements pointent vers d’autres enregistrements. Il n’y a aucune agrégation propre à une application, donc ça peut sembler un peu « hors sol » (il n’y a pas de vue de fil de discussion comme dans Bluesky, par exemple), mais il y a des fonctionnalités de navigation intéressantes, comme les backlinks.
Regardez-moi me promener un peu dans l’Atmosphère :
alt
Une promenade dans l’Atmosphère via pdsls, de collection en collection.
(Ouais, c’était quoi, ce lexique ?! Je ne m’attendais pas à tomber là-dessus pendant l’enregistrement.)
Ma démo préférée est celle-ci.
Regardez-moi créer une publication Bluesky en créant un enregistrement via pdsls :
alt
Une publication Bluesky apparaît dans l’application après la création d’un enregistrement dans pdsls.
L’application « réagit » au changement. Les fichiers sont la source de vérité !
Pour rendre la métaphore du système de fichiers plus tangible, je peux monter mon répertoire (ou celui de n’importe qui) comme un disque FUSE avec pdsfs. Chaque changement y apparaît alors aussi :
alt
Un répertoire atproto monté comme un disque FUSE ; les changements apparaissent dans le système de fichiers local.
À quoi servent les fichiers ? Eh bien, les agents adorent les fichiers. Ici, je demande à Claude de trouver ce que mes amis ont récemment créé à travers différentes applications de l’Atmosphère :
Pas d’appels d’API, pas de serveurs MCP. Ce n’est peut-être pas la façon la plus efficace d’analyser des données sociales, mais en plissant les yeux, vous pourriez entrevoir un aperçu d’un futur post-applications. Les applications organisent les données en expériences, mais le web que nous créons flotte au-dessus de chaque application.
Il n’y a rien de spécifique à Bluesky ici.
Les données coulent toujours vers le bas dans l’Atmosphère — de nos répertoires vers les applications.
Il y a un mois, j’ai créé une petite application appelée Sidetrail (elle est open source) pour m’entraîner au développement full-stack. Elle permet de créer des parcours guidés étape par étape et de les « parcourir ». Ici, vous me voyez supprimer un enregistrement app.sidetrail.walk dans pdsls, et le parcours correspondant disparaît de mon onglet « walking » de Sidetrail :
alt
La suppression d’un enregistrement dans pdsls fait disparaître le parcours correspondant dans Sidetrail.
Je sais exactement pourquoi ça marche, ce n’est pas censé me surprendre, et pourtant ça me surprend ! Mon répertoire est réellement la source de vérité. Mes données vivent dans l’Atmosphère, et les applications y « réagissent ».
C’est bizarre !!!
Voici le code de mon ingesteur :
export async function handleEvent(db: IngesterDb, evt: JetstreamEvent): Promise<void> {
if (evt.kind === "account") {
await handleAccountEvent(db, evt.account);
return;
}
if (evt.kind === "identity") return;
if (evt.kind !== "commit") return;
const { commit } = evt;
const { collection, rkey } = commit;
if (!COLLECTIONS.includes(collection)) return;
const [accountStatus] = await db
.select({ active: accounts.active })
.from(accounts)
.where(eq(accounts.did, evt.did))
.limit(1);
if (accountStatus && !accountStatus.active) {
return;
}
const uri = `at://${evt.did}/${collection}/${rkey}`;
if (commit.operation === "delete") {
switch (collection) {
case "app.sidetrail.trail":
await deleteTrail(db, uri);
break;
case "app.sidetrail.walk":
await deleteWalk(db, uri);
break;
case "app.sidetrail.completion":
await deleteCompletion(db, uri);
break;
}
return;
}
const record = commit.record as Record<string, unknown>;
await ensureAccount(db, evt.did);
switch (collection) {
case "app.sidetrail.trail":
await upsertTrail(
db,
uri,
commit.cid,
evt.did,
rkey,
record,
(record.createdAt as string) || new Date().toISOString(),
);
break;
case "app.sidetrail.walk": {
const trailRef = record.trail as { uri: string } | undefined;
const trailUri = trailRef?.uri || "";
await upsertWalk(
db,
uri,
commit.cid,
evt.did,
rkey,
trailUri,
record,
(record.createdAt as string) || new Date().toISOString(),
);
break;
}
case "app.sidetrail.completion": {
const trailRef = record.trail as { uri: string } | undefined;
const trailUri = trailRef?.uri || "";
await upsertCompletion(
db,
uri,
commit.cid,
evt.did,
rkey,
trailUri,
record,
(record.createdAt as string) || new Date().toISOString(),
);
break;
}
}
}
Cela synchronise les changements des répertoires de tout le monde vers ma base de données, de sorte que j’ai un instantané facile à interroger. Je suis sûr que je pourrais l’écrire plus clairement, mais conceptuellement, c’est comme si je re-rendais ma base de données. C’est comme si j’avais appelé un setState « au-dessus » d’internet, et maintenant les nouvelles props coulent des fichiers vers les applications, et ma base de données y réagit.
Je pourrais supprimer ces tables en production, puis utiliser Tap pour repeupler ma base de données à partir de zéro. Je ne fais que mettre en cache une tranche des données mondiales. Et tous ceux qui construisent des applications AT ont aussi besoin de mettre en cache des tranches. Peut-être des tranches différentes, mais elles se chevauchent. Alors mutualiser les ressources devient plus utile. Une plus grande partie de notre outillage peut être partagée aussi.
Voici un autre exemple que j’aime beaucoup.
C’est une démo de relais teal.fm faite par @chadtmiller.com. Elle montre la liste des morceaux récemment écoutés par tout le monde, ainsi que quelques statistiques d’écoute globales :
alt
La démo de relais teal.fm affichant les morceaux récemment écoutés.
Vous pouvez voir qu’il est écrit « 678 850 scrobbles » en haut de l’écran. Vous pourriez penser que les gens envoient leurs écoutes à l’API de teal.fm depuis un moment.
Eh bien, pas vraiment.
L’API de teal.fm n’existe pas, en fait. Ce n’est pas une chose qui existe. Mieux : le produit teal.fm n’existe pas non plus. Enfin, je crois qu’il est en développement (c’est un projet de loisir !), mais au moment où j’écris, https://teal.fm/ n’est qu’une page d’accueil.
Mais ça n’a pas d’importance !
Tout ce qu’il faut pour commencer à scrobbler, c’est de mettre des enregistrements du lexique fm.teal.alpha.feed.play dans votre répertoire.
(L’article original affiche ici un composant interactif montrant les dernières écoutes en direct — allez le voir sur overreacted.io.)
Le lexique n’est pas (encore ?) publié comme enregistrement, mais il est facile à trouver sur GitHub. N’importe qui peut donc construire un scrobbler qui écrit ces enregistrements. J’utilise l’un de ces scrobblers.
Voici mon scrobble qui apparaît :
alt
Un scrobble apparaît dans la démo teal.fm.
(C’est un peu lent, mais je pense que le délai vient du côté de l’intégration Spotify/scrobbler.)
Pour être clair, la personne qui a fait cette démo ne travaille pas sur teal.fm non plus. Ce n’est pas une démo « officielle » ni rien de la sorte, et elle n’utilise pas non plus la « base de données teal.fm » ou l’« API teal.fm » ou quoi que ce soit du genre. Elle indexe simplement les fm.teal.alpha.feed.play.
La couche de données de la démo utilise le nouveau paquet lex-gql, qui est une autre des expérimentations de @chadtmiller.com. Vous lui donnez des lexiques, et il vous laisse exécuter du GraphQL sur votre instantané repeuplé des parties pertinentes du système de fichiers social.
Si vous avez le JSON du monde entier, pourquoi ne pas faire des jointures entre produits ?
fragment TrackItem_play on FmTealAlphaFeedPlay {
trackName
playedTime
artists {
artistName
}
releaseName
releaseMbId
actorHandle
musicServiceBaseDomain
appBskyActorProfileByDid {
displayName
avatar {
url(preset: "avatar")
}
}
}
alt
Une requête GraphQL qui joint des écoutes teal.fm avec des profils Bluesky.
Chaque application peut mélanger ainsi des informations issues de plusieurs produits. Par exemple, voici une application AT appelée Blento qui vous permet d’afficher vos écoutes teal.fm sur votre page personnelle :

alt
Une page Blento affichant les écoutes teal.fm de l'utilisateur.
(Encore une fois, elle ne parle pas à teal.fm — qui n’existe pas encore ! — elle lit simplement vos fichiers.)
Blento est un remplaçant AT de Bento, qui ferme ses portes. Si Blento lui-même ferme un jour, n’importe quel développeur motivé peut le remettre en ligne avec le contenu existant.
Il y a un dernier exemple que je voulais partager.
Depuis des mois, je me plains du fil Discover par défaut de Bluesky qui, franchement, ne fonctionne pas très bien pour moi. Puis j’ai entendu des gens dire du bien de l’algorithme For You de @spacecowboy17.bsky.social.
Je l’ai essayé, et je l’aime beaucoup !
alt
Le fil For You en cours d’utilisation dans Bluesky.
J’ai fini par y passer complètement. Il me rappelle l’algorithme de Twitter en 2017 — les embardées sont un peu rudes, mais il trouve les choses que je ne voudrais pas rater. Il est aussi beaucoup plus réactif au « Show Less ». Son principe de base semble assez simple.
Comment fonctionne un fil personnalisé comme celui-ci ? Eh bien, un fil Bluesky est juste un point d’accès qui renvoie une liste d’URI at://. C’est ça, le contrat. Vous savez comment ça marche.
[
{ post: 'at://did:example:1234/app.bsky.feed.post/1' },
{ post: 'at://did:example:1234/app.bsky.feed.post/2' },
{ post: 'at://did:example:1234/app.bsky.feed.post/3' }
]
Pourrait-il y avoir des fils d’autres choses que des publications ? Bien sûr.
Chose amusante, @spacecowboy17.bsky.social faisait autrefois tourner For You depuis un ordinateur chez lui. Il publie plein de choses intéressantes, comme des tests A/B sur les changements du fil. Et voici un débogueur For You pour mon compte. Le « Switch perspectives » est chouette.
Il y a quelques semaines, un tweet se moquait de Bluesky, tellement mauvais en algorithmes que les utilisateurs doivent installer un fil tiers pour avoir une bonne expérience.
Je suis d’accord avec @dame.is : cela montre quelque chose d’important. Bluesky est un endroit où cela peut arriver. Pourquoi ? Dans l’Atmosphère, le tiers est de plein droit. Nous construisons tous des projections des mêmes données. C’est une fonctionnalité que quelqu’un puisse faire mieux.
An everything app tries to do everything.
An everything ecosystem lets everything get done.
Chargement…