Галоўная / Артыкулы / Практычныя прытамулкі: Не дазвольце вашым AI-агентам ціклізаваць без канца: Інжынерныя нарады

Практычныя прытамулкі: Не дазвольце вашым AI-агентам ціклізаваць без канца: Інжынерныя нарады

Практычныя прыказкі: Как не дазволіць вашым AI-агентам ціклізавацца назаўсёды: інжынерныя нарады па кантрактах, перакрыцчах і слотах для коду для команд, якія викорыстоўваюць гэты патерн.

4049 слоў

Існавайце гэты документ як перапрацоўаную версію ідэй з кніги «Не дазвольце вашым AI-агентам ціклічна працаваць назаўсёды: інжынерныя кансэпты критэрыяў завершэння» для аперацыйных працавнікаў: чыстыя этапы, арганізаваныя блакі коду і прыметкі па вяснаванню, якія застаюцца пасля перадачы задання. Этап агульнага аналізу работае найэфектывней, калі яго розглядаць як мерыемую плошчу. Запісаўце адна ідеальная транскрыпцыя, адзін прыклад неудачы і прыметкі па атрыбуцыі стану да перадачы задання, прычаму расшырюючы сферу дзеяння. Дакументаваць трэба як успішны, так і няуспешны шляхы вядзення задання. Перапрыбуткі, людскія контрольны пункты і обработка некоректных поведаньняў є часткай продукту, а не пасляднім дапрацоўкам.

Кошмар па пятнічныя апоўначы

Для ўрагу «Пяцьдзённы вечар» неабходна пазначыць даныя, адпаведальнага за крок і критэрыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не прабуючы спадароўваць схованы статус. Лепш выбіраць маленькія, тэставаныя елементы замест амаль неканчатых скрыптав. Калі крок не выйшоў, прычына нехацкага рэзультата должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцужок задач. Неабходна людская апраўда для тых крокаў, якія выкарыстоўваюць грошы чы зміняюць даныя у працэсе. Компіляцыйныя налашчэнні не ўзроўнаўцяюцься з пачатковым станом бізнес-процэсаў.

Структура агентнага цыклу

Для аналізу стадіі з виканнем задач пры змяне коду неабходна прадзефінаванне вхідных дадзеных, адпаведнага адпаведальніка за шаг і крэтарыяў завершэння. Аперацыйныя працавнікі павінны магчымае перзапускаць шаг з вядомай точкі контролю, не падозрываючы пра схованы стан. Спрыяць гэтай стадіі як кантракту межа вхіднымі дадзенымі і перакананымі выходнымі рэзультатамі. Даць назвы артыфактам, прадзефінаваць крэтарыяў успеху і не прабаваць прыймаць часткова завершаны рэзультаты без падтверджэння. Заставіць людзкую апраўдку для тых крокаў, якія выкарыстоўваюць грошы або зміняюць даны для працы системы. Компіляцыйныя налашчэння не ўзроўнаўцуюцца з павнай завершанасцю бізнес-процэсу.

                   ┌──────────────────────────────────────┐
                   │        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. Крэтарыяў успеху: Апраўдка дэтэрміністычных цялей

Для стадіі «1 Крэтэрыя успеху: Дэтэрміністычны» неабяжна практычна адзначыць вхідныя данні, адпаведальную особу за выкананне крока і крэтэрыя завершэння пры перадзеіснаванні коду. Аператары должны магчымаць перапрыявленне крока з вядомай точкі контролю без неабяжнага вычыслення захаваных станоў. Неабяжна фіксаваць час выканання, а таксама кост токенаў чы супылакоў праза рэзультаты функцыйнасці. Відразліва візуабільнасць костаў запобегае неспакойным рахункам, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя среды. Неабяжна атрымваць пашчатковае затверджэння чалавека для тых крокаў, якія выкарыстоўваюць грошы чы зміняюць данні ў працоўным серавысе. Прыяеднанне элементаў у час компілявання не абавесць цэлыснасцю бізнес-процэса.

Абман: самаацэнка

