Dev

Deine Agent-Konfiguration ist jetzt Infrastruktur

Sobald du ein paar Agents betreibst, hören ihre Definitionen auf, Konfiguration zu sein, die du bearbeitest, und werden zu Infrastruktur. Das Problem mit Infrastruktur ist: Sobald du sie nicht mehr im Kopf behalten kannst, driften ihr deklarierter Zustand und ihr laufender Zustand von selbst auseinander, und nichts meldet es. Die Lösung ist die Disziplin, die wir bei Servern gelernt haben: Deklariere die Menge als Dateien, vergleiche sie mit der Realität, halte fest, was du angewandt hast, und verweigere das Anwenden über eine hinter deinem Rücken passierte Änderung hinweg, statt einfach darüber hinweg zu konvergieren. Mit einem Haken, der leicht zu übersehen ist. Ein Reconciler erfasst immer nur das, was du zu deklarieren daran gedacht hast.

Ich führe ein Register der Agents, die ich betreibe, und ich habe es nicht immer generiert. Es fing als eine Liste an, die ich von Hand pflegte, und eine handgepflegte Liste hinkt dem hinterher, was existiert: Agents liefen wochenlang und schafften es nie in die Datei. Also ersetzte ich sie durch einen Generator, der das Register aus dem baut, was er auf der Platte findet, plus einer Prüfung, die nach Zeitplan läuft und mit einem Fehlercode endet, wenn die eingecheckte Datei nicht mehr passt. Ich habe zu lange gebraucht, um zu sehen, was ich da gebaut hatte: eine schlechte Neuerfindung von etwas, wofür ich jeden ausgelacht hätte, der es auf einem Server nachbauen würde. Die Lücke, auf die die Prüfung achtet, hat eine Disziplin weiter einen Namen. Es ist Drift.

Deine Agent-Konfiguration wurde zu Infrastruktur, und du hast es nicht bemerkt

Eine Weile lang ist ein Agent bloß Konfiguration. Einer, vielleicht zwei, ihre Definitionen ebenso im Kopf wie in irgendeiner Datei: Du bearbeitest einen Basis-Prompt, fügst einen Skill hinzu, erteilst ein Tool-Recht und hältst die ganze Gestalt auf einmal im Kopf. Nichts weicht ab, weil es nichts gibt, wovon es abweichen könnte. Die Datei ist der Agent.

Betreibe genug davon, und das hört auf. Die Definitionen werden zu einer Menge: ein Basis-Prompt oder ein CLAUDE.md pro Agent, die Skills, die er lädt, sein Memory-Store, die Tools, die er aufrufen darf, das Modell, auf das er gepinnt ist, die Umgebung, in der er läuft. Die Menge ist in Dateien deklariert, und sie hat ein laufendes Gegenstück. In dem Moment, in dem das zwei Dinge sind statt einem, bearbeitest du keine Konfiguration mehr, du betreibst Infrastruktur. Natürlich kann auch Konfiguration von dem abweichen, was läuft; eine bearbeitete nginx.conf, die nie neu geladen wurde, ist genau das. Der Unterschied ist nicht die Möglichkeit, es ist der Normalfall. Unterhalb einer bestimmten Größe merkst du es, weil du beide Hälften hältst. Jenseits des Punktes, an dem du das nicht mehr kannst, hört die Abweichung auf, ein Ereignis zu sein, das du erwischst, und wird zum Ruhezustand, den nichts meldet.

Drift ist der Fehler, nicht irgendeine einzelne Änderung

Ein Agent verhält sich mitten in einem Job daneben, also greifst du in die laufende Instanz und korrigierst seine Anweisungen, um die Blutung zu stoppen, und schreibst die Änderung nie zurück. Ein Memory-Store wird von Hand bearbeitet, um einen schlechten Eintrag zu entfernen. Eine lang laufende Session hat ihre Anweisungen beim Start geladen, und dann hast du die Datei bearbeitet, also hat sich die deklarierte Version bewegt und die laufende nicht. Keine davon ist eine falsche Handlung. In ein akutes Problem einzugreifen, ist oft die richtige Entscheidung. Der Fehler ist, dass danach das, was du deklariert hast, und das, was läuft, nicht übereinstimmen, und die Nichtübereinstimmung kündigt sich niemandem an.

