Zurück zum Blog
Agenten-Schleifen · Essay

Wie Loss Functions verändert haben, wie ich Agenten-Loops fahre

Simon Doba·4. August 2026·14 Min. Lesezeit·aktualisiert 6. August 2026

Gerade läuft eine Debatte über etwas, das Compound Engineering genannt wird. Will Larson hat beschrieben, wie er Everys Vier-Schritt-Variante übernommen hat — Plan, Work, Review, Compound, und im Team ausgerollt. Eugene Yan hat seine eigene Fassung aufgeschrieben: Kontext als Infrastruktur, Taste als Konfiguration, Verifikation für Autonomie, Delegation, Closing the Loop.

Beide sind lesenswert und weitgehend einig. Plane, bevor du baust. Schreib deine Präferenzen in eine Config-Datei. Halte Verifikation billig, dann kannst du größere Brocken abgeben. Notiere, was du gelernt hast, damit die nächste Session weiter vorn beginnt.

Mache ich alles. Es gibt aber einen Schritt dahinter, den keiner der drei behandelt.

Am weitesten ist HAMY gegangen. Er hat einen Orchestrator über acht Phasen gebaut, ihn über Nacht laufen lassen und berichtet, was dabei herauskam: 15 Tasks in 10 Stunden, rund 90 Dollar Tokens. Bewertet hat er das Ergebnis dann so, wie ich es auch getan hätte:

The idea being that it's okay to sacrifice speed for quality, considering we want this to be run ~fully autonomously. It's better for it to only complete 15 tasks over night and need no rework than 20 but we have to fix 5 of them.

HAMY, Februar 2026

Ein Qualitätskriterium ist das allemal, und zwar ein gutes. Nur eben keine Zahl. Bewerten konnte das in diesem Lauf niemand außer ihm selbst.

Genau da sind meine letzten Monate hingeflossen: Was passiert, wenn du den Output einer Loop nicht mehr reviewst, sondern bewerten lässt.

Kurze Antwort: Du hast dann keine Dokumentationspraxis mehr, sondern einen Optimierer. Und ein Optimierer hat eine Eigenschaft, die ein Notizbuch nicht hat — er drückt gegen das, womit du misst.

Eine vermessene Optimierungslandschaft mit einem flachen lokalen Minimum und einer tieferen, durch unabhängige Probes sichtbaren Lücke.
Der Score sagt, dass der Loop angekommen ist. Die unabhängige Probe zeigt, wo.

Was sich ändert, sobald eine Zahl im Spiel ist

Solange ich einen Lauf beaufsichtige, bin ich die Instanz. Ich lese den Diff, ich entscheide, ob das gut ist. Mein Urteil steht nirgends und passt sich unterwegs an. Funktioniert — bis meine Aufmerksamkeit nicht mehr reicht.

Die Alternative ist eine Loss Function: aufschreiben, was „gut" heißt, präzise genug, dass ein Skript es ausrechnen kann. Dann läuft die Loop dagegen, so lange es eben dauert. Bei mir steht das in einer goal.md, daneben eine spec.md, ein harness/, das bewertet, und ein eval/ mit den Fällen.

Der Gewinn ist echt. Die Loop läuft, während ich weg bin, sie weiß, ob sie vorankommt, und sie schafft viele Zyklen ohne mich.

Der Preis: Ich habe etwas ein Ziel gegeben. Und im Verfolgen von Zielen ist es besser, als ich dachte.

Ein Beispiel, das ich vollständig zeigen kann

Damit das konkret wird, ohne fremde Specs zu veröffentlichen, nehme ich eine Aufgabe, die ich von vorn bis hinten beschreiben kann: ein Werkzeug, das eine GitHub-Actions-Workflow-Datei liest und meldet, welche dokumentierten CI-Anti-Patterns darin stecken. Keine Concurrency-Group. Keine Path-Filter. Ungepinnte Action-Versionen. Kein Dependency-Cache.

Das lässt sich bewerten. Du labelst einen Satz Workflow-Dateien mit den Patterns, die tatsächlich drinstecken, das Harness rechnet die Trefferquote je Kategorie aus, der Mittelwert ist der Composite. Fertig ist eine Zahl zwischen 0 und 1, an der die Loop klettern kann.

So sieht ein solches Target abgespeckt aus:

## Target
harness/score.sh scores eval/dev and prints one JSON line:
{"categories": {...}, "composite": N}

## Constraints
- goal.md, spec.md, harness/ and eval/ are READ-ONLY (checksummed;
  modification voids the score)
