Agentische KI ist meistens while(true) mit Vibes
Produktionserfahrungen mit autonomen Agenten in lang laufenden Schleifen, Fallback-Pattern, die wirklich funktionieren, und der Tag, an dem dein Agent 47-mal hintereinander einen Retry beschloss.
Als ich meine erste Agent-Schleife in Produktion brachte, lief sie drei Stunden lang einwandfrei — und produzierte dann still und leise eine Rechnung von 312 USD, weil sie denselben Tool-Aufruf siebenundvierzig Mal wiederholte. Die Upstream-API hatte angefangen, ein höflich formuliertes 200 OK zurückzugeben, dessen Body "service temporarily unavailable" enthielt. Das Modell, hilfsbereit wie immer, hielt das für einen kurzen Schluckauf. Siebenundvierzig Mal in Folge.
Man lernt von Agenten viel über verteilte Systeme. Nichts davon ist neu — es sind dieselben Lektionen, die wir 2008 aus Cronjobs gelernt haben — aber das Modell bringt eine spezielle Art von Selbstvertrauen mit, die die Failure-Modes lauter macht.
Die naive Form
Jedes Agentic-AI-Tutorial beginnt gleich. Du schreibst eine Schleife:
async function agentLoop(task: string) {
const messages = [{ role: "user", content: task }];
while (true) {
const response = await llm.complete({ messages, tools });
if (response.stop_reason === "end_turn") {
return response.content;
}
for (const toolCall of response.tool_calls) {
const result = await runTool(toolCall);
messages.push({ role: "tool", content: result });
}
}
}In der Demo läuft das schön. Das Modell macht ein paar Runden, entscheidet, dass es fertig ist, kehrt zurück. Du gehst live.
In Produktion ist diese Schleife eine Geldkanone, die auf dein Konto zielt.
Wo es bricht
Eine kurze, nicht vollständige Liste, wie ich diese Schleife scheitern gesehen habe:
- Das Modell entschied, ein Funktionsaufruf sei "fast richtig", probierte zwölf Varianten derselben Argumente, gab dann auf.
- Ein Tool gab 500 zurück. Das Modell versuchte es erneut. Tool gab 500 zurück. Modell versuchte es erneut — mit denselben Argumenten. 91 Mal, bis der Budget-Alarm losging.
- Das Modell traf auf ein Tool-Ergebnis, das es nicht verstand, halluzinierte ein zweites Tool, das nicht existierte, und versuchte sich zu retten, indem es das halluzinierte Tool aufrief.
- Die Aufgabe des Nutzers war nach Schritt zwei eigentlich erledigt, aber das Modell rief weiterhin Such-Tools auf, "nur zur Bestätigung", bis es das 100k-Context-Limit erreichte und crashte.
- In einem Multi-Agent-System begannen zwei Agenten, sich gegenseitig Tool-Aufrufe zu Ping-Pong-en. Sie waren dabei sehr höflich.
Das Kernproblem ist nicht die Schleife. Es ist, dass das Modell keine Meinung dazu hat, wie lange es laufen soll. Es läuft glücklich weiter bis zum Wärmetod des Universums — oder bis du es ihm in Rechnung stellst.
Was tatsächlich hilft
Drei Kontrollen, in der Reihenfolge, wie viel Schmerz sie mir erspart haben.
1. Budget — nicht Retry-Anzahl
Tool-Aufrufe zu zählen ist die falsche Einheit. Token-Verbrauch ist die richtige, denn das tut weh. Verfolge ihn innerhalb der Schleife und brich kurz:
type Budget = {
maxInputTokens: number;
maxOutputTokens: number;
maxToolCalls: number;
spentInput: number;
spentOutput: number;
toolCalls: number;
};
function overBudget(b: Budget): boolean {
return (
b.spentInput > b.maxInputTokens ||
b.spentOutput > b.maxOutputTokens ||
b.toolCalls > b.maxToolCalls
);
}
async function agentLoop(task: string, budget: Budget) {
const messages: Message[] = [{ role: "user", content: task }];
while (!overBudget(budget)) {
const response = await llm.complete({ messages, tools });
budget.spentInput += response.usage.input_tokens;
budget.spentOutput += response.usage.output_tokens;
if (response.stop_reason === "end_turn") return response.content;
for (const call of response.tool_calls) {
budget.toolCalls += 1;
const result = await runTool(call);
messages.push({ role: "tool", content: result });
}
}
// Kein Throw — strukturiertes Teilergebnis zurückgeben.
return {
status: "budget_exceeded",
partial: messages.at(-1),
spent: budget,
};
}Der billige Erkenntnisgewinn: Wenn das Budget aufgebraucht ist, wirf keine Exception. Gib ein strukturiertes Teilergebnis zurück. Eine Exception zerstört den Kontext des Aufrufers. Ein Teilergebnis mit status: "budget_exceeded" lässt den Aufrufer entscheiden — eskalieren, auf einen einfacheren Pfad wechseln, oder einfach loggen.
2. Tool-Aufrufe sind nicht idempotent. Mach sie idempotent.
Wenn das Modell ein Tool neu aufruft, sollen sich Seiteneffekte nicht vervielfachen. Wickel jedes Tool in einen Idempotency-Key, der aus den Argumenten abgeleitet ist, und cache das Ergebnis innerhalb der Schleife:
function toolKey(name: string, args: unknown): string {
return `${name}:${stableStringify(args)}`;
}
const toolCache = new Map<string, ToolResult>();
async function runToolMemoized(call: ToolCall) {
const key = toolKey(call.name, call.arguments);
const hit = toolCache.get(key);
if (hit) {
return { ...hit, _cached: true };
}
const result = await runTool(call);
toolCache.set(key, result);
return result;
}Ruft das Modell dasselbe Tool mit denselben Argumenten zweimal in einer Schleife auf, lieferst du das gecachte Ergebnis und kennzeichnest es. Das Modell folgert meist aus dem Hinweis, dass es etwas anderes probieren soll. Manchmal nicht. Dafür ist das Budget da.
3. Der Fallback ist ein echtes Produkt-Feature
Jede Agent-Schleife braucht eine finale Antwort, wenn das Budget aufgebraucht, die Geduld zu Ende oder ein Tool-Fehler nicht behebbar ist. Der Fallback ist keine Exception. Der Fallback ist eine bescheidene, strukturierte Antwort, die sagt, was versucht wurde, was gelernt wurde und was der Agent als nächsten Schritt vorschlägt.
type AgentResult =
| { status: "ok"; answer: string; trace: TraceEntry[] }
| { status: "partial"; bestGuess: string; missing: string[]; trace: TraceEntry[] }
| { status: "failed"; reason: string; trace: TraceEntry[] };Wenn dein Agent { status: "failed", reason: "habe mein Bestes gegeben" } zurückgibt, ist das kein Bug. Das ist die Schleife, die weiß, wann sie stoppen muss. Der schwere Teil von agentischer KI ist genau das: zu wissen, wann man aufhört, es zu versuchen. Das Modell wird es dir nicht sagen. Das musst du tun.
Trade-offs
Für einen Agenten mit fünf Runden, der eine einzelne Suche durchführt und zusammenfasst, brauchst du nichts davon. Du brauchst jede Zeile, sobald der Agent mehr als ein Tool aufruft, länger als eine Minute läuft oder pro Aufruf mehr als einen Cent kostet.
Das Pattern skaliert nach unten auf null — budget=Infinity ist ein No-Op — und nach oben auf Stunden Laufzeit. Die Kosten, es zu schreiben, sind klein. Die Kosten, es nicht zu schreiben, waren bei mir eine Rechnungsposition mit "API-Nutzung" und einer vierstelligen Zahl daneben.
Die Zusammenfassung, die dein Chef will
Agentische KI ist meistens eine while(true)-Schleife mit Vibes. Die Vibes sind der einfache Teil. Die Schleife auch. Der schwere Teil ist das Budget, die Idempotenz und der Fallback — drei Dinge, die jeder Backend-Engineer schon einmal gebaut hat, für Cronjobs, 2008.
Das Modell fügt verteilten Systemen nichts Neues hinzu. Es macht die Failures nur ausdrucksstärker.
// wenn du schon hier bist
- 11 Min Lesezeit
Hybrid-Suche mit Qdrant: was über BM25 + Dense + Bild keiner sagt
Was du wirklich verkabelst, wenn du Stichwort, Dense-Vektor und Bild-Embedding zu einem Ranker fusionierst — Named Vectors, Fusion, Drift und der Tag, an dem die türkische Suche an Apostrophen scheiterte.
hybrid-searchqdrantembeddingsproduction - 8 Min Lesezeit
React Native, der Native Driver und der Jank, den du endlich fühlen kannst
Wann useNativeDriver dir wirklich etwas bringt, wann er dich anlügt, und wie du den verlorenen Frame findest, der deinen Scanner-Bildschirm langsam wirken lässt.
react-nativeperformancemobile