KI-Agenten Aufgaben stellen: eine Funktion nach der anderen, Funktionsliste und Fertigkriterien
Einem KI-Agenten stellt man Aufgaben einzeln: Immer genau eine Funktion ist in Arbeit, sie hat ein beobachtbares Verhalten und einen ausführbaren Prüfbefehl, und alles unterwegs Bemerkte schreibt der Agent in ein Backlog, statt es zu reparieren. Den Status aller Funktionen hält eine maschinenlesbare Liste (zum Beispiel feature_list.json), und auf »fertig« setzen darf sie nur das Prüfskript, nicht der Agent selbst.
Warum der Agent alles auf einmal anpackt
Sie bitten den Agenten, eine Registrierung zu bauen. Ein paar Stunden später umfasst der Diff zwölf geänderte Dateien und achthundert neue Zeilen: Benutzermodell, E-Mail-Bestätigung, angebundener Mailservice, Passwort-Hashing, umgebaute Middleware und umgezogene Tests. Alles hängt eng zusammen, und kein einziges Szenario läuft durchgängig. Die nächste Sitzung öffnet diesen Code und weiß nicht, wo sie anfangen soll.
Der Grund liegt nicht in einem »schlechten Modell«. Ein Mensch hat das Gefühl »für heute reicht es« und normale Müdigkeit – beides begrenzt den Umfang seiner Arbeit von selbst. Der Agent hat weder das eine noch das andere. Der Satz »und nebenbei korrigiere ich noch die Fehlerbehandlung« kostet ihn ein paar Dutzend Token, für das Projekt aber ist der Preis real: Aufmerksamkeit verteilt sich, die Kopplung wächst, und fertig wird nichts.
Dazu kommt eine einfache Rechnung. Sei C die nutzbare Kapazität des Kontexts und k die Zahl der Aufgaben, die der Agent gleichzeitig hält. Auf jede entfallen etwa C/k an Denkressourcen. Sinkt dieser Wert unter das, was zum Abschluss einer einzelnen Aufgabe nötig ist, wird keine davon fertig. Die richtige Antwort auf die Frage, wie man einem KI-Agenten Aufgaben stellt, beginnt deshalb nicht bei der Formulierung des Prompts, sondern bei einer Grenze: eine Aufgabe in Arbeit.
Die Ein-Funktion-Regel (WIP=1)
WIP steht für Work in Progress, die Zahl der Aufgaben in Arbeit. Für den Agenten setzt man sie auf eins: Zu jedem Zeitpunkt ist genau eine Funktion aktiv, die nächste beginnt erst, wenn die aktuelle die Prüfung bestanden hat. Die Regel schreibt man in die Projektanweisungen (AGENTS.md oder CLAUDE.md), damit der Agent sie in jeder Sitzung sieht:
## Arbeitsregeln
- Zu jedem Zeitpunkt ist GENAU EINE Funktion im Status `active`.
- Die nächste Funktion erst beginnen, wenn die aktuelle in `passing` übergegangen ist.
- Verboten ist, »nebenbei« umzubauen, umzubenennen, Verzeichnisse umzuordnen
und nicht zusammenhängende Fehler zu beheben. Ein Problem gefunden – in
`BACKLOG.md` notieren und weiterarbeiten.
- Änderungen außerhalb des Umfangs der aktiven Funktion gelten als Verstoß,
auch wenn sie nützlich sind.
Der letzte Punkt ist wichtiger, als es scheint. Der Agent hält seine »nebenbei«-Änderungen für nützlich, und oft stimmt das sogar. Aber eine nützliche Änderung außerhalb des Umfangs vermischt zwei Aufgaben in einem Diff, und dann lassen sich beide nicht mehr einzeln zurücknehmen oder prüfen.
BACKLOG.md: wohin mit dem »nebenbei«
Den Drang, »nebenbei« etwas zu reparieren, kann man nicht einfach verbieten – er braucht einen erlaubten Ausweg. Dieser Ausweg ist die Datei BACKLOG.md: Der Agent notiert das bemerkte Problem in ein oder zwei Zeilen und kehrt zur aktuellen Funktion zurück. Die Information geht nicht verloren, und der Umfang wächst nicht. Ohne Backlog wird der Agent die Ein-Funktion-Regel früher oder später umgehen, weil ihm das stille Liegenlassen eines Fehlers schlimmer erscheint als der Verstoß.
# BACKLOG.md
- [ ] middleware/errors.py: unterschiedliches Fehlerformat in /auth und /orders
- [ ] tests/: Benutzer-Fixtures sind in drei Dateien dupliziert
- [ ] Ist eine E-Mail-Bestätigung bei der Registrierung nötig? – nachfragen
Fertigkriterien: »fertig« ist ein Befehl
Die zweite Hälfte des Problems: Der Agent entscheidet selbst, dass eine Aufgabe erledigt ist. »Der Code sieht gut aus«, »die Funktion existiert«, »es gibt keine Compile-Fehler« – das sind Eindrücke, keine Fakten. Das Fertigkriterium einer Aufgabe muss für den Agenten ein ausführbarer Befehl mit eindeutigem Ergebnis sein: bestanden oder nicht.
F01: Benutzerregistrierung
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active
Der Unterschied zwischen einem schwachen und einem strengen Kriterium zeigt sich gut an Beispielen:
| Schwaches Kriterium | Strenges Kriterium |
|---|---|
| Der Code kompiliert | pytest tests/e2e/test_cart.py -q läuft durch |
| Es gibt keine Syntaxfehler | curl auf /cart liefert Code 200 |
Die Funktion addToCart existiert | Nach dem Hinzufügen eines Artikels zeigt GET /cart ihn und die richtige Summe |
| Es sieht richtig aus | npx playwright test cart.spec.ts läuft durch |
Ein guter Prüfbefehl ist deterministisch (gleicher Code, gleiches Ergebnis), läuft ohne Mitwirkung eines Menschen und prüft das Verhalten von außen – so, wie es der Nutzer oder der API-Client sieht. Gelingt es nicht, einen solchen Befehl zu schreiben, ist das ein Signal, dass die Aufgabe unscharf formuliert ist.
Zerlegung: wie groß eine Funktion sein sollte
Die häufigste Frage nach »eine Funktion nach der anderen« lautet: Was gilt als Funktion? Eine zu breite Aufgabe wird in einer Sitzung nicht fertig, eine zu schmale geht im Aufwand für Start, Kontext und Prüfung unter.
- Zu breit: »Warenkorb implementieren«. Das ist eine Woche Arbeit mit einem Dutzend Szenarien.
- Passend: »Der Nutzer kann einen Artikel in den Warenkorb legen und ihn dort sehen«. Ein beobachtbares Verhalten, ein Prüfszenario.
- Zu klein: »Feld quantity im Modell CartItem anlegen«. Das ist ein Implementierungsschritt, kein Verhalten; ihn separat zu prüfen hat keinen Sinn.
Eine praktische Faustregel für die Größe: eine Funktion = eine Agentensitzung = ein sinnvoller Commit = ein durchgängiges (E2E) Szenario. Passt eine Funktion nicht in eine Sitzung, zerlegt man sie weiter. Lässt sich kein eigenes Szenario dafür schreiben, ist sie zu klein und wird mit der Nachbarfunktion zusammengelegt.
Beispiel einer Zerlegung: der Warenkorb
Die breite Anforderung wird in atomare Einheiten zerlegt, jede mit einem einzigen Verhalten und eigenem Prüfbefehl:
- Einen Artikel in den Warenkorb legen und ihn in der Liste sehen.
- Die Menge eines Artikels ändern – die Summe wird neu berechnet.
- Einen Artikel aus dem Warenkorb entfernen.
- Der Warenkorb eines Gastes bleibt nach der Anmeldung erhalten.
- Einen nicht vorrätigen Artikel nicht hinzufügen können – mit einer verständlichen Fehlermeldung.
Abschlusstest der Zerlegung: Keine Einheit erfordert, dass man »nebenbei« eine andere anfasst. Müsste man für Punkt 4 den Punkt 1 umschreiben, sind Reihenfolge oder Grenzen ungünstig gewählt.
Die Funktionsliste: feature_list.json
Die Ein-Funktion-Regel hält die Ordnung innerhalb einer Sitzung. Doch wer erinnert sich zwischen den Sitzungen daran, was schon fertig ist? Eine Notiz im Chat wie »Auth und Katalog sind fertig, der Warenkorb fast, es gibt aber Fehler« hilft einer neuen Sitzung nicht: Was heißt »fast«, welche Tests laufen durch, was blockiert die Zahlung? Das bleibt unbekannt. Jede neue Sitzung gleicht einem neuen Fahrer desselben Autos: Lebten die Notizen nur im Kopf des Vorgängers, weiß der Nachfolger nichts.
Deshalb verlagert man den Zustand nach außen, in eine maschinenlesbare Datei. Im Kurs ist das .agent/feature_list.json:
[
{
"id": "F01",
"behavior": "Der Nutzer registriert sich mit E-Mail und Passwort und ist sofort angemeldet",
"verification": "pytest tests/e2e/test_auth.py::test_register_flow",
"state": "passing",
"evidence": "a3f9c12: 1 passed in 2.31s"
},
{
"id": "F02",
"behavior": "Der Nutzer legt einen Artikel in den Warenkorb und sieht ihn in der Liste",
"verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
"state": "active"
},
{
"id": "F03",
"behavior": "Bezahlung einer Bestellung per Karte",
"verification": "pytest tests/e2e/test_payment.py -q",
"state": "blocked",
"blocker": "keine Testschlüssel des Zahlungsgateways vorhanden"
}
]
Jeder Eintrag hat drei Pflichtfelder: die Beschreibung des von außen beobachtbaren Verhaltens, den Prüfbefehl (ausführbar, ohne Mensch) und den Status. Ein optionales viertes Feld ist evidence: der Commit-Hash und die Ausgabe der Prüfung als Beleg, dass der Übergang in passing tatsächlich stattgefunden hat.
Warum JSON und keine Markdown-Liste? Die Datei lesen nicht nur der Agent, sondern auch Skripte: Der Planer wählt die nächste Funktion im Status not_started, der Verifizierer holt den Prüfbefehl, der Sitzungsbericht entsteht automatisch, und die Verteilung der Zustände liefert Kennzahlen zur Gesundheit des Projekts. Die Funktionsliste ist das Rückgrat des Harness, keine Gedächtnisstütze.
Vier Zustände und erlaubte Übergänge
| Übergang | Wann |
|---|---|
not_started → active | Die Funktion wird begonnen; eine andere ist nicht aktiv |
active → passing | Das Prüfskript endete mit Code 0 |
active → blocked | Externe Blockade; der Grund steht im Feld blocker |
blocked → active | Die Blockade ist aufgehoben |
Andere Übergänge gibt es nicht. Insbesondere gibt es weder einen Weg nach passing an der Prüfung vorbei noch einen Zustand »fast fertig«.
Das Gate passing: Der Agent darf anfragen, aber nicht erklären
Die zentrale Regel des ganzen Schemas: Der Agent bearbeitet das Feld state nicht von Hand. Er kann die Prüfung starten, das Ergebnis aber nicht selbst festlegen – das erledigt das Skript. Der Unterschied zwischen »der Agent hat gemeldet« und »das Skript hat bestätigt« ist der Unterschied zwischen Text und Tatsache. Auch diese Regeln stehen in der Anweisungsdatei:
## Regeln für die Funktionsliste
- Datei: `.agent/feature_list.json`. Sie ist die einzige Quelle für den Umfang.
- Genau eine Funktion im Status `active`.
- Du bearbeitest das Feld `state` NICHT von Hand. Es wird von `scripts/verify.sh` gesetzt.
- Um eine Funktion auf `passing` zu setzen, führe `./scripts/verify.sh F02` aus und erreiche Code 0.
- Ist eine Funktion blockiert, setze sie auf `blocked` und gib unbedingt `blocker` an.
- Neue Funktionen werden nur als `not_started` und nur mit Prüfbefehl hinzugefügt.
Das Skript prüft auf zwei Ebenen: zuerst den allgemeinen Lauf des Projekts (die Funktion darf nichts anderes kaputt machen), dann den Prüfbefehl der konkreten Funktion. Lokaler Erfolg bei einer globalen Regression gilt nicht als Erfolg.
#!/usr/bin/env bash
# Die einzige Komponente, die passing setzen darf.
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"
CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
echo "FAIL: kein Prüfbefehl für $FID"; exit 2
fi
echo "== globale Prüfung =="
make check || { echo "FAIL: allgemeine Prüfung rot, $FID kann nicht passing werden"; exit 1; }
echo "== Prüfung der Funktion: $CMD =="
if OUT=$(eval "$CMD" 2>&1); then
SHA=$(git rev-parse --short HEAD)
LAST=$(printf '%s\n' "$OUT" | tail -n 1)
TMP=$(mktemp)
jq --arg id "$FID" --arg ev "$SHA: $LAST" \
'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
"$FILE" > "$TMP" && mv "$TMP" "$FILE"
echo "PASS: $FID"
else
printf '%s\n' "$OUT"
echo "FAIL: $FID"; exit 1
fi
Die Regel »bearbeite state nicht« im Text ist immer noch eine Bitte. Damit daraus eine Einschränkung wird, lässt sich das Schreiben in die Datei mechanisch sperren: durch eine Prüfung in der CI, die evidence mit der Commit-Historie abgleicht, oder durch eingeschränkte Rechte des Agenten im Werkzeug. Das Prinzip ist dasselbe wie überall im Harness: Eine wichtige Regel wird durch Mechanik abgesichert.
Wie man misst, dass der Agent Aufgaben zu Ende bringt
Eine verlockende Kennzahl ist das Volumen des Codes pro Sitzung. In der Praxis wirkt sie umgekehrt: Ein großer Diff bedeutet häufiger, dass der Agent sich auf mehrere Richtungen verteilt hat. Gezählt werden sollten Funktionen im Status passing, nicht Zeilen.
Eine praktische Kennzahl aus dem Kurs ist VCR (Verified Completion Rate): die Zahl der verifizierten Aufgaben geteilt durch die Zahl der aktivierten. Die Regel dazu ist einfach: Solange VCR kleiner als eins ist, dürfen keine neuen Aufgaben aktiviert werden. Das ist der Gegendruck, den das Harness auf den Agenten ausübt, und zugleich eine Möglichkeit, Unvollendetes als Zahl sichtbar zu machen statt als Gefühl.
Typische Fehler beim Stellen von Aufgaben
- Aufgabe ohne Prüfbefehl. »Bau die Registrierung« ohne Szenario – der Agent entscheidet selbst, wo er aufhört, und entscheidet falsch.
- Mehrere Funktionen in einer Anfrage. »Bau den Warenkorb und die Zahlung« sind zwei aktive Aufgaben, und keine wird zu Ende gebracht.
- Zustand nur im Chat. Der Fortschritt, der im Gespräch festgehalten wird, verschwindet mit dem Ende der Sitzung. Er muss in einer Datei leben.
- Selbstbericht statt Prüfung. »Der Agent sagt, die Tests laufen durch« ist nicht dasselbe wie der Rückgabecode des Skripts.
- Kein Backlog. Ein Verbot des »Nebenbei« ohne Ort, an dem das Bemerkte notiert werden kann, wird der Agent umgehen.
Wenn eine Funktion pro Schritt nicht mehr reicht
Das Schema »eine Funktion – eine Sitzung – eine Prüfung« funktioniert gut für einen Agenten in einem Zyklus. Wenn viele Aufgaben anfallen und unabhängige Zweige entstehen, erweitert man es zu einem System aus mehreren Zyklen: ein eigener Planungsknoten, Umsetzungsknoten, ein Prüfknoten und Kanten dazwischen – parallele, bedingte, mit Rückkehr zur Umsetzung, falls die Prüfung scheitert. Doch auch dort arbeitet jeder Umsetzungsknoten nach derselben Regel: eine Aufgabe, ein ausführbares Fertigkriterium, ein Zustand außerhalb des Kopfes des Agenten. Der Graph hebt die Disziplin der einen Funktion nicht auf, sondern baut auf ihr auf.
Eine wichtige Warnung aus derselben Quelle: Der Zyklus optimiert ehrlich die Kennzahl, die man ihm gibt. Ist das Fertigkriterium schlecht gewählt – etwa »Ticket geschlossen« statt »Problem gelöst« –, lernt der Agent, das Kriterium zu erfüllen, nicht die Aufgabe. Deshalb verdienen die Formulierung des Verhaltens und des Prüfbefehls mehr Zeit, als man denkt.
Kurz gesagt
Kurz gesagt stellt man einem KI-Agenten Aufgaben einzeln, mit beobachtbarem Verhalten und ausführbarem Prüfbefehl, mit einem Backlog für alles unterwegs Bemerkte und mit einer Funktionsliste in einer Datei, in der den Übergang auf »fertig« das Skript vollzieht, nicht der Agent. Die Bremse setzt beim Agenten das Harness – er legt sie sich nicht selbst ein. Die WIP=1-Regel, feature_list.json und verify.sh werden im Kurs zum Harness-Engineering an einem durchgehenden Projekt praktisch durchgespielt.
Häufige Fragen
Kann ich dem Agenten mehrere Aufgaben auf einmal geben, wenn sie klein sind?
Sie können in der Funktionsliste stehen, aber gleichzeitig darf nur eine in Arbeit sein. Der Agent nimmt die nächste erst, wenn die aktuelle die Prüfung bestanden hat – so wird jede Aufgabe zu Ende gebracht.
Was tun, wenn sich für eine Aufgabe keine automatische Prüfung schreiben lässt?
Meist ist die Aufgabe als Handlung formuliert statt als Verhalten. Formulieren Sie sie um zu dem, was der Nutzer oder der API-Client sieht; für den visuellen Teil eignen sich E2E-Tests im Browser.
Wer schreibt feature_list.json – der Mensch oder der Agent?
Die Anfangsliste erstellt man am besten gemeinsam: Der Agent schlägt die Zerlegung vor, der Mensch korrigiert sie. Danach darf der Agent neue Funktionen ergänzen, aber nur im Status not_started und mit Prüfbefehl.
Worin unterscheidet sich feature_list.json von Aufgaben im Ticketsystem?
Ein Ticketsystem ist für Menschen gedacht, die Funktionsliste für das maschinelle Lesen: Jeder Eintrag hat einen ausführbaren Befehl und einen strengen Status, den das Skript setzt. Beides lässt sich synchronisieren, doch der Agent braucht die Datei im Repository.
Bremst die Ein-Funktion-Regel die Arbeit nicht aus?
Die Sitzungen werden kürzer, aber der Anteil der vollendeten Aufgaben steigt. Die Zeit, die früher für das Durchsehen halbfertigen Codes aus drei vermischten Funktionen draufging, wird gespart.
Dieses Thema wird im Kurs „Harness Engineering: инженерия надёжности AI-агентов“ ausführlich mit Praxis behandelt.
- feature_list.json для AI-агента: WIP=1, статусы фич и верификация через verify.sh
- Graph Engineering для AI-агентов: когда одного цикла мало и нужен граф задач