Der lokale Coding-Agent im Eval

Der lokale Coding-Agent im Eval

Artikel 6 · Serie: Ein lokaler Coding-Agent mit apfel

Fünf Artikel lang haben wir gebaut: CLI, Serve-Protokoll, Swift-Client, Tool-Calling, abgesicherte Werkzeuge. Der Agent kann lesen, schreiben, ausführen. Offen ist die unbequeme Frage, wie viel er damit tatsächlich zustande bringt. Dieser Artikel baut kein neues Feature, er misst. Die These vorweg, damit der Befund nicht als Abrechnung missverstanden wird: Das lokale Modell ist nicht „schlechter" als ein Cloud-Modell, es hat ein anderes Einsatzprofil. Es gibt Aufgaben, die es zuverlässig löst, und Aufgaben, an denen es kippt, und die Trennlinie verläuft nicht dort, wo man sie vermutet. Eine zweite Frage messen wir getrennt mit: was das Modell über seinen eigenen Erfolg behauptet, im Verhältnis zu dem, was eine Maschine über diesen Erfolg feststellt.

Wie gemessen wurde

Das Eval besteht aus 15 Aufgaben in fünf Kategorien, je drei Aufgaben: kleine Ein-Datei-Edits, Code-Erklärungen, kurze Werkzeug-Ketten, mehrschrittige Pläne und Aufgaben unter Kontextfenster-Druck. Jede Aufgabe läuft dreimal, das Ergebnis ist das Mehrheitsvotum. Der entscheidende Punkt steckt in der Bewertung. Über Erfolg oder Scheitern urteilt nie das Modell, sondern eine deterministische check()-Funktion pro Aufgabe: ein grep auf den neuen Funktionsnamen, ein Datei-Diff gegen die Soll-Ausgabe, ein Exit-Code. Genau das ist der Fehler, den ein naives Eval macht und den dieser Artikel seziert. Wer das Modell fragt, ob es geklappt hat, misst die Selbsteinschätzung und nennt sie Ergebnis.

Hinter dem Aufgaben-Kanon steht eine implizite Wahl, welche Coding-Aufgaben „typisch" sind. Diese Wahl ist ein Bias, und wir benennen ihn als solchen. Alle Zahlen sind Eigenmessung gegen einen lokalen apfel-Serve (apfel 1.5.1, macOS 26.3, Foundation Model Snapshot 2026-06-08); das vollständige Harness liegt im Demo-Repo.

Wo das Modell überzeugt

Verlässlich überzeugt das Modell bei Erklärungen. Eine kurze Funktion in einem Satz erklären, einen Off-by-one-Fehler benennen, zwei Funktionen vergleichen: alle drei Aufgaben gelingen im Mehrheitsvotum (3 von 3; Eigenmessung v0.6). Das passt zur Natur der Aufgabe. Erklären heißt Text produzieren, und Text produzieren ist die Kernkompetenz eines Sprachmodells. Es muss kein Werkzeug bedienen, kein Argument-Schema treffen, keinen Zustand verändern.

In der Kategorie der kurzen Werkzeug-Ketten ist das Bild gemischt. Eine Datei suchen, lesen und einen Wert extrahieren gelingt; das Auflisten, Lesen und Zusammenfassen sowie das Lesen-Ändern-Zurückschreiben scheitern im Mehrheitsvotum (1 von 3; Eigenmessung v0.6). Sobald die Kette einen schreibenden Schritt enthält, beginnt das Modell zu straucheln, nicht beim Lesen.

Wo es kippt

Der erste überraschende Befund liegt dort, wo man Erfolg erwartet hätte: bei den kleinen Ein-Datei-Edits. Eine Funktion umbenennen, einen Docstring ergänzen, eine Signatur auf async umstellen, alle drei scheitern im Mehrheitsvotum (0 von 3; Eigenmessung v0.6). Das Modell kann den korrekten Code als Text formulieren, aber es bekommt ihn nicht zuverlässig durch das schreibende Werkzeug. Tool wählen, Schlüssel treffen, den vollständigen neuen Inhalt reproduzieren und das JSON sauber escapen, dieser Gleichzeitigkeit ist das kleine Modell nicht gewachsen. Dass sich genau hier mit dem richtigen Agent-Aufbau viel zurückholen lässt, ist das Thema des nächsten Artikels.

