Reverse Engineering von Claude Code Agent Teams: Architektur und Protokoll

Maschinelle Übersetzung Originalartikel.

Banner for Reverse Engineering von Claude Code Agent Teams: Architektur und Protokoll

Inhalt

Einleitung

Claude Code (v2.1.47) enthält eine experimentelle Funktion namens Agent Teams: Mehrere Claude-Code-Sitzungen koordinieren gemeinsame Arbeit in einer Topologie aus Teamleitung und Teammitgliedern. Ich entwickle Hive, einen Orchestrator für mehrere Coding-Agenten mit ähnlichen Zielen, aber einer ganz anderen Architektur. Deshalb wollte ich verstehen, wie der Ansatz von Anthropic unter der Haube funktioniert.

Dieser Artikel dokumentiert, was ich auf folgenden Wegen herausgefunden habe:

  1. Lesen der offiziellen Dokumentation
  2. Untersuchen tatsächlicher Artefakte, die frühere Teamsitzungen auf dem Datenträger hinterlassen haben
  3. Claude die Binärdatei von Claude Code (v2.1.47) auf Implementierungsdetails analysieren lassen (ha!)

Inhaltsverzeichnis

1. Architektur im Überblick

Ein Agententeam besteht aus vier Komponenten:

Komponente Rolle
Teamleitung Die Hauptsitzung von Claude Code, die das Team erstellt und Teammitglieder startet
Teammitglieder Separate Claude-Code-Instanzen mit jeweils eigenem Kontextfenster
Aufgabenliste Gemeinsame Arbeitsaufträge, die als einzelne JSON-Dateien auf dem Datenträger gespeichert werden
Postfach Eigene Posteingangsdateien pro Agent für die Nachrichtenzustellung

Die gesamte Koordinationsschicht ist dateibasiert. Das Dateisystem unter ~/.claude/ bildet die einzige Koordinationsgrundlage:

~/.claude/
├── teams/{team-name}/
│   ├── config.json                  # team membership registry
│   └── inboxes/{agent-name}.json    # per-agent mailbox
└── tasks/{team-name}/
    ├── .lock                        # flock() for concurrent task claiming
    ├── .highwatermark               # auto-increment counter
    ├── 1.json                       # individual task files
    ├── 2.json
    └── ...

Dies ist ein grundlegend dezentrales Design. Die Teamleitung ist einfach eine weitere Claude-Sitzung mit zusätzlichen Werkzeugen (TeamCreate, TeamDelete, SendMessage). Es gibt keinen Hintergrundprozess. Die Koordination ergibt sich aus dem gemeinsamen Dateizugriff.

Wenn du Claude in einer aktiven Sitzung bittest, ein Team für eine Aufgabe zu starten, und anschließend Folgendes in einem anderen Fenster ausführst, kannst du die Änderungen im Dateisystem in Echtzeit beobachten.

watch -n 0.5 'tree ~/.claude/teams/ 2>/dev/null; echo "---"; tree ~/.claude/tasks/ 2>/dev/null'

Zum Beispiel mit folgendem Prompt:

can you spawn an agent team to examine this codebase?
  - have one look for bugs
  - have one look for complexity
  - have one look for good things to call out and play devil's advocate against the other two agents

Dabei habe ich Folgendes beobachtet:

teams
└── code-review
    ├── config.json
    └── inboxes
        ├── bug-hunter.json
        ├── complexity-analyst.json
        ├── devils-advocate.json
        └── team-lead.json

Teamkonfiguration

Die Teamkonfiguration unter ~/.claude/teams/{team-name}/config.json enthält ein members-Array, das die Teammitglieder lesen, um einander zu finden:

{
  "members": [
    { "name": "team-lead", "agentId": "abc-123", "agentType": "leader" },
    {
      "name": "researcher",
      "agentId": "def-456",
      "agentType": "general-purpose"
    }
  ]
}

Namen sind der primäre Adressierungsmechanismus (UUIDs existieren, werden aber nicht für das Routing verwendet). Für sämtliche Nachrichten und Aufgabenzuweisungen wird das Feld name verwendet.

2. Die gemeinsame Aufgabenliste

Dateiformat

Jede Aufgabe wird als einzelne JSON-Datei in ~/.claude/tasks/{team-name}/ gespeichert. Hier ist ein echtes Beispiel aus einer früheren Sitzung:

{
  "id": "1",
  "subject": "Hunt for bugs across the codebase",
  "description": "...",
  "activeForm": "Hunting for bugs",
  "owner": "bug-hunter",
  "status": "completed",
  "blocks": [],
  "blockedBy": []
}

Aufgabenschema:

Feld Typ Beschreibung
id string Numerische ID, automatisch über .highwatermark hochgezählt
subject string Titel in Befehlsform (z. B. "Tests ausführen")
description string Detaillierte Anforderungen und Akzeptanzkriterien
activeForm string Verlaufsform für die Aktivitätsanzeige ("Tests werden ausgeführt")
status string pending → in_progress → completed (oder deleted)
blocks string[] IDs der Aufgaben, die diese Aufgabe blockiert
blockedBy string[] IDs der Aufgaben, die abgeschlossen sein müssen, bevor diese Aufgabe beginnen kann

Nebenläufigkeitskontrolle

Zwei spezielle Dateien ermöglichen die Koordination:

  • .lock: Eine 0-Byte-Datei für gegenseitigen Ausschluss auf Dateisystemebene (flock()). In allen 42 Aufgabenverzeichnissen vorhanden, die ich auf meinem Rechner vorgefunden habe.
  • .highwatermark: Enthält eine einzelne Ganzzahl (z. B. "3", "13"). Die nächste verfügbare Aufgaben-ID für die automatische Hochzählung.

Aufgabenübernahme

Die Aufgabenübernahme verwendet Dateisperren, um Race Conditions zu verhindern. Teammitglieder bevorzugen Aufgaben mit der niedrigsten ID. Eine Aufgabe mit einem nicht leeren blockedBy-Array kann erst übernommen werden, wenn sich alle blockierenden Aufgaben in einem Endzustand befinden.

Beobachtung: Die meisten Aufgabenverzeichnisse sind leer

Von 42 Aufgabenverzeichnissen auf meinem Rechner enthielten nur 5 tatsächliche Aufgaben-JSON-Dateien. Die übrigen 37 enthielten nur .lock und .highwatermark. Das bedeutet vermutlich, dass Aufgaben nach ihrem Abschluss bereinigt werden oder dass dies Sitzungen waren, in denen Claude die interne Aufgabenliste (seit Einführung der Aufgabenlistenfunktion verfügbar) nutzte, ohne eine Aufteilung in Unteraufgabendateien vorzunehmen.

3. Kommunikation zwischen Agenten

Postfachmuster

Jeder Agent hat unter ~/.claude/teams/{team-name}/inboxes/{agent-name}.json eine Datei mit einem JSON-Array. Hier ist ein echter Posteingang aus einer früheren Sitzung, in der eine Teamleitung Arbeit an einen controlplane-agent verteilte:

[
  {
    "from": "team-lead",
    "text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Phase 2: Control-plane - remove participants/presence\",\"description\":\"Remove multiplayer code from the control-plane package...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"2026-02-18T02:37:16.890Z\"}",
    "timestamp": "2026-02-18T02:37:16.890Z",
    "read": false
  }
]

Beachte die JSON-in-JSON-Kodierung: Das Feld text ist ein JSON-String, der ein serialisiertes Nachrichtenobjekt enthält. Die äußere Hülle enthält die Felder from, text, timestamp und read.

Nachrichtentypen

Das Feld type innerhalb der Nutzlast text unterstützt:

Typ Richtung Zweck
task_assignment Teamleitung → Teammitglied Eine Aufgabe mit allen Details zuweisen
message beliebiger Agent → beliebiger Agent Direktnachricht an einen Empfänger
broadcast Teamleitung → alle Dieselbe Nachricht an jedes Teammitglied
shutdown_request Teamleitung → Teammitglied Geordnetes Herunterfahren anfordern
shutdown_response Teammitglied → Teamleitung Herunterfahren genehmigen oder ablehnen
plan_approval_request Teammitglied → Teamleitung Einen Plan zur Prüfung einreichen
plan_approval_response Teamleitung → Teammitglied Mit Feedback genehmigen oder ablehnen
idle_notification Teammitglied → Teamleitung Wird automatisch gesendet, wenn die Gesprächsrunde des Teammitglieds endet

