Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Agentic Coding

Heinrich-Heine-Universität Düsseldorf

Während traditionelle KI-Assistenten (wie GitHub Copilot) primär als “Autovervollständigung auf Steroiden” agieren, leitet Agentic Coding einen fundamentalen architektonischen Wechsel ein. Agenten schlagen nicht nur passiv Codezeilen vor, sondern verändern den Code direkt. Der Begriff ‘Agent’ suggeriert eine gewisse Autonomie, bedeutet letzlich aber nur soviel wie eine Sequenz von Prompts, die auf einen Ordner und ausgewählte Kommandozeilenwerkzeuge oder Plugins zugreifen können.

Ein Coding-Agent durchläuft iterativ einen Plan-Execute-Verify-Zyklus: Er analysiert die Projektstruktur, plant die Änderungen über mehrere Dateien hinweg, führt selbstständig Shell-Kommandos aus (z.B. das Installieren von Abhängigkeiten via uv oder das Ausführen von pytest), liest die resultierenden Fehlermeldungen und korrigiert den Code iterativ, bis die Tests erfolgreich sind. Der Entwickler wandelt sich dabei vom operativen Coder zum Architekten und Reviewer. Weil das Review von gut formatiertem Code leich zu “sieht gut aus, passt schon” wird, muss man bereits im Vorfeld die Menge des zu sichtenden Codes klein halten.

Worum es geht. Programmieren mit Werkzeugen, die den Code selbst schreiben: die Produktlandschaft im September 2026, die Konzepte darunter, die deutlich länger halten als jede Produktliste, und eine Arbeitsweise, die nicht auf Glück angewiesen ist.

Was Sie danach können. Ein Werkzeug einordnen (Autocomplete, Chat-IDE, Terminal-Agent, Cloud-Agent) statt es mit einem anderen zu verwechseln; erklären, warum passender Kontext besser ist als viel Kontext; eine Projekt-Instruktionsdatei schreiben, die die vier Fragen beantwortet, bei denen ein Agent sonst rät; und benennen, warum ein Hook verlässlich ist und eine Instruktion nur eine Bitte.

Die Java-Entsprechung in einem Satz. Am nächsten kommt der Sprung von einem einfachen Editor zu einer IDE mit Refactoring-Werkzeugen — mit dem entscheidenden Unterschied, dass “Extract Method” in der IDE garantiert korrekt ist und ein Agent nur plausibel; deshalb verschiebt sich die Arbeit vom Schreiben zum Prüfen.

Dieses Kapitel hat vier Teile. Zuerst die Werkzeuglandschaft — eine Momentaufnahme mit Verfallsdatum. Dann die Konzepte, die die Werkzeuge gemeinsam haben und die deutlich länger halten als jede Produktliste. Danach eine Anleitung, wie man die Arbeitsweise praktisch angeht. Und zuletzt ein protokollierter Durchgang an einem echten Projekt, mit Zahlen. Wer wenig Zeit hat, liest den zweiten Teil.

Die Werkzeuglandschaft (Stand: September 2026)

Diese Liste ist eine Momentaufnahme und wird altern. Sie steht hier trotzdem, weil man ohne Namen nicht anfangen kann — und weil die Kategorien länger halten als die Produkte. Vier davon reichen zur Einordnung:

  • Autocomplete — schlägt beim Tippen die nächsten Zeilen vor. Kein Zustand, kein Plan, keine Werkzeuge.

  • Chat-IDE — Editor mit eingebautem Chat, der die Codebasis kennt und mehrere Dateien gleichzeitig ändern kann. Der Mensch bestätigt jede Änderung.

  • Terminal-Agent — läuft in der Shell, arbeitet auf dem Dateisystem, ruft selbst Kommandos auf. Keine IDE erforderlich.

  • Cloud-Agent — bekommt eine Aufgabe (typischerweise ein Issue), arbeitet asynchron auf fremder Infrastruktur und liefert einen Pull Request ab.

WerkzeugKategorieLizenz / Preismodellin einem Satz
GitHub CopilotAutocomplete, Chat-IDE und Cloud-Agentproprietär; Abo-Stufen bis hin zu verbrauchsabhängigen CreditsDer Allrounder: der “Agent Mode” arbeitet seit März 2026 regulär in VS Code, JetBrains und Eclipse, der serverseitige Coding Agent nimmt ein zugewiesenes Issue, arbeitet in einem GitHub-Actions-Runner und öffnet am Ende einen Pull Request.
CursorChat-IDEproprietär; AboFork von VS Code, der LLMs tief in den Editor integriert (codebase-weiter Chat, Multi-File-Edits); der Ansatz bleibt konservativ, der Mensch bleibt am Steuer.
Claude CodeTerminal-Agent (zusätzlich IDE-Extensions, Desktop und Web)proprietär; Abo oder Abrechnung nach API-NutzungVon Anthropic; läuft im Terminal, navigiert selbstständig im Dateisystem und führt Systemkommandos aus, ohne eine grafische IDE zu erzwingen.
Codex CLITerminal-AgentWerkzeug Apache-2.0 (openai/codex); Modellzugriff kostenpflichtigOpenAIs Gegenstück: quelloffenes Harness, proprietäre Modelle — eine Aufteilung, die inzwischen bei mehreren Anbietern die Regel ist.
Gemini CLITerminal-AgentWerkzeug Apache-2.0 (google-gemini/gemini-cli); Modellzugriff per API-Schlüssel oder AboGoogles Terminal-Agent mit sehr großem Kontextfenster. Der anfangs kostenlose Zugang zum gehosteten Backend ist im Lauf von 2026 entfallen — ein gutes Beispiel dafür, dass “open source” und “kostenlos nutzbar” zwei verschiedene Aussagen sind.
AiderTerminal-AgentApache-2.0; BYOK (“bring your own key”)Zeichnet sich durch die Git-Integration aus: für jede funktionierende Änderung ein eigener, sinnvoll benannter Commit. Provider frei wählbar.
OpenCodeTerminal-AgentMIT; BYOKBewusst provider-agnostisch — dasselbe Werkzeug mit verschiedenen Modellen. Seit Januar 2026 authentifiziert auch ein bezahltes Copilot-Abo dagegen, was die Grenze zwischen den Ökosystemen weiter aufweicht.
JetBrains JunieAgent innerhalb der IDEproprietär; Abo zusätzlich zur IDE-LizenzNative Integration in PyCharm und Geschwister; plant und führt mehrschrittige Änderungen im Projekt aus, statt nur zu vervollständigen.
Google AntigravityAgenten-Plattformproprietär; kostenlose Stufe plus AboDenkt die IDE für das Agenten-Zeitalter neu: Neben der Editor View gibt es eine Manager View, in der mehrere Agenten parallel arbeiten und statt rohem Code überprüfbare Artefakte abliefern — Task-Listen, Architekturpläne, Diffs.
OpenClawkein Coding-AgentquelloffenFramework für einen persönlichen Assistenten, der als Hintergrunddienst läuft und über Messenger beauftragt wird. Programmieren ist ein Anwendungsfall unter vielen. Lehrreich ist die Architektur — Agenten als isolierte, asynchron beauftragte Hintergrundprozesse —, nicht der Vergleich mit Claude Code.
~~Supermaven~~AutocompleteeingestelltFiel durch sehr großes Kontextfenster und geringe Latenz auf. Anysphere (die Firma hinter Cursor) hat das Team Ende 2024 übernommen, die Technik ist in Cursors Autocomplete aufgegangen. Steht hier als Mahnmal: Jede Toolliste in diesem Kapitel hat ein Verfallsdatum.

