Skip to main content
Glama

groundtruth-mcp

ci pypi python license

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

Китайская документация · Руководство по внедрению · Архитектура · Почему фиксированные сиды


Проблема

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

Поэтому он делает выводы. Он меняет лимит повторов и говорит вам, что изменение безопасно, потому что «безопасно» было наиболее правдоподобным следующим токеном при диффе, который выглядел разумным. Никто ничего не запускал. Нарушенное ограничение находится в инварианте тремя файлами дальше или в распределении, которое никто не сэмплировал с момента последней настройки политики.

Решение — не улучшенный промпт. Решение — дать агенту возможность что-то наблюдать.

Related MCP server: MCP Software-Engineering RL Environment

Что это делает

flowchart LR
    E[Agent edits a config] --> L[lint]
    L -->|DANGLING_TRANSITION at states 1.transitions 0.to| E
    E --> R[replay seed=7]
    R -->|the 5 steps that actually ran| E
    E --> S[simulate 2000 seeds]
    S -->|88.3% success · p95 2566ms · PASS| E
    S --> G["CI: groundtruth simulate --gate"]
    G -->|same config, same thresholds| S

Пять инструментов, построенных на четырёх небольших функциях, которые вы пишете:

Инструмент

Отвечает

Свойство, которое делает его полезным

lint

Является ли эта конфигурация самосогласованной?

Каждая проблема несёт точный путь для правки

replay

Что произойдёт, если я запущу именно эту?

Чистая функция от (config, seed) — воспроизводима где угодно

simulate

Стало ли моё изменение лучше или хуже в целом?

Сидированный батч, распределение, пороги, прохождение/непрохождение

query

Что на самом деле в данных?

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

describe_data

Какие таблицы существуют?

Чтобы никому не пришлось угадывать схему

Те же возможности доступны через CLI, так что groundtruth simulate --gate — это шлюз слияния, читающий те же пороги, против которых агент оптимизирует. Они не могут разойтись, потому что существует только одна копия.

Шестьдесят секунд

pip install "groundtruth-mcp[mcp]"

git clone https://github.com/ZhenGtai123/groundtruth-mcp && cd groundtruth-mcp
groundtruth --config examples/checkout-flow/groundtruth.toml lint broken_checkout

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

broken_checkout: BLOCKED  errors=6 warnings=1 infos=0
source: flows\broken_checkout.json

-- ERRORS — these block (6) --
[DANGLING_TRANSITION] states[1].transitions[0].to  'payment_methd' does not name any states.id
    fix: point it at an existing state id, or delete the transition
[DEAD_END] states[6]  'review_hold' has no outgoing edge and is not marked terminal — a run that arrives here stops with no result
    fix: give it a transition, or mark it kind = "terminal" with an outcome
[DUPLICATE_STATE] states[2]  duplicate id='shipping' (first declared at states[1])
    fix: rename one of them; the engine silently uses the first and ignores the rest
[RATE_OUT_OF_RANGE] policy.gateway_failure_rate  1.4 is above the maximum 1.0
    fix: this is a probability, not a percentage — 0.18, not 18
[RETRY_BUDGET_TOO_THIN] policy.max_retries  140% gateway failure with 1 retries leaves 196.0% of checkouts failing on payment alone (budget: 2.0%)
    fix: raise max_retries, or lower gateway_failure_rate if the gateway improved
[UNKNOWN_STATE_KIND] states[3].kind  'stage' is not one of ['step', 'gateway', 'retry', 'terminal']
    fix: the engine only knows these four kinds; anything else is treated as a plain step

-- WARNINGS (1) --
[UNREACHABLE_STATE] states[4]  'gift_wrap' cannot be reached from 'cart_review'
    fix: no path from start reaches this state — delete it, or wire it in

Шесть из них берутся из файла правил. RETRY_BUDGET_TOO_THIN возникает из восьми строк Python, потому что «соответствует ли этот бюджет повторов целевому показателю отказов продукта» — это арифметика, а не схема.

Теперь посмотрите на один запуск:

groundtruth --config examples/checkout-flow/groundtruth.toml replay standard_checkout --seed 3
standard_checkout  seed=3  outcome=success  steps=7  fingerprint=52b66a2024a61b5d
metrics: latency_ms=2506  payment_attempts=2  steps=7

-- TRACE --
  0. cart_review --always-->
  1. shipping --always-->
  2. payment_method --always-->
  3. authorize --failure-->  # attempt 1 declined
  4. retry_decision --retries_left-->  # 0 retry(s) used of 2
  5. authorize --success-->  # attempt 2 authorized
  6. confirmed  # terminal: success

Сид 3 всегда порождает эти семь шагов — на вашей машине, в CI, в следующем году. Именно поэтому его стоит читать.

И две тысячи таких:

groundtruth --config examples/checkout-flow/groundtruth.toml \
  simulate standard_checkout --runs 2000 --seed 0 --gate --check-determinism
standard_checkout: PASS  runs=2000  base_seed=0  fingerprint=449e16b50c8184c0

-- OUTCOMES --
  success: 1767 (88.3%)
  abandoned: 227 (11.3%)
  payment_failed: 6 (0.3%)

-- METRICS (mean / p50 / p95 / max) --
  latency_ms: 1587.75 / 1553 / 2566 / 3626
  payment_attempts: 1.06 / 1 / 2 / 3
  steps: 5.12 / 5 / 7 / 10

-- THRESHOLDS --
  PASS  rate:success = 0.8835  expected >= 0.8  (below this, the flow is losing customers faster than the business case allows)
  PASS  rate:stuck = 0  expected <= 0  (a run with nowhere to go is always a config bug, never bad luck)
  PASS  p95:latency_ms = 2566  expected <= 4000  (95th-percentile checkout wall time, retries included)
  PASS  mean:payment_attempts = 1.0585  expected <= 1.6  (rising attempts mean the gateway is degrading or the retry policy is too eager)

note: determinism: 20 seeds re-ran identically

Часть, которая окупает себя

Поднимите одно число — shipping.abandon_chance с 0.05 до 0.28, правку такого рода, которая выглядит как продуктовое изменение и проходит ревью:

$ groundtruth lint standard_checkout
standard_checkout: OK  errors=0 warnings=0 infos=0     # exit 0

$ groundtruth simulate standard_checkout --runs 2000 --seed 0 --gate
standard_checkout: FAIL  runs=2000  base_seed=0  fingerprint=5a7c0d9feed5adca

-- OUTCOMES --
  success: 1336 (66.8%)
  abandoned: 660 (33.0%)

-- THRESHOLDS --
  FAIL  rate:success = 0.668  expected >= 0.8
  PASS  rate:stuck = 0  expected <= 0
  PASS  p95:latency_ms = 2549  expected <= 4000
  PASS  mean:payment_attempts = 0.795  expected <= 1.6
                                                       # exit 1

Структурно безупречно. Двадцать один пункт конверсии потерян. Ни схема, ни система типов, ни ревью кода этого не ловят; сидированный батч с объявленным диапазоном ловит это за четыре секунды, в пулл-реквесте, до того как человек прочитает дифф.

Это работает и в обратную сторону. express_checkout показывает более высокий процент успеха, чем стандартный поток — 91.0% — и при этом является худшим конфигом: его платёжные сбои составляют 3.9% против 0.3%, скрываясь внутри заголовочного числа, которое выглядит нормально. Агрегат это упускает; написанный вручную валидатор говорит об этом прямо:

[RETRY_BUDGET_TOO_THIN] policy.max_retries  18% gateway failure with 1 retries
leaves 3.2% of checkouts failing on payment alone (budget: 2.0%)

Ни один слой не поглощает другой. Вот почему их два.

Внедрение

Один модуль, один файл конфигурации. examples/checkout-flow/groundtruth_app.py — это весь шаблон, около сотни строк, включая комментарии.

from groundtruth_mcp import Context, Issue, Loaded, Toolkit, Trace

