Перейти до вмісту
mp.
Усі тексти
Розробка

Як я працюю з агентами для програмування

Переклад з англійського оригіналу.

Локальний тест, відкритий симулятор і перевірка продакшену відповідають на різні питання. Більша частина моєї роботи з агентами зводиться до того, щоб знати, на яке з них уже справді отримано відповідь.

Найбільше використовую Claude Code, а поруч із ним Codex: від пошуку причини бага до перевірки релізу. Координую кілька розмов, даю їм обмінюватися результатами й залучаю іншу модель, щоб спробувати зламати зміну. Також пишу код сам і прискіпливо ставлюся до розуміння того, чому зміна має працювати.

Що вже є до мого промпту

Користуюся Claude Code через Supacode, де розмова, термінал і diff розташовані поруч. У моїх особистих інструкціях є фраза «I prompt short and let you run»: я пишу коротко й даю працювати. Там також сказано спочатку прочитати репозиторій, використовувати його команди, зберігати сторонні зміни й чесно вказувати, що було перевірено. Загальні побажання живуть у глобальних інструкціях, обмеження проєкту в репозиторії. Документація Claude пояснює, як вони завантажуються.

Розростання задачі є збоєм, який я помічаю найчастіше, зазвичай за розміром diff уже після роботи. Тому інструкції просять агента повідомляти розмір diff, пояснювати несподіване збільшення й подавати суміжні проблеми окремим списком, а не виправляти їх. Агент не питає дозволу перед кожним читанням файлу, але зупиняється, коли рішення змінює обсяг задачі.

Для планування, рев’ю та спрощення використовую Compound Engineering і навички Метта Покока. Навичка дає агенту процедуру, коли вона потрібна. Повний процес не запускаю заради кожної правки.

Іноді моє наступне повідомлення просто таке:

make it better. make no mistakes.

Тобто «зроби краще, не припускайся помилок». Це працює лише як продовження наявної задачі, де репозиторій, попереднє обговорення й перевірки задають значення слова «краще». Якщо вимога ще нечітка, спершу треба пояснити бажану поведінку.

Визначити, що має залишатися істинним

У моєму проєкті галереї два запити можуть оновлювати різні роботи в одній колекції. Вимога така: обидва успішні оновлення мають зберегтися незалежно від порядку завершення. Коли це сформульовано, агент має конкретне завдання: простежити, звідки кожен callback бере свою копію стану.

Якщо callback замінює колекцію знімком, який захопив на початку запиту, він може стерти інше оновлення. Це пояснює збій і дає тест: завершити B перед A, а потім перевірити, що обидва URL залишилися. Тест нижче робить саме це.

Тож перед реалізацією визначаю стан, хто може його змінювати й що має залишатися істинним. Пропозиція додати кеш або ще одну абстракцію має пояснювати, як це вирішить саме цей збій. Те саме стосується інструментів навколо агента: попередній перегляд не має змінювати файли, які оглядає, а рев’ю має стосуватися версії, яку випускають.

Дати агенту скористатися застосунком

Коли працюємо з інтерфейсом, агент відкриває браузер чи симулятор і пробує взаємодію. З браузерними інструментами або Argent він може натиснути кнопку, оглянути результат і повторити ті самі кроки після зміни. Прошу записати відео, якщо проблема стосується руху або послідовності дій.

У галереї я постійно питав, чи перевірені переклади справді потрапили на сайт. Для відповіді треба було звірити наданий документ, поточний текст і опубліковану сторінку. Успішна збірка не могла цього підтвердити.

Під час досліджень також використовую Sentry, PostHog і Langfuse. Маючи трасування, прошу спершу розібрати кроки, а вже потім виправлення. Типовий запит без деталей проєкту:

Переглянь трасування за цей проміжок часу. Лише читання.
Розбий тривалість p50 і p90 за кроками та визнач основні складові.
Відокрем те, що показує трасування, від гіпотези про причину.
Спочатку надай докази, потім пропонуй зміну.

Також питаю, яку версію й який бекенд оглянув агент. Я вже достатньо чув «тут працює», щоб хотіти знати, де саме це «тут».

Координувати розмови

У мене часто працює кілька чатів одночасно. Основна розмова координує задачу, передає роботу іншому чату й повертає його результати. Чати також можуть обмінюватися питаннями та висновками через доступні інструменти. Субагент є виконавцем усередині однієї сесії, з власним контекстом і доступом до інструментів; інший чат має власну історію й може використовувати іншу модель. У моєму наборі є спеціалісти зі стану React Native, продуктивності, доступності та безпеки, і я обираю їх під питання, яке вирішую.

