В LLM-инженерии постепенно меняется объект оптимизации.

Сначала подбирали промпты. Потом настраивали RAG. Параллельно тюнили модели под конкретные задачи и домены, подбирали грамматики, засовывали модель в цикл.

И вот появилось модное слово harness — по сути, сборная солянка из всего, что не LLM: тулы, MCP, память, агентные workflow, guard rails, record/replay-механики, механизмы компакции, маскирование, сабагенты, скиллы и много чего ещё. В попытках систематизировать весь этот зоопарк технологий был разработан интерактивный mindmap, который доступен для всех желающих.

Следующий логичный шаг — оптимизировать harness целиком: не только промпты или top-k в retriever, не только веса модели, а весь исполняемый runtime, в котором действует модель.

В литературе встречаются названия типа compound AI systems optimization или meta-harness optimization — оптимизация AI-систем, состоящих из нескольких взаимодействующих компонентов, а не из одного вызова модели.

Мы с командой не ограничились чтением статей, а разработали небольшой бенчмарк с записью работы СУБД и MCP-тулов в реальных нагрузочных кейсах и с последующим ускоренным Replay этой записи на диагностическом агенте с целью оптимизации его harness. Саму оптимизацию проводили через циклическое генетическое сэмплирование и выбор наилучшего варианта harness посредством парето-оптимизации.

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

  • в первом запуске: 0.478 -> 0.597, то есть +24.9% к baseline;

  • во втором запуске: 0.591 -> 0.695, то есть +17.6% к baseline.

Тут интересна даже не сама метрика, а то, какие изменения нашёл оптимизатор. Он не просто переписывал промпты. В удачных кандидатах он начал менять этапность workflow, доступные MCP-профили и процесс сбора доказательств как отдельный этап формирования AI-вердикта. Это уже похоже не на prompt-engineering, а на маленький AutoML для agent harness.

1. Минимальная формализация

Пусть есть модель:

M_\phi

где \phi — веса модели.

Вокруг неё есть harness:

H_\theta

где \theta — всё, что мы можем менять без дообучения модели:

\theta = { \theta_{prompt}, \theta_{context}, \theta_{rag}, \theta_{tools}, \theta_{workflow}, \theta_{memory}, \theta_{verifier}, \theta_{judge}, \theta_{safety} }

Большинство этих параметров нечисловые (это важно в плане отсутствия возможности градиентной оптимизации параметров). Они могут представлять собой markdown, Python, YAML, JSON schemas, описания тулов, MCP-профили, порядок и состав этапов, разные агентные циклы, политики compaction/masking и так далее.

На шагеt harness строит контекст:

p_t = C_\theta(x, h_t, s_t)

где:

  • x — исходная задача;

  • h_t — история сообщений, вызовы тулов и разные observations;

  • s_t — состояние среды (например, в случае анализа СУБД: телеметрия, snapshots, различные отчёты, query fingerprints);

  • C_\theta — context builder.

Модель порождает действие:

a_t \sim M_\phi(\cdot \mid p_t)

Действие может быть финальным ответом или вызовом инструмента.

Среда возвращает новый observation:

o_{t+1} = E_\theta(a_t, s_t)

И получается траектория:

\tau = {(p_t, a_t, o_{t+1}, s_{t+1})}_{t=0}^{T}

В случае с обычным single-turn LLM-приложением нас часто интересует только финальный ответ. Для агентной системы этого, очевидно, мало. Тут важно оценивать ещё и путь:

  • вызвал ли агент нужные тулы;

  • не пропустил ли negative evidence;

  • не сделал ли неподтверждённых выводов;

  • не перепутал ли причинно-следственную связь;

  • нашёл ли root cause;

  • не ушёл ли в слишком долгий цикл решения и вызова тулов;

  • не сломал ли JSON.

Поэтому objective лучше писать как функцию от траектории:

J(\theta; \phi) = \mathbb{E}_{\tau \sim P({\theta,\phi})} [ R_{final}(\tau) + \alpha R_{process}(\tau) + \beta R_{evidence}(\tau) + \gamma Latency(\tau) ]

Или чуть более наглядно в виде схемы:

Схема сбора данных для meta-harness оптимизации
Схема сбора данных для meta-harness оптимизации

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

2. Prompt/RAG optimization как первый симптом

Prompt optimization был первым массовым проявлением этой идеи.

OPRO

OPRO предложил использовать LLM как optimizer: мы даём модели описание задачи, историю предыдущих кандидатов и их оценки, а модель предлагает новых кандидатов. После оценки лучшие попадают в следующий раунд.

Условно:

archive = []

for step in range(num_steps):
    prompt_candidates = llm.generate(
        task_description=task,
        previous_attempts=archive,
    )

    for p in prompt_candidates:
        score = evaluate_prompt(p, dev_set)
        archive.append((p, score))

best_prompt = max(archive, key=lambda x: x.score)

То есть мы больше не пишем сами "please think step by step", "you are super expert" или иную ересь, за нас это теперь делает LLM. И тут уже работает неградиентная оптимизация по дискретному пространству текстов.

TextGrad

TextGrad предложил ещё более интересную аналогию: текстовый фидбек как «градиент» для compound AI system. LLM-critic генерирует natural-language feedback, который распространяется назад по computation graph и улучшает промпты, сниппеты и другие промежуточные компоненты.

Градиент здесь работает именно как метафора: LLM даёт семантически богатый фидбек, который потом учитывается на каждом этапе. Если критик говорит «ответ плохой, потому что агент не проверил negative evidence», это гораздо полезнее, чем просто score = 0.42.

GEPA

Из статей по оптимизации промптов GEPA (Genetic-Pareto reflective prompt optimizer) выделяется сочетанием нескольких идей:

  1. Genetic evolution: кандидаты мутируют и рекомбинируются как в классических генетических алгоритмах.

  2. Reflection: LLM анализирует траектории и формулирует, что именно пошло не так.

  3. Pareto selection: сохраняются кандидаты, которые хороши по разным осям.

GEPA позиционируется как оптимизатор для compound AI систем. Авторы пишут, что он использует траектории, вызовы тулов и их результаты, отражает ошибки на естественном языке, предлагает изменения промптов и использует Pareto frontier.

Схематично:

archive = initialize_prompts()

for generation in range(G):
    rollouts = evaluate(archive, tasks)

    reflections = [
        llm_reflect(trace=trace, score=score)
        for trace, score in rollouts
    ]

    mutants = [
        mutate_prompt(parent.prompt, reflection)
        for parent, reflection in select_parents(archive, reflections)
    ]

    evaluated = evaluate(mutants, tasks)

    archive = pareto_select(
        archive + evaluated,
        objectives=[
            "task_score",
            "robustness",
            "-latency"
        ],
    )

Тут важно вот что, если optimizer видит только:

{"candidate": "c_009", "score": 0.695}

он почти ничего не понимает.

Если он видит:

{
  "candidate": "c_009",
  "score": 0.695,
  "trace": [
    {"stage": "recent_context", "tools": ["get_snapshot", "get_query_fingerprints"]},
    {"stage": "query_regression", "tools": ["explain_query_fingerprint"]},
    {"stage": "waits_replication", "tools": ["get_wait_event_breakdown"]},
    {"judge_comment": "good OLTP recovery, missed backend_xmin evidence in sleep_blocking"}
  ]
}

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

AutoRAG

RAG-оптимизация тоже прошла похожий путь.

В простом варианте мы выбираем embedding model, chunk size, top-k и reranker. AutoRAG формализует это как автоматический подбор комбинации RAG-модулей под конкретный dataset. AutoRAG можно воспринимать как ранний прототип meta-harness: он оптимизирует в основном retrieval pipeline, но общая идея та же: мы ищем лучшую конфигурацию системы вокруг модели на базе верифицируемой метрики.

