pdml-agent
pdml-agent
MCP-сервер и агент вызова инструментов для экспериментального конвейера property-driven-ml с шлюзом с участием человека для всего, что потребляет вычисления, и структурированным трейсом каждого вызова.
Property-driven ML обучает классификаторы на основе ограничений формальной логики, поэтому запуск определяется ограничением, набором данных, дифференцируемой логикой и seed, и выдает метрики по эпохам как для прогностической производительности, так и для безопасности ограничений. Это делает его действительно инструментально-ориентированной областью, а не демонстрационной: можно перечислить эксперименты, восстановить конфиги, прочитать результаты, сравнить запуски, а также спланировать, утвердить и выполнить новые запуски.
Статус: завершено в рамках заявленного объема. Сервер, агент, шлюз, трассировка. Реальное выполнение продемонстрировано на CPU.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ agent.py (Anthropic SDK tool runner) │
│ │
│ claude-opus-5 ──► pending tool_use ──► ToolLedger.wrap │
│ ▲ │ memoise (RO) │
│ │ │ gate (compute)│
│ │ tool_result │ trace (JSONL) │
│ └───────────────────────────────────┘ │ │
└─────────────────────────────┬───────────────────────┼────────┘
MCP over stdio ▼
┌─────────────────────────────┴────────────────┐ traces/*.jsonl
│ server.py (mcp MCPServer, thin) │
│ list_experiments get_experiment_config │
│ get_results compare_runs │
│ search_logic_definitions │
│ run_experiment ──► PDML_ALLOW_EXECUTE=1 ? │
└────┬───────────┬──────────────┬──────────────┘
▼ ▼ ▼
experiments.py logic_defs.py runner.py ──► subprocess: main.py
(read CSVs) (parse source) (plan/execute) in property-driven-mlagent.py ничего не знает о предметной области. Он подключается к серверу через stdio, как и любой другой MCP-клиент, и работает только с инструментами, которые предоставляет сервер. Модули предметной области не имеют зависимости от MCP и тестируются импортом. server.py только регистрирует инструменты и делегирует.
Layout
pdml_agent/
experiments.py reading and comparing runs
logic_defs.py searching the logic implementations
runner.py validating, planning and executing runs
server.py the MCP layer, deliberately thin
agent.py the agent: runner, gate, memoisation, tracing
scripts/
make_fixtures.py generate sample runs
smoke_test.py start the server, exercise every tool, check refusals
demo.py run the agent on five tasks
fixtures/results/ sample runs, so nothing needs a GPU to demo
demo_output/ what the agent said and did, one JSON per task
traces/ one JSONL per run, every turn and every callTools
Инструмент | Возвращает |
| запуски, фильтруемые по ограничению, набору данных или логике |
| конфиг, с которым фактически обучался запуск |
| метрики для одной эпохи, по умолчанию последняя |
| diff конфигов и метрик между двумя запусками |
| классы логик, их операторы и docstrings |
| с |
The gate
run_experiment — единственный инструмент, потребляющий вычисления, и перед ним стоят две независимые вещи.
Сервер не будет выполнять, если он не был запущен с PDML_ALLOW_EXECUTE=1. Это решение принимает тот, кто запускает сервер, и никакой запрос не может его изменить. Без него dry_run=false возвращает status: refused с приложенным планом, и это не ошибка.
Агент не отправит запрос на выполнение без одобрения оператором точного вызова. Запрос на одобрение показывает имя инструмента и полные аргументы в JSON, а не сводку. Отказ возвращает обычный результат со значением declined_by_operator, и модели предписано сообщить об этом и остановиться, а не повторять попытку.
Любой из слоев по отдельности остановил бы нежелательный запуск. Вместе это означает, что ни один из них не должен быть идеальным. Политика, определяющая, что требует одобрения, — это одна функция needs_approval, достаточно маленькая, чтобы прочитать ее с одного взгляда.
The trace
Каждый запуск добавляет данные в traces/<timestamp>-<question>.jsonl. Одна строка на событие, никогда не перезаписывается.
Записи turn содержат номер шага, причину остановки модели, ее текст и сводку размышлений, вызовы, которые она собирается сделать, и использование токенов на этом шаге. Записи tool_call содержат инструмент, его аргументы, успешность вызова, был ли он из кэша или через шлюз, задержку, сводку результата и собственную указанную причину модели, взятую из предложения, которое она написала рядом с вызовом. Записи gate содержат решение. run_start и run_end обрамляют их с итогами.
Системный промпт просит модель указать одним предложением, почему она делает каждый вызов, и она это делает. Из трейса пути отказа:
turn 1 "I'll start by finding the existing YG runs to confirm identifiers."
turn 2 "No results with those filters; let me broaden."
turn 3 "The constraint is named `standard-robustness`. Let me get the seed-0 run's config and results."
turn 4 "Now the dry-run plan for the requested run (matching epsilon 0.3 from the seed-0 baseline)."
turn 5 "Plan validated. Now executing it." ← gate: declined
turn 6 "The training run was not executed: the operator declined ..."Этот трейс также выявил дефект в собственных инструментах этого репозитория. Шаг 1 получил пустой результат, потому что list_experiments фильтровал по имени папки результатов, а run_experiment принимал имя класса — два словаря для одного понятия. Модель восстановилась сама, ценой одного шага, и ее причина на шаге 3 говорит именно то, что она выяснила. list_experiments теперь принимает оба написания.
What the demos showed
Пять задач, ни одна из которых не решается одним вызовом. Полные транскрипты в demo_output/, полные трейсы в traces/.
A. Лучшая логика в рамках бюджета точности. Три шага. Перечислил запуски, получил все четыре результата за один параллельный шаг, ответил YG с безопасностью 0.9981 за 0.76 пунктов точности и сказал, что ничего не выполнялось.
B. Планирование варианта существующего запуска. Четыре шага. Получил конфиг, сравнение и определение логики за один параллельный шаг, вызвал run_experiment с dry_run=true, сообщил план и точную команду, и, поскольку существовал соответствующий запуск, сравнил их.
C. Сравнение с несуществующим запуском. Три шага. Сначала перечислил, а не угадал, подтвердил, что STL — это реальная логика, у которой просто нет запуска, и сообщил об этом.
D. Обучение, оператор отклоняет. Шесть шагов. Сначала спланировал с помощью сухого запуска, как просит описание инструмента, затем запросил выполнение. Утверждающий отклонил. Модель сообщила, что выполнение не было произведено, и не повторила попытку, предоставила план и ответила тем, что существовало.
E. Обучение, оператор одобряет. Шесть шагов и реальный обучающий запуск. Та же последовательность: план-затем-выполнение; утверждающий принял; сервер, запущенный с разрешенным выполнением, запустил main.py на одну эпоху на CPU за 28.6 секунд и записал fixtures/results/standard-robustness/mnist/1/YG.csv. Затем агент вызвал get_results и compare_runs для нового запуска и сообщил итоговые Test-P-Metric 0.9160 и Test-C-Sec-self 0.5482. Оба совпадают с CSV. Без запроса он перечислил confounding факторы по сравнению с seed-0 (одна эпоха против десяти, задержка, намеренно ослабленный бюджет атаки) и отметил из строки эпохи-0, что безопасность ограничений тривиально равна 1.0 на необученной модели и имеет смысл только вместе со сходимой точностью. Это правильное прочтение метрики.
Этот CSV seed-1 — реальный запуск и намеренно хранится рядом с синтетическими fixtures. Его первая строка — это argv, с которым он был обучен, как и у любого другого запуска.
Two things worth knowing about the data
Эпоха 0 — это оценка до обучения. Запуск, настроенный с --epochs 10, записывает одиннадцать строк с номерами от 0 до 10. Количество строк и финальная эпоха сообщаются отдельно, потому что называть количество строк «эпохами» завышает обучение на единицу.
Скрипт обучения пишет -1 для метрик, которые он не оценивал. get_results нормализует их в null, поэтому sentinel не может быть прочитан как измерение. Базовый запуск вообще не имеет метрик ограничений, и он должен так и говорить, а не сообщать минус единицу.
Limits, stated so they are not overclaimed
Модель ни разу не столкнулась с результатом инструмента is_error вживую за пять задач, потому что следовала инструкции перечислять, прежде чем доверять идентификатору. Путь ошибки тестируется на уровне протокола в smoke_test.py и на уровне обертки, но восстановление вживую после ошибки инструмента в середине задачи не было продемонстрировано.
Мемоизация ни разу не сработала вживую. Модель не повторяла идентичный вызов ни в одном запуске. Она протестирована модульно и бездействует в каждом трейсе.
Кэширование промптов не настроено. cache_read_input_tokens равен нулю в каждом трейсе, а количество входных токенов (от 11k до 46k на задачу) в основном является повторно отправленным контекстом. Точки останова кэша на определениях инструментов и системном промпте значительно сократили бы это и являются очевидным следующим улучшением.
Для выполнения запуска требовался checkout, чей main.py парсится. В upstream main это не так: --epsilon и --delta определены дважды, и argparse отклоняет дубликат до чтения любого аргумента, поэтому python main.py --help не работает. Это исправлено в ветке fix/duplicate-argparse-flags форка с регрессионным тестом, и демо указывало PDML_REPO_DIR на этот checkout.
Try it
uv sync
uv run python scripts/make_fixtures.py
uv run python scripts/smoke_test.pyДымовой тест запускает сервер через stdio, перечисляет инструменты, вызывает каждый, проверяет, что выполнение без PDML_ALLOW_EXECUTE отклоняется, и проверяет, что неизвестный идентификатор эксперимента вызывает ошибку, а не молча успешно выполняется. Это ничего не стоит.
uv run python -m pdml_agent.agent "Which mnist run has the best constraint security?"
uv run python scripts/demo.py A B C DЧтобы задать что-то агенту, с установленным ANTHROPIC_API_KEY:
export PDML_REPO_DIR=~/property-driven-ml
export PDML_PYTHON=~/property-driven-ml/.venv/bin/python
uv run python -m pdml_agent.agent --allow-execute "Train a one-epoch YG run on mnist at seed 2 ..."
uv run python scripts/demo.py EЧтобы позволить ему действительно обучать, укажите ему на checkout репозитория property-driven-ml, чей main.py парсится, и на интерпретатор с torch, затем передайте флаг, включающий выполнение:
Вам будет показан точный вызов и предложено его одобрить.
Переменные среды, которые читает сервер: PDML_RESULTS_DIR (где находятся запуски, по умолчанию fixtures/results), PDML_REPO_DIR (checkout репозитория property-driven-ml), PDML_PYTHON (интерпретатор для main.py, иначе .venv репозитория), PDML_ALLOW_EXECUTE (1 для разрешения выполнения), PDML_EXECUTE_TIMEOUT (секунды, по умолчанию 3600).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.
MCP server for generating rough-draft project plans from natural-language prompts.
The MCP server for Azure DevOps, bringing the power of Azure DevOps directly to your agents.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/HappyHackingOrange/pdml-agent'
If you have feedback or need assistance with the MCP directory API, please join our Discord server