kit = Toolkit(name="my-project", subject_noun="pipeline")

@kit.loader
def load(name: str):
    path = CONFIG_DIR / f"{name}.yaml"
    if not path.is_file():
        return None                        # → "no pipeline named X; available: ..."
    return Loaded(subject=parse(path), source=str(path))

@kit.validator
def check(pipeline, ctx: Context) -> list[Issue]:
    ...                                    # the checks a rule file can't express

@kit.runner
def run_once(pipeline, seed: int, ctx: Context) -> Trace:
    ...                                    # one run, pure in (pipeline, seed)

Один только @kit.runner даёт вам и replay, и simulate — библиотека запускает его по одному разу на каждый сид и сохраняет результат. Всё остальное (пакетирование сидов, агрегация, процентили, проверка порогов, бюджетирование вывода, формулировка ошибок, MCP-поверхность) поставляется пакетом.

# groundtruth.toml
[project]
toolkit = "groundtruth_app:kit"

[lint]
rules = "rules.toml"

[[thresholds]]
metric = "rate:success"
min = 0.80
note = "why this number, for whoever has to change it"

Затем groundtruth doctor сообщает, что подключено, groundtruth serve передаёт инструменты агенту, а groundtruth simulate --gate блокирует слияние. Полное пошаговое руководство с примерами по каждой предметной области: docs/ADOPTION.md.

Правила, которые вы получаете бесплатно

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

Тип

Ловит

Ключевые поля

required_fields

недописанные записи

select, fields

unique_key

дублирующиеся идентификаторы, которые движок молча затеняет

select, key

enum

значение, которое ваш движок не обрабатывает

select, values

type

строка там, где должно быть число

select, expect

range

1.4 в поле, которое является вероятностью

select, min, max

pattern

идентификаторы, нарушающие контракт именования

select, regex

not_empty

пустой список там, где требуется одна запись

select

ref_exists

ссылка на то, что было переименовано

select, collection, key

reachable

узел, до которого не ведёт ни один путь от начала

collection, key, edges, start

no_dead_end

нетерминальный узел без выхода

collection, key, edges, terminal_field

no_self_loop

узел, который переходит сам в себя

collection, key, edges

no_cycle

кольцо без выхода (со списком allow для намеренных)

collection, key, edges

Селекторы — это намеренно небольшой язык путей — states[].transitions[].to — и каждое совпадение сообщает конкретный путь, по которому оно найдено, что делает возможным states[3].transitions[1].to вместо «переход недействителен».

Каждое правило принимает необязательные code, severity и hint. Подсказка (hint) — это предложение, на которое действует агент, поэтому пишите его в повелительном наклонении.

Только чтение означает только чтение

query выполняет один SELECT. Это обеспечивают два уровня, и они не равны.

Сканирование ключевых слов — это пользовательский опыт: оно отклоняет DELETE FROM … предложением с объяснением, вместо ошибки базы данных, которую модели приходится расшифровывать. Это не граница — чёрный список по тексту всегда в одном случае от ошибки, и каноническая демонстрация — SELECT * INTO audit_copy FROM users, который начинается с SELECT, не содержит запрещённых глаголов и создаёт таблицу.

Граница — это хранилище данных: mode=ro плюс PRAGMA query_only в SQLite, транзакция READ ONLY в PostgreSQL, таймаут запроса в обоих. Тесты полностью обходят защиту и подтверждают, что соединение по-прежнему отказывает.

Маскирование столбцов — это единственный контроль на текстовом уровне, который является принудительным: значения в deny_columns отбрасываются после выборки и до того, как создаётся результирующая строка, поэтому SELECT * не может их утечь. Всё возвращаемое оборачивается в теги <untrusted>, потому что столбец notes, содержащий нечто похожее на инструкцию, — это данные, и он должен прийти помеченным как данные.

CLI

groundtruth [--config PATH] <command>

  doctor                     what is wired up, what is missing
  targets                    the configs this project exposes
  lint TARGET                exit 1 on errors
  replay TARGET --seed N     one deterministic run, full trace
  simulate TARGET            --runs N --seed N --gate --check-determinism
  query "SELECT ..."         one read-only statement
  schema                     readable tables and columns
  serve                      the MCP server, over stdio

