Главная / Статьи

Прекращение циклов ускользающих ИИ-агентов: четыре механизма защиты для производственной среды.

Контроль за поведением агента, токенами и временем, проверка целей с помощью реальных тестов, использование инструментов хеширования для выявления циклов типа «пинг-понг», а также безопасное возвратное действие в случае застревания автономного агента.

Издатель: REACTAPP.TOP14 мин чтения3289 слов

Источники: 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 долларов, а набор тестов был поврежден.

Большая часть внимания инженеров уделяется подсказкам, выбору инструментов и графам оркестрации. В реальных условиях наиболее значимым решением является то, как агент останавливается, а не то, как он запускается.

Что такое цикл агента

Цикл агента — это контрольный цикл, в котором большая языковая модель воспринимает состояние, выбирает действие, вызывает инструмент, считывает результат и решает, следует ли продолжать.

                   ┌──────────────────────────────────────┐
                   │        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. Критерии успеха: детерминированная проверка достижения цели

Опасность. Никогда не принимайте вердикт самого агента, задавая большой языковой модели вопрос о завершении работы. Модели склонны к предвзятости в пользу завершения задания и лести, и часто сообщают о успехе, когда результат некорректен или файл с результатом пуст.

Решение. Рассматривайте результаты работы модели как непроверенные гипотезы, и пусть цикл сравнивает фактическое состояние с чем-то детерминированным:

  • запустите 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.' };
  }
}

Принцип прост: если человек-рецензент использовал бы определённую команду для проверки pull request, эта же команда должна стать условием завершения цикла.

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. Механизмы контроля прогресса: выявление застрявших и сбившихся с курса агентов

Постоянно встречаются три типичных паттерна:

  • Пинг-понг. Агент использует инструмент A, сталкивается с ошибкой X, пытается её исправить с помощью инструмента B, сталкивается с ошибкой Y, затем снова использует инструмент A с теми же параметрами.
  • Беспорядочные действия. Изменение файла A портит файл B, а изменение файла B портит файл A. Рабочее пространство постоянно меняется, в то время как общий прогресс остаётся нулевым.
  • Семантическое смещение. Агент сталкивается с неожиданным предупреждением проверщика кода, тратит пять ходов на форматирование и забывает первоначальную цель.

Чтобы обнаружить застревание до исчерпания количества оборотов 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 и инструментах lint, а также запуск сервера разработки без ошибок в консоли.
  • Дизайнерские критерии, такие как адаптивная верстка, единые дизайн-токены, состояния пустоты и простые интеракции.

Тот же модель, который формирует матрицу, позже будет оценивать свою работу по этой матрице, поэтому её следует рассматривать как договор о границах задачи, а не как объективный стандарт. Её истинная ценность заключается в том, что она фиксируется на раннем этапе, до того, как у агента появятся причины изменять цели.

Разделение ролей

Чтобы предотвратить ситуацию, когда агент оценивает собственную работу как идеальную, необходимо разделить задачи:

  • Подагент-кодер создаёт функции, маршрутизацию и стилизацию.
  • Проверщик с бесголовым браузером запускает сервер разработки, делает скриншоты с помощью 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.

Чек-лист до начала производства

Перед тем, как запустить цикл агента, проверьте его согласно этим критериям:

  1. Проверка цели с помощью детерминистичного теста или компилятора, а также проверки внешнего кода выхода. Обязательно.
  2. Лимит ходов — фиксированный предел итераций, например currentTurn >= maxTurns. Обязательно.
  3. Бюджет токенов — суммарный лимит, например totalTokens >= tokenLimit. Обязательно.
  4. Таймаут — лимит во времени, например elapsed >= timeoutMs. Обязательно.
  5. Защита прогресса — детектор дублирующихся подписей инструментов с использованием окна скроллинга. Рекомендуется.
  6. Защита состояния — хеш различий рабочего пространства, выявляющий отсутствие изменений. Рекомендуется.
  7. Надежный запасной вариант — механизм отключения с возможностью возврата к предыдущему состоянию с помощью git stash при прерывании работы. Обязательно.

Границы важнее интеллекта

Надежные автономные агенты формируются скорее за счет установления четких операционных ограничений, чем за счет сделания модели более умной. Четыре фактора определяют разницу между нестабильным экспериментом и системой, которую можно запустить на весь уик-энд: проверки, не зависящие от модели, строгие лимиты на ресурсы, хеширование прогресса и возможность возврата к предыдущему состоянию.