agent-sync — un projet, de nombreux agents, aucune collision
Deux plans, et une règle entre eux :
Git est le plan d'enregistrement. Le nuage est le plan de coordination. Un fait qui doit survivre est écrit d'abord dans Git et référencé depuis le nuage. Un fait sur qui fait quoi maintenant vit dans le nuage et expire.
Aucun objet du nuage n'est jamais le seul foyer d'un fait durable. Tout ce qui suit existe pour maintenir cela vrai pendant que plusieurs agents écrivent simultanément.
Quatre pièges — lisez ceci avant toute autre chose
1. La base de connaissances ne décide jamais d'un bail. C'est impossible : douze ajouts simultanés à un
même document Outline ont renvoyé douze succès et laissé trois lignes. L'exclusion vient
de quelque chose qui a un véritable compare-and-swap. Le plan porte l'enregistrement et rien d'autre.
Mesures complètes dans references/lease-protocol.md.
2. Sachez quel bail vous possédez, et dites-le. leaseBackend: "local" est une création
de fichier atomique — exclusive entre processus sur un seul système de fichiers, consultative entre machines.
leaseBackend: "git" pousse une référence, et le rejet non-fast-forward du serveur distant est un
compare-and-swap — exclusif entre machines. acquire imprime lequel. Un bail simulé est
pire que pas de bail : l'autre agent cesse de vérifier.
acquire écrit également la revendication dans la feuille de route, et release restaure exactement
ce qui était là — une ligne, une cellule, refusé en cas d'ambiguïté, git diff vide après un
aller-retour. Lisez references/roadmap.md avant de configurer claimTags ou de fermer une
tâche ; fermer est une déclaration sur le travail et reste vôtre.
Travaillez sur une branche ; la branche d'intégration est la base stable de quelqu'un d'autre. acquire
écrit la revendication seulement là ; sur toute autre branche, le détenteur reste dans le plan
de coordination, où status le montre à chaque agent sans que personne n'ait à récupérer votre
branche. Validée sur une branche, une revendication est invisible jusqu'à la fusion et transforme la feuille de route
partagée en un fichier que deux branches modifient toutes deux. Atterrissez le travail avec merge : les conflits calculés par
git merge-tree avant que quoi que ce soit ne soit touché, nommés et refusés s'il y en a, la fusion
enregistrée dans docs/MERGES.md (jours récents en entier, plus anciens compactés à l'écriture), le bail --key
libéré. merges indique au prochain agent ce qui a atterri pendant son absence. Lisez
references/branching.md avant de fusionner.
3. Les hooks existent uniquement dans Claude Code. Ailleurs, rien ne bloque une modification
gardée : exécutez guard vous-même et enregistrez l'exécution comme ungated. Ne décrivez pas un projet comme
protégé quand il ne l'est pas.
4. Analysez libéralement, et n'appelez jamais un journal illisible une course perdue. Le magasin
réécrit ce que vous avez écrit — Outline transforme une puce - en * . Émettez - , acceptez
-/*/+, comptez tout ce qui a la forme d'une entrée mais échoue, et échouez bruyamment au-delà de 2%
d'éléments non analysables. Signaler perdu alors que la vérité est illisible nomme un détenteur
qui n'existe pas. Méfiez-vous d'un pré-filtre silencieux : un continue avant l'expression régulière
cache les mauvaises lignes du compteur conçu pour les exposer.
Intégrer ceci dans N'IMPORTE QUEL projet — toute la chaîne
scaffold → créer l'architecture documentaire, uniquement là où elle est absente
adopt → lire le dépôt, proposer une configuration, n'écrire rien
init → écrire la configuration approuvée + le fichier d'environnement ignoré par git
(l'opérateur colle le jeton — jamais vous)
reconcile --set-baseline → faire de l'historique un backlog compté, une fois
setup → générer l'instantané qui décrit le câblage de ce projet
check → valider l'ensemble ; non nul si ce n'est pas sain
check est ce qui rend la compétence autonome. Elle refuse d'appeler une configuration saine sur une
règle qui ne protège rien (un registre, un glob de garde, un modèle de revendication, une porte ou une source miroir pointant
vers ce qui n'existe pas), sur des informations d'identification manquantes, sur un fichier d'environnement suivi par git —
la seule erreur irrécupérable ici — sur un instantané obsolète ou délié, et sur un registre
sans ligne de base. Elle nomme chacun ; chacun a échoué pour de vrai lors de la propre adoption de cet outil.
Exécutez check après avoir adopté, après avoir modifié la configuration, et dans le CI.
scaffold n'écrase jamais. Il initialise un registre de décisions avec une ligne
d'allocation et un AGENTS.md qui pointe vers l'instantané, et laisse chaque fichier existant intact —
un outil qui réécrit les propres conventions d'un projet lors de l'adoption est pire qu'un outil qui ne fait
rien.
Projet existant : commencez par adopt
Exécutez adopt avant init. Il lit le dépôt et imprime ce qu'il a trouvé —
registres d'ID, fichiers de registre, portes — plus les décisions qu'il refuse de prendre pour vous —
puis propose une configuration.
python3 "$SKILL_DIR/scripts/agent_sync.py" adopt
Confirmez d'abord les registres et les fichiers gardés avec l'opérateur : un registre pointant vers le
mauvais fichier rend chaque vérification ultérieure faussement confiante, et une liste gardée qui manque un
fichier partagé laisse le seul endroit où les collisions se produisent sans protection. Dans un sous-module, il ne déclare
aucun registre : les décisions appartiennent au dépôt parent. Suivez ensuite la chaîne ci-dessus à partir de
init.
Première commande dans un projet : init
N'exécutez jamais rien d'autre sur un projet non initialisé. init est l'endroit où la
question de stockage est posée et répondue, une fois, et écrite.
Demandez ces deux choses à l'opérateur dans le chat — ne devinez pas, ne choisissez pas de valeur par défaut :
-
Où l'état de coordination doit-il résider ?
- un nuage de connaissances (
outline) — l'enregistrement partagé, la conscience et le tableau entre machines. Cela ne décide pas des baux ; rien dedans ne le peut (piège 1) ; - ou des fichiers locaux (
fs) — aucune information d'identification, et aucune visibilité pour un agent sur une autre machine : pas de conscience partagée, pas de signaux inter-dépôts, pas de tableau partagé.
Le bail est décidé séparément par
leaseBackend(piège 2), etgatedsuit ce choix, jamais le plan d'enregistrement. Rapportez la garantie que vous avez réellement, pas la plus forte que le plan d'enregistrement suggère. - un nuage de connaissances (
-
Si nuage : l'URL de l'instance. L'URL est une configuration, pas un secret, donc vous pouvez l'écrire. Le jeton ne l'est pas — vous ne le demandez jamais dans le chat, ne le relisez jamais, et ne le placez jamais vous-même.
Exécutez-le ensuite avec leurs réponses :
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend outline --url https://<leur-instance>
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend fs
init écrit .claude/agent-sync.json (forme, validée), écrit
.env.agent-sync avec les clés et une ligne de jeton vide (identité, mode 600),
ajoute .env.agent-sync et .agent-sync/ à .gitignore, puis imprime exactement
ce que l'opérateur doit faire lui-même — créer le jeton dans sa propre instance et le
coller dans cette seule ligne. Il n'écrase jamais une configuration ou un fichier d'environnement existant
sans --force.
Transmettez ces instructions de clôture textuellement à l'opérateur. Obtenir le jeton dans le fichier est son étape, et la conception dépend du fait qu'il reste le sien.
Ensuite, avant chaque session
python3 "$SKILL_DIR/scripts/agent_sync.py" status
Idempotent. Inspecte, répare, rapporte, nomme EXACTEMENT une prochaine action — et porte
le verdict de check, de sorte que la commande que chaque session exécute et celle qui valide la
configuration ne peuvent pas donner deux réponses concernant un même projet.
Lisez les deux sections de conscience qu'il imprime — elles sont le point, pas la décoration.
- Autres exécutions travaillant sur ce projet en ce moment. Qui détient quoi, cette minute. Ne les prenez pas en charge, et ne "jetez pas un œil" aux fichiers qu'elles couvrent. Un bail que vous ne pouvez pas voir vous rend bloqué ; un bail que vous pouvez voir vous rend coordonné.
- Nouveau depuis votre dernier regard. Les mouvements de dépendances inter-dépôts qui ont atterri pendant que vous étiez absent. Une dépendance qui a bougé peut débloquer ce que vous aviez prévu — ou l'invalider. Cette liste est filigranée par exécution, donc elle reste silencieuse jusqu'à ce que quelque chose change réellement ; quand elle parle, c'est important.
Un agent qui saute ce bloc va redériver le travail que quelqu'un d'autre fait et agir sur un état de dépendance qui a changé il y a une heure.
Ce que status décide d'autre : pas d'identifiants → mode dégradé, signalé, et il continue (un
mode plus petit, pas une erreur) ; task-pipeline absent → il imprime la ligne d'installation et s'arrête.
N'improvisez pas un flux de substitution — sans ces étapes, il n'y a rien à lier.
npx sshlg-skills install
Les commandes
| Commande | Fait |
|---|---|
init | Exécutez d'abord. Demandez où l'état réside, écrivez la configuration + le fichier d'environnement ignoré par git, imprimez l'étape de l'opérateur |
status | Inspecter, réparer, rapporter, nommer une prochaine action |
bootstrap | Créer le conteneur du nuage et imprimer l'ID à coller dans le fichier d'environnement |
acquire <CLÉ> | Prendre le bail sur un ID de tâche. Imprime gagné ou perdu <détenteur> |
renew <CLÉ> | Prolonger le bail. Le hook PostToolUse le fait pour vous |
release <CLÉ> | Rendre le bail. Faites-le toujours, y compris en cas d'échec |
reserve <REG> | Réserver le prochain ID dans un registre (DEC, OQ, DEP, …). Imprime l'ID |
release-id <REG> <ID> | Retourner un ID que vous n'avez finalement pas écrit dans git |
journal <texte> | Ajouter une ligne au journal de cette exécution |
record <texte> | Ajouter ce que vous avez réellement construit — --decision DEC-…, --files a,b |
reconcile | Intention (git) vs réalisé (nuage). --set-baseline une fois par projet |
signal <DEP-ID> <état> | Déplacer une dépendance inter-dépôts : filed/accepted/delivered/closed/refused |
guard <chemin> | Répondre si cette exécution peut écrire ce chemin. Code de sortie 0 = oui, 2 = non |
board | Régénérer le tableau partagé et la page de ce dépôt. --mirror rend également les docs git configurés dans le plan |
whoami | Imprimer l'ID de cette exécution et ses baux détenus |
setup | Écrire l'instantané généré du câblage de ce projet, pour que les agents le lisent |
adopt | Inspecter un projet existant et proposer une configuration — n'écrit rien |
merge | Atterrir cette branche : cible locale fast-forwardée, conflits vérifiés avant que quoi que ce soit ne soit touché, journal de fusion écrit, le bail --key libéré. --summary, --dry-run, --push |
merges | Ce qui a atterri pendant que vous étiez sur votre branche. --all inclut la queue compactée |
check | Valider toute la configuration de bout en bout. Non nul quand ce n'est pas sain |
scaffold [--full] | Créer uniquement ce qui manque, jamais une ligne sur ce qui existe. --full initialise également le registre de questions, l'index, le tableau de dépendances, le modèle de données avec son registre d'entités, et la porte des docs |
finish [--gates] | Le travail est-il terminé — chaque dépôt propre, poussé et pointé, aucun bail laissé détenu. check répond si le projet est câblé correctement ; celui-ci répond si vous avez terminé |
$SKILL_DIR est le propre répertoire de cette compétence : ${CLAUDE_PLUGIN_ROOT}/skills/agent-sync
sous le plugin Claude Code, ~/.agents/skills/agent-sync ailleurs. Résolvez-le une fois
par session et réutilisez-le — ne devinez pas un chemin. Chaque commande lit
.claude/agent-sync.json depuis la racine du projet et n'a besoin d'aucun argument au-delà de ceux
listés.
Une identité par session, et comment elle est décidée
Un bail n'est un bail que si deux agents obtiennent deux identités. L'ordre compte ici et les deux extrémités ont
mordu : dériver l'ID de CLAUDE_SESSION_ID seul a donné une session deux identités — elle
acquiert comme une et a été refusée par sa propre garde comme l'autre — et garder un ID par checkout a
donné deux sessions une identité, ce qui est pire. La seconde est silencieuse : les deux sessions acquièrent, les deux sont
gardées, et release prend un bail que l'appelant n'a jamais eu.
L'ordre est : AGENT_SYNC_RUN_ID · CLAUDE_SESSION_ID · la session qui a démarré ce shell ·
partagé.
Le troisième compte car une simple commande shell n'a pas d'ID de session alors qu'un hook en a. Donc
SessionStart timbre .agent-sync/sessions/<PID du CLI> avec la session qu'il connaît, et une commande
ultérieure se trouve en remontant sa propre arborescence de processus jusqu'à un PID timbré. Pourquoi cela et pas
l'analyse de la ligne de commande : references/earned-rules.md.
Quand aucun des quatre ne peut être établi, l'exécution le dit — "cette identité est partagée avec toute autre session dans ce checkout" — plutôt que de présenter une entrée partagée comme une séparation.
Revendication — la forme qui compte
acquire → faire le travail → release
Ne sautez jamais release, y compris en cas d'échec : un bail abandonné bloque la tâche jusqu'à ce que son
TTL expire, et le prochain agent ne peut pas distinguer "en cours" de "planté il y a une heure".
Le bail n'est pas la revendication. Le bail dit qui le détient maintenant et expire ; la
revendication durable est l'étiquette dans git, écrite par acquire et effacée par release. Un fait,
un foyer — n'inventez pas un troisième endroit qui enregistre la propriété.
Lisez references/lease-protocol.md avant de changer l'acquisition, l'expiration, le vol ou
l'allocation d'ID.
Fichiers gardés
La configuration liste les fichiers de registre que plusieurs agents écrivent. Avant d'en modifier un :
python3 "$SKILL_DIR/scripts/agent_sync.py" guard docs/DECISIONS.md
Le code de sortie 2 concerne cette exécution : elle ne détient aucun bail — pas que quelqu'un d'autre détienne ce fichier. Un bail couvre tous les fichiers gardés ; détenez-en un ou n'en écrivez aucun. Un refus nomme l'autre exécution et sa clé, car "r-x détient un bail" à côté d'un chemin se répète comme "r-x détient ce fichier". N'éditez pas quand même, et ne "corrigez pas juste une ligne" — une décision écrasée ressemble exactement à une décision.
Le hook PreToolUse de Claude Code exécute ceci pour vous. Ailleurs, rien ne le fait.
Réserver un ID
Lire une ligne "Prochain ID libre" n'est pas la réserver — deux agents lisent le même numéro et les deux l'utilisent.
python3 "$SKILL_DIR/scripts/agent_sync.py" reserve DEC # → DEC-0216
L'allocation est positionnelle sur le journal fusionné — chaque fragment, jamais seulement celui de cette exécution —
donc chaque agent calcule la même réponse. Réservé et non écrit dans git ? release-id
le retourne, ou le nombre est un trou que le tableau signale comme une fuite.
Rien dans un journal n'est jamais modifié ou supprimé
Les journaux sont rejoués dans l'ordre, donc une modification réécrit silencieusement une conclusion sur laquelle
d'autres agents ont déjà agi. Corrigez en ajoutant : libérez un bail, release-id un ID inutilisé,
supersédez une entrée de réalisé erronée par une ultérieure. Les pages générées sont la seule
exception, et celle qui a perdu sa marque agent-sync:generated
…
[tronqué pour la traduction]