Een terminalprotocol voor programmastatus (OSC 7501)

Ik heb een specificatie geschreven voor een nieuwe terminal-escapevolgorde: OSC 7501, het Program Status Protocol.

Dit protocol stelt elk programma in staat om de terminal te vertellen wat het aan het doen is: inactief (idle), bezig (working), wachtend op de gebruiker (waiting on the user), voltooid (finished) of mislukt (failed), en waarom.

Voor voorbeeld: zo zou Terraform kunnen aangeven dat het geblokkeerd is in afwachting van gebruikersinput, met het bericht "Apply 3 to add, 1 to change, 0 to destroy?" (base64-gecodeerd):

ESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=QXBwbHkgMyB0byBhZGQsIDEgdG8gY2hhbmdlLCAwIHRvIGRlc3Ryb3k/ ESC \

Een terminal (of een andere tool die Terraform uitvoert) kan deze informatie vervolgens op de gewenste manier weergeven: als een melding, in een inbox, via een statusicoon, enzovoort.

Dit bericht legt uit waarom ik denk dat dit protocol nodig is, waarom bestaande benaderingen onvoldoende zijn (vooral voor coding agents) en hoe het protocol werkt.

Dit is een volledig generieke, terminal-native specificatie en protocol. Het is voortgekomen uit mijn werk aan Superlogical en Ghostty, maar de specificatie bevat geen productspecifieke functionaliteit of taal. Het is ontworpen als een idiomatische, goed gevormde specificatie die voor elke terminal-ontwikkelaar herkenbaar zal zijn.

Het probleem

Langdurige taken zijn gebruikelijk in terminals: builds, deployments, pakket-updates, dataverwerking en, steeds vaker tegenwoordig, coding agents. Deze programma's wisselen af tussen zelfstandig werken, wachten op de gebruiker en het afronden van de taak. Ondertussen gaan gebruikers vaak iets anders doen en willen ze weten wanneer het werk is voltooid of wanneer hun input nodig is.

Aspecten van dit probleem zijn al decennia op verschillende manieren opgelost. Sommige terminals monitoren bijvoorbeeld het actieve voorgrondproces en hebben functies om te melden wanneer dit verandert. Andere wachten op een bepaalde periode van "stilte" (volgens diverse definities) in de output. In de specificatie staat ook een lijst met redenen waarom bestaande sequenties onvoldoende zijn.

Uiteindelijk was ik van mening dat er geen samenhangende, interactie-agnostische, generieke oplossing bestond voor dit probleem die voortgang, blokkades, voltooiing en taakbomen overbracht. Het was bovendien niet mogelijk om robuust een oplossing samen te stellen uit reeds bestaande sequenties.

Focus op "Agentic Inboxes"