- Surface allowlist: this repository only. No network.
- Determinism is part of the spec: no wall-clock, no Math.random,
  no filesystem-order dependence in scored output.

## Bar
Holdout composite >= 0.95 AND no single category < 0.90
Probe gap <= 0.02

## Stop conditions
Bar reached - marginal gain about zero for 5 cycles -
probe gap grows across 3 cycles despite removing artefacts

Spannend ist alles, was das Target verteidigt.

Das vollständige Gerüst, das ich benutze — Target, Constraints, die zweistufige Abnahme, die drei Eval-Splits, Stop-Bedingungen und die Entropie-Regeln — liegt als Vorlage zum Kopieren bereit, mit einer Notiz an jeder Klausel, welcher Lauf sie verursacht hat.

Die Loop optimiert die Eval, nicht das Problem

Was zuerst schiefgeht, ist das älteste Ergebnis des Machine Learning, und es kommt schneller, als einem lieb ist.

Du bittest die Loop nicht, CI-Anti-Patterns zu erkennen. Du bittest sie, score.sh eine hohe Zahl drucken zu lassen. Dasselbe Ziel ist das nur so lange, wie die Eval ein fairer Stellvertreter bleibt, und fair bleibt ein Stellvertreter genau bis zu dem Moment, in dem ihn jemand studieren darf.

Also findet die Loop Abkürzungen. Ihr fällt auf, dass jede Dev-Datei ohne Concurrency-Group zufällig über achtzig Zeilen lang ist, und schon hängt sie an der Länge. Ihr fällt ein Kommentar auf, der mit einem Label korreliert. Im Extremfall lernt sie schlicht das Dev-Set auswendig: ein Lookup auf irgendein Nebenmerkmal, als Logik verkleidet.

Jede dieser Varianten erreicht 1,0 auf Dev und taugt nichts.

Das ist Goodharts Gesetz, nur mit einem viel kürzeren Rückkopplungszyklus als sonst. Das Maß wurde in Zyklus eins zum Ziel, weil ich genau das gesagt habe.

Read-only, checksummed, blind

Drei Regeln folgen unmittelbar daraus:

  • Das Target ist unveränderlich. goal.md, spec.md, harness/ und eval/ tragen Prüfsummen, und jede Änderung daran macht die Bewertung ungültig. Sonst führt der billigste Weg zu einer hohen Zahl über den Scorer, und den findet sie.
  • Die Lösungen liegen nicht im Repository. Die Eingaben der Szenarien darf man lesen, die erwarteten Ausgaben sind nirgends zu finden. Einen Lösungsschlüssel, den es nicht gibt, kann man nicht abschreiben.
  • Determinismus gehört zur Spec, nicht zur Kür. Keine Wall-Clock, kein Zufall, keine Dateisystem-Reihenfolge in irgendetwas, das bewertet wird. Sonst liefert derselbe Lauf eine andere Zahl, und du kannst Fortschritt nicht mehr von Rauschen unterscheiden.

Die letzte Regel klingt pedantisch — bis sich ein Composite zwischen zwei identischen Läufen um 0,03 bewegt und du einen Abend lang einer Änderung nachjagst, die nie stattgefunden hat.

Sie ist auch der Grund, warum Evals überhaupt schwer zu trauen ist. Larson benennt das Problem direkt, als er über die Evals seines Teams schreibt:

The core challenge is the non-determinism introduced by these eval tests, where in practice there's very strong signal when they all fail, and strong signal when they all pass, but most runs are in between those two.

Will Larson, Evals to validate workflows

Sein Schluss: Er wird komplexe Workflows wohl von LLM-getriebener Ausführung wegholen müssen, um Konsistenz zu bekommen. Ich ziehe die Grenze woanders. Ein Teil dieses Nichtdeterminismus gehört dem Modell, damit muss man leben. Der Rest — Uhren, Zufall, Dateisystem-Reihenfolge, alles was zwischen zwei identischen Läufen schwankt — gehört dir, und den kannst du per Spec verbieten. Nimm diese Hälfte weg, und was übrig bleibt, ist ein Signal, das sich lesen lässt.

Der Probe-Gap

Die Regeln oben stoppen die Tricks, die du benennen kannst. Der nützliche Mechanismus ist der, der die anderen erwischt.

Teile die Eval in drei. Gegen Dev bewertet die Loop in jedem Zyklus. Holdout bekommt sie bis zum Schluss nie zu sehen. Probe ist dasselbe wie Dev, nur anders formuliert: umbenannte Jobs, umsortierte Keys, neu eingerücktes YAML.