3. Meta-harness: оптимизация агентной среды

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

AFlow формализует работу агента как граф: в узлах находятся вызовы модели, инструменты, проверки и промежуточные преобразования данных, а рёбра задают порядок выполнения. Дальше оптимизатор не просто переписывает промпты, а меняет сам граф: добавляет или убирает шаги, переставляет их местами, меняет связи между ними и проверяет, стало ли решение лучше. Для поиска используется MCTS — метод, который часто применяют там, где нужно исследовать большое пространство вариантов и постепенно смещаться в сторону более перспективных веток.

ADAS смотрит на задачу шире. Здесь агентная система задаётся кодом, а отдельный meta-agent пробует проектировать новые варианты таких систем: с другими промптами, другим порядком рассуждения, другим использованием инструментов и другим управляющим циклом. Это похоже на переход от ручного выбора «ReAct или planner?» к автоматическому поиску по пространству возможных агентных архитектур.

Meta-Harness делает следующий шаг: ищет лучший вариант, модифицируя исходный harness-код. В одноимённой работе harness определяется как код, который решает, что хранить, что доставать и что показывать модели; система использует agentic proposer, который имеет доступ к исходному коду агента, оценкам и траекториям предыдущих кандидатов через персистентное хранилище.

AgentSquare вводит модульный design-space: Planning, Reasoning, Tool Use, Memory; дальше идут эволюция и рекомбинация модулей.

Если обобщить, все эти работы, хоть и с разной степенью свободы, двигают нас в сторону оптимизации работы агента как исполняемой программы. Где-то меняется граф шагов, где-то — код harness, где-то — модульный состав агента, где-то — стратегия использования инструментов.

4. Эксперимент: meta-harness optimization для Virtual DBA

Мы с командой попробовали meta-harness-оптимизацию в одном из разрабатываемых агентов для диагностики проблем с СУБД. У агента уже уже есть MCP-инструменты для отслеживания live PostgreSQL activity, waits, locks, settings, query fingerprints, read-only SQL, PWR reports. AI healthcheck использует эти инструменты для диагностического вердикта по текущему состоянию системы.

Ключевая техническая деталь — record/replay. Во время записи интересующей нагрузки сохраняются PostgreSQL-телеметрия, контекст системы, текущие инциденты, настройки и результаты MCP-тулов. Результаты тулов хранятся как JSONL по tool name + args/kwargs: replay сначала пытается сделать exact match, а для некоторых записей умеет применять явные операции вроде сортировки и обрезки уже записанного набора строк. За счёт этого разные версии harness можно прогонять по одному и тому же трейсу нагрузки с произвольной скоростью, ограниченной временем самого AI healthcheck.

В meta-harness loop кандидаты подключались прямо в runtime. Таким образом, без перезапуска сервиса мы могли проверять предлагаемых кандидатов посредством создания агентов на базе заранее заданного абстрактного интерфейса. Затем для каждого кандидата мы прогоняли бенчмарк и записывали полный трейс с вердиктом оценщика.

Общая схема Optimization loop
Общая схема Optimization loop

Тут менялись не веса модели, а параметры среды вокруг неё: промпты, MCP tool descriptions, порядок и состав этапов. Такое пространство поиска уже способно покрывать множество оптимальных конфигураций для агента, но при этом не превращать оптимизатор в генератор произвольного кода, что потребовало бы больших вычислительных затрат.

В эксперименте было два replay-кейса:

  • Первый проверял, видит ли агент missing-index / query-plan recovery в OLTP/OLAP-сценарии;

  • Второй проверял, как агент реагирует на паразитную нагрузку в рамках TPC-C, отлеживал ли этапы pressure/recovery, какие waits/vacuum/xmin evidence способен предоставить.

В каждом кейсе производилось порядка 10 AI healthcheck.

Результат получился достаточно интересным для маленького бенчмарка:

Run

Judge

Candidates

Baseline

Best

Gain

Первый

deterministic

10

0.478