Ніхто з них автоматично не знає, що відбувалося деінде. Передача роботи має містити питання, відповідні файли, поточну версію, обмеження й те, що вже перевірено. Обмін між чатами корисний, коли дає щось конкретне: відповідний код, умови збою й перевірку, яка розрізняє два пояснення. Два чати, що повторюють висновки один одного, не є незалежним рев’ю.

Окремі розмови не ізолюють файли, тому, коли реалізаціям потрібні окремі робочі копії, використовую Git worktrees. Перед об’єднанням результатів перевіряю, яку версію читав кожен виконавець і що змінив. Якщо код змінився під час рев’ю, попередня знахідка може вже не стосуватися його, а об’єднана зміна все одно потребує власної перевірки.

Дати іншій моделі спробувати зламати зміну

ce-code-review із Compound Engineering є важливою частиною мого набору, переважно завдяки adversarial-рев’юеру. Він шукає, як зміна зламається при взаємодії компонентів, накладанні запитів або неочікуваному відновленні після помилки, і має побудувати послідовність, яка спричиняє збій. Для інструмента перевірки на кшталт uivet питання таке: чи може неправильний інтерфейс усе одно отримати зелений результат.

Його перевірка іншою моделлю може передати рев’ю іншій моделі, тож реалізацію перевіряє не лише її автор, повторно обдумуючи свій код. Мені важливо, яка модель справді виконала рев’ю. Другий чат в іншому застосунку сам по собі не доводить, що його обслуговувала інша модель. Якщо перевірка іншою моделлю не запустилася, це обмеження перевірки, про яке треба сказати, а не запасний варіант, який можна назвати незалежним рев’ю.

Перед застосуванням знахідки звіряються з кодом. Згода двох моделей не є доказом, а правдоподібна історія збою потребує шляху, який справді може виконатися. Іноді правильний результат полягає в тому, щоб відхилити знахідку, прибрати зайвий патч або залишити реалізацію як є.

Як задача проходить через мій робочий процес
Визначити задачуЗапит, інструкції, поточна версія, основна розмова
Дослідити незалежно
Простежити поведінкуКод, браузер, симулятор, трасування
Перевірити поясненняОкремий чат чи модель, adversarial review, інша гіпотеза
Порівняти докази
Зробити змінуПовернути знахідки, розв’язати розбіжності доказами
Запустити перевіркиТести й справжній браузер або пристрій
Переглянути diffКоректність, обсяг, потім спрощення
Перевірки пройдено
Передати перевірену роботуЗберегти корисні тести, інструкції, навички й рішення для наступної задачі.

Якщо щось не працює: переглянути зміну й повторити відповідні перевірки.

Чати обмінюються результатами через інструменти й передачу контексту. Незалежна робота все одно потребує інтеграції та перевірки; я не запускаю додаткових агентів заради кожної правки.

Дати перевірці шанс упасти

Самого рев’ю мало; потрібна ще перевірка, яку можна запустити. В uivet навмисно прибираю ціну з інтерфейсу й перевіряю, що звіт називає відсутнє значення, а процес завершується з помилкою. Якщо дивитися лише на код завершення, можна прийняти помилку запуску за успішний тест.

Ось гонка з галереї як самодостатній тест. Вона моделює оновлення стану без React, сервера чи мого репозиторію. Збережіть її як media-update.test.ts і запустіть bun test ./media-update.test.ts через Bun.

import { expect, test } from 'bun:test';

test('a slower request preserves the result that arrived first', async () => {
  let artworks = [
    { id: 'a', url: '' },
    { id: 'b', url: '' },
  ];
  const a = Promise.withResolvers<string>();
  const b = Promise.withResolvers<string>();

  const pending = [a, b].map((request, index) =>
    request.promise.then(url => {
      artworks = artworks.map((artwork, position) =>
        position === index ? { ...artwork, url } : artwork
      );
    })
  );

  b.resolve('/b.webp');
  await pending[1];
  expect(artworks).toEqual([
    { id: 'a', url: '' },
    { id: 'b', url: '/b.webp' },
  ]);

  a.resolve('/a.webp');
  await pending[0];
  expect(artworks).toEqual([
    { id: 'a', url: '/a.webp' },
    { id: 'b', url: '/b.webp' },
  ]);
});

Щоб побачити баг, додайте const initial = artworks; перед const pending, а потім замініть artworks = artworks.map на artworks = initial.map. Остання перевірка впаде, бо callback A повертає порожній URL для B. Поки писав статтю, запустив обидві версії.

