Startseite / Artikel

Loop Engineering: Vier verschachtelte Schleifen für sich selbst verbessernende KI-Agenten

Schleifenarchitektur für sich selbst verbessende KI-Agenten: vier verschachtelte Schleifen (ReAct, Wiederholung, Warteschlangenabfrager, Hügelbesteigung) sowie ein Bewertungssystem, ein Budgetanzeiger und eine Manipulationsprüfung.

Verlag: REACTAPP.TOP32 Min. Lesezeit7374 Wörter

Quellen: Loop Engineering: Building Self-Improving AI Agents with Four Nested Loops

Etwa zwei Jahre lang bedeutete die Arbeit mit einem Modell, abwechselnd tätig zu sein. Ein Ingenieur schreibt eine Anweisung, liest das Ergebnis und gibt anschließend den nächsten Kontext ein. Diese Routine hat einen versteckten Kostenfaktor – eine Art Koordinationsaufwand –, denn die Geschwindigkeit des gesamten Prozesses wird durch die Geschwindigkeit begrenzt, mit der ein Mensch etwas prüfen kann. Um dies zu überwinden, ist ein Fokuswechsel notwendig: Man muss aufhören, das Werkzeug selbst zu bedienen, und stattdessen das System entwerfen, das das Werkzeug steuert.

Drei Ideen leiten diesen Wandel ein.

  • Der Einflussbereich rückt eine Stufe nach oben. Die Rolle wechselt von der Erstellung genauer Anweisungen zur Gestaltung wiederholender Schleifen und schließlich zu einer Position ähnlich der eines Leiters. Der Mensch legt das Ziel sowie die Abbruchbedingung fest, während das System jede Etappe auf dem Weg dazwischen steuert.
  • Autonome Schleifen weisen eine erkennbare Struktur auf. Sie finden Aufgaben, weisen Teilaufgaben an Unteragenten zu, überprüfen die Ergebnisse mit unabhängigen Tests und dokumentieren ihren Zustand, wodurch der Mensch aus dem Kreislauf der Sortierung, Korrektur und Überprüfung herausgenommen wird.
  • Zielplanung steht an der Spitze. Hier rücken die internen Abläufe der Schleife in den Hintergrund. AlphaEvolve, das Rekorde bei der Matrixmultiplikation, die seit 1969 galten, brach, sowie Amazons Kiro, das hochrangige Ziele in Anforderungen und Spezifikationen umwandelt, zeigen dieses Muster: Wenn die Bewertungsfunktion richtig gestaltet ist, findet die Maschine die Lösung.

Um einen Loop um einen Agenten zu entwerfen, benötigt man ein klares mentales Modell des Agenten darin. Daher beginnt die sinnvolle Konstruktionsreihenfolge mit einem einfachen Arbeiter, der Werkzeuge aufruft, fügt anschließend ein zweites Modell hinzu, das dessen Ausgabe bewertet (damit das System aufhört, seine eigene Arbeit zu bewerten), verbindet die beiden in einem Loop miteinander und schreibt die Ergebnisse auf die Festplatte – schließlich vergessen Modelle, während ein Repository das nicht tut. Danach folgen Schutzmaßnahmen gegen Agenten, die die Metrik manipulieren, ähnlich wie bei den Betrügereien, die bei den Tests von OpenAI’s o3 durch METR auftraten, anschließend ein Messinstrument, das die Kosten in Umgebungen mit vielen oder wenigen Tokens erfasst, und schließlich der Übergang von einem 70-Zeilen-Skript zu etwas, das in der Produktion überlebt.

Was folgt, ist eine funktionierende Umsetzung dieser ganzen Idee, wobei ein Versicherungsprüfprozess als Testumgebung verwendet wird.

Warum dies ein Architekturproblem ist

Der Übergang von einem Modell, das Fragen beantwortet, zu einem Agenten, der zuverlässig Aktionen ausführt, ist keine Übung im Verfassen von Prompts. Es handelt sich um ein Designproblem. Die meisten Tutorials zeigen einen Agenten, der ein einziges Tool aufruft. Reale Systeme benötigen gleichzeitig Fehlertoleranz, Selbstkorrektur, Ausgabengrenzen, Manipulationserkennung sowie kontinuierliche Verbesserung.

Der hier angewandte Ansatz wird Loop Engineering genannt: Mehrere Steuerungsläufe werden übereinander gestapelt, wobei jeder eine eigene Aufgabe hat, sodass das gesamte System stabiler funktioniert als jeder einzelne Lauf für sich.

Die Versicherungsprüfung ist ein geeigneter Anwendungsbereich. Entscheidungen stammen aus einer kleinen, festgelegten Auswahl (zustimmen, ändern, ablehnen, weiterleiten), die Regeln sind klar definiert, und historische Daten liefern richtige sowie falsche Antworten. Die gleichen Muster lassen sich auf Code-Reviews, Dokumentenanalyse, Support-Triage sowie die Verarbeitung von Krankenakten übertragen.

Vier Läufe, einer im anderen

Herkömmliche Software bändigt die Komplexität durch Schichten der Abstraktion. Loop-Engineering bändigt die Unzuverlässigkeit eines Agenten durch Schichten der Rückkopplung:

┌──────────────────────────────────────────────────────┐
│  Loop 4 — Hill Climbing (self-improvement over time) │
│  ┌────────────────────────────────────────────────┐  │
│  │  Loop 3 — Event Queue Poller (orchestration)   │  │
│  │  ┌──────────────────────────────────────────┐  │  │
│  │  │  Loop 2 — Verification & Retry           │  │  │
│  │  │  ┌────────────────────────────────────┐  │  │  │
│  │  │  │  Loop 1 — ReAct Agent              │  │  │  │
│  │  │  │  (think → act → observe → think)   │  │  │  │
│  │  │  └────────────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────┘

Jeder Schleifenzyklus hat eine einzige Aufgabe, einen bestimmten Fehler, gegen den er schützen muss, sowie ein Feedbacksignal, auf das er reagiert. Die wichtige Disziplin besteht darin, zu verhindern, dass Probleme zwischen den Schleifenzyklen übergehen.

Das Domänenmodell

Vor der Architektur gibt es zunächst die Daten. Eine Versicherungsanfrage ist ein einfaches Datensatz:

{
  "id": "APP-001",
  "applicant_age": 34,
  "state": "FL",
  "property_type": "residential",
  "coverage_amount": 450000,
  "claim_history_5yr": 1,
  "credit_score_tier": "good",
  "flood_zone": true,
  "has_flood_rider": false,
  "business_use": false
}

Jede der 20 Beispielanwendungen verfügt außerdem über eine korrekte Referenzetikette:

"APP-001": {
  "split": "train",
  "expected_decision": "modify",
  "expected_flags": ["flood_rider_required", "windstorm_exclusion"]
}

Eine Entscheidung gehört zu vier Möglichkeiten:

  • akzeptieren: Standardrisiko, so wie es ist genehmigen
  • ändern: unter Bedingungen oder mit Ergänzungen genehmigen
  • ablehnen: Das Risiko liegt über dem zulässigen Schwellenwert
  • weiterleiten: Ein erfahrener Underwriter muss es prüfen

Eine kleine Wissensdatenbank enthält fünf versteckte Underwriting-Regeln, die Überschwemmungsgebiete, Schadenshistorien, Kreditwerte, Deckungsgrenzen sowie staatsspezifische Beschränkungen abdecken. Dem Agenten werden diese in seiner Anweisung nicht mitgeteilt. Er muss sie über Toolaufrufe finden und anwenden. Sechzehn Anwendungen bilden den Trainingsdatensatz, vier werden für Tests zurückbehalten.

Loop 1: Ein ReAct-Agent

Gegen was es schützt: ein Modell, das Entscheidungen ohne Beweise trifft. Feedback-Signal: das Tool-Call-Graph, das anzeigt, ob der Agent vor der Entscheidung alles Relevanten konsultiert hat.

ReAct, abgekürzt für reason plus act, ist das Grundmuster. Der Agent überlegt, was er benötigt, ruft ein Tool auf, liest das Ergebnis, überlegt erneut und wiederholt diesen Vorgang, bis er eine Entscheidung trifft:

System Prompt
     │
     ▼
[Think] → What information do I need?
     │
     ▼
[Act]   → Call tool (lookup_application, retrieve_rules, ...)
     │
     ▼
[Observe] → Tool returns result
     │
     ▼
[Think] → What does this mean? What next?
     │
    ... (repeat until decision is made)
     │
     ▼
[Act]   → draft_decision (terminal tool call)

