tmux ist ein Terminal-Multiplexer, gebaut für Menschen, die entfernte Server verwalten. Es teilt ein Terminal in Panes auf, hält Sessions über Verbindungsabbrüche hinweg am Leben und lässt Sie sich an laufende Prozesse anhängen und wieder lösen. Sysadmins nutzen es seit 2007 genau so. Für Agenten ist tmux aus einem grundlegenderen Grund interessant: Jedes Tool, das in einem Terminal läuft, gibt seinen Output als Text aus. Keine JSON-API, kein typisiertes Schema, kein strukturierter Event-Stream. Text. Derselbe Text, den ein Mensch liest, wenn er kurz auf den Bildschirm schaut. Ein Agent, der Text flüssig liest (und das können aktuelle LLMs), kann alles steuern, was in einem Terminal läuft, ohne für jedes Tool eine eigene Integrationsschicht zu brauchen.
grep, jq, curl, psql, eine Python-REPL, ein Browser, eine SSH-Session auf einem entfernten Rechner: Alle erzeugen Text-Output in einem Pane. Alle nehmen Tastatureingaben entgegen. tmux sitzt als universeller Adapter zwischen dem Agenten und diesen Tools. Dafür entworfen wurde es nicht, aber es rendert alles in das eine Format, mit dem Agenten ohnehin umgehen können.
Das ist die Verbindung. Der Agent liest den Bildschirm. Der Bildschirm spiegelt die Welt. tmux ist die Oberfläche.
Die zwei Primitive
Ein Agent muss zwei Dinge können: seine Umgebung beobachten und in ihr handeln. In tmux entsprechen dem zwei Befehle.
capture-pane ist die Beobachtung. Der Befehl liefert den aktuell gerenderten Inhalt eines Panes, also das, was Sie sehen würden, wenn Sie auf das Terminal schauen. Nicht den rohen stdout-Stream mit eingestreuten Escape-Codes, nicht einen Scrollback-Puffer voll mit veraltetem Kontext, sondern den gerade sichtbaren Zustand des Bildschirms. Das Flag -J fügt umgebrochene Zeilen zusammen, sodass ein langer Pfad, der sich über zwei Terminalzeilen zieht, zu einem einzigen String wird. Das Flag -S nimmt den jüngsten Scrollback dazu, sodass auch Output sichtbar bleibt, der oben aus dem Bildschirm gelaufen ist.
lines = pane.cmd("capture-pane", "-p", "-J", "-S-50").stdout
Das ist die Wahrnehmung des Agenten. Sauber, gerendert, begrenzt. Der Agent sieht genau das, was ein Mensch sehen würde, wenn er kurz auf das Terminal schaut.
send-keys ist die Handlung. Der Befehl tippt Tastenanschläge in ein Pane, genau so, als hätte ein Mensch diese Tasten gedrückt. Der Agent ruft keine API auf und keine Funktion. Er tippt einen Befehl und drückt Enter.
pane.send_keys("grep -r 'TODO' .", enter=True)
Auf diese Symmetrie kommt es an. Der Agent handelt so wie ein Mensch (Tastenanschläge) und beobachtet so wie ein Mensch (Bildschirminhalt). Zwischen der Schnittstelle des Agenten und der des Tools liegt keine Übersetzungsschicht. Das Tool weiß gar nicht, dass ein Agent es steuert.
Panes als Räume
Eine clive-Session legt pro Tool ein tmux-Fenster an. Die Shell bekommt ein Fenster. Der Browser bekommt ein Fenster. Der Datenprozessor bekommt ein Fenster. Jedes Fenster hat ein Pane, und jedes Pane hat seinen eigenen, unabhängigen Prozess, seinen eigenen Shell-Zustand und sein eigenes Arbeitsverzeichnis.
tmux session "clive"
├── window: shell → bash (local)
├── window: browser → lynx (local)
├── window: data → bash with jq/mlr (local)
└── window: agent-gpu → ssh → remote clive (remote)
Die Metapher sind Räume. Der Agent arbeitet immer nur in einem Raum, aber alle Räume laufen ständig weiter. Ein Browser-Pane, das eine Webseite anzeigt, zeigt sie so lange an, bis der Agent weiternavigiert. Ein Shell-Pane mit einem laufenden Prozess führt diesen Prozess weiter aus. Der Agent kann die Räume wechseln, also aus einem Pane lesen und in einem anderen handeln, ohne dabei irgendetwas zu stören.
Das passt direkt zum DAG-Scheduler. Unabhängige Teilaufgaben laufen parallel in verschiedenen Panes. Jedes Pane bekommt einen Thread, jeder Thread hält einen Lock auf sein Pane, und der Agent in jedem Thread arbeitet seine Teilaufgabe unabhängig ab. Ist eine Teilaufgabe fertig, erbt die nächste, die diesem Pane zugewiesen ist, die Umgebung: das Arbeitsverzeichnis, die exportierten Variablen, die Befehlshistorie.
# Per-pane locks prevent two subtasks from typing into the same pane
plan_locks: dict[str, threading.Lock] = {}
for pane_name in panes:
plan_locks[pane_name] = threading.Lock()
Ein Plan mit vier Teilaufgaben und zwei Panes könnte die Teilaufgaben 1 und 2 parallel in getrennten Panes ausführen, dann Teilaufgabe 3 im ersten Pane (mit der geerbten Umgebung von Teilaufgabe 1) und schließlich Teilaufgabe 4, sobald Teilaufgabe 3 fertig ist. Das Pane ist die Ausführungsumgebung. Der Lock sorgt für exklusiven Zugriff. Der DAG legt die Reihenfolge fest.
Der Beobachtungskanal
Das Design von capture-pane löst mehrere Probleme, mit denen sich Entwickler von Agenten oft herumschlagen.
Das Rendering ist erledigt. Terminal-Output enthält ANSI-Escape-Codes: Farben, Cursorbewegungen, Zeilenlöschungen. Rohes stdout ist voller Rauschen und schwer zu parsen. capture-pane liefert das gerenderte Ergebnis, nachdem tmux alle Escape-Codes verarbeitet hat. Der Agent sieht in einem Verzeichnislisting „file.txt“, nicht \033[1;34mfile.txt\033[0m.
Die Grenzen ergeben sich von selbst. Das Pane hat eine feste Größe, typischerweise 80×24 oder was immer die Maße des Terminals sind. Das ist ein natürliches Kontextfenster. Der Agent liest eine Bildschirmseite Text, denkt darüber nach und handelt. Langer Output läuft oben aus dem Bild, aber -S-50 nimmt den jüngsten Scrollback mit. Mit einem Megabyte unbegrenztem stdout muss sich der Agent nie befassen.
Der Snapshot ist konsistent. capture-pane liefert einen atomaren Snapshot. Zwischen dem Lesen der ersten und der letzten Zeile gibt es keine Race Condition, denn der gesamte Bildschirmzustand wird in einem einzigen Aufruf erfasst. Ein Befehl, der gerade Output produziert, erzeugt keinen verstümmelten Zwischenzustand.
Metadaten zur Umgebung stehen bereit. Über den Bildschirminhalt hinaus gibt tmux mit display-message die Maße des Panes, die Fensternamen und den Session-Zustand preis:
width = pane.cmd("display-message", "-p", "#{pane_width}").stdout[0]
height = pane.cmd("display-message", "-p", "#{pane_height}").stdout[0]
Der Agent weiß, dass sein Terminal 120 Spalten breit ist, und kann die Formatierung seiner Ausgabe entsprechend anpassen.
Das Pane ist der Kontext
MCP-Tools, Function-Calling-APIs, strukturierte Tool-Schnittstellen: Sie alle teilen dieselbe Annahme, nämlich dass Kontext ausdrücklich übergeben und ausdrücklich zurückgegeben werden muss. Sie rufen ein Tool auf, bekommen ein Ergebnis, serialisieren dieses Ergebnis zurück in die Konversation und reichen die relevanten Teile an den nächsten Aufruf weiter. Der Kontext lebt in Ihrem Code, nicht im Tool.
tmux dreht das um. Das Pane sammelt Zustand als natürliches Nebenprodukt der Arbeit darin. Das aktuelle Verzeichnis. Der letzte Befehl und sein Output. Eine halb fertige Pipeline. Eine in einem Editor geöffnete Datei. Eine Fehlermeldung von vor drei Schritten, die im Scrollback noch zu sehen ist. Nichts davon muss erfasst, serialisiert, gespeichert oder eingespeist werden. Es steht auf dem Bildschirm. capture-pane liest es, wann immer der Agent es braucht.
Das Pane ist damit sein eigener Kontextspeicher. Ein Agent, der die Arbeit in einem Shell-Pane wieder aufnimmt, muss nicht erst erfahren, was vorher passiert ist. Er liest den Bildschirm, und der Bildschirm sagt es ihm. Das Arbeitsverzeichnis steht im Prompt. Der letzte Befehl steht in der Historie. Der Output steht im Scrollback. Die Umgebungsvariablen stecken in der Shell. Der gesamte angesammelte Zustand einer Arbeitssession ist schon da, in demselben Format, in dem der Agent auch alles andere liest.
Mit MCP oder Tool-APIs entscheiden Sie ständig, welchen Kontext Sie mitnehmen und welchen Sie verwerfen. Mit einem tmux-Pane verschwindet dieses Problem weitgehend. Das Pane erinnert sich. Der Agent liest. Für die Kontextübergabe muss niemand etwas entwerfen, weil das Terminal sie längst erledigt.
Persistenz und Wiederherstellung
tmux-Sessions überstehen Verbindungsabbrüche. Wenn Sie sich per SSH auf einem Server anmelden, eine tmux-Session starten und die Verbindung abreißt, läuft die Session weiter. Sie verbinden sich neu und machen dort weiter, wo Sie aufgehört haben. Für Agenten ist diese Eigenschaft noch wertvoller. Der clive-Prozess orchestriert die Ausführung, aber die eigentliche Arbeit passiert in den tmux-Panes. Stürzt der Orchestrator ab, bleiben die Panes bestehen. Ein entfernter Befehl läuft weiter. Ein Browser behält seinen Zustand. Die Änderungen am Dateisystem sind bereits gemacht. Das heißt auch, dass sich der Arbeitsbereich jederzeit inspizieren lässt. Während clive läuft, können Sie ein weiteres Terminal öffnen und sich an die Session anhängen:
tmux attach -t clive
Sie sehen genau das, was der Agent sieht. Sie können zuschauen, wie er Befehle tippt, wie Output durchläuft und wie er zwischen den Panes wechselt. Das ist das nützlichste Debugging-Werkzeug im ganzen System: keine Logs, keine Traces, sondern dem Agenten in Echtzeit bei der Arbeit zusehen, an derselben Terminal-Oberfläche, die er selbst benutzt. Bei entfernten Agent-zu-Agent-Verbindungen bedeutet Persistenz, dass der Arbeitsbereich des inneren clive selbst dann überlebt, wenn das äußere clive die Verbindung verliert. SSH bricht ab, die tmux-Session auf dem entfernten Host läuft weiter. Wenn sich das äußere clive neu verbindet, enthält das Pane noch den neuesten Output.
Anlegen bei Bedarf
Nicht alle Panes müssen schon beim Start der Session existieren. Das Adressierungssystem clive@host legt Agenten-Panes bei Bedarf an: Stößt der Planer auf eine Adresse, öffnet der Session-Manager ein neues tmux-Fenster, startet die SSH-Verbindung und registriert das Pane. Der Arbeitsbereich wächst organisch mit den Anforderungen der Aufgabe.
window = session.new_window(window_name="agent-devbox", attach=False)
pane = window.active_pane
pane.send_keys("ssh devbox 'python3 clive.py --conversational'", enter=True)
Das entscheidende Detail ist der Parameter attach=False. Neue Fenster entstehen im Hintergrund: Das aktive Fenster der Session ändert sich nicht, die Ansicht des Benutzers (falls er zusieht) wird nicht gestört, und der Orchestrator muss sich nicht um den Fokus kümmern. Das Pane existiert, der Befehl startet, und der Agent kann daraus lesen, sobald er so weit ist.
Die Runtime härten
Wer tmux als Runtime für Agenten einsetzt, stößt auf praktische Probleme, die bei der beiläufigen Nutzung durch Menschen nicht auftreten.
Socket-Isolation. Ein Entwickler, der täglich mit tmux arbeitet, hat seine eigenen Sessions: Editor-Layouts, laufende Server, SSH-Verbindungen. Ein Agent, der auf demselben tmux-Server Sessions anlegt, käme diesen in die Quere. clive läuft auf einem eigenen tmux-Socket: libtmux.Server(socket_name="clive"). Die Sessions des Agenten sind für tmux ls unsichtbar, und ein versehentliches tmux kill-server zerstört nicht den Arbeitsbereich des Agenten (und umgekehrt). Das Flag -L clive gibt allem einen eigenen Namespace.
Absturzfeste Panes. Wenn ein Befehl in einem tmux-Pane endet, egal ob regulär oder durch einen Absturz, schließt sich das Pane standardmäßig. Für einen Agenten ist das katastrophal. Der Exit-Code und der letzte Output verschwinden, bevor der Agent sie lesen kann. Die Lösung heißt remain-on-exit: Das Pane bleibt offen, nachdem sein Prozess beendet ist, und der Bildschirminhalt bleibt erhalten. clive setzt das pro Session direkt nach dem Anlegen, so begrenzt, dass es nicht auf andere Sessions desselben Servers durchschlägt.
Setup ohne Sleep. Die naive Art, darauf zu warten, dass ein Pane bereit ist, heißt time.sleep(1.5). Das ist langsam und unzuverlässig: zu kurz für entfernte Verbindungen, Verschwendung bei lokalen. clive nutzt dasselbe Marker-Muster, das es auch zum Erkennen abgeschlossener Befehle verwendet:
marker = f"___SETUP_{uuid.uuid4().hex[:4]}___"
pane.send_keys(f'cd {cwd} && mkdir -p {workdir}; echo {marker}', enter=True)
Eine Polling-Schleife prüft alle Panes parallel mit exponentiellem Backoff: Sie beginnt bei 10 ms, deckelt das Intervall bei 500 ms, und die Obergrenze liegt bei 10 Sekunden. Lokale Panes sind in rund 50 Millisekunden bereit statt in 1.500. Entfernte Panes brauchen so lange, wie sie brauchen, und nicht länger. Dieselbe Technik, die das Ende eines Befehls erkennt, erkennt auch, ob die Umgebung bereit ist.
Das sind keine Features, die Benutzer zu sehen bekommen. Sie machen den Unterschied zwischen einem Prototyp und einem System, das Sie über Nacht laufen lassen können.
Was tmux nicht ist
tmux ist keine Container-Runtime. Es bietet keine Isolation des Dateisystems, keine Ressourcenlimits und keine Sicherheitsgrenzen. Ein Befehl in einem tmux-Pane hat alle Rechte des Benutzers, der die Session gestartet hat. Die Sicherheitsschichten (Befehls-Blocklists, eingeschränkte Shells, SSH-Beschränkungen) sind ein eigenes Thema und bauen darauf auf.
tmux ist kein Message Bus. Panes kommunizieren nicht über tmux miteinander. Das Shared Brain, das Scratchpad, die dateibasierte Kontextübergabe zwischen Teilaufgaben: Das sind clive-Konstrukte, die das Dateisystem nutzen, keine tmux-Features.
Genau genommen ist tmux ein gemultiplexter Terminalemulator mit einer Schnittstelle für die Steuerung durch Programme. Es kümmert sich um das Rendering, die Isolation zwischen Panes, die Persistenz über Verbindungsabbrüche hinweg und die atomare Bildschirmerfassung. Alles, was clive tut (der Turn Loop, der DAG-Scheduler, das Screen-Diffing, die Erkennung abgeschlossener Befehle, das Agent-zu-Agent-Protokoll), baut auf diesen vier Fähigkeiten auf.
Die Entscheidung, auf tmux aufzubauen statt eine eigene Agenten-Runtime zu entwickeln, war die folgenreichste Architekturentscheidung des Projekts. Sie bedeutete, Jahrzehnte an Terminal-Infrastruktur zu erben (SSH-Kompatibilität, Fernzugriff, das Tool-Ökosystem, die Beobachtbarkeit durch Menschen), statt das alles neu zu implementieren. Jedes Unix-CLI-Tool funktioniert in einem tmux-Pane. Jeder SSH-Server ist aus einem tmux-Pane erreichbar. Jeder Entwickler weiß bereits, wie man sich an eine tmux-Session anhängt und nachsieht, was gerade passiert.
Das Habitat war immer schon da. Es wartete nur darauf, dass ein Agent einzieht.
Aus dem Englischen übersetzt: zum Originalbeitrag