Ein Werkzeug, das die Aufgabe verstanden hat, liegt auf Dev und Probe ungefähr gleich. Eines, das Dev auswendig gelernt hat, bricht auf Probe ein, weil alles Nebensächliche, woran es hing, sich gerade verschoben hat.

Der Probe-Gap ist der Abstand zwischen dem Dev-Score einer Loop und ihrem Probe-Score. Er ist die nützlichste Zahl im ganzen Aufbau. Nicht weil er sagt, wie gut die Sache ist — dafür ist der Composite da, sondern weil er sagt, ob der Composite überhaupt etwas bedeutet.

Mein Abnahmekriterium ist ein Gap von höchstens 0,02. Die Stop-Bedingung darüber ist bewusst keine zweite Schwelle, sondern ein Trend: Wächst der Gap über drei Zyklen hintereinander, obwohl ich jedes auffindbare Artefakt entfernt habe, hält der Lauf an und schreibt den Verdacht auf Auswendiglernen auf.

Bis ich diese Unterscheidung richtig hatte, hat es gedauert. Eine Schwelle erwischt den Endzustand. Ein wachsender Gap erwischt den Vorgang, und das ist früher und brauchbarer — wenn eine absolute Zahl anschlägt, ist das Auswendiggelernte schon gebaut.

So zu stoppen ist ein legitimes Ergebnis, kein gescheiterter Lauf.

Lints für das Benennbare

Neben dem Probe-Gap gibt es eine billigere Schicht: Lass einen Lint über den Quelltext laufen, der die Tricks kennt, die du schon gesehen hast. Keine Szenario-IDs aus der Eval im Code. Keine Hex-Literale, die auf Answer-Hashes passen. Keine Datendatei über einer kleinen Grenze im Quellbaum. Obergrenzen dafür, wie oft ein verdächtiges Literal außerhalb von eval/ auftauchen darf.

Dicht ist davon nichts, und die ehrliche Fassung der Regel sagt das auch. Meine lautet: Hartkodierte Lookup-Tabellen auf Eval-Inhalte sind ein Verstoß, auch dort, wo der Lint blind ist — der Probe-Gap deckt sie auf.

Beide Hälften zählen. Der Lint fängt den naheliegenden Versuch früh und billig ab. Halten tut am Ende der Probe-Gap, weil er nicht voraussetzt, dass ich den konkreten Trick vorhergesehen habe.

Feststecken ist ein anderes Problem

Schummeln ist nicht der einzige Fehlermodus. Der zweite ist eine Loop, die sich nicht mehr bewegt und dasselbe immer härter versucht.

Drei Regeln stehen deshalb inzwischen in jedem Target, das ich schreibe:

  • Stall-Regel. Hat sich der Composite im letzten Zyklus nicht bewegt, muss der nächste Versuch strukturell anders sein — anderes Subsystem, anderer Entwurf. Am selben Rädchen härter drehen ist verboten.
  • Explorationsquote. Jeder fünfte Zyklus geht an die schwächste Kategorie, auch wenn der offensichtliche Hebel woanders liegt.
  • Spec neu lesen. Steht eine Kategorie drei Zyklen auf null, wurde die Semantik missverstanden — der Code ist dann nicht fast richtig. Zurück zur Spec, neu herleiten, aufschreiben, bevor wieder Code entsteht.

Die erste Regel leistet den größten Teil. Sich selbst überlassen dreht eine Loop ewig am selben Parameter, weil jeder einzelne Versuch lokal der billigste nächste Zug ist.

Wie ein echter Lauf aussieht

Das CI-Beispiel ist ein Platzhalter, damit ich keine Spec veröffentlichen muss. Diese Kurve stammt von einem echten Target — einem Partner-Attributionssystem, an dem ich baue —, die Kategorienamen passen also nicht dazu. Übertragbar ist die Form.

Eine Session, 11. Juli, 79 Minuten, elf bewertete Zyklen.

  • Start bei 0,8279 auf zwölf Szenarien, dann Erweiterung auf vierzig.
  • Bis Zyklus vier hoch auf 0,9212, und dann steht es. Vier Zyklen ohne Bewegung.
  • Zyklus sieben geht runter, auf 0,9102. Eine Änderung, die schlechter abschneidet, sichtbar stehen gelassen statt still zurückgenommen.
  • Zyklus elf springt in einem Schritt auf 1,0.

Dann die Verifikation: das volle Dev-Set mit 400 Szenarien kommt auf 0,9985, das Probe-Set auf 0,9940. Ein Gap von 0,0045, deutlich innerhalb der 0,02-Grenze. Was es gelernt hat, war jedenfalls nicht das Dev-Set.