Weitere Namen, denen man begegnet, ohne dass sie hier einzeln behandelt werden: Windsurf, Kiro, Devin, Cline, Zed, Goose.

Zwei Beobachtungen zur Landschaft insgesamt sind wichtiger als jede einzelne Zeile der Tabelle:

  1. Die Kategorien verschwimmen. Praktisch jedes Werkzeug bewegt sich in Richtung “Agent”, und der nächste Schritt ist bereits die parallele Orchestrierung mehrerer Agenten an einer Aufgabe. Wer heute ein Werkzeug auswählt, wählt eher ein Ökosystem als eine Funktion.

  2. Harness und Modell sind getrennte Produkte. Das Programm, das Werkzeuge aufruft und den Kontext verwaltet (das Harness), ist bei mehreren Anbietern quelloffen; das Modell dahinter ist es nicht. Für die Lehre ist das praktisch, weil sich dasselbe Harness mit verschiedenen Modellen vergleichen lässt — genau das ist die Aufgabe am Ende dieses Kapitels.

Konzepte, die alle Agenten teilen

Die Produktnamen oben wechseln; die folgenden Begriffe wechseln nicht. Sie beschreiben, wie ein Agent funktioniert, und wer sie einmal verstanden hat, findet sich in jedem der Werkzeuge in einer Viertelstunde zurecht. Die Beispiele sind mit Claude Code formuliert, weil es hier ohnehin im Einsatz ist; jede Aussage gilt sinngemäß für die anderen.

Kontextfenster und Kontext-Engineering

Ein LLM sieht in einem Durchgang genau einen Text: den Kontext. Darin steckt alles — Systeminstruktionen, die bisherige Unterhaltung, die Inhalte gelesener Dateien, die Ausgaben aufgerufener Werkzeuge und die eigentliche Frage. Das Kontextfenster ist die Obergrenze dafür, gemessen in Token (grob: ein Token ist ein Wortteil, ein Absatz Deutsch sind etwa 100 Token).

Daraus folgt fast alles Weitere. Der Kontext ist erstens endlich, zweitens teuer (man bezahlt pro Token) und drittens — das ist der am meisten unterschätzte Punkt — verwässerbar: Ein Modell, dessen Kontext zu 80 % aus irrelevanten Dateien besteht, antwortet schlechter als eines, das nur die drei richtigen Dateien gesehen hat. Mehr Kontext ist nicht besser, passender Kontext ist besser.

Kontext-Engineering ist die Tätigkeit, die sich daraus ergibt: dafür zu sorgen, dass zum richtigen Zeitpunkt das Richtige im Fenster steht. Praktisch heißt das:

  • Aufgaben so schneiden, dass sie mit wenigen Dateien auskommen. Eine Sitzung pro Thema statt einer Sitzung pro Arbeitstag.

  • Dem Agenten sagen, wo er suchen soll, statt ihn das ganze Projekt lesen zu lassen.

  • Lange Werkzeugausgaben (ein voller Testlauf, ein git log) nicht ungefiltert hineinkippen.

  • Den Kontext bewusst leeren, wenn das Thema wechselt. Die alte Unterhaltung kostet bei jeder weiteren Frage erneut Token.

Wenn der Kontext trotzdem volläuft, komprimieren die Werkzeuge ihn: Die bisherige Unterhaltung wird zusammengefasst und die Zusammenfassung ersetzt das Original. Das funktioniert überraschend gut und verliert trotzdem Details — nach einer Kompression lohnt sich die Frage, ob der Agent die Randbedingungen noch kennt.

Projekt-Instruktionsdateien

Die Alternative dazu, dem Agenten in jeder Sitzung dasselbe zu erklären, ist eine Datei im Projekt, die er automatisch liest. Das ist zur tool-übergreifenden Konvention geworden: CLAUDE.md bei Claude Code, AGENTS.md bei mehreren anderen Werkzeugen — dieselbe Idee, verschiedene Namen. Erwartet ein Werkzeug den jeweils anderen Namen, genügt ein Symlink.

Ein Minimalbeispiel, das die vier Dinge enthält, auf die es ankommt:

# CLAUDE.md

## Bauen und Prüfen
```sh
uv sync
uv run pytest
uv run ruff check .
```

## Aufbau
`wesen/engine.py` enthält die Spiellogik, `wesen/ui.py` nur die Ausgabe.
Die Trennung ist Absicht — Logik darf nichts drucken.

## Stil
* Type Hints an allen öffentlichen Funktionen.
* Deutsche Docstrings, englische Bezeichner.

## Nicht ändern
* `data/wesen.json` ist Fremddatenbestand, wird nur gelesen.
* Die Kommandozeilen-Schnittstelle ist festgelegt (Blatt 4).