In LangGraph wird dies zu einem Zustandsgraphen mit zwei Knoten und einer bedingten Kante. Der Zustand enthält die Anwendung-ID, die Nachrichtenliste, die Entscheidungsfelder, die Lektionen, die aktuell anfallenden Kosten sowie einen Schrittzähler:

class UnderwritingState(TypedDict):
    application_id: str
    messages: Annotated[list[AnyMessage], add_messages]  # reducer accumulates
    decision: str
    rationale: str
    flags: list[str]
    recommended_premium_adj: float
    lessons: list[str]
    spend: float
    _steps: int

Ein Detail verdient Aufmerksamkeit. Die add_messages-Anmerkung veranlasst LangGraph, bei jedem Schritt eine Nachricht zur Liste hinzuzufügen anstatt sie zu ersetzen, sodass der Agent das gesamte Gespräch über alle Tool-Aufrufe hinweg für eine Anwendung beibehält.

Der Builder übernimmt den Systemprompt sowie den gemeinsamen Kostenmesser und bindet die Tools an das Modell:

def build_loop1(system_prompt: str, meter: CostMeter):
    model = ChatAnthropic(model="claude-sonnet-4-6").bind_tools(TOOLS)

Darin befinden sich der Agentenknoten, der Tools-Knoten, die Routungsfunktion sowie die Graph-Zusammensetzung:

    def agent_node(state: UnderwritingState) -> dict:
        prompt = system_prompt
        if state.get("lessons"):
            prompt += "\n\nUnderwriting lessons:\n" + "\n".join(
                f"- {l}" for l in state["lessons"]
            )
        msgs = [SystemMessage(content=prompt)] + list(state["messages"])
        response = model.invoke(msgs)
        meter.record_from_message(response)          # track spend per call
        return {
            "messages": [response],
            "_steps": state.get("_steps", 0) + 1,
            "spend": meter.spent,
        }

    def tools_node(state: UnderwritingState) -> dict:
        last = state["messages"][-1]
        tool_results, updates = _dispatch_tools(last.tool_calls)
        return {"messages": tool_results, **updates}  # updates captures draft_decision output

    def should_continue(state) -> Literal["tools", "__end__"]:
        if state.get("_steps", 0) >= MAX_STEPS:   # hard cap at 8 steps
            return END
        if getattr(state["messages"][-1], "tool_calls", None):
            return "tools"
        return END

    g = StateGraph(UnderwritingState)
    g.add_node("agent", agent_node)
    g.add_node("tools", tools_node)
    g.set_entry_point("agent")
    g.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
    g.add_edge("tools", "agent")
    return g.compile()

Die vier Werkzeuge

Der Agent erhält genau vier Werkzeuge, die ausreichen, um alles zu bewältigen, was er benötigt:

@tool
def lookup_application(app_id: str) -> dict:
    """Return the full application record for the given application ID."""
    # reads from applications.json — deterministic, no LLM call
    ...

@tool
def retrieve_rules(risk_factor: str) -> list[str]:
    """Return underwriting rules matching the given risk factor keyword.
    Use terms like: flood, claims, credit, coverage, state, business."""
    # keyword match against rules_kb.json - forces explicit rule lookup
    ...

@tool
def fetch_risk_signal(signal_type: str) -> dict:
    # simulated external data pull (credit bureaus, flood maps, etc.)
    ...

@tool
def draft_decision(
    decision: Literal["accept", "modify", "decline", "refer"],
    rationale: str,
    flags: list[str],
    recommended_premium_adj: float,
) -> dict:
    """Finalize the underwriting decision with structured output."""
    # terminal action - structured schema forces the agent to commit explicitly
    ...

lookup_application liest nur Daten ein. retrieve_rules führt eine Schlüsselwortsuche in einem festen Regelwerk durch, was eine explizite Abfrage erzwingt. fetch_risk_signal simuliert das Abrufen externer Daten wie Kreditinformationsbüros oder Überschwemmungskarten. draft_decision ist die endgültige Aktion und am wichtigsten. Sie veranlasst den Agenten, eine schriftlich festgelegte Entscheidung mit Begründung und spezifischen Flaggen zu treffen, sodass das strukturierte Ergebnis direkt aus dem Toolaufruf stammt und niemand freien Text analysieren muss.

Warum die Schrittbegrenzung wichtig ist

MAX_STEPS = 8 verhindert endlose Toolketten. Ein verwirrtes Modell könnte zwanzig Mal nach Regeln zu „Überschwemmungen“ suchen, nach einer Antwort, die nicht vorhanden ist, wodurch Tokens und Zeit verschwendet werden. Wenn die Anzahl der Schritte die Obergrenze erreicht, erhält der Zustand die Entscheidung ran_out_of_steps, die ein Loop 2 erkennen und erneut versuchen kann.

Schleife 2: Überprüfen und erneut versuchen

Gegen was es schützt: eine einzige falsche Antwort aufgrund einer fehlerhaften Argumentationsweise. Feedback-Signal: der deterministische Prüfer is_correct(), ohne Beteiligung eines Modells.

Die Schleife 1 kann fehlschlagen. Der Agent könnte eine Flagge falsch interpretieren, eine Regel überspringen oder die Schrittanzahl erreichen. Schleife 2 erkennt diese Fehler und gibt dem Agenten mit gezieltem Feedback eine weitere Chance:

def run_loop2(
    app_id: str,
    system_prompt: str,
    meter: CostMeter,
    max_retries: int = 3,
) -> tuple[dict, bool]:
    state = run_loop1(app_id, system_prompt, meter)
    for attempt in range(max_retries):
        decision = state.get("decision", "ran_out_of_steps")
        if decision == "refer":
            _write_human_queue(app_id, state)   # human-in-the-loop path
            return state, True
        if is_correct(decision, app_id):         # deterministic check
            return state, True
        if attempt >= max_retries - 1:
            return state, False                  # exhausted retries
        # construct targeted feedback for next attempt
        expected = load_historical_decisions()[app_id]["expected_decision"]
        feedback = HumanMessage(content=(
            f"Your decision was '{decision}' but this application requires '{expected}'. "
            f"Review the risk factors carefully and call draft_decision again."
        ))
        extra_messages = list(state.get("messages", [])) + [feedback]
        state = run_loop1(app_id, system_prompt, meter, extra_messages=extra_messages)
    return state, False

Einige Gestaltungsentscheidungen verdienen eine nähere Erklärung.

Der Richter ist deterministisch. is_correct() liest die Datei mit den historischen Entscheidungen und vergleicht Zeichenketten. Er ruft niemals ein Modell auf. Der Einsatz eines zweiten Modells als Richter würde eine weitere Quelle der Unzuverlässigkeit schaffen, während hier die wahre Lösung einfach die richtige Lösung ist.

Die Überweisung an einen Menschen ist ein abgeschlossenes Ergebnis. Wenn der Agent refer angibt, schreibt Loop 2 den Fall in human_queue.json und meldet Erfolg. Die Eskalation an einen Menschen gilt als gültiger Endzustand und nicht als etwas, das erneut versucht werden muss.

Die Rückmeldung gibt das Ziel an. Anstelle von „Versuchen Sie es erneut“ teilt die Nachricht dem Agenten mit, welche Entscheidungskategorie richtig ist, und bittet ihn, die Risikofaktoren erneut zu prüfen. Dadurch wird der nächste Versuch auf die richtige Antwort gelenkt, während ein blindes Neustichproben nur zufällig vorgehen würde.

Die Geschichte wird weitergeführt. Die zusätzlichen Nachrichten enthalten das gesamte frühere Gespräch sowie die Rückmeldungen, sodass der Agent bei seinem nächsten Versuch alles sieht, was er bereits versucht hat, und weniger wahrscheinlich denselben falschen Weg wiederholt.

Loop 3: der Event-Queue-Poller

Gegen was er schützt: eine enge Verknüpfung zwischen demjenigen, der die Aufgaben erzeugt, und demjenigen, der sie verarbeitet. Rückmeldesignal: der Inhalt von pending_applications.json, einer einfachen Datei, die als Warteschlange dient.

Diese Schleife ist der Orchesterer. Sie holt Aufgaben aus der Warteschlange, führt eine Anwendung durch Loops 1 und 2, aktualisiert den Laufzustand und löst gelegentlich Loop 4 aus:

def run_event_loop(
    system_prompt: str,
    meter: CostMeter,
    pending_path: Path = DEFAULT_PENDING,
    state_path: Path = DEFAULT_STATE,
    once: bool = False,
) -> None:
    run_state = load_run_state(state_path)
    verify_judge_checksum(run_state.get("judge_checksum", ""))   # tamper check
    total = len(json.loads(pending_path.read_text(encoding="utf-8-sig"))
                if pending_path.exists() else [])
    processed = 0
    while True:
        pending = (json.loads(pending_path.read_text(encoding="utf-8-sig"))
                   if pending_path.exists() else [])
        if not pending:
            if once:
                print("[loop3] queue empty - done")
                break
            print("[loop3] queue empty - sleeping...")
            time.sleep(POLL_INTERVAL)
            continue
        app_id = pending[0]
        processed += 1
        print(f"[loop3] {processed}/{total}  {app_id} ...", flush=True)
        try:
            result_state, passed = run_loop2(app_id, prompt_with_lessons, meter)
        except BudgetExhaustedError:
            raise                                # budget exhaustion is fatal
        except Exception as exc:
            print(f"[loop3] {app_id} ERROR: {exc!r} - skipping")
            # remove from queue and continue - one bad app cannot block the rest
            remaining = [x for x in pending if x != app_id]
            pending_path.write_text(json.dumps(remaining), encoding="utf-8")
            continue
        # ... update state, distill lesson, trigger hill-climbing

Warum eine Datei für die Warteschlange?

Die Warteschlange ist absichtlich eine JSON-Datei und nicht ein Redis-Stream oder eine SQS-Warteschlange, weil sie bei der lokalen Entwicklung leicht überprüft werden kann (die Datei wird geöffnet, um zu sehen, was ausstehend ist), leicht wieder aufgenommen werden kann (nach einem Absturz wird sie einfach neu gestartet, und die Warteschlange bleibt unverändert) sowie entkoppelt ist (das, was sie füllt, benötigt keine Kenntnis über den Verbraucher). In einem Produktivsystem würde man einen geeigneten Message Broker einsetzen, wobei sich der Schnittstellenvertrag, also die Liste der IDs, nicht ändern würde.

Detektion von Manipulationen

Bevor etwas verarbeitet wird, überprüft Loop 3 die echten Werte:

verify_judge_checksum(run_state.get("judge_checksum", ""))

Die Überprüfung funktioniert wie folgt:

def compute_judge_checksum() -> str:
    content = _HIST.read_bytes()           # historical_decisions.json
    return hashlib.sha256(content).hexdigest()


def verify_judge_checksum(expected: str) -> None:
    actual = compute_judge_checksum()
    if actual != expected:
        raise RuntimeError(
            f"Judge checksum mismatch - historical_decisions.json was tampered with."
        )

Ein SHA-256-Hash der Datei mit den historischen Entscheidungen wird bei der Initialisierung im Laufzustand gespeichert und jedes Mal überprüft, wenn der Event-Loop startet. Wenn jemand die „Ground Truth“ ändert, um den Agenten zu begünstigen, weigert sich das System, weiterzulaufen. Dadurch wird eine bisher unentdeckte Lücke in selbstverbessernden Systemen geschlossen: Die günstigste Möglichkeit, die Bewertung zu erhöhen, besteht darin, den Bewertungsalgorithmus zu ändern.

Auslassen versus Absturz

Der try/except-Block in Schleife 3 zieht eine klare Grenze. Ein Budgetauslauf wird erneut ausgelöst, wodurch alles gestoppt wird. Jede andere Ausnahme wird protokolliert, die betroffene Anwendung wird aus der Warteschlange entfernt und die nächste wird verarbeitet. Somit kann eine einzige fehlerhafte Anwendung den gesamten Ablauf nicht blockieren, während ein Budgetauslauf aus Designgründen fatal ist, da das System nicht weiterlaufen sollte, wenn es nichts mehr zum Ausgeben hat.

Schleife 4: Hügelbesteigung

Gegen was es schützt: einen festen Prompt, der nie aus Erfahrungen lernt. Feedback-Signal: die Trainingsgenauigkeit zusammen mit dem Prüfungsurteil.

Jede achte Entscheidung (HILL_CLIMB_EVERY = 8): Loop 3 übergeben an Loop 4, der einen Zyklus aus Reflektieren, Verändern, Bewerten und Steuern ausführt, um den Systemprompt automatisch zu verbessern:

seed prompt
     │
     ▼
evaluate on training set → score, failures
     │
     ▼
┌────────────────────────────────┐
│  for each round:               │
│                                │
│  reflect(current, failures)    │  ← LLM analyzes failure patterns
│       │                        │
│       ▼                        │
│  candidate_prompt              │
│       │                        │
│       ▼                        │
│  evaluate(candidate, train)    │  ← full eval run
│       │                        │
│       ▼                        │
│  audit_prompt(candidate)       │  ← anti-cheating check
│       │                        │
│       ▼                        │
│  if score > best AND clean:    │
│      best = candidate          │  ← keep
│  else:                         │
│      discard                   │  ← revert
└────────────────────────────────┘
     │
     ▼
write best_prompt.txt

Reflektieren

REFLECT_TEMPLATE = """You are improving the system prompt of an insurance underwriting agent.

CURRENT PROMPT:
{prompt}
The agent got these decisions WRONG on training applications:
{failures}
Look for PATTERNS in the failures. Infer GENERAL underwriting rules that would fix them.
Do NOT memorize specific application IDs or applicant details.
Write an improved system prompt. Reply with ONLY the new prompt text."""


def reflect(current_prompt: str, failures: list[dict]) -> str:
    client = anthropic.Anthropic()
    failure_text = "\n".join(
        f"- APP {f['app_id']}: agent said '{f['got']}', expected '{f['expected']}'. "
        f"Application data: {f['application']}"
        for f in failures
    )
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1500,
        messages=[{"role": "user", "content": REFLECT_TEMPLATE.format(
            prompt=current_prompt,
            failures=failure_text,
        )}],
    )
    return response.content[0].text.strip()

Die Anweisung, sich keine spezifischen Anwendungs-ID- oder Antragstellerdaten einzuprägen, ist von großer Bedeutung. Ohne sie könnte das Modell einen Prompt wie „Für APP-003 immer ablehnen“ erstellen, der bei den Trainingsdaten eine perfekte Leistung zeigt, bei Fällen, die es noch nicht gesehen hat, jedoch völlig versagt. Der neue Prompt muss aus Mustern in den Fehlern generalisieren, nicht aus einzelnen Fällen.

Bewerten

def evaluate_prompt(system_prompt: str, split: str = "train", meter=None):
    tasks = load_tasks(split)
    failures = []
    for task in tasks:
        state = run_loop1(task["id"], system_prompt, meter)
        decision = state.get("decision", "ran_out_of_steps")
        if not is_correct(decision, task["id"]):
            failures.append({
                "app_id": task["id"],
                "got": decision,
                "expected": task["expected_decision"],
                "application": task["application"],
            })
    score = (len(tasks) - len(failures)) / len(tasks)
    return score, failures

Jeder Kandidat-Prompt wird in allen 16 Trainingsanwendungen durch Schleife 1 geprüft. Das ist kostspielig – 16 Modellaufrufe pro Runde und mindestens 80 insgesamt über fünf Runden – doch das Ergebnis ist zuverlässig: Ein Prompt muss in der gesamten Trainingsdatenmenge funktionieren und nicht nur auf einem einzelnen Beispiel.

Gate

def improve(seed_prompt: str, lessons: list[str], meter: CostMeter, rounds: int = 5) -> str:
    best = with_lessons(seed_prompt, lessons)
    best_score, failures = evaluate_prompt(best, split="train", meter=meter)
    for r in range(1, rounds + 1):
        candidate = reflect(best, failures)
        score, cand_failures = evaluate_prompt(candidate, split="train", meter=meter)
        verdict = audit_prompt(candidate)        # anti-cheat check
        if score > best_score and verdict == "VERDICT: CLEAN":
            best, best_score, failures = candidate, score, cand_failures
            BEST_PROMPT_PATH.write_text(best, encoding="utf-8")
            print(f"round {r}: {score:.0%} KEPT  ({len(cand_failures)} failures)")
        else:
            reason = "cheating" if verdict != "VERDICT: CLEAN" else f"{score:.0%} <= {best_score:.0%}"
            print(f"round {r}: {score:.0%} REVERTED ({reason})")
    return best

Zwei Bedingungen müssen gleichzeitig erfüllt sein, bevor ein Kandidat akzeptiert wird. Erstens muss score > best_score gelten, das heißt, er muss den aktuellen Bestwert deutlich übertreffen. Gleichstände werden abgelehnt, um eine Abweichung der Anfrage zu verhindern. Zweitens muss der Prüfer VERDICT: CLEAN zurückgeben, um zu bestätigen, dass der Kandidat keine fest kodierten Antworten enthält.

Der Anfrage-Prüfer