Interessant ist, warum es überhaupt ein Plateau gab.

Vier der sechs Kategorien standen ab Zyklus drei auf 1,00 und blieben dort. Eine — das Ledger — lag acht Zyklen lang bei 0,69. Der Composite ist ein Mittelwert, und 0,92 ist eben das, was ein Mittelwert anzeigt, wenn fünf Teile fertig sind und eines noch nicht angefangen hat.

Das sind zwei völlig verschiedene Lagen, und eine einzelne Zahl kann sie nicht auseinanderhalten. Ein Composite von 0,92 liest sich als „acht Prozent fehlen noch". Die Aufschlüsselung nach Kategorien liest sich als „ein Subsystem hängt". Der Sprung von 0,9212 auf 1,0 in einem Schritt zeigt, welche Lage es war: Die Loop hat keine kleinen Gewinne herausgeholt, sie hing an einer Sache, und dann nicht mehr.

In derselben Datei steckt eine schärfere Fassung davon, die ich erst beim erneuten Hineinschauen gefunden habe: die Anzahl der fehlschlagenden Szenarien. Sie liegt von Zyklus vier bis Zyklus zehn konstant bei 26. Sieben Zyklen, in denen der Composite zwischen 0,9102 und 0,9212 hin und her ging — hoch, runter, wieder hoch, und sich die Anzahl der tatsächlich falschen Dinge kein einziges Mal änderte.

Der Composite meldete Bewegung. Repariert wurde nichts.

Deshalb hat meine Abnahme zwei Bedingungen statt einer: Holdout-Composite mindestens 0,95 und keine einzelne Kategorie unter 0,90. Die zweite habe ich nicht aus Weitsicht hingeschrieben, sondern nachdem ein Mittelwert einen Lauf besser aussehen ließ, als er war.

Ein perfekter Score auf dem falschen Zuschnitt

Bisher zwei Fehlerarten: Die Loop betrügt, oder sie bleibt stecken. Es gibt eine dritte, und für die hatte ich nichts vorgesehen.

Dieser Lauf ist sauber durchgelaufen. Ein späterer auf demselben Target hat 1,0 auf der Stichprobe erreicht, 1,0 auf dem vollen Dev-Set und 1,0 auf dem Probe-Set — ein Gap von exakt null.

Nichts wurde ausgetrickst. Kein Lint hätte angeschlagen. Die Generalisierung war echt: dieselben Muster auf umformulierter Oberfläche, derselbe Score. Jede Verteidigung aus diesem Post hat funktioniert.

Und was dastand, war vollständig vom CLI-Kontrakt geformt, weil der Scorer darüber lief. Was ich in die Constraints geschrieben hatte, war tatsächlich alles da: eine echte Datenbank, Row-Level Security unter einer Rolle ohne Superuser-Rechte, Append-only mit Hash-Kette, direkt geprüft statt über einen Sonderweg fürs Scoring. Nicht da war der Einstiegspunkt, den das Produkt wirklich benutzt.

Alle 600 Eval-Szenarien — 400 Dev, 200 Holdout — sind vorgefertigte Zustandsdateien. Affiliates, Kanten, Events, Links, Offers, Auszahlungen. In keinem einzigen steckt ein HTTP-Request, weil das Harness diesen Zustand direkt an die Engine übergibt. Die Ingestion-Routen, über die jedes echte Event hereinkäme, standen also kein einziges Mal auf dem Prüfstand. Sie konnten es nicht. In der Eval gab es nichts, was sie erreicht hätte.

Das ist nicht Goodhart. Nichts ist auseinandergelaufen. Maß und Ziel blieben deckungsgleich — das Maß war nur kleiner geschnitten als das Produkt.

Das ist schwerer als Betrug, weil jedes Instrument aus diesem Post nach innen zeigt. Der Probe-Gap fragt, ob die Sache über die Oberfläche generalisiert, die ich abgetastet habe. Ob diese Oberfläche die richtige war, dazu sagt er nichts. Eine Eval, die man nicht bearbeiten darf, ist immer noch eine Eval, deren Grenzen man selbst gezogen hat.

Ich habe dafür noch keinen Mechanismus, und das will ich klar sagen. Gefunden habe ich es, indem ich mir das Produkt hinterher angesehen habe — dasselbe Argument, das ich für niemals denselben Agenten reviewen lassen, der gebaut hat mache, eine Ebene höher. Eine Loop kann den Zuschnitt ihres eigenen Targets so wenig prüfen wie ihren eigenen Diff.

