Beenden Sie Schleifen von entgleisten KI-Agenten: Vier Schutzmaßnahmen für die Produktion
Der Cap dreht sich, Tokens und Zeit – Ziele werden mit echten Tests überprüft, Hash-Tool-Aufrufe dienen dazu, Ping-Pong-Schleifen zu erkennen, und es wird sicher rückgängig gemacht, wenn ein autonomer Agent stecken bleibt.
Quellen: Don’t Let Your AI Agents Loop Forever: An Engineering Guide to Termination Criteria
Eine Rechnung über 3.800 Dollar für eine API-Nutzung durch einen autonomen Agenten, der zwölf Stunden lang in einer Schleife zur Syntaxkorrektur feststeckte, ist ein Initiationsritus, den niemand durchlaufen wollen sollte. Hier erfahren Sie, wie man ein agierendes System mit stoppenden Regeln produktionstauglicher Art umgibt.
Das Szenario am Freitagnachmittag
Es ist Freitag, 17:00 Uhr. Ein neuer Programmieragent wird in die Testumgebung eingeschleust. Seine Aufgabe besteht darin, Pull-Requests zu überprüfen, fehlerhafte TypeScript-Typen zu reparieren und die Tests erneut auszuführen. Die Anweisungen scheinen in Ordnung zu sein, der Laptop wird geschlossen und das Wochenende beginnt.
Bis Samstagmorgen überschwemmen PagerDuty- und Rechnungsbenachrichtigungen das Telefon. Der Agent hat user.service.ts aufgrund eines Typfehlers repariert und damit auth.service.ts beschädigt. Die Korrektur dieses Files beschädigte wiederum den ersten, und die beiden Files setzten sich ständig gegenseitig außer Kraft. Nach 1.400 Toolaufrufen beträgt die Rechnung für Token 3.800 Dollar und der Testumfang ist beschädigt.
Der größte Teil der ingenieurtechnischen Aufmerksamkeit gilt Prompten, der Auswahl von Tools sowie Orchestrierungsgraphen. In der Produktion hat die Entscheidung mit den größten Folgen darüber, wie der Agent stoppt, Vorrang vor der Entscheidung darüber, wie er startet.
Was ein agenter Zyklus ist
Ein agenter Zyklus ist ein Steuerungszyklus, bei dem ein LLM den Zustand wahrnimmt, eine Aktion auswählt, ein Tool aufruft, die Ausgabe liest und entscheidet, ob er fortfahren soll.
┌──────────────────────────────────────┐
│ Agent Perception Loop │
│ (Perceive → Plan → Act) │
└──────────────────┬───────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ 1. Success Guard │ │ 2. Resource Caps │ │ 3. Progress Guard │
│ (Programmatic Test)│ │ (Tokens/Turns/Time)│ │ (Loop/Hash Detect) │
└────────────────────┘ └────────────────────┘ └────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 4. Human Handoff / Safe Rollback │
└──────────────────────────────────────┘
Falls das um es herum liegende Harness keine schichtweisen, präzisen Beendigungsregeln aufweist, landet der Agent letztendlich in einem von drei Fehlermodi:
- Endlose Ausführung, bei der wiederholt falsche oder doppelte Tool-Aufrufe erfolgen.
- Frühzeitiger Abbruch, bei dem der Agent aufgibt und einen Sieg erklärt, obwohl die Arbeit unvollendet ist oder stillschweigend fehlschlägt.
- Ausnutzung des Budgets, bei der der gesamte Token-Vorrat aufgebraucht wird, bevor eine mehrstufige Aufgabe abgeschlossen ist.
Vier Arten von Beendigungskriterien decken diese Fälle ab, und jeder Agent benötigt alle vier, bevor er veröffentlicht werden kann.
1. Erfolgskriterien: Das Ziel deterministisch überprüfen
Die Fallgrube. Akzeptieren Sie niemals das eigene Urteil des Agents, indem Sie das LLM fragen, ob es fertig ist. Modelle neigen zum Abschlussbias und zur Schmeichelei und melden oft Erfolg, obwohl die Erstellung fehlerhaft ist oder die Ausgabe-Datei leer ist.
Die Lösung. Behandeln Sie die Ausgabe des Modells als unausgeprüfte Hypothese und lassen Sie den Loop den tatsächlichen Zustand mit etwas Deterministischem abgleichen:
- Führen Sie
npm testoderpytestin einem Kindprozess aus und lesen Sie den Abbruchcode - Führen Sie Typüberprüfungen und Linting mit strengen Abbruchcodes durch, wie z. B.
tsc --noEmit - Analysieren Sie die Ausgabe mithilfe von Zod- oder Pydantic-Schemata
import { execSync } from 'node:child_process';
export interface VerificationResult {
success: boolean;
message: string;
stepFailed?: string;
}
/** execSync throws on non-zero exit; diagnostics may land on either stream. */
function runOrCapture(command: string, cwd: string): string | null {
try {
execSync(command, { cwd, stdio: 'pipe' });
return null;
} catch (err: unknown) {
const e = err as { stdout?: Buffer; stderr?: Buffer };
const out = e.stdout?.toString() ?? '';
const errOut = e.stderr?.toString() ?? '';
return [out, errOut].filter(Boolean).join('\n') || String(err);
}
}
export class GoalVerifier {
public static verify(workspacePath: string): VerificationResult {
const steps: Array<[string, string]> = [
['tsc', 'npx tsc --noEmit'],
['npm_test', 'npm test'],
];
for (const [stepId, command] of steps) {
const failure = runOrCapture(command, workspacePath);
if (failure !== null) {
return {
success: false,
message: `\`${command}\` failed:\n${failure}`,
stepFailed: stepId,
};
}
}
return { success: true, message: 'All typechecks and tests passed cleanly.' };
}
}
Die Faustregel ist einfach. Wenn ein menschlicher Prüfer einen Befehl ausführen würde, um einen Pull Request zu überprüfen, sollte derselbe Befehl auch die Abbruchbedingung der Schleife bilden.
2. Ressourcen- und Budgetobergrenzen: feste Grenzen
Das Problem. Wenn ein Agent auf einen Sonderfall oder einen unerwarteten Tool-Fehler stößt, versucht er standardmäßig mit kleinen Anpassungen der Anfragen erneut. Da es auf Engine-Ebene keine Obergrenzen gibt, kann eine feststeckende Schleife innerhalb weniger Minuten Millionen an Tokens verbrauchen.
Die Lösung. Setzen Sie feste Grenzen in drei unabhängigen Dimensionen fest: Anzahl der Schritte, Tokens und reine Laufzeit. Zählen Sie Schritte und Tokens getrennt voneinander, da ein Schritt mehrere Modellaufrufe beinhalten kann, und eine Vermischung beider Werte heimlich die Anzahl der Schritte erhöht.
export interface ResourceLimits {
maxTurns: number; // e.g. 10 iterations
maxTotalTokens: number; // e.g. 100_000 input + output
timeoutMs: number; // e.g. 120_000 (2 minutes)
}
export class ResourceGuard {
private readonly startTime = Date.now();
private totalTokensUsed = 0;
private currentTurn = 0;
constructor(private readonly limits: ResourceLimits) {}
/** Call once per loop iteration, before the model call. */
public beginTurn(): void {
this.currentTurn += 1;
}
/** Call for every model call, including retries inside a turn. */
public recordUsage(tokens: number): void {
this.totalTokensUsed += tokens;
}
public getTurnCount(): number {
return this.currentTurn;
}
public checkShouldTerminate(): { terminate: boolean; reason?: string } {
if (this.currentTurn >= this.limits.maxTurns) {
return {
terminate: true,
reason: `Exceeded turn cap (${this.limits.maxTurns})`,
};
}
if (this.totalTokensUsed >= this.limits.maxTotalTokens) {
return {
terminate: true,
reason: `Exceeded token budget (${this.totalTokensUsed}/${this.limits.maxTotalTokens})`,
};
}
const elapsed = Date.now() - this.startTime;
if (elapsed >= this.limits.timeoutMs) {
return {
terminate: true,
reason: `Wall-clock timeout reached (${elapsed}ms/${this.limits.timeoutMs}ms)`,
};
}
return { terminate: false };
}
}
Die Obergrenzen dienen nur als Notfallmaßnahme, nicht als Designelement. Wenn Ihr Agent regelmäßig abbricht, weil die Anzahl der Schritte maxTurns erreicht hat, liegt das Problem nicht an den Obergrenzen, sondern an der Aufgabenstruktur.
3. Fortschrittsüberwachung: Erkennung von blockierten und abgeschweiften Agenten
Drei Muster tauchen immer wieder auf:
- Ping-Pong-Effekt. Der Agent verwendet Werkzeug A, stößt auf Fehler X, verwendet Werkzeug B, um das Problem zu beheben, stößt auf Fehler Y und führt anschließend Werkzeug A erneut mit denselben Eingaben aus.
- Unkontrolliertes Handeln. Die Bearbeitung von Datei A zerstört Datei B, und die Bearbeitung von Datei B zerstört Datei A. Die Arbeitsumgebung ändert sich ständig, während der Gesamtfortschritt bei null bleibt.
- Semantische Abweichung. Der Agent stößt auf eine unerwartete Warnung des Linters, verbringt fünf Schritte mit Stilfragen und vergisst das ursprüngliche Ziel.
Um Deadlocks zu erkennen, bevor maxTurns aufgebraucht wird, sollte man ein schräg verlaufendes Fenster mit Hash-Werten von Tool-Aufrufen zusammen mit Hash-Werten von Workspace-Diffs speichern. Wiederholte identische Hash-Werte deuten darauf hin, dass der Agent im Kreis läuft.
import { createHash } from 'node:crypto';
export interface ToolCall {
name: string;
args: Record<string, unknown>;
}
export class ProgressGuard {
private readonly recentActionHashes: string[] = [];
constructor(
private readonly windowSize = 5,
private readonly repeatThreshold = 3,
) {}
/** Stable stringify: key order must not change the hash. */
private hashToolCall(call: ToolCall): string {
const args = JSON.stringify(call.args, Object.keys(call.args).sort());
return createHash('sha256').update(`${call.name}:${args}`).digest('hex');
}
/** Returns true when the same call has appeared `repeatThreshold` times in the window. */
public trackAndCheckStuck(call: ToolCall): boolean {
const actionHash = this.hashToolCall(call);
const priorOccurrences = this.recentActionHashes.filter((h) => h === actionHash).length;
this.recentActionHashes.push(actionHash);
if (this.recentActionHashes.length > this.windowSize) {
this.recentActionHashes.shift();
}
return priorOccurrences + 1 >= this.repeatThreshold;
}
}
Nicht jede Wiederholung verdient denselben Verdacht. Ein dreimal ausgeführter nur-Lese-Aufruf ist in der Regel harmlos, während ein dreimal wiederholter Schreibvorgang fast nie harmlos ist. Wenn Tools-Metadaten verfügbar sind, sollte der Schwellenwert entsprechend angepasst werden, anstatt alle Aufrufe gleich zu behandeln.
4. Menschliche Eskalation und sichere Rückschaltung
Wenn eine Schleife aufgrund einer Budgetobergrenze oder eines Deadlocks endet, ist das schlimmste Ergebnis ein teilweise beschädigtes System. Ein Circuit Breaker sollte in dieser Reihenfolge drei Dinge tun: Diagnosedaten erfassen (welches Tool versagt hat und welche letzten Fehler aufgetreten sind), die Arbeitsumgebung in einen bekannten Zustand zurückversetzen und anschließend mit einem klaren Zusammenfassung an einen Menschen eskalieren.
Achten Sie bei dem zweiten Schritt darauf. Das Ausführen von git reset --hard gefolgt von git clean -fd lässt sich nicht rückgängig machen. git stash --include-untracked ist sicherer, da es die Arbeit des Agents zur Überprüfung bereithält und später noch verworfen werden kann. Verwenden Sie den vollständigen Reset nur in temporären Sandbox-Umgebungen.
import { execSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
export class AgentEscalationRequiredError extends Error {
constructor(message: string, public readonly reportPath?: string) {
super(message);
this.name = 'AgentEscalationRequiredError';
}
}
export class AgentCircuitBreaker {
constructor(
private readonly workspaceDir: string,
private readonly reportDir: string, // keep reports OUTSIDE the workspace
) {}
public handleAbort(reason: string, history: unknown[] = []): never {
console.error(`[CIRCUIT BREAKER] Terminating agent loop: ${reason}`);
// 1. Park workspace changes recoverably.
try {
execSync('git stash push --include-untracked -m "agent-abort"', {
cwd: this.workspaceDir,
stdio: 'pipe',
});
} catch (err) {
console.error('git stash failed during abort; workspace left as-is:', err);
}
// 2. Write a diagnostic trace for human review.
const reportPath = this.writeFailureReport(reason, history);
// 3. Signal the orchestrator.
throw new AgentEscalationRequiredError(`Agent failed safely. Reason: ${reason}`, reportPath);
}
private writeFailureReport(reason: string, history: unknown[]): string {
const reportPath = join(this.reportDir, `agent_failure_${Date.now()}.json`);
writeFileSync(
reportPath,
JSON.stringify(
{
timestamp: new Date().toISOString(),
workspace: this.workspaceDir,
reason,
historyLength: history.length,
history: history.slice(-10),
},
null,
2,
),
'utf-8',
);
return reportPath;
}
}
Fallstudie: Ein offenes App-Generator-Tool
Bei der Behebung eines Fehlers gibt es eine feste Testsuite, gegen die überprüft werden kann. Schwieriger ist hingegen eine offene, subjektive Anweisung – beispielsweise: Erstelle eine moderne Webanwendung lokal, die Aufgaben verwalten kann, autonom arbeitet, bis die Arbeit abgeschlossen ist, optisch ansprechend ist und voll funktionsfähig ist, und entscheide selbst, wann sie gestoppt werden soll.
Begriffe wie „optisch ansprechend“ und „abgeschlossen“ sind äußerst unklar. Lässt man das System unbeeinflusst, stoppt der Agent entweder nach der Erstellung eines einfachen index.html-Dateis oder verbringt endlose Zeit damit, CSS-Margins anzupassen.
┌──────────────────────────────────────────────────────────┐
│ Vague prompt ("build a modern web app locally") │
└────────────────────────────┬─────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ Phase 1: Dynamic spec synthesis (`ac-matrix.json`) │
└────────────────────────────┬─────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ Phase 2: Multi-agent execution loop │
│ (Coder agent + design critic + headless E2E verifier) │
└────────────────────────────┬─────────────────────────────┘
│
┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Gate 1: Goal │ │ Gate 2: Resource │ │ Gate 3: Progress │
│ verification │ │ caps (turns/ │ │ guard (deadlock/ │
│ (build/E2E/ACs) │ │ token budget) │ │ repetition) │
└─────────┬────────┘ └─────────┬────────┘ └─────────┬────────┘
└───────────────────────┼───────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ Gate 4: Safe exit OR circuit-breaker rollback │
└──────────────────────────────────────────────────────────┘
Zuerst eine Spezifikation erstellen
In Runde eins, bevor Code geschrieben wird, erstellt der Agent eine explizite Matrix mit Akzeptanzkriterien und speichert sie als ac-matrix.json. Dabei werden die Kriterien in drei Kategorien eingeteilt:
- Funktionskriterien, wie lokale SQLite- oder JSON-Speicherung, CRUD-Operationen sowie ein Schalter für Dark Mode.
- Technische Kriterien, wie keine TypeScript- oder Lint-Warnungen sowie ein Dev-Server, der ohne Konsolefehler startet.
- Designkriterien, wie ein responsives Layout, konsistente Design-Elemente, leere Zustände sowie einfache Interaktionen.
Das gleiche Modell, das die Matrix erstellt, bewertet sich später selbst anhand dieser Kriterien – daher sollte sie als Rahmenvertrag und nicht als objektives Standardverfahren betrachtet werden. Ihr eigentlicher Wert liegt darin, dass sie frühzeitig festgelegt wird, bevor der Agent einen Grund hat, die Ziele zu ändern.
Trennung der Rollen
Um zu verhindern, dass ein Agent seine eigene Arbeit als perfekt bewertet, sollten die Aufgaben getrennt werden:
- Ein Code-Sub-Agent entwickelt Funktionen, kümmert sich um die Routing-Logik und das Styling.
- Ein Headless-Browser-Prüfer startet den Entwicklungsserver, erstellt Screenshots mit Playwright und überprüft auf Konsolefehler.
- Ein Design-Kritiker-Sub-Agent prüft die Benutzeroberfläche anhand der Standards des Design-Systems hinsichtlich Hierarchie, Abständen und Kontrast.
Die Orchestrierungslösung
Der folgende Code setzt injizierte Abhängigkeiten voraus (llmAgent, executeTool, runHeadlessBrowserCheck und runDesignCriticAgent), die Sie durch Ihre eigene Orchestrierungsschicht ersetzen sollten.
import { execSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { ResourceGuard, ProgressGuard, AgentCircuitBreaker } from './guards';
interface AcceptanceCriterion {
id: string;
description: string;
status: 'PENDING' | 'IN_PROGRESS' | 'DONE';
}
interface AcMatrix {
items: AcceptanceCriterion[];
}
export interface RunResult {
success: true;
turns: number;
summary: string;
}
export async function runOpenEndedWebAppGenerator(
userPrompt: string,
workspacePath: string,
reportDir: string,
devServerUrl = 'http://localhost:5173',
options = { maxTurns: 15, maxTotalTokens: 200_000, timeoutMs: 300_000 },
): Promise<RunResult> {
const resources = new ResourceGuard(options);
const progress = new ProgressGuard();
const circuitBreaker = new AgentCircuitBreaker(workspacePath, reportDir);
const acMatrixPath = join(workspacePath, 'ac-matrix.json');
let currentPrompt = userPrompt;
let designRetries = 0;
const maxDesignRetries = 3;
while (true) {
// GUARD 1: resource ceilings
const resourceCheck = resources.checkShouldTerminate();
if (resourceCheck.terminate) {
circuitBreaker.handleAbort(resourceCheck.reason!);
}
resources.beginTurn();
const turnResult = await llmAgent.step(currentPrompt);
resources.recordUsage(turnResult.tokensUsed);
// GUARD 2: deadlock detection (only meaningful when a tool was called)
let toolOutput = '(no tool call this turn)';
if (turnResult.toolCall) {
if (progress.trackAndCheckStuck(turnResult.toolCall)) {
circuitBreaker.handleAbort('Repeating tool call detected (stuck agent)');
}
toolOutput = await executeTool(turnResult.toolCall);
}
// GUARD 3: deterministic convergence check
const verification = await evaluateConvergence(workspacePath, acMatrixPath, devServerUrl);
if (verification.gateFailed === 'design') {
designRetries += 1;
if (designRetries > maxDesignRetries) {
circuitBreaker.handleAbort(
`Design gate never converged after ${maxDesignRetries} refinement passes`,
);
}
}
if (verification.isConverged) {
return {
success: true,
turns: resources.getTurnCount(),
summary: 'Web app built, tested, and design-reviewed cleanly.',
};
}
currentPrompt = `Tool output:\n${toolOutput}\n\nConvergence status:\n${verification.statusMessage}`;
}
}
interface ConvergenceResult {
isConverged: boolean;
statusMessage: string;
gateFailed?: 'build' | 'runtime' | 'acs' | 'design';
}
async function evaluateConvergence(
workspacePath: string,
acMatrixPath: string,
devServerUrl: string,
): Promise<ConvergenceResult> {
// Gate 1: build and typecheck
try {
execSync('npx tsc --noEmit && npm run build', { cwd: workspacePath, stdio: 'pipe' });
} catch (err: unknown) {
const e = err as { stdout?: Buffer; stderr?: Buffer };
const output = [e.stdout?.toString(), e.stderr?.toString()].filter(Boolean).join('\n');
return {
isConverged: false,
gateFailed: 'build',
statusMessage: `Gate 1 failed (build/typecheck):\n${output || String(err)}`,
};
}
// Gate 2: dev server and runtime health
const e2eResult = await runHeadlessBrowserCheck(devServerUrl);
if (!e2eResult.noConsoleErrors) {
return {
isConverged: false,
gateFailed: 'runtime',
statusMessage: `Gate 2 failed (console errors): ${e2eResult.errors.join(', ')}`,
};
}
// Gate 3: acceptance criteria fully complete
let acMatrix: AcMatrix;
try {
acMatrix = JSON.parse(readFileSync(acMatrixPath, 'utf-8')) as AcMatrix;
} catch {
return {
isConverged: false,
gateFailed: 'acs',
statusMessage: 'Gate 3 incomplete: `ac-matrix.json` missing or unparseable.',
};
}
const pending = acMatrix.items.filter((ac) => ac.status !== 'DONE');
if (pending.length > 0) {
return {
isConverged: false,
gateFailed: 'acs',
statusMessage: `Gate 3 incomplete: ${pending.length} ACs remaining (${pending
.map((a) => a.id)
.join(', ')})`,
};
}
// Gate 4: design audit (soft gate — see retry cap in the caller)
const criticVerdict = await runDesignCriticAgent(e2eResult.screenshots);
if (criticVerdict.score < 8.5) {
return {
isConverged: false,
gateFailed: 'design',
statusMessage: `Gate 4 incomplete (design ${criticVerdict.score}/10): ${criticVerdict.feedback}`,
};
}
return { isConverged: true, statusMessage: 'All four convergence gates passed.' };
}
Beachten Sie, dass die „Tore“ nicht gleich sind. Die ersten beiden, der Compiler und der Browser, sind deterministisch, da keiner eine eigene Meinung hat. Die letzten beiden werden von Modellen beurteilt, was bedeutet, dass sie sich endlos hin und her bewegen können. Deshalb benötigt das Design-Tor seine eigene Obergrenze für Wiederholungsversuche. Ein subjektiver Schwellenwert ohne Begrenzung der Anzahl der Versuche ist im Grunde nur ein langsamer endloser Loop.
Ein wiederverwendbarer Master-Prompt
Um diesen Ablauf in Claude Code, Copilot CLI, Cursor oder Aider auszuführen, weist das untenstehende Template den Agenten an, seine eigene Akzeptanzmatrix zu erstellen, in Entwicklungszyklen zu iterieren, die Ergebnisse lokal zu überprüfen, die Qualität des Designs zu bewerten und erst dann aufzuhören, wenn die Anwendung fertig ist.
You are an autonomous lead software engineer, UX designer, and QA verifier. Your goal
is to build a production-quality web application locally, from scratch.
You must operate in a self-terminating agentic loop, running iteratively until the
application is complete, polished, functional, and verified.
================================================================================
1. TARGET APPLICATION SPECIFICATION
================================================================================
[DESCRIBE YOUR APP IDEA HERE — e.g. "A task management web app with local SQLite
persistence, a kanban board with drag-and-drop, priority tags, search/filter
controls, and a dark mode theme."]
================================================================================
2. EXECUTION PROTOCOL
================================================================================
PHASE 1 — DYNAMIC SPEC SYNTHESIS (TURN 1)
Before writing application code or installing dependencies:
1. Initialize the local project structure (e.g. Vite + React, Next.js, or Node).
2. Create `ac-matrix.json` in the workspace root defining explicit acceptance
criteria:
- Feature ACs: persistence, full CRUD, interactive components, error
handling, edge cases.
- Engineering ACs: strict TypeScript (`npx tsc --noEmit`), zero build
errors, zero linter warnings, dev server boots cleanly.
- Design ACs: visual hierarchy, responsive layout, dark/light toggle,
empty states, micro-interactions.
Format:
{
"project": "<app-name>",
"items": [
{ "id": "FEAT-1", "category": "feature", "description": "Local database persistence for tasks", "status": "PENDING" },
{ "id": "FEAT-2", "category": "feature", "description": "Drag-and-drop kanban re-ordering", "status": "PENDING" },
{ "id": "ENG-1", "category": "engineering", "description": "Clean TypeScript build, zero errors", "status": "PENDING" },
{ "id": "ENG-2", "category": "engineering", "description": "Dev server starts with 0 console errors","status": "PENDING" },
{ "id": "DSGN-1", "category": "design", "description": "Responsive UI with dark/light mode", "status": "PENDING" }
]
}
Once written, treat `ac-matrix.json` as frozen scope. Do not delete or weaken an
AC to make a gate pass. If an AC turns out to be genuinely infeasible, mark it
BLOCKED with a reason and surface it in the final summary.
PHASE 2 — AUTONOMOUS DEVELOPMENT LOOP
In each turn:
- Implement features, components, schemas, and routes incrementally.
- Run local validation after code changes (`npx tsc --noEmit`, `npm run build`).
- Update AC statuses (PENDING → IN_PROGRESS → DONE) as work is verified.
- Self-correction rule: if a command fails, read the exact error, fix the root
cause, and re-verify. Do not repeat the same failing command or edit more
than twice — change approach instead.
PHASE 3 — THE 4-GATE CONVERGENCE CHECK (MANDATORY)
Do not end execution or declare the project finished until all four gates pass
in the same turn:
Gate 1 — Compiler and build
`npx tsc --noEmit` and `npm run build` both exit 0 with zero errors.
Gate 2 — Dev server and runtime health
`npm run dev` boots cleanly with zero unhandled console or network errors.
Gate 3 — Acceptance criteria complete
Every item in `ac-matrix.json` has "status": "DONE".
Gate 4 — Design audit score >= 8.5/10
Audit visual hierarchy, color consistency, typography scale, spacing,
transitions, responsive behavior, and empty states. Score out of 10. If
below 8.5, refine and re-audit — but no more than 3 design passes total.
After 3 passes, stop and report the final score as-is.
================================================================================
3. FINAL COMPLETION OUTPUT
================================================================================
Only when all four gates pass, output:
- Final status: PROJECT COMPLETE & VERIFIED
- App summary and architecture overview
- Build and test commands executed, with exit codes
- Completed acceptance-criteria summary (including any BLOCKED items)
- Final design score (X/10) and UX highlights
- Instructions for running the app locally
Then stop.
Vorproduktions-Überprülliste
Bevor ein Agenten-Loop in Betrieb genommen wird, prüfen Sie ihn anhand folgender Kriterien:
- Zielerfüllungsprüfung durch einen deterministischen Test oder Compiler sowie eine externe Überprüfung des Ausgangskodes. Pflichtig.
- Wendezahlbegrenzung, eine feste Iterationsgrenze wie
currentTurn >= maxTurns. Pflichtig. - Token-Budget, eine kumulative Grenze wie
totalTokens >= tokenLimit. Pflichtig. - Zeitlimit, eine zeitbasierte Grenze wie
elapsed >= timeoutMs. Pflichtig. - Fortschrittsschutz, ein Detektor für duplizierte Tool-Signaturen mittels Schiebefenster-Prinzip. Empfohlen.
- Zustandsprüfung, ein Diff-Hash des Arbeitsbereichs, der keine Veränderungen erkennt. Empfohlen.
- Sichere Rückfalllösung: Ein Circuit Breaker mit Rollback über
git stashim Falle einer Abbruchaktion. Pflicht.
Grenzen statt Intelligenz
Zuverlässige autonome Agenten entstehen weniger dadurch, dass das Modell intelligenter wird, sondern vielmehr durch die Festlegung klarer betrieblicher Grenzen. Vier Faktoren machen den Unterschied zwischen einem fragilen Experiment und einem System, das man das ganze Wochenende über laufen lassen kann: Verifier, die nicht vom Modell abhängen, strenge Ressourcenlimits, Fortschrittsverfolgung mittels Hashing sowie Rollbacks, von denen man sich erholen kann.