Тест явно фіксує порядок завершення, а не сподівається, що один таймер випередить інший. Він передбачає незмінний склад колекції та незалежні оновлення різних елементів. Він не перевіряє рендеринг React, завершення терміну дії URL або демонтування компонента; власні тести галереї перевіряють це окремо.

Розбиратися у великих змінах через артефакти

Я досі читаю код. У великому репозиторії хочу допомоги з пошуком частин, які заслуговують на уважне читання. Для цього використовую артефакти: HTML-розбір, схему шляху запиту або звіт із результатами поруч зі зміною. Відео Тео про HTML-артефакти є одним з орієнтирів для цієї частини моєї роботи.

Навичка pr-review-canvas від Cursor отримує опис pull request, diff і коментарі рев’ю та створює HTML-сторінку з поясненнями біля змін. Механічні правки згортаються, важлива реалізація залишається відкритою. Для медіаоновлення це означає побачити, який callback володіє станом і чому стара версія губить оновлення, а потім читати код із цими питаннями в голові.

Відкрийте приклад HTML-розбору. Я зробив його для цієї статті на тій самій гонці, що й тест вище; це не експорт приватного PR.

Артефакт може пропустити сценарій збою так само легко, як відповідь у чаті, тому від нього має бути шлях до джерела: справжній diff, версія, яку переглядаємо, і перевірки. Якщо реалізація змінилася після створення артефакту, рев’ю теж треба оновити.

Перевіряти вимогу й реалізацію

Спершу перевіряю коректність, потім спрощую: помилки стану, дозволів і поведінки платформи йдуть раніше за пошук коду, який можна прибрати. Навичка рев’ю коду Метта Покока відокремлює стандарти проєкту від початкової вимоги, і мені подобається це розрізнення. Зміна може дотримуватися всіх домовленостей і все одно робити не те.

Часто питаю, навіщо знадобилася нова допоміжна функція, пакет або конфігураційний файл. Іноді є вагома причина, іноді наявний код уже справляється. Після виправлення відповідна перевірка запускається знову, і я хочу знати, який застосунок чи платформу вона охопила. Потім простежую зміну до версії, якою користуються люди: чи її запушили, чи завершився деплой і чи опублікована поведінка збігається з перевіреною.

Перевіряти, чи варто взагалі робити зміну

Деякі задачі мають завершуватися дослідженням. Якщо я ще не вирішив, чи потрібна зміна, прошу рекомендацію до створення гілки. Те, що агент уже щось побудував, не є причиною це випускати.

Також використовую агентів для порівняння результатів моделей, дослідження затримок і вартості. Ставлю під сумнів рекомендацію, якщо вона спирається на невелику пробу або непояснену кількість, і прошу вхідні дані, відмінності між варіантами та запит, з якого отримано вимірювання. Нова модель може бути повільнішою, не ставши кращою. Акуратний рефакторинг може розширити задачу, не вирішивши проблему. Залишити те, що вже працює, теж нормальний результат.

Важливо й те, для кого ці докази. Звіт для людини, яка ухвалює продуктове рішення, має спершу показувати головне порівняння, а технічні деталі тримати нижче. Часто доводиться спрямовувати першу версію саме до цього.

Налаштування теж тримаю в Git

Приватний репозиторій agent-config зберігає переносні частини Claude Code і Codex: інструкції, налаштування, визначення агентів, команди й навички. Спільні навички живуть в одному каталозі, щоб обидва інструменти могли ними користуватися. Резервна копія не містить розмов, пам’яті, облікових даних, кешів і стану конкретного комп’ютера; для плагінів є маніфести повторного встановлення.

Синхронізація копіює лише файли, які я вже вирішив зберігати, і перед комітом я дивлюся попередній перегляд змін. Сам процес резервування має власні тести. Один змінює тимчасову копію моїх інструкцій, запускає попередній перегляд синхронізації й перевіряє, що копія в репозиторії не змінилася. Інший перевіряє, що відновлення відмовляється перезаписувати наявний файл налаштувань. Третій підтверджує, що перевірка секретів відхиляє фіктивні облікові дані.

Залишати корисне

Коли сесія знаходить щось варте збереження, перетворюю це на конкретний тест, інструкцію або навичку. Для галереї це була передача контексту, яку інший агент зможе звірити з репозиторієм і сайтом, коли надійде наступний лист. Для мого середовища це стало синхронізацією конфігурації.

Хочу, щоб наступна сесія починала з того, чого ми навчилися, і все одно перевіряла, чи відповідають ці нотатки коду.

Усі тексти: розробка, есеї та оповідання.