Спыняем бясконцыя цыклы AI-агентаў: чатыры абмежаванні для прадакшну
Абмяжоўвайце крокі, токены і час, правярайце мэты рэальнымі тэстамі, хэшуйце выклікі інструментаў, каб лавіць пінг-понг, і бяспечна адкочвайцеся, калі аўтаномны агент затрымаўся.
Крыніцы: Don’t Let Your AI Agents Loop Forever: An Engineering Guide to Termination Criteria
Рахунак за API на суму 3 800 долера, які з’явіліся пасля таго, як автонамны агент праспяў дванаццаць гадзін у цыкле выправлень сынтаксісу, — гэта такі «рытуал пераходу», якога ніхто не хацеў бы. Цікава, як можна стварыць правіла зупнення працы для сістэмы з агентамі на рэвэльнацыйным рэвэле.
Сцэнарый пятнічнага вечара
Пятніца, 17:00. Новы агент для кодавання запускаецца ў тэстовую срэду. Яго задача — пераглядаць запросы прыўязкі коду, вылечваць проблемы з тыпамі у TypeScript і паўтараць тэсты. Усе выглядае нормальна, ноутбук закрываецца, і пачынаеся вякенд.
До ранку субатку тэлефон засыпаецца спавяшчымі паведамленнямі з PagerDuty і сістэмы білінгу. Агент вылечыў файл user.service.ts ад памылкі з тыпамі, але тым самым зламаў файл auth.service.ts. Вылечванне гэтага файла знову зламало першы, і гэтыя два файла продавжалі перакрываць адзін другога. Пасля чатырнаццаці згулкоў інструментаў сума за токены склалася 3 800 долераў, а набор тэстаў быў зіпсаваны.
Большая частка увагіі ў інжынерыі прыкладзена да запытоў, выбору інструментаў і графаў кіравання. У працэсе вырабаткі рашэнне з найбольшымі наследкамі — гэта тое, як агент зупіняецца, а не тое, як ён пачынае.
Што такое агентны цыкл
Агентны цыкл — цэла кіравання, пры якой LLM спрыягае стан, выбірае дзеянне, вызывае інструмент, чытае выходны рэзультат і рашыць, чы робіць працу далей.
┌──────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────┘
Калі канцэлера, які выконваецца навакола яго, не мае шаравых, чыстарактарызаваных правіл завершэння, агент у канцыкуцея падае ў адну з трох кантэкстаў неудачы:
- Бесканцэнтная выконванне — цыклізацыя на галюцынацыйных або павтаральных вызовах інструментаў.
- Раннее завершэнне — завершэнне працы і прызнанне перамогі, калі праца застаецца незавершанай або таямна не выконваецца.
- Выкарыстоўванне всіх рэсурсаў — выкарыстоўванне всіх дазволеных токэнаў пры тым, калі багатоэтапная задача яшчэ не завершылася.
Чатыры відэкі крэтэрыяў зупінкі пакрываюць гэтыя кантэксты, і кожны агент патрэбуе ўсіх чатырох, прытымкі яго выпуску.
1. Крэтэрыяў успеху: даказваць цялесапраўднасць меты
Апасцярожнасць. Ніколі не прымайце сабственнага вердыкута агента, запытваючы LLM, чы ён завершыў працу. Мадэлі схіляюцца да уперадзеўлення завершэння і лесткі, і часта адзвярцаюць пра успех, калі структура ўражаная або файл выходных дадзеных порожні.
Рашэнні. Спрыткайце выход модэлю як непарабяраную гіпатэзу, і нехай цыкл пераканаецца ў рэальны стан па якомусь дэтэрміністычным крытэрыю:
- запускайце
npm testабоpytestу дзецячым процэсе і чытайце код выхіду - запускайце перакананне тыпаў і аналіз коду з строгімі кодамі выхіду, напрыклад
tsc --noEmit - парсайце выход праз шыматы Zod або 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.' };
}
}
Правілле простае. Калі людзі-рэвізоры бы выканалі каманду для перагляду запрошэння прыўязкі, тая ж каманда павінна стаць умовай выходу з ціклу.
2. Ліміты рэсурсоў і бюджэту: жорсткія практычныя межы
Проблема. Калі агент сталкнуецца з экстрэмальным кейсам або нечаканай памылкай інструмента, яго стандартны падход — спробаваць зноў з незначнымі змянамі запітаў. Без лімітаў на рэверсе можаць знадобіцца мільйоны токенаў, каб выйсці з застраглага ціклу, і гэта за калікве хвілін.
Рашэнне. Неабходна встановіць жорсткія практычныя межы па трох незалежных параметрах: колькасці паворотаў, токенаў і часу, які прыходзіцься чакаць. Пісакуйце колькасць паворотаў і токенаў окрэма, адтымушто адзін паворот можа включаць калькі модэльных запытоў, і сумешчанне іх таямніча падвайнае лічбу паворотаў.
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 };
}
}
Ліміты ўжоўнаюцца як апатэнцыя, а не як элемент дызайну. Калі ваш агент рэгулярна зупіняецца таму, што досягае значэння maxTurns, проблема не ў лімітах, а ў спосабе распаду задання.
3. Засобы контролю прагрэсу: выяўленне агентаў, якія застралі
Тры патэрны паўтараюцца знова і знова:
- Ping-pong. Агент выкананаеін А, сталакаецца на Адказ X, выкананаеін Б, каб тое пасправіць, сталакаецца на Адказ Y, пасля чаго зноў выкананаеін А з тымі ж данымі.
- Thrashing. Рэдагаванне файла А пашкаджае файл B, а рэдагаванне файла B — файл А. Рабочая прастора постаўляецца змянюваным, хоць аб’ектыўны прагрэс застаецца нулявым.
- Semantic drift. Агент сталакаецца на нечаканай павестці лінтара, пасвячае пяць раундоў на стыль і забывае пачатковую мэту.
Ёнкэлі застаць сітуацыю з зацісненнем перад тым, як будзе выкарыстоўвацца весь maxTurns, трэба зберагаць «скользячыя» вікна з хэшамі вызоў інструментаў разам з хэшамі разніцы ў рабочай прасторы. Павтарэнне ідэнтычных хэшаў означае, што агент ціркулюе ў кале.
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;
}
}
Не кожна паўтарэнне заслуговае на аднаковую падозру. Выклік з рэжымам толькі чытання, які адбываецца тры разы, зазвычай нешкодны, тады як паўтарэнне запісу тры разы практычна ніколі не ёсць такім. Калі ёсць метаданы інструмента, неабходна скорэктаваць прагу, ўзгодзіўшы ўсё з рэальным станом, а не ставіцца да всіх выклікаў аднакава.
4. Перадача задачы чалавеку і безпечнае відкатванне
Калі цыкл завершаецца праз досягнення ліміту бюджэту або застой, найгоршым наследкам ёсць часткова зламаная сістэма. Спецыяльны механізм захісту павінен адбыць тры дзеянні: зафіксаваць дыягнастычную інфармацыю (які інструмент не працаваў і якія былі апошнія памылкі), вярнуць робочую суперфіс у вядомы стан і перадаць задачу чалавеку з чыстым підсумкам сітуацыі.
Будзьце абярожны ў другім кроку. Выкананне git reset --hard заўсёды заўершаецца git clean -fd, і гэтага нельга вярнуць. git stash --include-untracked ўбачлівей, калі ўжо ўсё, таму што ён захоўвае працу агента для перагляду, а пазней яе仍 можна адмовіцца. Вярны рэшэт для врэменных сэндбоксаў.
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;
}
}
Аналіз прыклада: генератор аплікацыяў з адкрытымі можлівасцямі
Для выправлення бага існуе стаўкі тэстаў, па якімі яго можна пераканацца. Ёшчы сложнейшы ў выпадку — адкрыты, суб’ектывны запіт, напрыклад: стварыць саврэменную веб-аплікацыю локальна, якая керуе задачамі, працавае самастойна пакуль не завершыць працу, будзе аптаймованаі і абоўсюды функцыональная, і сам выбраце, калі яна гатова зупініцца.
Такія словы, як «аптаймованаі» і «завершыць», ўскладнююць задачу. Калі неяк кераваць, агент альбо зупініцца пасля стварэння простага index.html, альбо будзе вечна правяць маржы 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 │
└──────────────────────────────────────────────────────────┘
Спачатку сінтэзуецца спецыфікацыя
У першы тыраж, прытаму як толькі пачынаецца напісанне коду, агент стварае матрыцу крытэрыяў апрацоўкі і зберагае яе як ac-matrix.json. У ёй крытэрыі раздзеляюцца на тры катэгорыі:
- Крытэрыі функцыянальнасці, такія як падтрымка локальнага SQLite або JSON, операцыі CRUD і можлівасць пераключэння у тэмны рэжым.
- Інжынерныя крытэрыі, такія як відсутнасць паведамленняў пра проблемы у TypeScript і лінт-інструментах, а таксама сервер разработкі, які запускаецца без якіх-лібо памылак у консолі.
- Дызайнавыя крытэрыі, такія як адпаведны лейаут, адносныя елементы дызайну, станы «празь» і простыя взаімадзеі.
Той жа модэль, які стварае матрыцу, пазней будзе самае сябе оцэніваць за ёю, таму яе трэба спрыятаць як даговор пра межы працы, а не як об’ектыўны стандарт. Яе справжняя цэннаць заключаецца ў тым, што ёй даёцца фіксаваны статус рана, перш чым у агента з’яўляюцца прычыны змяніць цілі.
Раздзяленне ролей
Ёнколі агент хочаць оцаніць сваю працу як высокай якосці, трэба разлучыць ція заведзенні:
- Падагент-кодаваць стварае функцыі, маршрутызацыю і стыль.
- Пераканальнік-верыфікатор запускае сервер распрацоўшчыка, дэлае скрыншоты за дапамою Playwright і пераканальвае на наявнасць памылак у консолі.
- Падагент-крытык дызайну аналізуе інтэрфейс за стандартамі дызайн-сістэмы ў чысці іерархіі, адступоў і контрасту.
Інструментарый
Код ніжэй прыпускае наявнасць ін’ектаваных залежнасцей (llmAgent, executeTool, runHeadlessBrowserCheck і runDesignCriticAgent), якія можна заменіць на ваш сае роўэнь оркестрацыі.
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.' };
}
Заўважыце, што «вораты» не ўсе роўныя. Першыя два — кампайляр і браузер — ёсць дэтэрміністычнымі, адколькі ніхто з іх не мае суб’ектываў падходу. Апошнія два ацэнююцца за дапамогою модэляў, што значыць, яны можуць постаўляць разныя рашэнні без канца. Самэ гэтая прычына вымагае, каб «ворат» дызайну меў свой саём ліміт на праказы. Суб’ектываў порог без ліміту праказаў — гэта проста спадзейшы бесканечны цыкл.
Маста-прампт, які можна вярнуць у паўтарэнні
Каб запустыць гэты працэс у Claude Code, Copilot CLI, Cursor чы Aider, наведаны ніжэй шаблон паведамляе агента пра неабходнасць стварыць саёю матрыцу адзначэння, ведаць ітэрацыі ў цыклах разработкі, пераканацца ў рэзультатах локальна, пераглянуць якасць дызайну і зупініцца толькі пасля таго, як прылад будзе завершаны.
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.
Спісак перакантролькі для прадзеявлення
Перад тым, як агентны цыкл запусціцца, перакантрольце яго па гэтыяя пункты:
- Перакантролька меты па адказальным тэсце або кампайлярэ і перакантролька зовнішньага коду выходу. Бяспекавае.
- Ліміт тураў — фіксаваны ліміт ітэрацый, напрыклад
currentTurn >= maxTurns. Бяспекавае. - Бюджэт токенаў — кумуляўны ліміт, напрыклад
totalTokens >= tokenLimit. Бяспекавае. - Таймаут — ліміт па часу, напрыклад
elapsed >= timeoutMs. Бяспекавае. - Захаванне прагрэсу — дэтэктар дуплікатных падпісаў інструментаў. Рэкамендаванае.
- Захаванне стану — хеш разлікі рабочага прастору, які выяўляе абсэнцыю змян. Рэкамендаванае.
- Безпечны ўзаемны выключальнік — цэлкам автонамны механізм з можлівасцю адкатування за дапамою
git stashу разы прызупінення. Бяспека ўжо ў самай сутнасі.
Межы працэйнасці працоўнае інтэлігэнцыі
Надзеяныя автонамныя агенты ствараюцца не столькі за счытком удосконалення модэлю, сколькі за счытком правільнага задання тэхнічных меж яго працы. Чатыры факторы вядуць да розліку межу хвороблівага эксперымента і сістэмы, якую можна заліць у працы на весь вякенд: верыфікаторы, якія не залежаць ад модэлю, жорсткія ліміты рэсурсаў, хэшаванне працы і можлівасць адкатування, якое дазволяе восстанавіць стан сістэмы.