CommunityCodierung & Entwicklunggithub.com

bitekas/web-hierarchy-server

Zeigt die gesamte Delegationshierarchie einer Agentensitzung als LIVE, selbstaktualisierende Webseite für Menschen, die kein Terminal sehen können.

Was ist web-hierarchy-server?

Diese Fähigkeit verwandelt die gesamte Sitzungshierarchie eines KI-Agenten (wie Claude Code) in eine Live-Webseite, die sich selbst alle 10 Sekunden aktualisiert. Sie richtet sich an Benutzer, die keinen Zugriff auf das Terminal haben, aber den Fortschritt eines Agenten und seiner Unteragenten in Echtzeit verfolgen möchten. Die Seite zeigt einen interaktiven Spawn-Baum mit allen Agenten, deren Aufgabenbeschreibungen, Modelle, Start-/Endzeiten und Status (aktiv/gestoppt) – standardmäßig alle oder nur die aktiven Agenten. Klickt man auf einen Agentenknoten, wird das vollständige Log geladen. Die Fähigkeit unterstützt verschiedene Betriebsmodi: als systemd-Dienst (Linux), als LaunchAgent (macOS) oder als eigenständiger, dauerhafter Prozess. Sie ist für die Verwendung mit Claude Code und anderen Agenten-Frameworks konzipiert, die Sitzungstranskripte in einem konfigurierten Verzeichnis ablegen.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/bitekas/agent-fleet/tree/HEAD/.claude/skills/web-hierarchy-server

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Web-Hierarchieserver

Der Mensch, für den ein Agent arbeitet, befindet sich oft auf einem anderen Gerät und kann das Terminal der Unteragenten nicht sehen. Diese Fähigkeit verwandelt den gesamten Spawn-Baum in eine einzige Live-Seite, die er einmal öffnet und beobachtet: oben ein klickbarer Graph, darunter das vollständige Log des ausgewählten Agenten.

Der Installationskontext dieser Fähigkeit – das Verzeichnis, das fleet where web-hierarchy-server ausgibt – enthält zwei Dateien dieser Installation: CONTEXT.md nennt den Agenten, seinen Port und seine URL, und config.json enthält alle Werte, die die Skripte lesen. config.example.json neben dieser Datei dokumentiert jeden Schlüssel. Die Skripte finden config.json, indem sie denselben Befehl abfragen, oder lesen die Datei, die WEB_HIERARCHY_CONFIG benennt, wenn diese gesetzt ist; ein relativer Pfad darin wird relativ zu seinem eigenen Verzeichnis aufgelöst.

Führen Sie die Prüfungen und Skripte per Pfad aus:

"${CLAUDE_SKILL_DIR}/installation/check"     # Läuft ein Server für dieses Konfigurationsverzeichnis, ist er aktuell?
node "${CLAUDE_SKILL_DIR}/serve.mjs" --help  # Die Konfiguration, das Transkript-Root und den verwendeten Port

Die eine goldene Regel: EIN Server pro Konfigurationsverzeichnis

Ein Konfigurationsverzeichnis (CLAUDE_CONFIG_DIR) ist das Transkript-Root eines Agenten, und genau ein Server bedient es. Ein zweiter Server auf demselben Port ist der Fehler, den diese Regel verhindern soll: Welcher Prozess auch immer die Bindung gewinnt, bedient die Seite, der andere stirbt mit EADDRINUSE, und aus Sicht des Betrachters sieht die Seite in beiden Fällen gut aus. Bevor Sie einen starten, stellen Sie sicher, dass für dieses Konfigurationsverzeichnis noch kein Server läuft (installation/check beantwortet dies und startet niemals etwas).