0.597

+24.9%

Второй

LLM judge

10

0.591

0.695

+17.6%

Первый запуск был скорее smoke test: score вырос, но выигрыш почти целиком пришёл из одного кейса. Второй уже больше похож на рабочий optimization loop: лучший кандидат candidate_009 поднял score с 0.591 до 0.695, а почти такой же результат дал более простой candidate_0050.694.

Самое полезное здесь не само число, а найденные оптимизации.

candidate_005 оставил всего два этапа: recent_context и query_regression. При этом recent_context получил набор тулов, ориентированный на анализ запросов, чтобы проверять recovery через query fingerprints и plan evidence. Это резко улучшило OLTP/OLAP-кейс: агент начал лучше видеть, какие изменения происходят на уровне запросов и что текущая картина связана с index/plan evidence, а не с абстрактным shared_buffers.

candidate_009 добавил к этой схеме лёгкий waits_replication stage. Он стал лучшим по overall score: 0.695, без failed runs, с лучшим успешным OLTP/OLAP: score 0.633. Но преимущество над candidate_005 составило всего 0.001, поэтому с production-точки зрения candidate_005 может быть более консервативным выбором: чуть проще pipeline, почти тот же score, меньше surface для latency/context regressions.

Был и показательный negative result. candidate_007 дал recent_context сразу инструментарий в доменах query + vacuum + waits. TPC sleep стал лучшим в запуске: 0.897, но OLTP/OLAP упал до 0.300. То есть широкий выбор тулов помог одному типу задач и сломал другой. Это хороший аргумент за Pareto-анализ вместо выбора по одному среднему score: такой кандидат плох как новый baseline, но может быть полезен как специалист для wait/vacuum-heavy-сценариев.

В итоге Pareto-картина выглядела примерно так:

candidate_009:
    лучший raw score,
    лучший OLTP/OLAP,
    чуть сложнее.

candidate_005:
    почти тот же score,
    проще,
    хороший кандидат на baseline.

candidate_007:
    лучший TPC sleep,
    плохой OLTP/OLAP,
    полезный кандидат для будущего conditional routing.

Из эксперимента получились довольно практичные выводы.

Во-первых, full prompt replacement оказался разрушительным. Он терял базовый контракт: политики для использования тулов, следование формату вывода и правила для конечного синтеза. Промпт лучше менять как patch поверх устойчивого шаблона, а не заменять целиком.

Во-вторых, stage composition влияла сильнее, чем очередная формулировка в промпте. Перестановка stages и изменение MCP profiles меняли пространство поиска у агента, а вместе с ним и путь поиска причины, по которому он шёл к вердикту.

В-третьих, «больше тулов» не всегда хорошо. Широкий выбор тулов увеличивает шанс найти нужный evidence, но также расширяет пространство ложных причин. Для OLTP/OLAP recovery выбор специфичных query tools оказался лучше, чем смесь query/vacuum/waits в одном этапе.

В-четвёртых, метрика должна быть multi-objective. Нужны не только mean_score, но и failed_run_count, per-case floor, timeout penalty, complexity penalty, tool-call count, latency и process/evidence score.

Если переносить это на более общий язык, meta-harness-оптимизация выглядит как маленький генетический поиск по конфигурациям agent runtime. Кандидат — это «геном» из prompt patches, stages, MCP profiles, tool descriptions и разных policy. Mutation меняет один из этих элементов. Judge возвращает score и качественный фидбек. Selection должен смотреть не только на среднее качество, но и на Pareto frontier: качество, устойчивость, стоимость, сложность, per-case деградации.

Даже на двух replay-кейсах видно, что такой outer loop способен находить неочевидные улучшения harness. Пока бенчмарк небольшой, остаётся риск overfitting, нужны ablations и больше сценариев. Но как демонстрация направления результат показательный. Мы оптимизировали не модель и не один промпт, а форму среды, в которой агент собирает доказательства, выбирает тулы и строит диагностический вывод.