Arbeitsprobe · Lab (lokal)

Den Agenten beim Arbeiten zusehen.

agent-observatory ist ein Echtzeit-Kontrollraum für Claude-Code-Sessions und ihre Subagenten. Hook-Events laufen über einen lokalen FastAPI-Server in SQLite und erscheinen im Browser als Live-Swimlanes — eine Spur pro Session, eine pro Subagent, jeder Tool-Aufruf ein eigener Chip. Was vorher nur in fragmentierten Terminal-Logs sichtbar war, wird zum nachvollziehbaren, replaybaren Ereignisstrom.

agent-observatory — Live-Kontrollraum für Multi-Agent-Sessions.

Für die Rolle: belegt Observability- und Tooling-Engineering für Multi-Agent-Systeme — eine vollständige Event-Pipeline (Hook-Capture → Ingest → Persistenz → Live-Visualisierung), gebaut stdlib-nah und fail-soft, damit das Beobachten den beobachteten Agenten nie ausbremst. Relevant für AI-Infrastruktur-, Developer-Tooling- und Observability-Rollen.

Echtzeit-ObservabilityClaude-Code-HooksFastAPI · WebSocketSQLiteSwimlanes + ReplayLokal · fail-soft

Reine Lab-Arbeit, lokal. Der Code läuft nur auf 127.0.0.1 — kein öffentliches Repository, keine Live-URL. Mechanik und Stand sind hier dokumentiert, ein Walkthrough ist auf Anfrage möglich.

Warum gebaut?

Agentenarbeit ist unsichtbar — bis man sie zum Kontrollraum macht.

Subagenten und Hook-Aktivität tauchen normalerweise nur verstreut in Terminal-Logs auf: man sieht, dass etwas passiert, aber nicht das Wer/Wann/Womit als zusammenhängenden Strom. Erfasst werden die ohnehin gefeuerten Hook-Typen — PreToolUse, PostToolUse, SubagentStop, UserPromptSubmit, Stop und SessionStart.

agent-observatory baut daraus einen einzigen lokalen Ereignisstrom: welche Session welches Tool nutzte, welche Subagenten parallel liefen, wann ein Aufruf begann und endete. Eine Lane je Session bzw. Subagent, und alles wird persistiert — für eine spätere Analyse oder einen Replay.

Und ehrlich eingeordnet: das Ganze ist bewusst klein und lokal gehalten. Kein externer Dienst, kein Deployment — ein Diagnose- und Erzählwerkzeug, das Agentenarbeit aus der Black Box holt, nicht ein Produkt.

Das visuelle Herzstück

Vier Spuren, ein Live-Strom von Tool-Events.

So sieht ein Lauf aus: oben die Hauptsession, darunter die Subagenten — auf jeder Spur laufen Tool-Chips zeitlich versetzt ein. Ein Chip pulsiert, solange der Aufruf läuft (PreToolUse), und schließt, sobald er fertig ist (PostToolUse). Die Demo unten nutzt ausschließlich synthetische, anonymisierte Events.

Live-Swimlanes · Tool-Event-Strom synthetisches Beispiel
session · mainHauptagent
general-purpose:a4f…Subagent
general-purpose:9c1…Subagent
general-purpose:7e2…Subagent
Offener Chip (pulsierend): ein Tool-Aufruf läuft gerade — das PreToolUse-Event ist eingetroffen, das PostToolUse noch nicht.
Geschlossener Chip (gedimmt): der Aufruf ist fertig. Eine Spur je Session bzw. Subagent, ein Chip je Tool-Aufruf.

Rein synthetische, anonymisierte Events · keine echten Hook-Daten, IDs redigiert · Chips zeigen nur Tool-Namen

Die Pipeline

Von Hook-Event zu sichtbarer Spur — in einem geraden Lauf.

Bewusst klein und lokal: ein stdlib-only Hook-Sender liest die Payload von stdin, kürzt sie, leitet die Lane ab und schickt sie per HTTP an einen FastAPI-Server. Der normalisiert, schreibt nach SQLite und broadcastet live über WebSocket. Das Dashboard ist rein clientseitig. Kein externer Dienst, kein Auth, nur 127.0.0.1.

Pipeline · Hook-Event → sichtbare SpurLokal · 127.0.0.1
POST /eventingestpersistbroadcastClaude CodeSession + Subagentensend_event.pystdlib · fail-soft · 250 msFastAPI /event127.0.0.1:7171SQLitelazy _db_path · Ring 200DashboardWebSocket /stream+ WS-Snapshot beim ConnectReplayGET /api/runsLauf-Liste/runs/{id}/events→ Scrubber
Ein gerader Lauf: der Hook liest die Payload von stdin, kürzt sie, leitet die Lane ab und schickt sie per HTTP an den Server — der normalisiert, persistiert und broadcastet live.
Replay aus SQLite: gespeicherte Läufe lassen sich über die Run-Endpunkte zurückspielen und in einem Scrubber durchscrubben — kein externer Dienst, kein Auth, nur Loopback.
Die Kerneinsicht