Die vier Abschnitte beantworten genau die Fragen, bei denen ein Agent sonst rät: Wie baue und prüfe ich? (sonst erfindet er einen Testbefehl), wie ist das Projekt geschnitten? (sonst legt er Dateien an der falschen Stelle an), welcher Stil gilt? (sonst mischt er zwei) und was ist tabu? (sonst räumt er auf, was absichtlich so ist). Der letzte Abschnitt ist der wertvollste und wird am häufigsten vergessen.

Dieses Skript hat selbst eine solche Datei — CLAUDE.md im Wurzelverzeichnis des Repositorys, mit einem Symlink AGENTS.md daneben. Sie enthält die Build-Befehle, die Konventionen für Notebook-Zellen und die Liste der bewussten Entscheidungen, die nicht “korrigiert” werden sollen. Wer wissen will, wie so eine Datei in echt aussieht, findet dort ein Beispiel statt einer Beschreibung.

Zwei Faustregeln aus der Praxis: Die Datei sollte kurz sein (sie belegt in jeder Sitzung Kontext) und sie sollte stimmen. Eine Instruktionsdatei, die einen Testbefehl nennt, den es nicht mehr gibt, ist schlimmer als keine — der Agent probiert ihn, scheitert, und repariert dann womöglich das Falsche.

Skills und Slash-Commands

Instruktionsdateien gelten immer. Für Arbeitsabläufe, die man manchmal braucht, gibt es die kleinere Form: eine Textdatei mit einer Anleitung, die erst geladen wird, wenn sie gebraucht wird. Je nach Werkzeug heißt das Skill, Slash-Command, Prompt oder Rule; das Prinzip ist identisch — ein wiederverwendbarer Arbeitsablauf als Datei im Projekt, versioniert wie Code.

Typische Kandidaten: “so schreibst du hier einen Test”, “so gehst du ein Refactoring an”, “so sieht eine Commit-Message aus”. Der Gewinn ist derselbe wie bei einer Funktion: Man schreibt es einmal auf, statt es jedes Mal zu tippen, und es wird für alle im Team gleich.

Ein ausgearbeitetes Beispiel dazu steht im Kapitel Ein Skill-Dokument für TDD — geschrieben, um es dem eigenen Agenten direkt zu geben.

Planen, dann ausführen

Der teuerste Fehler eines Agenten ist der, den er zwanzig Dateien lang konsequent durchhält. Deshalb haben praktisch alle Werkzeuge einen Modus, in dem der Agent nur liest und einen Plan schreibt, ohne etwas zu ändern. Man liest den Plan, korrigiert die Annahme, die falsch ist, und lässt ihn erst dann laufen.

Das ist kein Komfort-Feature, sondern die billigste Stelle im ganzen Prozess, an der man eingreifen kann: Ein falscher Plan kostet einen Absatz Lesen, eine falsche Umsetzung kostet ein Review über zwanzig Dateien. Die Regel lautet: Je größer die Aufgabe, desto sicherer erst planen lassen. Für “benenne diese Variable um” ist der Modus überflüssig, für “trenne Engine und UI” ist er Pflicht.

Berechtigungen und Sandbox

Ein Agent, der Shell-Kommandos ausführen darf, ist ein Programm, das Shell-Kommandos ausführt — mit allen Rechten des Benutzerkontos. Alle Werkzeuge haben deshalb Abstufungen, die sich stark ähneln:

  1. Nachfragen vor jeder Aktion, die etwas verändert (Standardeinstellung, und für den Anfang die richtige).

  2. Regeln für das, was ohne Nachfrage laufen darf (pytest ja, rm nein). Damit wird die Arbeit erträglich, ohne dass man blind zustimmt.

  3. Freie Ausführung — bequem, und nur dann vertretbar, wenn der Agent in einer Sandbox läuft: Container, virtuelle Maschine oder ein isoliertes Arbeitsverzeichnis.

Drei Dinge sind unabhängig vom Werkzeug richtig. Erstens: im Repository arbeiten, mit sauberem Arbeitsverzeichnis. Solange alles in Git liegt, ist jede Änderung sichtbar und rücknehmbar (git diff, git checkout --). Zweitens: keine Zugangsdaten im Arbeitsverzeichnis. Ein Agent liest Dateien, um seine Aufgabe zu erledigen, und eine .env mit einem API-Schlüssel ist eine Datei. Drittens: Vorsicht bei fremden Inhalten. Alles, was der Agent aus einer Webseite, einem Issue oder einer heruntergeladenen Datei liest, ist Text von jemand anderem — und wenn darin steht “ignoriere deine bisherigen Anweisungen”, dann ist das ein Angriff und keine Kuriosität (prompt injection). Für fremden Code gilt hier dasselbe wie in den Übungsblättern zum Wesen-Projekt: vor dem Ausführen ansehen.

Hooks

Ein Hook ist ein Kommando, das das Werkzeug bei einem bestimmten Ereignis automatisch ausführt — nach jeder Dateiänderung, vor jedem Commit, am Ende einer Sitzung. Der klassische Fall ist ruff format nach jeder Änderung: Dann ist Formatierung nie wieder Gegenstand eines Reviews, weil sie nie wieder falsch sein kann.

Der Unterschied zu einer Instruktion ist wichtig: Eine Instruktion ist eine Bitte, die das Modell befolgen kann oder auch nicht. Ein Hook ist Code, der läuft. Alles, was verlässlich passieren muss, gehört deshalb in einen Hook (oder in die CI) und nicht in eine Markdown-Datei. Das ist dieselbe Unterscheidung wie zwischen einem Kommentar “bitte vorher formatieren” und einem pre-commit-Hook — siehe Code-Qualität.

Sub-Agenten

Ein Agent kann einen weiteren Agenten mit einer Teilaufgabe beauftragen. Der Sub-Agent bekommt einen eigenen, frischen Kontext, arbeitet die Aufgabe ab und gibt nur das Ergebnis zurück. Genau darin liegt der Sinn: Wenn eine Recherche vierzig Dateien lesen muss, aber die Antwort drei Sätze sind, sollen nicht die vierzig Dateien im Hauptkontext landen, sondern die drei Sätze. Der zweite Nutzen ist Parallelität — mehrere unabhängige Teilaufgaben gleichzeitig.