Коды выхода: 0 — чисто, 1 — найдены проблемы (ошибки линтера, порог вне своего диапазона, недетерминизм), 2 — не удалось запустить (плохой конфиг, отсутствующая возможность, отклонённый запрос). Добавьте --json к lint, replay и simulate для машиночитаемого вывода.

Установка

pip install groundtruth-mcp          # core: rules, simulation, gating, CLI
pip install "groundtruth-mcp[mcp]"   # + the MCP server
pip install "groundtruth-mcp[postgres]"  # + the PostgreSQL data source

Python 3.11+. Ядро не имеет сторонних зависимостей — это сделано намеренно, чтобы CI-шлюз не зависел от стека агента. Голый раннер может обеспечивать соблюдение ваших порогов без установки SDK.

Проверка подключения

python scripts/mcp_smoke.py [path/to/groundtruth.toml]

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

Что CI проверяет в каждом пулл-реквесте

Не значок, означающий «тесты прошли», — шесть вещей, каждая из которых однажды что-то заблокировала:

Проверка

Почему это шлюз, а не предложение

ruff check + ruff format --check

Включая BLE, поэтому каждый широкий except содержит письменное обоснование

mypy

Пакет поставляется с py.typed; неверная аннотация — это неверный API

pytest на 3.11 / 3.12 / 3.13

69 тестов, нижняя граница покрытия 75% (сейчас 78% с покрытием ветвлений)

scripts/mcp_smoke.py

Настоящий подпроцесс, настоящий stdio, настоящие tools/list и tools/call

simulate --gate --check-determinism

Собственный аргумент проекта, применённый к самому себе

lint broken_checkout должен завершиться с кодом 1

Линтер, который не может упасть, декоративен

Ограничения, изложенные прямо

  • Белый список таблиц SQL является текстовым. Он сканирует идентификаторы после FROM и JOIN. Настоящее обеспечение по таблицам — это грант базы данных; это защитное ограждение с хорошим сообщением об ошибке, а удерживает на самом деле транзакция только для чтения.

  • Чёрный список ключевых слов срабатывает внутри строковых литералов. Запрос, фильтрующий по значению, содержащему grant, будет отклонён. Исправление требует настоящего SQL-парсера, который не стоит строить, когда парсер не является границей.

  • Авто-LIMIT — это эвристика. LIMIT внутри подзапроса подавляет добавление на верхнем уровне. max_rows по-прежнему ограничивает то, что выводится.

  • Селекторы не фильтруют. states[].transitions[] обходит всё; нет states[kind=terminal]. Язык предикатов был бы третьей функцией, которую никто не просил. Вместо этого напишите @kit.validator.

  • Пороги действуют на весь проект, а не на каждую цель. Каждая цель в проекте оценивается по одним и тем же диапазонам. Проекты, чьи конфиги требуют действительно разных диапазонов, должны использовать отдельные файлы groundtruth.toml.

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

Откуда это взялось

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

  • Единый список пороговых значений, читаемый и агентом, и CI, потому что две копии рассинхронизировались, и инструмент какое-то время выдавал PASS для чисел, которые CI отверг бы.

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

  • Описания инструментов, формируемые из текущей конфигурации, потому что устаревшее описание — это инструмент, которым агент пользуется уверенно, но неправильно.

  • Ограничение объёма вывода на каждом пути, потому что один слишком активный запрос может вытеснить остальную часть разговора.

docs/ARCHITECTURE.md содержит карту модулей и полное обоснование.

Лицензия

MIT.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search code by meaning, explore codebase structure, store and query knowledge with temporal facts, and read source code through a set of MCP tools.
    310 npm
    7
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents to perform file, search, patch, git, process, test, package, network, and system operations through 60 typed MCP tools with structured inputs/outputs, structured errors, and a full event journal, replacing terminal use with a typed machine API.
    MIT