Zustellungsmechanismus

Schreibpfad: Der Absender hängt einen neuen Eintrag an die JSON-Array-Datei im Posteingang des Empfängers an.

Lesepfad: Der Empfänger fragt seine eigene Posteingangsdatei regelmäßig ab. Neue Nachrichten werden als synthetische Gesprächsrunden eingefügt (sie erscheinen, als hätte ein Nutzer sie gesendet).

Broadcast: Schreibt buchstäblich dieselbe Nachricht in die Posteingangsdatei jedes Teammitglieds. Die Token-Kosten steigen linear mit der Teamgröße.

Die Kommunikation besteht lediglich aus dem Anhängen an Dateien und dem Lesen von Dateien. Die Latenz zwischen Versand und Empfang hängt vom Abfrageintervall des Empfängers ab.

Sichtbarkeit von Direktnachrichten zwischen Teammitgliedern

Wenn ein Teammitglied einem anderen eine Direktnachricht sendet, enthält die Leerlaufbenachrichtigung an die Teamleitung eine kurze Zusammenfassung. Dadurch erhält die Teamleitung Einblick in die Zusammenarbeit zwischen Teammitgliedern, ohne den vollständigen Nachrichteninhalt zu sehen.

4. Starten von Agenten und Lebenszyklus

Wie Teammitglieder erstellt werden

Jedes Teammitglied ist ein separater claude-CLI-Prozess. Die Teamleitung startet sie über das Werkzeug Task mit den Parametern team_name und name. Für den gestarteten Prozess werden Umgebungsvariablen gesetzt:

  • CLAUDE_CODE_TEAM_NAME: bei gestarteten Teammitgliedern automatisch gesetzt
  • CLAUDE_CODE_PLAN_MODE_REQUIRED: auf true gesetzt, wenn eine Plangenehmigung erforderlich ist

Kontextinitialisierung

Teammitglieder laden denselben Projektkontext wie jede neue Sitzung:

  • CLAUDE.md-Dateien aus dem Arbeitsverzeichnis
  • MCP-Server
  • Skills
  • Den Start-Prompt der Teamleitung

Der Gesprächsverlauf der Teamleitung wird NICHT übernommen. Jedes Teammitglied beginnt von vorn und hat nur den Start-Prompt als Kontext.

Interne Implementierung

Die Binäranalyse von Claude Code v2.1.47 zeigt, dass der Kontext der Teammitglieder über AsyncLocalStorage mit diesen Feldern verwaltet wird:

  • agentId, agentName, teamName
  • parentSessionId, color
  • planModeRequired

Wichtige interne Funktionen:

  • isTeammate() / isTeamLead(): Rollenerkennung
  • waitForTeammatesToBecomeIdle(): Synchronisationsprimitiv für die Teamleitung
  • getTeammateContext() / setDynamicTeamContext(): Verwaltung des Laufzeitkontexts

Leerlauferkennung

Nach jeder LLM-Gesprächsrunde wechselt ein Teammitglied automatisch in den Leerlauf und sendet eine idle_notification an die Teamleitung. Dies ist der normale Ruhezustand und kein Hinweis auf einen Fehler oder fehlenden Fortschritt. Eine Nachricht an ein Teammitglied im Leerlauf weckt es wieder auf (der nächste Abfragezyklus erfasst die Nachricht im Posteingang).

Protokoll zum Herunterfahren

  1. Die Teamleitung sendet shutdown_request an ein Teammitglied
  2. Das Teammitglied kann zustimmen (beendet sich geordnet) oder ablehnen (arbeitet mit einer Erklärung weiter)
  3. Die Teambereinigung über TeamDelete entfernt ~/.claude/teams/{team-name}/ und ~/.claude/tasks/{team-name}/
  4. Die Bereinigung schlägt fehl, wenn noch Teammitglieder aktiv sind; sie müssen zuerst heruntergefahren werden

Vererbung von Berechtigungen

Teammitglieder erben beim Start den Berechtigungsmodus der Teamleitung. Wenn die Teamleitung mit --dangerously-skip-permissions läuft, gilt das auch für alle Teammitglieder. Die Modi einzelner Teammitglieder lassen sich nach dem Start ändern, beim Start jedoch nicht individuell konfigurieren.