AUDIT_SYSTEM = (
    "You are a security auditor for AI systems. "
    "Check whether the prompt contains any hardcoded application IDs (e.g. APP-001) "
    "or hardcoded decisions for specific applications. "
    "These would constitute cheating — memorizing answers instead of learning rules. "
    "Reply with exactly one of:\n"
    "VERDICT: CLEAN\n"
    "VERDICT: CHEATING\n"
    "Followed by a one-sentence explanation."
)


def audit_prompt(evolved_prompt: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=200,
        system=AUDIT_SYSTEM,
        messages=[{"role": "user", "content": f"PROMPT TO AUDIT:\n{evolved_prompt}"}],
    )
    return response.content[0].text.strip().split("\n")[0]   # first line only

Es handelt sich um eine Konfiguration mit einem LLM als Richter, jedoch beschränkt auf eine einzige binäre Strukturfrage: Enthält die Anfrage spezifische IDs oder auswendig gelernte Antworten? Das ist die Art von Klassifizierung, bei der Modelle als Richter zuverlässig sind. Der Prüfer bewertet niemals Qualität, Stil oder Richtigkeit – er sucht lediglich nach einem bestimmten Muster.

Lerngedächtnis

Neben dem Hill-Climbing-Verfahren speichert das System eine lessons-Liste, die zwischen den Ausführungen erhalten bleibt. Wenn eine Anwendung nach jedem Neversuch weiterhin fehlschlägt, ruft es die Funktion distill_lesson() auf:

DISTILL_PROMPT = """An insurance underwriting agent made a wrong decision.

Application details: {application}
Agent decided: {got}
Correct decision: {expected}
Write ONE short, general underwriting rule that would prevent this mistake.
Do NOT reference this specific application ID or any applicant names/details.
Reply with only the rule, as a single sentence."""


def distill_lesson(application: dict, got: str, expected: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=150,
        messages=[{"role": "user", "content": DISTILL_PROMPT.format(...)}],
    )
    return response.content[0].text.strip()

Jede Lektion wird anschließend bei jedem späteren Ausführungsvorgang von Loop 1 über with_lessons() hinzugefügt:

def with_lessons(base_prompt: str, lessons: list[str]) -> str:
    if not lessons:
        return base_prompt
    lessons_text = "\n".join(f"- {l}" for l in lessons)
    return base_prompt + f"\n\nUnderwriting lessons learned:\n{lessons_text}"

Das ist Lernen mit wenigen Beispielen ohne Gradientenabstieg. Das System lernt während der Ausführung aus Fehlern, indem es komprimiertes Wissen in Form von Regeln in natürlicher Sprache sammelt. Eine Funktion im Stil des CL-BENCH-Protokolls prüft, ob diese Lektionen tatsächlich helfen:

def measure_gain(base_prompt: str, lessons: list[str], split: str = "test", meter=None) -> float:
    stateless_score, _ = evaluate_prompt(base_prompt, split=split, meter=meter)
    augmented = with_lessons(base_prompt, lessons)
    stateful_score, _ = evaluate_prompt(augmented, split=split, meter=meter)
    return stateful_score - stateless_score   # positive = lessons help

Budgetkontrolle mit einem Kostenmesser

Jeder Modellaufruf geht durch einen gemeinsamen CostMeter, der von Loop 4 auf Loop 3, dann auf Loop 2 und schließlich auf Loop 1 weitergegeben wird, sodass alle Ausgaben an einem Ort zusammengefasst werden:

BUDGET_USD = 2.00
INPUT_PRICE_PER_TOKEN  = 3.00  / 1_000_000
OUTPUT_PRICE_PER_TOKEN = 15.00 / 1_000_000


class CostMeter:
    def record(self, input_tokens: int, output_tokens: int) -> None:
        if self.spent >= self._budget:              # check BEFORE adding
            raise BudgetExhaustedError(
                f"Budget ${self._budget:.2f} exhausted at ${self.spent:.4f}"
            )
        self.spent += input_tokens * INPUT_PRICE_PER_TOKEN \
                    + output_tokens * OUTPUT_PRICE_PER_TOKEN

Die Reihenfolge ist entscheidend: Überprüfen Sie das Budget, bevor Sie die Kosten hinzufügen. Auf diese Weise tritt der Fehler sofort auf, sobald die Grenze erreicht ist, anstatt erst nachdem ein Überschuss gebucht wurde. BudgetExhaustedError wird die Hierarchie der Schleifen nach oben weitergeleitet, und Schleife 3 wirft ihn erneut auf, anstatt ihn zu ignorieren – dadurch beendet ein erschöpftes Budget stets den gesamten Ablauf.

Ausführungszustand mit idempotenter Einrichtung

run_state.json speichert alles, was geschehen ist: verarbeitete Anwendungen, Listen der erfolgreichen und fehlgeschlagenen Anwendungen, gesammelte Erkenntnisse, Entscheidungen seit dem letzten „Hill-Climb“ sowie die Prüfsumme des Bewerters. Seine Initialisierung ist defensiv gestaltet:

def _init_run_state() -> dict:
    path = _STATE_DIR / "run_state.json"
    state = {}
    if path.exists():
        try:
            content = path.read_text(encoding="utf-8-sig").strip()  # handles Windows BOM
            if content:
                state = json.loads(content)
        except (json.JSONDecodeError, UnicodeDecodeError):
            pass                                 # corrupt file → start fresh
    if not state:
        state = dict(_DEFAULT_RUN_STATE)
    state["judge_checksum"] = compute_judge_checksum()   # always refresh
    path.write_text(json.dumps(state, indent=2), encoding="utf-8")
    return state

Drei Resilienzgewohnheiten zeigen sich hier. Das Lesen mit utf-8-sig entfernt stumm die Windows-Byte-Order-Markierungen. Ein leerer oder beschädigter Datei wechselt in den Standardzustand, anstatt abzustürzen. Zudem wird die Prüfsumme beim jedem Start neu berechnet – sie wird niemals auf Grundlage eines vorherigen Laufs vertraut.

Drei Möglichkeiten, es auszuführen

Es gibt drei Eingangswege, wobei jeder nur einen Teil des Stack verwendet.

Im Einzelmodus wird nur Loop 1 ausgeführt, was nützlich ist, um eine Anwendung zu debuggen und zu beobachten, welche Tools der Agent aufruft und warum:

python -m src.main --mode single --app-id APP-001

Im Event-Loop-Modus werden Schleifen 1, 2 und 3 für jede ausstehende Anwendung ausgeführt, und Schleife 4 kann nach jeder achtsten Entscheidung ausgelöst werden. Die --once-Flagge leert die Warteschlange und beendet den Betrieb anstelle einer endlosen Abfrage:

python -m src.main --mode event-loop --once

Im Verbesserungsmodus wird Loop 4 direkt ausgeführt, wobei fünf Runden des Hügelbesteigens im Trainingsdatensatz durchgeführt werden. Anschließend wird der beste gefundenen Prompt gegen den für die Bewertung zurückgehaltenen Testdatensatz bewertet und der erzielte Gewinn ausgedruckt:

python -m src.main --mode improve --rounds 5

Eine Anwendung, von Anfang bis Ende

Das ist der vollständige Datenfluss einer einzelnen Anwendung innerhalb des Systems:

pending_applications.json
          │
          │  [Loop 3 reads APP-009]
          ▼
    run_loop2("APP-009", prompt, meter)
          │
          │  [Loop 2 calls Loop 1]
          ▼
    run_loop1("APP-009", prompt, meter)
          │
          │  [LangGraph StateGraph]
          ▼
    agent_node → ChatAnthropic.invoke([SystemMsg, HumanMsg])
          │
          │  response: tool_call(lookup_application, {"app_id": "APP-009"})
          ▼
    tools_node → lookup_application.invoke({"app_id": "APP-009"})
          │
          │  returns: {id: APP-009, state: TX, coverage: 1200000, ...}
          ▼
    agent_node → ChatAnthropic.invoke([..., ToolMessage])
          │
          │  response: tool_call(retrieve_rules, {"risk_factor": "coverage"})
          ▼
    tools_node → retrieve_rules.invoke({"risk_factor": "coverage"})
          │
          │  returns: ["Coverage > $1M requires mandatory refer. Flag: high_coverage"]
          ▼
    agent_node → ChatAnthropic.invoke([..., ToolMessage])
          │
          │  response: tool_call(draft_decision, {decision: "refer", ...})
          ▼
    tools_node → draft_decision.invoke({...})
          │
          │  updates state: {decision: "refer", flags: ["high_coverage"], ...}
          ▼
    should_continue → END (no more tool calls)
          │
          ▼
    Loop 2: decision == "refer" → write human_queue.json → return (state, True)
          │
          ▼
    Loop 3: passed=True → update run_state.json → remove APP-009 from queue
          │
          │  [decisions_since_last_improvement becomes 9 >= 8]
          ▼
    Loop 4: improve(seed_prompt, lessons, meter, rounds=5)
          │
          │  reflect → evaluate → audit → gate → write best_prompt.txt
          ▼
    Loop 3: continue with APP-010 using new best_prompt