(Wie geen interesse heeft in AI, LLM's, etc., kan deze sectie overslaan. Het probleem is generiek en blijft relevant zonder AI, maar het is bijzonder hardnekkig bij AI, waardoor ik het hier apart benoem.)

Het is steeds gebruikelijker dat mensen veel langdurige agents tegelijk draaien voor diverse redenen: achtergrondonderzoek, monitoring van issues, het oplossen van bugs, grote features, enzovoort. Elke agent werkt een tijdje en stopt vervolgens om toestemming te vragen, een vraag te stellen of te melden dat hij klaar is.

Hierdoor is er een nieuwe categorie tools ontstaan die ik simpelweg "agentic inboxes" noem: een enkel overzicht van elke draaiende agent, waarop te zien is welke bezig zijn, welke klaar zijn en welke op jou wachten. Herdr, cmux en Agent Deck zijn enkele voorbeelden van honderden dergelijke tools.

Zonder een dedicated protocol lossen zij het statusprobleem op twee manieren op: via heuristieken en via niet-terminal API's.

Heuristieken

De eerste benadering is gissen door het scherm of de venstertitel te lezen en deze te matchen met bekende patronen.

Herdr is hier een goed voorbeeld van, omdat het dit goed doet en openlijk documenteert. De detectiemanifesten zijn TOML-regels die een agent classificeren als idle, working of blocked. Hier is de eerste van de 16 regels voor Claude Code:

[[rules]]
id = "osc_title_working"
state = "working"
priority = 1100
region = "osc_title"
visible_working = true
# Braille covers <= 2.1.227; half-circles are the 2.1.228 busy spinner.
regex = ['^[\x{2800}-\x{28FF}\x{25D0}-\x{25D3}] ']

Claude Code wordt als "working" beschouwd als de venstertitel begint met een Braille-spinner-karakter of, sinds versie 2.1.228, een half-cirkel-karakter. De geschiedenis van dat bestand laat tien wijzigingen in drie maanden zien voor alleen al Claude Code.

Dit is geen kritiek op Herdr; de beheerders doen hun best met de beschikbare tools. Maar het illustreert wel het voordeel van een uniform protocol.

Niet-terminal API's

De tweede benadering is dat het programma zijn eigen status rapporteert via een specifieke, out-of-band API voor de inbox, zoals de socket API van Herdr of cmux notify. Dit is in sommige opzichten beter dan heuristieken, omdat het programma dat de status daadwerkelijk kent, deze ook rapporteert.

Echter, elk programma moet dan apart integreren met elke inbox. Bovendien werkt een lokale socket niet via SSH of vanuit een container zonder extra bridging. De pty (pseudo-terminal) werkt daarentegen wel over al deze omgevingen heen.

Het Programmastatusprotocol

OSC 7501 is een terminal-native antwoord op dit probleem. Het programma rapporteert zijn eigen status direct via de pty die het altijd heeft, gebruikmakend van een formaat dat veilig is om overal te verzenden (goed functionerende terminals negeren onbekende OSC's).

De body van de sequentie is een lijst van sleutel=waarde paren, gescheiden door een dubbele punt (:). De enige verplichte sleutel is state, die een van de volgende waarden heeft:

StatusBetekenis
idleIn rust, wachtend op de volgende instructie van de gebruiker.
workingBezig. Kan een voortgangspercentage bevatten.
doneVoltooid. Het resultaat is klaar en de gebruiker heeft het nog niet gezien.
blockedKan niet doorgaan totdat de gebruiker iets doet. kind geeft aan wat (toestemming, vraag of authenticatie) en msg geeft aan waarom.
errorMislukt en gestopt.

Optionele sleutels zijn onder andere app (een stabiele, machine-leesbare programmanaam zoals cargo of claude-code) en msg (een enkele mens-leesbare regel, gecodeerd als base64).

Programma's die meerdere taken tegelijk uitvoeren, kunnen meerdere records rapporteren met behulp van hiërarchische ID's. Een deployment-tool kan op het root-niveau "working" zijn, terwijl us-east een image pusht op 40% en eu-west geblokkeerd is in afwachting van goedkeuring voor productie. Beiden zijn tegelijkertijd waar, en de terminal beslist wat er wordt getoond. Een "clear state" verwijdert records.

Hier is een volledige integratie voor een shell-script om rsync te wrappen zodat het deelneemt aan dit protocol:

status() {
  printf '\e]7501;state=%s:msg=%s\e\\' "$1" "$(printf '%s' "$2" | base64 | tr -d '\n')"
}

status working "Syncing photos"
rsync -a ~/Photos backup:/photos && status done "Photos synced" || status error "rsync failed"

Dit is triviaal te scripten met standaard POSIX sh. Er is geen SDK nodig, geen sockets, geen omgevingsvariabelen en geen JSON. Er is geen voorkeur voor specifieke GUI-presentaties of specifieke workloads (zoals AI). Het is een goed gevormde, generieke basis waarop functionaliteit kan worden gebouwd waar iedereen aan kan deelnemen.

De volledige specificatie behandelt de rest: levensduur van records, feature-detectie, terminfo, groottelimieten en beveiliging. Het is kort; ik heb het volledig handmatig geschreven. Lees het a.u.b.

Implementatie

Ik heb deze specificatie geschreven op basis van mijn ervaring met het beheren van een terminalemulator gedurende vele jaren. Het is zo geschreven dat het voor applicatieontwikkelaars gemakkelijk is om uit te zenden, en voor terminalemulators gemakkelijk om te consumeren en te parsen.

Ik heb dit protocol al twee keer geïmplementeerd: één keer in libghostty en een parallelle implementatie in Rex. Ook heb ik het als proof-of-concept geïmplementeerd in Terraform, Claude Code, Codex en Homebrew via plugins of forks. In elk geval was de implementatie niet meer dan een dozijn regels code.

Ik ben in contact geweest met de beheerders van veel populaire terminalprogramma's en emulators, en zij hebben geholpen bij het reviewen en vormgeven van de specificatie. Mocht je nog feedback hebben, dan hoor ik dat graag.

Als je de specificatie hebt geïmplementeerd, laat het me dan weten via e-mail; ik voeg je dan toe aan de lijst met tools die dit ondersteunen. Dank je wel.

Ik zou graag willen dat we allemaal stoppen met gissen naar wat programma's doen door hun schermen of procesbomen te lezen. Het programma weet het al. Laten we het een manier geven om ons dat te vertellen!