Der Preis: Der Sub-Agent weiß nichts von der bisherigen Unterhaltung. Er braucht eine Aufgabenbeschreibung, die für sich allein steht. Das ist dieselbe Disziplin wie beim Schreiben einer Funktion mit klarer Signatur — und es scheitert aus denselben Gründen, wenn man sie sich spart.

MCP: Werkzeuge als Funktionen

Ein Modell kann von sich aus nur Text erzeugen. Alles andere — Datei lesen, Test ausführen, Datenbank abfragen — passiert, weil das Harness dem Modell eine Liste von Werkzeugen mit Namen, Beschreibung und Parametern anbietet, das Modell eines davon “aufruft” (also einen strukturierten Funktionsaufruf als Text erzeugt) und das Harness die Antwort zurückliefert. Mehr ist an tool calling nicht dran.

Das Model Context Protocol (MCP) standardisiert die Schnittstelle dafür. Statt dass jedes Werkzeug seine eigene Plugin-Architektur erfindet, spricht ein MCP-Server ein festgelegtes Protokoll und kann von jedem MCP-fähigen Agenten benutzt werden. Claude Code, Codex, Cursor, Copilot, Gemini CLI und OpenCode unterstützen es — damit ist ein einmal geschriebener Server überall einsetzbar.

Wer das Kapitel Datenverarbeitung gelesen hat, kennt die Idee bereits von der anderen Seite: Die Funktionen, die man dort einem pydantic-ai-Agenten als Tools übergibt, sind dasselbe Prinzip — eine typisierte Signatur, aus der eine Beschreibung für das Modell erzeugt wird. MCP ist diese Idee als Protokoll zwischen Prozessen statt als Dekorator in einer Datei.

Und derselbe Vorbehalt gilt: Was ein MCP-Server zurückgibt, sind Daten, keine Anweisungen. Ein Server, der auf eine harmlose Anfrage einen Text mit Instruktionen zurückliefert, ist ein Angriffsvektor.

Kosten und Token

Abgerechnet wird nach Token, getrennt nach Eingabe und Ausgabe, wobei Ausgabe-Token deutlich teurer sind. Das hat zwei Konsequenzen, die im Alltag mehr ausmachen als die Wahl des Anbieters:

  • Die Eingabe dominiert. In einer langen Sitzung wird der gesamte bisherige Kontext bei jeder Anfrage erneut übertragen und bezahlt. Eine Sitzung, die seit zwei Stunden läuft, kostet pro Frage ein Vielfaches einer frischen. Caching dämpft das (unveränderte Kontextanfänge werden günstiger abgerechnet), hebt es aber nicht auf.

  • Sich Code ausgeben zu lassen ist teuer, ihn lesen zu lassen ist billig. Deshalb ist “ändere genau diese Funktion” nicht nur besser zu reviewen als “schreibe die Datei neu”, sondern auch günstiger.

Eine Größenordnung fürs Gefühl: Ein mittleres Python-Modul sind wenige tausend Token, ein voller Testlauf-Output kann zehntausende sein. Wer wissen will, wo sein Geld hingeht, schaut zuerst auf die Werkzeugausgaben, nicht auf die eigenen Prompts.

Für die Projektarbeit in diesem Kurs heißt das ganz praktisch: Die kostenlosen Stufen reichen für die Übungsblätter. Wer dabei in Grenzen läuft, hat in der Regel kein Modellproblem, sondern ein Kontextproblem — siehe oben.

Anleitung zum Vibe Coding

Der Begriff ``Vibe coding’’ wurde von Andrej Karpathy (2. Feb. 2025) geprägt:

There’s a new kind of coding I call “vibe coding”, where you fully give in to the vibes, embrace exponentials, and forget that the code even exists. It’s possible because the LLMs (e.g. Cursor Composer w Sonnet) are getting too good. Also I just talk to Composer with SuperWhisper so I barely even touch the keyboard. I ask for the dumbest things like “decrease the padding on the sidebar by half” because I’m too lazy to find it. I “Accept All” always, I don’t read the diffs anymore. When I get error messages I just copy paste them in with no comment, usually that fixes it. The code grows beyond my usual comprehension, I’d have to really read through it for a while. Sometimes the LLMs can’t fix a bug so I just work around it or ask for random changes until it goes away. It’s not too bad for throwaway weekend projects, but still quite amusing. I’m building a project or webapp, but it’s not really coding - I just see stuff, say stuff, run stuff, and copy paste stuff, and it mostly works.

Darin wird die Vorgehensweise beschrieben, sich den generierten Code nicht gründlich anzusehen. Das ist zwar durchaus ein möglicher Zugang (ohne Ironie!), aber wir wollen hier eine andere Arbeitsweise beschreiben, vor dem Hintergrund dass diese ``Vibe coding’'-Arbeitsweise ohne hinreichende Erfahrung schnell eine Komplexitätsdecke erreicht, mit der ein LLM-System allein nicht mehr sinnvoll umgehen kann (und der Nichtprogrammierer dann auch nicht mehr).

Wir benutzen den Begriff jetzt auch im erweiterten Sinne: Wir lassen uns Code generieren und schreiben nicht mehr selbst direkt Zeilen Code. Im etwas engeren Sinne könnte man zur Abgrenzung noch von agentic coding sprechen, wenn man spezielle Sequenzen aus dem LLM-Output ``tool calls’’ an externe Programme füttert und mit dem Output wiederum das LLM in einer Feedbackschleife. Damit kann ein LLM so lange Code verändern, bis Compiler, Linter und Test-Suite durchlaufen. Wir profitieren aber davon, Erfahrungen mit dem einzelnen Schritt zu sammeln (da frühe Fehler im Iterationsprozess Folgefehler nach sich ziehen, und damit tendenziell größere Mengen verschwendeter Rechnungen bewirken).