Lektionen aus dem Aufbau

1. Halten Sie den Orakel-Teil getrennt vom Agenten. Der Richter (is_correct, evaluate_prompt) teilt keinen Code mit dem Agenten (run_loop1). Der Richter liest statische Dateien, während der Agent ein Modell aufruft. Lassen Sie das Modell seine eigene Ausgabe bewerten, dann erhalten Sie einen Ja-Sager, der niemals versagt.

2. Machen Sie die Bewertung deterministisch. Die Korrektheit pro Anwendung sowie die Trainings- und Testgenauigkeit sind bei denselben Eingaben alle deterministisch. Die einzige Zufälligkeit liegt in den Antworten des Modells, sodass jede Bewertung aus dem Prompt und dem Seed reproduziert werden kann.

3. Verwenden Sie ein Tool für Endantworten zur strukturierten Ausgabe. Ein draft_decision-Tool ist besser als das Parsen von Prosa. Sein Schema legt das Vokabular (accept | modify | decline | refer), die erforderlichen Felder (flags, rationale, recommended_premium_adj) sowie deren Typen fest, sodass der Agent explizit vorgehen muss.

4. Anti-Betrug ist in selbstverbessernden Systemen nicht optional. Sobald ein System seine eigenen Anweisungen ändern kann, wird es Abkürzungen finden – die offensichtlichste ist das Auswendiglernen von Trainingsantworten. audit_prompt() erkennt dies automatisch. Ohne dieses Tool führt das Hill-Climbing-Verfahren zu Prompts mit nahezu perfekter Trainingsgenauigkeit und nahezu zufälliger Testgenauigkeit.

5. Byte-Order-Markierungen und Kodierungen sind Produktionsfehler. Wenn Sie unter Windows entwickeln oder Dateien von Windows-Nutzern erhalten, ist bei jeder Leseoperation encoding="utf-8-sig" zwingend erforderlich. Die von Windows Notepad hinzugefügte BOM verursacht json.JSONDecodeError: Unexpected UTF-8 BOM, was schwer zu diagnostizieren ist, da die Datei in einem Editor völlig in Ordnung aussieht.

6. Überprüfen Sie den Budget vor dem Ausgeben, nicht danach.

# WRONG: allows one overspend before raising
self.spent += cost
if self.spent > self._budget:
    raise BudgetExhaustedError(...)
# CORRECT: raises before the overspend registers
if self.spent >= self._budget:
    raise BudgetExhaustedError(...)
self.spent += cost

Die Reihenfolge spielt eine Rolle, wenn mehrere Modellaufrufe nacheinander erfolgen. Fügen Sie sie zuerst hinzu und überprüfen Sie sie später – der Budgetrahmen kann bereits durch den Wert mehrerer Aufrufe überschritten werden, bevor ein Fehler auftritt.

7. Leeren Sie die Fortschrittszeilen in langen Schleifen.

print(f"[loop3] {processed}/{total}  {app_id} ...", flush=True)

Auf Windows wird stdout standardmäßig gebuffert, es sei denn, es wird anders angegeben. Wenn flush=True weggelassen wird, erscheint die Zeile „Processing APP-009“ erst nachdem der API-Aufruf abgeschlossen ist, was den Zweck eines Fortschrittsanzeigers zunichtemacht. Leeren Sie immer die Zeilen, die vor blockierenden I/O-Aufgaben kommen.

8. Halten Sie das Budget für das Hill-Climbing-Verfahren getrennt vom Budget des Event-Loops. Ein Auslaufen des Budgets während des Hill-Climbing-Verfahrens muss an der jeweiligen Aufrufstelle erkannt werden, damit der Event-Loop nicht abstürzt. Nachdem acht Anwendungsüberprüfungen den größten Teil eines $2-Budgets verbraucht haben, bleibt für das Hill-Climbing-Verfahren kaum noch Spielraum. Der Event-Loop fängt dabei gezielt den BudgetExhaustedError auf und setzt die Verarbeitung der verbleibenden Anwendungen fort:

try:
    system_prompt = run_hill_climbing(system_prompt, lessons, meter)
    run_state["decisions_since_last_improvement"] = 0
except BudgetExhaustedError:
    print(f"[loop3] hill-climbing skipped — budget exhausted at ${meter.spent:.4f}")
    run_state["decisions_since_last_improvement"] = 0

Anwendung des Musters an anderer Stelle

Das Vier-Schleifen-Design ist nicht auf Versicherungen beschränkt. Um es anzupassen, ersetzen Sie diese Komponenten:

  • Werkzeuge der Schleife 1. Hier: Abfrage, Regeln, Signal, Entscheidung. Bei Ihnen: Tools zur Datenerfassung und Ausführung für Ihr Fachgebiet.
  • Ground Truth. Hier: Die Datei mit den historischen Entscheidungen. Bei Ihnen: Ihr eigenes, beschriftetes Datensatz.
  • Urteiler. Hier: Vergleich von Zeichenketten. Bei Ihnen: Ein Korrekturtest, der speziell auf Ihre Aufgabe zugeschnitten ist.
  • Lektionen. Hier: Prüfregeln für die Risikobewertung. Bei Ihnen: Fehlermuster aus Ihrem Fachgebiet.
  • Aufforderung zum Hill-Climbing. Hier: Die Aufforderung für das Risikobewertungssystem. Bei Ihnen: Die Aufforderung für das System Ihres Agenten.
  • Budget. Hier: 2,00 $. Bei Ihnen: Der Betrag, den jeder Durchlauf kosten sollte.

Eine Bedingung ist unumstößlich: Der Richter muss deterministisch sein und unabhängig vom Agenten. Wenn für Ihre Aufgabe kein Test zur deterministischen Korrektheit definiert werden kann, kann das System sich nicht zuverlässig verbessern.

Ausprobieren

Einrichtung und die vier Befehle:

# 1. Install
cd underwriting_loop
pip install -e ".[dev]"
export ANTHROPIC_API_KEY=sk-ant-...
# 2. Run a single application (debug mode - cheapest)
python -m src.main --mode single --app-id APP-001
# 3. Drain the pending queue with event loop
python -m src.main --mode event-loop --once
# 4. Run standalone hill-climbing (5 rounds)
python -m src.main --mode improve --rounds 5
# 5. Run the full offline test suite (no API key needed)
pytest tests/ -v

Die Ausgabe beim Hügelsteigen sieht so aus:

seed score: 44%  (9 failures)
round 1: 56% KEPT  (7 failures)
round 2: 56% REVERTED (score 56% <= best 56%)
round 3: 69% KEPT  (5 failures)
round 4: 75% KEPT  (4 failures)
round 5: 69% REVERTED (score 69% <= best 75%)
Final test evaluation...
spend:              $1.8342
test score:         75%
gain (vs baseline): +25%

Der Ausgangsprompt erreicht bei der Trainierung 44 %. Fünf Runden später erzielt der siegreiche Prompt 75 % im zurückgehaltenen Testdatensatz – das ist ein Anstieg von 25 Punkten im Vergleich zur automatischen Prompt-Verbesserung ohne manuelle Prompt-Entwicklung.

Was die Schleifen bewirken

Loop-Engineering ist kein Framework. Es handelt sich dabei um eine Methode, die Zuverlässigkeit von Agenten in Schichten aufzuteilen, wobei jede Schicht eine einzige Verantwortung sowie einen einzigen Feedback-Mechanismus hat. Die vier Schleifen beantworten vier verschiedene Fehlermuster:

  • Schleife 1 kümmert sich um die Grundlage: Der Agent muss vor einer Entscheidung Beweise heranziehen.
  • Schleife 2 befasst sich mit Fehlern bei einer einzigen Ausführung: Ein schlechter Schlussweg sollte eine Anwendung nicht dauerhaft zum Scheitern bringen.
  • Schleife 3 kümmert sich um Durchsatz und Widerstandsfähigkeit: Eine fehlerhafte Anwendung sollte den gesamten Ablauf nicht blockieren.
  • Loop 4 behebt wiederkehrende Fehler, indem es die Anweisung selbst anhand der angesammelten Fehlschläge neu formuliert.

Man stapelt diese Fehlschläge, fügt einen deterministischen Bewertungsfaktor hinzu, schützt alles durch Budgetkontrolle und Manipulationserkennung – dadurch wird das System im Laufe der Zeit messbar besser, ohne menschliches Eingreifen.

Was Sie gewinnen

