Продакшн-харнес для Claude Code: шесть слоев от Slack до объединенного MR
Как шестислойный харнес превращает тикеты Slack в объединенный код: прием событий, циклы агентов с контролем доступа, устойчивое состояние, самовосстановление, возможности мониторинга и управление оператором.
Источники: Building a Production Agent Harness: Turning Claude Code Into a Multi-Agent Engineering Pipeline
Если поместить модель кодирования в изоляцию, получится «мозг в банке»: она отлично пишет код и вызывает функции, но не сможет прочитать ваше личное сообщение в Slack в 3 часа ночи, не попытается повторно запустить сборку, которая завершилась с ошибкой, не решит конфликт при слиянии, только что появившийся в открытом запросе на слияние, и не вспомнит, что рецензент вчера задавал вопрос, на который никто не ответил.
То, что превращает модель в полезный инструмент, — это специальная инфраструктура: среда выполнения, обеспечивающая «чувства», возможности действия и память. Её компоненты включают прием событий, оркестрацию агентов, сохранение состояния, циклы с самовосстановлением, возможности мониторинга и механизм управления со стороны человека-оператора. Описанная ниже система — это продакшн-решение, а не демо-версия для исследований. Она отслеживает канал в Slack, создает запросы на слияние в пяти внутренних репозиториях, отвечает на комментарии рецензентов ночью и самостоятельно устраняет ошибки в сборках, даже без прямых указаний.
Это внутренняя многоагентная система, построенная на Claude Code. Она работает непрерывно уже несколько месяцев, принимая задачи из Slack и направляя их на реальную инженерную обработку. Далее приведено общее описание: что она делает, как связаны её компоненты и какие особенности процесса повлияли на её дизайн.
Разделы описываются в следующем порядке: зачем вообще нужен такой инструмент, что делает конкретно этот инструмент, три проблемы, которые он решает, шесть уровней работы (прием событий, оркестрация агентов с упором на автономный цикл и уровень самосовершенствования, постоянное хранение данных, способность к самовосстановлению, возможности мониторинга и управление человеком), несколько дополнительных аспектов, полное описание процесса работы от начала до конца и сравнение с другими подобными инструментами.
Почему недостаточно просто чат-окна и нескольких инструментов
Когда в 2023 году появились плагины для чат-ассистентов, многие команды попробовали очевидный упрощенный подход: разместить интерфейс чата перед LLM, предоставить ему несколько инструментов для выполнения функций и назвать полученный результат «инженерным агентом». Такая конфигурация почти срабатывает в демо-версии, но никогда не выдерживает нагрузок в реальных условиях. Уже через неделю проявляются три основных проблемы.
- Контекст исчезает, когда оператор закрывает вкладку. Память должна сохраняться дольше одной сессии чата. Настоящая инженерная задача занимает дни: открытие тикета, создание ветки, разработка проекта, его проверка, ответы на комментарии, ожидание положительного результата тестирования и слияние изменений. Одного вызова модели недостаточно для обработки всего этого.
- Ничто не реагирует на изменения снаружи. Кто-то оставляет комментарий в 16:00, тестирование ломается после третьего коммита, коллега отвечает в дискуссии. Агент должен реагировать на эти события, а не постоянно выполнять запросы или ждать новых указаний от человека.
Задача инструмента-оболочки — устранять такие проблемы. Именно это отличает обычную вкладку чата от самого Claude Code, от фоновых агентов Cursor, от Devin или от описанной здесь системы. Модель, лежащая в основе, практически идентична у всех них; различия кроются в оболочке, которая её окружает.
Модель может взаимодействовать с внешним миром только через эту оболочку, и именно оболочка выполняет почти всю инженерную работу, необходимую для того, чтобы большая языковая модель вела себя как целая система.
Задача, для которой был создан инструмент
Исходная проблема была конкретной. Чат Slack с названием #support принимает около 30 заявок в неделю. Каждая из них требует расследования, а обычно после этого — изменения кода в одном из пяти репозиториев. Расследование затягивается на несколько дней. Рецензенты комментируют предложения по изменениям, процессы тестирования сбоят, а некоторые расследования оказываются повторением тех, что проводились в прошлом месяце. Команда, отвечающая за это бремя, постоянно получала уведомления вечером и в выходные, и ситуация не улучшалась, поскольку каждый цикл возникали одни и те же типы заявок.
Инструмент выполняет три функции, распределенные в зависимости от степени его вмешательства:
- Расследование. Когда в
#supportпоступает новая заявка, необходимо проследить за ходом обсуждения, собрать информацию из разных репозиториев и опубликовать структурированный анализ в том же чате.
Именно эта третья функция отличает его от простых ботов для обработки заявок. Основным «сырьём» для системы является её собственный исходный код, на который LLM может вносить изменения через запросы на слияние, проходящие проверку человека.
Система состоит из шести логических слоёв, каждый из которых рассматривается отдельно ниже.
Три пробела, которые обычная модель не может устранить
У всей этой конструкции есть одна основная идея: безстатусный, чисто реактивный LLM не может служить производственной системой. Шесть слоёв существуют для устранения трёх пробелов, которые модель не может преодолеть самостоятельно.
Первая проблема — реактивность. Модель не может самостоятельно «пробудиться». Эту задачу решает слой 1 (обработка событий), поскольку упомянутые Slack, результаты GitLab CI и страницы PagerDuty все поступают в одну очередь диспетчеризации. LLM никогда не выполняет запросы самостоятельно — его вызывают.
Вторая проблема — сохранение данных. Модели забывают всё между сессиями и теряют контекст при переносе работы между устройствами; кроме того, они не могут отличить ситуацию, когда проблема была решена, от ситуации, когда изменения действительно были отправлены на сервер. Эту проблему решает слой 3 (перманентное состояние): структуры в памяти устраняют дубликаты внутри одного процесса, локальные JSON-файлы хранят информацию о работе, а рабочие пространства, синхронизированные через git, обеспечивают долговременное сохранение состояния задач. Продолжительность сессии между устройствами, о которой будет рассказано позже, также основывается на том же принципе: состояние должно храниться в git, а не внутри работающего процесса.
Третья проблема — качество. При отсутствии структуры цикл рассуждений больших языковых моделей делает уверенные выводы на основе скудных доказательств, объявляет расследования завершенными до проверки всех доступных источников, а описания изменений выглядят так, будто их написала машина, уже с первого предложения. Уровень 2 (оркестрация агентов) и цикл расследований решают эту проблему с помощью структурированных контрактов вывода, проверки качества на каждой итерации, адверсарского анализа по окончании цикла и механизмов самовосстановления в рамках внешнего цикла изменений кода, тестирования и слияний.
Синергия связывает все уровни воедино, поскольку каждое закрытое дело ускоряет работу над следующим. Утвержденные расследования превращаются в записи в базе знаний. Утвержденные изменения становятся шаблонами, на которые может указывать анализатор пробелов. Завершенное дело ведет к созданию изменения, направленного на улучшение самого инструмента. С каждым циклом все меньше типов задач обрабатываются некорректно.
Уровень 1: как события пробуждают систему
Три внешних источника сигналов могут активировать систему:
- Сообщения в Slack, а именно упоминания в каналах, ответы в тредах и команды администратора, отправленные в личные сообщения самому себе.
- Действия в GitLab, а именно комментарии к заявкам на ревью, результаты пайплайнов и сбои в их работе.
- Уведомления от PagerDuty, используемые тогда, когда сотрудник дежурной группы упоминает заявку, которую система уже видела, чтобы можно было восстановить предыдущий контекст.
Эти три источника отличаются по задержке и надежности, поэтому у каждого из них есть свой собственный путь входа.
Два практических аспекта никогда не указываются в стандартной версии этой диаграммы.
Временная согласованность. Конечный пункт доступа conversations.history в Slack может отставать на период от 1 до 30 секунд. Запросчик, увидев пустой результат и наивно переместивший курсор вперед, навсегда пропустит любые сообщения, временная метка которых попадает в этот промежуток. В результате курсор застывает на пустых запросах, а следующая попытка начинается с той же самой старейшей временной метки, что повторяется до тех пор, пока не поступит хотя бы одно сообщение. Набор для удаления дубликатов на основе временной метки каждого сообщения фильтрует дубли, возникающие в результате этих повторных попыток.
Режим Socket в сочетании с механизмом опроса — это полная защита. Режим Socket обеспечивает получение ответов от потоков с задержкой менее секунды, но соединения WebSocket прерываются. Механизм опроса выступает в роли резервной сети, ловя всё то, что упустил режим Socket, причем оба метода используют файл socket-dedup, чтобы ни одно сообщение не было отправлено дважды. Без этого всё, что поступает во время прерванного соединения, исчезает бесшумно, без какого-либо сигнала оператору о том, что сообщение было упущено.
Цикл cron для mr-monitor ведет себя иначе. 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 для каждого дела он редактирует код, выполняет push, отслеживает процесс 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"
}
Вокруг блока может находиться проза свободного формата, но контрольные механизмы анализируют обработанный отчет, а файлы состояния сохраняют его. Замена части свободы в прозе на возможность парсинга контракта оказалась самым эффективным способом обеспечения пригодности выводов больших языковых моделей для использования в рамках пайплайна. Если пропустить схему, каждый последующий шаг будет вынужден извлекать факты непосредственно из прозы. Если же схема сохранена, контрольные механизмы превращаются в обычный детерминистичный Python.
Контрольные механизмы качества, придающие значимость уровню уверенности
Балл уверенности стал бы просто маркетинговым шумом, если бы Claude мог просто ввести confidence: 95 и продолжить работу. Поэтому результат каждой итерации проходит через контрольные механизмы качества, которые не позволяют продолжить анализ, пока доказательства и указанный уровень уверенности не совпадут.
После каждого шага Claude запускаются три группы контрольных механизмов:
- G-ворота проверяют логическую согласованность, чтобы рассуждения не противоречили сами себе между раундами. Самый яркий пример — G1, который активируется, когда количество
open_questionsувеличивается в течение раунда, ноconfidenceповышается, поскольку увеличение знаменателя нивелирует любое повышение значения. Эта группа также проверяет, что выражения напряженности в тексте регистрируются как противоречия, а соседние проблемы отслеживаются как объекты с действительным статусом, а не как временные строковые значения. Их четыре. - N-ворота проверяют структурную полноту, убеждаясь, что элементы полностью сформированы и не являются неполными структурами. N1 требует, чтобы каждое действие по устранению ошибок сопровождалось операцией
verify_action, содержащей описание, ожидаемый результат и сроки выполнения. Исправление, предложенное без возможности его проверки, считается незавершенной работой. Их три.
COMPLETE, пока соседние проблемы остаются нерешенными, требуют наличия непустого регистра предположений и обязывают к тому, чтобы хотя бы одно предложенное действие напрямую соответствовало корневой причине. Их всего семь.Выше них находятся два дополнительных контроля. EQ1 требует, чтобы узлы корневых причин ссылались на достоверные доказательства (любые, кроме INFERRED), иначе применяется лимит в 60%, а P7 снижает этот предел на 7% за каждую адверсарскую характеристику с результатом FAIL, пока он не достигнет минимума в 40%.
Всего в этой системе 19 функций. Удобный способ их запомнить: G-ворота реагируют на то, что пишет Claude, N-ворота — на то, что он опускает, а A-ворота — на то, что он утверждает.
Нарушение превращается в запись pending_guard_notes, которая помещается в начало запроса следующей итерации под заголовком ## SYSTEM QUALITY GUARD NOTES (from previous iteration) с четким указанием обработать каждую запись в первую очередь. Именно это и является механизмом принуждения. «Ворота» не блокируют модель; они заставляют её сначала рассматривать свои предыдущие нарушения в следующем раунде. Вежливость со стороны модели не имеет значения, а именно структурное обеспечение принуждения способствует продвижению расследования.
Шаг фиксации контекста (A7) также выполняется в конце каждого формирователя запросов, независимо от того, нарушены ли какие-либо правила. Он вновь присоединяет нерешенные противоречия и непроверенные предположения из текущего отчета, слово в слово, в конец следующего запроса. Это противодействует известному явлению ухудшения внимания модели, при котором она замечает сложность на третьем этапе, но к седьмому этапу уже игнорирует ее.
Уровень уверенности в виде конкретного числа
Самым важным аспектом дизайна является то, что агент присваивает себе значение confidence: в шкале от 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 равно ложному. Это указание на симптомы без выявленной коренной причины. Он позволяет выполнить ещё до 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 или запись в базу данных, выполняются по одной. Таким образом, одна итерация расследования может запустить шесть параллельных запросов к логам, сохраняя при этом порядок операций записи.Одна структура, четыре размера
Каждый пайплайн в этой системе использует какую-то версию одной и той же структуры:
Процесс расследования в режиме дежурства выполняется до 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 — к уведомлению. Предложения формулируются конкретно. Например, если в этом месяце трижды возникал сбой «пропущен прямой запрос к БД», то теперь стандартная процедура требует выполнения прямого запроса к таблице перед любыми выводами о структуре данных. Люди проверяют эти предложения и применяют их.
Эм, здесь ничего не применяется автоматически по замыслу, поскольку автоматизированные решения, умножающие собственные ошибки, представляют известную опасность в системах саморедактирования.
Из этого следует персонализация. СПО содержит описание того, как команда проводит расследования, и изначально оно является универсальным. Со временем обновлитель базы знаний формирует профиль моделей сбоев конкретного проекта на основе имеющихся случаев и повторений. При 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хранит флаг отключения, описанный в разделе Layer 6.socket-dedupсодержит временные метки недавних сообщений, уже обработанных в режиме Socket Mode.
Этот слой принадлежит одной машине и не является общим. Он сохраняется после перезагрузки, но исчезает, если машина выходит из строя.
Состояние, синхронизированное с Git — это надежный слой. Рабочие пространства дел по пути case-workspaces// фиксируются и загружаются в частный репозиторий Git при каждом значимом изменении состояния. Вторая машина, клонирующая этот репозиторий, может продолжить ту же конверсацию, что обеспечивает преемственность сессии между машинами. Оператор может общаться с системой с помощью ноутбука или удаленной виртуальной машины разработчика, не теряя ничего.
Позже консолидация памяти стала третьей функцией для состояния, синхронизируемого через git. Длительный диалог приводит к созданию десятков файлов followup_transcript_*.md. Когда накапливается достаточное количество данных, вспомогательный агент читает их все, переписывает TASK.md, чтобы сохранить сжатое состояние, после чего рабочий процесс прекращает подачу необработанных транскриптов на последующие этапы. Игнорирование консолидации может привести к тому, что объем запросов превысит размер окна контекста модели. Ее выполнение также несет риск чрезмерного сжатия, в результате которого теряется информация; это действительно происходило: документ объемом 14 КБ сократился до 587 байт. В результате этого инцидента были разработаны три меры защиты: шаблон запроса с указанием «UPDATE, а не REWRITE», механизм контроля размера вывода, запрещающий запись данных объемом менее 50% от размера входных данных, и файл блокировки для каждого случая, предотвращающий одновременную работу двух процессов консолидации.
Путь отдельной темы в Slack до файла TASK.md соответствующего случая невелик, но его стоит показать:
В обоих файлах состояния хранится один и тот же тип данных — абсолютный путь к рабочей области дела. Любой рабочий процесс рано или поздно обращается к case_dir, после чего работает относительно него. Когда корень рабочей области был переименован на протяжении реализации проекта, один скрипт миграции справился с этим, переписав абсолютные пути в обоих файлах при одновременном перемещении каталога.
Стабильное состояние является основой. Следующий вопрос заключается в том, что происходит, когда операции, основанные на нем, терпят неудачу; ответ заключается в том, что система восстанавливается после этих сбоев и не передает их оператору.
Слой 4: циклы, которые самостоятельно восстанавливаются
На этом слое система перестает выполнять функцию уведомителя и становится автономной. Здесь имеются три цикла, каждый из которых завершается без участия оператора.
Первые два являются полностью автономными. Третий преднамеренно не является таковым. Для обновления сессии поставщика идентификаторов требуется запрос на двухфакторную аутентификацию, зависящий от оборудования, поэтому агент останавливается и запрашивает действие человека, вводя 30-секундный процедурный план в личные сообщения. Это предусмотренное решение с участием человека, и при этом нет никаких пробелов в функциональности.
У этих циклов есть одна сходная особенность: у каждого из них есть лимит попыток и возможность определять, что он уже сделал. Автоматическое исправление в CI ограничено по каждому MR, поэтому три последовательных сбоя приводят к вмешательству оператора. Автоматический ответ рецензента устраняет дублирование по автору, что означает, что если последнее сообщение бота в обсуждении новее, чем сообщение рецензента, цикл остаётся без действий, поскольку ответ уже существует. Вспомогательный инструмент для поставщиков идентификации позволяет использовать окно одобрения в 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. Если следующая попытка завершается с кодом, отличным от 0, система записывает об ошибке, независимо от того, насколько уверенно Claude заявлял о успехе. - Порог уверенности 70 и выше. Этот параметр взят без изменений из конфигурации
oncall_runпайплайна расследования инцидентов. Claude оценивает свою уверенность в диапазоне от 0 до 100 за каждый цикл; при оценке ниже 70 он обязан прекратить действия вместо того, чтобы продолжать их. Оценка ниже установленного порога не позволяет ему вносить изменения в рабочую дерево проекта.
git push с кодом выхода 0 и никогда не связан с тем, что говорит Claude о себе. L2 не может обойти этот механизм; он может предоставить ему только реальный пуш-запрос.L3 представляет собой эскалацию к оператору. Если L2 отказался продолжать из-за низкой уверенности или исчерпал количество попыток, система отправляет оператору личное сообщение со всей информацией о действиях L2 — каждой попыткой, каждым действием, совершенным Claude, каждой оценкой уверенности и результатом команды git status. Оператор начинает работу с полностью задокументированной ситуацией, а не с информацией о том, что «что-то пошло не так». Временной масштаб составляет часы, а не секунды.
Выделяются два принципа:
- L2 используется только при каждом инциденте и не работает постоянно. L1 запускается при каждом операторе push, так как его использование дешево, в то время как L2 тратит токены и активируется только после сбоя L1; поэтому затраты зависят от редких случаев, а не от обычных.
- Истинные данные поступают непосредственно из системы. Независимо от того, что Claude считает о своей работе, процесс верификации повторяет реальную операцию: код выхода команды
git pushдля операторов push, реальный запуск теста для исправлений и проверка кода выхода оператора push в рамках ответов на предложения по улучшению. Самоотчет агента никогда не рассматривается как истинные данные, поскольку система снова проверяет его против реальной системы.
Этот же контейнер подходит гораздо шире, чем только для выполнения команд. Операция считается пригодной, если для нее существуют три элемента: простое решение на основе правил (L1), строго ограниченная попытка повтора с участием агента (L2) и четкий способ передачи задачи человеку (L3). Такая трехчастная структура позволяет отличить автономный цикл, способный восстанавливаться после известных ему сбоев, от цикла, который требует вмешательства человека при малейших изменениях в окружающей среде.
Слой 5: четыре источника информации о произошедшем
Инструмент записывает данные в четыре источника информации, каждый из которых настроен для разных пользователей:
- Актуальные обновления в Slack. Пока агент работает, сообщение-заглушка в потоке дела постепенно меняет статусы (
Чтение TASK.md, затемПоиск в кодовой базе, потомСоздание патча) и в конце получает статусГотовоилиНе удалось: <причина>. Этот потоковый отчетчик редактирует сообщение непосредственно на месте, поэтому потоки не заполняются ненужным шумом. - Структурированные логи в файлах
~/.harness/logs/agent.logи~/.harness/logs/error.log. Каждое запускание, отправка в цепочку обработки и попытка повтора превращаются в структурированное событие, и именно эти файлы используются для диагностики при возникновении проблем.
~/.harness/logs/system.log для событий, касающихся различных аспектов работы, таких как завершение развертывания, действия watchdog и статус синхронизации.Панель управления была намеренно не включена. В качестве консолей используются 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 сначала проверяется ее пункт назначения по списку разрешенных каналов. Единственными допустимыми пунктами назначения являются личные сообщения оператору и один жестко заданный ID канала для общения оператора с ботом. Если агент хочет связаться с коллегой, он передает содержимое обратно оператору, который решает, следует ли его пересылать. Это архитектурное правило по-прежнему оказывается эффективным: агент может общаться снаружи только через почтовый ящик оператора.
За пределами шести слоев: что делает систему готовой к использованию
Шесть слоев представляют собой несущую конструкцию. Существует несколько механизмов, которые находятся вне этой классификации, но имеют не меньшее значение. Они позволяют отличить харнес, который эффективно демонстрирует свои функции, от того, который работает без перебоев.
Маршрутизация нескольких проектов из одного файла 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 обслуживать две команды с двумя наборами правил.
Эта модель универсальна. Любая организация, имеющая несколько команд по разработке продуктов в рамках одного 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--/, взятую с чистой ветки. Claude работает там с использованием своих встроенных инструментов редактирования и записи, что позволяет избежать использования команды git apply и дополнительной обработки текста различий. Этот механизм функционирует как у человека-инженера: создается новая ветка, она берется для работы, затем происходит редактирование, коммит и пуш изменений.
Как только предложение по улучшению кода объединяется или закрывается, рабочая дерево удаляется. Частичная ошибка влияет только на это рабочее дерево, не затрагивая основную ветку и другие одновременно выполняемые задачи.
Именно это делает возможным безопасную работу нескольких агентов на одном компьютере. Без отдельных рабочих деревьев для каждого случая две задачи в одном репозитории могли бы конкурировать за состояние файловой системы. С использованием таких рабочих деревьев задачи по своей структуре изолированы друг от друга.
Harness сам исправляет свои запросы на пул-реквесты
Исходный код ремня безопасности хранится в приватном репозитории GitHub. Когда процесс подготовки отчета о пробелах, окончательной доработки и цикла MR приводит к созданию улучшения для самого ремня безопасности, это улучшение проходит через собственную систему CI, включающую pytest, инструменты проверки стиля и проверку синтаксиса Python. Отдельный сервис pr-ci-fixer обрабатывает любые сбои в этом процессе таким же образом, как mr-monitor обрабатывает сбои в MR по тикетам.
В результате код, написанный для тикетов, и код, связанный с ремнем безопасности, обладают способностью к самовосстановлению. Предположим, что MR добавляет новый лимит попыток и нарушает существующий тест, из-за чего происходит сбой в CI. pr-ci-fixer обнаруживает это, запускает сессию Claude в рабочей директории, исправляет проблему и снова делает пуш, так что человек-рецензент находит MR в состоянии «зеленый», готовый к слиянию. Этот цикл уже неоднократно закрывался при реальных изменениях в ремне безопасности.
Проверка того, соответствует ли цикл собственным стандартам инструментария, является лучшим способом определить, заслуживает ли он названия инструмента для производственного использования. Агент, способный сохранять свои запросы на изменения в активном состоянии, скорее всего, годится для работы с чужими запросами.
Пять уровней тестирования для пяти категорий сбоев
В инструментарии имеется 4,656 тестовых функций в 126 файлах. Однако эта цифра мало что значит без структуры, определяющей, что проверяет каждый тест, поскольку тесты не одинаковы.
Структура от L0 до L4 распределяет каждый тест по одному из пяти уровней в зависимости от того, что он проверяет:
- L0 — чистые граничные условия. Ничего не имитируется, отсутствуют файловая система и ввод-вывод. Он охватывает чистую логику, такую как функции управления, извлечение данных в формате JSON и вычисления, связанные с уровнем надежности; всё, что проходит на уровне L0, будет работать в любой среде развертывания.
Тесты L4g требуют наибольших усилий для написания, но их поддержка имеет наибольшую ценность. Они содержат информацию об особенностях возникновения сбоев, которая остаётся незаметной до тех пор, пока сбой не произойдёт снова.
Руководство по стилю, предотвращающее выглядение текста как машинного
Каждый предложенный для рассмотрения изменения проходит через ещё один фильтр перед тем, как быть представленным, причём существует специально разработанный стандарт операционной процедуры, направленный на то, чтобы результат не выглядел как продукт ИИ. Правила очень чёткие:
- Абсолютно никаких упоминаний ИИ. Никаких обозначений вида
Co-Authored-By: Claude, никаких фраз «Сгенерировано с помощью Claude Code», а также никаких суффиксов[bot]в сообщениях об изменениях. Изменения принадлежат команде, а не модели. - Запрещены заголовки с пробелами для заполнения. Такие шаблоны, как «Что делает это изменение / почему оно нам нужно», запрещены. Описание должно представлять собой непрерывный текст с конкретным списком пунктов
Changes:, написанным так, как это сделал бы инженер вручную. - Запрещены жирные шрифты внутри текста. Обозначения вида
Root cause:с последующим абзацем, а такжеFix:иRisk:также запрещены. Использование жирного шрифта в формате тегов — это самый надёжный признак автоматического создания текста.
pytest tests/test_foo.py::test_bar -v с реальной командой и соответствующими тремя строками вывода.В момент составления описания патча в запрос на генерацию патча вставляется SOP. Он хранится отдельно от шаблона запроса, поскольку операторы сами его управляют: вы можете редактировать его в любое время, и следующий патч будет соответствовать новым правилам без необходимости перезапуска демона или изменения кода. Даже цикл обработки пробелов может предлагать изменения к SOP, поэтому метаправила относительно того, как агент составляет патчи, передаются в виде патчей к файлу, в котором они хранятся.
Это важнее, чем кажется. Ничто так быстро не разрушает доверие к автоматизированной системе обработки изменений, как осознание командой того, что все описания написаны машиной. Как только сложится такое впечатление, уровень риска слияния увеличивается, истинные сигналы от операторов теряются, и они начинают переписывать описания вручную, что противоречит цели системы. Стандартные процедуры стиля помогают сохранять описания в стиле человеческих текстов, что обеспечивает нормальную работу всей системы.
Одна заявка, одно изменение — от начала до конца
Вот что происходит, когда поступает реальная заявка. Указанные времена являются приблизительными.
В среднем весь процесс занимает от 30 до 90 минут для небольших или средних заявок. Общее время активного участия оператора обычно составляет менее 5 минут: подтверждение анализа, при необходимости принятие решений по неоднозначным случаям и, при желании, нажатие кнопки слияния.
Постепенно накапливаются результаты исследований. После обработки 30 случаев формируется 30 файлов gap_report*.md, в каждом из которых указано то, что, по мнению большой языковой модели, можно было улучшить. Каждые две недели выполняется команда admin: gap-patterns, которая группирует эти данные по повторяющимся моделям сбоев, а последующий этап преобразует наиболее крупные группы в патчи для репозитория harness, что позволяет самому harness улучшаться. Это и есть осязаемая реализация цикла самосовершенствования.
Как это сравнивается с другими harnessами
Несколько других систем имеют схожую структуру с harnessом. В таблице показано, в чем они совпадают с ним и в чем отличаются:
В том, что этот harness выходит за рамки обычных решений:
- Блокировка гипотез до начала исследования. Claude сформулирует как минимум шесть кандидатских гипотез до начала любой итерации исследования, что препятствует рационализации фактов впоследствии. Ни один из рассматриваемых коммерческих инструментов не предлагает аналогичной функции.
- Платформа контроля качества с 19 функциями и жесткими лимитами уверенности. Уровень уверенности ограничивается фиксированной формулой: он начинается с 1,0 и снижается на 0,08 за каждый нерешенный вопрос и на 0,05 за каждый непроверенный источник, поэтому это более чем просто самооценка. К этому добавляются дополнительные штрафы за нарушения в каждой из трех категорий: логическая последовательность, структурная полнота и лимиты утверждений. Ни один из коммерческих инструментов не обеспечивает подобного контроля.
oncall_run до case_followup, gap_analyzer, finalize_case и dev-agent. Каждый этап имеет определенную схему передачи задач и изолированную структуру работы, благодаря чему задачи распределяются между агентами, а не ложатся на одного из них.Где функционал намеренно упрощен:
- Не существует формального инструмента оценки. Работа большой языковой модели не оценивается по стандартным тестам, в качестве метрик используется обратная связь от пользователей в производственной среде.
- Отсутствует панель управления — есть только Slack и логи.
- Нет инструмента для отслеживания затрат. Использование токенов отслеживается косвенно и не отображается на панели управления; затраты ограничиваются шагами подтверждения, выполняемыми человеком.
Что осталось, и мета-цикл
Инструментарий по-прежнему развивается: есть нерешенные вопросы, связанные с покрытием автономных циклов, отслеживанием затрат и созданием формального инструмента оценки, но ни один из этих моментов не является препятствием. Он уже выполняет свою основную функцию: преобразует сигналы из Slack в готовый код, отвечает на замечания рецензентов в течение ночи, обеспечивает самовосстановление в процессах CI и включает полученные улучшения в следующую версию.
Настоящий урок заключается в мета-цикле. Каждый «шрам» на системе появляется из-за того, что реальный запрос оказал на неё давление. Процессы разработки и эксплуатации представляют собой один цикл, работающий со скоростями, отличающимися друг от друга. Сигналы из производственной среды возвращаются к источнику системы, агент готовит исправление, человек его проверяет и объединяет с другими изменениями, в результате чего ещё одна группа ошибок приближается к устранению.
Система ещё не готова. Производственная система, считающая себя завершённой, прекращает накапливать «шрамы», что означает, что она больше не работает под реальной нагрузкой.
Сначала создайте цикл, а уже затем сделайте его автономным. Автономная система без пути обратной связи в собственную структуру лишь быстрее и в большем количестве передаёт те же ошибки, поэтому порядок действий имеет большое значение.