Zatrzymanie pętli uciekającego agenta AI: cztery zasady bezpieczeństwa dla środowiska produkcyjnego
Zmienia się kap, tokeny i czas, cele są weryfikowane za pomocą rzeczywistych testów, narzędzia do hashowania służą do wykrywania pętli typu ping-pong, a w przypadku utknięcia autonomicznego agenta następuje bezpieczne cofnięcie.
Źródła: Don’t Let Your AI Agents Loop Forever: An Engineering Guide to Termination Criteria
Rachunek za API w wysokości 3800 dolarów za autonomicznego agenta, który przez dwanaście godzin utknął w pętli korygowania składni, to rytuał inicjacyjny, którego nikt nie powinien pragnąć. Oto jak ustawić reguły zatrzymywania o poziomie produkcyjnym dla systemu opartego na agentach.
Sytuacja w piątkowe popołudnie
Jest piątek, 17:00. Nowy agent do kodowania trafia do środowiska testowego. Jego zadaniem jest sprawdzanie pull requestów, naprawianie błędnych typów w TypeScript oraz ponowne uruchamianie testów. Polecenie wygląda dobrze, laptop się zamyka i zaczyna weekend.
Do sobotniego ranka telefony zalewają powiadomienia z PagerDuty i systemu fakturowania. Agent naprawił plik user.service.ts z powodu błędu typu, przez co uszkodził plik auth.service.ts. Naprawa tego drugiego pliku ponownie uszkodziła pierwszy, a oba pliki ciągle się wzajemnie unieważniały. Po czternastu stuzach wywołań narzędzi rachunek za tokeny wynosi 3800 dolarów, a zestaw testów jest uszkodzony.
Najwięcej uwagi inżynierów skupia się na promptach, wyborze narzędzi oraz grafach orkiestracji. W środowisku produkcyjnym decyzja o największych konsekwencjach dotyczy sposobu zatrzymania się agenta, a nie sposobu jego uruchomienia.
Czym jest pętla agentowa
Pętla agentowa to cykl sterowania, w którym LLM percepcjonuje stan, wybiera działanie, wywołuje narzędzie, odczytuje wynik i decyduje, czy kontynuować.
┌──────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────┘
Jeśli system zarządzający nim nie zawiera szczegółowych, wielowarstwowych zasad zakończenia, agent ostatecznie trafia do jednego z trzech trybów awarii:
- Nieskończone wykonywanie – ciągłe powtarzanie fałszywych lub powtórzonych wywołań narzędzi.
- Wcześniejsze zakończenie – zakończenie pracy i ogłoszenie zwycięstwa, mimo że praca jest nieukończona lub po cichu się nie udaje.
- Wyczerpanie budżetu – wykorzystanie całego przydziału tokenów, zanim zadanie wieloetapowe zostanie ukończone.
Cztery rodzaje kryteriów zatrzymania obejmują te przypadki, a każdy agent potrzebuje wszystkich czterech, zanim zostanie wdrożony.
1. Kryteria sukcesu: weryfikacja celu w sposób deterministyczny
Pułapka. Nigdy nie akceptuj własnego werdyktu agenta, pytając model językowy, czy ten zakończył pracę. Modele mają tendencję do przesady w ocenie sukcesu i pochlebstwa, często zgłaszając sukces, mimo że budowa jest uszkodzona lub plik wyjściowy jest pusty.
Rozwiązanie. Traktuj wynik modelu jako nieweryfikowaną hipotezę i spraw, aby pętla porównywała rzeczywisty stan z czymś deterministycznym:
- uruchom
npm testlubpytestw procesie potomnym i odczytaj kod wyjścia - przeprowadź sprawdzanie typów i analizę kodu z użyciem ścisłych kodów wyjścia, np.
tsc --noEmit - przeprocesuj wynik za pomocą schematów Zod lub Pydantic
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.' };
}
}
Zasada jest prosta. Jeśli człowiek sprawdzający wykonałby polecenie w celu weryfikacji pull requesta, to samo polecenie powinno stanowić warunek zakończenia pętli.
2. Limity zasobów i budżetu: sztywne granice
Pułapka. Gdy agent napotyka przypadek krawędziowy lub nieoczekiwany błąd narzędzia, jego domyślnym działaniem jest ponowna próba z niewielkimi modyfikacjami promptu. Bez ograniczeń na poziomie silnika, jedna utknięta pętla może zużyć miliony tokenów w ciągu zaledwie kilku minut.
Rozwiązanie. Wprowadź sztywne granice w trzech niezależnych wymiarach: liczbie rund, tokenach oraz czasie upływającym według zegara. Licz rundy i tokeny oddzielnie, ponieważ jedna runda może obejmować kilka wywołań modelu, a ich mieszanie potajemnie zwiększa liczbę rund.
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 };
}
}
Limity są środkiem zapobiegawczym, a nie elementem projektu. Jeśli twój agent regularnie kończy pracę z powodu osiągnięcia wartości maxTurns, problemem nie są te limity, lecz sposób podziału zadań.
3. Zabezpieczenia postępu: wykrywanie agentów utknętych lub zbaczających z kursu
Wciąż pojawiają się trzy określone wzorce:
- Ping-pong. Agent używa Narzędzia A, napotyka błąd X, używa Narzędzia B, by to naprawić, napotyka błąd Y, a następnie ponownie używa Narzędzia A z identycznymi danymi wejściowymi.
- Thrashing. Edycja pliku A uszkadza plik B, a edycja pliku B uszkadza plik A. Przestrzeń robocza ciągle się zmienia, podczas gdy całkowity postęp pozostaje zerowy.
- Semantyczne zboczenie. Agent napotyka nieoczekiwane ostrzeżenie lintera, spędza pięć rund na kwestiach stylu i zapomina o pierwotnym celu.
Aby wykryć sytuacje deadlock przed wyczerpaniem limitu maxTurns, przechowuj okno przesuwane z haszami wywołań narzędzi wraz z haszami różnic w przestrzeni roboczej. Powtarzające się identyczne hasze oznaczają, że agent krąży w kółko.
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;
}
}
Nie każde powtórzenie zasługuje na takie samo podejrzenie. Wywołanie odczytu wykonywane trzy razy zazwyczaj jest nieszkodliwe, natomiast operacja zapisu powtórzona trzy razy prawie nigdy nie jest. Gdy dostępne są metadane narzędzia, należy dostosować próg, zamiast traktować wszystkie wywołania jednakowo.
4. Ludzka interwencja i bezpieczne cofnięcie
Gdy pętla kończy się z powodu limitu budżetu lub martwego punktu, najgorszym skutkiem jest częściowo uszkodzony system. Bezpiecznik powinien wykonać trzy kroki w kolejności: zebrać informacje diagnostyczne (które narzędzie zawiodło i jakie były ostatnie błędy), przywrócić przestrzeń roboczą do znanego stanu oraz przekazać sprawę osobie z kompletnym podsumowaniem.
Bądź ostrożny podczas drugiego kroku. Wykonanie polecenia git reset --hard a następnie git clean -fd nie da się odwrócić. git stash --include-untracked jest bezpieczniejsze, ponieważ zachowuje prace agenta w celu dalszej inspekcji i można je później usunąć. Zachowaj silne sformatowanie tylko dla tymczasowych środowisk testowych.
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;
}
}
Przypadek studialny: generator aplikacji o otwartym zakończeniu
Poprawka błędu ma do sprawdzenia określony zestaw testów. Trudniejszym przypadkiem jest subiektywny, otwarty opis zadania, na przykład: stwórz lokalnie nowoczesną aplikację internetową do zarządzania zadaniami, która będzie działać samodzielnie aż do zakończenia pracy, będzie dopracowana i w pełni funkcjonalna, a Ty sam zdecydujesz, kiedy przestać ją używać.
Słowa takie jak „dopracowana” i „zakończona” są niebezpiecznie niejednoznaczne. Jeśli zostawić to bez interwencji, agent albo zatrzyma się po stworzeniu prostego pliku index.html, albo będzie wiecznie poprawiał marginesy w CSS.
┌──────────────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────────────────────────┘
Syntetyzacja specyfikacji najpierw
W pierwszej turze, zanim zostanie napisany jakikolwiek kod, agent tworzy wyraźną macierz kryteriów akceptacji i zapisuje ją jako ac-matrix.json. Kryteria te dzieli na trzy kategorie:
- Kryteria funkcjonalne, takie jak przechowywanie danych w lokalnym SQLite lub JSON, operacje CRUD oraz funkcja przełączania między trybem jasnym a ciemnym.
- Kryteria inżynieryjne, takie jak brak ostrzeżeń typu TypeScript i lint oraz serwer deweloperski, który uruchamia się bez błędów w konsoli.
- Kryteria projektowe, takie jak responsywny układ, spójne elementy projektowe, stan pusty oraz proste interakcje.
To samo modele, które tworzy macierz, będzie później oceniać własną pracę według niej, więc należy je traktować jako umowę dotyczącą zakresu prac, a nie jako obiektywny standard. Ich prawdziwa wartość polega na tym, że są ustalane wcześnie, zanim agent ma jakikolwiek powód, by zmieniać cele.
Rozdzielenie ról
Aby uniemożliwić jednemu agentowi ocenianie własnej pracy jako doskonałej, należy rozdzielić zadania:
- Podagent kodujący tworzy funkcje, routery oraz stylizację.
- Weryfikator przeglądarki bez interfejsu uruchamia serwer deweloperski, robi zrzuty ekranu za pomocą Playwright i sprawdza błędy w konsoli.
- Podagent krytyka projektu analizuje interfejs użytkownika pod kątem standardów systemu projektowego dotyczących hierarchii, odstępów i kontrastu.
Infrastruktura
Kod poniżej zakłada użycie zewnętrznych zależności (llmAgent, executeTool, runHeadlessBrowserCheck i runDesignCriticAgent), które należy zastąpić własną warstwą orkiestracji.
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.' };
}
Zauważ, że bramy nie są równe. Pierwsze dwa – kompilator i przeglądarka – są deterministyczne, ponieważ żaden z nich nie ma własnych opinii. Ostatnie dwa są oceniane przez modele, co oznacza, że mogą zmieniać decyzje w nieskończoność. Dlatego brama projektowa wymaga własnego ograniczenia liczby prób. Subiektywny próg bez ograniczeń liczby prób to po prostu wolniejszy pętla nieskończona.
Używany prompt główny
Aby uruchomić ten proces w Claude Code, Copilot CLI, Cursor lub Aider, poniższy szablon instruuje agenta do napisania własnej macierzy akceptacji, iterowania w cyklach rozwojowych, sprawdzania wyników lokalnie, oceny jakości projektu oraz zatrzymania się dopiero po ukończeniu aplikacji.
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.
Lista kontrolna przed produkcją
Zanim pętla agenta zostanie uruchomiona, sprawdź ją pod kątem następujących kryteriów:
- Weryfikacja celu za pomocą testu deterministycznego lub kompilatora oraz sprawdzenia kodu wyjścia z zewnątrz. Obowiązkowe.
- Limit tur – stały limit iteracji, np.
currentTurn >= maxTurns. Obowiązkowe. - Budżet tokenów – kumulatywny limit, np.
totalTokens >= tokenLimit. Obowiązkowe. - Czas timeout – limit czasowy, np.
elapsed >= timeoutMs. Obowiązkowe. - Zaawansowany mechanizm ochrony – detektor duplikatów podpisów narzędzi w formie przesuwającego się okna. Zalecane.
- Zaawansowany mechanizm monitorowania stanu – hash różnic w przestrzeni roboczej, który wykrywa brak zmian. Zalecane.
- Bezpieczny plan awaryjny – mechanizm ochronny z możliwością cofnięcia działań za pomocą
git stashw przypadku przerwania. Obowiązkowy.
Granice zamiast inteligencji
Niezawodne autonomiczne agenty powstają raczej dzięki ustalaniu ścisłych ograniczeń operacyjnych niż poprzez czynienie modelu inteligentniejszym. Cztery elementy decydują o różnicy pomiędzy kruchym eksperymentem a systemem, który można uruchomić przez cały weekend: weryfikatory niezależne od modelu, sztywne limity zasobów, haszowanie postępów oraz możliwość cofnięcia działań, dzięki którym można się odbudować.