Verwenden Sie dieses Design mit offenen Augen. Die größte Vorsichtsmaßnahme aus der umfangreichen Literatur zu Agenten-Loops ist Addy Osmanis Konzept der „kognitiven Kapitulation“: Man überlässt dem Loop die volle Kontrolle und hört auf, seine Ausgaben zu prüfen. Die Vorteile bleiben dennoch real und messbar.

  1. Selbstverbesserung ohne Mensch. Durch das Hill-Climbing-Verfahren stieg die Trainingsgenauigkeit von 44 % auf 75 % – ohne manuelle Anpassung der Anweisungen – und das System extrahierte selbst die richtigen Prüfregeln aus den Fehlerrmustern.
  2. Fehlerisolation zwischen den Schichten. Ein fehlerhafter Anwendungscode bringt den gesamten Prozess nicht zum Stillstand. Schleife 3 fängt Fehler pro Anwendung ab, protokolliert sie und setzt die Ausführung fort. BudgetExhaustedError ist die einzige Ausnahme, die bis nach oben weitergeleitet wird – und das ist beabsichtigt.
  3. Fortschritt, den man messen und überprüfen kann. measure_gain() liefert eine konkrete Zahl; bei der getesteten Stichprobe lassen sich Vorteile im Vergleich zu zustandslosen Systemen feststellen. Die Verbesserung kann einem Stakeholder als Prozentsatz sowie in Dollarkosten dargestellt werden.
  4. Fälschungssichere Bewertung. Die SHA-256-Prüfsumme der Referenzwerte verhindert den häufigsten stillen Fehler selbstverbessernder Systeme – nämlich dass jemand die Antworten ändert, um bessere Ergebnisse zu erzielen. Wenn die Prüfsummen nicht übereinstimmen, läuft nichts.
  5. Wissen, das sich ansammelt. Die aus Fehlern gewonnenen Erkenntnisse bleiben über mehrere Ausführungen hinweg bestehen, sodass jede neue Aufrufung von Loop 1 intelligenter startet als die vorherige und nicht von vorne lernen muss.
  6. Produzent und Konsument bleiben voneinander getrennt. Mit einer Datei-Warteschlange ist das, was die Warteliste erzeugt, von der Auswertungspipeline getrennt. Die Warteschlange kann pausiert, wieder aufgenommen, überprüft oder abgespielt werden, während der Agent-Code unverändert bleibt.
  7. Human-Eskalation ist ein erstklassiger Zustand. refer-Entscheidungen werden in human_queue.json gespeichert und als erfolgreich gewertet. Der Loop versucht nicht erneut, etwas zu entscheiden, was von einem Menschen getan werden sollte.
  8. Die Wiederholungsversuche sind gezielt. Die Rückmeldung nach einem Fehlschlag gibt an, was schiefgelaufen ist – beispielsweise „Sie sagten ‚ändern‘, doch die richtige Antwort lautet ‚verweisen‘, prüfen Sie die Risikofaktoren erneut“. Dadurch wird schneller Fortschritt erzielt als durch zufälliges Resampling.

Was das kostet

  1. Die Token vermehren sich schnell. Fünf Durchgänge des Hill-Climbing-Verfahrens bei 16 Trainingsanwendungen erfordern mindestens 80 Ausführungen von Loop 1, plus Reflexions- und Prüfaufrufe. Eine Ausführung mit dem Parameter --mode improve verbrauchte 1,83 Dollar von einem Budget von 2,00 Dollar. Das Erreichen des Budgets ist kein Ausnahmefall – es ist die übliche Art und Weise, wie eine Ausführung endet.
  2. Die Latenz ist real. Eine Anwendung, die den gesamten Prozess durchläuft, kann 30 bis 60 Sekunden in Anspruch nehmen, und 16 Anwendungen im Event-Loop-Modus benötigen Minuten. Das ist für batchbasierte Aufgaben in Ordnung, aber für interaktive Nutzung ungeeignet.
  3. Der Nichtdeterminismus verdeckt Fehler. Schleife 1 verwendet ein lebendiges Modell, sodass derselbe Prompt in derselben Anwendung unterschiedliche Tool-Sequenzen hervorrufen kann. Ein Fehler lässt sich möglicherweise nicht reproduzieren, und aussagekräftige Genauigkeitswerte erfordern mehrere Bewertungsrunden.
  4. Die Einrichtung ist aufwendig. Vor der ersten Ausführung benötigt man den Ausführungszustand, die Warteschlange, die historischen Entscheidungen mit Checksummen, eine Datei mit den besten Prompts sowie die Lernliste. Das erfordert um ein Vielfaches mehr Infrastruktur als ein einziger Aufruf von agent.invoke().
  5. Überanpassung verschwindet nie. Ohne den Prüfmechanismus führt das Hügelsteigen letztendlich zu einem Prompt, der sich die Trainingsantworten einprägt, bei den Trainingsdaten perfekte Ergebnisse erzielt, bei Tests aber nur zufällige Ergebnisse liefert. Die Prüfung ist das Einzige, was zwischen Selbstverbesserung und Selbstbetrug steht.
  6. Kognitive Kapitulation ist ein echter Fehlermodus. Je zuverlässiger der Schleifenprozess wird, desto seltener liest jemand seine Ausgabe, und wenn man aufhört zu lesen, bemerkt man nicht, dass die Schleife auf neue Weise zu versagen beginnt. Unbeobachtete Schleifen benötigen externe Überwachung, da eine Schleife ihre eigene semantische Korrektheit nicht prüfen kann.
  7. Debugging umfasst den gesamten Stack. Eine falsche Entscheidung kann auf eine fehlerhafte Reflexion in Schleife 4, eine übersehene Kriterienregel in Schleife 2, ein Tool zurückzuführen sein, das in Schleife 1 nichts zurückgab, oder auf einen beschädigten Ausführungszustand in Schleife 3. Das Auffinden eines Fehlers in vier verschachtelten Schleifen ist schwieriger als das Auffinden desselben in einer einzigen Funktion.

Sollte man überhaupt eine Schleife erstellen?

Eine Übersicht zu öffentlichen Schleifenkonfigurationen gibt klare Ratschläge: Wiederholbare, überprüfbare und wertvolle Aufgaben sollten in Schleifen abgebildet werden, wobei ein integrierter Überprüfer vorhanden sein sollte; für alles andere reicht ein einziger Aufruf aus. Es reduziert sich auf zwei Fragen.

  1. Wird die Arbeit wiederholt oder erfordert sie viele Schritte, die niemand vorhersagen kann? Wenn ein einziger Aufruf ausreicht, führt eine Schleife nur zu zusätzlichem Aufwand ohne wirkliche Architekturvorteile.
  2. Kann der Agent selbst feststellen, ob er fertig ist? Ohne Test, erwartetes Format oder Referenzantworten endet die Schleife nicht und läuft unkontrolliert weiter.

Beide Antworten müssen mit „ja“ lauten. Wenn eine davon „nein“ ist, sollte die Schleife weggelassen werden.

Gute Anwendungsgebiete

Wiederholende, große Mengen an Arbeit. Sechzehn Versicherungsanträge, 500 Code-Dateien, 1.000 Support-Tickets. Die Schleife amortisiert ihre Konfigurationskosten, wenn die Aufgabe mehrfach ausgeführt wird und nicht nur einmal.

Es existiert eine deterministische Überprüfung auf Erfolg. Echte Referenzetiketten, ein Testumfang, ein Schema-Validator oder eine Liste binärer Bestehen/Fehlern-Regeln. Der Bewertungssystem sollte niemals ein Modell verwenden, denn ein auf Modellen basierendes System bringt seine eigene Unzuverlässigkeit mit ein.

Der Agent kann seine eigene Arbeit überprüfen. Anthropics Agent SDK bezeichnet dies als den am meisten unterschätzten Schritt. Hier stellt die Funktion is_correct() diese Überprüfung bereit, und bei einem Programmieragenten übernimmt dies der Testumfang. Wenn der Agent seine Ausgabe nicht überprüfen kann, schließt sich der Kreislauf nicht.

Etikettierte Daten oder wiederverwendbare Erfolgskriterien. Das Hill-Climbing-Verfahren benötigt eine Aufteilung in Trainings- und Testdaten mit bekannten Antworten. Ohne diese kann man über Fehlschläge nachdenken, aber nicht feststellen, ob diese Reflexion hilfreich war.

Niemand überwacht jede Ausführung. Denken Sie an Überprüfungsbatchs über Nacht, geplante Audits oder Dokumentenpipelines – dort sitzt niemand im inneren Schleifenzyklus, und genau dafür ist dieses Design gedacht. Wenn jemand jede Ausgabe sofort überprüfen kann, ist direktes Anweisen schneller.