5. Qualitätskontrollen und Hooks

Agent Teams ist zur Durchsetzung von Qualitätsanforderungen in das Hook-System von Claude Code integriert:

TeammateIdle-Hook

Wird ausgelöst, wenn ein Teammitglied kurz davorsteht, in den Leerlauf zu wechseln. Exit-Code 2 gibt stderr als Feedback zurück und verhindert den Wechsel in den Leerlauf, sodass das Teammitglied weiterarbeitet.

{
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "my-project"
}

TaskCompleted-Hook

Wird ausgelöst, wenn eine Aufgabe als abgeschlossen markiert wird. Exit-Code 2 verhindert den Abschluss und gibt stderr als Feedback zurück.

{
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "my-project"
}

Dieser Hook wird in zwei Situationen ausgelöst: (1) wenn ein beliebiger Agent eine Aufgabe über TaskUpdate ausdrücklich als abgeschlossen markiert oder (2) wenn ein Teammitglied eines Agententeams seine Gesprächsrunde mit laufenden Aufgaben beendet.

Typen von Hook-Handlern

Typ Beschreibung
command Shell-Skript. JSON über stdin, Exit-Codes für Entscheidungen.
prompt LLM-Auswertung in einer Gesprächsrunde. Gibt {ok, reason} zurück.
agent Subagent mit Lesewerkzeugen und mehreren Gesprächsrunden. Bis zu 50 Runden.

6. Token-Ökonomie

Agententeams verbrauchen ungefähr 7× mehr Token als Standardsitzungen, wenn die Teammitglieder im Planmodus laufen. Jedes Teammitglied verwaltet als separate Claude-Instanz sein eigenes vollständiges Kontextfenster.

Basiswerte

  • Durchschnittliche Nutzung von Claude Code: ~6 $/Entwickler/Tag
  • Agententeams: zusätzlich zum Grundverbrauch ungefähr proportional zur Teamgröße

7. Zusammenfassung der Architektur

Dimension Claude Code Agent Teams
Koordinationsgrundlage Einfache Dateien (~/.claude/tasks/, ~/.claude/teams/)
Aufgabenformat Eine JSON-Datei pro Aufgabe + .lock für die Übernahme
Nachrichtenaustausch JSON-Posteingangsdateien (Anhängen + Abfragen)
Agentenlebenszyklus Sich selbst verwaltende CLI-Prozesse
Isolation der Arbeit Gemeinsames Arbeitsverzeichnis
Merge-Strategie Keine (Agenten bearbeiten Dateien direkt)
Wiederholungsversuche/Eskalation Manuell (Teamleitung entscheidet oder Nutzer greift ein)
Topologie Teamleitung + Teammitglieder auf einer Ebene, Peer-to-Peer-Nachrichten
Aufgabenplanung Eigenständige Übernahme (Teammitglieder nehmen sich die nächste Aufgabe)
Zustandspersistenz Nur Dateien; keine Wiederaufnahme von Teammitgliedern im selben Prozess
Qualitätskontrollen Shell-Hooks (TeammateIdle, TaskCompleted)
Token-Erfassung Nur pro Sitzung, keine agentenübergreifende Aggregation
Erkennung von Stillständen Manuell (Nutzer bemerkt den Stillstand eines Teammitglieds)
Nebenläufigkeitskontrolle Implizit (Teamgröße = Anzahl der Teammitglieder)
Abhängigkeitsmodell blocks/blockedBy in Aufgabendateien

Quellen

Offizielle Dokumentation

Artefakte auf dem Datenträger (Claude Code v2.1.47)

Beobachtet unter /Users/tau/.claude/:

  • Teamverzeichnisse mit config.json- und inboxes/{agent-name}.json-Dateien
  • Aufgabenverzeichnisse mit .lock, .highwatermark und einzelnen Aufgaben-JSON-Dateien
  • Beispiel einer Nachricht zur Aufgabenzuweisung von team-lead an cp-agent mit dem Zeitstempel 2026-02-18T02:37:16.890Z

Binäranalyse

Binärdatei von Claude Code v2.1.47. Durch Zeichenkettenanalyse identifizierte interne Funktionen: getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. Kontextfelder von AsyncLocalStorage: agentId, agentName, teamName, parentSessionId, color, planModeRequired.

Hive-Codebasis