đŹđ§ English version
Le bus d'événements du run
PérimÚtre : orkeon run --events jsonl, le protocole qu'il parle, et le siÚge qu'il donne à un processus observateur sur l'EventHub du run.
Public : quiconque pilote Orkeon depuis un autre programme â Studio, une passerelle, un job de CI, votre propre outillage.
Un run sans --events imprime du texte pour un humain. Avec, il parle un protocole versionnĂ© : un document JSON par ligne sur stdout, un par ligne sur stdin en retour. Rien d'autre ne change â mĂȘme crew, mĂȘme hĂŽte, mĂȘmes codes de sortie.
1. Pourquoi un protocole plutÎt qu'une sortie analysée
Le texte destiné à une personne change dÚs que quelqu'un en améliore la formulation. Un programme qui le lit casse sur une virgule. Le protocole existe pour que les deux publics cessent de partager un canal : les humains gardent le rendu, les programmes obtiennent un contrat.
Le contrat, c'est le flux sortant. L'entrant est dĂ©libĂ©rĂ©ment tolĂ©rant : une ligne malformĂ©e, un verbe inconnu, une charge utile inutilisable sont ignorĂ©s plutĂŽt que fatals. Un client qui envoie n'importe quoi ne doit pas arrĂȘter une crew qui fonctionnait.
2. L'enveloppe
Chaque ligne sortante est un objet portant ces clés, et elles seules comme noms réservés :
| Clé | Signification |
|---|---|
v |
Version du protocole. Actuellement 2. |
seq |
Numéro de séquence monotone dans le run, à partir de 1. |
ts |
Horodatage ISO-8601 UTC. |
kind |
Ce qui s'est passé (voir §3). |
crewId, agentId |
Qui est concerné, quand le run le sait. |
correlationId |
Lie une question Ă sa rĂ©ponse, une requĂȘte Ă sa rĂ©plique. |
causationId |
L'événement qui a causé celui-ci, pour qu'un client reconstruise l'arbre. |
Une clé absente est omise, jamais écrite à null. Un client traite tout champ d'identité comme optionnel.
Les huit noms ci-dessus sont rĂ©servĂ©s : un champ de charge utile qui entre en collision avec l'un d'eux est supprimĂ© plutĂŽt qu'autorisĂ© Ă se faire passer pour l'enveloppe. Ce n'est pas thĂ©orique â la charge utile de l'entrĂ©e humaine appelait d'abord son champ kind et le perdait, d'oĂč le nom inputKind sur le fil.
Les champs de charge utile sont à plat à cÎté de l'enveloppe, pas imbriqués sous une clé payload. Seule exception : hub.message, dont la charge est opaque au protocole et transportée telle quelle.
3. Sortant â ce qu'un run dit
kind |
Charge utile | Quand |
|---|---|---|
run.started |
target, stream |
Le run commence. |
task.started |
taskId, agentRole |
Une tĂąche commence, dans les six modes d'orchestration â au moment oĂč son agent est choisi, avant qu'on ne lui demande quoi que ce soit. Entre ce kind et son task.completed, un observateur montre la tĂąche en cours ; la charge utile est volontairement mince, rien n'a encore Ă©tĂ© mesurĂ©. Un CLI plus ancien ne dit rien ici, et un client ne doit pas refuser un task.completed dont il n'a jamais vu le dĂ©part. |
task.completed |
taskId, agentRole, success, durationMs, tokens, toolCalls |
Chaque tĂąche se termine, dans les six modes d'orchestration â Ă©chec et annulation compris (voir error). tokens et toolCalls valent 0 quand le mode ne les mesure pas, jamais absents. |
tool.called |
toolName, argsSummary? |
Un outil est invoqué. argsSummary résume les noms d'arguments, jamais leur contenu : un appel peut porter un fichier entier. |
tool.returned |
toolName, success, durationMs |
L'outil a fini â y compris s'il a levĂ©, pour qu'un observateur n'affiche jamais une Ă©tape Ă©ternellement en cours. CorrĂ©lĂ© Ă son tool.called. |
delegation.started |
toRole? |
Un agent a confié du travail à un autre (l'outil delegate_work_to_coworker). La description de la tùche reste hors du flux, comme toute valeur d'argument. |
agent.spawned |
role?, reason? |
L'Ă©quipe a grandi en cours d'exĂ©cution â Ă©mis quand un appel Ă l'outil spawn_agent est observĂ©. rc.2 ne cĂąble cet outil sur aucun agent par dĂ©faut : ce kind n'apparaĂźt que dans les dĂ©ploiements qui l'attachent eux-mĂȘmes. |
cost.updated |
tokens, model?, provider? |
Le compteur de jetons bouge. tokens est cumulatif ; model quand le fournisseur le rapporte ; provider seulement sur les appels de la façade de scripting. Aucun champ de prix : le framework n'a pas de table de prix, et en inventer une serait pire que l'omettre. |
llm.delta |
text |
Un fragment de texte généré. Seulement sous --stream. |
input.needed |
inputKind (text|confirm|choice), prompt, choices?, defaultValue?, taskDescription? |
Une tùche déclarée humanInput: true pose une question. |
hub.message |
from?, topic?, payload?, expectsReply? |
Le hub du run a relayĂ© quelque chose Ă ce processus. from est l'adresse hub de l'expĂ©diteur (agent://{crew}/{agent}, crew://{crew}), pour que le pair puisse attribuer et rĂ©pondre ; absent quand le hub ne la connaĂźt pas (la rĂ©ponse Ă un send, appariĂ©e par correlationId). expectsReply: true n'apparaĂźt que sur le send d'un agent : l'expĂ©diteur est bloquĂ© en attente d'un reply sous son propre timeout, et le silence au-delĂ est un refus. Le pair n'a pas Ă deviner quelles lignes corrĂ©lĂ©es sont des questions â un relais de topic peut porter un correlationId lui aussi. |
error |
code, message, recoverable |
Quelque chose a Ă©chouĂ©. Un run qui s'arrĂȘte â annulĂ© ou en Ă©chec, dans n'importe quel mode â se termine par code: crew_cancelled ou crew_failed avant run.finished. |
run.finished |
success, exitCode, tokens |
Le run se termine. |
Un run qui ne rapporte rien n'est pas un run qui se passe bien â c'est un run qui ne rapporte rien. Un client doit montrer la diffĂ©rence, pas la masquer.
4. Entrant â ce qu'un processus observateur peut dire
Un document JSON par ligne sur stdin. Chaque verbe se projette sur un membre d'IEventHub, sauf la réponse humaine :
kind |
Charge | Effet |
|---|---|---|
input.given |
correlationId?, value |
Répond à un input.needed en attente. |
post |
to, payload |
IEventHub.PostAsync â Ă©crit dans une boĂźte aux lettres. |
send |
to, payload, timeoutMs?, correlationId? |
SendAsync ; la réponse revient en hub.message corrélé. |
publish |
topic, payload, retainAs? |
PublishAsync. |
reply |
correlationId, payload |
Répond à une question qu'un agent a posée à ce processus. |
subscribe / unsubscribe |
topic |
Ouvre ou ferme un relais de ce topic vers hub.message. |
Un input.given sans identifiant de corrélation répond à la question en attente : un humain qui tape dans un terminal n'a pas d'identifiant à citer.
Le silence ne vaut pas consentement
Sans --events, une tĂąche dĂ©clarĂ©e humanInput: true est approuvĂ©e d'office â un repli dĂ©fendable pour un run non surveillĂ©, et la mauvaise rĂ©ponse dĂšs qu'un Ă©cran regarde. Demander le protocole remplace ce fournisseur : la question part sur le flux et le run attend.
Si aucune rĂ©ponse ne vient â canal fermĂ©, run annulĂ© â la confirmation est refusĂ©e, jamais accordĂ©e.
5. Le siĂšge au hub
Un processus observateur est adressable en client://{nom} (--client, défaut studio). Les agents lui écrivent exactement comme à un autre agent, et il peut écrire, publier et s'abonner en retour.
Qui a le droit de l'atteindre est la décision de la crew, pas celle du protocole. Une crew autorise l'échange en nommant le pair dans son bloc links: :
name: billing-crew
links:
- to: "client:studio"
direction: bidirectional
allowed_topics: [run.progress]
Sans lien dĂ©clarĂ©, la politique par dĂ©faut de l'ACL laisse quand mĂȘme passer â le hub a Ă©tĂ© livrĂ© sans ACL, et refuser le trafic non dĂ©clarĂ© casserait toutes les crews existantes â mais une crew qui dĂ©clare un lien est tenue Ă ce qu'elle a dĂ©clarĂ©. Les rĂšgles complĂštes sont dans EventHub §10.
6. Piloter un run depuis un autre programme
orkeon run crew.yaml --events jsonl --client mon-observateur
Lisez stdout ligne par ligne, analysez chaque ligne en JSON, aiguillez sur kind. Ăcrivez rĂ©ponses et commandes sur stdin, un document JSON par ligne, avec vidage du tampon. Trois faits sur lesquels un pilote peut compter :
- Les deux dialectes le parlent. Une cible
.ork.tsest observĂ©e par les mĂȘmes coutures qu'une crew YAML â outils, compteur de jetons, pont du hub et fournisseur de rĂ©ponses humaines arrivent tous jusqu'Ă l'hĂŽte de script. - stdout porte le protocole et rien d'autre. Sur un run observĂ©, chaque ligne de log part sur stderr ; un log entre deux documents JSONL serait une erreur de parsing chez vous.
jsonlest la seule valeur que--eventsaccepte, et il le dit plutĂŽt que de deviner ;--clientsans--eventsavertit au lieu d'ĂȘtre ignorĂ© en silence.
Un échange minimal :
â {"v":2,"seq":1,"ts":"âŠ","kind":"run.started","target":"crew.yaml","stream":false}
â {"v":2,"seq":2,"ts":"âŠ","correlationId":"c-1","kind":"input.needed","inputKind":"confirm","prompt":"Publier le rapport ?"}
â {"kind":"input.given","correlationId":"c-1","value":"yes"}
â {"v":2,"seq":3,"ts":"âŠ","kind":"task.started","taskId":"t1","agentRole":"writer"}
â {"v":2,"seq":4,"ts":"âŠ","kind":"task.completed","taskId":"t1","agentRole":"writer","success":true,"durationMs":4200,"tokens":1840,"toolCalls":3}
â {"v":2,"seq":5,"ts":"âŠ","kind":"run.finished","success":true,"exitCode":0,"tokens":1840}
Deux rĂšgles Ă respecter en construisant votre client. Ignorez un kind que vous ne connaissez pas â un Orkeon plus rĂ©cent en dit plus qu'un client plus ancien n'en comprend, et planter sur une ligne non lue est pire qu'en afficher un peu moins. Et gardez ce que vous n'avez pas su analyser : une ligne non protocolaire reste quelque chose que le run a dit, et la perdre perd le diagnostic.
7. Qui lit ceci aujourd'hui
- Orkeon Studio, dont l'Ă©cran « Lancer » montre la progression â la tĂąche en cours et l'outil au travail, depuis
task.startedettool.called, autant que les tĂąches terminĂ©es â le coĂ»t et les questions du run au lieu d'un dĂ©filement â voir Studio. L'Ă©cran « Lancer » occupe aussi le siĂšge du hub : lesendd'un agent versclient://studio(marquĂ©expectsReply) apparaĂźt comme un panneau de demande auquel l'utilisateur rĂ©pond, et la rĂ©ponse repart par stdin ; les posts du hub sont listĂ©s au lieu d'ĂȘtre perdus. Le silence au-delĂ du timeout propre Ă l'agent reste un refus â la rĂšgle que le silence suit partout sur ce bus â l'Ă©cran donne simplement Ă un humain la chance de parler avant. Orkeon.Studio.Core.RunâRunClientetRunProgressModel, un client de rĂ©fĂ©rence en ~460 lignes, sans aucune dĂ©pendance Ă Infrastructure ni Ă un LLM.RunClientest la forme Ă copier pour un pair qui prend le siĂšge sans Ă©cran : subscribe, post, reply.
La mĂȘme enveloppe porte le flux de l'Atelier, donc un client qui lit l'un lit l'autre.