У стадії самацэнкі рызыка неабяжна практычна ваказаць параметры, адпавядаючага за крок адпаведальнага, і крэтарыя для завершэння працы перад змінайом коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Конфігурацыю трэба залічыць праза кодам прыкладнення. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць, не чытаяўшы весь ланцуг задач. Пры выконанні операцый, якія коштуюць грошей або зменяюць даны варабочага сераўіса, неабяжна ўключыць людзкую апраўдку. Прыєднанне элементаў у час компіляцыі не ўзначае повнайсткавасці бізнес-процэса.

Рашэння: зовнішнія програмныя верыфікаторы

Для стадзіі зовнішняя програмна рэшэнкі неабходна ўзначыць вхідныя данні, адпаведальную особу за кожны крок і критэрыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы схованы стан. Неабходна адначасовая документацыя як успішнага, так і патэнтнага шляха. Перапрыбуткі, людзкія перакрыцці та обробка некоректных паведамленняў є часткай продукту, а не яго пазнейшага дапрацоўкі. Неабходна людзкая аправарэнне для тых крокаў, якія выкарыстоўваюць грошы або зміняюць данні ў працэсе вырабоцтва. Працэс складання коду не ўзначае повнайшага адпрацоўкі бізнес-функцый.

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. Ліміты рысаў і бюджэту: жорсткія межы дварчыка

У стадії 2 «Рычаслы і бюджэт» неабяцо паказаць вхідныя даны, адпаведальнага за крок і критэрыі завершэння пры перадзеяванні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшоў, прычына неудачы павінна вказываць на адзіну адпаведальнасць, а не на заплутаны процес. Неабяцо працаваць з адзмененнямю людзькім у тых моментах, калі выдаваюцца грошы або зменяюцыся даны для працы. Компіляцыйныя налашчэння не ўзначаюць павнае выпанаванне бізнес-процэса.

Падступ: безмежныя перазапускі

Для стадіі неактуальных безлімітных спроб неабяжна прадзефінавацыя вхідных дадзеных, адпраўніка крока і крэтарыяў завершэння пры перамены коду. Аперацыйныя працавнікі павінны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Спрыяйце гэтай стадіі як дагавору межа вхіднымі і перакананымі выходнымі дадзенымі. Даўце назвы артыфактам, прадзефінавацыя перакананняў успеху і адмовіцеся ад тыхнай частковай рэалізацыі без паведамлення. Забяжце людзкую апраўду для ситуацыяй, калі выкананы расходы або змененыя данні ў працэсе. Компіляцыйныя налашчанні не ўзроўнаўцуюцца з павнай рэалізацыяю бізнес-праблем.

Рашэнне: багатавымярныя ліміты

Для стадіі багатавымiarных лімітов рашэння неабяжна прадзефінаваць вхідныя даны, адпраўніка крока і крэтыяры выходу пры перадзеўці коду. Аператары должны магчыма было перзапускаць крок з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Запісвайце час выконання і кост токенаў або запытаў разам з функцыйнальнымі рэзултатамі. Відразы костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спаканую. Заставіце людзкую апраўдку для тых крокаў, якія витрачаюць грошы або зменяюць даны ў працэсе. Компіляцыйныя налашчанні не ўзроўнаўваюцца з повнай адпрацоўкай бізнес-процэсаў. Для стадіі багатавымiarных лімітов рашэння неабяжна прадзефінаваць вхідныя даны, адпраўніка крока і крэтыяры выходу пры перадзеўці коду. Аператары должны магчыма было перзапускаць крок з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Дакументавайце як «шчаслівы» шлях, так і шлях вяснавання проблем. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных запытаў є часткай працэсу вырабніцтва.

ct, не пазнейш часоў польскі.

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 };
  }
}

3. Абараннікі працы: выяўленне застрэйвання і зсуву

Калі працюеце над стадіяй 3 Progress Guards Stuck, спачатку запісайце умовы: неабходныя даны, сигнал успеху і тое, што выканаецца у разе частковага неудачы. Такі список контроля дапамагае заліцвачыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптаў. Калі якісь крок не выйшоў, неудача должна вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг задач. Зробіце перапактаванне пасля дорогіх крокаў. Програма не должна занова ставіць плату за той самы вызыв LLM, калі аператар праканае пазнейшы вузел.

Пашчэрстаныя спосабы неудач

