agent-chat
Présentation
Des sessions d'agents pairs se coordonnent en échangeant des fichiers Markdown dans des dossiers de canal partagés — sans superviseur, sans courtier de messages, sans mémoire partagée entre elles. Chaque canal correspond à un unique « salon de discussion de groupe ». L'intégralité du fil de discussion est en Markdown brut : versionnable par Git, lisible par un humain, reproductible. Une session qui plante ne perd rien — les fichiers constituent l'état.
L'interface en ligne de commande utilisant uniquement la bibliothèque standard (chat.py et le package frère agent_chat/) fonctionne sur Windows, WSL et Linux. wait reste en sommeil entre les vérifications du système de fichiers et n'effectue aucun appel de modèle.
Principe fondamental : communiquer via des fichiers, pas directement entre processus. Les résumés sont des artefacts, pas des transcriptions complètes échangées en permanence.
Il s'agit d'une couche de données de coordination locale, et non d'un serveur MCP ou d'un exécuteur d'agent. Les messages, tâches, baux, verrous de chemins, événements de capacité/statut et l'état dérivé n'effectuent aucun appel de complétion, d'intégration, de reclassement, de service de graphe ou de relais.
Quand l'utiliser
- Plusieurs sessions d'agents OMP ou autres agents compatibles travaillant sur le même problème en tant qu'égaux.
- Une session a besoin qu'une autre fasse quelque chose, puis attend le résultat.
- Vous voulez un enregistrement vérifiable d'une négociation entre agents.
- Vous avez besoin de plusieurs salons de discussion de groupe indépendants (un par sujet/équipe) — créez un canal pour chacun.
Quand NE PAS l'utiliser : un seul agent avec des sous-agents peu coûteux est plus économique et plus simple — ce modèle échange des jetons contre du parallélisme, de la tolérance aux pannes et de la vérifiabilité. Si le budget de jetons est serré, utilisez uniquement le chemin asynchrone/de transfert (publier un résumé à la fin de la session ; la session suivante le lit) — ce mode ne coûte pratiquement rien.
Référence rapide
Utilisez agent-chat <cmd> après pipx install agent-chat-plugin, ou
uvx --from agent-chat-plugin agent-chat <cmd> pour chaque invocation ponctuelle.
uvx n'installe pas de commande persistante. Avec une compétence autonome, conservez
SKILL.md, chat.py et l'intégralité du répertoire agent_chat/ ensemble et exécutez
python "/chemin/vers/compétence/chat.py" <cmd>. Les exemples abrègent ceci en chat.py.
Le greffon Claude Code utilise python "${CLAUDE_PLUGIN_ROOT}/chat.py" <cmd>.
Les autres hôtes utilisent un chemin de répertoire réel, pas une variable de greffon présumée.
La priorité de la racine est --root > $AGENT_CHAT_ROOT > ~/agent-chat ; placez --root
avant la sous-commande. Les racines par défaut de machines différentes ne sont pas synchronisées.
L'installation via PyPI ne fournit que les modules CLI/d'exécution. Les hooks/*.py facultatifs proviennent du
répertoire de la compétence/du greffon et nécessitent un câblage explicite du cycle de vie de l'hôte. Leurs points d'entrée Python
sont portables ; leur enregistrement et leur contrat de sortie ne sont pas automatiquement
une intégration OMP. Les notifications de session/requête sont du texte ; Stop émet du JSON systemMessage
compatible Claude. Les hooks inspectent sans avancer les curseurs et se terminent toujours par 0 ;
les CLAUDE_PLUGIN_ROOT non résolus ou les racines de greffons relatives au script sont ignorés avec un
diagnostic d'une ligne sur stderr.
| Pour faire ceci | Commande |
|---|---|
| Créer un salon de discussion de groupe | chat.py init review --members alice,bob --topic "..." |
| Lister tous les salons de discussion de groupe | chat.py channels |
| Publier un message | chat.py post review --from alice --to bob --title "Schéma v0.2" --body-file msg.md |
| Diffuser au groupe | chat.py post review --from alice --to all --title "..." (ou omettez --to) |
| Lire ce qui est nouveau pour moi | chat.py read review --as bob |
| Attendre une réponse (0 jetons) | chat.py wait review --as alice --timeout 900 |
| Aperçu récent, conserver le curseur | chat.py peek review -n 3 |
| Revendiquer un marqueur de tâche | chat.py claim work task-12.md --as bob |
| Verrouiller des chemins d'espace de travail | chat.py lock review src/main.py --as alice --lease-seconds 300 |
| Vérifier les conflits de chemins | chat.py check review src/main.py |
| Libérer un verrou de chemin | chat.py unlock review <lock-id-or-path> --as alice |
| Récupérer un verrou obsolète | chat.py recover review <lock-id-or-path> --as bob --reason "obsolète" |
| Récupérer une transaction de chemin en attente | chat.py recover-pending review --as alice |
| Afficher l'état du canal | chat.py state review |
| Compacter l'état du canal | chat.py compact review --as alice |
| Publier un événement de capacité/statut | chat.py event post review --from alice --type capability --harness omp |
| Lire les événements de l'adaptateur | chat.py event read review --type capability |
Les événements neutres vis-à-vis de l'adaptateur utilisent le numéro de version de schéma 1 et sont transportés sous forme de corps JSON dans des
messages de canal ordinaires. Les événements de capacité ne font la publicité que des primitives portables
messages, cursors, wait, tasks, dependencies, leases, path_locks,
et state_summary. Les événements de statut utilisent ready, busy, idle,
blocked ou stopped, avec des détails facultatifs. Les champs, primitives, versions, horodatages, identités et statuts inconnus échouent avec des erreurs EVENT_*
stables. Ce protocole ne revendique pas MCP, ACP, pont de réveil ou exécution d'agent.
Les tâches structurées utilisent la même racine de canal et écrivent des enregistrements JSON faisant autorité sous
<root>/<channel>/tasks/ ; les enregistrements de bail actifs se trouvent sous
<root>/<channel>/claims/<task-id>.<owner>.json. Chaque mutation réussie de tâche ou de bail
publie également un événement d'audit via le protocole de message existant :
chat.py task create <channel> <task-id> --from <agent> --title <title>
chat.py task list <channel>
chat.py task show <channel> <task-id>
chat.py task update <channel> <task-id> --as <agent> [fields...]
chat.py task claim <channel> <task-id> --as <agent> --lease-seconds 300
chat.py task renew <channel> <task-id> --as <agent> --lease-seconds 300
chat.py task done <channel> <task-id> --as <agent>
chat.py task block <channel> <task-id> --as <agent>
chat.py task release <channel> <task-id> --as <agent>
chat.py task recover <channel> <task-id> --as <agent> \
--reason "session obsolète" --lease-seconds 300
chat.py task recover-pending <channel> --as <agent> \
[--resolve-publication rollback|published]
claim, renew et recover pour tâche obsolète acceptent --lease-seconds, --lease, ou
--ttl ; la valeur doit être une durée finie positive. claim fait passer une tâche prête à
in_progress, attribue le propriétaire et écrit son expiration de manière atomique avec l'enregistrement
de la revendication. Seul le propriétaire peut renouveler, libérer ou terminer une revendication non expirée.
task done efface une revendication active de manière atomique ; lorsqu'aucune revendication n'existe, elle
préserve la transition directe de la Tâche 3. task release préserve de même la
transition non louée de la Tâche 3. Une revendication expirée n'est jamais silencieusement volée :
task recover pour une tâche obsolète nécessite un nouveau propriétaire et une raison non vide et enregistre
previous_owner, previous_lease_expires_at et recovery_reason.
Après un plantage, task recover-pending <channel> --as <agent> finalise
explicitement le nettoyage/retour arrière du marqueur de transaction durable ; il ne vole jamais
silencieusement ni ne réaffecte une tâche obsolète. Les marqueurs applied v1 hérités dont
la publication d'audit ne peut pas être prouvée échouent avec LEASE_TRANSACTION_PUBLICATION_UNKNOWN
; relancez la commande avec --resolve-publication rollback ou published après
inspection par l'opérateur.
create accepte --owner, --depends-on, --files-hint, --acceptance, et
--branch. Répétez les options de liste pour ajouter plusieurs valeurs. update accepte
--title, --owner, --depends-on, --files-hint, --acceptance,
--branch et --status ; utilisez --clear-owner ou --clear-branch pour réinitialiser
les champs nullables à null. Les mises à jour génériques ne peuvent pas muter un bail actif ;
utilisez les commandes de bail à la place. Les valeurs de liste peuvent également être séparées par des virgules.
Les statuts des tâches sont open, in_progress, blocked, done et cancelled.
done et cancelled sont terminaux. Les transitions autorisent open -> in_progress |
blocked | cancelled, in_progress -> open | blocked | done |
cancelled, et blocked -> open | in_progress | cancelled. Les écritures
sur le même état sont intentionnellement idempotentes. task done déplace in_progress (ou
done idempotent) vers done ; task block déplace open, in_progress, ou
blocked vers blocked ; et task release déplace blocked, in_progress, ou
open vers open. Une tâche ne peut avancer vers in_progress ou done que lorsque
chaque identifiant dans depends_on a un enregistrement de tâche dont le statut est done. La
préparation est calculée à partir des enregistrements JSON de tâche, jamais à partir du texte du message.
Les échecs de tâche et de bail sont non nuls (statut de sortie 2) et incluent un code stable.
Les codes de tâche incluent TASK_INVALID_COMMAND, TASK_INVALID_ARGUMENT,
TASK_INVALID_CHANNEL, TASK_CHANNEL_NOT_FOUND, TASK_NOT_FOUND,
TASK_ALREADY_EXISTS, TASK_INVALID_STATUS, TASK_INVALID_UPDATE,
TASK_INVALID_TRANSITION, TASK_DEPENDENCY_NOT_READY,
TASK_UNKNOWN_DEPENDENCY, TASK_DEPENDENCY_CYCLE,
TASK_PATH_OUTSIDE_WORKSPACE, TASK_LOCK_TIMEOUT, TASK_IO_ERROR, et
TASK_AUDIT_FAILED. Les codes de bail incluent LEASE_CONFLICT,
LEASE_RECOVERY_REQUIRED, LEASE_OWNER_MISMATCH, LEASE_NOT_EXPIRED,
LEASE_NOT_FOUND, LEASE_INCONSISTENT, LEASE_MUTATION_REQUIRED,
LEASE_INVALID_OWNER, LEASE_INVALID_TASK_ID, LEASE_INVALID_CHANNEL,
LEASE_INVALID_REASON, LEASE_INVALID_DURATION, LEASE_INVALID_TIMESTAMP,
LEASE_INVALID_RECORD, LEASE_REQUIRED_FIELD_MISSING, LEASE_UNKNOWN_FIELD,
LEASE_RECORD_ID_MISMATCH, LEASE_STORAGE_INVALID,
LEASE_PATH_OUTSIDE_WORKSPACE, LEASE_TRANSACTION_PENDING,
LEASE_TRANSACTION_CLEANUP_FAILED, LEASE_TRANSACTION_PUBLICATION_UNKNOWN,
LEASE_TRANSACTION_INVALID, LEASE_TRANSACTION_NOT_FOUND,
LEASE_TRANSACTION_RECOVERY_FAILED,
LEASE_AUDIT_FAILED et LEASE_AUDIT_ROLLBACK_FAILED. Les écritures de bail utilisent un
marqueur de transaction durable avec des phases préparées/appliquées/publiées ; après un
plantage ou un échec de nettoyage, l'accès échoue en mode fermé jusqu'à ce que
task recover-pending/LeaseStore.recover_pending() revienne explicitement en arrière
ou termine la transaction publiée.
Les verrous de chemin coordonnent l'accès exclusif aux fichiers et répertoires relatifs à l'espace de travail :
chat.py lock <channel> <paths...> --as <agent> [--lease-seconds 300]
chat.py check <channel> <paths...> [--as <agent>]
chat.py unlock <channel> <lock-id-or-path> --as <agent>
chat.py recover <channel> <lock-id-or-path> --as <agent> \
--reason "session obsolète" [--lease-seconds 300]
chat.py recover-pending <channel> --as <agent> \
[--resolve-publication rollback|published]
lock et recover acceptent --lease-seconds, --lease, ou --ttl. La cible peut
être l'identifiant du verrou ou le chemin normalisé exact. La normalisation résout
les chemins relatifs à l'espace de travail, empêche les évasions de répertoire, de racine/canal/lien symbolique,
applique les règles de casse de la plateforme (insensible à la casse sur Windows) et rejette
les noms réservés de Windows, les points/espaces de fin, les caractères de contrôle et les paires de substitution isolées.
Les collisions fichier/fichier et les chevauchements répertoire/fichier sont en conflit. Seul le propriétaire peut
déverrouiller un verrou actif ; les verrous expirés nécessitent un recover explicite enregistrant
previous_owner, previous_expires_at et recovery_reason. Les mutations utilisent
une publication de fichier atomique en termes de contenu, des verrous de fichier consultatifs et une journalisation de transaction à sécurité de plantage.
Les erreurs de verrou de chemin se terminent avec un code non nul et des codes stables tels que
PATH_LOCK_INVALID_PATH, PATH_LOCK_PATH_OUTSIDE_WORKSPACE,
PATH_LOCK_CONFLICT, PATH_LOCK_RECOVERY_REQUIRED,
PATH_LOCK_OWNER_MISMATCH, PATH_LOCK_NOT_FOUND, PATH_LOCK_NOT_STALE,
PATH_LOCK_INVALID_OWNER, PATH_LOCK_INVALID_LOCK_ID,
PATH_LOCK_INVALID_CHANNEL, PATH_LOCK_INVALID_REASON,
PATH_LOCK_INVALID_DURATION, PATH_LOCK_INVALID_TIMESTAMP,
PATH_LOCK_INVALID_RECORD, PATH_LOCK_STORAGE_INVALID,
PATH_LOCK_STORAGE_ERROR, PATH_LOCK_TIMEOUT,
PATH_LOCK_TRANSACTION_PENDING, PATH_LOCK_TRANSACTION_CLEANUP_FAILED,
PATH_LOCK_TRANSACTION_INVALID, PATH_LOCK_AUDIT_FAILED, et
PATH_LOCK_AUDIT_ROLLBACK_FAILED.
Le résumé et le compactage de l'état du canal produisent un artefact state.md déterministe :
chat.py state <channel> [--write] [--json] [--strict]
chat.py compact <channel> [--as <agent>] [--no-audit] [--json] [--strict]
state affiche ou imprime le résumé dérivé du canal contenant :
- Objectif / Sujet (depuis
_meta.json) - Décisions (extraites des messages et des enregistrements structurés)
- Tâches Ouvertes (depuis
tasks/*.json, incluant le statut, le propriétaire, le bail, les dépendances) - Bloqueurs (bloqueurs de dépendances de tâches, statuts de tâches bloquées et messages de blocage)
- Propriétaires et Baux (revendications actives, propriétés de tâches et verrous de chemin)
- Verrous de Chemin (verrous de chemin actifs depuis
locks/*.json) - Dernière Preuve de Vérification (critères d'acceptation et messages de vérification)
compact écrit le résumé dérivé dans <root>/<channel>/state.md en utilisant le remplacement
atomique par fichier temporaire frère (.state.md.tmp.*) et par défaut publie un événement de message
state.compacted vérifiable dans le canal.
state.md est strictement un artefact dérivé et n'est jamais faisant autorité.
Le compactage est non destructif : il ne supprime, ne mute ni ne tronque jamais aucun fichier de message,
enregistrement de tâche, revendication active, verrou de chemin ou curseur de lecture. Tous les enregistrements source restent la
vérité fondamentale immuable.
Les erreurs d'état se terminent avec un code non nul et des codes stables tels que STATE_INVALID_CHANNEL,
STATE_CHANNEL_NOT_FOUND, STATE_INVALID_RECORD, STATE_MALFORMED_SOURCE,
STATE_RECORD_NOT_FOUND, STATE_IO_ERROR et STATE_AUDIT_FAILED.
--reply <seq> chaîne un message à un précédent. python chat.py <cmd> --help pour tous les indicateurs.
Commandes d'événement neutres vis-à-vis de l'adaptateur :
chat.py event post <channel> --from <agent> --type capability --harness <harness>
chat.py event post <channel> --from <agent> --type status --harness <harness> --status ready
chat.py event read <channel> [--type capability|status]
Le corps de l'événement est du JSON versionné validé par rapport à
schemas/agent-chat-event.schema.json. Les messages Markdown ordinaires restent
inchangés et sont ignorés par event read.
Structure des dossiers
<root>/
review/ # un canal = un salon de discussion de groupe
_meta.json # membres, sujet, création
state.md # résumé d'état dérivé (non faisant autorité)
0001-alice-schema-draft.md # NNNN-<from>-<slug>.md, préface + corps
0002-bob-schema-review.md
tasks/ # enregistrements de tâches structurées
claims/ # enregistrements de baux actifs
locks/ # verrous de chemin d'espace de travail actifs
.cursors/bob.txt # "dernier seq lu" par agent
deploy/ # un DEUXIÈME salon de discussion de groupe, espace de séquence distinct
...
Mess...