Die dauerhafte Form ist eine Service-Unit (installation/web-hierarchy-server.service.example): maschinenstart, Restart=always, ab dem Boot aktiv, bevor Arbeiten delegiert werden, und überlebt Neustarts. Die Installation einer solchen erfordert root, was ein Schritt für den Menschen ist. Unter macOS ist es ein LaunchAgent in der GUI-Domäne des Benutzers (installation/web-hierarchy-server.plist.example), installiert ohne sudo. Wenn niemand es ausgeführt hat, läuft der Server als abgetrennter Prozess, der einmal gestartet wird (setsid nohup ... & unter Linux, nohup ... & unter macOS, das kein setsid hat, sodass er die Sitzung überlebt, die ihn gestartet hat) – diese Form funktioniert und überlebt keinen Neustart, was genau das ist, was installation/check in Worten sagt, anstatt grün zu melden.

Änderungen an serve.mjs oder format.mjs sind ABSICHTLICH latent. node lädt sie einmalig beim Prozessstart, sodass eine eingespielte Änderung eine laufende Seite erst beim nächsten Neustart erreicht. Das ist erwartet; es ist kein Grund, einen Server neuzustarten, den ein Betrachter gerade ansieht, und es IST der Grund, warum ein Bericht "meine Korrektur wurde nicht übernommen" normalerweise einen laufenden Prozess meint, der den alten Code hält.

Der Baum wird BEDIENT; die Logs werden ABGERUFEN

Dies ist die Form des Ganzen, und es ist eine Größenregel, kein Stil.

Ein Sitzungs-BAUM kostet etwa ein Kilobyte pro Agent, unabhängig davon, was dieser Agent getan hat. Seine LOGS sind unbegrenzt, und das Rendern eines Logs zieht seine Bilder mit ein. Jedes Agentenlog in jeden Snapshot zu packen, macht GET / für eine 228-Agenten-Sitzung 358 MB, davon 110 MB Base64-Bilddaten, die bei jeder 10-Sekunden-Abfrage erneut gesendet werden. Nichts ist kaputt, und die Seite kann nicht geöffnet werden.

Daher wird die Nutzlast aufgeteilt, und jede Route transportiert eine Sache:

RouteTransportiertKostet
GET /den gesamten Baum, das Log der ROOT-Sitzung und die Bilder dieses Inhalts inline als data-URIsein Dokument, vollständig ohne Server
GET /stateden gesamten Baum und KEINE Logs (?logs=<id>,<id> benennt Ausnahmen); ?view=active schränkt den GRAPHEN auf die aktive Ansicht einwas die 10-Sekunden-Abfrage zahlt, jedes Mal, für immer
GET /agent/<id>genau das gerenderte Log dieses Agentenein Abruf, wenn der Betrachter einen Block auswählt
GET /agent/<id>?since=<n>nur Ereignisse ab Index nein leeres Array für einen ruhigen Agenten
GET /img/<hash>ein Bild nach Content-Hash, immutableeinmal pro Bild pro Betrachter, dann gecached

Zwei Konsequenzen, die es wert sind, zu wissen, bevor Sie hier etwas ändern:

  • since indiziert den eigenen count des Baums. Eine Baumzeile trägt die tatsächliche Ereigniszählung des Agenten, auch wenn sein Log nicht gerendert wurde, und diese Zahl ist das, was ein Client als since zurückgibt. Beide stammen aus derselben Transkript-Analyse, und das müssen sie auch: Leiten Sie eine davon woanders ab und ein Client überspringt oder wiederholt still Ereignisse, mit nichts zu sehen außer einem Log, das subtil falsch ist.
  • GET /img/<hash> ist content-adressiert, daher wird eine Antwort für immer gecached. Deshalb ist ein unbekannter Hash ein 404 und niemals ein leerer 200 – ein leerer 200 ist ein dauerhaft defektes Bild für diesen Betrachter, und kein Neuladen behebt es.

Nichts wird rot, wenn diese Aufteilung verloren geht. Packen Sie jedes Log in GET /state oder betten Sie Bilder in jeden Snapshot ein, und der Baum ist richtig, die Beschriftungen sind richtig, die Seite rendert, und sie ist hunderte Megabyte groß. Die GRÖSSE ist die zu schützende Eigenschaft; die Größentests in serve.test.mjs (6-9) sind das Einzige, was einen Rückschritt sehen kann, und jeder ist ein PAAR – hier abwesend, dort vorhanden – weil "X ist abwesend" sonst aus allen falschen Gründen wahr ist.