Калі працюеце над стадзіяй «Звычайныя спосабы абярошэння», спачатку запісайце контракт: неабяжлівыя даннэ, сігнал успеху і тое, што выходзіць пад частыя абярошэння. Такі список перакладае пазнейшыя змены коду ў правільны направленні. Спрэцьвуйце да гэтай стадзіі як да контракту межа даннэмі і перакананымі выходамі. Дайце назву кожнам элементам, задаце критэрыя успеху і не падзволяйце частым, непূরным виконанням задач. Зрабіце пераконтроль пасля дорогіх крокаў. Програма не должна зноў выклікаць той самы LLM-званак, калі аператар прабуе зноў виконаць пазнейшы элемент.

Рашэння: падпісы інструментаў і хешаванне стану рабочай прасторы

Калі працуеце на стадыі падтверджэння параметраў інструмента рашэння, спачатку запісайце угоду: неабходныя даны, сігнал успеху і тое, што выканаецца у разе частковага нявыпалення. Такі список контролю дапамагае заліцварыць пазнейшыя змены ў кодзе. Запісвайце час выканання і кост токена або запыту праза функцыйнальныя рэзултаты. Відразувая візуабельнасць костаў запобегае неспакойным рахункам, калі процес пераходзіць з дэмаверсіі ў спяльныя среды. Запішыце назву інструмента, хэш аргументаў, час затрымкі і рэзултат кожнага вызову. Без такога следу дэбаггін агента губіць гады часу. Калі працуеце на стадыі падтверджэння параметраў інструмента рашэння, спачатку запісайце угоду: неабходныя даны, сігнал успеху і тое, што выканаецца у разе частковага нявыпалення. Такі список контролю дапамагае заліцварыць пазнейшыя змены ў кодзе. Дакументавайце як шлях успеху, так і шлях вяснавання ситуацыі. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.

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. Чалавек у працэйным цыкле і безпечныя атрыбуты

Этап 4 «Чалавек у працэйным цыкле і безпечныя атрыбуты» працюе найкраща, калі яго розглядаць як мерыемую структуру. Запісаўце адна ідеальная версія, адзін прыклад неудачы і прыметкі па атрыбутах перад расшырэнням масштабу. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына неудачы павінна вказываць на адную адповядальнасць, а не на заплутаны ланцюг задач. Рэзультаты роботы графа павінны быть простымі та з адначытаемымі типамі дадзеных. Вярнутыя структуры дадзеных маскуюць інфармацыю пра тое, який вузел запісаў кожны атрыбут, і спакоююць працэздатнась пасля перерываў.

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;
  }
}

Практычны прыклад: Усе чатыры правілы безпекі ў генераторы апленавальных дапамог

У разліку з даным кейсам, усе чатыры стадзіі працуюць наяўней, калі іх расследжваць як вимерную паверхню. Запісаўце адна «золатая» версія даных, адзін кейс неудачы і прыметкі па поверненню да попярэдня стану пры розшырэнні масштаба. Расследжвайце гэтую стадзію як даговор межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даўце назвы артыфактам, задаце критэрыя успеху і не падзеўляйцеся частым, непূরным викананнем задач. Храніце стан графа як просты і з адначытаемымі дадзеннямі. Вкладаныя блокі даных маскуюць інфармацыю пра тое, який вузел запісаў кожны поле, і спакойваюць працэс пасля перарываў.

 ┌──────────────────────────────────────────────────────────┐
 │ 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            │
 └──────────────────────────────────────────────────────────┘

Дынамічная сінтэза спефікацый

Этап сынтэзу дынамічных спефікацый працюе наяўней, калі яго розглядаць як вимерную паверхню. Зафіксавайце адна ідеальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перад расшырэнням масштаба. Запісвайце часы выконання і косты токеноў або запытак па боку ад функцыйнальных рэзультатаў. Візуабельнасць костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмаверсіі ў спяльныя сераўысы. Храніце стан графа ў простам і типаваным формате. Вкладзеныя блокі маскуюць інфармацыю пра тое, який вузел запісаў які поле, і спакштуюць продовжэнне роботы пасля перарываў. Этап сынтэзу дынамічных спефікацый працюе наяўней, калі яго розглядаць як вимерную паверхню. Зафіксавайце адна ідеальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перад расшырэнням масштаба. Дакументавайце як шлях успеху, так і шлях вярнэння да нормальнага стану адночасна. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней.

