Agent braucht Dokumentation statt Gedächtnis: Repos für Menschen und Agenten

1. Überblick

Coding-Agenten werden peu à peu zu „Teamsmitgliedern”: Sie lesen Code, beheben Bugs, schreiben Tests und öffnen Pull Requests. Das Problem: Agenten merken sich nichts zwischen den Sitzungen. Es gibt Tools, die eine „Memory”-Funktion bieten, aber dieser Speicher hängt meist an einer bestimmten Person, ist schwer zu reviewen und folgt nicht der Versionskontrolle des Codes.

Dieser Artikel erklärt, warum Dokumentation im Repository zuverlässiger ist als das Gedächtnis eines Agenten. Der Artikel zeigt auch, wie man ein Spring-Boot-Repository so aufbaut, dass Neueinsteiger im Team und Agenten beide von Anfang an richtig arbeiten. Nach dem Lesen wirst du wissen, wie man eine Anleitung AGENTS.md schreibt, Prüfungen in einem einzigen Befehl zusammenfasst, Konventionen in Tests verwandelst und Entwurfsentscheidungen dokumentierst.

2. Warum Gedächtnis nicht ausreicht

Vergleichen wir zwei Wege, wie ein Agent „weiß”, dass die domain-Schicht nicht von Spring abhängen darf:

  • Gedächtnis: Du hast es ihm früher mitgeteilt, und es hat sich eine Notiz gemacht, irgendwo.
  • Repository: Die Regel steht in AGENTS.md und es gibt einen Test, der fehlschlägt, wenn jemand sie bricht.

Der erste Weg hat mehrere offensichtliche Schwächen:

  • Die Notiz existiert nur auf dem Rechner oder dem Konto einer Person. Ein Kollege mit einem anderen Agent wird sie nicht haben.
  • Niemand reviewed die Notiz. Wenn sich der Agent „falsch merkt”, erkennst du es nur schwer.
  • Die Notiz folgt nicht dem Branch. Wenn die Regel auf main sich ändert, bleibt das alte Gedächtnis erhalten.

Der zweite Weg ist versioniert, wird per Pull Request reviewed und passt zu jedem Commit. Noch wichtiger: Er hilft auch den Menschen. Das, was ein Agent braucht, um richtig zu arbeiten, deckt sich fast exakt mit dem, was ein neuer Developer in seiner ersten Woche braucht.

Das ist der Grund, warum dieses Thema Aufmerksamkeit bekommt. Agenten machen kein neues Problem sichtbar. Sie machen den Preis für schlechte Dokumentation sichtbar – weil Agenten nicht einfach einen neuen Developer fragen können: „Hey, wie läuft das hier?” wie eine Person es könnte.

3. Anleitung für Agenten: AGENTS.md

Viele Agent-Tools lesen eine Markdown-Datei im Repository-Root, um Anweisungen zu erhalten. Der Dateiname hängt vom Tool ab, aber AGENTS.md ist eine verbreitete Konvention. Wenn dein Tool einen anderen Namen verwendet, kannst du einen Symlink erstellen, statt den Inhalt zu duplizieren.

3.1. Was der Inhalt sein sollte

# AGENTS.md

## Projekt
Bestellungsdienst (Spring Boot 3, Java 21). Hauptmodule: `order`, `payment`, `customer`.

## Befehle
- Build + vollständige Prüfung: `./scripts/verify.sh`
- Test ausführen: `./mvnw -q test -Dtest=DiscountPolicyTest`
- App lokal ausführen: `./mvnw spring-boot:run -Dspring-boot.run.profiles=local`

## Verbindliche Konventionen
- `..domain..` importiert nicht `org.springframework..` (Test: `ArchitectureTest`).
- Währung ist `BigDecimal`, nicht `double`.
- Jede Änderung der Geschäftslogik muss einen Test in `src/test/java/.../domain` haben.

## Nicht erlaubt
- Editiere keine Datei in `src/main/resources/db/migration`, die bereits gemergt wurde; erstelle stattdessen eine neue Migration.
- Committe nicht `application-local.yml`.

## Weitere Lektüre
- Entwurfsentscheidungen: `docs/adr/`
- Zahlungsfluss: `docs/payment-flow.md`