Wir haben gelernt, Server nicht mehr so zu betreiben. Der Snowflake-Server, einmal von Hand aufgesetzt und über Jahre im Betrieb gepatcht, bis ihn niemand mehr nachbauen konnte, existierte nur in seinem laufenden Zustand und wurde nie aufgeschrieben. Wir haben entschieden, dass das für Server nicht hinnehmbar ist. Wir haben angefangen, Agents genau so zu betreiben, und es Iteration genannt.

Das ist eine gelöste Disziplin, ein Substrat weiter

Die Server-Welt hat das nicht mit einer klügeren Kiste gelöst. Sie hat es mit einer Disziplin gelöst, langweilig einheitlich über die Werkzeuge hinweg, die sie umsetzen. Terraform und Pulumi führen eine State-Datei und zeigen einen Plan, bevor sie irgendetwas anfassen, sodass eine Änderung als Diff geprüft wird, statt als Überraschung aufzutauchen. Eine Kubernetes-Reconcile-Schleife treibt den laufenden Zustand auf den deklarierten Zustand zu. Ansible setzt auf idempotente Module, sodass ein gutes Playbook erneut läuft und konvergiert. Der gemeinsame Nenner darunter: Deklariere die gewünschte Menge als Dateien, vergleiche sie mit der Realität und konvergiere gegen das, was du deklariert hast. Reconcile ist weder clever noch neu. Neu ist, dass die Agent-Konfiguration groß genug geworden ist, um in die Kategorie zu gehören, und die meisten, die Agents betreiben, haben sie nicht dorthin bewegt.

Was der Anbieter niedergeschrieben hat

Anthropics ant apply, das CLI-Version 1.30.0 oder neuer braucht, behandelt Agents, Skills, Memory-Stores, Environments und Deployments als Dateien in einem Repository. Du beschreibst jede Ressource in einer Datei, führst den Befehl aus, und an einem Terminal gibt es einen Plan aus und wartet. Bestätige ihn, und es schreibt ein claude-lock.json, das Lockfile, das die ID jeder Ressource neben zwei Fingerabdrücken festhält: einen Hash dessen, was es zuletzt gesendet hat, und den Hash, den die API zurückgegeben hat. Beim nächsten Lauf vergleicht es deine Dateien mit beiden, sodass es eine Änderung, die du gemacht hast, von einer Änderung unterscheiden kann, die auf der anderen Seite passiert ist.

Die Dokumentation sagt genau, was es tut, wenn die Änderung auf der anderen Seite passiert ist:

If a resource was edited, archived, or deleted outside these files
(in the Claude Console, for example), the plan ends with
`This plan cannot be applied:` and the reason. The command then
exits with `refusing to apply`. Pass `--force` to overwrite the edit
or create a replacement.

Das verwaltet gehostete Ressourcen auf Anthropics Plattform, nicht eine Flotte lokaler Prozesse, es verwaltet also nicht die Agents, die ich auf meiner eigenen Maschine betreibe. Ich behaupte nicht, der Anbieter hätte ein Werkzeug für mein Setup gebaut, oder dass sein Erscheinen beweist, dass Agent-Konfiguration im Allgemeinen irgendeine Grenze überschritten hat: Anbieter schreiben deklaratives Tooling für alles, was sie hosten. Was es tut, ist, die Semantik niederzuschreiben, Plan und Lock und Verweigerung, und dem lokalen Fall ein Vokabular zu geben, das er nicht hatte. Der Kategorie-Anspruch ruht auf dem Beweis aus erster Hand, dem Register, das immer wieder veraltete, nicht auf dem Produktplan eines Anbieters.

Die Verweigerung ist der Bruch

