Продакшн-харнес для Claude Code: шість шарів від Slack до об’єднаного MR.
Як шестошаровий харнес перетворює заявки в Slack на об’єднаний код: прийом подій, цикли агентів з контролем доступу, стабільний стан, самовідновлення, можливості моніторингу та керування оператором.
Джерела: Building a Production Agent Harness: Turning Claude Code Into a Multi-Agent Engineering Pipeline
Якщо поставити модель кодування окремо, ви отримаєте „мозок у банці“. Вона чудово пише код та викликає функції, проте не зможе побачити ваше приватне повідомлення в Slack о 3 години ранку, не спробує знову запустити завдання CI, яке показало помилку, не вирішить конфлікт при злитті, що щойно виник у відкритому запиті на злиття, та не згадає, що рецензент учора щось запитував, а ніхто не відповів.
Те, що перетворює модель на корисний інструмент, — це „оплетка“: інфраструктура часу виконання, яка забезпечує „чуття“, „руки“ та пам’ять. Її складові — прийом подій, оркестрація агентів, стабільний стан, цикли, які самостійно виправляються, можливість спостереження та засіб для керування з боку людини-оператора. Описана нижче система — це продакшн-рішення, а не демонстраційний зразок для досліджень. Вона стежить за каналом Slack, створює запити на злиття у п’яти внутрішніх репозиторіях, відповідає на коментарі рецензентів протягом ночі та самостійно виправляє помилки у завданнях CI, без будь-яких запитів.
Це внутрішня, багатоагентна система, створена на базі Claude Code. Вона працює безперервно вже кілька місяців, отримуючи завдання з Slack та розподіляючи справжню інженерну роботу. Далі наведено загальний огляд: що вона робить, як пов’язані її компоненти та які фактори вплинули на її проектування.
Розділи розглядаються у такому порядку: чому взагалі потрібен цей інструмент, що саме він робить, три проблеми, які він вирішує, шість рівнів (прийом подій, оркестрація агентів із детальним розглядом автономного циклу та рівня самовдосконалення, постійний стан, самовідновлення, можливості моніторингу та людський контроль), кілька додаткових елементів окремо від цих рівнів, поетапний огляд всього процесу та порівняння з іншими подібними інструментами.
Чому чат-вікно та деякі інструменти недостатні
Коли у 2023 році з’явилися плагіни для чат-асистентів, багато команд спробували очевидний шлях: розмістити інтерфейс чату перед ШІ, надати йому кілька інструментів для виклику функцій та назвати результат «інженерним агентом». Така конфігурація майже працює у демо-версії, але ніколи не витримує роботи у продакшені. Вже протягом тижня з’являються три способи збою.
- Контекст зникає, коли користувач закриває вкладку. Пам’ять має зберігати інформацію після завершення окремої сесії чату. Справжнє інженерне завдання триває кілька днів: створення заявки, робота над варіантом коду, підготовка проєкту до перевірки, відповідь на коментарі, очікування позитивного результату тестування та злиття коду. Жоден окремий виклик моделі не може охопити все це.
- Ніщо не реагує на зовнішні зміни. Кореспондент залишає коментар о 16:00. Система тестування зламується після третього коміту. Колега відповідає у дискусії. Агент має реагувати на ці події, а не постійно перевіряти стан чи чекати, поки людина знову його активує.
Виправлення цих проблем — це завдання інструментарію. Саме це відрізняє звичайну вкладку чату від самого Claude Code, від фонових агентів Cursor, від Devin чи від описаної тут системи. Модель, що лежить в основі, майже ідентична у всіх них. Відрізняється лише інструментарій, який її оточує.
Модель потрапляє у світ лише через інструментарій, і саме інструментарій виконує майже всю інженерну роботу, яка змушує LLM поводитися як система.
Завдання, для якого створювався цей інструмент
Початковою проблемою був конкретний сценарій. Чат Slack під назвою #support отримує близько 30 заявок на тиждень. Кожна з них потребує розслідування, а зазвичай після цього — змін у коді в одному з п’яти репозиторіїв. Розгляд однієї заявки триває кілька днів. Рецензенти коментують пропозиції щодо змін, іноді виникають проблеми з автоматичним тестуванням, а деякі розслідування виявляються повторенням попередніх. Команда, яка вирішувала ці проблеми, постійно отримувала сповіщення ввечері та на вихідних, і ситуація не покращувалась, оскільки щоразу з’являлися однакові типи заявок.
Цей інструмент має три функції, впорядковані за ступенем втручання:
- Розслідування. Коли у чат
#supportз’являється нова заявка, необхідно простежити хід обговорень, зібрати інформацію з різних репозиторіїв та опублікувати структурований аналіз у тому ж чаті.
Саме ця третя функція відрізняє його від звичайних ботів для обробки заявок. Основним матеріалом для системи є її власний вихідний код, і LLM може змінювати його через запити на об’єднання, які проходять перевірку людини.
Система складається з шести логічних шарів, кожен з яких розглянутий нижче.
Три прогалини, які проста модель не може подолати
Усією конструкцією керує одна ідея: безстановий, суто реактивний LLM не є продуктовою системою. Шість шарів існують для подолання трьох прогалин, які модель не може подолати сама.
Першою проблемою є реактивність. Модель не може сама себе активувати. Цим займається шар 1 (прийом подій), адже дані від Slack, результати GitLab CI та сторінки PagerDuty потрапляють у один чергувальний буфер. Штучний інтелект ніколи не перевіряє стан самостійно – його просто викликають.
Другою проблемою є стійкість даних. Моделі забувають усе між сесіями та втрачають контекст, коли робота переміщується між пристроями; крім того, вони не можуть розрізнити ситуацію, коли щось було оброблено, та ситуацію, коли зміни справді надійшли до основного сервера. Це вирішує шар 3 (стійкий стан): структури в пам’яті усувають дублікати всередині одного процесу, локальні JSON-файли зберігають інформацію про роботу, а простори роботи, синхронізовані через git, зберігають стабільний стан справ. Продовження сесії між пристроями, про яке йтиметься далі, ґрунтується на тій самій ідеї – а саме, що стан має зберігатися у git, а не всередині працюючого процесу.
Третьою проблемою є якість. Якщо не впорядкувати процес, цикл міркувань ШІ робить впевнені висновки на основі слабких доказів, оголошує розслідування завершеними ще до перевірки кожного доступного джерела та створює описи змін, які вже з першого речення здаються написаними машиною. Рівень 2 (оркестрація агентів) та цикл розслідувань вирішують цю проблему за допомогою структурованих контрактів вихідних даних, перевірки якості після кожної ітерації, адверсарського аналізу після завершення циклу та механізмів самовідновлення під час змін коду, тестування та об’єднання змін.
Складний ефект — це те, що об’єднує всі рівні, адже кожен завершений випадок прискорює наступний. Схвалені розслідування стають записами у базі знань. Схвалені зміни перетворюються на шаблони, на які може посилатися аналізатор проблем. Завершений випадок спонукає до створення змін, спрямованих на вдосконалення самого інструменту. З кожним циклом все менше типів заявок обробляються неналежним чином.
Рівень 1: як події пробуджують систему
Три зовнішні джерела сигналів можуть пробудити систему керування:
- Повідомлення в Slack, тобто згадки у каналах, відповіді у дискусіях та команди адміністратора, надіслані як приватне повідомлення самому собі.
- Активність у GitLab, тобто коментарі до заявок на перегляд, результати обробки та збої у процесах.
- Попередження в PagerDuty, які використовуються тоді, коли спеціаліст, який чергує, згадує тикет, який система вже бачила, щоб можна було ознайомитися з попереднім контекстом.
Ці три джерела відрізняються за затримкою та надійністю, тому кожне має власний шлях входу сигналів.
Два практичні нюанси ніколи не вказуються у стандартній версії цього діаграми.
Еволюційна консистентність. Кінцева точка Slack conversations.history може відставати на від 1 до 30 секунд. Запитувач, який бачить порожній результат та наївно переміщує свій курсор вперед, остаточно пропустить будь-яке повідомлення, чий ts знаходиться в цьому проміжку часу. Таким чином курсор застигає на порожніх запитах, а наступна спроба починається з того самого найстарішого часового позначення, що повторюється доти, доки не повернеться принаймні одне повідомлення. Множина для видалення дублікатів за часовим позначенням кожного повідомлення фільтрує дублікати, які створюються цими спробами.
Режим Socket разом із механізмом опитування – це повний набір засобів для стабільної роботи. Режим Socket забезпечує отримання відповідей від потоків з затримкою менше секунди, але з’єднання WebSocket перериваються. Механізм опитування виступає як запасна мережа, ловлячи все те, що пропустив режим Socket, і обидва компоненти використовують файл socket-dedup, щоб жодне повідомлення не надсилалося двічі. Якщо це видалити, будь-яке повідомлення, яке надходить під час переривання з’єднання, зникає безслідно, не даючи оператору жодного сигналу про його пропуск.
Цикл mr-monitor cron поводиться інакше. GitLab не додає коментарі до рецензування у пайплайн, тому єдиним варіантом є опитування, а масштабування самого цього процесу виявилося складним завданням. Кожен додатковий тривалий MR, який бере на себе цей механізм, подовжує 10-хвилинний цикл, що призводить до накопичення курсорів для „мертвих“ потоків, причому кожен з них коштує одного виклику API Slack за цикл. Якщо їх не контролювати, вони спричиняють постійні проблеми з обмеженням кількості запитів 429, що ускладнює виконання важливих опитувань. Існуюче рішення автоматично видаляє курсор, коли API надає відповідь thread_not_found, тож він не береться до уваги у наступному циклі. До впровадження рішення один цикл здійснював 12 спроб кожні кілька годин; після цього — жодної.
Рівень 2: як організовуються агенти
LLM насправді працює тут. Система запускає робочі процеси як тимчасові одиниці systemd, і кожному з них передається JSON-дані з іменем потоку роботи, каталогом case_dir для використання та ідентифікатором потоку, до якого він належить.
Кожен потік роботи є частиною одного тривалого запуску Claude Code („мозку“), який отримує підготовлений запит та каталог case_dir з можливістю запису. Цей каталог слугує простором для роботи агента, де зберігаються файл TASK.md справи, все більша кількість файлів типу followup_transcript_*.md з транскрипціями, будь-які звіти gap_report*.md, створені агентом, та проміжні JSON-дані.
Порядок виконання агентами завдань є частиною дизайну. Замість того, щоб один агент робив усе сам, кілька спеціалізованих агентів підключаються один до одного, і кожен з них передає завдання у визначений момент:
oncall_runпроводить перше дослідження, здійснюючи до 20 ітерацій міркувань з перевіркою якості, що завершується прямою самокритикою. Його результатом є файлANALYSIS.md, у якому описується, що не так, та файлTASK.md, у якому описується, що потрібно зробити.case_followup— це агент для довготривалого спілкування. Оператор може відповідати у приватних повідомленнях у темі справи протягом кількох днів, і кожна відповідь знову запускаєcase_followupіз усіма попередніми розмовами. Крім того, кожні N відповідей цей агент викликаєgap_analyzerяк підагента, і цей цикл створює файлиgap_report*.md, які вказують на власний код системи (цикл самовдосконалення розглядається далі).
finalize_case є об’єднувачем. Як тільки справа залишається нерухомою протягом чотирьох годин, або оператор виконує команду admin: finalize <case>, він збирає всі звіти про пропуски, видаляє хибні позитивні результати та створює один MR із виправленнями, які залишилися. Цей MR спрямований на кодбазу harness, а не на початковий репозиторій тикета, що є конкретним проявом циклу самовдосконалення.dev-agent — це той, хто виконує практичну роботу. У своєму власному git-робочому дереві для кожної справи він редагує код, пускає зміни, стежить за CI, виправляє проблеми в CI та відповідає на запитання рецензентів. Він працює у трьох режимах: address_review, fix_ci та task (початкова реалізація).У цьому діаграмі є пара деталей, які легко пропустити.
По-перше, ніщо не торкається основної системи оплати, оскільки весь робочий процес відбувається у робочих деревах. Це дозволяє кільком справам виконуватися одночасно без конфліктів. Збирач даних та диспетчер агента включають шлях до робочого дерева у навантаження, тож ШІ взагалі не бачить основної системи оплати.
По-друге, case_dir є основою для всього. Файли стану у ~/.harness/ (див. розділ про збереження даних) пов’язують кожну тему у Slack із конкретним case_dir, а агент обробляє всі шляхи відносно нього. У результаті все інше стає простішим: достатньо перемістити каталог, оновити карту, і кожен робочий процес буде слідувати за нею.
Граф відображає те, що виконується. У наступному розділі пояснюється, як це відбувається, тобто механізми, які передають команду з Slack до завершення розслідування.
Автономний цикл, крок за кроком
Якщо хтось запитає, що насправді робить ця система, найкоротка та чесна відповідь така: оператор вводить одну команду Slack, і після цього починається замкнений цикл повторного міркування, самокритики та використання інструментів без людського контролю, доки робота не буде завершена або система не визнає, що не може продовжувати. Майже кожне цікаве дизайнерське рішення приховане в цьому реченні, а решта цього розділу пояснює його.
Усередині конструкції знаходяться чотири окремі цикли агентів, кожен з яких пристосований до свого типу задачі:
На цьому малюнку показано їхні форми. Далі йде суттєва частина — кожен механізм описаний у порядку його функціонування. Діаграма відображає всі ці елементи, а її вузли стають зрозумілими після прочитання наступних розділів. Коди воріт, які вона використовує, — це G1–G4, N1–N3, N5, A1–A3 та D1–D15.
До початку циклу: блокування перед прийняттям рішення
Два захисні механізми діють ще до першого виклику Claude, причому перший з них є структурним. Перш ніж відбудеться будь-яка ітерація розслідування, система змушує Claude сформулювати hypothesis_slate. У цьому документі перелічується принаймні шість можливих пояснень разом із чотирма–п’ятьма критеріями для їх вибору, і він зафіксовується у kickoff_precommitment.json, який після цього не може бути змінений.
Серед усього в системі цей механізм забезпечення є тим захистом, який має найбільшу методологічну значущість та при цьому залишається майже непомітним. Неконтрольований цикл міркувань ШІ схильний обирати ту версію, яку підтримують перші дані, а всі наступні докази сприймати як підтвердження цього вибору. Цей механізм не може зупинити цю тенденцію. Однак він змушує кожне дослідження починатися з усього простору гіпотез та фіксувати, як саме воно дійшло до свого висновку. Потім адверсарійний рецензент (фаза B) ставить ще складніше запитання, ніж про внутрішню послідовність: чи є цей висновок тим, який спрогнозував би початковий алгоритм, враховуючи наявні докази?
Другим захистом є емпіричний фундамент (фаза C). Лише під час першої ітерації виконується кілька команд рівня операційної системи без участі LLM: перегляд останніх помилок journalctl, зрізи диска та пам’яті, а також пошук файлів журналу. Результати використовуються як первинні докази у початковому запиті на розслідування, надаючи Claude фактичну картину стану пристрою ще до формулювання будь-яких гіпотез. Цей крок ніколи не блокується, тихо ігнорує помилки та триває менше секунди. Він має значення, оскільки наступний крок міркувань починається з достовірних даних, а не лише з опису проблеми.
Структурований вихід: звіт про завершення
Кожна ітерація завершується парсованим JSON-документом completion_report, який зчитується системою для визначення подальших дій. Його структура є фіксованою:
{
"status": "IN_PROGRESS" | "BLOCKED" | "COMPLETE",
"confidence": 78,
"open_questions": ["..."],
"unchecked_sources": [{"name": "...", "access_status": "..."}],
"contradiction_register": ["..."],
"assumption_register": ["..."],
"adjacent_problems": [{"summary": "...", "status": "...", "blocker": "..."}],
"draft_response": "the user-facing Slack message"
}
Навколо блоку може бути проза вільного формату, але контрольні механізми оцінюють оброблений звіт, а файли стану зберігають його. Заміна частини свободи у формулюванні прози на можливість обробки контракту стала найефективнішим способом забезпечення придатності результатів роботи LLM у межах пайплайну. Якщо пропустити схему, кожен наступний крок змушений буде видобувати факти безпосередньо з прозового тексту. Якщо ж схему зберегти, контрольні механізми перетворюються на звичайний детерміністичний Python.
Контрольні механізми якості, що надають значення показнику впевненості
Показник впевненості був би просто маркетинговим шумом, якби Claude міг просто ввести confidence: 95 та продовжити роботу. Тому результат кожної ітерації проходить через контрольні механізми якості, які не дозволяють продовжити аналіз, якщо докази та заявлений рівень впевненості не узгоджуються.
Після кожного кроку Claude запускаються три групи контрольних механізмів:
- G-gates перевіряють логічну послідовність, щоб між раундами міркування не суперечили одне одному. Найбільш очевидним прикладом є G1, який активується тоді, коли кількість
open_questionsзросла протягом раунду, алеconfidenceзбільшилась, оскільки зростання знаменника компенсує будь-яке підвищення. Ця група також перевіряє, чи вираження напруги у тексті реєструються як суперечності, та чи сусідні проблеми відстежуються як об’єкти з дійсним статусом, а не як місця заміни. Їх є чотири. - N-gates перевіряють структурну повноту, переконуючись, що елементи повністю сформовані та не є неповними. N1 вимагає, щоб кожна дія з виправлення була поєднана з
verify_action, який містить опис, очікуваний результат та час виконання. Виправлення, запропоноване без можливості його перевірки, вважається незавершеною роботою. Їх є три.
COMPLETE, поки суміжні проблеми залишаються нерозв’язаними, вимагають наявності не порожнього реєстру припущень та наполягають на тому, щоб принаймні одна запропонована дія безпосередньо відносилася до кореневої причини. Їх є сім.Над ними знаходяться два додаткові контролювання. EQ1 змушує вузли кореневої причини наводити переконливі докази (щось інше, окрім INFERRED) або погоджуватися на ліміт у 60%, а P7 знижує цей ліміт на 7% за кожен адверсарський параметр, позначений як FAIL, до мінімуму у 40%.
Усього в цій системі є 19 функцій. Зручний спосіб їх запам’ятати: G-гейти реагують на те, що пише Claude, N-гейти — на те, що він пропускає, а A-гейти — на те, що він стверджує.
Порушення стає записом pending_guard_notes, який розміщується на початку запиту наступної ітерації під заголовком ## SYSTEM QUALITY GUARD NOTES (з попередньої ітерації) із чітким завданням спочатку розглянути кожну примітку. Це і є механізмом примусу. «Брами» не блокують модель; вони роблять її попередні порушення першими, що вона читає у наступному раунді. Ввічливість з боку моделі не має значення, а саме структурне забезпечення просуває розслідування вперед.
Крок фіксації контексту (A7) також виконується наприкінці кожного конструктора запитів, незалежно від того, чи були порушені якісь правила. Він знову додає нерозв’язані суперечності та неперевірені припущення з поточного звіту, слово в слово, у кінець наступного запиту. Це допомагає протидіяти відомому явищу зниження уваги моделі, коли вона помічає складність на третьому кроці, але до сьомого кроку вже ігнорує її.
Впевненість як конкретне число
Найважливішим елементом дизайну є те, що агент присвоює собі значення confidence: <int> у масштабі від 0 до 100 у структурованому результаті кожної ітерації. Це число не є декоративним, оскільки воно керує реальною поведінкою:
- Якщо показник нижчий за 70, цикл не може бути завершений. Система змушує виконати ще до трьох кроків та додає примітку про те, що агент стверджував завершення роботи, хоча показник залишився нижчим встановленого рівня.
status: COMPLETE та без open_questions відкривається можливість переходу до фази адверсарського огляду (червона команда проти синьої).degrading. Агент рухається назад, тому зупиняється та повідомляє про це оператора.Повна траєкторія рівня впевненості кожного випадку записується у ANALYSIS.md у вигляді спарклайну, наприклад 45% → 62% → 78% → 80%. Оператори швидко переглядають її, щоб визначити, чи можна довіряти результату, а сам цикл самовдосконалення системи читає його під час вибору тих випадків, які потрібно перевиконати з іншим запитом.
Три способи, чому цикл відмовляється зупинятися раніше
На діаграмі три гілки досягають стану ready_for_adversarial або blocked, при цьому цикл не закінчується. Кожна з них є правилом примусового продовження: система ігнорує сигнал про вихід та замість нього вводить захисну записку.
- Правило CONFIDENCE_FORCE застосовується, коли
status=COMPLETEтаopen_questions=[], але значенняconfidenceменше 70. Модель оголошує про перемогу, водночас показуючи сумніви через це число. Примушується виконати ще до 3 ітерацій, які нараховуються в межах ліміту у 20 ітерацій, а не додаються до нього.
status=COMPLETE та confidence становить принаймні 70, але causal_chain_complete дорівнює false. Це результат на рівні симптомів без визначення первинної причини. Цей параметр змушує провести ще до 3 ітерацій у межах загального ліміту у 20 ітерацій.status=BLOCKED, а доступні технічні джерела були проігноровані або визнані недоступними на основі припущень, а не через справжню помилку. Розслідування не може завершитися як заблоковане, поки не буде повністю перевірено все можливе. Цей параметр дозволяє продовжити роботу ще до 3 разів у межах того самого ліміту.Разом ці параметри запобігають трьом найпоширенішим передчасним завершенням: невизначеності, яка виглядає впевненою, завершенню на рівні симптомів та уявній недоступності.
Агресивний рецензент
Коли система, яка працює у режимі чергування, оголошує про завершення роботи з рівнем впевненості 70 або вище, керування передається окремому запуску Claude, який виконує роль адверсарійного рецензента. Цей рецензент не бачить запиту дослідника. Він отримує текст аналізу та перелік з 15 критеріїв оцінки, від D1 до D15, які стосуються достовірності припущень, ланцюга доказів, причинно-наслідкових зв’язків, альтернативних гіпотез тощо. Він повертає структуроване рішення:
Існує три способи завершення процесу рецензування:
- Критичні проблеми повертаються досліднику. Його нотатки вводяться у наступну ітерацію як
pending_guard_notes, після чого цикл починається знову, причому дослідник отримує чітке попередження про те, що рецензент вважає, ніби щось було пропущено. - Якщо після першого проходження є лише незначні проблеми, цикл повторюється ще раз із доданою незначною критикою.
complete, і результати перевірки публікуються.Розділення кроку атаки на окреме запитання з іншим запрошенням зроблено навмисно. Той самий модель у іншій ролі виявляє значну частину самопохвали, яку дозволяє проходження кількох запитань під час однієї ітерації. У одному випадку рівень впевненості знизився з 90 до 65 між перевіркою та її аналізом, причому рецензент вказав саме на те припущення, яке інвестигатор проігнорував.
Разом із ним проводиться ще один огляд, створений по-іншому: огляд червоної команди (G5). Оглядач-адверсар читає повний опис розслідування, тоді як оглядач червоної команди бачить лише початкове формулювання проблеми та кінцевий висновок. Він не отримує жодних проміжних міркувань, жодної послідовності доказів та інформації з попередніх етапів. На основі цих двох елементів він формує незалежну гіпотезу та позначає моменти, коли результати розслідування відрізняються від того, що міг би припустити сторонній спостерігач.
Метою є когнітивна ізоляція, а не додаткове покриття. Адверсарський огляд перевіряє, чи є розслідування структурно повним (D1 до D15). Команда «червона команда» перевіряє, чи можна було дійти висновку без певної структури розслідування: чи ґрунтується міркування справді на проблемі, чи розслідування спиралося на раннє висновок і дозволило висновку слідувати за цим висновком замість доказів? Обидва процеси виконуються як окремі запити до Claude, і обидва мають пройти перевірку, перш ніж справу можна буде позначити як COMPLETE.
П’ять чесних способів завершення
Цикл розслідування може завершитися з п’яти причин. Кожна з них фіксується та відображається оператору, як у таких рядках:
exit=complete confidence=89% iterations=12 elapsed=13min
exit=blocked confidence=62% iterations=4 elapsed=4min (cannot proceed without info)
exit=stalled confidence=58% iterations=7 elapsed=22min (two rounds, zero new facts)
exit=degrading confidence=42% iterations=5 elapsed=8min (dropped >15% twice)
exit=timeout confidence=73% iterations=18 elapsed=50min
Серед п’яти варіантів blocked — це той, на який оператор може вплинути найбільш прямо. Агент записує явні перешкоди у unchecked_sources або adjacent_problems, і людина мусить здійснити дзвінок, щоб їх усунути. Інші чотири — це моди технічних збоїв, які система виявляє та не намагається подолати сліпо.
Що може досягти Claude зсередини
Тут важливі межі інструментів, адже рівень впевненості частково залежить від того, до чого насправді може дістатися Claude. Умова, яка змушує агента перевіряти свої неперевірені джерела, не має значення, якщо відсутні інструменти для їх перевірки. Кожен потік запускає підпроцес Claude Code з параметром --permission-mode bypassPermissions. У запиті не вказується список дозволених інструментів. Межею є все, що включено до каталогу Model Context Protocol (MCP), який охоплює п’ять зовнішніх систем:
Кілька обмежень варто згадати, оскільки вони впливають на поведінку більше, ніж щось інше, зазначене в картці моделі:
- Інструментарій для досліджень прямо забороняє використання
Bash(--disallowedTools Bash), що запобігає ситуаціям конкуренції в git, адже два процеси Claude, які одночасно редагують одну робочу структуру, можуть пошкодити її стан. Інструментарій dev-agent дозволяє використовувати Bash, але він працює у ізольованій робочій структурі. - Інструменти для запису в Slack заблоковані під час кожного запуску інструментарію. Функції
send_message_to_selfтаedit_message_to_selfзаборонені, тому Claude не може самостійно публікувати повідомлення в Slack. Усе зовнішнє спілкування відбувається через полеdraft_responseу структурованому звіті про завершення, який система надсилає лише після перевірки умов завершення.
bypassPermissions на практиці не означає повної вільності. У кожному пайплайні запит на обробку містить жорсткі правила. Файл ANALYSIS.md можна редагувати, але message.json — ні. Зміни, зроблені поза директорією case_dir, автоматично скасовуються за допомогою git diff після виконання. Записи в продакшн-базі даних вимагають прямої підтвердження оператора відповідно до команди. Безпека забезпечується на рівні формування запитів та перевірки кордонів після виконання, а не на рівні виклику інструментів.search_datadog_services повертає помилку через відсутність прав, запит спрямовує агента на використання mcp__glean_default__search замість нього, використовуючи ключові слова з Jira як замінник. Ця ланцюгова структура альтернатив є частиною механізму дотримання стандартних процедур та не використовується під час виконання.tool_use у момент їх надходження та виконує до 10 одночасних операцій читання (запити до Datadog, пошук у Glean, читання файлів). Усе, що виконує записи — чи то Bash із побічними ефектами, зміни, коментарі MR чи записи до бази даних — виконується по одному. Таким чином, одна ітерація розслідування може запустити шість паралельних запитів до журналів, зберігаючи при цьому порядок записів.Один каркас, чотири розміри
Кожен конвеєр у цьому комплексі виконує певну версію того самого каркаса:
Процес розслідування у режимі 24/7 виконує до 20 ітерацій. Фаза аналізу завдання — до 8 ітерацій. Фаза реалізації виконує основну структуру один раз, але циклізує крок будування тричі. Dev-agent не має внутрішнього циклу та виконується один раз за кожне запускання, проте mr-monitor фактично переміщує ітерацію назовні: він створює нового dev-agent щоразу, коли CI зазнає невдачі, до трьох разів поспіль, перш ніж передати справу людині.
Ця однорідність є навмисною. Чотири цикли існують тому, що проблеми різні, проте вони використовують одну й ту саму мову: рівень впевненості — це число, результат виконання має назву, самокритика — це окремий виклик Claude, а ескалація означає додавання приватного повідомлення в Slack у темі, де почалася справа. Оператор вивчає цю структуру один раз та потім розпізнає її скрізь.
Шар самовдосконалення
Це завершує описаний раніше цикл, у якому інструмент коригує власний код під контролем людини. Для виконання завдань використовуються два цикли, які працюють у різних часових масштабах.
Цикл 1 виправляє проблеми окремо, по черзі, від невеликих недоліків до серйозних. Після завершення розслідування gap_analyzer шукає місця, де інструмент показав слабкі результати: пропущені джерела даних, неправильний вибір інструментів, помилки у потоці обробки, відсутні тести та погано сформульовані описи змін. Фільтри за категорією та рівнем впевненості скорочують кількість проблем, перш ніж вони потрапляють до finalize_case. Як тільки справа залишається нерозв’язаною протягом чотирьох годин, finalize_case запускає patch_suggester у спеціальному робочому середовищі та створює одну пропозицію змін до власної бази коду інструменту, що стосується файлів коду, шаблонів запитів чи розділів СП. Для об’єднання цих змін потрібна людина, а для їх написання — ні.
Цикл 2 посилює ефективність дій у різних ситуаціях, від моделей збоїв до пропозицій щодо стандартних процедур. Наприкінці кожної ситуації крок auto_retrospective фіксує, які моделі збоїв виникли, класифікуючи їх на три типи: збої під час збору даних (чи були проігноровані доступні джерела?), порушення стандартів якості (чи суперечила твердження про надійність наявним даним?) та збої у повноті аналізу (чи не припинялась розслідування первинної причини занадто рано?). Записи накопичуються за проектом у реєстрі ситуацій. Щотижня завдання cron під назвою knowledge-updater підраховує останні десять ситуацій, і коли якась з моделей збоїв досягає певного порогу, генерує пропозицію щодо стандартної процедури: 3 з 10 ситуацій призводять до пропозиції, а 6 — до попередження. Пропозиції є конкретними. Наприклад, «пропуск прямого запиту до БД» траплявся 3 рази цього місяця, тому тепер стандартна процедура вимагає виконання прямого запиту до таблиці перед будь-якими висновками щодо схеми. Люди переглядають їх та застосовують.
З цього випливає персоналізація. Стандартна процедура описує, як команда проводить розслідування, і спочатку є універсальною. З часом оновлювач бази знань створює профіль шаблонів невдач одного проекту на основі подібних випадків та їх повторень. Після 20–30 випадків стандартна процедура цього проекту більше не нагадує універсальну шаблонну форму: вона фіксує, які джерела даних постійно ігноруються, які інструменти використовуються неправильно та які підходи до розслідування регулярно не враховуються в цій конкретній базі коду. Проекти мають окремі стандартні процедури, тож помилки команди А впливають лише на правила команди А та ніколи не заражають команду Б.
Ще однією перевагою є довговічність. Вікно контексту існує лише протягом однієї сесії, тоді як об’єднана зміна коду має версіонування, проходить перевірку, тестування та залишається постійною. Оновлення СПО, після схвалення оператором, з’являється у кожному подальшому розслідуванні. Субстрат постійно вдосконалюється, тож кожен новий випадок обробляється за допомогою кращої системи, ніж попередня.
Рівень 3: стан, який зберігається після перезапуску
Оркестрація — це те, що виконується для кожної події. Персистентний стан — це те, що залишається після перезапуску процесу, перезавантаження машини чи переміщення оператора з ноутбука до хмарної ВМ. Система зберігає свій стан у трьох рівнях із суттєво різними властивостями:
Кеші у пам’яті є невеликими та можуть бути відновлені. Вони знаходяться у процесах опитування та підтримки живості та відновлюються з локального стану після перезапуску.
Локальний стан у файлах ~/.harness/*.json є операційною картою. Ключові файли:
task-thread-map.jsonвідповідає за зв’язок між значенням thread_ts у Slack та ідентифікатором JIRA, а також абсолютним шляхом доtask_dir.oncall_thread_map.jsonмає таку саму структуру для випадків розслідувань у режимі on-call.monitored-mrs.jsonмістить список MR, які були відкриті системою harness, разом із їхнім статусом у CI.patch-pr-state.jsonфіксує кінцевий статус кожного випадку (merged,no_gaps,failedтощо).oncall-state.jsonмістить прапорець kill-switch, про який йдеться у Рівні 6.socket-dedupзберігає часові позначки останніх повідомлень, які вже були оброблені у режимі Socket.
Цей шар належить одній машині та не є спільним. Він зберігається після перезавантаження, але якщо машина зупиняється, він зникає.
Стан синхронізований з Git є стабільним шаром. Робочі простори справ під адресою case-workspaces// фіксуються та додаваються до приватного репозиторія Git після кожної значущої зміни стану. Інша машина, яка клонує цей репозиторій, може продовжити ту саму розмову, що й забезпечує безперервність сеансу між машинами. Оператор може спілкуватися з системою з ноутбука чи віддаленої віртуальної машини розробника, не втрачаючи жодної інформації.
Консолідація пам’яті згодом стала третьою функцією для стану, синхронізованого за допомогою git. Довга розмова створює десятки файлів типу followup_transcript_*.md. Коли накопичується достатня кількість даних, підагент читає їх усі, переписує TASK.md, щоб він містив стислий опис ситуації, після чого робочий процес припиняє надсилати неопрацьовані транскрипти на подальші етапи. Якщо пропустити консолідацію, запити можуть перевищити обсяг контекстного вікна моделі. Її виконання також несе ризик надмірного стиснення та втрати інформації, що дійсно траплялося: документ об’ємом 14 КБ скоротився до 587 байтів. У результаті цього інциденту з’явилися три заходи захисту: шаблон запиту з позначкою "UPDATE, а не REWRITE", механізм контролю розміру вихідних даних, який забороняє записи об’ємом менше 50% від обсягу вхідних даних, та файл блокування для кожного окремого випадку, щоб два процеси консолідації не взаємодіяли одночасно.
Шлях пошуку від окремої теми у Slack до файлу TASK.md цього випадку є коротким, але варто його навести:
Обидві карти стану містять однаковий тип даних — абсолютний шлях до робочого простору справи. Будь-який процес рано чи пізно доходить до case_dir, і потім працює відносно нього. Коли кореневий директорій робочого простору було перейменовано в процесі реалізації проекту, один із скриптів міграції вирішив цю проблему, переписавши абсолютні шляхи в обох картах під час переміщення директорії.
Стабільний стан є основою. Наступне питання — що відбувається, коли операції, побудовані на цьому стані, зазнають невдачі, і відповідь полягає у тому, що система відновлюється після цього та не передає проблеми оператору.
Рівень 4: цикли, які самостійно виправляються
Це рівень, на якому система перестає бути просто інформатором та стає автономною. Вона містить три цикли, кожен з яких завершується без участі оператора.
Перші два є повністю автономними. Третій навмисно не є таким. Щоб оновити сеанс постачальника ідентичності, потрібен запит на додаткову верифікацію, пов’язаний із апаратним забезпеченням, тому агент зупиняється та просить у людини дію, вводячи 30-секундний процес у приватні повідомлення. Це є вбудованою людською участю за проектом, і жодних прогалин у функціоналі немає.
Ці цикли мають одну суттєву рису: кожен з них має ліміт на спроби та може визначати, що він вже зробив. Автоматичне виправлення в CI обмежене за кожним MR, тому три поспіль невдачі призводять до втручання оператора. Автоматична відповідь рецензента усуває дублікації за автором, тож коли останній запис бота в дискусії є новішим за запис рецензента, цикл залишається без дій, оскільки відповідь вже існує. Функція identity-provider дозволяє встановити термін схвалення у 15 хвилин. Якщо прибрати ці обмеження, цикли іноді виходять з-під контролю. На початковому етапі один MR із нестабільним тестом інтеграції прийняв 80 комітів типу „виправити CI“, які насправді нічого не змінювали, протягом одного пополудня, перш ніж хтось це помітив.
Відновлення після окремих операцій
Описані вище цикли автоматичного виправлення CI та відповідей рецензентів дозволяють відновити всі робочі процеси з початку до кінця. Під ними кожна примітивна операція, яка може зазнати невдачі (push, rebase, коментар у MR, скидання гілки), ґрунтується на окремій, меншій трьохшаровій структурі для самовідновлення кожної окремої операції. Вона відрізняється від трьохшарової моделі безпеки з Шаром 6, яка стосується обмежень, тобто того, що агент може робити. Ця модель стосується відновлення, тобто того, що відбувається, коли дозволена операція зазнає невдачі. Їх легко плутати, тому тримайте їх окремо.
L1 — це детерміністичний механізм захисту. Він базується на правилах та працює постійно, реагуючи менш ніж за секунду. Для операцій push використовується _pre_push_rebase(repo_path, branch): безпосередньо перед push харнес виконує git fetch origin та git rebase origin/. Якщо ребейс пройшов успішно, операція push може продовжитися. У разі конфлікту чи невдачі fetch L1 безпечно припиняє роботу, вказує файли з конфліктами та передає ситуацію на обробку L2. L1 не може діяти творчо навмисно. Правила залишаються достатньо простими для розуміння та достатньо дешевими, без витрати токенів, щоб застосовуватися до кожної операції.
L2 — це цикл автономного самовідновлення. Коли L1 не може вирішити проблему, система запускає обмежену сесію Claude (_self_heal_push_loop) та надає їй всю інформацію про збою: стан git, конфліктуючі файли, завдання, яке спонукало до дії, та критичні обмеження. Ніколи не виконуйте примусове додавання змін до захищеної гілки, ніколи не видаляйте недодані комітти та ніколи не обирайте сторону конфлікту сліпо. Три обмеження забезпечують безпеку:
- Обмежена кількість спроб. Кожен інцидент має не більше 3 спроб, а кожен етап роботи Claude має таймаут у 4 хвилини. Після кожної дії Claude інструмент знову виконує справжній
git push. Думка Claude щодо того, чи усунуто конфлікт, не має значення; єдиним критерієм є код вихіду 0 відgit push. Якщо наступний запуск повертає ненульове значення, цикл фіксує невдалий спроби, незалежно від того, наскільки впевнено Claude стверджував про успіх. - Поріг впевненості 70 та вище. Цей параметр запозичений без змін з конфігурації
oncall_runу процесі розслідування інцидентів. Claude оцінює свою ефективність від 0 до 100 балів за кожен етап, і якщо оцінка нижча за 70, він повинен припинити роботу замість того, щоб продовжувати дії. Оцінка нижче встановленого порогу не дозволяє йому впливати на робочу структуру даних.
git push із результатом 0 та ніколи не залежить від того, що Claude каже про себе. L2 не може обійти цей механізм; він може надати йому лише справжнє оновлення.L3 — це ескалація до оператора. Якщо L2 припинив роботу через низький рівень впевненості або вичерпав кількість спроб, система надсилає оператору приватне повідомлення з усіма даними L2, тобто кожною спробою, кожною дією, яку виконав Claude, кожним рейтингом впевненості та результатом команди git status. Оператор починає роботу з повністю інструментованого стану, а не з ситуації „щось пішло не так“. Часовий масштаб становить години, а не секунди.
Виділяються два принципи:
- L2 функціонує лише під час конкретної проблеми та не завжди увімкнений. L1 працює під час кожного запуску, оскільки його використання дешеве, тоді як L2 споживає токени та активується лише після невдачі L1, тож витрати залежать від рідкісних випадків, а не від звичайних.
- Абсолютною істиною є дана безпосередньо з системи. Незалежно від того, що Claude вважає про свою роботу, процес верифікації повторює реальну операцію: код вихіду
git pushдля запусків, фактичний тест для виправлення помилок та перевірка коду вихіду запуску для відповідей на пропозиції змін. Саморепорт агента ніколи не вважається абсолютною істиною, оскільки система знову перевіряє його проти реальної системи.
Цей самий обгорток підходить значно більше, ніж лише для виконання дій. Операція вважається придатною, коли для неї існують три елементи: просте вирішення на основі правил (L1), суворо обмежена спроба автоматичного виконання (L2) та чіткий спосіб передачі завдання людині (L3). Така трьохчастинна структура дозволяє відрізнити автономний цикл, який може відновитися після вже відомих помилок, від такого, який потребує участі людини щойно ситуація змінюється.
Рівень 5: чотири місця для перегляду того, що сталось
Інструмент записує дані у чотири системи для спостереження, причому кожна з них налаштована під різного користувача:
- Актуальні оновлення у Slack. Поки агент працює, повідомлення-замінник у потоці справи змінює статуси (
Reading TASK.md, потімSearching codebase, потімDrafting patch) та зрештою стаєDoneабоFailed: <reason>. Цей потоковий звітник редагує одне повідомлення безпосередньо, тож потоки не заповнюються зайвими даними. - Структуровані журнали у
~/.harness/logs/agent.logта~/.harness/logs/error.log. Кожне запускання, розподіл завдань у ланцюзі та спроба повторної обробки стають структурованими подіями, і саме ці файли використовуються для пошуку при необхідності діагностики.
~/.harness/logs/system.log для подій, що стосуються різних аспектів роботи, таких як завершення розгортання, дії системи моніторингу та статус синхронізації.Панель керування була навмисно не додана. Slack та grep виконують функції консолей. Панель керування стає третім інструментом для отримання актуальної інформації, і саме її інженери перестають читати першою, як тільки інформація у ній устаріває. Модель потокових повідомлень забезпечує достатню кількість операційних сигналів, тому ніхто не хотів додавати до неї інтерфейс користувача.
Спостереження за з’єднаннями MCP
П’ять зовнішніх серверів MCP (Slack, Datadog, Jira та Confluence, Glean, GitLab) є основою цієї системи. Кожен з них має власний токен OAuth, частоту оновлення та способи виявлення несправностей. Коли токен закінчується без помітки, сесія Claude починає відповідати «У мене немає доступу до цього інструменту», а оператор зовсім не бачить жодної помилки.
Невелика служба під назвою mcp-watchdog закриває цю прогалину. Кожні 10 хвилин вона перевіряє кінцеву точку стану кожного сервера MCP та намагається виконати бездіяльний запит до інструменту. Якщо це зазнає невдачі, вона робить наступне:
- Вона намагається тихо оновити токен.
- Якщо оновлення зазнає невдачі, вона надсилає оператору приватне повідомлення в Slack приблизно з текстом «Atlassian MCP потребує повторної автентифікації, натисніть тут».
- Після двох послідовних втрат з’єднання (
DISCONNECT_CONFIRM_THRESHOLD) вона підвищує рівень тривоги, додаючи реакцію:warning:до останнього заявлення.
Без цього циклу прострочений токен може бути сплутаний із ситуацією „агент сьогодні неспроможний“, що є типом збою, діагностика якого дуже складна, оскільки файл agent.log ніколи не вказує „токен прострочений“. У ньому зазначається лише „інструмент повернув порожній результат“. Розгляд стану автентифікації як важливого показника для моніторингу — це те, що слід було впровадити раніше.
Рівень 6: інтерфейс керування оператором
Це найбільш використовуваний інтерфейс, який спочатку був недостатньо розвинений. Кожна взаємодія оператора відбувається через один канал — особисті повідомлення, а команди поділяються на шість категорій:
- Завдання:
admin: task <jira>запускає весь процес аналізу, реалізації та подання змін, а додавання параметра--autoробить його автономним.
admin: oncall-run <url> розпочинає розслідування з потоку Slack, тоді як oncall-toggle увмикає або вимикає автоматичний моніторинг.admin: mr rescan знову перевіряє певний MR, а mr pause/resume увмикає або вимикає цикл автоматичних відповідей для цього MR.admin: oncall pause [duration] тимчасово призупиняє автоматичну диспетчеризацію, а oncall resume/status закінчує паузу або повідомляє про поточний стан справ.admin: finalize <case> об’єднує звіти про проблеми та створює PR із пропозиціями щодо вдосконалення самого інструментарію.admin: register-thread вручну пов’язує потік у Slack із конкретною справою, тоді як close-case позначає її як завершену.Найбільше часу знадобилося, щоб належним чином реалізувати функцію тимчасового призупинення. У першій версії все призупинялося, включаючи команди адміністратора, що унеможливлювало оператору, який призупинив систему, її відновити. У другій версії призупинення було занадто обмеженим, тому команда „зупинити шум“ не діяла. Текуща реалізація ґрунтується на одному точному розрізненні: призупинення змушує мовчати автоматичну розсилку, спричинену діями інших користувачів, але ніколи не впливає на власні команди адміністратора чи дії в межах потоку. У механізмі опитування місця розсилки діляться на дві групи. Одна група — це автоматична діяльність, спричинена іншими, і вона призупиняється під час тимчасового призупинення. Інша група — це власні наміри оператора, і вона залишається активною.
Калібрування відбулося складним шляхом. Невдовзі після випуску першої версії автоматична відповідь на коментарі рецензента мовчки запрацювала у іншому випадку та залишилася непоміченою, оскільки здавалося, що функція паузи діє. Нинішній дизайн розглядає паузу як контрольоване мовчання: система затихає, але зберігається можливість для оператора діяти.
На малюнку показано розподіл функцій:
Правило полягає у тому, що власні дії оператора ніколи не пригнічуються, а діяльність інших осіб блокується під час паузи. Саме цим керує параметр admin: oncall pause.
Ще два рівні безпеки
Кнопка вимкнення паузи є найбільш помітним елементом безпеки, хоча й не єдиним. Ще два механізми працюють без будь-якої конфігурації під час роботи системи: один — у коді, інший — через правила розгортання.
Список дозволених каналів у Slack заслуговує на більшу увагу, ніж здається. На ранньому етапі проекту майже сталася проблема через неправильно налаштований токен Slack, який міг писати у внутрішні канали команди. Агент міг би надсилати повідомлення безпосередньо колегам, обходячи оператора, і ніхто не зміг би відрізнити ці повідомлення від людських. Тепер кожне зовнішнє запитання типу chat.postMessage перевіряє свою адресатку за списком дозволених каналів перед надсиланням. Єдиним дозволеним адресатом є особистий приватний чат оператора, а також один жорстко закодований ідентифікатор каналу для розмови оператора з ботом. Якщо агент хоче зв’язатися з колегою, він передає контент оператору, який вирішує, чи надсилати його далі. Це архітектурне правило постійно доводить свою ефективність: агент може спілкуватися ззовні лише через поштову скриньку оператора.
Поза шістьма шарами: що робить це рішення придатним для використання в продакшені
Шість шарів є несучою конструкцією. Існує кілька механізмів, які не входять до цієї класифікації, але мають таку ж важливість. Вони дозволяють розрізнити пристрій, який ефективно функціонує під час демонстрацій, від того, який працює без перерв.
Направлення кількох проєктів з одного файлу YAML
Наразі на цьому пристрої працюють два проєкти, кожен зі своими правилами функціонування, і додавання третього не вимагає жодних змін у коді — лише новий запис у agent-config/projects.yaml:
projects:
team-a:
sop: "team-a/SOP.md"
kb: "team-a/KNOWLEDGE-BASE.md"
dfr_threads_dir: "team-a/threads"
task_dir: "task-workspaces"
slack_channels: ["support-channel-a", "support-channel-b"]
jira_prefixes: ["AAA", "BBB", "CCC"]
team-b:
sop: "team-b/SOP.md"
kb: "team-b/knowledge-base/kb.md"
dfr_threads_dir: "team-b/threads"
task_dir: "task-workspaces"
slack_channels: []
jira_prefixes: ["XX", "YY"]
Функція resolve_project() спрямовує кожен надійшлий сигнал згідно з пріоритетом: спочатку префікс Jira, потім канал Slack, а далі стандартний варіант. Кожен проєкт має свої власні файли SOP, файли KB та дерево каталогів, і механізм формування запитів для розслідувань автоматично вибирає відповідні файли SOP та KB залежно від проєкту, до якого припадає сигнал. Саме це дозволяє одній інстанції Harness обслуговувати дві команди з двома наборами правил.
Ця схема є універсальною. Будь-яка організація з кількома командами, що працюють над продуктами, може використовувати такий самий підхід: декларативна конфігурація без окремих гілок коду для кожного проєкту. Це також забезпечує чітку дисципліну, адже все, що є специфічним для певного проєкту, має знаходитися у його файлі SOP, а не в коді, оскільки код є спільним для всіх проєктів.
Відновлення сеансу на будь-якій машині
Оператор працює на двох пристроях — ноутбуці та довготривалій хмарній ВМ у центрі даних, — і одна сесія Claude має можливість відновлення на обох. Це складно, тому що Claude Code за замовчуванням зберігає стан сесії на локальному диску, тож після перезапуску в іншому місці сесію не можна побачити.
Ця система вирішує цю проблему за допомогою сховища сесій, підтримуваного git:
Механізм складається з трьох частин:
- Сесії відстежуються у git. Файли кожної сесії
.jsonlта.metaфіксуються та відправляються у приватний репозиторій після кожної значущої зміни стану, і саме цей репозиторій є джерелом правди. - Фоновий збиральник даних працює кожні 5 секунд на кожному пристрої, щоб підтримувати актуальність локальної копії. Він заздалегідь завантажує необхідний контекст, тож для відновлення сесії Claude не потрібно чекати на мережеву затримку після кожного натискання клавіші.
session-start.sh переглядає локальні файли .meta у пошуках запису, пов’язаного з цією робочою директорією, та бере його UUID. Таким чином один UUID супроводжує оператора між різними пристроями.Скрипт синхронізації user-prompt-submit.sh запускається після кожного запиту в Claude та виконує три операції у суворій послідовності, серіалізовані за допомогою flock:
- Отримання та скидання. Виконання команд
git fetch origin main && git reset --hardзалишає локальний файлactive.jsonlз транскриптом іншого пристрою. - Витягніть дельту перед перезаписом. Він читає водяний знак, який показує, скільки рядків вже було додано з іншої машини, витягує лише новіші рядки та зберігає їх у тимчасовому файлі. Це має відбуватися перед кроком 3. Раніше через помилку ці два етапи були поміняні, тож процес додавання продовжував читати власний запис машини.
- Надішліть свій власний запис. Він копіює локальний запис сесії Claude у файл
active.jsonl, робить коміт та надсилає його, а потім встановлює водяний знак за кількістю рядків у локальному файлі.
Після того, як система звільнить місце, дельта, збережена на кроці 2, надходить до Claude у вигляді системного повідомлення, яке інформує про те, що інша машина додала N повідомлень з моменту останньої синхронізації. Потім наступне запитання бачить контекст обох машин.
Окремий файл .linecount містить справжню загальну кількість рядків, оскільки команда wc -l дає хибні дані, якщо транскрипт було скорочено перед завантаженням. Щоб не перевищувати ліміт у 25 МБ для git, транскрипти обрізають до 1 000 рядків. Без файлу .linecount кожне скорочення скасовувало б попередній позначник кількості рядків, і система знову надсилала б тисячі вже переглянутих рядків.
Операція в git, серіалізована за допомогою flock, запобігає пошкодженню файлу index.lock внаслідок одночасних завантажень, що траплялося коли-небудь під час збігу початку застряглої сесії з виконанням процедури поглибленої обробки даних. Механізм обмеження швидкості, налаштовуваний за замовчуванням на 30 секунд, пропускає повну синхронізацію під час швидкого натискання клавіш, щоб уникнути затримок у роботі git для кожної окремої літери.
Ширша ідея, яку варто запозичити, — це використання git як сховища стану. Тут git виконує функцію більшого, ніж просто репозиторія коду. Він діє як стійкий журнал стану агента, схожий на CRDT, причому дві машини оператора виступають у ролі читачів з еволюційною консистентністю.
Консолідація пам’яті тісно пов’язана з безперервністю між пристроями. Крок memory_consolidate, який переписує транскрипти у чисті файли TASK.md та ANALYSIS.md, не виконується за таймером. Окремий сервіс consolidate_trigger перевіряє кожен етап на наявність чотирьох сигналів: SHA-хеш коміту або дієслова рішення в транскрипті, нещодавно закритої теми обговорення, робочого простору справи з накопиченим обсягом транскрипту понад 5 КБ або пройшлоє принаймні година з моменту останньої консолідації. Будь-який з цих сигналів може її запустити. Ідея полягає у тому, що інформація перестає накопичуватися після прийняття рішення, а не через певний проміжок часу, і ці чотири сигнали разом допомагають визначити, чи досягла розмова природної паузи, щоб оператору ніколи не доводилося це пропонувати.
Worktrees — це більше, ніж деталь реалізації
Раніше казали, що все відбувається у робочих деревах. Це потребує пояснення, адже вибір у цьому питанні має справжню вагу.
У початковому дизайні агент мав генерувати відмінності, а інструмент harness виконувати команду git apply у основному репозиторії. Це призводило до двох проблем, які постійно повторювалися:
- Нестабільні заголовки відмінностей. Claude генерував відмінності, у яких номери рядків були правильними на момент створення, але помилковими після подання іншого коміту, через що
git applyвідхиляв усю зміну. - Застрягле основне дерево. Якщо команда
git apply --checkзазнавала невдачі на півдорозі, основний репозиторій залишався наполовину обробленим, і наступна операція у цьому ж репозиторії успадковувала це „брудне“ дерево.
worktree_manager — це рішення проблеми. Перед будь-яким агентом, який має змінити код, він створює ізольовану робочу деревину у каталозі ~/.worktrees/case-<case_id>-<stamp>/, отриману з чистої гілки. Claude працює там за допомогою своїх вбудованих інструментів Edit та Write, що усуває необхідність використання git apply та подвійної обробки тексту різниць. Цей механізм функціонує як у людського інженера: створюється нова гілка, її отримують, потім вносяться зміни, робиться коміт та пуш.
Як тільки PR об’єднується або закривається, робоча деревина видаляється. Часткова невдача впливає лише на цю деревину, залишаючи основну версію коду та інші паралельні завдання недоторканими.
Саме це робить паралельну роботу агентів на одному комп’ютері можливою. Без окремих робочих деревин для кожного завдання два проекти в одному репозиторії конкурували б за стан файлової системи. З їх використанням завдання за своєю конструкцією ізольовані одне від одного.
Harness сам виправляє свої pull requests
Джерело цього харнесу знаходиться у приватному репозиторії GitHub. Коли процес gap-report, finalize та MR loop призводить до створення покращувального PR для самого харнесу, цей PR проходить через власний CI з використанням pytest, інструментів лінтингу та перевірки синтаксису Python. Окремий сервіс pr-ci-fixer обробляє будь-які помилки таким самим чином, як mr-monitor обробляє помилки у PR до тикетів.
У результаті код, написаний для тикетів, та код, пов’язаний із харнесом, мають здатність до самовідновлення. Припустимо, PR додає новий ліміт спроб та порушує існуючий тест, що призводить до невдачі CI. pr-ci-fixer це виявляє, запускає сесію Claude у робочому дереві, виправляє проблему та знову пускає код, щоб людський рецензент знайшов PR у статусі «зелений», готовий до об’єднання. Цей цикл вже неодноразово допомагав у виправленні змін до самого харнесу.
Направлення циклу на власне джерело харчування — це найкращий спосіб перевірити, чи заслуговує він на назву «продакшн». Агент, який може підтримувати свої власні запити на зміни у стані «зеленому», ймовірно, гідний довіри щодо чужих запитів.
П’ять рівнів тестування для п’яти класів помилок
У харчувальній системі є 4,656 функцій тестування у 126 файлах. Ця кількість мало що означає без фреймворку, який би визначав, що саме перевіряє кожен тест, адже тести не є однаковими.
Фреймворк від L0 до L4 присвоює кожному тесту один із п’яти рівнів залежно від того, що він ізолює:
- L0, чисті межі. Нічого не імітується, немає файлової системи чи введення-виведення. Він охоплює чисту логіку, таку як функції перекриття, вилучення даних у форматі JSON та арифметику, пов’язану з межею надійності; все, що пройде на рівні L0, буде функціонувати в будь-якому середовищі розгортання.
Тести L4g коштують найбільше у написанні, але їх зберігання має найвищу цінність. Вони фіксують інституційну пам’ять про способи виникнення помилок, які залишаються непомітними до тих пір, поки не стануться знову.
Керівництво зі стилю, яке запобігає тому, щоб результат був схожий на текст, написаний машиною
Кожен PR, який проходить через систему кріплення, проходить ще один фільтр перед тим, як його подати; існує спеціально розроблений стандарт дій, щоб результат не виглядав як продукт штучного інтелекту. Правила є чіткими:
- Заборонено будь-які посилання на штучний інтелект. Не допускається
Co-Authored-By: Claude, написи „Generated with Claude Code“ та суфікс[bot]у повідомленнях про зміни. PR належить команді, а не моделі. - Заборонені заголовки з пропусками для заповнення. Темплати у форматі „Що робить цей PR / чому він нам потрібен“ заборонені. Опис має бути безперервним текстом із конкретним списком
Changes:, написаним так, як це зробив би інженер вручну. - Заборонено жирний шрифт усередині тексту. Не допускається використання позначок на кшталт
Root cause:з наступним абзацем, а такожFix:таRisk:. Жирний шрифт у форматі тегів є найнадійнішим ознакою автоматичного написання.
pytest tests/test_foo.py::test_bar -v разом із справжньою командою та відповідними 3 рядками результату — так, це підходить.Коли складається опис змін, SOP вставляється у запит на створення патча. Він знаходиться окремо від шаблону запиту, оскільки його підготовлюють оператори: ви можете редагувати його коли завгодно, і наступні зміни будуть відповідати новим правилам без необхідності перезапуску демона чи зміни коду. Навіть механізм gap loop може пропонувати зміни до SOP, тож мета-правила щодо того, як агент складає зміни, також передаються у вигляді змін до файлу, який їх містить.
Це має більше значення, ніж здається. Ніщо так швидко не руйнує довіру до автоматизованої процедури MR, як коли команда помічає, що кожен опис схожий на той, який створений машиною. Як тільки утворюється таке враження, рівень складності зростає, справжні сигнали від оператора загублюються, і люди починають хотіти переписувати описи вручну, що суперечить початковій меті. Стандарти стилю забезпечують те, що описи поєднуються з тими, які написані людьми, і це дозволяє процесу продовжувати функціонувати.
Одна заява, один MR — від початку до кінця
Ось що відбувається, коли надходить справжня заява. Часи є приблизними.
У цілому цей цикл зазвичай завершується протягом 30–90 хвилин для невеликої чи середньої за складністю заяви. Загальний час активної участі оператора зазвичай становить менше 5 хвилин: підтвердження аналізу, за потреби ухвалення рішень щодо неоднозначних ситуацій та, за потреби, натискання кнопки об’єднання.
Кількість досліджень зростає. Після 30 випадків утворюється 30 файлів gap_report*.md, кожен з яких фіксує щось, що, на думку ШІ, могло б бути кращим. Кожні два тижні виконується команда admin: gap-patterns, яка групує їх за типами постійних проблем, а наступний етап обробки перетворює найбільші групи на зміни, спрямовані на репозиторій harness, що означає, що сам harness покращується. Це конкретна реалізація циклу самопокращення.
Як це порівнюється з іншими harnessами
Кілька інших систем мають схожу структуру harness. У таблиці показано, де цей harness збігається з ними та де відрізняється:
У чому цей harness виходить за межі звичайного:
- Блокування гіпотез перед їх перевіркою. Claude сформулює принаймні шість кандидатських гіпотез перед тим, як розпочнеться будь-яка ітерація дослідження, що запобігає раціоналізації фактів після їх виявлення. Жоден з комерційних інструментів у порівнянні не пропонує щось подібне.
- Фреймворк контролю якості з 19 функціями та механічними лімітами довіри. Рівень довіри обмежується фіксованою формулою, яка починається з 1,0 та зменшується на 0,08 за кожне нерозв’язане питання та на 0,05 за кожне неперевірене джерело, тому це більше, ніж просто самооцінка. Додаткові штрафи за порушення правил діють у кожному з трьох напрямків контролю: логічна послідовність, структурна повнота та ліміти тверджень. Жоден з комерційних інструментів у порівнянні не має такої функціональності.
oncall_run та проходить через case_followup, gap_analyzer, finalize_case та dev-agent. Кожен етап має визначену схему передавання завдань та окрему структуру роботи, тож робота розподіляється між агентами, а не покладається на одного з них.Де це навмисно спрощено:
- Немає формального інструменту оцінки. Рівень LLM не оцінюється за стандартними тестами, а оцінкою слугують повідомлення від користувачів у продакшені.
- Немає панелі керування — лише Slack та журнали подій.
- Немає інструменту для вимірювання витрат. Використання токенів відстежується неточно та не відображається на панелі керування; витрати обмежуються кроками підтвердження, які виконує людина.
Що залишилося та метапетля
Інструмент продовжує розвиватися: залишаються нерозв’язані завдання щодо покриття автономних циклів, вимірювання витрат та створення формального інструменту оцінки, причому жодне з цих завдань не є перешкодою. Він вже виконує свою основну функцію: перетворює сигнали з Slack у об’єднаний код, відповідає на повідомлення рецензентів протягом ночі, забезпечує самовідновлення у процесах CI та вносить власні покращення у наступну версію.
Справжнім уроком є мета-цикл. Кожна «шрам» у системі існує тому, що справжня проблема чинила на неї тиск. Створення та експлуатація — це один цикл, який працює з різною швидкістю. Сигнал від продукції повертається до власного джерела системи, агент розробляє виправлення, людина його перевіряє та приєднує, і ще один тип помилок наближається до усунення.
Система ще не готова. Продукційна система, яка вважає себе готовою, припиняє накопичувати «шрами», що означає, що вона більше не працює під справжньою навантаженням.
Спочатку створіть цикл, а вже потім зробіть його автономним. Автономна система без зворотного зв’язку у власну структуру лише швидше та у більшій кількості посилає ті самі помилки, тому порядок має значення.