Jeder Abschnitt hat einen eigenen Zweck:

  • Befehle: Agenten führen Befehle häufig aus. Ein falscher Befehl kostet viele Iterationen. Schreibe Befehle genau auf, zum Copy-Pasten.
  • Verbindliche Konventionen: Jede Zeile sollte zeigen, wie man sie prüft (Testname, Lint-Regel). Konventionen ohne Prüfmechanismus werden leicht ignoriert.
  • Nicht erlaubt: Listet Aktionen auf, die schwer rückgängig zu machen sind.
  • Weitere Lektüre: Zeigt auf detaillierte Dokumentation, statt Inhalte hier zu duplizieren.

3.2. Kurz halten, auf die Quelle der Wahrheit verweisen

AGENTS.md sollte kurz sein – vollständig in einer Minute zu lesen. Der Agent lädt diese Datei in seinen Kontext bei jeder Sitzung. Je länger die Datei, desto mehr Kontext verschwendest du und desto eher enthält sie veraltete Informationen.

Grundsatz: Beschreibe nicht neu, was Code oder Build bereits klarmachen. Nicht jede Abhängigkeit auflisten, weil pom.xml sie bereits hat. Nicht jeden Endpoint beschreiben, weil OpenAPI-Spec sie bereits hat. Schreibe nur Dinge auf, die sich nicht aus dem Code ableiten lassen.

Warnung: Nie Secrets, Tokens oder sensible interne URLs in AGENTS.md eintragen. Diese Datei wird von externen Tools gelesen und bleibt für immer in der Git-Historie.

4. Ein einziger Verifizierungsbefehl

Agenten arbeiten am besten mit einer klaren Rückkopplung: Code ändern, Befehl ausführen, bestanden oder fehlgeschlagen. Wenn vollständige Prüfung vier verschiedene Befehle in der richtigen Reihenfolge erfordert, wird der Agent einen vergessen. Menschen auch.

#!/usr/bin/env bash
# scripts/verify.sh – genauso wie CI. Exit-Code ≠ 0 bedeutet: nicht bereit zum Mergen.
set -euo pipefail

echo ">> Compile + Unit-Test + Architecture-Test"
./mvnw -B -q verify

echo ">> Prüfe: keine TODO-AGENT mehr übrig"
if grep -rn "TODO-AGENT" src/; then
  echo "TODO-AGENT Markierungen nicht bearbeitet" >&2
  exit 1
fi

echo "OK: bereit für Pull Request"

Erklärung:

  • set -euo pipefail bricht das Script ab, wenn ein Befehl fehlschlägt. So erhält der Agent einen Exit-Code ≠ 0 statt verwirrend langen Output mit „OK” am Ende.
  • -B (Batch-Modus) deaktiviert Farbausgabe und Fortschrittsbalken. Log ist übersichtlicher, Agenten können Fehler leichter lesen.
  • Der grep-Schritt ist ein Beispiel einer Team-Konvention: Agenten dürfen mit TODO-AGENT markieren, was unvollständig ist, aber nichts davon darf beim PR-Öffnen übrigbleiben.

Wenn erfolgreich, sieht man:

>> Compile + Unit-Test + Architecture-Test
>> Prüfe: keine TODO-AGENT mehr übrig
OK: bereit für Pull Request

Das Wichtigste: CI muss dieses exakte Script aufrufen. Falls CI etwas anderes läuft, wird verify.sh langsam falsch und zur Falschdokumentation.

5. Konventionen in Tests mit ArchUnit verwandeln

Ein Satz wie „domain ist unabhängig von Spring” in der Doku ist nur Rat. Wenn du ihn in einen Test verwandelst, wird er zum Gesetz. ArchUnit ermöglicht dir, Architekturregeln als JUnit-Tests zu schreiben.

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>
package com.nonla.shop;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

@AnalyzeClasses(packages = "com.nonla.shop", importOptions = ImportOption.DoNotIncludeTests.class)
class ArchitectureTest {