Was eine GESPEICHERTE Seite hat, und was nicht

GET / ist ein einzelnes eigenständiges HTML-Dokument: Inline-CSS und -JS, keine externen Assets, und ein eingebackener Snapshot, der ohne Server rendert. Eingebacken sind der gesamte Baum und das Log der Root-Sitzung, mit den Bildern dieses Inhalts als data-URIs.

Nicht eingebacken: jedes andere Agentenlog. Diese sind nicht im Dokument; sie treffen bei Auswahl von /agent/<id> ein. Daher zeigt eine auf Disk gespeicherte Seite oder eine, deren Server gestorben ist, den vollständigen Graphen und das Log der Root-Sitzung, und zeigt nichts, wenn auf einen Unteragentenblock geklickt wird. Die Bilder innerhalb dieser Logs sind die gleiche Geschichte: sie rendern von /img/<hash> und es gibt keinen Server hinter einer gespeicherten Datei.

Für einen eingefrorenen Datensatz ALLER Logs in einer Datei verwenden Sie build.mjs – der statische Bake bettet jedes Agentenlog und jedes Bild ein, weil ein Andenken nichts abrufen muss. Das ist der eine Aufrufer, der snapshot() nach allen Logs fragt (logsFor: null).

Wie es Sitzungen folgt

Ein Server ist langlebig; eine Claude-Sitzung nicht: Das Beenden einer Sitzung, ein Update und ein Unit-Neustart erzeugen alle eine neue Sitzungs-ID. Daher wählt der Server bei jedem Snapshot erneut die zuletzt aktive Sitzung aus, anstatt eine beim Start aufzulösen, und zeigt eine "Warte auf eine Sitzung..."-Seite, solange keine existiert – der normale Zustand beim Booten. "Zuletzt aktiv" umfasst das ROOT-Transkript, nicht nur Unteragenten-Dateien: Eine frisch gestartete Sitzung, die noch nicht delegiert hat, wird sofort verfolgt und als Root-only-Baum gerendert, und der Spawn-Baum wächst ab ihrer ersten Delegation. Nichts muss neugestartet werden, wenn eine neue Sitzung beginnt; die Seite erkennt die ID-Änderung und baut ihre Logs neu auf.

--session=<id> fixiert stattdessen eine Sitzung (eine eingefrorene Ansicht zum Debuggen).

Jeder Projekt-Slug unter dem Konfigurationsverzeichnis wird gescannt, nicht nur einer. Das Transkript einer Sitzung landet unter dem Projekt-Slug ihres CWD, daher schreibt eine Sitzung, die in einem Git-Worktree arbeitet, unter dem Slug des WORKTREE; das Fixieren eines Slugs macht jede Worktree-Sitzung unsichtbar, während die Seite dennoch perfekt rendert.

Welchen Agenten eine Seite anzeigt

Der Root-Block ist mit dem Fleet-Mitglied beschriftet, zu dem das Konfigurationsverzeichnis gehört, aufgelöst über config.json's fleet.by_config_dir. Der Schlüssel wird abgeleitet, nicht der rohe Basisname, und die Ableitung ist der Teil, der lesenswert ist:

fleetKey() in format.mjs nimmt den Basisnamen des Konfigurationsverzeichnisses, AUSSER wenn dieser Basisname das wörtliche .claude ist, wo es stattdessen den Basisnamen des ÜBERGEORDNETEN Verzeichnisses nimmt. Ein Fleet, das jedem Agenten ein Home-Verzeichnis gibt, platziert jedes Konfigurationsverzeichnis unter <home>/<agent>/.claude, sodass alle nach Basisname auf einen Eintrag kollidieren und die Seite jedes Agenten mit dem Agenten beschriftet ist, den dieser Eintrag benennt.