Klar und ohne Überraschung kippt das Modell bei mehrschrittigen Plänen. Ein Test hinzufügen, ausführen und den Fehler beheben; einen Refactor über zwei Dateien ziehen; ein kleines Feature aus einem Satz Beschreibung bauen, alle drei scheitern (0 von 3; Eigenmessung v0.6). Mehrschrittig heißt, dass das Modell einen Plan über mehrere Werkzeug-Aufrufe hinweg halten muss, und genau dieses Halten bricht.

Die Kontextfenster-Grenze in der Praxis

Das Foundation Model arbeitet mit einem Kontextfenster von 4096 Token. In der Theorie ist das eine Zahl, in der Praxis eine harte Wand. Von den Aufgaben unter Kontext-Druck gelingt nur das Zusammenfassen von fünf kurzen Dateien; eine Inkonsistenz über vier Konfigurationsdateien zu finden und denselben Refactor über sechs Dateien zu ziehen, scheitern beide (1 von 3; Eigenmessung v0.6). Sobald mehrere Dateien gleichzeitig in den Kontext müssen, konkurrieren Aufgabenstellung, Dateiinhalte und bisherige Werkzeug-Antworten um denselben knappen Platz. Was herausfällt, ist meist der Anfang: die ursprüngliche Anweisung. Der Agent verliert mitten in der Arbeit, was er eigentlich tun sollte.

Was das Modell über sich selbst sagt

Die getrennte Messung der Selbsteinschätzung liefert das, was am meisten überrascht. Von 31 Läufen, die maschinell als gescheitert gelten, hat das Modell genau einen fälschlich als Erfolg ausgegeben (Eigenmessung v0.6). Das kleine Modell ist über sein eigenes Scheitern erstaunlich ehrlich. Es behauptet selten, fertig zu sein, wenn es nicht fertig ist.

Das ist ein beruhigender Befund, aber es ist nicht der eigentliche Punkt. Der Punkt ist, dass diese Quote eine Eigenschaft dieses Modells in diesem Eval ist, kein Naturgesetz. Die gefährliche Zelle, behauptet Erfolg bei tatsächlichem Scheitern, ist die teuerste in jedem Agent-System, und ihre Häufigkeit steigt tendenziell mit der Fähigkeit des Modells: Größere Modelle formulieren überzeugendere „erledigt, Tests grün"-Sätze, auch wenn nichts grün ist. Die Selbsteinschätzung ist eine Behauptung, kein Beweis, unabhängig davon, wie selten sie hier falsch war.

Der Cloud-Vergleich als Einsatzprofil

Um die Grenze des lokalen Modells einzuordnen, haben wir je einen Vertreter pro Kategorie gegen ein Frontier-Modell laufen lassen, Claude Sonnet 4.6 als Cloud-Agent mit echten Datei-Werkzeugen, geprüft mit derselben check()-Funktion wie lokal.

KategorieVertreterLokalCloud
Ein-Datei-EditFunktion umbenennenscheitertgelingt
ErklärungFunktion erklärengelingtgelingt
Werkzeug-Kettelesen, ändern, zurückschreibenscheitertgelingt
Multi-StepRefactor über zwei Dateienscheitertgelingt
Großer KontextInkonsistenz über vier Dateienscheitertgelingt

Cloud-Stichprobe: Sonnet 4.6 via Claude Code, 2026-06-08, ein Lauf je Vertreter.