Die Kosten pro Bewertungsausführung sind akzeptabel. Eine einzige Anpassungsrunde kostet genauso viel wie ein vollständiger Durchlauf aller Trainingsanwendungen. Wenn das unerschwinglich ist, gilt das auch für Loop 4 – führen Sie ihn daher seltener durch oder mit einem kleineren Trainingsdatensatz.

Ihnen sind Zahlen wichtig. measure_gain() existiert, weil „das System hat sich verbessert“ quantifiziert werden muss. Wenn die Interessengruppen Zahlen wollen, liefert die Schleifenentwicklung diese. Wenn „es sieht besser aus“ ausreicht, ist das übertrieben.

Schlechte Passgenauigkeit

Ein einziger Prompt reicht aus. Das Umformatieren einer CSV-Datei, das Auslesen eines Datums aus einem Dokument oder das Zusammenfassen eines Absatzes – all diese Aufgaben haben vorhersehbare Schrittanzahlen, weshalb man entweder ein Skript oder einen einmaligen Prompt verwenden sollte. Ein Loop in solchen Fällen ist eine überflüssige Komplexität für ein einfaches Problem.

„Fertig“ ist rein subjektiv. Der Auftrag „Mache diesen E-Mail-Text professioneller“ verfügt über keinen binären Prüfmechanismus, und ein zweites Modell, das die Ästhetik bewertet, führt zu Unsicherheiten, ohne etwas wirklich zu lösen. Definieren Sie zunächst klare Ziele – beispielsweise weniger als 100 Wörter, professioneller Tonfall, keine Ausrufezeichen –, erst danach kann geprüft werden, ob das Ziel erreicht wurde.

Aktionen sind ohne Überprüfung rückgängig zu machen. Das Versenden von E-Mails, das Zusammenführen von Pull-Requests, das Löschen von Datensätzen oder das Überweisen von Geldern – all das erfordert strenge Berechtigungsregelungen sowie eine Pause, bevor etwas gelöscht, gesendet oder bezahlt wird. Loops, die irreversible Aktionen ohne Kontrollmechanismen ausführen können, sind potenzielle Fehlerquellen.

Das Budget reicht nicht aus für die Bewertung. Wenn 2,00 Dollar knapp sind, wird dieses Geld bereits beim Anstieg der Komplexität aufgebraucht, bevor alle Trainingsanfragen bearbeitet werden können. Das System hat dies mit dem Fehler BudgetExhaustedError gelöst, doch bei einer budgetbeschränkten Konfiguration liegt die eigentliche Lösung in einem kleineren Trainingsdatensatz, weniger Durchgängen oder dem Verzicht auf Loop 4, bis das Budget wieder zur Verfügung steht.

Es gibt keine Zustandspersistenz. Wenn man den Laufzustand, die Lektionen sowie die Warteschlange weder auf Disk noch in eine Datenbank schreiben kann, ist es nicht möglich, nach einem Absturz fortzusetzen, Lektionen über verschiedene Sitzungen hinweg zu sammeln oder vor dem Auslaufen des Budgets während des Laufs zu schützen. Zustandslose Umgebungen benötigen eine andere Architektur.

Vous erkundet noch. Ein einmaliger Aufruf des Agenten mit geeigneten Werkzeugen zeigt auf, wo der Agent Fehler macht, während verschachtelte Schleifen nur zeigen, wo das gesamte System fehlschlägt. Prüfen Sie zunächst den einzelnen Agenten und fügen Sie Schichten hinzu, sobald seine eigenen Fehlermuster bekannt sind.

Ein weiteres Faustregel stammt von Rhys Sullivan, der vor zwei Extremen warnt: „Tokenmaxxing“-Schleifen, die nur dazu dienen, die Ausgaben in die Höhe zu treiben, sowie naive „Promptchud“-Einzeilensätze. Eine Schleife rechtfertigt sich, wenn sie echte Produktfehler in einer realistischen Testverteilung nachahmt, und verliert ihre Berechtigung, wenn sie lediglich beeindruckend aussieht.

Acht Eigenschaften einer funktionierenden Schleife

Schleifen brechen in der Regel auf unattraktive Weise ab. Sie hören niemals auf, erschöpfen das Budget oder liefern Unsinn ab, der als erledigt markiert wird. Eine Überprüfung öffentlicher Schleifenkonfigurationen ergab acht Eigenschaften in jeder zuverlässig funktionierenden Schleife, wobei jede davon auf eine Entwurfsentscheidung in dieser Codebasis zurückzuführen ist.

1. Ein überprüfbares Ziel

Definieren Sie „erledigt“ so, dass eine Maschine es überprüfen kann – nicht damit ein Mensch sich wohlfühlt. „Eine gute Prüfungsergebnisentscheidung erstellen“ ist nicht überprüfbar. is_correct(decision, app_id) vergleicht das Ergebnis mit historischen Entscheidungen; entweder stimmt es überein oder nicht. Ohne ein überprüfbares Ziel weiß die Schleife nie, wann sie aufhören soll, und liefert selbstbewusst, fein abgestimmt, aber falsche Ergebnisse als fertige Arbeit aus. Das Ziel ist ein binärer Prädikat und keine Beschreibung der Qualität. Die Aufteilung in Trainings- und Testdaten stellt sicher, dass das System nicht nur aus dem Gelernten memorisiert.

2. Ein festes Ende

Max tries, a budget or a time limit, always, with no exceptions. Never assume the model will stop on its own. Grassroots setups like a shell while :; do ... done loop contain nothing on the model side that ends them, so they run until somebody kills the process, and OpenAI's SDK ships max_turns because experience showed models don't reliably end themselves. This codebase has three stops at three levels: MAX_STEPS = 8 in Loop 1 prevents endless tool chains, max_retries = 3 in Loop 2 prevents endless retries, and BUDGET_USD = 2.00 through CostMeter is the ultimate backstop. At the step cap Loop 1 returns ran_out_of_steps, when retries run out Loop 2 returns passed=False, and when the budget is gone the error propagates up and halts everything.

3. Good tools

Die dem Agenten zur Verfügung stehenden Aktionen müssen zuverlässig, klar beschrieben und eng abgegrenzt sein. Der eigentliche Schleifenprozess ist unkompliziert, und die Arbeit erfolgt in den Tools. Laut den Richtlinien von Anthropic müssen Toolsets sowie ihre Dokumentation sorgfältig entworfen werden, da eine vage Beschreibung zu unklaren Aufrufen führt und ein übermächtiges Tool unumkehrbare Fehler verursacht. Es gibt hier vier Tools, von denen jedes eine bestimmte Aufgabe hat: reine Informationsabrufung ohne Nebeneffekte, Schlüsselwortsuche in einer statischen Wissensdatenbank, eine simulierte Abfrage externer Daten sowie eine strukturierte Terminalaktion, die eine Einhaltung eines vorgegebenen Schemas (Literal["accept", "modify", "decline", "refer"]), eine Begründung sowie explizite Flags erzwingt. Ohne dieses letzte Element gibt der Agent Prosa zurück, die eine empfindliche Parsing-Verarbeitung erfordert.

4. Speicher

Bewahren Sie die Historie auf, aber fassen Sie sie zusammen, damit der Kontext nicht übermäßig anwächst. Bei Gesprächen, die länger als ein Chat dauern, schreiben Sie den Zustand auf die Festplatte. Anthropics Long-Running-Agent-Tool ermöglicht es dem Agenten, vorherigen Zustand sowie Fortschrittsdateien aus Git zu lesen, schrittweise Fortschritte zu erzielen und die Umgebung für die nächste Iteration sauber zu hinterlassen. Ein roher Kontextfenster überlebt keine Neustarts, Abstürze oder Größenbeschränkungen. Hier wirken zwei Mechanismen in unterschiedlichen Zeitskalen: Innerhalb einer Laufzeit sammelt LangGraphs add_messages-Reducer den gesamten Dialog eines Anwendungsfalls. Über mehrere Laufzeiten hinweg sammelt die lessons-Liste in run_state.json zusammengefasste Regeln aus allen Anwendungsfallen und Neustarts. distill_lesson() ist der Komprimierungs-Schritt – er speichert eine einzelne, allgemein anwendbare Regel statt eines rohen Fehlerprotokolls, wodurch die Liste kurz bleibt und der eingefügte Kontext überschaubar bleibt.

5. Ein separater Prüfer