Dieser Fehler ist still: Die Seite rendert vollständig und sagt den falschen Namen, und ein Betrachter mit zwei geöffneten Seiten kann sie nicht unterscheiden und weiß nicht, dass er das nicht kann. Ein wörtlicher .claude-Eintrag wird als Fallback berücksichtigt, sodass eine Einzelagenteninstallation, die als .claude geschlüsselt ist, ihr Label behält; sie kann zwei Agenten nicht unterscheiden und sollte nicht verwendet werden, wo mehr als einer existiert. Derselbe abgeleitete Schlüssel wählt den Standardport aus config.json's servers, aus demselben Grund: Ein Basisnamen-Match würde jedem Agenten den Port des ersten Eintrags geben.

Dateien

  • serve.mjs – der Live-Server: GET /, GET /state, GET /agent/<id> und GET /img/<hash> (siehe die Routentabelle oben). Das primäre Werkzeug. --port=N, --host=<addr>, --cap=KB, --session=<id>, jedes mit Standardwerten aus config.json; --help gibt die Nutzung, den aufgelösten Konfigurationspfad, das Transkript-Root und den Standardport aus und bindet nichts. --port=0 nimmt einen flüchtigen Port und das Banner gibt den tatsächlich erhaltenen aus.
  • format.mjs – der gemeinsame Formatierer: Transkript-Analyse, Spawn-Baum-Layout, HTML pro Ereignis, das Theme-CSS und der eine Ort, an dem lokale Werte gelesen werden (cfg() über die config.json, die fleet where nennt, oder über WEB_HIERARCHY_CONFIG, wenn diese gesetzt ist).
  • build.mjs – optionaler STATISCHER Snapshot (node build.mjs <out.html>): backt den aktuellen Zustand in eine einzige eigenständige Datei für einen eingefrorenen, teilbaren Datensatz. Verwenden Sie dies für ein Andenken, nicht für die Live-Ansicht.
  • identities.json + avatars/ – die Anzeigenamens-Liste, verwendet, es sei denn, identities.file nennt eine andere.
  • serve.test.mjs + fixtures/ – der Test: Er startet einen echten Server auf einem flüchtigen Port gegen einen synthetischen Transkriptbaum, behauptet den Baum, treibt jede Route (Baum ohne Logs, Log eines Agenten und niemandes sonst, ?since liefert nur das Neue, ein Bild wird Byte für Byte per Hash ausgeliefert), und LÖSCHT dann das Transkript eines Unteragenten und verlangt, dass der Baum UND die agentenspezifische Route es verlieren. Sein Header gibt an, warum jedes Bein da ist, welches Paar jedes Größenbein die Hälfte von ist und warum die Negativkontrolle der tragende Teil ist. Bein 11 verlangt, dass GET /state?view=active den gestoppten Block fallen lässt, den die Standardansicht zeichnet.
  • view.test.mjs – die "nur aktive"-Regel für sich allein (filterView() über einen synthetischen Baum): lebende Blöcke, die Blöcke darüber und der Root, mit den gestoppten Geschwistern und Kindern daneben fallen gelassen, und "alle" gibt jede Zeile unverändert zurück.
  • format.test.mjs – wie die Konfiguration gefunden und gelesen wird: durch fleet where, wenn WEB_HIERARCHY_CONFIG nicht gesetzt ist (ein gefakter Fleet, ausgewählt durch FLEET_BIN), die gewinnende Variable, ein fehlschlagender Fleet wird weitergegeben, nicht geraten, relative Pfade, die Listenvorgabe und packages.dir_pattern.
  • installation/ – die Unit-Vorlage, das Installationsverfahren und check.

Was die Seite zeigt