Was das nicht löst

  • Die Kurve oben sind 79 Minuten, keine Woche. Eine Session, elf Zyklen, und die beiden Verifikationspunkte sind Dev und Probe, nicht Holdout — Holdout läuft aggregat-only auf einem Budget von sechs Aufrufen pro 48 Stunden und taucht in der Verlaufsdatei gar nicht auf. Es ist die Kurve, die ich habe. Die, die ich will, ist es noch nicht.
  • Für den Zuschnitt habe ich kein Instrument. Alles oben prüft, ob ein Score ehrlich ist. Nichts oben prüft, ob das Gemessene das ganze Produkt ist, und das war der Fehler, der mich tatsächlich Zeit gekostet hat.
  • Es kostet etwas, aber es ist kein Tagewerk. Von Hand schreibe ich die Dinger nicht mehr. Ein Skill erzeugt Target, Constraints, Instrumente und die Entropie-Regeln und geht das Ergebnis anschließend selbst auf Schummelwege durch, bevor irgendetwas startet. Bei mir bleibt die Entscheidung, was „gut“ für diese eine Aufgabe heißt, und die automatisiert sich nicht.
  • Ein guter Score bleibt trotzdem kein Beweis. Er ist ein deutlich besseres Signal als ein grüner Testlauf, wie ich anderswo argumentiert habe. Dasselbe wie „die Sache ist gut" ist er nicht.
  • Die Spec musst du sowieso schreiben. Der meiste Nutzen entstand beim Spezifizieren, bevor ein einziger Zyklus lief. Für einen Text über Automatisierung ist das ein unbequemer Befund.

Next

Wo ich gelandet bin:

  • Eine Loss Function macht aus Beaufsichtigung etwas, das ohne dich läuft. Darin liegt der ganze Gewinn, und der ist groß.
  • Das Target muss unveränderlich sein, sonst führt der kürzeste Weg zu einem hohen Score durch den Scorer.
  • Halte ein Probe-Set zurück. Der Abstand zwischen Dev und Probe sagt dir, ob die Zahl etwas bedeutet.
  • Verbiete das Weiterdrehen am selben Rädchen. Feststecken sieht ganz anders aus als Schummeln und braucht eine eigene Regel.
  • Ein sauberer Score heißt, dass das Gemessene stimmt. Ob du das Produkt gemessen hast, ist eine andere Frage, und nichts von dem oben stellt sie.

Compounding, wie Larson und Yan es beschreiben, und das Von-Hand-Beurteilen, das HAMY am Ende eines Laufs macht — findet zwischen den Läufen statt: die Notiz, die geänderte Config, die Regel, die letzten Monat noch nicht da war. Was ich ergänzen würde: Sobald ein Lauf bewertet wird, bekommt Compounding einen Gegenspieler. Und der Gegenspieler ist genau das, was du compoundest.

Als Nächstes will ich eine längere Fassung dieser Kurve. 79 Minuten reichen, um zu zeigen, dass es die Form gibt. Sie reichen nicht, um zu zeigen, wie ein Abstieg über Tage aussieht, mit durchgehend mitlaufendem Holdout statt sechs Aufrufen aus einem Budget.

Und ich will ein Instrument für den Zuschnitt — etwas, das anschlägt, wenn die Eval das Produkt nicht mehr abdeckt, so wie der Probe-Gap anschlägt, wenn die Eval nicht mehr ehrlich ist. Einen Entwurf dafür habe ich nicht. Womöglich ist es nichts, was ein Skript halten kann.

Falls du bewertete Loops fährst, würde mich interessieren, was deine gefunden hat, das du nicht kommen sahst. Meine ist an beiden Instrumenten vorbei, die ich für sie gebaut hatte — indem sie nichts falsch gemacht hat.

Und falls du einen Weg gefunden hast zu prüfen, ob eine Eval das Produkt noch abdeckt statt nur ehrlich über das zu sein, was sie abdeckt: Wie sieht der aus?

Die Mechanismen hier stammen aus Targets, die ich tatsächlich laufen lasse. Die CI-Linter-Aufgabe ist ein Beispiel, ausgesucht, weil ich sie vollständig beschreiben kann, ohne fremde Specs zu veröffentlichen.

Artikel teilen

Baust du an etwas Ähnlichem?

Ich schreibe hier über Setups, die ich selbst benutze. Wenn du an etwas Vergleichbarem arbeitest, würde mich interessieren, wie dein Workflow aussieht.

Schreib mir

Cookie-Einstellungen

Wir nutzen Cookies für Analyse und Verbesserung unserer Website. Datenschutzerklärung