Wie man Subagenten überhaupt auseinanderhält.

Der nicht offensichtliche Teil: alle Events eines Session-Baums teilen dieselbe session_id. Der einzige Diskriminator für einen Subagenten ist das Feld agent_id (beim Hauptagenten abwesend), agent_type liefert das lesbare Label. Der naive Default 'eine Lane pro session_id' hätte alle Subagenten in eine Spur kollabiert. Empirisch verifiziert an echten Capture-Payloads.

/01

Hauptagent → (session_id, None)

Der Hauptagent trägt kein agent_id-Feld. Genau diese Abwesenheit macht ihn erkennbar — er bekommt seine eigene Top-Lane.

/02

Subagent → (agent_type:agent_id, session_id)

Beim Subagenten ist agent_id der einzige Diskriminator, agent_type liefert das lesbare Label. Die geteilte session_id wird zum Parent.

/03

Eine Stelle, ein Ground-Truth

derive_lane ist die einzige Stelle für Lane-Anpassungen — kalibriert gegen die offizielle Hooks-Dokumentation, nicht gegen Vermutungen.

/04

Phase 0 empirisch abgeschlossen

Ein realer general-purpose-Subagent wurde gespawnt; agent_id und agent_type erschienen in PreToolUse UND SubagentStop. Der Capture-Hook fing nebenbei alle parallel laufenden Sessions ein.

Beobachten ohne zu stören

Der Beobachter darf den Beobachteten nie ausbremsen.

Ein Observability-Tool, das den überwachten Agenten blockieren oder crashen kann, ist schlimmer als keins. Der Hook-Sender ist deshalb durchgehend fail-soft ausgelegt: läuft der Server nicht, arbeitet Claude Code einfach weiter. Diese Entscheidungen sind die eigentliche Engineering-Substanz.

Fire-and-forget POST

Kein raise, kein stdout — bei jedem Fehler beendet der Sender mit exit 0. Der beobachtete Agent merkt nichts.

250 ms Timeout

Der Sender wartet nie lange auf den Server. Läuft der nicht, arbeitet Claude Code einfach weiter.

Payload-Truncation im Sender

Strings >500 Zeichen, Dicts >20 Keys und Listen >20 Items werden gekürzt — Schutz vor riesigen Payloads.

stdlib-only

Der Hook hat keine externen Dependencies und kann in jeder Claude-Umgebung laufen, ohne etwas zu installieren.

Test-DB-Isolation + Poison-Guard

conftest.py lenkt AGENT_OBS_DATA_DIR auf eine Temp-DB; ein Poison-Guard prüft nach jedem Test gegen die echte data/events.db.

Ehrlicher Stand

Live — aber lokal, ohne offenen Code.

Das System läuft lokal auf 127.0.0.1:7171, mit grünen Tests und empirisch verifizierter Lane-Logik. Es ist bewusst Lab-Arbeit: rein lokal, kein öffentliches Repository, keine Live-URL. Was läuft, läuft echt; was offen ist, steht hier offen.

32

Tests grün — Hook-Sender, Ingest, DB, Hub, Stream, Lane-Logik, Poison-Guard

~150 ms

Latenz, bis ein neuer Tool-Call im Dashboard erscheint

250 ms

Hook-Timeout — fire-and-forget, exit 0 bei Fehler

200

Events im In-Memory-Ring-Buffer (Snapshot für neue Verbindungen)

7171

Port — gebunden ausschließlich an 127.0.0.1, kein Public-Zugang

Phase 0

empirisch abgeschlossen — derive_lane gegen echte Subagent-Payloads verifiziert

Offen / Lab
  • Multi-Session-Parallel-Capture sauber trennen — der globale Hook fängt schon heute alle laufenden Sessions ein.
  • Automatisierter Lane-Diskriminator und lesbarere Lane-Labels (last_assistant_message als Kandidat).
  • Live-E2E-Smoke bewusst deferred.

Kein Repo-Link, keine Live-URL. Anders als dual-bridge ist dieser Code nicht öffentlich. Ein Walkthrough ist auf Anfrage möglich.

Walkthrough auf Anfrage

Welchen Teil des Kontrollraums soll ich zeigen?

Die Hook-Pipeline, die Lane-Logik, der fail-soft Sender — wenn dich ein Mechanismus interessiert, schreib mir. Der Code ist lokal, ein Walkthrough ist jederzeit möglich.