    @ArchTest
    static final ArchRule domainUnabhaengigVonSpring = noClasses()
            .that().resideInAPackage("..domain..")
            .should().dependOnClassesThat().resideInAPackage("org.springframework..")
            .because("domain muss ohne Spring Context testbar sein, siehe docs/adr/0003-domain-rein.md");

    @ArchTest
    static final ArchRule webNichtDirektAufRepository = noClasses()
            .that().resideInAPackage("..web..")
            .should().dependOnClassesThat().haveSimpleNameEndingWith("Repository")
            .because("Controller gehen über Service, um Transaktionen an einem Ort zu halten");
}

Das Wertvollste liegt in .because(...). Wenn die Regel verletzt wird, enthält die Fehlermeldung die Begründung und einen Link zur ADR. Angenommen, jemand fügt @Component zu DiscountPolicy in domain hinzu, sieht man:

Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package
'..domain..' should depend on classes that reside in a package 'org.springframework..',
because domain muss ohne Spring Context testbar sein, siehe docs/adr/0003-domain-rein.md'
was violated (1 times):
Class <com.nonla.shop.order.domain.DiscountPolicy> is annotated with <org.springframework.stereotype.Component> ...

Ein Agent, der diese Meldung liest, weiß sofort, was zu tun ist: Annotation entfernen, Bean stattdessen in einer @Configuration-Klasse in der infra-Schicht registrieren. Eine gute Fehlermeldung ist Dokumentation, die genau zum richtigen Zeitpunkt erscheint.

6. Tests sind Geschäftsspezifikation

Geschäftsregeln sind das, was Agenten am ehesten falsch machen. Es kann nicht erahnen, dass „erste Bestellung hat max. 50.000 VND Rabatt”. Lass Code und Tests das klarmachen.

package com.nonla.shop.order.domain;

import java.math.BigDecimal;
import java.math.RoundingMode;

public record Order(String customerId, BigDecimal subtotal, boolean firstOrder) {}

class DiscountPolicy {

    static final BigDecimal FIRST_ORDER_RATE = new BigDecimal("0.10");
    static final BigDecimal FIRST_ORDER_CAP = new BigDecimal("50000");

    BigDecimal discountFor(Order order) {
        if (!order.firstOrder()) {
            return BigDecimal.ZERO;
        }
        BigDecimal raw = order.subtotal().multiply(FIRST_ORDER_RATE)
                .setScale(0, RoundingMode.DOWN);
        return raw.min(FIRST_ORDER_CAP);
    }
}

Tests sollten nach Verhalten benannt sein, nicht nach Methodennamen:

package com.nonla.shop.order.domain;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("Bestellungsrabatt")
class DiscountPolicyTest {

    private final DiscountPolicy policy = new DiscountPolicy();

    @Test
    @DisplayName("Erste Bestellung erhält 10 % Rabatt auf Gesamtsumme")
    void firstOrderGetsTenPercent() {
        var order = new Order("c-1", new BigDecimal("200000"), true);

        assertThat(policy.discountFor(order)).isEqualByComparingTo("20000");
    }

    @Test
    @DisplayName("Rabatt auf erste Bestellung wird auf max. 50.000 VND begrenzt")
    void firstOrderDiscountIsCapped() {
        var order = new Order("c-1", new BigDecimal("2000000"), true);

        assertThat(policy.discountFor(order)).isEqualByComparingTo("50000");
    }

    @Test
    @DisplayName("Ab zweiter Bestellung kein Rabatt mehr")
    void repeatOrderGetsNoDiscount() {
        var order = new Order("c-1", new BigDecimal("2000000"), false);

        assertThat(policy.discountFor(order)).isZero();
    }
}

Erklärung:

  • @DisplayName macht den Test-Report zu einer lesbaren Spezifikation. Auch der Product Owner versteht es.
  • isEqualByComparingTo vergleicht BigDecimal nach Wert, daher sind 20000 und 20000.00 gleich. isEqualTo hier zu nutzen ist ein häufiger Fehler, da es auch die Skala vergleicht.
  • Wenn der Agent gebeten wird, „Rabatt auf 15 % erhöhen”, sieht er sofort, welcher Test sich ändern muss. Falls er versehentlich die Obergrenze entfernt, schlägt der zweite Test fehl.

Beim Ausführen sieht der Report so aus:

Bestellungsrabatt
  ✔ Erste Bestellung erhält 10 % Rabatt auf Gesamtsumme
  ✔ Rabatt auf erste Bestellung wird auf max. 50.000 VND begrenzt
  ✔ Ab zweiter Bestellung kein Rabatt mehr

7. Dokumentiere das „Warum”: ADR und package-info

Code sagt dir, was das System tut. Tests sagen dir, was es tun muss. Aber nichts erklärt das Warum für eine Designentscheidung. Ohne „Warum” wird der Agent oder ein neuer Entwickler „räumen” Features auf, die sinnlos aussehen, aber eine wichtige Rolle spielen.

Architecture Decision Record (ADR) ist eine kurze Datei pro Entscheidung:

# 0003. Domain-Schicht unabhängig von Spring

Status: Akzeptiert

## Kontext
Domain-Tests brauchten früher Spring Context zum Laden, waren langsam und fragil.

## Entscheidung
Das Package `..domain..` verwendet nur JDK. Beans werden in `..infra.config` deklariert.

## Folgen
- Domain-Tests laufen mit reinem JUnit.
- Mehr Konfigurationsklassen nötig. Überwacht durch `ArchitectureTest`.

Für Notizen an ein Package ist package-info.java der richtige Platz, weil der Agent es sofort sieht, wenn er den Ordner öffnet:

/**
 * Geschäftsregeln für Bestellungen. Nur JDK, kein Spring (ADR 0003).
 * Alle Geldbeträge sind BigDecimal, Einheit VND, abgerundet auf ganze Zahl.
 */
package com.nonla.shop.order.domain;

8. Häufige Fehler und wann man es nicht übertreiben sollte

Duplizierte Dokumentation. Dieselbe Regel steht in README.md, AGENTS.md und Wiki. Nach ein paar Monaten sagen die drei verschiedene Dinge. Wähle eine Quelle der Wahrheit und verweise vom Rest darauf.

Dokumentation widerspricht CI. AGENTS.md sagt „führe mvn test aus”, aber CI führt auch Integration-Tests aus. Der Agent meldet „fertig”, während der PR rot ist.

An den Agent mit vagen Befehlen schreiben. „Schreibe sauberen Code” hilft nicht. „Jede öffentliche Methode in ..service.. muss einen Test haben” ist hilfreich – noch besser, wenn verifizierbar.

Alles in eine Datei packen. Ein AGENTS.md mit tausenden Zeilen kostet zu viel Kontext. Wichtiges erstickt im Rest.

Hinweis: Nicht jedes Repository braucht das ganze Arsenal. Ein einmalig genutztes Script, ein Prototype zum Verwerfen oder ein Repo mit ein paar hundert Zeilen Code – die brauchen nur README.md mit Befehlen. ArchUnit lohnt sich erst, wenn es mindestens ein paar Schichten oder Module mit echten Grenzen gibt. Dokumentation für Dinge, die es noch nicht gibt, schafft nur Wartungslast.

Umgekehrt hat das Agent-Gedächtnis seinen Platz. Es passt zu persönlichen Vorlieben wie die Stimme beim Erklären oder der liebste Editor. Das gehört nicht ins Repository. Aber Projektregeln gehören immer ins Repository.

9. Fazit

Agenten brauchen kein besseres Gedächtnis, sie brauchen ein verständlicheres Repository. Du hast gesehen, wie man:

  • AGENTS.md kurz schreibt mit exakten Befehlen und überprüfbaren Konventionen.
  • Alle Prüfschritte in scripts/verify.sh fasst und diese CI gemeinsam nutzen lässt.
  • Architekturkonventionen in ArchUnit-Tests verwandelt mit Fehlermeldungen, die auf den Grund verweisen.
  • Tests mit @DisplayName als Geschäftsspezifikation nutzt.
  • Das „Warum” mit ADR und package-info.java dokumentiert.

All das hilft zunächst den Menschen. Agenten sind nur der Grund, warum du das nicht länger aufschieben kannst. Fang klein an: verify.sh und die ersten fünf Zeilen von AGENTS.md noch diese Woche.

ai-agentdocumentationarchunittestingspring-boot