Diese Erkenntnisse leiten dabei den Ko-Entwicklungsprozess:

  • LLM-Output lesen kostet Zeit und ist teilweise sehr anstrengend, weil der Output vor Allem aussieht wie korrekter Output, aber dennoch subtile Fehler jeder Art enthalten kann

  • Viel LLM-Output generieren kostet viele Token und damit Geld

  • Die Kontextgröße, die ein LLM erfassen kann, ist begrenzt.

  • Auch bei grundsätzlichen großen Kontextfenstern ist die Auswahl von relevantem Kontext eine der nützlichsten Maßnahmen, um Ergebnisse zu verbessern und Token zu sparen.

  • Wir müssen uns vorher überlegen, wie wir den Output sinnvoll bewerten können (woran wir erkennen, dass das Programm tut, was es soll). Entweder ist das Programm kurz genug, damit wir es lesen können, oder es ist anderweitig statisch oder zur Laufzeit prüfbar.

  • Wenn wir Prüfkriterien (Test cases, Type checks, Contracts) haben, können wir ein LLM iterativ dagegen anarbeiten lassen.

  • LLMs sind sehr gut im Extrahieren/Transformieren und erlauben damit facet-oriented development, indem man eine einzelne Facette (z.B. den Happy Path, oder das Exception Handling, das Logging) isoliert entwickelt und das LLM später Code zusammenfügen lässt.

  • Alle ``Entscheidungen’', die man einem LLM überlässt, werden willkürlich getroffen (mit einem Bias zu dem, was im Trainingscorpus vorkam). Insbesondere wird nicht Code für die aktuelleren APIs geschrieben, sondern i.d.R. für ältere, was immer wieder zu Fehlermeldungen oder unnötigen Performance-Einbußen führt.

  • LLMs sind nicht nur gut in Code-Transformationen, sondern auch darin, Dokumentation zu konsultieren und so eine neue API zu verwenden (denn das ist eine Form von Code-Transformation).

  • LLMs sind nicht nur gut in Code-Transformationen, sondern auch in allgemeinen Text-Transformationen. Somit lässt sich auch ein Design-Dokument (Anforderungsheft, Implementierungsentscheidungen mit Begründung) generieren und nach Wünschen anpassen. Das ist oft ein guter erster Schritt bei komplexeren Vorhaben.

  • LLMs haben kein eigenes Gefühl dafür, welche Teile eines Textes oder Codes bewahrenswert sind und welche flüchtig / gerne geändert werden können. Bittet man ein LLM um eine Änderung eines Codes an Stelle A wird daher oftmals Stelle B mit verändert, meist ohne die Semantik wesentlich zu ändern. Mit geringem Kontext wird dabei gern on the fly die API angepasst, was dann zu Konflikten führt. Hier hilft nur das Prinzip der minimalen Änderung.

  • LLMs haben kein eigenes Gefühl dafür, was der Einsatzzweck des Codes werden soll und welche Rahmenbedingungen herrschen (darf man viele Paketabhängigkeiten nutzen? Ist PyTorch für die Aufgabe Overkill und eine händische Methode tut’s auch? Muss es schnell laufen? Benötigt man eine Pause/Resume-Logik bei längeren Aufgaben?)

  • LLMs bevorzugen häufig genutzte Frameworks und sind besser darin, solchen Code zu generieren. Choose a Popular Tech Stack.

Baby steps

Als allerersten Schritt sollte man sich damit vertraut machen, was passiert, wenn man einfach promptet ``Schreibe ein Python-Programm, welches die n-te Fibonacci-Zahl berechnet’'. Dann gibt es zwei nützliche Übungen:

  1. Iterativ am Output herumkritteln und neu generieren lassen (z.B. könnten wir spezifische Anfragen zum User Interface haben, oder Nutzung von Numpy einfordern oder einen Algorithmus, der dynamische Programmierung einsetzt).

  2. Am Ende den finalen Output betrachten, und versuchen, einen Prompt zu schreiben, zu dem das direkt der Output sein könnte. Dabei kann das LLM auch helfen!

  3. Staunen, wie schwierig es ist, exakt zu spezifizieren, was man haben will.

Serious steps

Manchmal kann man nicht einschätzen, wie viel Code eine kleine Aufgabe braucht. Lässt man sich schnell etwas Code generieren, merkt man sofort, ob sich dieser lesen (und dann direkt verwenden) lässt. Bei Instant-Throwaway-Code sind Typenannotationen, Logging usw. oft völlig irrelevant.

Stellt sich heraus (oder ahnte man bereits), dass willkürlich generierter Code die Aufgabe nicht zufriedenstellend lösen wird (oder es zu nervig ist, das zu lesen), arbeitet man mehrstufig:

  1. Schreiben eines Designdokumentes: Grobe Zielformulierung, Pläne zur Aufteilung in Subsysteme/Dateien, zur Verwendung von Bibliotheken. Worauf liegt der Fokus? Woran erkennt man, ob es (gut) geklappt hat?

  2. Einrichten einer Testumgebung anhand des Designdokumentes (in einfachen Fällen, ein Terminal, in dem man das Programm ausführt und den Output betrachtet).

  3. Draft der Datenstrukturen, Datentypen und Interfaces (Hybrid aus Designdokument in Kommentaren und Code ohne Implementierung).

  4. Kritische Teile der Business-Logik einzeln (mit Tests).

  5. Zusammensetzen der Teile, generieren von Glue Code.

  6. Generieren von passenden Type Hints, ggf. Contracts/Pydantic, Assertions, Docstrings.

  7. Energie darauf konzentrieren, Tests zu generieren, die das Erfolgskriterium betreffen.

  8. Offene Frage ``Welche Probleme könnte es mit diesem Code geben’’

Wenn zwischendurch etwas nicht (richtig) gelöst wird, z.B. wenn ein Test dauerhaft nicht erfüllt wird und das LLM keine Lösung findet, muss man das Problem in kleinere Teilprobleme zerlegen (lassen), die man einzeln debuggen kann.

Im agentic coding ist so eine Liste mit Schritten oft selbst als Skill-Dokument kodiert und kann so direkt auf ein Designdokument angewendet werden.