Das ist ausdrücklich kein Sieg-Vergleich. Es ist ein Einsatzprofil. Der Cloud-Agent besteht überall dort, wo das lokale Modell kippt, und der Unterschied ist die Modellgröße, nicht der Ansatz. Das Tool-Calling-Protokoll funktioniert, die Werkzeuge funktionieren, die Aufgaben sind lösbar. Eine ehrliche Einschränkung gehört dazu: Die Vertreter sind die jeweils einfacheren ihrer Kategorie, die schwersten Aufgaben des Kanons haben wir Cloud-seitig nicht gemessen. Die Stichprobe belegt, dass die Grenze des lokalen Modells modellgrößenbedingt ist, nicht dass ein Cloud-Agent jede Aufgabe löst.

Folgerung für den Agent-Loop

Aus der Messung der Selbsteinschätzung folgt eine Bauregel, und sie gilt nicht nur für unseren kleinen Agenten. Wenn das Modell meldet „fertig", ist das eine weitere Werkzeug-Antwort, kein verlässlicher Zustand. Done ist ein Tool-Result wie jedes andere und verdient denselben Argwohn. Im Agent-Loop gehört die Frage, ob eine Aufgabe erledigt ist, an eine maschinelle Prüfung: ein erneutes Lesen der Datei, ein Test-Lauf, ein Diff gegen die Erwartung. Dieselbe check()-Disziplin, die diesem Eval zugrunde liegt, gehört in den Loop selbst. Die zweite Folgerung ist eine Konsequenz aus den Befunden: Aufgaben klein und lokal halten. Das lokale Modell bewältigt einen Schritt, eine Datei, einen klaren Edit; es kippt am mehrschrittigen Plan und am vollen Kontextfenster. Ein Agent, der um diese Form gebaut ist, holt mehr heraus als einer, der dem Modell den großen Wurf zutraut.

Demo-Repo: das Eval selbst laufen lassen

Der Stand dieses Artikels ist eingefroren als Tag v0.6. Das Artefakt ist kein neuer Agent-Code, sondern das Eval-Harness.

Das Eval reproduzieren

Auf den Tag einsteigen:

git clone https://codeberg.org/rotecodefraktion/apfel-coding-agent.git
cd apfel-coding-agent
git checkout v0.6

Neu in v0.6 gegenüber v0.5:

  • eval/tasks/01..15 — 15 Aufgaben, je mit Prompt, Fixtures und deterministischem check()
  • eval/run.sh / eval/run-all.sh — Einzel- und Gesamtlauf gegen einen lokalen apfel-Serve
  • eval/report.py — rendert eval/results.md aus den Roh-Läufen
  • eval/results.md — Mehrheitsvotum pro Aufgabe und Diskrepanz der Selbsteinschätzung
  • eval/cloud-reference.md — die Cloud-Stichprobe und die Gegenüberstellung

Einen Serve starten und das Eval fahren (apfel belegt standardmäßig denselben Port wie Ollama, deshalb ein eigener):

apfel --serve --port 11509 &
APFEL_PORT=11509 ./eval/run-all.sh
python3 eval/report.py > eval/results.md

Jede check()-Funktion urteilt deterministisch, nie das Modell. Ein einzelner Lauf zur Kontrolle:

APFEL_PORT=11509 ./eval/run.sh eval/tasks/04-explain.sh

Was offen bleibt

Der größte Vorbehalt ist der Bias des Kanons. Fünfzehn Aufgaben in fünf Kategorien sind eine Auswahl, keine Vollerhebung, und die Auswahl entscheidet mit, wo die Trennlinie verläuft. Andere Aufgaben würden andere Stärken und Schwächen zeigen. Das Eval ist reproduzierbar, damit diese Auswahl überprüfbar bleibt und erweitert werden kann.

Der schärfste Einzelbefund bleibt der unerwartete: Schon der kleine Ein-Datei-Edit scheitert naiv. Das Modell kann den Code, aber es bedient das Werkzeug nicht. Genau dort setzt der nächste Artikel an. Wir bauen den Agenten nicht um ein stärkeres Modell, sondern um die Schwächen des vorhandenen herum, und messen, wie viel sich vom Edit zurückholen lässt.


Vorheriger Artikel: Die ersten echten Werkzeuge: Dateisystem und Shell. Nächster Artikel: Editieren, das funktioniert: Constrained Output statt Tool-Raten. Repo-Tag: v0.6.