Die Verweigerung ist der Punkt, an dem ant apply die Linie verlässt, statt sich ihr anzuschließen. Terraform zeigt dir den Drift und schlägt vor, ihn beim nächsten Apply zurückzunehmen. Ein Kubernetes-Controller überschreibt die Änderung von außerhalb fortlaufend und sagt nichts. Der Server-Normalfall ist zu konvergieren, die Änderung zurück auf den deklarierten Zustand zu falten. ant apply hält stattdessen an. Das Lockfile ist nicht der neuartige Teil; kubectl apply führt seit Jahren einen last-applied-Eintrag, um denselben Drei-Wege-Vergleich zu machen, und es hat sich fürs Mergen entschieden. Neu ist die Antwort am Konfliktpunkt.

Zu verweigern statt zu konvergieren ist nichts Unerhörtes. npm ci installiert nicht, wenn Lockfile und Manifest auseinandergelaufen sind; es bricht mit einem Fehler ab, statt stillschweigend eines von beiden umzuschreiben. Aber es ist die Minderheitsentscheidung, und Agents sind ein guter Grund, sie zu treffen. Die Änderung von außerhalb ist meist der Live-Fix, für den du eingegriffen hast. Konvergiere stillschweigend, und du nimmst diesen Fix zurück und erfährst nie, dass er weg ist. Eine lokale Flotte tut dir das schon ohne die Hilfe irgendeines Werkzeugs an: Starte eine Session neu, und sie lädt die Datei erneut, verwirft die Korrektur, die du in die laufende getippt hast, und nichts markiert den Verlust.

Das ist auch der Unterschied zwischen einer Verweigerung und einem Plan. Er ist leicht zu übersehen, weil ein Plan den Drift ohnehin sichtbar macht. Der Unterschied ist der Normalfall. Ein Plan zeigt dir die Änderung und lässt dich daran vorbei bestätigen, eine Zeile in einem Diff, das du aus Gewohnheit durchscrollst und abnickst. Eine Verweigerung fährt nicht fort, bis die Datei die Änderung festhält. Sie blockiert auf dem Schreibpfad, wo der Detektor, den ich gebaut habe, es nicht tut: Meine zeitgesteuerte Prüfung erwischt eine veraltete Datei im Nachhinein und endet mit einem Fehlercode, nach ihrem eigenen Zeitplan, abseits. Sie ist echt und nützlich, und sie deckt das Fenster zwischen zwei Applies ab, in dem ein blockierendes Gate nichts sieht. Aber ein Detektor lässt sich ignorieren, und er feuert nach der Abweichung, nicht an ihr. Die beiden sind Hälften derselben Aufgabe, nicht dieselbe Hälfte.

Was ein Lockfile nicht sehen kann

Die Verweigerung schützt nur, was im Lockfile steht, und die Dokumentation ist über diese Grenze eindeutig. ant apply kann eine Ressource, die du außerhalb dieser Dateien erstellt hast, nicht übernehmen. Wende eine Datei an, die einen bereits existierenden Agent beschreibt, und du bekommst einen zweiten Agent, nicht die Hoheit über den ersten. Es gibt überhaupt keinen Import-Schritt, was strenger ist als bei den Servern, von denen es erbt: Terraform hat terraform import für genau das, und ant apply hat nichts, also ist die einzige gangbare Reihenfolge, eine Ressource zu deklarieren, bevor du sie erstellst.

Das bedeutet, dass Deklariert-gegen-laufend immer eine zu großzügige Rahmung war. Was ein Reconciler tatsächlich abgleicht, ist der deklarierte Zustand mit dem Teil des laufenden Zustands, den du zu deklarieren daran gedacht hast. Die Ressource, die du von Hand hochgezogen und nie aufgeschrieben hast, driftet nicht, denn Drift ist ein Vergleich, und es gibt nichts auf der anderen Seite davon. Sie sitzt dort auf der Plattform, ungemanagt, neben der gemanagten Menge, und der Plan, der bei deinen erfassten Ressourcen so laut verweigert, sagt nichts über sie.