Erstellen, bewerten, korrigieren, wiederholen – der Ersteller bewertet niemals seine eigene Arbeit. Anthropics SDK bezeichnet die Überprüfung der Arbeit als den am meisten unterschätzten Schritt, doch diese Überprüfung muss unabhängig von der Erstellung erfolgen. Ein Agent, der sich selbst bewertet, wird zu einem Ja-Sager, und indem man einem zweiten Agenten nur die Bewertungsaufgabe überlässt, wird das verhindert. Diese Codebasis verfügt über drei unabhängige Ebenen der Überprüfung:

  • Rubrik-Prüfung (Struktur). RubricMiddleware prüft neun Regeln anhand der analysierten Ausgabe mithilfe reiner boolescher Prädikate ohne Verwendung eines Modells.
  • Bewertungs-Prüfung (Richtigkeit). is_correct() vergleicht die Antwort mit der gespeicherten Kennzeichnung durch einen exakten String-Vergleich.
  • Audits-Prüfung (Gegen Betrug). audit_prompt() sucht nach auswendig gelernten Antworten mithilfe einer binären Modellklassifizierung, nicht einer Qualitätseinschätzung.

Die Bewertungskriterien sowie die Bewertenden sind vollständig deterministisch. Die Überprüfung verwendet zwar ein Modell, jedoch nur für eine binäre Strukturfrage („Enthält dies Anwendungsinformationen?“), in der modellbasierte Bewertungen am zuverlässigsten sind.

6. Ein Plan für komplexe Aufgaben

Bei großen, mehrschrittigen Aufgaben sollte der Agent vor dem Handeln einen Plan erstellen, bei kleinen Aufgaben kann dieser weggelassen werden. Anthropics langfristige Forschung zu langlaufenden Agenten hat gezeigt, dass es entscheidend ist, sich nacheinander auf eine Eigenschaft zu konzentrieren – schließlich führt ein Befehl, alles auf einmal zu beheben, zu Fehlverhalten, während ein Schritt pro Durchlauf zu konvergenten Ergebnissen führt. In dieser Codebasis legt die Anweisung vier Schritte in der richtigen Reihenfolge fest, die als integrierter Plan fungieren: Daten abrufen, Regeln abrufen, externe Signale abrufen, eine Entscheidung treffen. Die Reihenfolge verhindert, dass der Agent direkt zu einer Entscheidung gelangt, ohne Beweise zu prüfen, und die Obergrenze von acht Schritten ist so gewählt, dass die gesamte Abfolge plus eine Wiederholung möglich sind.

7. Protokollierung

Protokollieren Sie jeden Gedanken, jede Aktion und jedes Ergebnis, damit bei einer Debugging-Sitzung um 3 Uhr morgens etwas zum Lesen vorhanden ist. Wenn ein Schritt nicht protokolliert wird, ist er aus Sicht des Debuggings nicht stattgefunden. Fehler in verschachtelten Schleifen sind ohne Protokolle nahezu undiagnostizierbar: Welche Schleife, welche Anwendung, welcher Versuch, welches Tool hat das Falsche zurückgegeben? Drei Ebenen decken dies ab. Fortschrittsanzeigen wie [loop3] 3/16 APP-009 ... werden aktualisiert, was unter Windows, wo stdout gebuffert wird, unverzichtbar ist. Entscheidungsanzeigen enthalten in einer Zeile pro Anwendung die Entscheidung, den Status sowie die bisherige Laufzeit. Lernhinweiszeilen bestätigen, dass die Extraktion stattgefunden hat.

8. Kostenbewusstsein

Loops verbrauchen Tokens schnell, daher sollte man mit kleinen, begrenzten Mengen beginnen und diese anschließend schrittweise erhöhen. Beobachten Sie die ersten Ausführungen, bevor Sie einen Loop unbeaufsichtigt lassen. Eine einzige „Hill-Climbing“-Runde über 16 Trainingsanwendungen, inklusive fünf Runden sowie Reflexions- und Prüfaufrufen, kostet fast 2,00 Dollar. Erst wenn Sie die Kosten pro Iteration kennen, können Sie ein Budget festlegen und weitermachen. CostMeter verfolgt die Ausgaben bis auf vier Nachkommastellen und löst den BudgetExhaustedError aus, bevor ein Überschuss verzeichnet wird:

# The ordering matters:
if self.spent >= self._budget:          # check BEFORE adding
    raise BudgetExhaustedError(...)
self.spent += cost                       # add AFTER the check clears

Der Event-Loop erkennt außerdem einen Budgetausfall während des „Hill Climbing“-Prozesses getrennt vom Ausfall beim Verarbeiten von Aufgaben – dadurch wird ein Ausfall mitten in einer Selbstverbesserungsrunde nicht dazu führen, dass der Rest der Warteschlange verloren geht. Der Loop reagiert hierauf sanft: Der teure Verbesserungsschritt wird übersprungen, und die Verarbeitung geht weiter.

Checkliste, bevor man ihn unüberwacht laufen lässt

Bevor man einem Loop erlaubt, unüberwacht zu laufen, sollte man Folgendes überprüfen:

  1. Das Ziel ist maschinenprüfbar (hier is_correct() im Vergleich zur Referenzwahrheit).
  2. Es gibt an jeder Stufe eine feste Abschaltbedingung (MAX_STEPS, max_retries, BUDGET_USD).
  3. Die Tools sind klar definiert und dokumentiert (vier Tools, jeweils eine Aufgabe).
  4. Der Zustand bleibt auch nach Neustarts erhalten (run_state.json zusammen mit der Lernliste).
  5. Der Prüfer ist vom Generierer getrennt (Rubrik-Middleware zusammen mit einem deterministischen Bewertungssystem).
  • Komplexe Aufgaben haben einen impliziten oder expliziten Plan (die vierstufige Abfolge in der Anweisung).
  • Wichtige Schritte werden protokolliert und abgeschlossen (die [loop3]-Fortschrittslinien).
  • Ein Budget wird bereits bei dem ersten Aufruf festgelegt und überwacht (ein CostMeter, der in allen Schleifen gemeinsam genutzt wird).
  • Fehlt einer dieser Aspekte, scheitert die Schleife in der Produktion – nicht immer sofort, aber zuverlässig letztendlich. Die meisten Produktionsfehler lassen sich auf das Fehlen eines Überprüfungsschritts, einer Stoppsbedingung oder eines persistenten Zustands zurückführen. Stellen Sie diese zuerst richtig ein, bevor Sie alles Weitere optimieren.

    Verwandte Literatur

  • Playwright und TypeScript – ohne Vorkenntnisse in TypeScript: Ein Ingenieur, eine KI-Schicht und vier Schritte — Wie ein QA-Ingenieur mit Erfahrung in Selenium und C# ein Playwright-Framework in TypeScript mithilfe von KI entwickelte: Struktur, Architekturregeln, erforderliche Fähigkeiten, Hook-Funktionen sowie ein vierstufiger Ticket-Verarbeitungsprozess.
  • Loop-Engineering für KI-Agenten: Fünf Bestandteile, vier Formen und die Erstellung mit LangGraph — Wie man gezielt den Loop hinter einem Agenten entwirft: seine fünf Bestandteile, vier gängige Formen, ein in LangGraph implementierter Forschungsagent zur Überprüfung und Wiederholung, Fehlerabwehrmechanismen sowie Situationen, in denen kein Loop erforderlich ist.
  • Selbstverbessernde Agenten: Heilung, Lernen und kontrollierte Veränderung — Eine Architektur für Agenten, die sich während der Ausführung erholen, über mehrere Ausführungen hinweg lernen und nur durch geprüfte, getestete Versionen verändert werden, unter Verwendung von LangGraph, MongoDB und Phoenix.
  • Von Agentenschleifen zu Workflow-Graphen: Typisierte Zustände, Beweisgatter und begrenzte Erholung — Warum langlaufende Agenten eine einzelne Schleife überschreiten und wie explizite Knoten, Kanten, typisierte Artefakte, überprüfte Übergänge sowie per-Knoten-Rechte zu ihrer Zuverlässigkeit beitragen.
  • Aufzeichnen der Schleifen, die ein KI-Agent in einer mobilen App ausführt — Vier Fragen, die jede Agentenschleife beantworten muss, sechs verschachtelte Schleifen vom Tippen bis zur Verarbeitung sowie fünf Möglichkeiten, wie eine Überprüfung „erledigt“ angezeigt werden kann, ohne etwas zu erfassen.
  • Schleifenengineering: Erstellung von Agentenschleifen mit Überprüfungsfunktionen und Stoppsregeln — Ein praktischer Leitfaden zur Erstellung von vier übereinanderliegenden Agentenschleifen: Grundlagen von ReAct, Bewertungssysteme, LangGraph-Routing, Schleifenverträge, Ereignistreiger, Hill-Climbing-Verfahren sowie Sicherheitsmechanismen gegen Fehler.