Derselbe Agent hatte vier Identitäten
Die Identität eines Agents lag an vier Stellen, jede eine eigene Schreibroute, keine glich sich mit den anderen ab. Sie drifteten auseinander. Eine Notiz darüber, wie aus einer Identität vier wurden — nicht durch Design, sondern durch Anbauen — und was es brauchte, um den Drift überhaupt sichtbar zu machen.
Ein Agent in meinem Setup hat eine Mailadresse als Identität. Diese Adresse liegt an vier Stellen. Und an einem bestimmten Tag stimmten die vier nicht mehr überein.
Die vier Stellen
Der IdP vergibt die Adresse — er ist die Quelle. Von dort wird sie kopiert: in die Troop-Registry, in der die Agents als laufende Instanzen stehen. In die Org, die festhält, welcher Agent zu welcher Organisation gehört und wem er berichtet. Und in den Task-Tracker, der Agents zu Teams zuordnet.
Vier Stores, vier Mal dieselbe Adresse als Schlüssel. Dazu kommt eine fünfte, lokale Stelle — das Home-Verzeichnis des Agents, in dem seine eigene auth.json liegt —, aber die ist privat und nicht von außen abfragbar. Die vier sind das Problem.
Keiner dieser vier Stores zeigt auf einen gemeinsamen Datensatz. Jeder hat seine eigene Zeile mit der Adresse als Text. Es gibt keinen Fremdschlüssel, keinen Trigger, keine Synchronisation zurück zur Quelle. Jeder Dienst schreibt einmal, und danach lebt seine Kopie ihr eigenes Leben.
Wie sie auseinanderlaufen
Das Symptom war ein Agent, der in einem Task-Team stand, aber in der Troop-Registry fehlte. Er war einem Team zugeordnet worden, aber als laufende Instanz gab es ihn nicht — eine verwaiste Mitgliedschaft. Aus der anderen Richtung dasselbe: ein Agent in der Org, der nie in der Troop ankam.
Solche Lücken entstehen, weil die vier Schreibrouten unabhängig sind. Ein Agent wird in die Org eingeladen, ohne dass jemand prüft, ob er in der Troop existiert. Er wird einem Team zugewiesen, ohne Abgleich. Jeder einzelne Schritt ist für sich korrekt. Nur die Summe ergibt einen Agent, der je nachdem, wen man fragt, existiert oder nicht.
Dazu kommt, dass dieselbe Adresse in verschiedenen Stores verschieden geschrieben sein kann — Groß-/Kleinschreibung, die Kodierung der Owner-Domain, ein Hash-Suffix, das einen Agent von einem gleichnamigen unterscheidet. Wenn der Vergleich auf dem rohen String läuft, sind zwei Schreibweisen derselben Identität plötzlich zwei Identitäten.
Was es brauchte, um es zu sehen
Bevor ich irgendetwas reparieren konnte, musste ich den Drift überhaupt erkennen können. Und das fängt damit an, eine Agent-Adresse kanonisch zu lesen.
Eine Agent-Adresse hat eine Struktur: ein Agent-Name, optional ein Hash, der den Owner-Bezug eindeutig macht, dann der Owner selbst, kodiert, dann der IdP. Aus name+owner+owner_domain@id.openape.ai wieder den Agent-Namen und die echte Owner-Mail herauszuholen, ist nicht trivial — die Domain ist kodiert, der Hash muss abgeschnitten werden, alles case-insensitiv. Diese Logik lag vorher in der Troop, als lokale Hilfsfunktion. Ich habe sie nach @openape/core gezogen, an eine Stelle, die alle benutzen: ein Parser, eine Wahrheit darüber, was eine Agent-Adresse bedeutet.
Darauf sitzt ein Reconcile. Es nimmt die Listen aus Org, Troop und Tasks, parst jede Adresse mit demselben Parser, wirft die Menschen heraus — Adressen, die nicht der Agent-Konvention folgen — und legt die Reste übereinander. Für jeden Agent steht dann da, in welchen Stores er vorkommt. Wer in Org und Troop steht, ist stimmig. Wer nur in einem steht, driftet. Daneben ein kleines Diagnose-Skript, das genau das ausgibt: eine Tabelle mit Häkchen pro Store und der Liste der Verwaisten am Ende.
Das Reconcile repariert nichts. Es zeigt nur. Welche Richtung richtig ist — den fehlenden Agent nachspawnen, oder die tote Mitgliedschaft entfernen —, entscheide ich, nicht das Skript.
Warum aus einer Identität vier wurden
Nicht, weil ich vier wollte. Sondern weil die Dienste nacheinander dazukamen. Erst gab es den IdP und die Troop — eine Adresse, eine Kopie. Dann kam die Org und brachte ihre eigene Tabelle mit. Dann die Tasks. Jeder neue Dienst hat die Identität nach unten kopiert, weil das der einfachste Weg war: keine Abhängigkeit auf einen fremden Store, keine Migration, jeder Dienst bleibt für sich deploybar. Der Preis steht erst am Ende da, wenn vier Kopien existieren und keine die anderen kennt.
Das ist kein dummer Fehler, es ist ein verständliches erstes Muster. Identität nach unten kopieren funktioniert, solange es zwei Stellen sind. Bei vier driftet es, wenn nichts sie zurückführt.
Schluss
Der Bug war nicht eine kaputte Zeile. Es war eine Architektur ohne Abgleich, die sich erst zeigte, als genug Stellen dieselbe Sache unabhängig festhielten. Was ich gebaut habe, ist kein automatischer Fix — es ist die Fähigkeit, den Drift zu sehen: ein kanonischer Parser, ein Reconcile, ein Diagnose-Skript. Das Reparieren bleibt eine Entscheidung. Aber man kann nichts entscheiden, was man nicht sieht.