Achtung: im Moment ist es noch so, dass es manchmal viel günstiger ist, statt einer großen Test-Suite den generierten Code zu lesen (und sich zu erbitten, dass der möglichst kurz ist) und zu verstehen, um dämliche Fehler frühzeitig zu finden. Dazu muss man aber relativ gut im Lesen von Code sein, was man unter Anderem durch das eigene Programmieren lernt - und mit LLMs evtl. ein bisschen verlernt. Ein schöner Kompromiss ist ``Erkläre mir Zeilen 23--27, warum ist da XYZ’', um schnell nachzuvollziehen, was passiert.

Ein Feature mit einem Agenten: ein Durchgang, protokolliert

Die Abschnitte oben beschreiben Werkzeuge und Konzepte. Dieser beschreibt einen konkreten Durchgang, vom leeren Auftrag bis zum Commit, mit den Zahlen dazu. Er ist am 25. September 2026 tatsächlich so gelaufen; das Protokoll unten ist gekürzt, aber nicht ausgedacht.

Der Aufbau:

ProjektWesen, festgenagelt auf Commit 89e4d415 — dasselbe Repository wie im Kapitel Brownfield und in den Übungsblättern
ModellOpus 5, mit frischem Kontext: Der Agent wusste nichts über dieses Skript, über das Projekt oder über die Aufgabe, was nicht im Auftrag oder im Repository stand
AufgabeEine zweidimensionale Geometrie-Berechnung aus einer verschachtelten Schleife in eine eigene, getestete Funktion boxBlocks in point.py herausziehen und die Aufrufstelle umstellen
VorgabeTest zuerst, rot sehen, Grund prüfen, dann implementieren — die Reihenfolge aus skill-python-tdd

Die Aufgabe ist absichtlich unspektakulär: ein Refactoring mit einer klar beschreibbaren Semantik und harten Randfällen. Die Welt in Wesen ist ein Torus, das Fenster um eine Position läuft am Rand also um und besteht dann aus zwei oder vier Rechtecken statt aus einem. Das ist prüfbar, und es ist die Sorte Sache, bei der man einem Agenten nicht ansieht, ob er sie richtig gemacht hat.

Schritt 0: die Instruktionsdatei

Vor dem Auftrag steht die CLAUDE.md — geschrieben von mir, nicht vom Agenten, denn sie enthält genau das Wissen, das er nicht raten kann. Die entscheidenden Zeilen daraus:

## Bauen und Prüfen
```sh
uv run python -m unittest discover -s tests -t . -p '*.py'
```
**Die Tests laufen über `unittest`, nicht über `pytest`.** Das Suchmuster ist `*.py`, nicht
`test_*.py`. Ein `pytest`-Aufruf findet in diesem Projekt **keine** Tests und meldet
trotzdem Erfolg; das ist die häufigste Fehlannahme.

## Nicht ändern
1. **Die Spielregeln.** … macht alle bisherigen Turnierergebnisse ungültig.
3. **`tests/determinism.py`.** Spielt einen Seed in zwei frischen Interpretern und
   vergleicht. Wird er rot, ist eine Änderung nicht mehr reproduzierbar — das ist ein
   Befund, kein Testproblem.
4. **Namen bestehender öffentlicher Funktionen** bleiben.

Halten Sie diese vier Punkte im Kopf; wir sehen am Ende nach, welche davon etwas bewirkt haben.

Schritt 1: der Test, und was „rot" wirklich heißt

Der Agent legte tests/geometry.py an — nicht tests/test_geometry.py, weil die Instruktionsdatei das Suchmuster nennt — und ließ ihn laufen, bevor boxBlocks existierte:

ERROR: tests.geometry (unittest.loader._FailedTest.tests.geometry)
ImportError: Failed to import test module: tests.geometry
  File ".../tests/geometry.py", line 22, in <module>
    from Wesen.point import boxBlocks, ringBlocks
ImportError: cannot import name 'boxBlocks' from 'Wesen.point'
Ran 1 test — FAILED (errors=1)

Und dann tat er etwas, das im Auftrag nicht stand und das die interessanteste Stelle des ganzen Durchgangs ist. Aus seinem Bericht:

Das ist rot aus dem richtigen Grund, aber es ist ein Import-Fehler auf Modulebene und beweist damit nichts über die Testkörper.

Ein ImportError beim Import der Testdatei bedeutet, dass keine einzige Zusicherung ausgeführt wurde. Der Test ist rot, ja — aber er wäre auch rot, wenn alle assert-Zeilen darin leer wären. Der Agent hat deshalb absichtlich eine falsche Implementierung eingesetzt (ein Rechteck, Wrap ignoriert) und nochmal gemessen:

Ran 13 tests — FAILED (failures=726)

726 Fehlschläge aus 13 Testmethoden — die Zahlen passen zusammen, weil der erschöpfende Test unittest.subTest benutzt:

for x in range(length):
    for y in range(length):
        for radius in range(length + 2):
            with self.subTest(x=x, y=y, radius=radius):
                self.assertCovers((x, y), radius, length)

subTest macht aus einer Testmethode so viele einzeln gemeldete Fälle, wie die Schleife Durchläufe hat — hier 9 × 9 × 11 = 891, von denen 726 gegen die falsche Implementierung gescheitert sind. Der Gewinn gegenüber einem assert in der Schleife ist, dass jeder Fall gemeldet wird statt nur der erste; das ist bei einer Randfall-Matrix genau das, was man wissen will. Erst damit ist belegt, dass die Zusicherungen scharf sind. Danach setzte er die Datei aus einer Kopie zurück und implementierte richtig.

Das ist die Lektion, die man selten aufgeschrieben findet: „Der Test ist rot" ist zwei verschiedene Aussagen. Rot, weil das Verhalten fehlt — das ist der Beweis, den man haben will. Oder rot, weil der Test nicht einmal startet — das ist gar kein Beweis. Bei einer neuen Datei ist der erste rote Lauf fast immer die zweite Sorte.

Schritt 2 bis 4: implementieren, Aufrufstelle, prüfen

Die Implementierung lief im ersten Versuch durch alle 13 Tests. Die einzige Nachbesserung war kosmetisch: ruff format --check wollte zwei Zeilen anders umbrechen.

Die Aufrufstelle wurde danach in einem eigenen Commit umgestellt, und die Schleife verschwand wie vorgesehen:

-        for x0, x1 in ringBlocks(x, radius, length):
-            rows = counts[x0:x1]
-            for y0, y1 in ringBlocks(y, radius, length):
-                xs, ys = np.nonzero(rows[:, y0:y1])
-                if xs.size:
-                    for cx, cy in zip(
-                        (xs + x0).tolist(), (ys + y0).tolist()
-                    ):
-                        cells.append(grid[cx][cy])
+        for block in boxBlocks((x, y), radius, length):
+            xs, ys = np.nonzero(counts[block])
+            if xs.size:
+                x0 = block[0].start

Die gewählte Signatur gibt tuple[tuple[slice, slice], ...] zurück — jeder Block ein Paar aus Zeilen- und Spalten-slice, sodass der Aufrufer grid[block] schreiben kann. Diese Entscheidung hatte ich offen gelassen, und sie ist gut begründet.

Bemerkenswert ist eine Nebenentscheidung, die der Agent aus der Instruktionsdatei abgeleitet hat: Die Reihenfolge der Blöcke ist nicht beliebig, weil sie die Reihenfolge ist, in der ein Wesen die Objekte um sich herum antrifft — und tests/determinism.py hängt daran. Er hat sie deshalb mit einem eigenen Test festgenagelt (test_isTheProductOfRingBlocks), damit ein späterer Umbau sie nicht still umsortiert. Der Anlass dafür stand in der CLAUDE.md, nicht im Auftrag.

Das Ergebnis, mit Zahlen

Dauer3 min 58 s vom Auftrag bis zum Bericht
Token87.956
Werkzeugaufrufe18
Commits2
Diff3 Dateien, 254 Zeilen ergänzt, 16 entfernt — davon 214 Zeilen Test
Testsuitevorher 113 Tests, nachher 126, beide Male OK
Linter / Typecheckerruff check grün, ruff format --check grün (72 Dateien), mypy src grün (30 Dateien)

Ich habe das nachgeprüft, statt es zu glauben: Testsuite, Linter und Typechecker selbst laufen lassen, den Diff gelesen, das Arbeitsverzeichnis auf Sauberkeit geprüft. Alle Angaben stimmen.

Zwei Zahlen lohnen einen zweiten Blick. 214 der 254 ergänzten Zeilen sind Test — das Verhältnis ist typisch für diese Arbeitsweise und nicht schlecht, sondern der Grund, warum man dem Ergebnis trauen kann. Und 87.956 Token für vier Minuten Arbeit: Wer die aktuellen Preise seines Anbieters kennt, kann sich daraus ausrechnen, was ein solcher Durchgang kostet; die Größenordnung ist „Kleingeld", und das ist genau der Grund, warum die Prüfung des Ergebnisses und nicht seine Erzeugung der teure Teil ist.

Ein kleiner, ehrlicher Nebenbefund: Auf die Frage, wie viele Werkzeugaufrufe er gemacht habe, antwortete der Agent „etwa 13". Es waren 18. Selbstauskünfte von Modellen über ihr eigenes Verhalten sind Schätzungen, auch wenn sie als Zahl daherkommen — die belastbaren Werte kommen aus dem Werkzeug, nicht aus dem Modell.

Was die Instruktionsdatei bewirkt hat

Dies ist der Teil, für den der ganze Durchgang gemacht ist. Aus dem Bericht des Agenten, zur Frage, ob sein erster Versuch beim Testbefehl richtig war:

Mein erster Versuch war richtig, weil CLAUDE.md es ausdrücklich sagt und warnt, dass ein pytest-Aufruf in diesem Projekt keine Tests findet und trotzdem Erfolg meldet. Ohne diese Warnung hätte ich sehr wahrscheinlich pytest aufgerufen, grün gesehen und es geglaubt.

Lesen Sie den letzten Satz zweimal. Der Fehler wäre nicht „der Agent findet die Tests nicht" gewesen, sondern „der Agent hält ein leeres Ergebnis für ein gutes" — und hätte dann eine Änderung als geprüft gemeldet, die nie geprüft wurde. Ein einziger Satz in einer Textdatei hat das verhindert. Das ist der konkrete Gegenwert einer Projekt-Instruktionsdatei, und er lässt sich schwer eindrucksvoller zeigen.

Vier Punkte der Datei haben nachweisbar etwas geändert:

Zeile in CLAUDE.mdWirkung
unittest, nicht pytest, Muster *.pyrichtiger Testbefehl, Datei heißt tests/geometry.py statt test_geometry.py
„Die Welt ist ein Torus"die Liste der Randfälle: kein Wrap, Wrap in x, in y, in beiden, Radius über die ganze Welt
„tests/determinism.py … ein Befund, kein Testproblem"Block-Reihenfolge wurde als Verhalten behandelt und eigens festgenagelt
„Namen bestehender öffentlicher Funktionen bleiben"ringBlocks blieb öffentlich; boxBlocks baut darauf auf statt es zu ersetzen

Und was er raten musste

Genauso lehrreich ist die andere Liste — sie ist die To-do-Liste für die nächste Fassung der CLAUDE.md:

  • Der Name der Testdatei. Die Tabelle sagt nur „benannt nach Thema". Er nahm geometry.py; das ist vernünftig und hätte auch point.py heißen können.

  • Die Rückgabeform von boxBlocks. Das hatte ich absichtlich offen gelassen — aber es gibt im Projekt auch keine Konvention für Geometrie-Rückgaben, an der man sich orientieren könnte.

  • Ob Type Hints dazugehören. point.py hat nirgends welche, die Stilregel verlangt sie „an allem, was mypy src erreicht". Er hat nur die neue Funktion annotiert und den Bestand gelassen — mit der Begründung, ein pauschaler Annotations-Durchgang sei eine eigene Entscheidung. Genau richtig, und genau die Sorte Abwägung, die man nicht jedes Mal neu treffen lassen will.

Dass mypy hier nicht strict läuft, hat er bemerkt und ausdrücklich nicht als Erlaubnis gelesen, die Annotationen weglassen zu dürfen.

Was dieser Durchgang zeigt — und was nicht

