Sessions d'agents IA
Une session dont le process d'avant-plan est une CLI d'agent de code connue reçoit un marqueur indiquant son état détecté — au repos, en travail, bloquée (elle vous attend) ou terminée — au lieu du seul marqueur d'activité générique.
Le problème
Le marqueur d'activité générique (●) ne sait pas distinguer « ça a produit de la
sortie » de « ça veut une réponse ». Avec trois agents ouverts, c'est exactement la distinction
dont on a besoin : deux d'entre eux travaillent tranquillement, le troisième attend une
autorisation depuis dix minutes.
| Marqueur | État | Signification |
|---|---|---|
· | idle | Agent détecté, au repos. |
… | working | Un tour est en cours. |
‼ | blocked | L'agent attend une réponse de votre part. |
✓ | done | L'agent a fini son tour. |
Détection sans configuration
Les CLI claude, codex et opencode sont reconnues sans
aucun réglage : lazyshell confronte les manifestes intégrés (sous
pkg/agent/manifests) à l'écran visible de la session et au titre de terminal qu'elle
pose. Rien à installer, rien à déclarer.
Manifestes personnalisés
Déposez un fichier <nom-du-process>.yml dans
~/.config/lazyshell/agents/ (ou l'équivalent $XDG_CONFIG_HOME) pour
surcharger un manifeste intégré, ou pour en ajouter un pour un autre agent : le même nom de
fichier qu'un manifeste intégré le remplace purement et simplement, un nom différent s'ajoute au
lot. Voir les manifestes intégrés pour le format.
Les manifestes sont locaux uniquement : lazyshell n'en télécharge jamais un depuis le réseau.
État autoritaire par hooks
La détection par manifeste est une déduction à partir de ce qui est affiché. Un second
canal permet à l'agent de déclarer son état directement. Chaque session dispose de sa propre
socket Unix, exposée au process qui tourne dedans sous $LAZYSHELL_SOCK (à côté de
$LAZYSHELL_SESSION_ID), et lazyshell hook <état> — parmi
idle, working, blocked et done — y écrit.
Ce n'est pas fait pour être tapé à la main, mais branché dans le mécanisme de hooks de l'agent :
$ lazyshell init --agents # affiche la config à coller dans Claude Code / Codex
Claude Code
Un bloc hooks dans settings.json :
UserPromptSubmit→lazyshell hook workingNotification→lazyshell hook blockedStop→lazyshell hook done
Codex
Une ligne notify dans config.toml. Codex n'a qu'un seul événement
(agent-turn-complete), il ne peut donc jamais rapporter que done.
opencode
Pas encore branché : son signal le plus riche est un abonnement SSE plutôt que quelque chose qu'il pousse de lui-même — une forme d'intégration différente, laissée pour plus tard.
Dès qu'une session a reçu un seul événement de hook, la déduction par manifeste s'arrête définitivement pour cette session : le hook fait autorité à partir de là, et pas seulement jusqu'au prochain changement d'écran.
API de contrôle par les agents
Le canal de hooks ci-dessus ne permet à un agent que de se décrire. Lui permettre
d'agir — créer des sessions, lire ce que les autres ont affiché, y taper, ou attendre
qu'une session atteigne un état donné — est une fonctionnalité distincte, sur une socket
distincte, désactivée sauf si control.enabled en décide autrement. C'est ce dont un
agent « chef d'orchestre » a besoin.
lazyshell ctl list # id, nom, statut, état d'agent
lazyshell ctl read session-2 --tail 40 # texte brut, sans séquences d'échappement
lazyshell ctl new --name build --cwd ./api --command 'make test'
lazyshell ctl send build 'echo bonjour' --enter # comme si c'était tapé
lazyshell ctl kill build
lazyshell ctl rename build tests
Les groupes (voir Groupes) sont lisibles et modifiables depuis ici, ce qui permet à un agent d'en orchestrer plusieurs autres comme un bloc :
lazyshell ctl list --group agents # seulement ce groupe
lazyshell ctl new --name w1 --group agents
lazyshell ctl group build agents # affecte une session à un groupe
lazyshell ctl ungroup build # l'en retire
lazyshell ctl group-send agents 'git pull' --enter
lazyshell ctl group-kill agents
Les deux verbes de diffusion affichent combien de sessions ils ont touchées. Un groupe vide ou
inexistant est une erreur, jamais un « 0 session » silencieux : celui qui a fait une faute de
frappe dans un nom de groupe ne doit pas s'entendre dire que son kill a réussi.
group-send saute les sessions déjà terminées. Relancer un groupe n'est délibérément
pas exposé — aucun verbe restart n'existe pour une session seule non plus ; cela
reste la touche W, dans l'interface.
wait bloque jusqu'à ce qu'une session — ou, avec --group au lieu d'une
cible, le premier membre de ce groupe — atteigne un état d'agent donné, au lieu de faire boucler
un chef d'orchestre sur ctl list :
lazyshell ctl wait build --state blocked --timeout 300
lazyshell ctl wait --group agents --state blocked # rend la main au premier qui bloque
--timeout est en secondes et optionnel (120 par défaut) ; un délai dépassé, une
session/un groupe/un état inconnu, ou la session visée qui se termine avant d'atteindre l'état,
sont tous rapportés comme un échec — la même sortie non nulle que toute autre erreur de
ctl, jamais un blocage.
Une session se désigne par son id (session-2, la valeur de
$LAZYSHELL_SESSION_ID) ou par son nom exact ; --json affiche la réponse
brute. Contrairement à lazyshell hook, qui sort toujours en 0 pour ne jamais casser
le tour d'un agent, ctl sort en code non nul au moindre échec — celui qui a demandé
une session sans l'obtenir doit pouvoir le savoir. ctl new ne vole délibérément ni
la sélection ni le clavier, contrairement à la touche n.
À lire avant d'activer. Il n'y a ni jeton ni permission par session : les
permissions 0600 de la socket sont l'intégralité du contrôle d'accès. Activer
signifie que tout processus tournant sous votre compte peut créer des sessions, y
taper des commandes et lire leur sortie — pas seulement les agents que vous avez lancés dans
lazyshell. Et ctl read rend le scrollback verbatim, secrets compris : le masquage
de l'onglet environnement n'a pas d'équivalent ici, parce qu'un identifiant affiché dans un
shell est indiscernable de n'importe quel autre texte une fois à l'écran.
Notifications
Une session qui passe blocked ou done déclenche une notification de
bureau : OSC 9 et OSC 777 vers le terminal hôte par défaut — les deux sont envoyés sans
condition, un terminal qui n'en comprend qu'un ignore l'autre — ou bien la commande de
notify.fallback_command, avec le texte de la notification sur son entrée standard,
pour un terminal qui en a besoin.
Au-delà de deux ou trois sessions d'agent ouvertes, B déplace la sélection directement
vers la prochaine session blocked, en boucle : c'est le point du marqueur et de la
notification réunis.
Une session sans agent notifie de la même façon quand sa dernière commande — via l'intégration shell OSC 133 — échoue, pour qu'un build ou un script long n'ait pas besoin d'être surveillé pour savoir qu'il a échoué. Une session d'agent ne déclenche jamais cette seconde notification en plus de la sienne.
Watchers de motifs
Les watchers de motifs généralisent l'idée à n'importe quelle session : un motif regex évalué sur chaque ligne de sortie, en plus de la détection par code de sortie et non à sa place — utile pour un serveur de dev qui logue une erreur sans pour autant se terminer. Déclarés dans un fichier de projet :
sessions:
- name: api
watch:
- pattern: "ERR!"
notify: true
ou armés à la volée sur la session sélectionnée avec v — un seul motif remplaçable
par session, en plus de ce que le fichier de projet a déjà déclaré pour elle ; soumettre un
motif vide le désarme. Dans les deux cas, une correspondance notifie par le même canal
OSC/commande de secours que ci-dessus, au plus une fois toutes les 3 secondes par motif — un log
qui répète la même correspondance 200 fois dans une rafale ne déclenche qu'une seule
notification, pas 200. La correspondance se fait sur le texte visible (séquences d'échappement
retirées) et se suspend tant qu'une application plein écran (vim, htop)
tient la session, la même règle d'écran alterné que suit déjà OSC 133.
Durée de tour et statistiques
Une session en plein tour (working) affiche depuis combien de temps ce tour tourne,
dans la liste des sessions — par exemple ⏱ 1m32s.
Renseigner agent_stats_command exécute cette commande pour la session
sélectionnée seulement (au plus une fois toutes les 5 secondes : c'est prévu
pour une recherche de coût ou de tokens, pas pour quelque chose d'assez léger pour tourner par
session à chaque tick), avec $LAZYSHELL_SESSION_ID dans son environnement, et
affiche sa première ligne de sortie à côté de la durée — la même forme « commande externe, on
montre sa ligne » que le statusLine de Claude Code.
# ~/.config/lazyshell/config.yml
agent_stats_command: "~/bin/mon-compteur-de-tokens.sh"
Ce que lazyshell ne fait pas
-
Il ne compte pas les tokens. Aucun format d'agent n'est analysé pour cela ;
agent_stats_commandest le point d'extension prévu. - Il n'appelle jamais l'agent par la socket de hooks. Ce canal-là est entrant et déclaratif uniquement : lazyshell écoute, et la seule chose qu'un événement de hook peut faire est de fixer cet unique état. L'API de contrôle ci-dessus est une seconde socket, distincte, et elle est fermée par défaut.
-
L'API de contrôle n'accorde rien qu'on ne lui demande. Il n'y a pas de verbe
delete—ctl killtermine le processus et laisse la session listée comme terminée, il ne la supprime jamais définitivement — et rien dans le protocole ne permet à un agent de modifier la configuration ou les raccourcis de lazyshell.