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.

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.
## Stop conditions
Bar hit on holdout · marginal gain about zero for 5 cycles ·
probe gap over 0.15 twice in a row
Spannend ist nicht das Target. Spannend ist alles, was es verteidigt.
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/undeval/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.
In meinen Stop-Bedingungen ist ein gerissener Gap ein Ergebnis, kein Fehlschlag. Zweimal hintereinander über 0,15, und der Lauf hält an und schreibt den Verdacht auf Auswendiglernen auf. Das ist ein legitimes Ende. Einige der nützlicheren Läufe, die ich hatte, sind so ausgegangen.
Lints für das Benennbare
Neben dem Probe-Gap gibt es eine billigere Schicht: Linte den Quelltext auf die Tricks, 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.
Was das nicht löst
- Eine Score-Kurve kann ich dir nicht zeigen. Ich habe diese Targets gebaut und laufen lassen, aber keine saubere Historie über viele Zyklen, auf die ich ein Diagramm setzen würde. Ein Text über Messung ohne Messungen darin — die Ironie benenne ich lieber, als sie zu verstecken.
- 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.
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 die Score-Kurve, die ich oben nicht habe: ein langer Lauf mit genug Zyklen, dass die Form des Abstiegs etwas aussagt, und ein Probe-Gap, der die ganze Zeit danebenläuft.
Falls du bewertete Loops fährst, würde mich interessieren, was deine Loop gefunden hat, das du nicht kommen sahst — und ob ein Lint oder ein Holdout es aufgedeckt hat.
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.
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.