Er zeigt: Eine klar abgegrenzte Aufgabe mit prüfbarem Erfolgskriterium, in einem Projekt mit einer brauchbaren Instruktionsdatei und einer laufenden Testsuite, ist in vier Minuten und für Kleingeld erledigt — inklusive Tests, die besser sind als die, die man von Hand geschrieben hätte, weil niemand freiwillig einen erschöpfenden Test über alle Positionen einer 9×9-Welt tippt.

Er zeigt nicht, dass das immer so läuft. Alle vier Voraussetzungen waren erfüllt, und jede einzelne hat der Mensch hergestellt, nicht der Agent:

  1. Die Aufgabe war klein und abgeschlossen — eine Funktion, eine Aufrufstelle.

  2. Das Erfolgskriterium war maschinell prüfbar — 113 bestehende Tests, die grün bleiben mussten.

  3. Die Instruktionsdatei enthielt das nicht-erratbare Wissen.

  4. Die Semantik war vorher aufgeschrieben — im Docstring der alten Methode stand bereits „covered by at most four rectangular blocks".

Fehlt einer dieser vier Punkte, sieht das Protokoll anders aus. Fehlen drei, ist man beim Vibe Coding aus dem Abschnitt Anleitung zum Vibe Coding oben — was ebenfalls legitim ist, aber nichts liefert, dem man ohne Lesen trauen dürfte.

Übungen

Die folgenden fünf Aufgaben sind absichtlich so gewählt, dass sie mit einer kostenlosen Stufe eines beliebigen Werkzeugs zu machen sind. Keine davon verlangt, dem Ergebnis zu trauen.

  1. Erklären lassen. Nehmen Sie einen Codeschnipsel, den Sie nicht geschrieben haben — etwa gen7engine.py aus Generierte Wirtschaftssimulation — und lassen Sie sich fünf zusammenhängende Zeilen erklären. Dann prüfen Sie die Erklärung gegen den Code. Notieren Sie jede Stelle, an der die Erklärung plausibel klang und trotzdem nicht stimmte.

  2. Eine ganze Datei analysieren. Lassen Sie sich dieselbe Datei als Ganzes zusammenfassen: Welche Verantwortlichkeiten hat sie, welche Abhängigkeiten, wo liegt der Zustand? Vergleichen Sie die Antwort mit dem, was ruff, mypy und ein Blick in die Datei ergeben. Wo hat das Modell etwas gesehen, das Sie übersehen haben — und umgekehrt?

  3. Zwei Modelle gegeneinander. Geben Sie denselben Prompt für ein kleines, vollständiges Programm an zwei verschiedene Modelle (oder an dasselbe Harness mit zwei Modellen, siehe Tabelle oben). Vergleichen Sie nicht, welches “besser” ist, sondern worin sie sich unterscheiden: Bibliothekswahl, Fehlerbehandlung, Länge, Alter der verwendeten API.

  4. Tests generieren und misstrauen. Lassen Sie sich Tests für Ihr CYOA-Programm aus Blatt 1–4 schreiben. Suchen Sie darin den Test, der nichts prüft (assert True, ein Test ohne Assertion, ein Test, der nur den Aufruf ausführt). Schiedsrichter ist Mutation Testing: Wenn pytest --gremlins Mutanten überleben lässt, obwohl alle Tests grün sind, haben Sie ihn gefunden. Siehe Testing.

  5. Kontext messen. Stellen Sie dieselbe Frage zweimal: einmal am Anfang einer frischen Sitzung, einmal nach einer Stunde Arbeit im selben Fenster. Vergleichen Sie Antwortqualität und — falls Ihr Werkzeug es anzeigt — Token-Verbrauch. Das ist der Abschnitt über Kontext als Selbstversuch.

Zusammenfassung und Kontrollfragen

  1. Ein Agent ist keine Autonomie, sondern eine Schleife: Kontext lesen, Werkzeug aufrufen, Ergebnis lesen, weitermachen — bis ein Abbruchkriterium erfüllt ist.

  2. Das Kontextfenster ist endlich, kostet Geld und lässt sich verwässern; passender Kontext schlägt viel Kontext, und das ist der wirksamste Hebel, den man hat.

  3. Projekt-Instruktionsdateien beantworten dauerhaft, was sonst in jeder Sitzung geraten wird; Skills tun dasselbe für Abläufe, die man nur manchmal braucht.

  4. Was verlässlich passieren muss, gehört in einen Hook oder in die CI — eine Instruktion kann das Modell befolgen oder auch nicht, ein Hook ist Code, der läuft.

  5. Alles, was ein Agent aus einer Webseite, einem Issue oder einer Datei liest, ist Text von jemand anderem; Berechtigungsstufen und eine Sandbox sind deshalb keine Bequemlichkeitsfrage.

Kontrollfragen

  1. Ein Agent bekommt dieselbe Aufgabe zweimal: einmal mit den drei relevanten Dateien im Kontext, einmal mit dem gesamten Projekt. Warum ist das zweite Ergebnis oft schlechter — und warum ist es in jedem Fall teurer?

  2. Sie wollen sicherstellen, dass nach jeder Änderung ruff format läuft. Warum genügt dafür ein Satz in der CLAUDE.md nicht, und was nehmen Sie stattdessen?

  3. Nennen Sie die vier Fragen, die eine Projekt-Instruktionsdatei beantworten sollte. Welche davon fehlt nach Erfahrung am häufigsten, und was ist die Folge?

Weitere Lektüre

  • Claude Code: Anthropic – Dokumentation zum Terminal-nativen Agenten.

  • Codex CLI: openai/codex – quelloffenes Harness (Apache-2.0), lesenswert als Beispiel dafür, wie ein Agent intern aufgebaut ist.

  • Gemini CLI: google-gemini/gemini-cli – ebenfalls quelloffen (Apache-2.0).

  • Model Context Protocol: modelcontextprotocol.io – Spezifikation und SDKs, u.a. für Python.

  • Cursor vs. VS Code: Review-Blog – Warum “Multi-File Edits” den Workflow verändern.

  • OpenClaw: freeCodeCamp Tutorial – Architektur eines persönlichen Hintergrund-Agenten (kein Coding-Agent).

  • Google Antigravity: Google Codelab – Einstieg in die orchestrative “Manager View”.