Sie liest sich wie die Claude Code-Konsole im dunkelfarbenblinden (dunkel-daltonisierten) Theme – Palette wörtlich aus dem Claude Code-Bundle übernommen (schwarzer Hintergrund, weiße Monospace-Schrift, Werkzeug-Aufzählungspunkte in Erfolgsblau #3399ff, Fehler #ff6666, gedimmte Ergebniszeilen mit "... +N Zeilen"-Expandern, Denkmarkierungen, Aufgaben- und Benutzernachrichten wie Konsoleneingabe eingerahmt).

  • Spawn-Baum-Graph. Die Root-Sitzung ist der oberste Knoten; jeder elternlose Agent hängt an ihr. Blau = aktiv (mit einem rotierenden Spinner-Bogen neben dem Zeitbereich; statisch unter prefers-reduced-motion), grau = gestoppt. Klicken Sie auf einen Block, um das vollständige Log dieses Agenten unten anzuzeigen.
  • Ein "Alle / nur aktive"-Schalter über dem Graphen. "Alle" ist die Vorgabe und zeichnet jeden Block. "Nur aktive" zeichnet die blauen Blöcke, jeden Block über einem von ihnen und den Root: Das Aktiv-Flag ist das, das einen Block färbt, daher ist der Schalter nie anderer Meinung als die Farben. Die Blöcke darüber bleiben, weil ein Elternteil, das auf ein langes Kind wartet, nichts in sein eigenes Transkript schreibt und als gestoppt gelesen wird, während das Kind läuft; ohne sie würde das Kind an nichts hängen. Wenn nichts aktiv ist, ist die Ansicht nur der Root. Der Schalter schränkt nur den GRAPHEN ein: Der Stempel zählt immer noch die gesamte Sitzung (plus "N angezeigt"), und ein From-Link oder #a-<id> zu einem versteckten Agenten öffnet immer noch sein Log. Die Auswahl wird pro Betrachter im localStorage des Browsers gespeichert; wo der Speicher blockiert ist, funktioniert der Schalter für diesen Besuch trotzdem. filterView() in format.mjs ist die Regel; view.test.mjs fixiert sie.
  • Aufgabenbeschreibung pro Agent auf jedem Block. Wahrheitsquelle: die description des aufrufenden Agent-Aufrufs, die das Harness in agent-<id>.meta.json kopiert. Fallback bei Fehlen: die erste Zeile der Aufgabenaufforderung des Agenten. Die Beschreibung umbricht und der Block wächst, um sie aufzunehmen.
  • Die letzte Logzeile auf jedem Block als Hover-Tooltip. Bei ein paar hundert Agenten ist es das einzige, was zwei Blöcke mit demselben Typ und derselben Beschreibung trennt, und für einen aktiven Agenten ist es das, was er gerade tut. Es reist im BAUM, weil es der eine Log-Inhalt ist, der günstig genug ist: Klartext, begrenzt, niemals HTML – sobald es Markup rendert, zieht es Bilder und Expander in jeden Snapshot.
  • Modell auf jedem Block. parseTranscript liest das Modell aus den Assistant-Turns des Transkripts (das claude--Präfix entfernt); wenn der Agent noch nicht geantwortet hat, steht der grobe Alias in seiner .meta.json als Ersatz. Ein Knoten mit keinem von beidem rendert keine Zeile anstelle eines Platzhalters.
  • Benannte Agenten mit Avataren. Jeder Block zeigt einen Namen, seinen Avatar, den agentType, die Beschreibung, das Modell und den Zeitbereich, sodass fünf gleichzeitige Agenten eines Typs auf einen Blick unterscheidbar sind.
    • Der Name ist ein reines Anzeigelabel: zugewiesen aus der Liste bei identities.file als reine Funktion von (agentType, Spawn-Reihenfolge innerhalb dieses Typs), neu berechnet aus den Transkripten bei jeder Aktualisierung. Nichts wird irgendwo gespeichert, und die Agenten kennen ihre eigenen Namen nicht – das Harness besitzt die Transkripte und dieser Formatierer ist ein reiner, schreibgeschützter Beobachter.
    • Spawn-Reihenfolge, nicht ein Hash der Agenten-ID: Bei N Namen und N Agenten kollidiert ein Hash durch das Taubenschlagprinzip; die Spawn-Reihenfolge kann nicht kollidieren.
    • names-Reihenfolge ist lasttragend – nur anhängen. Das Neuordnen oder Einfügen in der Mitte benennt still jede Grafik vergangener Sitzungen um.
    • avatar = die Silhouette der Rolle; portraits = optionale, maßgeschneiderte Kunst für einen Namen. Der Avatar identifiziert

Verwandte Skills