Zwei Dinge machen das für Agents schärfer als für Server. Ein Memory-Store ist einer der deklarierten Ressourcentypen, aber die Erinnerung, die ein Agent zur Laufzeit hineinschreibt, ist nicht Teil der deklarierten Datei, das Lockfile nimmt also nie den Fingerabdruck des Teils der Ressource, der am stärksten prägt, wie sie sich verhält. Und die Server-Kur für einen ungemanagten Snowflake, ihn aus einer Deklaration neu zu bauen und wiederherzustellen, funktioniert bei einem Agent, der seine eigene Erinnerung geschrieben hat, nicht vollständig: Ihn wiederherzustellen wirft weg, was er gelernt hat. Der zustandsbehaftete Teil eines Agents widersteht dem sauberen Reconcile in beide Richtungen, einmal, weil er nicht erfasst werden kann, und einmal, weil er nicht ersetzt werden kann.

Was die Aufzeichnung abdeckt und was nicht

Die Modellversion, das, worüber ein anderer Essay von mir seine ganze Länge lang sagt, du sollst sie pinnen, damit du die Änderung eines Anbieters von deiner eigenen unterscheiden kannst, ist ein Feld in der Deklaration des Agents, in denselben Hash gefaltet wie alles andere. Jener Essay setzte voraus, dass ich meine eigene Seite der Linie kannte: Seine ganze Methode ruht auf der Prämisse, dass ich meine Seite nicht geändert habe, also hat es die Abhängigkeit getan. Dieser hier handelt von dem Tag, an dem diese Prämisse falsch ist, an dem ich meine Seite doch geändert und es nie festgehalten habe. Er ist die Vorbedingung für den anderen, nicht eine breitere Version davon.

Und die Deckungslücke, mit der er endet, schließt sich nicht mit mehr Disziplin. Sich vorzunehmen, alles aufzuschreiben, ist das Mittel, das immer versagt, weil das, was du vergisst, für genau die Aufzeichnung unsichtbar ist, die du führst, um zu erwischen, was du vergisst. Die mechanische Antwort läuft andersherum. Ein Deklarations-zuerst-Reconciler kann nur sehen, was du deklariert hast; ein Entdeckungs-zuerst-Sweep sieht, was existiert. Liste auf, was tatsächlich da ist, und vergleiche es mit dem, was du deklariert hast, und die Lücke wird zu etwas, das du lesen kannst, statt zu etwas, von dem du hoffst, dass du es geschlossen hast. Das ist der Teil, in den ich hineingestolpert bin, ohne ihn zu benennen: Die Lösung für das Register war nicht, es sorgfältiger zu pflegen, sie war, es aus dem zu generieren, was existiert. Kombiniere das Lockfile, das verweigert, mit dem Sweep, der entdeckt, und die gemanagte Menge lässt sich endlich gegen die gesamte Menge prüfen. Lass den Sweep weg, und das Lockfile deckt ab, was du aufgeschrieben hast, und sonst nichts.

Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit

Max Nardit

@mnardit

Weitere Artikel

Der Connector ist ein nicht vertrauenswürdiger Autor

Wer einen Span geschrieben hat, ist eine Tatsache, die der Harness schon hält. Ob dieser Span sicher zu befolgen ist, ist es nicht. Die Lösung ist also kein clevererer Leser: Es geht darum, dem Tool und dem Peer den Rang des fremden Dings zu geben, das sie sind, statt den Rang der Leitung, durch die sie ankamen.

Der Agent greift zum Werkzeug, das er lesen kann

Du optimierst ein Werkzeug danach, was es kann. Ein Agent wählt ein Werkzeug danach, ob er das Ergebnis vorhersagen und die Antwort lesen kann, wenn sie leer zurückkommt. Das sind verschiedene Eigenschaften, und der Abstand zwischen ihnen ist der Grund, warum gerade das mächtigere Werkzeug so oft unangetastet bleibt.

Ein Exit ist kein Urteil

Ein Monitor, der auf die Abwesenheit eines Prozesses hin alarmiert, beantwortet eine Frage, deren Antwort du längst siehst. Ob ein Job weg ist, lässt sich billig feststellen und ist fast nie das, worauf es ankommt. Ob er fehlgeschlagen ist oder absichtlich beendet wurde, durch ein Deploy, einen Neustart, einen Operator, das ist es, woran ein Alarm hängt, und ein bloßer Exit trägt davon nur einen Bruchteil.