Разпад працы між калькамі

Для стадіі розпаду працы межаў калькулятора неабходна пазначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння пры перадзеі коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі перапытку без неабясненняя схованага стану. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптаў. Калі крок не выйшоў, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцужок задач. Неабходна людская апраўда для тых крокаў, якія выкарыстоўваюць грошы чы зміняюць даны працы. Прыўязка пад час компілявання не є гарантыяй полнай адпрацоўкі бізнес-процэса.

Галоўная сістэма керування

Для стадіі главнага канцэлерыя неабходна ўзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы схованы стан. Спрыяйце цій стадіі як даговору межы вхідных і перакананых выходных данных. Даўце назвы артыфактам, узначыць перакананні пра успех і адмовіцеся ад тыхняга частковага завершэння без паведамлення. Автентыфікуйцеся на воратах і парадзейце правыя ў роўні дадзенняў. Толькі токэн-носіцель не ёстся межай аренды.

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.' };
}

Готовыя да вжывання главныя запиты

Для стадіі «Готовыя да выкарыстанняе мастер-прампты» неабходна прадзеявлення вхідных дадзеных, адпаведнага адпаведальніка за крок і крэтарыяў выходу пры перадзеявленні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць час выконання і кост токенаў або запытаў разам з функцыйнальнымі рэзултатамі. Відразы костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмаверсіі ў спяльныя среды. Калі наступны крок — гэта код або вызов інструмента, лепш выкарыстоўваць структураваныя выходныя данні з перакананням схемы, чым вольныя тэкстовыя апісанні. Для стадіі «Готовыя да выкарыстанняе мастер-прампты» неабходна прадзеявлення вхідных дадзеных, адпаведнага адпаведальніка за крок і крэтарыяў выходу пры перадзеявленні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Адначасова документаваць успішны і варыянт вярнення да нормальнага стану. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных дадзеных ўжо є часткай продукту, а не чымсь, што дадаецца пазней.

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.

Спісак пунктав для падзеў

Калі працуеце над спісакам пунктав для падзеў, спачатку запішыце умовы контракту: неабходныя даны, сігнал успеху і тое, што выходзіць у разе частковага невыпання. Такі спісак дапамагае залічваць пазнейшыя змены ў кодзе чыста. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына невыпання павінна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг задач. Робіце перапактаванні пасля дорогіх крокаў. Система не павинна знову стягваць плата за той самы вызов LLM, калі аператар праказвае пазнейшы вузел.

Вывад

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

Чэк-ліст для эксплуатацыі

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

Зберагаеце настройкі паза кодам прыемленае. Файлы сяродавішняй среды, хранільнікі секрэтных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць без неабяжлівага чытання всей структуры.

Контрольныя точкі пасля дорогіх крокаў. Система адновлення не должна занова нарахоўваць плата за той самы вызыв LLM, калі аператар праказвае спробу на пазнейшым вузле.

Фіксуйце версіі залежнасцяў і запішыце хэш адобраза, які выканаў дэманстрацыю. Возможнасць павторнага стварэння результата лепш за традыцыйныя знаёмства.

Документавайце як шлях успеху, так і шлях вярнення да нормальнага стану. Праказвы, людзкі контроль і обработка некоректных паведамленняў є часткай продукту, а не дадатковым удосконаленнем пазнейша.

Контрольныя точкі пасля дорогіх крокаў. Система адновлення не должна занова нарахоўваць плата за той самы вызыв LLM, калі аператар праказвае спробу на пазнейшым вузле.

Перад апранаванням стака заморажуйце версіі, зафіксавайце ідеальны транскрыпт для критычнага шляху і паказвайце крокі для вярнення да пачатковага стану. У спільных средах неабходны ліміты частоты вызывоў, пераказкі належнасці і чыстае вялічынство для ротацыі секрэтных дадзенняў. Лепш надзейнае, але простае функцыонаванне, чым крэатывныя, але еднакратныя дэманстрацыі.

Запіскі для пакета c09d8d68f871: не трэба кантрацаваць ключы прадастоўцаў у репазітары, задаць максімальную кантроль на токены за сесію, а таксама зберагчы транскрыпціі праза фіксатуры адлічэння, каб пазнейшыя замены моделяў заставаліся пораўнанымі.